Skip to content
Go back

从领域事件到松耦合系统:如何用EF Core和MediatR构建高效架构

Published:  at  06:52 PM

从领域事件到松耦合系统:如何用EF Core和MediatR构建高效架构

在软件开发中,如何构建松耦合的系统一直是技术领域的热门话题,也是系统架构设计中的核心挑战之一。随着领域驱动设计(DDD)的流行,领域事件(Domain Events)作为一种重要的战术模式,越来越受到技术开发者的青睐。今天,我们将深入探讨领域事件的定义、实现方式以及相关技术工具(EF Core和MediatR)的应用,帮助您轻松构建高效松耦合系统。


什么是领域事件?🤔

定义与作用

领域事件是指在某个领域中已经发生的事实,它不可更改,是系统业务逻辑的自然输出。通过领域事件,我们可以显式地表达业务逻辑中的副作用,并实现多个聚合之间的解耦。

核心优势


领域事件 VS 集成事件 📤

虽然领域事件与集成事件在语义上都表示过去发生的事情,但它们的意图和应用场景有所不同:

特性领域事件集成事件
适用范围单一领域内跨子系统、微服务
传输方式内存消息总线消息队列(如RabbitMQ、Kafka)
处理方式同步或异步完全异步
目的触发领域内的副作用通知其他系统

示例:领域事件可以用来更新订单状态,而集成事件则可以通知库存系统减少库存。


如何实现领域事件 🌟

设计领域事件的基本原则

  1. 不可变性:领域事件是事实,应该具备不可变性。
  2. 命名约定:使用过去时态命名事件,例如OrderCompletedDomainEvent
  3. 信息设计:根据业务需求决定领域事件的“胖瘦”,即事件中包含的信息量。

示例代码:定义领域事件

使用MediatR库实现领域事件的抽象:

using MediatR;

public interface IDomainEvent : INotification
{
}

定义具体的领域事件:

public class CourseCompletedDomainEvent : IDomainEvent
{
    public Guid CourseId { get; init; }
}

从领域中触发事件 🚀

实现领域事件的触发机制

通过创建一个Entity基类来管理领域事件的触发和存储:

public abstract class Entity : IEntity
{
    private readonly List<IDomainEvent> _domainEvents = new();

    public IReadOnlyList<IDomainEvent> GetDomainEvents()
    {
        return _domainEvents.ToList();
    }

    public void ClearDomainEvents()
    {
        _domainEvents.Clear();
    }

    protected void RaiseDomainEvent(IDomainEvent domainEvent)
    {
        _domainEvents.Add(domainEvent);
    }
}

在实体中触发领域事件:

public class Course : Entity
{
    public Guid Id { get; private set; }

    public void Complete()
    {
        RaiseDomainEvent(new CourseCompletedDomainEvent { CourseId = this.Id });
    }
}

使用EF Core发布领域事件 📡

EF Core作为一个强大的ORM工具,可以用来发布领域事件。通过重写SaveChangesAsync方法,我们可以在保存数据库记录后发布领域事件。

发布时机的选择

  1. 保存前:领域事件与数据库保存共享事务,保证立即一致性。
  2. 保存后:领域事件单独事务,支持最终一致性(推荐)。

示例代码:发布领域事件

public class ApplicationDbContext : DbContext
{
    public override async Task<int> SaveChangesAsync(
        CancellationToken cancellationToken = default)
    {
        var result = await base.SaveChangesAsync(cancellationToken);
        await PublishDomainEventsAsync();
        return result;
    }

    private async Task PublishDomainEventsAsync()
    {
        var domainEvents = ChangeTracker
            .Entries<Entity>()
            .Select(entry => entry.Entity)
            .SelectMany(entity =>
            {
                var domainEvents = entity.GetDomainEvents();
                entity.ClearDomainEvents();
                return domainEvents;
            })
            .ToList();

        foreach (var domainEvent in domainEvents)
        {
            await _publisher.Publish(domainEvent);
        }
    }
}

使用MediatR处理领域事件 🛠️

MediatR提供了方便的发布-订阅模式,可以轻松实现领域事件的处理逻辑。

示例代码:事件处理器

public class CourseCompletedDomainEventHandler
    : INotificationHandler<CourseCompletedDomainEvent>
{
    private readonly IBus _bus;

    public CourseCompletedDomainEventHandler(IBus bus)
    {
        _bus = bus;
    }

    public async Task Handle(
        CourseCompletedDomainEvent domainEvent,
        CancellationToken cancellationToken)
    {
        await _bus.Publish(
            new CourseCompletedIntegrationEvent(domainEvent.CourseId),
            cancellationToken);
    }
}

一致性问题与Outbox模式 📦

为什么需要Outbox模式?

在领域事件发布后,如果事件处理失败可能导致系统状态的不一致。Outbox模式通过将事件存储到数据库的单独表中,确保领域事件与业务数据的原子性。

Outbox模式的工作流程

  1. 在数据库中保存业务数据和领域事件。
  2. 使用后台任务异步处理事件。
  3. 确保事件的可靠交付。

结尾总结 🙌

领域事件是构建松耦合系统的重要工具,能有效分离核心业务逻辑与副作用处理。通过结合EF Core和MediatR,我们可以轻松实现领域事件的发布与订阅。虽然领域事件的最终一致性带来了挑战,但使用Outbox模式可以很好地解决这一问题。

互动环节 🎯

你认为领域事件在构建分布式系统中的未来应用场景有哪些?欢迎在评论区留言讨论!

👉 如果你喜欢这篇文章,记得点赞、收藏并分享给你的开发朋友们!

🚀 更多技术干货,欢迎关注我们的公众号!



Previous Post
【ASP.NET Core】用ServiceCollection Extension Pattern简化服务注册,提升代码质量!
Next Post
Git中的合并与变基:你真的选对了吗?