当前位置: 首页 > news >正文

“事件风暴 → 上下文映射 → 模块化”在 ABP vNext 的全链路模板 - 详解

“事件风暴 → 上下文映射 → 模块化”在 ABP vNext 的全链路模板 ✨



0) 摘要(TL;DR)

本文交付一套从业务共创工作坊可运行工程骨架的闭环:
事件风暴 → 子域/上下文划分 → 上下文映射(关系/协作模式) → ABP 模块边界与依赖矩阵契约门禁(CI)伴随测试分阶段迁移老系统持续度量与反模式清单

总览图(从白板到上线)

事件风暴
Event Storming
上下文映射
Context Mapping
ABP 模块边界
七层分离 & DependsOn
契约清单
HTTP OpenAPI / 消息 Schema
️ CI 门禁
oasdiff / ArchRules / Pact
伴随测试
单元/集成/Testcontainers
渐进迁移
Strangler Fig
可观测 & 度量
耦合/破坏率/SLO

1) 工作坊与产出物

1.1 事件风暴(Event Storming)

  • 自上而下:Big PictureProcess/Design level
  • 把“命令 → 领域事件 → 聚合 → 读模型”排成时间线,沉淀统一语言(UL),形成“能力清单”。

仓库产出模板

/docs/event-storming/board.md          # 事件清单/照片转录
/docs/event-storming/glossary.yaml     # 统一语言词典
/docs/event-storming/capabilities.csv  # 能力项(为切上下文/模块做输入)

1.2 从事件风暴到上下文映射(Context Mapping)

  • 常见关系:Customer–Supplier、Conformist、ACL、Open Host、Published Language、Shared Kernel
  • 明确上游/下游、治理关系、语义边界与演进策略。

上下文映射示意

Downstream
Upstream
API/PL
翻译/对齐
ACL 防腐层
Sales
Conformist
Catalog
OHS + Published Language

2) 映射到 ABP 模块边界(工程化落地)️

2.1 模块命名与分层(建议 7 层)

约定命名:Company.Product.<Context>.*。每个上下文建议包含:

  • Domain.Shared / Domain
  • Application.Contracts / Application
  • HttpApi / HttpApi.Client
  • EntityFrameworkCore(或 MongoDB

依赖方向(只允许“向内”)

Web/UI
HttpApi
Application.Contracts
Application
Domain
EntityFrameworkCore

关键约束:

  • HttpApi仅依赖Application.Contracts(不依 Application 实现)。
  • HttpApi.Client仅依赖Application.Contracts
  • ORM 集成层仅依赖 Domain
  • 严禁跨上下文直连仓储,一律通过 契约(HTTP/消息)

2.2 ABP CLI:创建解决方案与模块

安装/更新 CLI

dotnet tool install -g Volo.Abp.Studio.Cli
# 或
dotnet tool update -g Volo.Abp.Studio.Cli

新建解决方案(MVC 示例)

abp new Contoso.SalesSuite -t app -u mvc

为上下文创建 DDD 模块并加入解决方案Studio CLI 前缀:abpc):

cd Contoso.SalesSuite
abpc new-module Contoso.Sales   -t module:ddd -ts Contoso.SalesSuite.sln
abpc new-module Contoso.Billing -t module:ddd -ts Contoso.SalesSuite.sln
abpc new-module Contoso.Catalog -t module:ddd -ts Contoso.SalesSuite.sln

3) 上下文通信:HttpApi.Client 动态代理 + Polly(重试/熔断)

端点配置(消费者侧 appsettings.json

{
"RemoteServices": {
"Default": { "BaseUrl": "https://localhost:5001/" },
"Billing": { "BaseUrl": "https://localhost:6001/" }
}
}

注册动态代理 + Polly(关键扩展点 ProxyClientBuildActions

[DependsOn(typeof(AbpHttpClientModule),
typeof(Contoso.Billing.ApplicationContractsModule))]
public class Contoso.BillingClientModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpHttpClientBuilderOptions>(options =>{options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) =>{var jitter = new Random();clientBuilder.AddTransientHttpErrorPolicy(pb =>pb.WaitAndRetryAsync(3, i =>TimeSpan.FromSeconds(Math.Pow(2, i)) +TimeSpan.FromMilliseconds(jitter.Next(0, 150))));});});}public override void ConfigureServices(ServiceConfigurationContext context){context.Services.AddHttpClientProxies(typeof(Contoso.Billing.ApplicationContractsModule).Assembly,remoteServiceConfigurationName: "Billing");}}

调用时序图

