Service DDD Repo-Repo.findByChildId(id)和Repo.transferChild(to,from,child)

Service DDD Repo-Repo.findByChildId(id)和Repo.transferChild(to,from,child),service,repository,domain-driven-design,aggregate,Service,Repository,Domain Driven Design,Aggregate,我最近开始研究DDD,并将一个旧的个人项目重构为这种模式。我对埃文斯的蓝皮书已经了解了一半,但似乎在那里或网上的任何地方都找不到答案 基本上,我的应用程序是一个库存跟踪器。存货将包含一组项目,项目是存货之间的可转让实体。库存将具有类似于transferIn()transferOut()的方法,这些方法将包含一些验证逻辑,即检查库存是否已满或项目是否处于可转移状态。这些约束使我相信库存是聚合根,而项目是一个实体 1) 在某些情况下,如果用户请求其库存的特定项目实体,我希望有一个inventoryR

我最近开始研究DDD,并将一个旧的个人项目重构为这种模式。我对埃文斯的蓝皮书已经了解了一半,但似乎在那里或网上的任何地方都找不到答案

基本上,我的应用程序是一个库存跟踪器。存货将包含一组项目,项目是存货之间的可转让实体。库存将具有类似于
transferIn()transferOut()
的方法,这些方法将包含一些验证逻辑,即检查库存是否已满或项目是否处于可转移状态。这些约束使我相信库存是聚合根,而项目是一个实体

1) 在某些情况下,如果用户请求其库存的特定项目实体,我希望有一个
inventoryRepo.findByItemId(id)
,它将返回当前拥有该项目的库存。以便我可以:

2) 通过服务执行以下操作:

boolean requestItemTransfer(destInvId, itemId){
    Inv from = invRepo.findByItemId(itemId);
    Inv to = invRepo.findById(destInvId);
    from.transferOut(itemId);
    to.transferIn(from.getItem(itemId));
    return invRepo.transferChild(to, item); //Edited
}
基本上是在inventory类(rich domain模型)中编写验证逻辑,如果没有异常,则使用repo.transfer()方法保存更改

我会违反DDD吗?有更好的选择吗?

从我阅读和理解的情况来看,这似乎是有效的,即使只是非常规的。我发现的每个示例都显示了只能存在于1个根实例中的实体。还有银行账户转账的例子,但这些例子涉及的金额是价值对象,并且有一个转账存储库,因为转账将记录在特定场景中,而不是我的场景中

编辑: 用例如下所示:

1) 用户请求他们的库存和物品列表

2) 用户从一个库存中选择一个或多个项目,并请求将其发送到另一个库存。这就是我的TransferService介入的地方,协调指定库存中的txIn和txOut,并通过回购将这些变化保持下去。也许这应该是基础设施服务?这是我不清楚的一件事

3) 用户预先定义了一组他希望能够转移到库存的项目,这些项目当前在哪个库存中。TransferService将找到这些项目当前所在的位置,并像用例2那样协调其余项目

EDIT2:关于回购转让
这实际上是一个约束/优化?从数据方面,我被告知它所做的一切就是查找项目并更改它指向的库存id。这是因为物料不能同时在两个库存中。因此,与
repo.update(frominwinnewstate)
repo.update(toinwinnewstate)
不同的是
repo.moveChild(toInv,child)
,因为我们不想重写库存的整个状态(其所有未移动的项目,并且其其余状态是从其在任何点的项目派生的),只需移动一些项。

您至少缺少一个聚合,并试图用持久性替换它。与您的领域专家交谈,了解谁或什么在进行此传输。我打赌你不会听说这是由“存储库”或“数据库”完成的。这个东西将是你的聚合,它可能有这个
Transfer
方法。此调用还将封装来自
transferIn
transferOut
的登录,因为这似乎是一个事务性过程,您在三个不同的位置执行此操作。记住,您的事务边界是您的集合。不是您的存储库。

