我正在迁移一个"大泥球#34;基于域驱动设计思想的系统(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用作状态对象,实体框架或其中任何一种。
该应用程序目前使用经典的层架构,其中基础结构(包括提到的表包装器/状态对象)位于底部,然后是域。域知道基础结构,并且使用基础结构在域中实现存储库。如上所示,大多数实体都包含一个公共构造函数,用于在域内方便地创建新实例,内部只创建一个新的状态对象(因为域知道它)。
现在,我们希望进一步发展这个并逐渐转变架构,从而产生更多的洋葱"一种建筑。在该体系结构中,域只包含存储库接口,实际的实现将由位于其上的基础结构层提供。在这种情况下,域无法再知道实际的状态对象/数据库表包装器。
解决这个问题的一个想法是让状态对象实现域定义的接口,这实际上似乎是一个很好的解决方案。它在技术上也是可行的,因为即使状态对象必须从特殊的数据访问基类继承,它们也可以自由地实现接口。 所以上面的例子会改为:
public class Customer
{
private readonly ICustomerState state;
public Customer(ICustomerState state)
{
this.state = state;
}
public Customer()
{
this.state= <<<-- what to do here??;
}
[...]
}
因此,当存储库(现在在基础结构中实现)实例化一个新的Customer时,它可以轻松地传入实现ICustomerState的数据库包装器对象。到目前为止一切顺利
但是,在域中创建新实体时,再也不可能创建内部状态对象,因为我们不再知道它的实际实现。
有几种可能的解决方案,但它们似乎都没有吸引力:
StateFactory.Instance.Create<TState>()
方法来创建内部状态对象。然后,基础设施负责为其注册适当的实施。类似的方法是以某种方式获得DI容器并从那里解决工厂。我个人并不喜欢这种服务定位器方法,但在这种特殊情况下可能是可以接受的。我有没有更好的选择?
答案 0 :(得分:0)
领域驱动的设计不适合大型泥球。尝试在大型系统中应用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);
}
}