Real Life与SOLID开发合作

Gal*_*iss 5 .net architecture asp.net-mvc dependency-injection solid-principles

最近我学习了SOLID开发,现在我遇到了一些挑战,当它是一个好的练习时,什么时候不是.

例如,我开发了一个包含成员的网站.我构建了一个Authentication Business Logic类,它可以解决身份验证方案,它们是:

  1. 登录用户
  2. 登录用户
  3. 注册用户
  4. 恢复用户密码

这个类有4个依赖项:

  1. DB /服务
  2. HttpContext(用于用户状态)
  3. ValidationRules(一些其他业务逻辑规则,以便登录用户)
  4. SMTP - 用于发送

现在感觉有些代码有难闻的气味,因为没有使用依赖注入.

我不确定的一些问题:

  1. 我可以直接将SMTP依赖注入到Restore password和register方法,因为所有其他方法都与它无关.我是不是该?
  2. Logout等一些方法没有使用DB,当我知道不需要使用它时,为什么要启动db依赖项.
  3. 这是我的一些业务逻辑.在我的控制器中,这个问题要大得多,因为我需要加载多个BL并且我的所有业务类都有相同的难闻气味......

我觉得我做错了什么,请帮忙!

Ste*_*ven 14

一个身份验证业务逻辑类,它应该解决身份验证方案,包括:登录用户,登录用户,注册用户,恢复用户密码.

您的身份验证业务逻辑类已违反单一责任原则.虽然您可能认为该类的唯一责任是"身份验证",但这很难被称为一种责任,因为您可以实现一百个处理身份验证的用例,从而导致一个丑陋的大屁股类.那个班级还有一个责任吗?相反,每个类实现一个用例.在这种情况下,你的课程将有一个非常明确和狭隘的责任.

HttpContext(用于用户状态)

你的业务逻辑不应该对所使用的技术有任何了解(除了它当然是.NET,我们不能真正抽象.NET),因此它不应该依赖于HttpContext.取决于HttpContext违反依赖性倒置原则(DIP)和接口隔离原则(ISP).

DIP说"高级模块应该......取决于抽象." 您的业​​务层是"更高级别的模块",但它不依赖于抽象,而是依赖于较低级别的模块(HttpContext).这将业务层类与实际的Web逻辑紧密结合在一起.

此外,即使您将使用System.Web.HttpContextBase抽象,您仍将依赖于由较低级别模块定义的抽象,而根据DIP"抽象由上层/策略层拥有".这背后的想法是你不希望你的代码强烈依赖于较低层,因为这会使更高层的层更依赖于此,除此之外,较低层的层无法定义一个抽象.纠正你的申请.接下来将表达此问题.

ISP声明"不应该强迫任何客户端依赖它不使用的方法".换句话说,抽象应该根据客户的特殊需求而定制,并且应该是狭窄的."这种缩小的接口也称为角色接口".无论HttpContextHttpContextBase违反ISP,因为他们是很广,意味着用于一般用途.他们是一个大的国家坏,一切都是一个字符串.这会导致您的代码使用非常广泛的API,这使得该API更难使用.它也阻碍了可测试性,因为它不可能伪造HttpContext甚至抽象HttpContextBase是假的不愉快.如果您应用ISP,您的生产代码和测试代码将变得更加清洁.它还会阻碍应用程序的可重用性,因为您可能希望稍后在后台进程中运行业务逻辑的某些部分,而不会出现Web请求的概念.它会阻碍灵活性,因为有时您可能想要拦截或修饰您在其上执行的某些操作HttpContext.例如,您可能希望在从HttpContext请求用户详细信息时记录.但是,当您的代码直接依赖于HttpContext时,这意味着您必须在整个代码库中进行彻底的更改.必须在整个代码中进行全面更改,这表明您违反了开放/封闭原则.

相反,IUserContext在BL中定义自己的抽象,并在Web应用程序中创建一个AspNetUserContext实现(实现IUserContext).请注意,您不应该定义IHttpContext抽象,因为这仍然会违反SRP和ISP,并且基本上是漏洞抽象(漏洞抽象是DIP违规),因为业务层不应该知道是否存在网络请求.

我可以直接注入SMTP依赖

执行此操作时,您将再次违反依赖性倒置原则.您的业​​务层不依赖于抽象,而是依赖于较低级别的模块(您的SMTP类).这将业务层类与实际的SMTP逻辑紧密结合在一起.相反,你应该为此定义自己的抽象,例如IMailSender.

这可能听起来很奇怪,您可能会觉得永远不会改变邮件的发送方式,因为电子邮件是唯一的方法.但即使电子邮件多年来一直存在,您也可能希望更改电子邮件的处理方式,因为在当前的实现中,电子邮件不会随业务事务原子发送.这意味着您可能已将消息推送到SMTP服务器(无法回滚的操作),但此后总操作仍可能失败.当它失败时,意味着回滚打开的数据库事务,但此时仍然发送邮件.您无法回拨,最后您将再次发送该邮件.

要缓解这种情况,您必须将邮件消息写入事务性队列(例如,包含应发送消息的数据库表).此操作应在与业务逻辑运行相同的事务中运行.这意味着您可以在同一事务中对多个消息进行排队,并且在操作失败时它们都会回滚.当操作成功时,所有消息都可用,而其他一些(后台)进程可以接收并发送它们.

这只是一种可能的方式.发送邮件的方式将来可能会发生变化,但如果没有IMailSender抽象,这将很难修复.

Logout等一些方法没有使用DB,当我知道不需要使用它时,为什么要启动db依赖项.

通常,当依赖关系并不总是被使用时,这不是问题,因为创建对象图(具有所有直接和间接依赖性的服务)应该非常快.但在你的情况下,你所看到的是你的方法不是很有凝聚力.这表明单一责任违规.相反,你应该给Logout操作它自己的类.由于此操作不需要db,因此您不需要db依赖项.

在我的控制器中,这个问题要大得多,因为我需要加载多个BL,并且我的所有业务类都有相同的难闻气味.

您的控制器也可能违反单一责任原则.SRP违规的一个很好的指示是一个类具有的依赖数.启发式是4或5.有超过5个依赖项,你的班级很可能有太多的责任.

当您按照SOLID进行操作时,您将获得许多小型和专注的课程.这看起来好像很多工作和代码,但实际上这会导致系统更容易维护.如果您在如何创建业务逻辑方面苦苦挣扎,请查看本文.