ABP vNext 的 Outbox/Inbox 系统化落地(非 CAP / 非 Kafka):幂等写、去重、延迟到达与死信回补 ✨
📚 目录
一、问题与目标 🧭
不使用 DTC;以本地事务 + Outbox 实现“效果上的一次”。核心要解决:重复投递、延迟/乱序、幂等更新、不可恢复失败(死信)。💡
二、总体架构 🏗️
- 写路径:AppService → 聚合操作 →
SaveChanges()同事务 写业务表 & Outbox 表;提交后由 Dispatcher 异步派发到 Broker。 - 读路径:Consumer 收到消息 → Inbox 先占位去重(
(messageId, consumer))→ 幂等处理 → 提交。 - 可观测性:记录 commit→publish、publish→consume 延迟与重试/死信指标。🔍
三、数据模型与表结构 🗃️
兼容多租户、多版本与回补;补齐运营定位字段。✅
OutboxMessages(PostgreSQL 版)
CREATE TABLE outbox_messages (
id uuid PRIMARY KEY,
aggregate_id uuid NOT NULL,
aggregate_type text NOT NULL,
aggregate_version bigint NOT NULL DEFAULT 0, -- 乱序/并发合并
type text NOT NULL, -- 事件类型/路由键
payload jsonb NOT NULL,
headers jsonb NOT NULL,
tenant_id text,
occurred_at timestamptz NOT NULL,
visible_at timestamptz NOT NULL, -- 延迟/重试
attempts int NOT NULL DEFAULT 0,
status smallint NOT NULL DEFAULT 0, -- 0:new 1:sent 3:dead 9:processing
last_error text,
routing_key text,
partition_key text
);
CREATE INDEX idx_outbox_visibility ON outbox_messages(status, visible_at);
CREATE INDEX idx_outbox_tenant ON outbox_messages(tenant_id);
CREATE INDEX idx_outbox_type ON outbox_messages(type);
SQL Server:把
jsonb换nvarchar(max),或加 JSON 校验约束;时间函数换SYSUTCDATETIME()。
Inbox(去重/幂等)
CREATE TABLE inbox (
message_id uuid NOT NULL,
consumer text NOT NULL,
processed_at timestamptz NOT NULL,
tenant_id text,
PRIMARY KEY (message_id, consumer)
);
(message_id, consumer)作为幂等键;支持 TTL/归档。🧹
四、ABP 集成点 🧩
使用自研事件收集接口,避免与 ABP 内置 Outbox 混用。聚合根实现
IHasDomainEvents,拦截器在同事务落 Outbox。
4.1 领域事件接口与基类
public interface IDomainEvent
{
Guid AggregateId {
get; }
string AggregateType {
get; }
long AggregateVersion {
get; }
string TenantId {
get; }
}
public interface IHasDomainEvents
{
IReadOnlyCollection<IDomainEvent> DomainEvents {
get; }
void ClearDomainEvents();
}
public abstract class AggregateRootBase : Entity<Guid>, IHasDomainEvents
{
private readonly List<IDomainEvent> _domainEvents = new();
public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
protected void RaiseDomainEvent(IDomainEvent @event) => _domainEvents.Add(@event);
public void ClearDomainEvents() => _domainEvents.Clear();
}
4.2 Outbox 实体与工厂
public enum OutboxStatus {
New = 0, Sent = 1, Dead = 3, Processing = 9 }
public sealed class OutboxMessage
{
public Guid Id {
get; init; }
public Guid AggregateId {
get; init

最低0.47元/天 解锁文章
868

被折叠的 条评论
为什么被折叠?