Sales(Consumer)HttpApi.Client ProxyHTTPBilling(HttpApi)调用 IInvoiceAppService.Get("123")HTTP GET /api/invoices/123转发至 Controller200 OK / JSONopt[Retry(指数退避 +抖动 3 次)]DTOSales(Consumer)HttpApi.Client ProxyHTTPBilling(HttpApi)

4) 一致性:分布式事件总线 + Outbox/Inbox(EF Core)

DbContext 接线(最小示例)

public class SalesDbContext : AbpDbContext<SalesDbContext>, IHasEventOutbox, IHasEventInbox{public DbSet<OutgoingEventRecord> OutgoingEvents { get; set; }public DbSet<IncomingEventRecord> IncomingEvents { get; set; }protected override void OnModelCreating(ModelBuilder builder){base.OnModelCreating(builder);builder.ConfigureEventOutbox();builder.ConfigureEventInbox();}}

模块中绑定 Outbox/Inbox 到事件总线

public class SalesEntityFrameworkCoreModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpDistributedEventBusOptions>(o =>{o.Outboxes.Configure(c => c.UseDbContext<SalesDbContext>());o.Inboxes.Configure(c  => c.UseDbContext<SalesDbContext>());});}}

处理流示意

应用服务
Save + Publish
本地事务
业务表
Outbox(OutgoingEvents)
Outbox Processor
消息中间件
Consumer Service
Inbox(IncomingEvents)
幂等处理
业务逻辑

多实例需配置分布式锁(如 Redis)防止重复并发处理;事件载荷中建议携带 TenantId,消费端使用 using (CurrentTenant.Change(...)) 切换。


5) 多租户与数据边界

服务侧按租户执行

public class SalesReportAppService : ApplicationService
{
private readonly IRepository<Order, Guid> _orders;public SalesReportAppService(IRepository<Order, Guid> orders) => _orders = orders;public async Task<long> CountOrdersAsync(Guid tenantId){using (CurrentTenant.Change(tenantId)){return await _orders.GetCountAsync();}}}

原则:上下文内统一通过 ICurrentTenant 获取租户,严禁跨上下文“越界”读写他域租户数据。


6) 契约门禁(流水线守门员)️

6.1 API 契约:Swashbuckle CLI + oasdiff(检测破坏性变更)

生成 OpenAPI(构建后导出)

dotnet tool install --global Swashbuckle.AspNetCore.Cli
dotnet swagger tofile --output ./artifacts/api.v1.json \
./src/Contoso.Sales.HttpApi.Host/bin/Release/net8.0/Contoso.Sales.HttpApi.Host.dll v1

CI 检查(GitHub Actions 片段)

- name: Check OpenAPI breaking changes
uses: Tufin/oasdiff-action@v2.1.3
with:
base: './contracts/api/sales.v1.json'
revision: './artifacts/api.v1.json'
check-breaking: true
fail-on-diff: true

6.2 架构门禁:NetArchTest / ArchUnitNET

using NetArchTest.Rules;
using Xunit;
public class ArchitectureTests
{
[Fact]
public void Catalog_Should_Not_Depend_On_Billing_EFCore()
{
var result = Types.InAssemblies(AppDomain.CurrentDomain.GetAssemblies())
.That().ResideInNamespace("Contoso.Catalog", true)
.ShouldNot().HaveDependencyOn("Contoso.Billing.EntityFrameworkCore")
.GetResult();
Assert.True(result.IsSuccessful, string.Join("\n", result.FailingTypeNames));
}
}

CI 编排图

无破坏
通过
通过
有破坏
违规
失败
开发者提交 PR
构建/测试
生成 OpenAPI
oasdiff breaking
️ NetArchTest 规则
PactNet 合同测试
✅ 合并
⛔ 退回并评论

7) 伴随测试(从用例到回归)

7.1 契约测试(PactNet v4 Consumer 示例)——已换为 .WithHttpInteractions()

var pact = Pact.V4("SalesConsumer", "BillingProvider", new PactConfig { PactDir = "../../../pacts" })
.WithHttpInteractions();
pact.UponReceiving("get invoice")
.WithRequest(HttpMethod.Get, "/api/invoices/123")
.WillRespond()
.WithStatus(HttpStatusCode.OK)
.WithJsonBody(new { id = Match.Type("123"), amount = Match.Decimal(10.5) });
await pact.VerifyAsync(async ctx => {
var client = new HttpClient { BaseAddress = ctx.MockServerUri };
var res = await client.GetAsync("/api/invoices/123");
res.EnsureSuccessStatusCode();
});

7.2 组件/集成:Testcontainers(PostgreSQL/RabbitMQ)

