我正在将类似“大泥球”(BBOM)的系统迁移到基于领域驱动设计思想的系统。
经过多次重构迭代后,领域聚合/实体目前使用内部状态对象建模,如 Vaughn Vernon 在本文中所述,例如:https://vaughnvernon.co/?p=879#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;
[...]
}
到目前为止,该系统中的状态对象始终是来自应用程序当前使用的专有数据访问框架的数据库表包装器,类似于 Active Record 模式。因此,所有状态对象都继承自数据访问框架的基类部分。目前,不能将 POCO 用作状态对象、 Entity Framework 或任何其他对象。
该应用程序当前使用经典层架构,其中基础设施(包括提到的表包装器/状态对象)位于底部,然后是域。域知道基础设施,并且使用基础设施在域中实现存储库。正如您在上面看到的,大多数实体都包含一个公共(public)构造函数,用于在域内方便地创建新实例,它在内部只是创建一个新的状态对象(因为域知道它)。
现在,我们想进一步发展这一点,并逐渐扭转架构,从而产生更多的“洋葱”式架构。在该架构中,域将仅包含存储库接口(interface),而实际实现将由位于其之上的基础设施层提供。在这种情况下,域不再知道实际的状态对象/数据库表包装器。
解决这个问题的一个想法是让状态对象实现由域定义的接口(interface),这实际上似乎是一个很好的解决方案。这在技术上也是可能的,因为即使状态对象必须从特殊的数据访问基类继承,它们也可以自由地实现接口(interface)。
所以上面的例子会变成这样:
public class Customer
{
private readonly ICustomerState state;
public Customer(ICustomerState state)
{
this.state = state;
}
public Customer()
{
this.state= <<<-- what to do here??;
}
[...]
}
因此,当存储库(现在在基础架构中实现)实例化新客户时,它可以轻松传入实现 ICustomerState 的数据库包装器对象。到现在为止还挺好
但是,当在域中创建新实体时,不再可能同时创建内部状态对象,因为我们不再知道它的实际实现。
有几种可能的解决方案,但它们似乎都不是真正有吸引力的:
StateFactory.Instance.Create<TState>()
用于创建内部状态对象的方法。然后,基础设施有责任为其注册适当的实现。类似的方法是以某种方式获取 DI 容器并从那里解析工厂。我个人不太喜欢这种服务定位器方法,但在这种特殊情况下它可能是可以接受的。 我还缺少什么更好的选择吗?
最佳答案
领域驱动设计不适合大泥球。尝试在大型系统中应用 DDD 不如面向对象的设计有效。尝试从相互协作的对象的角度来思考,隐藏数据的复杂性,并开始思考方法/行为,以通过行为来操纵对象内部。
为了实现洋葱结构,我建议以下规则:
以下是我将如何使用对象设计业务规则以便具有可读性和维护性:
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);
}
}
关于C#/DDD : How to model entities with internal state objects not instantiable by the domain layer when using onion architecture?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47941019/