如何使聚合根更小

How to make aggregate root smaller

怎么回事

我正在开发使用一次性密码处理操作确认的内部微服务。我决定使用事件溯源方法创建良好的领域模型。

我有什么

我有确认会话 用于操作 某个操作类型。用户可以添加询问 Session 以获得 Confirmation Attempt 并指定他想要接收 Channel 挑战(一次性密码)。 频道 就像短信、电子邮件、推送等

这是命令处理程序的代码摘录

var session = _sessionFactory.CreateSession(operationType, userId);

var challengeCreationResult = await _challengeProvider.CreateAsync(
    recipient, channelType, session, addOnFields);

if (!challengeCreationResult.IsSuccess)
{
    return OperationResult<AttemptCreationResult>.Error(
        challengeCreationResult.ErrorCode, challengeCreationResult.ErrorMessage);
}

session.AddAttempt(challengeCreationResult.Data);

创建确认尝试并收到挑战后,用户能够提交他的验证响应。

var session = await _sessionStore.GetAsync(sessionId);

var result = await session.ApplyResponseAsync(response, _responseValidator, actingUserId, operationType);

AddAttemptApplyResponseAsync 都会触发适当的事件,return 操作结果 return 到 用户

出了什么问题

在尝试添加需要了解多个 Confirmation Session 的业务规则之前,我对这一实施感到满意。它们是:

  1. 如果用户在一小时内针对单个操作类型给出了五个错误答案,我们需要冻结他一小时。
  2. 只有在自上次没有正确答案的确认尝试 30 秒后,才允许用户请求新的确认尝试

问题

如何在保持聚合 root/services/event 处理程序小而集中的同时实施这些规则?

我认为 UserConfirmation 作为聚合看起来不错,但不是 UserConfirmationHistory 的想法,因为不建议有很多子级的聚合,否则会出现这种情况这里。

在这种情况下,业务规则自然不适合聚合,应向上移动到域服务,该域服务可以在应用程序服务提供的 UserConfirmation 个实例列表上运行。

我的意思是,我根本不喜欢在我的域 classes 中有任何基础设施问题(无论是聚合还是服务),并且获取 UserConfirmation 条目的列表需要查询或存储库实现,这是基础架构问题,即使您使用接口抽象它们也是如此。


我会做什么

  • 应用程序服务:持有对 IUserConfirmationRepository 的引用并获取 UserConfirmation 条目的列表,将它们传递给...
  • 域服务:它将接收 UserConfirmation 个条目的列表并验证跨越多个聚合的逻辑

这样,您的业务规则仍然受到域 class 的保护。

谨慎行事:

  • 这样做,你可能刚刚打开了一个缺口。如果您的代码的用户能够在不通过此域服务的情况下创建和保留 UserConfirmation 实体的实例,那么他们将能够绕过此不变量;
  • 那你就得想办法防止了。也许通过在 UserConfirmation 聚合中创建构造函数 internal,并使创建此聚合的唯一可能路径是通过域服务。

PS。你提到当你只需要做简单的插入时事情会更容易,但是:

  • DDD 并非适用于每个项目的方法。在使用 DDD 的项目早期存在很多摩擦,只有当您的领域非常复杂时,这才是合理的。然后你会在路上抢走好处;
  • 如果 DDD 确实是该项目的正确方法,那么以后您将真正受益于这种方式