(三)MMA(KeyCloak身份服务器/OutBox Pattern/装饰器模式/通用定时任务/Inbox Pattern)



项目地址

  • 教程作者:
  • 教程地址:
  • 代码仓库地址:
  • 所用到的框架和插件:
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; 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值