带等待策略与测试集合夹具(复用容器,提速 & 稳定)

[CollectionDefinition("integration-shared")]
public class IntegrationSharedCollection : ICollectionFixture<SharedContainers> { }public class SharedContainers : IAsyncLifetime{public PostgreSqlContainer Pg { get; private set; } = null!;public RabbitMqContainer Mq { get; private set; } = null!;public async Task InitializeAsync(){Pg = new PostgreSqlBuilder().WithImage("postgres:16-alpine").Build();Mq = new RabbitMqBuilder().WithImage("rabbitmq:3-management-alpine").Build();await Pg.StartAsync();await Mq.StartAsync();}public async Task DisposeAsync(){await Mq.DisposeAsync();await Pg.DisposeAsync();}public string Db => Pg.GetConnectionString();public string Amqp => Mq.GetConnectionString();}

8) 分阶段迁移老系统(Strangler Fig)

Phase 0 盘点
域/能力/耦合图谱
️ Phase 1 围栏
ACL + 只读镜像
Phase 2 影子/双写
对账+差异报表门禁
Phase 3 切换
分段接管/回退剧本
发布复盘
破坏率/回滚率/改进项

反模式红线:共享数据库、跨上下文事务、DTO 当领域模型复用、HttpApi 依赖 Application 实现、跨境引用 *.EntityFrameworkCore


9) 可观测与演进度量


10) 工程骨架(落地目录)️

/src/Contoso.Sales.Domain.Shared/Contoso.Sales.Domain/Contoso.Sales.Application.Contracts/Contoso.Sales.Application/Contoso.Sales.HttpApi/Contoso.Sales.HttpApi.Client/Contoso.Sales.EntityFrameworkCore/Contoso.Billing.(同上)/Contoso.Catalog.(同上)
/contracts/api/sales.v1.json/api/billing.v1.json/messages/.schema.json
/quality-gates/ApiCompat          # oasdiff 产物与基线/ArchRules.Tests    # 架构规则测试
/tests/Sales.AcceptanceTests/Sales.ComponentTests
/docs/event-storming/*/context-map/*
http://www.jsqmd.com/news/34165/

相关文章:

  • LRU缓存科普与实现(Kotlin 与 Swift) - 实践
  • 十六、Linux网络基础理论 - OSI模型、TCP/IP协议与IP地址详解 - 教程
  • Physicians High School Chemistry
  • 深入解析:【Unity】uNet游戏服务端框架(一)服务端架构设计
  • Python哈希机制深度解析:从底层原理到工程实践
  • LaTeX学习笔记:数学公式编辑
  • 飞牛OS Root用户SSH公钥登录完整教程
  • 红黑树简
  • ⸢ 柒-Ⅲ⸥⤳ 可信纵深防御建设方案:数据使用可信端安全可信 - 详解
  • 【日记】我从来没见过有酒店这么设计的(533 字)
  • 第181-182天:横向移动篇PTH哈希PTT票据PTK密匙Kerberoast攻击点TGTNTLM爆破
  • 2025年上海装修设计标杆公司最新推荐:中古风装修/轻法式装修/现代简约装修/极简风装修、上海千祥建筑定义品质居住新标准
  • AI在开源情报搜集系统中的应用汇总
  • 清理docker的overlay2目录
  • 升鲜宝生鲜配送供应链管理系统---PMS--商品品牌多语言存储与 Redis 缓存同步实现
  • 网站在苹果 Safari 进行适配遇到的问题
  • Python对象模型的认知陷阱:类的`__name__`属性与名字绑定的本质辨析
  • Python环境教程(三)-环境高级之uv pixi
  • 升鲜宝生鲜配送供应链管理系统---PMS 商品模块 + 动态翻译设计说明
  • 深入浅出 SPA/MPA
  • CPP 学习笔记 语法总结 - 阿源
  • 2025 11 7
  • 深入解析:大数据集群环境搭建(Ubantu)
  • Unity Shader 入门教程:从零开始编写你的第一个 Shader
  • 中电金信​​:「AI智变」这个AI自动化工厂,助力模型高质效落地
  • 实用指南:Linux内核架构浅谈2- Linux内核与硬件交互的底层逻辑:硬件抽象层的作用
  • 2025年有机纯驼奶粉加工厂权威推荐榜单:初乳配方驼乳粉/全脂羊奶粉/绵羊奶粉源头厂家精选
  • 2025年网络安全法要求下,主流项目管理工具如何选
  • 转录组基因表达差异分析全流程:以GSE65682为例
  • 英伟达DCGM说明和安装