在微服务中使用 ASP.NET Core 实现事件溯源和 CQRS

作者:微信公众号:【架构师老卢】
2-14 19:2
39

概述:事件溯源和命令查询责任分离 (CQRS) 已成为解决微服务设计的复杂性的强大架构模式。基本 CQRS 表示形式在本文中,我们将探讨 ASP.NET Core 如何使你能够将事件溯源和 CQRS 无缝集成到微服务生态系统中。通过了解其基础知识、实际实现和可用工具,您将有能力构建强大而高效的微服务解决方案。事件溯源简介事件溯源的核心是一种数据存储模式,它将应用程序状态的每个更改捕获为一系列不可变事件。与仅存储当前状态的传统方法不同,事件溯源维护状态更改的完整历史记录。此技术不仅使您能够重建应用程序的过去状态,而且还提供了有关系统如何以及为何达到其当前状态的审核跟踪。什么是CQRS?命令查询责任分离

事件溯源和命令查询责任分离 (CQRS) 已成为解决微服务设计的复杂性的强大架构模式。

基本 CQRS 表示形式

在本文中,我们将探讨 ASP.NET Core 如何使你能够将事件溯源和 CQRS 无缝集成到微服务生态系统中。通过了解其基础知识、实际实现和可用工具,您将有能力构建强大而高效的微服务解决方案。

事件溯源简介

事件溯源的核心是一种数据存储模式,它将应用程序状态的每个更改捕获为一系列不可变事件。与仅存储当前状态的传统方法不同,事件溯源维护状态更改的完整历史记录。此技术不仅使您能够重建应用程序的过去状态,而且还提供了有关系统如何以及为何达到其当前状态的审核跟踪。

什么是CQRS?

命令查询责任分离 (CQRS) 是一种将系统的读取和写入操作分成不同路径的模式。在 CQRS 体系结构中,命令表示更改系统状态的请求,而查询则提取数据以进行读取。通过隔离这些问题,CQRS 允许独立优化每个路径,从而实现高效扩展、性能调整和增强的用户体验。

微服务中事件溯源和 CQRS 的优势

事件溯源和 CQRS 的组合在应用于基于微服务的应用程序时具有以下几个优势:

  1. **历史透明度:**事件溯源可确保全面记录所有状态更改,为审核、合规性和调试目的提供历史透明度。
  2. **查询优化的灵活性:**CQRS 使您能够以不同的方式优化读取和写入路径。这意味着您可以定制读取路径以提高查询性能,同时优化写入路径以实现高吞吐量。
  3. **可扩展性和性能:**微服务架构需要高效的扩展。事件溯源和 CQRS 允许您独立缩放应用程序的不同部分,从而提高整体性能。
  4. **弹性和容错能力:**事件溯源允许在发生故障后重建应用程序状态,从而增强了弹性。CQRS 有助于隔离写入路径中的错误和故障,以免影响读取路径。
  5. **支持复杂域:**事件溯源允许您通过记录细粒度事件来准确捕获复杂的域行为。然后,CQRS 为各种用例启用此数据的定制视图。

ASP.NET 核心微服务入门

奠定基础:创建 ASP.NET 核心微服务解决方案

若要在 ASP.NET Core 微服务中实现事件溯源和命令查询责任分离 (CQRS),首先需要设置开发环境并创建必要的解决方案结构。以下是帮助您入门的分步指南:

1. 安装 .NET Core SDK:确保计算机上安装了最新的 .NET Core SDK。您可以从Microsoft官方网站下载它。

**2. 创建新的解决方案:**打开命令行界面并导航到要创建解决方案的目录。使用以下命令创建新的 ASP.NET Core 解决方案:

dotnet new sln -n MicroservicesSolution

3. 创建微服务项目:在解决方案文件夹中,为每个微服务创建单独的项目。例如,您可以创建名为 、 和 :OrderServicePaymentServiceNotificationService

dotnet new webapi -n OrderService  
dotnet new webapi -n PaymentService  
dotnet new webapi -n NotificationService

4. 将项目添加到解决方案:使用以下命令将微服务项目添加到解决方案:

