文章目录
项目地址
- 教程作者:
- 教程地址:
- 代码仓库地址:
- 所用到的框架和插件:
dbt
airflow
一、KeyCloak
王教员 029-033
二、OutBox Pattern
王 38-40
使用OutBox Pattern + Quartz进行事件的发布
- 流程
服务 A:
1. 业务操作(例如:创建订单)
2. 将订单写入数据库
3. 同时,将“OrderCreated”事件写入本地 Outbox 表(通常同库同事务)
4. 提交事务(订单 + 事件记录写入一起提交 ✅)
5. 后台进程(Outbox Dispatcher)定时轮询 Outbox 表
6. 读取未发送的事件
7. 将事件发布到消息中间件(如 Kafka, RabbitMQ)
8. 标记事件为“已发送”或删除
2.1 配置OutBox(Common)
1. OutboxMessage(Common)
- 定义Outbox message
namespace Evently.Common.Infrastructure.Outbox;
public sealed class OutboxMessage
{
//消息id
public Guid Id {
get; init; }
//消息类型 例如UserRegisteredEvent
public string Type {
get; init; }
//消息内容 Json格式
public string Content {
get; init; }
//消息发生时间
public DateTime OccurredOnUtc {
get; init; }
//消息处理时间,可为空,表示没有被消费
public DateTime? ProcessedOnUtc {
get; init; }
//消息错误信息
public string? Error {
get; init; }
}
2. 数据库配置OutboxMessageConfiguration(Common)
- 创建OutBoxMessage表需要用到的数据库配置
namespace Evently.Common.Infrastructure.Outbox;
public sealed class OutboxMessageConfiguration : IEntityTypeConfiguration<OutboxMessage>
{
public void Configure(EntityTypeBuilder<OutboxMessage> builder)
{
builder.ToTable("outbox_messages");
builder.HasKey(o => o.Id);
builder.Property(o => o.Content).HasMaxLength(2000).HasColumnType("jsonb");
}
}
- 在User模块里需要用到消息服务,所以需要在User模块的schema里添加表的migration
namespace Evently.Modules.Users.Infrastructure.Database;
public sealed class UsersDbContext(DbContextOptions<UsersDbContext> options) : DbContext(options), IUnitOfWork
{
internal DbSet<User> Users {
get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema(Schemas.Users);
modelBuilder.ApplyConfiguration(new OutboxMessageConfiguration());
modelBuilder.ApplyConfiguration(new UserConfiguration());
modelBuilder.ApplyConfiguration(new RoleConfiguration());
modelBuilder.ApplyConfiguration(new PermissionConfiguration());
}
}
- 在User模块下,执行迁移,生成outbox_messages表
3. 创建Save前的EF拦截器(Common)
- 之前我们使用的拦截器是在数据库save之后在进行事件的发布,现在我们直接在save之前,自动将领域事件转换为OutBoxMessage并存入数据库
public sealed class InsertOutboxMessagesInterceptor : SaveChangesInterceptor
{
public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
if (eventData.Context is not null)
{
InsertOutboxMessages(eventData.Context);
}
return await base.SavingChangesAsync(eventData, result, cancellationToken);
}
//将所有领域事件转换为Outbox消息,并且存储在DbContext中
private static void InsertOutboxMessages(DbContext context)
{
var outboxMessages = context
.ChangeTracker
.Entries<Entity>()
.Select(entry => entry.Entity)
.SelectMany(entity =>
{
IReadOnlyCollection<IDomainEvent> domainEvents = entity.DomainEvents;
entity.ClearDomainEvents();
return domainEvents;
})
.Select(domainEvent => new OutboxMessage //转为OutBoxMessage
{
Id = domainEvent.Id,
Type = domainEvent.GetType().Name,
Content = JsonConvert.SerializeObject(domainEvent, SerializerSettings.Instance),
OccurredOnUtc = domainEvent.OccurredOnUtc
})
.ToList();
//批量将OutboxMessage写入Dbcontext
context.Set<OutboxMessage>().AddRange(outboxMessages);
}
}
SerializerSettings.cs
:需要将json序列化为jsonb的格式放入到数据库中
namespace Evently.Common.Infrastructure.Serialization;
public static class SerializerSettings
{
public static readonly JsonSerializerSettings Instance = new()
{
TypeNameHandling = TypeNameHandling.All,
MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead
};
}
4. 服务注册(Common)
- 先注册Quartz服务在
InfrastructureConfiguration.cs
里
2.2 发布者配置(所有模块)
- 所有需要发布消息的模块都需要在
infrastructure层
配置outbox
1. 配置后台任务(Users)
- Quartz后台任务的设置
namespace Evently.Modules.Users.Infrastructure.Outbox;
internal sealed class OutboxOptions
{
//调度频率
public int IntervalInSeconds {
get;