百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

在多模块单体应用中使用 Outbox/Inbox 模式实现可靠的事件处理

ccwgpt 2025-04-08 12:27 28 浏览 0 评论

本文介绍如何在使用多个数据库的模块化单体应用中, 通过 Outbox/Inbox 模式实现可靠的事件处理. 我们将以 ModularCRM 项目为例进行说明.

项目背景

ModularCRM 是一个集成了多个 ABP 框架开源模块的单体应用, 包括:

  • Account
  • Identity
  • Tenant Management
  • Permission Management
  • Setting Management
  • 等开源模块

除了ABP框架开源模块外, 项目还包含三个业务模块:

  • 订单模块(Products), 使用 MongoDB 数据库
  • 产品模块(Ordering), 使用 SQL Server 数据库
  • 支付模块(Payment), 使用 MongoDB 数据库

项目在 appsettings.json 中分别为 ModularCRM 和三个业务模块配置了独立的数据库连接字符串:

{
"ConnectionStrings": {
"Default": "Server=localhost,1434;Database=ModularCrm;User Id=sa;Password=1q2w3E***;TrustServerCertificate=true",
"Products": "Server=localhost,1434;Database=ModularCrm_Products;User Id=sa;Password=1q2w3E***;TrustServerCertificate=true",
"Ordering": "mongodb://localhost:27017/ModularCrm_Ordering?replicaSet=rs0",
"Payment": "mongodb://localhost:27017/ModularCrm_Payment?replicaSet=rs0"
}
}

业务场景

这些模块通过 ABP 框架的 DistributedEventBus 进行通信, 实现以下业务流程:

这里我们以一个简单的业务流程为例, 实际业务流程会更复杂. 示例代码主要用于演示和问题解决.

  1. 订单模块: 用户下单后发布 OrderPlacedEto 事件
  2. 产品模块: 订阅 OrderPlacedEto 事件后更新产品库存
  3. 支付模块: 订阅 OrderPlacedEto 事件后处理支付, 完成后发布 PaymentCompletedEto 事件
  4. 订单模块: 订阅 PaymentCompletedEto 事件后更新订单状态

实现这个流程时, 我们需要确保:

  • 下单操作和事件发布的事务一致性
  • 各模块处理消息时的事务一致性
  • 消息传递的可靠性(包括持久化、确认和重试机制)

仅使用 ABP 框架的 DistributedEventBus 无法满足上述要求, 因此我们需要引入新的机制.

Outbox/Inbox 模式解决方案

为了满足上述要求,我们采用 Outbox/Inbox 模式:

Outbox 模式

  • 将分布式事件与数据库操作在同一事务中保存
  • 通过后台作业将事件发送到分布式消息中间件
  • 确保数据更新与事件发布的一致性
  • 防止系统故障期间的消息丢失

Inbox 模式

  • 先将接收到的分布式事件保存到数据库
  • 通过事务性方式处理事件
  • 通过保存已处理消息来确保消息只被处理一次
  • 维护处理状态以实现可靠处理

如何在项目和模块中启用和配置 Outbox/Inbox, 请参考:
https://abp.io/docs/latest/framework/infrastructure/event-bus/distributed#
outbox-inbox-for-transactional-events

模块配置

每个模块需要配置独立的 Outbox/Inbox. 由于是单体应用, 所有消息处理类都在同一个项目中, 我们需要为每个模块配置 Outbox/InboxSelector/EventSelector, 以确保模块只发送和接收它关注的消息, 避免消息重复处理.

ModularCRM 主应用配置

它会发送和接收所有ABP框架开源模块的消息.

// This selector will match all abp built-in modules and the current module.
Func<Type, bool> abpModuleSelector = type => type.Namespace != && (type.Namespace.StartsWith("Volo.") || type.Assembly == typeof(ModularCrmModule).Assembly);

Configure(options =>
{
options.Inboxes.Configure("ModularCrm", config =>
{
config.UseDbContext();
config.EventSelector = abpModuleSelector;
config.HandlerSelector = abpModuleSelector;
});

options.Outboxes.Configure("ModularCrm", config =>
{
config.UseDbContext();
config.Selector = abpModuleSelector;
});
});

订单模块配置

它只发送OrderPlacedEto事件, 并接收PaymentCompletedEto事件和执行
OrderPaymentCompletedEventHandler
.

Configure(options =>
{
options.Inboxes.Configure(OrderingDbProperties.ConnectionStringName, config =>
{
config.UseMongoDbContext();
config.EventSelector = type => type == typeof(PaymentCompletedEto);
config.HandlerSelector = type => type == typeof(OrderPaymentCompletedEventHandler);
});

options.Outboxes.Configure(OrderingDbProperties.ConnectionStringName, config =>
{
config.UseMongoDbContext();
config.Selector = type => type == typeof(OrderPlacedEto);
});
});

