我正在迁移一个"大泥浆"(BBOM)的系统迁移到基于域驱动设计思想的系统。
在重构的各种迭代后,目前使用内部状态对象对域聚合/实体进行建模,如沃恩·弗农(Vaughn Vernon)在本文中所述,例如:https://vaughnvernon.co/?p=879#comment-comment-1896
基本上,一个实体可能看起来像这样:
public class Customer
{
private readonly CustomerState state;
public Customer(CustomerState state)
{
this.state = state;
}
public Customer()
{
this.state = new CustomerState();
}
public string CustomerName => this.state.CustomerName;
[...]
}
截至今天,该系统中的状态对象始终是来自当前使用的专有数据访问框架的数据库表包装器,该框架类似于活动记录模式。因此,所有状态对象都从数据访问框架的基类部分继承。目前,不可能将POCO用作状态对象,实体框架或任何一个。
该应用程序当前使用经典的层体系结构,其中基础架构(包括提到的表包装器/状态对象)在底部,其次是域。域知道基础架构和存储库是使用基础架构在域中实现的。如上所述,大多数实体都包含一个公共构造函数,可方便地在域内创建新实例,该实例在内部仅创建一个新的状态对象(因为域知道它)。
现在,我们想进一步发展它并逐渐扭转架构,从而产生了一种"洋葱"的架构。在该体系结构中,域仅包含存储库接口,实际实现将由位于其顶部的基础架构层提供。在这种情况下,域不再知道实际状态对象/数据库表包装器。
解决此问题的一个想法是让状态对象实现由域定义的接口,这实际上似乎是一个很好的解决方案。从技术上讲,这也是可能的,因为即使状态对象必须从特殊的数据访问基类继承,它们也可以免费实现接口。因此,上面的示例会更改为:
public class Customer
{
private readonly ICustomerState state;
public Customer(ICustomerState state)
{
this.state = state;
}
public Customer()
{
this.state= <<<-- what to do here??;
}
[...]
}
因此,当存储库(现在在基础架构中实现)实例化新客户时,它可以轻松地传递到实现IcustomerState的数据库包装对象中。到目前为止很好
但是,当在域中创建新实体时,不再有可能创建内部状态对象,因为我们不再知道它的实际实现。
有几种可能的解决方案,但它们似乎都没有真正吸引人:
- 我们总是可以使用抽象工厂来创建新实体,然后这些工厂将由基础架构实施。虽然在某些情况下,由于实体的复杂性,域工厂是适当的,但我不想在每种情况下都使用一个,因为它们会导致域中的大量混乱,而在周围传递了另一个依赖性。。
- ,我们可以使用另一个类(POCO),而不是直接使用数据库表包装器作为状态对象,该类别仅容纳值,然后通过基础结构从/转换为数据库包装器。这可能会起作用,但最终会出现许多其他映射代码,并导致每个数据库表(DB包装器,状态对象,域实体)的3个或更多类,使维护复杂化。如果可能的话,我们想避免这种情况。
- 避免在工厂周围传递工厂,实体内部的构造函数可以称呼一些魔术,类似单元的
StateFactory.Instance.Create<TState>()
方法来创建内部状态对象。然后,为其注册适当的实施是基础架构的责任。类似的方法是以某种方式获取DI容器并从那里解决工厂。我个人真的不喜欢这种服务定位器方法,但是在这种特殊情况下可能是可以接受的。
我缺少更好的选择吗?
域驱动的设计不是适合大泥球的上帝。尝试在大系统中应用DDD不如面向对象的底漆有效。尝试用协作并隐藏数据的复杂性并开始思考方法/行为以通过行为来操纵对象内部的对象进行思考。
为了实现洋葱,我建议以下规则:
- 尝试避免您的业务规则中的ORM(EF,Hibernate等),因为它在业务代码中添加了数据库(DataContext,DataContext,DataSet,Geters,Setters,setters,Anecitys,Code Mopels等)的复杂性。
- 在业务规则中使用构图,关键是通过构造函数注入对象(系统中的参与者),尝试在业务规则中纯洁。
- 要求对象使用数据做点事
- 在对象API的设计上投入时间。
- 将植入细节留在末尾(数据库,云,蒙哥等)。您应该在课堂上实现细节,不要让代码的复杂性在其外部传播。
- 尽量不要在需要时始终在代码中拟合设计模式。
这是我将如何使用对象设计业务规则以具有可读性和维护:
public interface IProductBacklog
{
KeyValuePair<bool, int> TryAddProductBacklogItem(string description);
bool ExistProductBacklogItem(string description);
bool ExistProductBacklogItem(int backlogItemId);
bool TryDeleteProductBacklogItem(int backlogItemId);
}
public sealed class AddProductBacklogItemBusinessRule
{
private readonly IProductBacklog productBacklog;
public AddProductBacklogItemBusinessRule(IProductBacklog productBacklog)
{
this.productBacklog = productBacklog ?? throw new ArgumentNullException(nameof(productBacklog));
}
public int Execute(string productBacklogItemDescription)
{
if (productBacklog.ExistProductBacklogItem(productBacklogItemDescription))
throw new InvalidOperationException("Duplicate");
KeyValuePair<bool, int> result = productBacklog.TryAddProductBacklogItem(productBacklogItemDescription);
if (!result.Key)
throw new InvalidOperationException("Error adding productBacklogItem");
return result.Value;
}
}
public sealed class DeleteProductBacklogItemBusinessRule
{
private readonly IProductBacklog productBacklog;
public DeleteProductBacklogItemBusinessRule(IProductBacklog productBacklog)
{
this.productBacklog = productBacklog ?? throw new ArgumentNullException(nameof(productBacklog));
}
public void Execute(int productBacklogItemId)
{
if (productBacklog.ExistProductBacklogItem(productBacklogItemId))
throw new InvalidOperationException("Not exists");
if(!productBacklog.TryDeleteProductBacklogItem(productBacklogItemId))
throw new InvalidOperationException("Error deleting productBacklogItem");
}
}
public sealed class SqlProductBacklog : IProductBacklog
{
//High performance, not loading unnesesary data
public bool ExistProductBacklogItem(string description)
{
//Sql implementation
throw new NotImplementedException();
}
public bool ExistProductBacklogItem(int backlogItemId)
{
//Sql implementation
throw new NotImplementedException();
}
public KeyValuePair<bool, int> TryAddProductBacklogItem(string description)
{
//Sql implementation
throw new NotImplementedException();
}
public bool TryDeleteProductBacklogItem(int backlogItemId)
{
//Sql implementation
throw new NotImplementedException();
}
}
public sealed class EntityFrameworkProductBacklog : IProductBacklog
{
//Use EF here
public bool ExistProductBacklogItem(string description)
{
//EF implementation
throw new NotImplementedException();
}
public bool ExistProductBacklogItem(int backlogItemId)
{
//EF implementation
throw new NotImplementedException();
}
public KeyValuePair<bool, int> TryAddProductBacklogItem(string description)
{
//EF implementation
throw new NotImplementedException();
}
public bool TryDeleteProductBacklogItem(int backlogItemId)
{
//EF implementation
throw new NotImplementedException();
}
}
public class ControllerClientCode
{
private readonly IProductBacklog productBacklog;
//Inject from Services, IoC, etc to unit test
public ControllerClientCode(IProductBacklog productBacklog)
{
this.productBacklog = productBacklog;
}
public void AddProductBacklogItem(string description)
{
var businessRule = new AddProductBacklogItemBusinessRule(productBacklog);
var generatedId = businessRule.Execute(description);
//Do something with the generated backlog item id
}
public void DeletePRoductBacklogItem(int productBacklogId)
{
var businessRule = new DeleteProductBacklogItemBusinessRule(productBacklog);
businessRule.Execute(productBacklogId);
}
}