领域驱动设计:聚合创建聚合

问题描述 投票:0回答:1

最近我看到 Udi Dahan 的一篇post,谈论聚合不应该凭空创建,而应该从其他聚合创建聚合,因为这样可以更好地捕获域。对我来说似乎有点合乎逻辑,但我似乎正在努力解决如何实现这一点并理解应该在哪里引发事件。

我正在创建一个排行榜应用程序。在我的域中,只有管理员可以创建排行榜。我正在尝试了解如何以干净的方式最好地捕获此规则,以便可以突出显示该域,同时确保尊重不变性。到目前为止,我已经决定管理员和排行榜将成为聚合根(如果您不这么认为,请告知)。

以下是我迄今为止在 Typescript 中得到的内容。

const User extends AggregateRoot { /*whatever*/}

class Adminstrator extends User {
  // blah blah
  
  createLeaderboard(name) {
    return Leaderboard.Create(name);
  }
}

class Leaderboard extends AggregateRoot {
  public id;
  public name;
  
  private create(id: LeaderboardId, name: LeaderboardName) {
    const leaderboardCreatedEvent = new LeaderboardCreatedEvent(id, name);
    this.applyNewEvent(leaderboardCreatedEvent);
  }

  static Create(id: LeaderboardId, name: LeaderboardName): Leaderboard {
    const params: ConstructorCreateParams = {
      type: ConstructorType.Create,
      id,
      name
    }
    return new Leaderboard(params);
  }

  private constructor(params) {
    if (params.type === ConstructorType.Create) {
      this.create(params.id, params.name);
    } else {
      this.load(params.events);
    }
  }
}

class CreateLeaderboardCommandHandler {
  constructor(adminstratorRepo, leaderboardRepo)
  
  execute(command) {
    const admin = this.adminRepo.get(command.userId);
    const leaderboard = admin.createLeaderboard(command.name)
    leaderboardRepo.save(leaderboard);
  }
}

关于我整理的实施的几个问题:

  1. 哪里是强制执行只有管理员才能创建排行榜这一事实的最佳位置。我的方法好不好呢。它会在排行榜的构造函数中吗?
  2. 在上面的示例中,管理员聚合正在执行某些操作(即创建排行榜),但我们保留了 Leaderboard 事件(即 LeaderboardCreatedEvent)。这样好吗?这是一种反模式吗?我想我想知道执行操作(创建排行榜)的实体(管理员)是否应该是引发事件的实体。

非常感谢您的意见。

typescript domain-driven-design aggregates
1个回答
0
投票

创作模式很奇怪


哪里是强制执行只有管理员才能创建排行榜这一事实的最佳位置。我的方法好不好呢。它会在排行榜的构造函数中吗?

我相信要认识到的关键是你在这里面对的是一个分支;当你的数据模型看起来像这样时,你想要一种结果,当你的数据模型看起来像那样时,你想要一种不同的结果。

一旦您认识到存在一个分支,您就需要考虑您希望该分支在代码中的出现程度有多明显。这可能意味着有一个快乐的路径和一个隐式的异常路径,它可能意味着有两个回调,它可能意味着返回一个“或类型”,......

说明该想法的一种非稳健方法是想象管理存储库返回一个 promise

this.adminRepo.get(command.userId).then((admin) -> {
    const leaderboard = admin.createLeaderboard(command.name)
    leaderboardRepo.save(leaderboard);
}).catch( /* handler for the not-an-admin case */ )

另一种可能性是分支属于更上游的地方 - 我们运行一些协议来检查命令的来源是否经过授权,然后再将该信息传递给域模型进行处理。 (即使在这种情况下,您可能需要“冗余”检查,因为防御性编程?这是一路权衡。)


在上面的示例中,管理员聚合正在执行某些操作(即创建排行榜),但我们保留了 Leaderboard 事件(即 LeaderboardCreatedEvent)。这样好吗?这是反模式吗?

在一般情况下,这是值得怀疑的——在你的情况下可能没问题。

有两个候选问题:

  • 最终会得到一个损坏的数据模型,因为您“遵循了实体建模域的规则”,而这些规则是在您无法在您的环境中重现的理想条件下开发的(没有丢失的消息,没有重复的消息) ,没有并发写入...)

  • 拥有一个鲁棒的实现,但设计不令人满意,安全地更改它的成本也很高(因为模块边界选择不当,或者代码掩盖了真正发生的事情,或者因为同一核心指令有太多不同的变体。 ...)

© www.soinside.com 2019 - 2024. All rights reserved.