首先,我想回顾一下您的场景定义的域模型

您说过您正在使用下一个规范构建库存跟踪器:

  • 用户拥有库存
  • 仓库由物品组成
  • 用户可以将项目从一个库存转移到另一个库存。我猜这两个清单都属于用户,正如您所说: “用户请求其库存及其项目的列表。用户从一个库存中选择一个或多个项目,并请求将其发送到另一个库存…”
另一方面,您指出的一个不变量是:

  • 只有在库存B尚未满时,才能将项目从已存在的库存(库存A)转移到另一个库存(库存B)。我想如果物品不能转移,就应该保存在库存中
  • 如果我理解得很好,用户会在其存储库之间传输其项目
比如:

class TransferItemService {
    public function execute(TransferItemRequest request)
    {
        user = userRepository.findOfId(request.userId());
        user.transferItem(request.itemId(), request.fromInventoryId(), request.toInventoryId()); //Checks invariant -> the given Item is in one of his Inventories, the destination Inventory is owned by him, the destination Inventory is not full and finally transfers the Item
        userRepository.save(user);
    }
}
现在,为了定义聚合根目录,我需要知道我的业务是否能够处理最终的一致性。也就是说,如果移动项目必须以原子方式完成(仅一个请求),或者可能需要一些时间(多个请求)

没有最终的一致性

如果业务部门表示此处不允许最终一致性,那么如果您希望确保您的域保持一致并与不变量对齐,那么用户将是唯一的聚合者,因为他是其清单之间的连接点。在这种情况下,由于装载所有库存及其项目,您可能会面临性能问题

最终一致性

如果您可以使用最终一致性,则可以使用下一个聚合根:
用户
库存
项目
。因此,使用前面的代码对传输项目的用例进行建模:

class TransferItemService {
    public function execute(TransferItemRequest request)
    {
        user = userRepository.findOfId(request.userId());
        user.transferItem(request.itemId(), request.fromInventoryId(), request.toInventoryId()); //Checks invariant -> the given Item is in one of his Inventories, the destination Inventory is owned by him, the destination Inventory is not full and finally transfers the Item
        userRepository.save(user);
    }
}
在这种情况下,
transferItem
方法如下所示:

class User {
  private string id;
  private List<UserInventory> inventories;

  public function transferItem(itemId, fromInventoryId, toInventoryId)
  {
    fromUserInventory = this.inventories.get(fromInventoryId);
    if(!fromUserInventory) throw new InventoryNotBelongToUser(fromInventoryId, this.id);

    toUserInventory = this.inventories.get(toInventoryId);
    if(!toUserInventory) throw new InventoryNotBelongToUser(toInventoryId, this.id);

    toUserInventory.addItem(itemId);
    fromUserInventory.deletetItem(itemId);
  }
}

class UserInventory {
  private String identifier;
  private int capacity;

  public function deleteItem(userId, itemId)
  {
      this.capacity--;
      DomainEventPublisher.publish(new ItemWasDeleted(this.identifier, itemId));

  }

  public function addItem(userId, itemId)
  {
    if(this.capacity >= MAX_CAPACITY) {
      throw new InventoryCapacityAlreadyFull(this.identifier);
    }
    this.capacity++;

    DomainEventPublisher.publish(new ItemWasAdded(this.identifier, itemId));
  }
}
除非我犯了一个错误,否则我认为我们已经满足了所有的不变量,我们只是对每个请求修改了一个聚合根,并且我们不需要加载所有的项目来对库存执行操作


如果您发现有什么问题,请告诉我:D.

是库存造成的
class ItemWasRemovedListener()
{
  public function handleEvent(event)
  {
    removeItemFromInventoryService.execute(event.inventoryId(), event.itemId());
  }
}

class ItemWasAddedListener()
{
  public function handleEvent(event)
  {
    addItemToInventoryService.execute(event.inventoryId(), event.itemId());
  }
}