产品模块配置

它只接收EntityCreatedEtoOrderPlacedEto事件, 并执行
ProductsOrderPlacedEventHandler

ProductsUserCreatedEventHandler
. 暂时不需要发送任何事件.

Configure(options =>
{
options.Inboxes.Configure(ProductsDbProperties.ConnectionStringName, config =>
{
config.UseDbContext();
config.EventSelector = type => type == typeof(EntityCreatedEto) || type == typeof(OrderPlacedEto);
config.HandlerSelector = type => type == typeof(ProductsOrderPlacedEventHandler) || type == typeof(ProductsUserCreatedEventHandler);
});

// Outboxes are not used in this module
options.Outboxes.Configure(ProductsDbProperties.ConnectionStringName, config =>
{
config.UseDbContext();
config.Selector = type => false;
});
});

支付模块配置

它只发送PaymentCompletedEto事件, 并接收OrderPlacedEto事件和执行
PaymentOrderPlacedEventHandler
.

Configure(options =>
{
options.Inboxes.Configure(PaymentDbProperties.ConnectionStringName, config =>
{
config.UseMongoDbContext();
config.EventSelector = type => type == typeof(OrderPlacedEto);
config.HandlerSelector = type => type == typeof(PaymentOrderPlacedEventHandler);
});

options.Outboxes.Configure(PaymentDbProperties.ConnectionStringName, config =>
{
config.UseMongoDbContext();
config.Selector = type => type == typeof(PaymentCompletedEto);
});
});

运行ModularCRM模拟业务流程

  1. ModularCrm 目录下运行:
# 在Docker中启动SQL Server和MongoDB数据库
docker-compose up -d

# 还原安装依赖项
abp install-lib

# 迁移数据库
dotnet run --project ModularCrm --migrate-database

# 启动应用
dotnet run --project ModularCrm
  • 访问 https://localhost:44303/ 进入应用首页

  • 输入一个客户名称然后选择一个产品并提交一个订单. 稍等片刻后刷新页面可以看到订单,产品以及支付信息.

系统日志显示完整的处理流程:

[Ordering Module] Order created: OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88, ProductId: 0f95689f-4cb6-36f5-68bd-3a18344d32c9, CustomerName: john

[Products Module] OrderPlacedEto event received: OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88, CustomerName: john, ProductId: 0f95689f-4cb6-36f5-68bd-3a18344d32c9
[Products Module] Stock count decreased for ProductId: 0f95689f-4cb6-36f5-68bd-3a18344d32c9

[Payment Module] OrderPlacedEto event received: OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88, CustomerName: john, ProductId: 0f95689f-4cb6-36f5-68bd-3a18344d32c9
[Payment Module] Payment processing completed for OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88

[Ordering Module] PaymentCompletedEto event received: OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88, PaymentId: d0a41ead-ee0f-714c-e254-3a1834504d65, PaymentMethod: CreditCard, PaymentAmount: ModularCrm.Payment.Payment.PaymentCompletedEto
[Ordering Module] Order state updated to Delivered for OrderId: b7ad3f47-0e77-bb81-082f-3a1834503e88

此外,当新用户注册时,产品模块还会接收到 EntityCreatedEto 事件, 我们会给新用户发送一个邮件, 这只是为了演示Outbox/Inbox的Selector机制.

[Products Module] UserCreated event received: UserId: "9a1f2bd0-5b28-210a-9e56-3a18344d310a", UserName: admin
[Products Module] Sending a popular products email to admin@abp.io...

总结

通过引入 Outbox/Inbox 模式, 我们实现了:

  1. 事务性的消息发送和接收
  2. 可靠的消息处理机制
  3. 多数据库环境下的模块化事件处理

ModularCRM 项目不仅实现了可靠的消息处理, 还展示了如何在单体应用中优雅地处理多数据库场景. 项目源码:
https://github.com/abpframework/abp-samples/tree/master/ModularCrm-OutboxInbox-Pattern

参考资料

  • Outbox/Inbox for transactional events https://abp.io/docs/latest/framework/infrastructure/event-bus/distributed#outbox-inbox-for-transactional-events
  • ConnectionStrings https://abp.io/docs/latest/framework/fundamentals/connection-strings
  • ABP Studio: Single Layer Solution Template https://abp.io/docs/latest/solution-templates/single-layer-web-application

相关推荐

谷歌正在为Play商店进行Material Design改造

