域事件的真实世界、生产级案例是什么(理解为同一限界上下文和过程中的事件)?

What are the real world, production grade cases for domain events (understood as events within same bounded context & process)?

如果我们有一个限界上下文,假设有 2 个聚合,其中 aggregate1 发布 event1 并且 aggregate2 想要对其做出反应,我们有 1 种方法可以做到这一点:

  1. 正在提出 event1 > aggregate2 对此做出反应
  2. event1 发布到消息总线并让一些单独的进程获取它并调用 aggregate2 方法

不管在同一个限界上下文中,如果我们想确保我们不会丢失 event1(应用程序崩溃在 aggregate1aggregate2 之间保存作为对 event1 的反应,例如)我很难找到选项 1 何时会比选项 2 更好的例子(可能超出验证范围)?

我一定遗漏了什么,但就我所知,这对我来说似乎是一个纯粹的理论概念,在可靠性和保持正确状态的能力方面没有任何现实价值。

当然,发布一条消息并对其进行单独的处理 listen/react 可能看起来有点矫枉过正,但是是否有任何未在某处持久保存的域事件的实际用途(即使在本地数据库中被轮询我将哪种情况称为原始消息总线)?

我错过了什么?

What is a real world application of domain events within a bounded context and process?

要求:

  • 用户可以创建类别。
  • 类别名称必须是唯一的。
  • 用户可以重命名类别。

(类别将具有许多与命名无关的其他属性)。

DDD 概念:

  • 一个类别聚合应该负责它自己的内部不变量,但不能知道其他类别聚合的细节。

您将如何确保当前类别的类别名称在全球范围内唯一,而该类别无法访问所有其他类别?

答案:领域事件

域事件

public CategoryRenamed : DomainEvent
{
    public Category Category { get; }

    internal CategoryRenamed(Category category)
    {
        this.Category = category;
    }
}

DomainEventHandler

public CategoryRenamedHandler : IDomainEventHandler<CategoryRenamed>
{
    public CategoryRenamedHandler(CategoryRenamed domainEvent)
    {
        string proposedName = domainEvent.Category.Name;

        // query database to ensure that proposedName is not already in use
        if (inUse)
            throw new Exception($"Name {proposedName} already in use." ;
    }
}

实体

public abstract class Entity
{
    List<DomainEvent> _domainEvents = new List<DomainEvent>();

    protected AddDomainEvent(DomainEvent domainEvent)
    {
        _domainEvents.Add(domainEvent);
    }

    public List<DomainEvent> DomainEvents => _domainEvents;
}

类别

public class Category : Entity
{
    public Guid Id { get; private set; }
    public string Name { get; private set; }

    public Category(Guid id, string name)
    {
        Id = id;
        SetName(name);
    }

    public Rename(string name)
    {
        SetName(name);
    }

    void SetName(string name)
    {
        // Local Invariants
        if (string.IsNullOrWhitespace(name))
            throw new Exception("Invalid name");

        Name = name;

        // Add a domain event for the infrastructure to process
        AddDomainEvent(new CategoryRenamed(this));
    }
}

命令

public class AddCategoryCommand
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

CommandHandler

public class CommandHandler : ICommandHandler<AddCategoryCommand>
{
    readonly ICategoryRepository _categoryRepository;

    public CommandHandler(ICategoryRepository categoryRepository)
    {
        _categoryRepository = categoryRepository;
    }

    public void HandleCommand(AndCategoryCommand command)
    {
        Category newCategory = new(command.Id, command.Name);

        // Check for domain events before committing to repository
        DomainEventDispatcher.DispatchEvents(newCategory.DomainEvents);
        // Dispatcher will find the CategoryRenamed event and send 'in-process'
        // to CategoryRenamedHandler
        // If name was is in use an error will be thrown by the handler (see above)

        _categoryRepository.Add(newCategory);
    }
}

结果

您的类别聚合已强制执行其自身的局部不变性,并且已利用域命令和域事件处理基础结构来确保名称在所有类别中的唯一性。