【发布时间】:2020-10-29 22:54:59
【问题描述】:
我们以 DDD 方式写作,这意味着我们不会“将 DDD 视为圣经”,而是利用它提供的好处,并在它迫使我们做的事情时弯曲它对我们来说不是“常识”——毕竟 DDD 的主要观点之一是代码与现实生活中的用例保持接近。
这里有一个技术让我们有点偏离轨道的地方:
将项目添加到购物篮用户故事:(我在这里保持简短...)
"当客户将商品添加到购物篮时,如果销售额超过限制 (1000$),用户将收到“最大销售额为 1000$”的弹出窗口 如果此商品出现在购物篮中的次数超过允许的次数,用户将收到“产品超出一个订单中允许的最大数量”弹出窗口 并且该商品在库存中不存在用户将收到“产品 XXX 缺货”弹出窗口 如果所有条款都可以,请将商品添加到购物篮中”
应用程序代码可能如下所示:
func basketapp.AddItem(basketId, productId, quantity){
myBasket := BasketRepo.LoadByID(basketId) //load the basket
Product := ProductService.GetByID(productId) //get the requested product
myBasket.AddItem(Product, quantity) //runs internal domain business logic to add the item
BasketRepo.Save(basketId, myBasket) //Persist/add event source
}
领域业务逻辑如下所示:
func model.basket.AddItem(product, quantity){
if basket.total() > maxTotalPerBasket {
return maxItemsPerBasketExceededError
}
if basket.CountItem(product.id) > product.MaxRepeatsPerSale {
return productCountLimitExceededError
}
.....
item := newBasketItem(product, quantity ....)
basket.Items.Append(item)
}
当我希望在简单验证后验证产品是否有库存时,问题就开始了 对于人类(客户、产品经理开发人员)而言,这是另一个验证,因此按照用户故事的建议编写:
func model.basket.AddItem(product, quantity){
if basket.total() > maxTotalPerBasket {
return maxItemsPerBasketExceededError
}
if basket.CountItem(product.id) > product.MaxRepeatsPerSale {
return productCountLimitExceededError
}
if !inventoryService.exists(product.Id,storeId ){
return productNotInInventoryError
}
item := newBasketItem(product, quantity ....)
basket.Items.Append(item)
}
但是根据 DDD 的概念,模型不会调用另一个服务(甚至不是注入方案),
所以额外的代码将成为应用层的一部分,我需要以这种方式拆分 model.AddItem:
func basketapp.AddItem(basketId, productId, quantity){
myBasket := BasketRepo.LoadByID(basketId)
Product := ProductService.GetByID(productId)
//myBasket.AddItem(Product, quantity)
if !myBasket.ValidateItem(Product, quantity){ //Where ValidateItem is the model basket.total()+basket.CountItem code
return ...
} else if !StockLevelService.exists(productId, storeId ){
return ...
}
myBasket.AddItem(Product, quantity) //leaving lines from model.Additem after validation
BasketRepo.Save(basketId, myBasket)
}
它的代码更简洁,但现在用户故事需要更改(为此 - 编写此用户故事的人应该了解架构方面的 stockLevel 是在不同的服务/域中完成的事情”。
我们对这个想法进行了一些其他突变,但我们无法找到一种方式,一方面可以“按照技术规则行事”,另一方面“将作为用户故事阅读”(当然需要考虑到团队中的每个人都会很容易理解所有事情的正确位置,所以我们不会找到混合方法并且需要修复代码审查......以及在调试每个部分时快速知道去哪里等)
欢迎提出想法 :)
【问题讨论】:
-
您可以将要检查的服务作为参数传递给 AR 方法。
-
!StockLevelService.exists使用storedid:它来自架构的什么地方? (您的伪代码中缺少它) -
@plalx 评论是一个好点,但是。不过,我看到了另一个问题:当您将商品添加到购物篮时,库存应该会更改其产品可用性状态。如果这可以异步发生,也许您不需要与库存交互,发送域事件(并在不可用的情况下接收一个)可以工作。否则,您可以实现域服务接口并将其注入 AR,如果可用,它将“提交”库存产品
标签: design-patterns domain-driven-design clean-architecture