域事件的真实世界、生产级案例是什么(理解为同一限界上下文和过程中的事件)?
What are the real world, production grade cases for domain events (understood as events within same bounded context & process)?
如果我们有一个限界上下文,假设有 2 个聚合,其中 aggregate1
发布 event1
并且 aggregate2
想要对其做出反应,我们有 1 种方法可以做到这一点:
- 正在提出
event1
> aggregate2
对此做出反应
- 将
event1
发布到消息总线并让一些单独的进程获取它并调用 aggregate2
方法
不管在同一个限界上下文中,如果我们想确保我们不会丢失 event1
(应用程序崩溃在 aggregate1
和 aggregate2
之间保存作为对 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);
}
}
结果
您的类别聚合已强制执行其自身的局部不变性,并且已利用域命令和域事件处理基础结构来确保名称在所有类别中的唯一性。
如果我们有一个限界上下文,假设有 2 个聚合,其中 aggregate1
发布 event1
并且 aggregate2
想要对其做出反应,我们有 1 种方法可以做到这一点:
- 正在提出
event1
>aggregate2
对此做出反应 - 将
event1
发布到消息总线并让一些单独的进程获取它并调用aggregate2
方法
不管在同一个限界上下文中,如果我们想确保我们不会丢失 event1
(应用程序崩溃在 aggregate1
和 aggregate2
之间保存作为对 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);
}
}
结果
您的类别聚合已强制执行其自身的局部不变性,并且已利用域命令和域事件处理基础结构来确保名称在所有类别中的唯一性。