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

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

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


📚 目录

  • “事件风暴 → 上下文映射 → 模块化”在 ABP vNext 的全链路模板 ✨
    • 0) 摘要(TL;DR)📝
    • 1) 工作坊与产出物 🤝
      • 1.1 事件风暴(Event Storming)
      • 1.2 从事件风暴到上下文映射(Context Mapping)
    • 2) 映射到 ABP 模块边界(工程化落地)🏗️
      • 2.1 模块命名与分层(建议 7 层)
      • 2.2 ABP CLI:创建解决方案与模块
    • 3) 上下文通信:HttpApi.Client 动态代理 + Polly(重试/熔断)🔗
    • 4) 一致性:分布式事件总线 + Outbox/Inbox(EF Core)📨
    • 5) 多租户与数据边界 🏢
    • 6) 契约门禁(流水线守门员)🛡️
      • 6.1 API 契约:Swashbuckle CLI + oasdiff(检测破坏性变更)
      • 6.2 架构门禁:NetArchTest / ArchUnitNET
    • 7) 伴随测试(从用例到回归)🧪
      • 7.1 契约测试(PactNet v4 Consumer 示例)——**已换为 `.WithHttpInteractions()`**
      • 7.2 组件/集成:Testcontainers(PostgreSQL/RabbitMQ)
    • 8) 分阶段迁移老系统(Strangler Fig)🌿
    • 9) 可观测与演进度量 📈
    • 10) 工程骨架(落地目录)🗂️


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 Proxy🌐 HTTP🧩 Billing(HttpApi)调用 IInvoiceAppService.Get("123")HTTP GET /api/invoices/123转发至 Controller200 OK / JSONopt[Retry(指数退避 +抖动 3 次)]DTO🧩 Sales(Consumer)🤖 HttpApi.Client Proxy🌐 HTTP🧩 Billing(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 changesuses: Tufin/oasdiff-action@v2.1.3with:base: './contracts/api/sales.v1.json'revision: './artifacts/api.v1.json'check-breaking: truefail-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) 可观测与演进度量 📈

  • 架构健康度:模块耦合方向稳定性、门禁通过率、契约破坏率。
  • 业务健康度:关键事件吞吐/延迟、失败率、回滚率。
  • 自动化文档:CI 生成 Context Map / 依赖图;版本附“架构体检报告”。

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/<topic>.schema.json
/quality-gates/ApiCompat          # oasdiff 产物与基线/ArchRules.Tests    # 架构规则测试
/tests/Sales.AcceptanceTests/Sales.ComponentTests
/docs/event-storming/*/context-map/*
http://www.dtcms.com/a/473617.html

相关文章:

  • 如何在Linux服务器上部署jenkins?
  • 2.1 阵列信号处理基础
  • Centos7下docker的jenkins下载并配置jdk与maven
  • 网络数据侦探:抓包工具在爬虫开发中的艺术与科学
  • 手搓docker - 实现篇
  • soho做网站谷歌推广网站建设采购项目
  • 深入理解HTTP协议的本质
  • 以太网通信
  • 网站运营推广方式网站建设需要学编程么
  • 开源合规:GPL-3.0项目的专利风险规避
  • Java基于SpringBoot的医院门诊管理系统,附源码+文档说明
  • windows查询与设备通讯的mac地址
  • Tauri Android 开发踩坑实录:从 Gradle 版本冲突到离线构建成功
  • nuxt3中使用defineAsyncComponent懒加载组件,但其中的loadingComponent和errorComponent为什么不生效
  • GIS中最常用的编程语言
  • 用wordpress做的网站有哪些公司网站建设成本
  • 网站网页怎么设计无代码开发软件
  • 阿里发布「夸克 AI 眼镜」:融合阿里购物、地图、支付生态;苹果拟收购计算机视觉初创 Prompt AI丨日报
  • 【精品模板鉴赏】WORD版企业IT管理参考资料模板-数据安全|信息安全|网络安全|应急预案|灾备恢复..
  • Vue 核心特性详解:计算属性、监听属性与事件交互实战指南
  • 建设银行 嘉定 网站ai的优点和缺点
  • LeetCode 刷题【115. 不同的子序列】
  • 图像去雾之 Retinex 算法
  • 为什么 React 推荐 “不可变更新”:深入理解 React 的核心设计理念
  • 模型缝合的思想和步骤
  • 【基础算法】DFS中的剪枝与优化
  • 做暧昧视频网站做网页用什么软件写代码
  • Migo报错,可直接记账的提醒
  • 甘肃温室大棚建设网站佛山网页网站设计多少钱
  • js绑定事件的方法有几种?