dotnet sln MicroservicesSolution.sln add OrderService\\OrderService.csproj  
dotnet sln MicroservicesSolution.sln add PaymentService\\PaymentService.csproj  
dotnet sln MicroservicesSolution.sln add NotificationService\\NotificationService.csproj

定义域事件和命令

解决方案结构就绪后,即可定义事件溯源和 CQRS 的核心构建基块:域事件和命令。

  • **创建共享库:**若要避免重复与域相关的代码,请创建一个包含常见类(如域事件、命令和值对象)的共享库。使用以下命令创建类库项目:
dotnet new classlib -n SharedDomain
  • **将共享库添加到解决方案:**将共享库项目添加到解决方案中:Add the shared library project to the solution:
dotnet sln MicroservicesSolution.sln add SharedDomain\\SharedDomain.csproj
  • **定义域事件和命令:**在项目中,将域事件和命令定义为 C# 类。例如:SharedDomain
public class OrderPlacedEvent  
{  
    public Guid OrderId { get; set; }  
    public DateTime Timestamp { get; set; }  
}  
  
public class ProcessPaymentCommand  
{  
    public Guid OrderId { get; set; }  
    public decimal Amount { get; set; }  
}
  • **引用共享库:**在每个微服务项目中,添加对库的引用:SharedDomain
dotnet add OrderService\\OrderService.csproj reference SharedDomain\\SharedDomain.csproj  
dotnet add PaymentService\\PaymentService.csproj reference SharedDomain\\SharedDomain.csproj  
dotnet add NotificationService\\NotificationService.csproj reference SharedDomain\\SharedDomain.csproj

实施事件溯源:设计聚合和事件

  1. **定义聚合:**首先确定微服务中的聚合。聚合表示域实体的一致性边界。例如,在电子商务应用程序中,“订单”可以是封装行项目、客户详细信息和其他相关信息的聚合。
  2. **创建域事件:**将域事件定义为纯 C# 类。每个事件都应封装聚合状态中的特定更改。例如:
public class OrderPlacedEvent  
{  
    public Guid OrderId { get; set; }  
    public DateTime Timestamp { get; set; }  
}

创建事件存储和存储库

  1. **设置事件存储:**创建事件存储以保存域事件。此存储负责存储与每个聚合相关的事件。可以使用关系数据库、NoSQL 数据库或专用事件存储解决方案。
  2. **实施存储库:**存储库提供了一种从事件存储中检索聚合并管理其生命周期的方法。存储库从事件存储加载事件,并通过按正确的顺序应用事件来重新构造聚合。
public class OrderRepository  
{  
    private readonly IEventStore _eventStore;  
  
    public OrderRepository(IEventStore eventStore)  
    {  
        _eventStore = eventStore;  
    }  
  
    public Order GetOrder(Guid orderId)  
    {  
        var events = _eventStore.GetEventsForAggregate(orderId);  
        var order = new Order(orderId);  
        order.Apply(events);  
        return order;  
    }  
}

发布和处理域事件

  1. **发布域事件:**当聚合发生状态更改时,它会发出一个或多个域事件。需要发布这些事件,以便将更改通知系统的其他部分。实现将域事件发布到事件总线或队列的机制。
  2. **处理域事件:**事件处理程序订阅特定的域事件并相应地执行操作。例如,“OrderPlacedEventHandler”可能会在下订单时向客户发送通知电子邮件。
using System;  
using YourProjectName.Events; // Import the namespace where your events are defined  
  
namespace YourProjectName.EventHandlers  
{  
    public class OrderPlacedEventHandler : IEventHandler<OrderPlacedEvent>  
    {  
        public void Handle(OrderPlacedEvent @event)  
        {  
            // Send notification email to the customer  
            SendEmailToCustomer(@event.OrderId);  
        }  
  
        private void SendEmailToCustomer(Guid orderId)  
        {  
            // Implement the logic to send an email to the customer  
            // You can use email libraries like SendGrid, SMTP, etc.  
            Console.WriteLine($"Notification email sent for order {orderId}");  
        }  
    }  
}

