所以,我在这里看了一些关于规范模式的帖子,但还没有找到这个问题的答案。
我的问题是,在n层架构中,我的规范应该在哪里"更新"?
-
我可以把它们放在我的服务层(又名,应用层,有时被称为…基本上,这是.aspx后台代码可以与之对话的东西),但我觉得这样做会让业务规则泄露出域。如果以其他方式访问域对象(除了服务层),则域对象不能强制执行自己的业务规则。
-
我可以通过构造函数注入将Specification注入到我的Model类中。但同样,这感觉"不对"。我觉得唯一应该注入Model类的是"服务",比如缓存、日志记录、脏旗跟踪等等。如果可以避免的话,可以使用aspect,而不是用大量的服务接口来乱扔Model类的构造函数。
-
我可以通过方法注入注入规范(有时称为"双重调度"),并显式地让该方法封装注入的规范以强制执行其业务规则。
-
创建一个"域服务"类,它将通过构造函数注入获取一个规范,然后让服务层使用域服务来协调域对象。这对我来说似乎没问题,因为规范所执行的规则仍然在"域"中,并且域服务类可以像它所协调的域对象一样命名。这里的事情是,我觉得我正在编写大量的类和代码,只是为了"正确"实现规范模式。
添加到此,所讨论的规范需要一个存储库来确定它是否"满意"。
这可能会导致性能问题,特别是如果我使用构造函数注入,b/c消费代码可能会调用一个属性,这个属性可能包装了规范,而这反过来又调用了数据库。
有什么想法/想法/文章链接吗?
哪里是更新和使用规格的最佳地点?
短答:
你主要在你的服务层使用规范,所以在那里。
长答:首先,这里有两个问题:
你的规格应该放在哪里,它们应该在哪里更新?
就像您的存储库接口一样,您的规范应该位于域层,因为它们毕竟是特定于域的。有一个关于存储库接口的问题。
他们应该在哪里新起来呢?嗯,我在我的存储库中使用LinqSpecs,并且在我的存储库中通常有三个方法:
public interface ILinqSpecsRepository<T>
{
IEnumerable<T> FindAll(Specification<T> specification);
IEnumerable<T> FindAll<TRelated>(Specification<T> specification, Expression<Func<T, TRelated>> fetchExpression);
T FindOne(Specification<T> specification);
}
其余的查询都是在服务层中构造的。这可以防止存储库变得臃肿,如GetUserByEmail, GetUserById, GetUserByStatus等方法。在我的服务中,我更新了规范并将它们传递给存储库的FindAll或FindOne方法。例如:
public User GetUserByEmail(string email)
{
var withEmail = new UserByEmail(email); // the specification
return userRepository.FindOne(withEmail);
}
规格如下:
public class UserByEmail : Specification<User>
{
private readonly string email;
public UserByEmail(string email)
{
this.email = email;
}
#region Overrides of Specification<User>
public override Expression<Func<User, bool>> IsSatisfiedBy()
{
return x => x.Email == email;
}
#endregion
}
我来回答你的问题,服务层的规范是新的(在我的书中)。
我觉得唯一应该注入模型类的东西"服务"
在我看来,你不应该向域实体中注入任何东西。
添加到此,所讨论的规范需要一个存储库以确定它是否"满意"。
那是代码的味道。我会在那里检查你的代码。规范绝对不应该需要存储库。
规范是对业务规则的实现检查。它必须存在于域层中。
很难具体说明如何做到这一点,因为每个代码库都是不同的,但在我看来,任何业务逻辑都需要在领域层,而不是其他地方。此业务逻辑需要完全可测试,并与UI、数据库、外部服务和其他非域依赖关系松散耦合。所以我肯定会排除上面的1、2和3。
4是一个选项,至少该规范将存在于您的域层中。然而,规范的更新实际上又取决于实现。我们通常使用依赖注入,因此几乎所有对象的更新都是通过IOC容器和相应的引导代码执行的(也就是说,我们通常将应用程序流畅地连接起来)。然而,我们永远不会直接将业务逻辑直接链接到UI模型类等。我们通常在UI和领域等事物之间有轮廓/边界。我们通常定义域服务契约,然后可以由外部层(如UI等)使用。
最后,我的答案是假设你正在研究的系统至少在某种程度上是复杂的。如果它是一个非常简单的系统,那么领域驱动设计作为一个概念可能就太夸张了。然而,在我看来,无论代码库如何,都应该尊重一些概念,如可测试性、可读性、SoC等。