You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET模块化单体应用:模块间集成事件传递对象的问题

解决方案

针对你在模块化单体DDD架构中遇到的跨模块事件通信问题,这里提供几个符合DDD原则的可行方案:

方案一:事件携带订单创建所需的最小化数据

不要在事件中传递整个Basket领域对象,而是提取创建订单必须的原始类型或值对象(这些类型可定义在Shared模块,或直接用基础类型)构建事件。既避免模块间的领域对象依赖,也无需额外DTO层。

修改事件定义:

public record BasketCheckedOutEvent(
    Guid CustomerId,
    Guid BasketId,
    List<OrderItemData> Items, // 自定义值对象,仅包含订单所需的商品信息
    decimal TotalAmount,
    string CouponCode,
    int ShippingMethod,
    int PaymentMethod) : IEvent;

// 订单项数据值对象(可放在Shared模块或事件所在模块)
public record OrderItemData(Guid ProductId, int Quantity, decimal UnitPrice);

在CheckoutBasketCommand的Handle方法中提取数据构建事件:

public async Task<Unit> Handle(CheckoutBasketCommand command, CancellationToken cancellationToken)
{
    var customerId = _currentUserService.UserId;
    var basket = await _basketRepository.GetBasketByCustomerId(customerId)
        ?? throw new NotFoundException("Basket not found.");

    await _unitOfWork.CommitAndDispatchDomainEventsAsync(basket);
    
    // 从Basket对象中提取订单所需的最小数据
    var orderItems = basket.Items.Select(item => new OrderItemData(
        item.ProductId,
        item.Quantity,
        item.UnitPrice)).ToList();
    
    await _eventDispatcher.PublishAsync(new BasketCheckedOutEvent(
        customerId,
        basket.Id,
        orderItems,
        basket.TotalAmount,
        command.CouponCode,
        command.ShippingMethod,
        command.PaymentMethod));

    return Unit.Value;
}

这个方案的核心是事件仅传递消费方(Order模块)需要的最少数据,完全隔离模块间的领域对象依赖,是DDD跨模块事件通信的首选方式。

方案二:Basket模块对外暴露只读查询服务

如果不想在事件中传递大量数据,可只传递BasketId,同时在Basket模块提供对外公开的只读查询服务,Order模块通过该服务获取创建订单所需的数据。

  1. 在Basket模块定义查询服务接口与实现:
// 接口可放在Shared模块,实现留在Basket模块内部
public interface IBasketQueryService
{
    Task<BasketOrderData> GetBasketOrderDataAsync(Guid basketId, CancellationToken cancellationToken);
}

// 仅包含订单所需字段的查询模型
public record BasketOrderData(
    Guid CustomerId,
    List<OrderItemData> Items,
    decimal TotalAmount);

public class BasketQueryService : IBasketQueryService
{
    private readonly IBasketRepository _basketRepository;

    public BasketQueryService(IBasketRepository basketRepository)
    {
        _basketRepository = basketRepository;
    }

    public async Task<BasketOrderData> GetBasketOrderDataAsync(Guid basketId, CancellationToken cancellationToken)
    {
        var basket = await _basketRepository.GetByIdAsync(basketId, cancellationToken)
            ?? throw new NotFoundException("Basket not found.");
        
        return new BasketOrderData(
            basket.CustomerId,
            basket.Items.Select(item => new OrderItemData(item.ProductId, item.Quantity, item.UnitPrice)).ToList(),
            basket.TotalAmount);
    }
}
  1. Order模块监听事件时,注入查询服务获取数据:
public class BasketCheckedOutEventHandler : IEventHandler<BasketCheckedOutEvent>
{
    private readonly IOrderRepository _orderRepository;
    private readonly IBasketQueryService _basketQueryService;

    public BasketCheckedOutEventHandler(IOrderRepository orderRepository, IBasketQueryService basketQueryService)
    {
        _orderRepository = orderRepository;
        _basketQueryService = basketQueryService;
    }

    public async Task Handle(BasketCheckedOutEvent @event, CancellationToken cancellationToken)
    {
        var basketData = await _basketQueryService.GetBasketOrderDataAsync(@event.BasketId, cancellationToken);
        
        // 使用获取到的basketData创建订单
        var order = Order.Create(
            basketData.CustomerId,
            basketData.Items,
            basketData.TotalAmount,
            @event.CouponCode,
            @event.ShippingMethod,
            @event.PaymentMethod);
        
        await _orderRepository.AddAsync(order, cancellationToken);
        await _unitOfWork.CommitAsync(cancellationToken);
    }
}

此方案既保持了Basket模块的封装性(仓储仅内部访问),又让Order模块能合法获取所需数据,适合数据量较大且不想在事件中传递过多数据的场景。

方案三:基于事件投影的共享只读存储

在Basket模块处理领域事件时,提前将订单需要的数据投影到共享的只读存储(如单独数据库表、视图或缓存),Order模块监听BasketCheckedOutEvent后,直接从该存储读取数据,无需依赖Basket模块的任何服务或仓储。

例如,在Basket模块添加投影处理器:

public class BasketToOrderProjectionHandler : IEventHandler<BasketCheckedOutEvent>
{
    private readonly ISharedReadOnlyRepository _sharedReadOnlyRepository;

    public BasketToOrderProjectionHandler(ISharedReadOnlyRepository sharedReadOnlyRepository)
    {
        _sharedReadOnlyRepository = sharedReadOnlyRepository;
    }

    public async Task Handle(BasketCheckedOutEvent @event, CancellationToken cancellationToken)
    {
        var basketProjection = new BasketOrderProjection(
            @event.BasketId,
            @event.CustomerId,
            @event.Items,
            @event.TotalAmount);
        
        await _sharedReadOnlyRepository.AddOrUpdateBasketProjectionAsync(basketProjection, cancellationToken);
    }
}

Order模块的事件处理器直接从共享存储读取投影数据:

public class BasketCheckedOutEventHandler : IEventHandler<BasketCheckedOutEvent>
{
    private readonly IOrderRepository _orderRepository;
    private readonly ISharedReadOnlyRepository _sharedReadOnlyRepository;

    public BasketCheckedOutEventHandler(IOrderRepository orderRepository, ISharedReadOnlyRepository sharedReadOnlyRepository)
    {
        _orderRepository = orderRepository;
        _sharedReadOnlyRepository = sharedReadOnlyRepository;
    }

    public async Task Handle(BasketCheckedOutEvent @event, CancellationToken cancellationToken)
    {
        var basketProjection = await _sharedReadOnlyRepository.GetBasketProjectionAsync(@event.BasketId, cancellationToken);
        
        // 创建订单逻辑
        var order = Order.Create(
            basketProjection.CustomerId,
            basketProjection.Items,
            basketProjection.TotalAmount,
            @event.CouponCode,
            @event.ShippingMethod,
            @event.PaymentMethod);
        
        await _orderRepository.AddAsync(order, cancellationToken);
        await _unitOfWork.CommitAsync(cancellationToken);
    }
}

这个方案适合高并发场景,能降低Basket模块的查询压力,但需要维护额外的投影存储,增加了系统复杂度。


内容的提问来源于stack exchange,提问作者Mateusz Babski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 03:24:57