通过实施事件溯源,您可以捕获应用程序域中更改的历史记录,从而能够重建过去的状态并提供可靠的审计跟踪。

生成命令处理程序和命令模型

  • **定义命令模型:**命令模型表示更改应用程序状态的意图。它们封装了操作所需的数据。例如,“PlaceOrderCommand”可以封装下订单的详细信息。
public class PlaceOrderCommand  
{  
    public Guid OrderId { get; set; }  
    // Other relevant properties...  
}
  • 实现命令处理程序: 命令处理程序负责处理和执行命令。它们验证命令,执行必要的操作,并在需要时引发域事件。
public class PlaceOrderCommandHandler : ICommandHandler<PlaceOrderCommand>  
{  
    public void Handle(PlaceOrderCommand command)  
    {  
        // Validate the command, perform actions, and raise events  
        var order = new Order(command.OrderId);  
        // Perform actions like adding line items, calculating totals, etc.  
        // Raise domain events like OrderPlacedEvent  
    }  
}

创建查询模型和查询处理程序

  • **定义查询模型:**查询模型表示用于从应用程序的读取端获取信息的数据结构。它们是为高效查询和检索而量身定制的。
public class OrderQueryModel  
{ 
  public Guid OrderId {  
   get;set;  
  } // 其他相关属性...  
}
  • **实现查询处理程序:**查询处理程序根据用户查询从读取存储中检索数据。它们从查询模型中提取数据并返回请求的信息。
public class OrderQueryHandler : IQueryHandler\<OrderQueryModel\>  
{  
    public OrderQueryModel Handle()  
    {  
        // Fetch data from the read store and return the query model  
        var orderData = ReadStore.GetOrderData();  
        var orderQueryModel = MapToOrderQueryModel(orderData);  
        return orderQueryModel;  
    }  
}

分离写入和读取关注点

CQRS 的一个关键原则是分离应用程序的写入和读取关注点。这种分离允许您优化体系结构的性能、可伸缩性和可维护性。

通过采用 CQRS,您可以构建微服务以分别处理命令和查询,从而根据其特定要求优化应用程序的每个部分。

为事件溯源和 CQRS 选择正确的数据库

  • **选择数据库:**选择数据库时,请考虑应用程序数据的特征。对于事件溯源,您可以选择擅长处理写入密集型工作负载的数据库,例如 MongoDB 等 NoSQL 数据库或 EventStoreDB 等事件存储数据库。

// Example of using MongoDB as the event store

services.AddEventStore(options =>  
{  
    options.UseMongoDb(Configuration.GetConnectionString("EventStore"));  
});

事件存储和读取模型存储策略

  • 存储**事件存储数据:**事件存储是事件溯源体系结构的核心。它存储表示应用程序状态更改的域事件。实现事件存储,无论是使用专用数据库还是专用解决方案。
using System;  
using System.Collections.Generic;  
using System.Threading.Tasks;  
  
public interface IEventStoreRepository  
{  
    Task<IEnumerable<DomainEvent>> GetEventsAsync(Guid aggregateId);  
    Task SaveEventsAsync(Guid aggregateId, IEnumerable<DomainEvent> events);  
}  
  
public class EventStoreRepository : IEventStoreRepository  
{  
    private readonly IEventStoreDbContext _dbContext;  
  
    public EventStoreRepository(IEventStoreDbContext dbContext)  
    {  
        _dbContext = dbContext;  
    }  
  
    public async Task<IEnumerable<DomainEvent>> GetEventsAsync(Guid aggregateId)  
    {  
        // Retrieve events for the specified aggregate from the event store  
        var events = await _dbContext.Events  
            .Where(e => e.AggregateId == aggregateId)  
            .OrderBy(e => e.SequenceNumber)  
            .ToListAsync();  
  
        return events.Select(e => e.DomainEvent);  
    }  
  
