DDD:有没有一种优雅的方法可以在更新聚合根时传递审计信息?

假设我有一个如下所示的 CQRS 命令:


public sealed class DoSomethingCommand : IRequest

{

    public Guid Id { get; set; }


    public Guid UserId { get; set; }


    public string A { get; set; }


    public string B { get; set; }

}

这是在以下命令处理程序中处理的:


public sealed class DoSomethingCommandHandler : IRequestHandler<DoSomethingCommand, Unit>

{

    private readonly IAggregateRepository _aggregateRepository;


    public DoSomethingCommand(IAggregateRepository aggregateRepository)

    {

       _aggregateRepository = aggregateRepository;

    }


    public async Task<Unit> Handle(DoSomethingCommand request, CancellationToken cancellationToken)

    {

        // Find aggregate from id in request

        var id = new AggregateId(request.Id);


        var aggregate = await _aggregateRepository.GetById(id);


        if (aggregate == null)

        {

            throw new NotFoundException();

        }


        // Translate request properties into a value object relevant to the aggregate

        var something = new AggregateValueObject(request.A, request.B);


        // Get the aggregate to do whatever the command is meant to do and save the changes

        aggregate.DoSomething(something);


        await _aggregateRepository.Save(aggregate);


        return Unit.Value;

    }

}

我需要保存审计信息,例如“CreatedByUserID”和“ModifiedByUserID”。这是一个纯粹的技术问题,因为我的业务逻辑都不依赖于这些字段。

我在这里找到了一个相关的问题,其中有人建议引发一个事件来处理这个问题。这将是一种很好的方法,因为我还使用类似于此处描述的方法,基于从聚合引发的域事件来持久化更改。

(TL;DR:将事件添加到聚合中的每个操作的集合中,将聚合传递给Save存储库中的单个方法,在该存储库方法中使用模式匹配来处理存储在聚合中的每个事件类型以持久保存更改)


一只名叫tom的猫
浏览 97回答 1
1回答

喵喵时光机

这将取决于具体情况。我将尝试解释几个不同的问题及其解决方案。您有一个系统,其中审计信息自然是域的一部分。让我们举一个简单的例子:在Bank和Person之间签订合同的银行系统。银行由BankEmployee代表。签署或修改合同时,您需要在合同中包含有关谁做的信息。public class Contract {&nbsp; &nbsp; public void AddAdditionalClause(BankEmployee employee, Clause clause) {&nbsp; &nbsp; &nbsp; &nbsp; AddEvent(new AdditionalClauseAdded(employee, clause));&nbsp; &nbsp; }}您有一个系统,其中审计信息不是域的自然组成部分。这里有几件事需要解决。例如,用户可以只向您的系统发出命令吗?有时另一个系统可以调用命令。解决方案:记录所有的传入命令及其处理后的状态:成功、失败、拒绝等。包括命令发布者的信息。记录命令发生的时间。您可以在命令中包含有关发行者的信息,也可以不包含。public interface ICommand {&nbsp; &nbsp; public Datetime Timestamp { get; private set; }}public class CommandIssuer {&nbsp; &nbsp; public CommandIssuerType Type { get; pivate set; }&nbsp; &nbsp; public CommandIssuerInfo Issuer {get; private set; }}public class CommandContext {&nbsp; &nbsp; public ICommand cmd { get; private set; }&nbsp; &nbsp; public CommandIssuer CommandIssuer { get; private set; }}public class CommandDispatcher {&nbsp; &nbsp; public void Dispatch(ICommand cmd, CommandIssuer issuer){&nbsp; &nbsp; &nbsp; &nbsp; LogCommandStarted(issuer, cmd);&nbsp; &nbsp; &nbsp; &nbsp; try {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DispatchCommand(cmd);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LogCommandSuccessful(issuer, cmd);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; catch(Exception ex){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LogCommandFailed(issuer, cmd, ex);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; // or&nbsp; &nbsp; public void Dispatch(CommandContext ctx) {&nbsp; &nbsp; &nbsp; &nbsp; // rest is the same&nbsp; &nbsp; }}优点:这将从有人发出命令的知识中删除您的域缺点:如果您需要有关事件更改和匹配命令的更多详细信息,您将需要匹配时间戳和其他信息。根据系统的复杂性,这可能会变得很难看解决方案:记录entity/aggregate中的所有incomming命令和相应的事件。查看本文以获取详细示例。CommandIssuer您可以在事件中包含。public class SomethingAggregate {&nbsp; &nbsp; public void Handle(CommandCtx ctx) {&nbsp; &nbsp; &nbsp; &nbsp; RecordCommandIssued(ctx);&nbsp; &nbsp; &nbsp; &nbsp; Process(ctc.cmd);&nbsp; &nbsp; }}你确实将一些来自外部的信息包含到你的聚合中,但至少它是抽象的,所以聚合只是记录它。看起来还不错,是吗?解决方案:使用将包含有关您正在使用的操作的所有信息的传奇。在分布式系统中,大多数时候你需要这样做,所以它会是一个很好的解决方案。在另一个系统中,它会增加您可能不想拥有的复杂性和开销 :)public void DoSomethingSagaCoordinator {&nbsp; &nbsp; public void Handle(CommandContext cmdCtx) {&nbsp; &nbsp; &nbsp; &nbsp; var saga = new DoSomethingSaga(cmdCtx);&nbsp; &nbsp; &nbsp; &nbsp; sagaRepository.Save(saga);&nbsp; &nbsp; &nbsp; &nbsp; saga.Process();&nbsp; &nbsp; &nbsp; &nbsp; sagaRepository.Update(saga);&nbsp; &nbsp; }}我已经使用了此处描述的所有方法以及您的Option 2的变体。在我处理请求时的版本中,他们Repositoires可以访问context包含用户信息的对象,因此当他们保存事件时,此信息包含在EventRecord同时具有事件数据和用户信息的对象中。它是自动化的,因此其余代码与它分离。我确实使用 DI 将上下文注入存储库。在这种情况下,我只是将事件记录到事件日志中。我的聚合不是事件来源的。我使用这些准则来选择一种方法:如果它是一个分布式系统 -> 去 Saga如果不是:我是否需要将详细信息与命令相关联?是:传递Commands和/或CommandIssuer信息到聚合如果没有那么:数据库是否具有良好的事务支持?是:保存Commands在CommandIssuer聚合之外。否:保存Commands并CommandIssuer汇总。
打开App,查看更多内容
随时随地看视频慕课网APP