.NET 9模块化单体(Modulith)跨模块调用方案咨询
针对.NET 9模块化单体跨模块调用的替代方案
方案1:独立契约包+轻量本地事件总线
核心思路是将跨模块的事件/消息契约从Base模块剥离,放到独立的小型类库(如ContratosComuns),各模块按需引用该契约包,Base模块仅提供轻量的事件总线核心实现,避免Base臃肿。
实现步骤:
- 创建独立的
ContratosComuns类库,定义跨模块事件:
public class RecebimentoCompraCriadoEvent { public int CompraId { get; set; } public decimal ValorTotal { get; set; } }
- Base模块实现轻量内存事件总线(无需依赖第三方库):
public interface IEventBus { void Publish<TEvent>(TEvent @event) where TEvent : class; void Subscribe<TEvent>(Action<TEvent> handler) where TEvent : class; } public class InMemoryEventBus : IEventBus { private readonly Dictionary<Type, List<Delegate>> _handlers = new(); public void Publish<TEvent>(TEvent @event) where TEvent : class { if (_handlers.TryGetValue(typeof(TEvent), out var handlers)) { foreach (var handler in handlers) { ((Action<TEvent>)handler).Invoke(@event); } } } public void Subscribe<TEvent>(Action<TEvent> handler) where TEvent : class { var eventType = typeof(TEvent); _handlers.TryAdd(eventType, new List<Delegate>()); _handlers[eventType].Add(handler); } }
- 在Base的DI容器中注册
IEventBus:
builder.Services.AddSingleton<IEventBus, InMemoryEventBus>();
- RecebimentoCompras模块引用
ContratosComuns,在新增操作后发布事件:
public class RecebimentoCompraService { private readonly IEventBus _eventBus; public RecebimentoCompraService(IEventBus eventBus) { _eventBus = eventBus; } public void CriarRecebimento(CompraNovaDto dto) { // 执行RecebimentoCompras的新增逻辑 var recebimento = new RecebimentoCompra { Id = dto.Id, Valor = dto.Valor }; _dbContext.Recebimentos.Add(recebimento); _dbContext.SaveChanges(); // 发布事件 _eventBus.Publish(new RecebimentoCompraCriadoEvent { CompraId = recebimento.Id, ValorTotal = recebimento.Valor }); } }
- Faturamento模块引用
ContratosComuns,通过模块初始化器订阅事件:
public class FaturamentoService { public void ProcessarFaturamentoParaRecebimento(RecebimentoCompraCriadoEvent @event) { // 执行Faturamento模块的业务逻辑 var fatura = new Fatura { CompraId = @event.CompraId, Valor = @event.ValorTotal }; // 保存或处理发票逻辑 } } // 使用.NET 9的ModuleInitializer自动注册订阅 public static class FaturamentoModuleSetup { [ModuleInitializer] public static void Initialize() { var eventBus = ServiceProviderAccessor.Current.GetService<IEventBus>(); eventBus.Subscribe<RecebimentoCompraCriadoEvent>( new FaturamentoService().ProcessarFaturamentoParaRecebimento); } }
优缺点:
- 优点:完全解耦模块,Base模块仅负责总线核心,契约独立管理,各模块可单独编译发布。
- 缺点:异步调用模式,需自行处理事件重试、幂等性问题;内存总线重启后事件会丢失,若需持久化可搭配本地消息队列(如SQL Server队列)。
方案2:独立契约包+依赖反转(接口驱动)
通过在独立契约包中定义服务接口,Faturamento模块实现该接口,RecebimentoCompras模块通过DI获取接口实例调用,避免Base模块包含业务相关接口。
实现步骤:
- 创建
ContratosComuns类库,定义跨模块服务接口:
public interface IFaturamentoProcessamentoService { void ProcessarFaturamento(int compraId, decimal valorTotal); }
- Faturamento模块引用
ContratosComuns,实现接口:
public class FaturamentoProcessamentoService : IFaturamentoProcessamentoService { public void ProcessarFaturamento(int compraId, decimal valorTotal) { // Faturamento模块的业务逻辑实现 } }
- Faturamento模块通过ModuleInitializer注册服务到DI:
public static class FaturamentoModuleSetup { [ModuleInitializer] public static void Initialize(IServiceCollection services) { services.AddScoped<IFaturamentoProcessamentoService, FaturamentoProcessamentoService>(); } }
- RecebimentoCompras模块引用
ContratosComuns,通过DI注入接口并调用:
public class RecebimentoCompraService { private readonly IFaturamentoProcessamentoService _faturamentoService; public RecebimentoCompraService(IFaturamentoProcessamentoService faturamentoService) { _faturamentoService = faturamentoService; } public void CriarRecebimento(CompraNovaDto dto) { // 新增RecebimentoCompras逻辑 _dbContext.Recebimentos.Add(new RecebimentoCompra { Id = dto.Id, Valor = dto.Valor }); _dbContext.SaveChanges(); // 直接调用Faturamento模块的方法 _faturamentoService.ProcessarFaturamento(dto.Id, dto.Valor); } }
优缺点:
- 优点:同步调用,类型安全,模块间仅依赖契约接口,Base模块无业务耦合。
- 缺点:模块间存在显式依赖(RecebimentoCompras依赖契约接口),若接口频繁变更需同步更新契约包和相关模块。
方案3:模块间弱引用(仅依赖接口项目)
如果两个模块的依赖关系稳定,可让RecebimentoCompras模块仅引用Faturamento的接口项目(而非实现项目),实现编译时依赖接口、运行时依赖实现的弱耦合。
实现步骤:
- Faturamento模块拆分出
Faturamento.Contracts类库,定义对外暴露的接口:
public interface IFaturamentoService { void GerarFatura(int compraId, decimal valor); }
- Faturamento的实现项目
Faturamento.Implementation引用Faturamento.Contracts并实现接口:
public class FaturamentoService : IFaturamentoService { public void GerarFatura(int compraId, decimal valor) { // 业务逻辑 } }
- RecebimentoCompras模块引用
Faturamento.Contracts,通过DI注入接口调用:
public class RecebimentoService { private readonly IFaturamentoService _faturamentoService; public RecebimentoService(IFaturamentoService faturamentoService) { _faturamentoService = faturamentoService; } public void CriarRecebimento(CompraDto dto) { // 新增逻辑 _faturamentoService.GerarFatura(dto.Id, dto.Valor); } }
- Base模块通过扫描所有模块的程序集,自动注册实现类:
builder.Services.Scan(scan => scan .FromAssembliesOf(typeof(IFaturamentoService)) .AddClasses() .AsImplementedInterfaces());
优缺点:
- 优点:实现简单,同步调用类型安全,各模块可单独编译(修改Faturamento实现无需重新编译RecebimentoCompras)。
- 缺点:模块间存在定向依赖,若后续依赖关系变化可能需要调整模块引用结构,不适合频繁变更的业务场景。
方案选择建议
- 若模块间交互频繁且需要解耦,优先选择方案1(事件总线)。
- 若需要同步调用且业务逻辑依赖稳定,优先选择方案2(依赖反转)。
- 若依赖关系非常稳定且追求简单实现,可选用方案3(弱引用)。
所有方案均保持Base模块的轻量化,仅负责核心基础设施(DI、总线核心等),业务相关契约/接口均独立管理,满足模块化单体的单独编译发布需求。
内容的提问来源于stack exchange,提问作者Iury
相关产品推荐
相关产品推荐