    public async Task SaveEventsAsync(Guid aggregateId, IEnumerable<DomainEvent> events)  
    {  
        // Store events for the specified aggregate in the event store  
        var sequenceNumber = await _dbContext.Events  
            .Where(e => e.AggregateId == aggregateId)  
            .MaxAsync(e => (int?)e.SequenceNumber) ?? 0;  
  
        var eventEntities = events.Select(domainEvent => new EventEntity  
        {  
            AggregateId = aggregateId,  
            SequenceNumber = ++sequenceNumber,  
            DomainEvent = domainEvent  
        });  
  
        await _dbContext.Events.AddRangeAsync(eventEntities);  
        await _dbContext.SaveChangesAsync();  
    }  
}

在此示例中,该类使用用于检索和存储事件的方法实现接口。它与表示事件存储的数据库上下文的 进行交互。EventStoreRepositoryIEventStoreRepositoryIEventStoreDbContext

  • **存储读取模型:**读取模型包含为查询量身定制的数据。根据查询模式选择合适的数据库来存储读取模型。例如,将 SQL Server 或 PostgreSQL 等 SQL 数据库用于关系数据,将 NoSQL 数据库(如 MongoDB)用于灵活且无架构的数据。
// Example of defining a read model using Entity Framework Core  
public class OrderReadModel  
{  
    public Guid OrderId { get; set; }  
    // Other relevant properties...  
}

通过使用存储库模式,可以封装与事件存储的交互,从而更轻松地管理和维护事件溯源和 CQRS 体系结构。

请记住,这是一个简化的示例,在实际方案中,可以考虑错误处理、并发控制和其他注意事项。

用于 ASP.NET 核心事件溯源和 CQRS 的工具和库

事件存储提供程序

在 ASP.NET Core 微服务中实施事件溯源和 CQRS 时,可以利用各种事件存储提供程序来简化事件和聚合的管理。这些提供程序提供事件的存储、检索和查询等功能。让我们来探讨一些流行的事件存储提供商:

1. EventStoreDB**:EventStoreDB** 是一个健壮的开源事件存储,旨在高效处理大量事件。它提供基于流的存储、事件投影以及对事件溯源和 CQRS 的内置支持等功能。

// Example of connecting to an EventStoreDB instance  
var settings = new EventStoreClientSettings { ConnectionString = "esdb://localhost:21XX" };  
var eventStoreClient = new EventStoreClient(settings);

2. NEventStore:NEventStore 是另一个著名的事件存储库,它支持各种存储后端并提供与 ASP.NET Core 的集成。

// Example of configuring NEventStore  
var store = Wireup.Init()  
    .UsingSqlPersistence("ConnectionStringName")  
    .WithDialect(new MsSqlDialect())  
    .InitializeStorageEngine()  
    .UsingJsonSerialization()  
    .Compress()  
    .Build();

CQRS 框架和库

为了简化命令查询责任分离 (CQRS) 模式的实现,多个框架和库可以帮助您构建必要的组件:

1. MediatR**:MediatR** 是一个流行的开源库,它简化了中介模式的实现。它支持命令处理程序、查询处理程序和域逻辑之间的松散耦合。

// Example of using MediatR for command handling  
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, Guid>  
{  
    public async Task<Guid> Handle(CreateOrderCommand request, CancellationToken cancellationToken)  
    {  
        // Create order logic  
        return orderId;  
    }  
}

2. SimpleInjector**:SimpleInjector** 是一个高效的依赖注入容器,可用于管理 ASP.NET Core 应用程序中 CQRS 命令处理程序和查询处理程序的生存期。

// Example of configuring SimpleInjector for dependency injection  
services.UseSimpleInjector(container, options =>  
{  
    options.AddAspNetCore()  
           .AddControllerActivation();  
});

到目前为止,您已经了解了事件溯源和 CQRS 的复杂性,以及它们在使用 ASP.NET Core 构建强大且可扩展的微服务方面的关键作用。

在实现这些模式时,请记住考虑项目的独特要求,选择合适的工具,并不断完善您的实践。掌握事件溯源和 CQRS 的旅程才刚刚开始,未来的道路有望实现更具弹性和响应能力的微服务架构。

相关留言评论
昵称:
邮箱:
阅读排行