Sin*_*tic 2 c# domain-driven-design
所以我仍然想要达到那个"啊哈!" 聚合和聚合根的时刻(什么是新的,对吧?)我看到Martin Fowler说了以下内容:
聚合将其组件对象之一作为聚合根.来自聚合外部的任何引用都应该只转到聚合根.因此,根可以确保整个聚合体的完整性.
所以我正在读这个,因为聚合是父对象,并且聚合中的一个对象被选为聚合根.此聚合之外的任何内容都只能包含对根的引用.这对我来说很困惑.如果根是聚合的一部分,它如何确保任何东西?
我的理解是聚合的整个目的是:a)充当域对象(值对象和其他聚合)的逻辑分组,b)充当事务边界,所有与聚合的交互必须发生,并且回购只处理聚合.但是说我有这样的聚合:
public class UserInventory
{
private List<InventoryItem> _inventoryItems;
// the aggregate root
public User User { get; }
public ReadOnlyCollection<InventoryItem> Inventory => _inventoryItems;
public UserInventory(User root, IEnumerable<InventoryItem> inventory)
{
User = root;
_inventoryItems = inventory;
}
public void UpdateItemDescription(Guid itemId, ItemDescription newDescription)
{
_inventoryItems.Single(i => i.Id == itemId).Description = newDescription;
DomainEvents.Notify(new InventoryItemUpdated(User));
}
}
Run Code Online (Sandbox Code Playgroud)
现在,我的应用层想要更改特定库存项目的描述,因此,由于它不应该直接与库存项目对话,因此我会公开UpdateItemDescription(Guid, ItemDescription)控制此过程,因为这将是聚合强制执行任何不变量的机会(为了清楚起见我遗漏了
我觉得这是一个很好的聚合示例,但我不明白聚合根在这里如何为"确保完整性"做任何事情.我看过C#DDD示例应用程序,但我找不到任何明显的问题答案,但可以在答案中引用它.
任何人都可以澄清吗?我错过了什么或误解了什么吗?
它与封装(基本的OOP原理)有关.我们花点时间看看这份公共合同:
public class UserInventory
{
public User User { get; }
public ReadOnlyCollection<InventoryItem> Inventory => _inventoryItems;
}
Run Code Online (Sandbox Code Playgroud)
你在这里做的是介绍两个弱点.第一个是从另一个聚合根公开一个聚合根.这意味着有人可以这样做:
inventory.User.FirstName = "Arne";
_inventoryRepository.Update(inventory);
Run Code Online (Sandbox Code Playgroud)
这违反了得墨忒耳法则.
该代码可以工作吗?可能不是库存存储库的责任是持久化库存对象.其他一切都会导致数据层中的泥泞(即每个存储库需要能够调用所有其他存储库等)和业务层(何时是允许/有效的更改?)
首先,引用其他根聚合的ID:
public class UserInventory
{
public int UserId { get; }
public ReadOnlyCollection<InventoryItem> Inventory => _inventoryItems;
}
Run Code Online (Sandbox Code Playgroud)
下一个问题是您公开InventoryItem列表.这就是DDD书中谈论的内容.该Inventory课程无法控制其聚合.假设你有一个TotalValue属性Inventory:
public class UserInventory
{
public int UserId { get; }
public ReadOnlyCollection<InventoryItem> Inventory => _inventoryItems;
public decimal TotalValue {get; set; }
}
Run Code Online (Sandbox Code Playgroud)
如果有人直接在库存项目上调整价格会怎样?
inventory.FirstOrDefault(x=>x.Name = XX).Value = 456.32;
Run Code Online (Sandbox Code Playgroud)
总数会反映出正确的价值吗?不.因为您没有保护聚合.
正确的设计根本不是暴露物品:
public class UserInventory
{
private List<InventoryItem> _inventoryItems;
public UserInventory(User root, IEnumerable<InventoryItem> inventory)
{
User = root;
_inventoryItems = inventory;
}
public int UserId { get; }
public void UpdateItemDescription(Guid itemId, ItemDescription newDescription)
{
_inventoryItems.Single(i => i.Id == itemId).Description = newDescription;
DomainEvents.Notify(new InventoryItemUpdated(User));
}
}
Run Code Online (Sandbox Code Playgroud)
现在你问.如果我要在任何地方进行血腥的适当封装,我可以如何向用户显示内容?
将写入模型与读取模型分开.当您从存储库查询内容以显示信息时,您可以返回DTO.它们不包含任何方法,只代表状态.
在做某些工作的时候,应用程序服务可以使用存储库来获取真实对象,对其进行操作然后保留它.
那么在这个例子中,UserInventory是一个域对象,被选为UserInventory-User-Inventory聚合的根?并且作为非根的聚合成员的所有域对象都作为私有成员保存在根目录中,通过根上的显式行为向其公开访问权限?你能推荐一些好的模式或如何从根源中获取状态的例子吗?
大多数人在向正确设计的DDD迈进时看到的是CQRS非常适合.因为在CQRS中,您可以清楚地区分读取和写入什么.您用于读取的任何内容(即显示用户的信息)都不会在写入站点中重复使用.驱动写入方面的是基于任务的操作.即,不是将更新字段A,B,c更改为这些值,而是将命令更多地关注于业务操作使用信息A和B完成订单.区别非常重要,因为底层域模型结构完全不受命令的影响.我的意思是你的域不会在写模型中的任何地方暴露.所有更改都通过命令驱动.
它还意味着读取端可以包含满足特定用例的专用对象.我个人创建了解决UI中特定需求的对象.我的订单实体可以表示OrderListDTO每行的最小属性集能够显示概览,而"OrderDetailsDTO"表示订单的细节.
如果您尚未准备好进行更改(使用CQRS保护您的域),您可以通过为读写模型创建专门的应用程序服务来专门化您的应用程序.
重要的转变是要认识到订单没有单一的表示形式,而是根据订单在顶层消费的方式而有所不同.