16 architecture design-patterns domain-driven-design
只是想知道其他人如何分层他们的架构.说我有我的图层如下:
领域层
-产品
--ProductService(如若小鬼进入这一层?)
--IProductService
--IProductRepository
Infrastructure Layer
--ProductRepository(我域中IProductRepository的Imp)
现在,当创建新产品时,我需要通过调用ProductService.GetNextProductId()方法来分配产品ID.
由于服务依赖于存储库,因此我使用IProductRepository接口设置ProductService ctor,该接口可以在以后注入.这样的事情:
public class ProductService : IProductService
{
private IProductRepository _repository;
public ProductService(IProductRepository repository)
{
_repository = repository;
}
public long GetNextProductId()
{
return _repository.GetNextProductId();
}
}
Run Code Online (Sandbox Code Playgroud)
我的问题是,当我在Product Class中使用该服务时,我在实例化一个新的ProductService类时引用了ctor中的Repository.在DDD中,有一个很大的不,没有这样的参考.我甚至不确定我的产品域类是否正确设置以调用该服务,有人可以建议:
public class Product : Entity
{
private ProductService _svc;
private IProductRepository _repository;
public Product(string name, Address address) //It doesnt seem right to put parm for IProductRepository in the ctor?
: base(_svc.GetNextProductId) // This is where i pass the id
{
// where to create an instance of IProductRepository?
}
}
Run Code Online (Sandbox Code Playgroud)
我怎样才能优雅地解决这个设计问题?我愿意接受经验丰富的DDD的建议
编辑:
谢谢你的评论.我还怀疑是否应该从产品类调用该服务.我还没有使用工厂模式,因为对象的构造仍然很简单.我觉得它不保证工厂方法吗?
我很困惑......如果我的Product类需要来自服务的一些其他数据,例如GetSystemDateTime()(我知道,这是一个不好的例子,但试图演示非db调用),那么将ProductId放在一边这里会调用这个服务方法吗?
DDD中的服务是逻辑转储,其中逻辑对域对象不是天真的,对吧?那怎么粘在一起呢?
Tor*_*ups 15
到目前为止,DDD中的服务是将我描述为"笨拙"逻辑的地方.如果您有某种类型的逻辑或工作流程依赖于其他实体,那么这种逻辑类型通常不适合在域对象本身内部.示例:如果我的业务对象上有一个方法来执行某种类型的验证,那么服务类可能会执行此方法(仍然保持与其类中的实体相关的实际验证逻辑)
我经常提到的另一个非常好的例子是资金转账方法.您不会将帐户对象从一个对象转移到另一个对象,而是您将拥有一个带有"到"帐户和"来自"帐户的服务.然后在服务中,您将在"来自"帐户和"到"帐户上的存款方式中调用提款方式.如果你试图将它放在帐户实体本身内,那就会感觉很尴尬.
可以在这里找到一个很好的播客,深入讨论这个话题.David Laribee做得非常好,现在只解释DDD的"如何"和"为什么".
gro*_*ver 10
您的域模型不应该引用ProductService或IProductRepository.如果您创建新产品,则必须通过工厂创建 - Factory可以使用ProductService获取产品ID.
事实上,我将使用适当的接口(例如IProductIdGeneratorService)包装ProductService,以便您可以使用IoC容器将其注入工厂.
这是我将如何构建您的问题。我相信这也是推荐的 DDD 方式。
public class ProductService : IProductService // Application Service class, used by outside components like UI, WCF, HTTP Services, other Bounded Contexts, etc.
{
private readonly IProductRepository _prodRepository;
private readonly IStoreRepository _storeRepository;
public ProductService(IProductRepository prodRepository, IStoreRepository storeRepository) // Injected dependencies DI
{
if(prodRepository == null) throw new NullArgumentException("Prod Repo is required."); // guard
if(storeRepository == null) throw new NullArgumentException("Store Repo is required."); // guard
_prodRepository = prodRepository;
_storeRepository = storeRepository;
}
public void AddProductToStore(string name, Address address, StoreId storeId) //An exposed API method related to Product that is a part of your Application Service. Address and StoreId are value objects.
{
Store store = _storeRepository.GetBy(storeId);
IProductIdGenerator productIdGenerator = new ProductIdGenerator(_prodRepository);
Product product = Product.MakeNew(name, address, productIdGenerator);
}
... // Rest of API
}
public class Product : Entity
{
public static MakeNew(string name, Address address, IProductIdGenerator productIdGenerator) // Factory to make construction behaviour more explicit
{
return new Product(name, address, productIdGenerator);
}
protected Product(string name, Address address, IProductIdGenerator productIdGenerator)
: base(productIdGenerator.GetNextProductId())
{
Name = name;
Address = address;
}
... // Rest of Product methods, properties and fields
}
public class ProductIdGenerator : IProductIdGenerator
{
private IProductRepository _repository;
public ProductIdGenerator(IProductRepository repository)
{
_repository = repository;
}
public long GetNextProductId()
{
return _repository.GetNextProductId();
}
}
public interface IProductIdGenerator
{
long GetNextProductId();
}
Run Code Online (Sandbox Code Playgroud)
基本上,ProductService 是应用程序服务的一部分,即需要使用您的域或跨越其边界的所有内容的入口和出口点。它负责将每个用例委托给可以处理它的适当组件,并在所有这些组件之间进行协调(如果需要多个组件来满足用例)。
产品是您的 AggregateRoot 和域中的实体。它负责规定捕获企业域的 UbiquitousLanguage 合同。所以,就其本身而言,这意味着你的领域有一个产品的概念,其中包含数据和行为,无论你公开公开的数据和行为都必须是UbiquitousLanguage的概念。它的字段不应该具有域模型之外的外部依赖关系,因此没有服务。但是,它的方法可以将领域服务作为参数来帮助它执行行为逻辑。
ProductIdGenerator 就是此类域服务的一个示例。领域服务封装了跨越实体自身边界的行为逻辑。因此,如果您有需要其他聚合根或外部服务(例如存储库、文件系统、密码学等)的逻辑。基本上,任何您无法从实体内部执行的逻辑,而不需要其他任何东西,您可能需要域服务。如果逻辑是包容性的,并且在概念上看起来可能并不真正属于实体上的方法,那么这表明您可能需要一个全新的应用程序服务用例来满足它,或者您在设计中错过了实体。还可以以非双重调度的方式直接从应用程序服务使用域服务。这有点类似于 C# 扩展方法与普通静态方法。
=========== 回答您的编辑问题===============
我还怀疑是否应该从产品类中调用该服务。
如果领域服务作为临时引用通过方法参数传递,则可以从产品类调用它们。决不应该从 Product 类中调用应用程序服务。
我还没有使用工厂模式,因为对象的构造仍然很简单。我觉得还没有必要使用工厂方法吗?
这取决于你期望什么会花费你更多的时间,现在创建一个工厂,即使你没有多个构造逻辑,或者稍后当你这样做时进行重构。我认为对于不需要以多种方式构建的实体来说这是不值得的。正如维基百科所解释的,工厂用于使每个构造函数所做的事情更加明确和可区分。在我的示例中,MakeNew 工厂解释了实体的这种特定构造的用途:创建新产品。您可以拥有更多工厂,例如 MakeExisting、MakeSample、MakeDeprecated 等。每个工厂都会创建一个产品,但用途不同,方式也略有不同。如果没有工厂,所有这些构造函数都将被命名为 Product(),并且很难知道哪个构造函数用于什么以及做什么。缺点是当你扩展实体时,Factory 很难使用,子实体不能使用父 Factory 来创建子实体,这就是为什么我倾向于在构造函数中完成所有构造逻辑,并且只使用 Factory给他们起一个漂亮的名字。
我很困惑...如果我的 Product 类需要来自服务的其他数据,例如 GetSystemDateTime() (我知道,这是一个坏例子,但试图演示非数据库调用),则将 ProductId 放在一边,该服务方法将在哪里调用?
假设您认为 Date 实现是基础设施的一个细节。您将围绕它创建一个抽象以在您的应用程序中使用。它将从一个接口开始,可能是 IDateTimeProvider 之类的东西。该接口有一个方法 GetSystemDateTime()。
您的应用程序服务可以随时实例化 IDateTimeProvider 并调用其方法,然后将结果传递给聚合、实体、域服务或任何其他需要它的东西。
您的域服务可以自由地将 IDateTimeProvider 的引用作为类字段,但它不应该创建实例本身。它要么通过依赖注入接收它,要么通过服务定位器请求它。
最后,您的实体、聚合根和值对象可以自由调用 GetSystemDateTime() 和 IDateTimeProvider 的其他方法,但不能直接调用。它需要经过双重调度,您可以为其提供一个域服务作为其方法之一的参数,并且它将使用该域服务来查询它想要的信息,或执行它需要的行为。它还可以将自身传递回域服务,域服务将在其中执行查询和设置。
如果您认为 IDateTimeProvider 实际上是一个域服务,作为通用语言的一部分,那么您的实体和聚合根可以直接调用它的方法,它只是不能将其作为类字段的引用,而是本地的方法参数的变量很好。
DDD 中的服务是逻辑转储,其中逻辑对于域对象来说并不自然,对吧?那么它是如何粘合在一起的呢?
我认为我的整个答案已经很清楚地说明了这一点。基本上,你有 3 种可能性来粘合它们(至少我现在能想到)。
1) 应用程序服务实例化领域服务,调用其方法,并将结果返回值传递给需要它的其他东西(存储库、实体、聚合根、值对象、另一个领域服务、工厂等)。
2) 领域服务由应用程序域实例化,并作为参数传递给使用它的方法。无论什么使用它,都不会保留对它的永久引用,它只是一个局部变量。
3) 领域服务由应用程序域实例化,并作为参数传递给使用它的方法。无论使用什么,都使用双重调度以非依赖的方式使用域服务。这意味着它会将对其自身的引用传递给域服务的方法,如 DomainService.DoSomething(this, name, Address) 中所示。
希望这可以帮助。如果我做错了什么或者违背了 DDD 的最佳实践,欢迎评论。