“事件风暴 → 上下文映射 → 模块化”在 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

bash 复制代码
dotnet tool install -g Volo.Abp.Studio.Cli
# 或
dotnet tool update -g Volo.Abp.Studio.Cli

新建解决方案(MVC 示例)

bash 复制代码
abp new Contoso.SalesSuite -t app -u mvc

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

bash 复制代码
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

json 复制代码
{
  "RemoteServices": {
    "Default": { "BaseUrl": "https://localhost:5001/" },
    "Billing": { "BaseUrl": "https://localhost:6001/" }
  }
}

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

csharp 复制代码
[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 转发至 Controller 200 OK / JSON opt [Retry(指数退避 + 抖动 3 次)] DTO 🧩 Sales(Consumer) 🤖 HttpApi.Client Proxy 🌐 HTTP 🧩 Billing(HttpApi)


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

DbContext 接线(最小示例)

csharp 复制代码
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 到事件总线

csharp 复制代码
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) 多租户与数据边界 🏢

服务侧按租户执行

csharp 复制代码
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(构建后导出)

bash 复制代码
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 片段)

yaml 复制代码
- 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

csharp 复制代码
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()

csharp 复制代码
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)

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

csharp 复制代码
[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/*
相关推荐
Light606 天前
领码方案|微服务与SOA的世纪对话(3):方法论新生——DDD、服务网格与AI Ops的融合之道
运维·人工智能·微服务·ddd·soa·服务网格·ai ops
xiangji9 天前
PocoEmit遥遥领先于AutoMapper之打通充血模型的任督二脉
ioc·ddd·expression·类型转化
Light6010 天前
领码方案|微服务与SOA的世纪对话(1):从“大一统”到“小而美”
微服务·ddd·soa·服务网格·ai ops
rolt14 天前
[pdf、epub]320道《软件方法》强化自测题业务建模需求分析共279页(202509更新)
产品经理·ddd·架构师·uml·领域驱动设计
Kookoos1 个月前
Chaos Mesh / LitmusChaos 混沌工程:验证 ABP 的韧性策略
sre·abp vnext·chaos mesh·slo/sla·networkchaos
Kookoos1 个月前
多模联邦查询网关:ABP + Trino/Presto 聚合跨源数据
minio·presto·trino·数据网关·abp vnext·join优化
Kookoos1 个月前
多租户配额与预算:限额、配额周期与突发桶的结算模型(Final)
redis·令牌桶·abp vnext·配额·突发桶·账期结算
Kookoos1 个月前
差分隐私在运营指标:ABP 的 DP 计数器与噪声预算
.net·差分隐私·abp vnext·拉普拉斯机制·隐私预算
Kookoos1 个月前
ABP + ClickHouse 实时 OLAP:物化视图与写入聚合
clickhouse·c#·linq·abp vnext·实时olap