谷歌最近一直忙于在其应用程序中完成MaterialDesign风格的改造,而Play商店似乎是接下来的一个。9to5Google网站报道,有用户在Play商店的最新版本中发现了新界面,暗示该应用和网...

企业网站免费搭建,定制化建站CMS系统

科腾软件企业网站CMS管理系统已完成开发工作,首次开源(全部源码)发布。开发工具:VisualStudioEnterprise2022数据库:SQLite(零配置,跨平台,嵌入式)开发...

您需要的 11 个免费 Chrome 扩展程序

来源:SEO_SEM营销顾问大师Chrome扩展程序是SEO的无名英雄,他们在幕后默默工作,使您的策略脱颖而出并提高您的努力效率。从竞争对手研究到审核您的网站,速度比您说“元描述”还快,这些小工具发...

户外便携设备抗干扰困境如何破局?CMS-160925-078S-67给出答案

  在户外复杂的电磁环境中,便携式设备中的扬声器需具备出色抗干扰能力,CUID的CMS-160925-078S-67在这方面表现突出。  从其结构设计来看,矩形框架虽主要为适配紧凑空...

一个基于NetCore开发的前后端分离CMS系统

今天给大家推荐一个开源的前后端分离架构的CMS建站系统。项目简介这是一个基于.Net3构建的简单、跨平台、模块化建站系统。系统业务简单、代码清晰、层级分明、全新架构便于二次扩展开发。支持多种数据库,...

本地Docker部署ZFile网盘打造个人云存储

前言本文主要介绍如何在LinuxUbuntu系统使用Docker本地部署ZFile文件管理系统,并结合cpolar内网穿透工具实现远程访问本地服务器上的ZFile传输与备份文件,轻松搭建个人网盘,无...

pcfcms企业建站系统 免费+开源的企业内容管理系统

项目介绍pcfcms是基于TP6.0框架为核心开发的免费+开源的企业内容管理系统,专注企业建站用户需求提供海量各行业模板,降低中小企业网站建设、网络营销成本,致力于打造用户舒适的建站体验。演示站...

【推荐】一个高颜值且功能强大的 Vue3 后台管理系统框架

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!项目介绍SnowAdmin是一款基于Vue3、TypeScript、Vite5、Pinia、Arco-Desi...

java开源cms管理系统框架PublicCMS后台管理系统

一款使用Java语言开发的CMS,提供文章发布,图片展示,文件下载,用户权限、站点模块,内容管理、分类等功能。可免费用于商业用途maven工程数据库脚本在工程中database文件夹下代码结构:效果...

一定要大量读书:当我问Deepseek,它给出的高效阅读方法厉害了!

一年一度的世界读书日,总该写点什么。于是,我去问Deepseek给我推荐人生破局必读的10本书,结果它给了我回复,竟然10本推荐的书籍里,我都曾经浏览过,同时还给出破局关键。而说浏览过,不是读过,是因...

《搜神札记》:不应磨灭的惊奇(小说《搜神记》)

□黄勃志怪传说的书写一直是文人墨客的后花园,晚近尤盛,从张岱到袁枚到纪昀,收集那些或阴森或吊诡的行状故事,遂成一类,到民国年间,周作人挟此遗传,捋袖子拿希腊神话动刀,乃兄鲁迅不甘其后,《故事新编》虎...

《如何构建金字塔》之第三章总结(构建金字塔结构的方法有)

“没有什么比一套好理论更有用了。”——库尔特.勒温这篇读后感依然引用了这句库尔特.勒温名言,这句话也是我读芭芭拉.明托这本书的初衷。今天就“如何构建金字塔”,我来谈谈我的读后心得。我热爱写作,但是写...

《助人技术》第一章助人引论内容框架

第一章内容基本呈现如何成为助人者(心理咨询师)以及一些相关基础知识,对于进入这个行业有兴趣以及希望通过心理咨询寻求帮助但存有疑虑的当事人,都值得一读。心理咨询的三个阶段(不是说严格的三个阶段,而是广义...

AI助手重构读后感写作流程:从提纲到完整性思考的转换

大家好!你有没有遇到过读完一本书,想要写读后感,却不知道从何下手的情况呢?今天我们要来探讨一下如何利用稿见AI助手来重构读后感写作流程,从提纲到完整性思考的转换。让我们一起来看看这个全新而又实用的方法...

图解用思维导图做读书笔记技巧(图解用思维导图做读书笔记技巧视频)

做阅读笔记非常有利于读后进行有效的深入思考,而思维导图这一强大的工具其最大的特点就是架构清晰,在阅读过程中对文章的分析、总结、分类起着很大的辅助作用。思维导图读书笔记步骤:1、阅读大纲。首先要快速浏览...

取消回复欢迎 发表评论: