csharp-wolverinefx

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

WolverineFX for .NET

面向.NET的WolverineFX

When to Use This Skill

什么时候使用该技能

Use this skill when:
  • Building message handlers or command handlers with Wolverine
  • Creating HTTP endpoints with WolverineFx.HTTP (alternative to Minimal API/MVC)
  • Implementing event sourcing with Marten and Wolverine
  • Setting up transactional outbox pattern for reliable messaging
  • Configuring message transports (RabbitMQ, Azure Service Bus, Amazon SQS, TCP)
  • Implementing CQRS with event sourcing
  • Processing messages in batches
  • Using cascading messages for testable, pure function handlers
  • Configuring error handling and retry policies
  • Pre-generating code for optimized cold starts
当你需要完成以下工作时可使用本技能:
  • 基于Wolverine构建消息处理器或命令处理器
  • 用WolverineFx.HTTP创建HTTP端点(可替代Minimal API/MVC)
  • 结合Marten与Wolverine实现事件溯源
  • 搭建事务发件箱模式实现可靠消息传递
  • 配置消息传输中间件(RabbitMQ、Azure Service Bus、Amazon SQS、TCP)
  • 结合事件溯源实现CQRS
  • 批量处理消息
  • 使用级联消息实现可测试的纯函数处理器
  • 配置错误处理与重试策略
  • 预生成代码优化冷启动性能

Related Skills

相关技能

  • efcore-patterns
    - Entity Framework Core patterns for data access
  • csharp-coding-standards
    - Modern C# patterns (records, pattern matching)
  • http-client-resilience
    - Polly resilience patterns (complementary)
  • background-services
    - Hosted services and background job patterns
  • aspire-configuration
    - .NET Aspire orchestration
  • efcore-patterns
    - 用于数据访问的Entity Framework Core模式
  • csharp-coding-standards
    - 现代C#模式(记录类型、模式匹配)
  • http-client-resilience
    - Polly弹性模式(互补工具)
  • background-services
    - 托管服务与后台任务模式
  • aspire-configuration
    - .NET Aspire编排配置

Core Principles

核心原则

  1. Low Ceremony Code - Pure functions, method injection, minimal boilerplate
  2. Cascading Messages - Return messages from handlers instead of injecting IMessageBus
  3. Transactional Outbox - Guaranteed message delivery with database transactions
  4. Code Generation - Runtime or pre-generated code for optimal performance
  5. Vertical Slice Architecture - Organize code by feature, not technical layers
  6. Pure Functions for Business Logic - Isolate infrastructure from business logic
  1. 低冗余代码 - 纯函数、方法注入、最小化样板代码
  2. 级联消息 - 从处理器返回消息而非注入IMessageBus
  3. 事务发件箱 - 结合数据库事务保证消息投递可靠性
  4. 代码生成 - 运行时或预生成代码实现最优性能
  5. 垂直切片架构 - 按功能而非技术层级组织代码
  6. 业务逻辑使用纯函数 - 隔离基础设施与业务逻辑

Required NuGet Packages

所需NuGet包

Core Messaging

核心消息功能

xml
<PackageReference Include="Wolverine" />
<PackageReference Include="WolverineFx.Http" />
xml
<PackageReference Include="Wolverine" />
<PackageReference Include="WolverineFx.Http" />

Persistence Integration

持久化集成

xml
<PackageReference Include="WolverineFx.Marten" />
xml
<PackageReference Include="WolverineFx.Marten" />

Transports

传输中间件

xml
<PackageReference Include="WolverineFx.RabbitMQ" />
<PackageReference Include="WolverineFx.AzureServiceBus" />
<PackageReference Include="WolverineFx.Kafka" />
<PackageReference Include="WolverineFx.AmazonSQS" />
xml
<PackageReference Include="WolverineFx.RabbitMQ" />
<PackageReference Include="WolverineFx.AzureServiceBus" />
<PackageReference Include="WolverineFx.Kafka" />
<PackageReference Include="WolverineFx.AmazonSQS" />

Basic Setup

基础配置

Program.cs (ASP.NET Core)

Program.cs (ASP.NET Core)

csharp
using JasperFx;
using Wolverine;
using Wolverine.Http;

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseWolverine(opts =>
{
    opts.Policies.AutoApplyTransactions();
    opts.Policies.UseDurableLocalQueues();
});

builder.Services.AddWolverineHttp();

var app = builder.Build();

app.MapWolverineEndpoints();

return await app.RunJasperFxCommands(args);
csharp
using JasperFx;
using Wolverine;
using Wolverine.Http;

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseWolverine(opts =>
{
    opts.Policies.AutoApplyTransactions();
    opts.Policies.UseDurableLocalQueues();
});

builder.Services.AddWolverineHttp();

var app = builder.Build();

app.MapWolverineEndpoints();

return await app.RunJasperFxCommands(args);

Message Handlers

消息处理器

Simple Message Handler

简单消息处理器

csharp
public record DebitAccount(long AccountId, decimal Amount);

public static class DebitAccountHandler
{
    public static void Handle(DebitAccount command, IAccountRepository repository)
    {
        repository.Debit(command.AccountId, command.Amount);
    }
}
csharp
public record DebitAccount(long AccountId, decimal Amount);

public static class DebitAccountHandler
{
    public static void Handle(DebitAccount command, IAccountRepository repository)
    {
        repository.Debit(command.AccountId, command.Amount);
    }
}

Handler with Cascading Messages

带级联消息的处理器

csharp
public record CreateOrder(Guid OrderId, string[] Items);
public record OrderCreated(Guid OrderId);

public static class CreateOrderHandler
{
    public static (OrderCreated, ShipOrder) Handle(
        CreateOrder command,
        IDocumentSession session)
    {
        var order = new Order { Id = command.OrderId, Items = command.Items };
        session.Store(order);
        
        return (
            new OrderCreated(command.OrderId),
            new ShipOrder(command.OrderId)
        );
    }
}
csharp
public record CreateOrder(Guid OrderId, string[] Items);
public record OrderCreated(Guid OrderId);

public static class CreateOrderHandler
{
    public static (OrderCreated, ShipOrder) Handle(
        CreateOrder command,
        IDocumentSession session)
    {
        var order = new Order { Id = command.OrderId, Items = command.Items };
        session.Store(order);
        
        return (
            new OrderCreated(command.OrderId),
            new ShipOrder(command.OrderId)
        );
    }
}

Using OutgoingMessages for Multiple Messages

使用OutgoingMessages返回多条消息

csharp
public static OutgoingMessages Handle(ProcessOrder command)
{
    var messages = new OutgoingMessages
    {
        new OrderProcessed(command.OrderId),
        new SendEmail(command.CustomerEmail, "Order processed"),
        new UpdateInventory(command.Items)
    };
    
    messages.Delay(new CleanupOrder(command.OrderId), 5.Minutes());
    
    return messages;
}
csharp
public static OutgoingMessages Handle(ProcessOrder command)
{
    var messages = new OutgoingMessages
    {
        new OrderProcessed(command.OrderId),
        new SendEmail(command.CustomerEmail, "Order processed"),
        new UpdateInventory(command.Items)
    };
    
    messages.Delay(new CleanupOrder(command.OrderId), 5.Minutes());
    
    return messages;
}

HTTP Endpoints (WolverineFx.HTTP)

HTTP端点(WolverineFx.HTTP)

Basic GET Endpoint

基础GET端点

csharp
[WolverineGet("/users/{id}")]
public static Task<User?> GetUser(int id, IQuerySession session)
    => session.LoadAsync<User>(id);
csharp
[WolverineGet("/users/{id}")]
public static Task<User?> GetUser(int id, IQuerySession session)
    => session.LoadAsync<User>(id);

POST with Message Publishing

带消息发布的POST端点

csharp
[WolverinePost("/orders")]
public static async Task<IResult> CreateOrder(
    CreateOrderRequest request,
    IDocumentSession session,
    IMessageBus bus)
{
    var order = new Order { Id = Guid.NewGuid(), Items = request.Items };
    session.Store(order);
    
    await bus.PublishAsync(new OrderCreated(order.Id));
    
    return Results.Created($"/orders/{order.Id}", order);
}
csharp
[WolverinePost("/orders")]
public static async Task<IResult> CreateOrder(
    CreateOrderRequest request,
    IDocumentSession session,
    IMessageBus bus)
{
    var order = new Order { Id = Guid.NewGuid(), Items = request.Items };
    session.Store(order);
    
    await bus.PublishAsync(new OrderCreated(order.Id));
    
    return Results.Created($"/orders/{order.Id}", order);
}

Compound Handler (Load/Validate/Handle)

复合处理器(加载/校验/处理分离)

csharp
public static class UpdateOrderEndpoint
{
    public static async Task<(Order?, IResult)> LoadAsync(
        UpdateOrder command,
        IDocumentSession session)
    {
        var order = await session.LoadAsync<Order>(command.OrderId);
        return order != null
            ? (order, new WolverineContinue())
            : (order, Results.NotFound());
    }

    [WolverinePut("/orders")]
    public static void Handle(UpdateOrder command, Order order, IDocumentSession session)
    {
        order.Items = command.Items;
        session.Store(order);
    }
}
csharp
public static class UpdateOrderEndpoint
{
    public static async Task<(Order?, IResult)> LoadAsync(
        UpdateOrder command,
        IDocumentSession session)
    {
        var order = await session.LoadAsync<Order>(command.OrderId);
        return order != null
            ? (order, new WolverineContinue())
            : (order, Results.NotFound());
    }

    [WolverinePut("/orders")]
    public static void Handle(UpdateOrder command, Order order, IDocumentSession session)
    {
        order.Items = command.Items;
        session.Store(order);
    }
}

Event Sourcing with Marten

结合Marten实现事件溯源

Aggregate Handler Workflow

聚合处理器工作流

csharp
public class Order
{
    public Guid Id { get; set; }
    public int Version { get; set; }
    public Dictionary<string, Item> Items { get; set; } = new();
    public DateTimeOffset? Shipped { get; private set; }

    public void Apply(ItemReady ready) => Items[ready.Name].Ready = true;
    public void Apply(IEvent<OrderShipped> shipped) => Shipped = shipped.Timestamp;

    public bool IsReadyToShip() => Shipped == null && Items.Values.All(x => x.Ready);
}

public record MarkItemReady(Guid OrderId, string ItemName, int Version);

[AggregateHandler]
public static IEnumerable<object> Handle(MarkItemReady command, Order order)
{
    if (order.Items.TryGetValue(command.ItemName, out var item))
    {
        item.Ready = true;
        yield return new ItemReady(command.ItemName);
    }
    
    if (order.IsReadyToShip())
    {
        yield return new OrderReady();
    }
}
csharp
public class Order
{
    public Guid Id { get; set; }
    public int Version { get; set; }
    public Dictionary<string, Item> Items { get; set; } = new();
    public DateTimeOffset? Shipped { get; private set; }

    public void Apply(ItemReady ready) => Items[ready.Name].Ready = true;
    public void Apply(IEvent<OrderShipped> shipped) => Shipped = shipped.Timestamp;

    public bool IsReadyToShip() => Shipped == null && Items.Values.All(x => x.Ready);
}

public record MarkItemReady(Guid OrderId, string ItemName, int Version);

[AggregateHandler]
public static IEnumerable<object> Handle(MarkItemReady command, Order order)
{
    if (order.Items.TryGetValue(command.ItemName, out var item))
    {
        item.Ready = true;
        yield return new ItemReady(command.ItemName);
    }
    
    if (order.IsReadyToShip())
    {
        yield return new OrderReady();
    }
}

Read Aggregate (Read-Only)

读取聚合(只读)

csharp
[WolverineGet("/orders/{id}")]
public static Order GetOrder([ReadAggregate] Order order) => order;
csharp
[WolverineGet("/orders/{id}")]
public static Order GetOrder([ReadAggregate] Order order) => order;

Write Aggregate with Validation

带校验的聚合写入

csharp
public static IEnumerable<object> Handle(
    MarkItemReady command,
    [WriteAggregate(Required = true, OnMissing = OnMissing.ProblemDetailsWith404)] Order order)
{
    order.Items[command.ItemName].Ready = true;
    yield return new ItemReady(command.ItemName);
}
csharp
public static IEnumerable<object> Handle(
    MarkItemReady command,
    [WriteAggregate(Required = true, OnMissing = OnMissing.ProblemDetailsWith404)] Order order)
{
    order.Items[command.ItemName].Ready = true;
    yield return new ItemReady(command.ItemName);
}

Returning Updated Aggregate

返回更新后的聚合

csharp
[AggregateHandler]
public static (UpdatedAggregate, Events) Handle(
    MarkItemReady command,
    Order order)
{
    var events = new Events();
    events.Add(new ItemReady(command.ItemName));
    return (new UpdatedAggregate(), events);
}
csharp
[AggregateHandler]
public static (UpdatedAggregate, Events) Handle(
    MarkItemReady command,
    Order order)
{
    var events = new Events();
    events.Add(new ItemReady(command.ItemName));
    return (new UpdatedAggregate(), events);
}

Transactional Outbox

事务发件箱

Marten Integration

Marten集成

csharp
builder.Services.AddMarten(opts =>
{
    opts.Connection(connectionString);
}).IntegrateWithWolverine();

builder.Host.UseWolverine(opts =>
{
    opts.Policies.AutoApplyTransactions();
});
csharp
builder.Services.AddMarten(opts =>
{
    opts.Connection(connectionString);
}).IntegrateWithWolverine();

builder.Host.UseWolverine(opts =>
{
    opts.Policies.AutoApplyTransactions();
});

Using Outbox in Controllers

在控制器中使用发件箱

csharp
[HttpPost("/orders")]
public async Task Post(
    [FromBody] CreateOrder command,
    [FromServices] IDocumentSession session,
    [FromServices] IMartenOutbox outbox)
{
    outbox.Enroll(session);
    
    var order = new Order { Id = command.OrderId };
    session.Store(order);
    
    await outbox.PublishAsync(new OrderCreated(command.OrderId));
    
    await session.SaveChangesAsync();
}
csharp
[HttpPost("/orders")]
public async Task Post(
    [FromBody] CreateOrder command,
    [FromServices] IDocumentSession session,
    [FromServices] IMartenOutbox outbox)
{
    outbox.Enroll(session);
    
    var order = new Order { Id = command.OrderId };
    session.Store(order);
    
    await outbox.PublishAsync(new OrderCreated(command.OrderId));
    
    await session.SaveChangesAsync();
}

Transport Configuration

传输中间件配置

RabbitMQ

RabbitMQ

csharp
builder.Host.UseWolverine(opts =>
{
    opts.UseRabbitMq("host=localhost")
        .AutoProvision()
        .AutoPurgeOnStartup();
    
    opts.PublishAllMessages()
        .ToRabbitExchange("wolverine.events", exchange =>
        {
            exchange.ExchangeType = ExchangeType.Topic;
        });
});
csharp
builder.Host.UseWolverine(opts =>
{
    opts.UseRabbitMq("host=localhost")
        .AutoProvision()
        .AutoPurgeOnStartup();
    
    opts.PublishAllMessages()
        .ToRabbitExchange("wolverine.events", exchange =>
        {
            exchange.ExchangeType = ExchangeType.Topic;
        });
});

Azure Service Bus

Azure Service Bus

csharp
builder.Host.UseWolverine(opts =>
{
    opts.UseAzureServiceBus(asbConnectionString)
        .AutoProvision()
        .ConfigureQueue(q => q.MaxDeliveryCount = 5);
});
csharp
builder.Host.UseWolverine(opts =>
{
    opts.UseAzureServiceBus(asbConnectionString)
        .AutoProvision()
        .ConfigureQueue(q => q.MaxDeliveryCount = 5);
});

Amazon SQS

Amazon SQS

csharp
builder.Host.UseWolverine(opts =>
{
    opts.UseAmazonSqs(sqsConfig)
        .AutoProvision();
});
csharp
builder.Host.UseWolverine(opts =>
{
    opts.UseAmazonSqs(sqsConfig)
        .AutoProvision();
});

Batch Message Processing

批量消息处理

Configure Batching

配置批量处理规则

csharp
builder.Host.UseWolverine(opts =>
{
    opts.BatchMessagesOf<SubTaskCompleted>(batching =>
    {
        batching.BatchSize = 500;
        batching.TriggerTime = 1.Seconds();
    });
});
csharp
builder.Host.UseWolverine(opts =>
{
    opts.BatchMessagesOf<SubTaskCompleted>(batching =>
    {
        batching.BatchSize = 500;
        batching.TriggerTime = 1.Seconds();
    });
});

Batch Handler

批量处理器

csharp
public static class ItemBatchHandler
{
    public static void Handle(Item[] items, IRepository repository)
    {
        foreach (var item in items)
        {
            repository.Process(item);
        }
    }
}
csharp
public static class ItemBatchHandler
{
    public static void Handle(Item[] items, IRepository repository)
    {
        foreach (var item in items)
        {
            repository.Process(item);
        }
    }
}

Custom Batching Strategy

自定义批量处理策略

csharp
public record SubTaskCompleted(string TaskId, string SubTaskId);
public record SubTaskBatch(string TaskId, string[] SubTaskIds);

public class SubTaskBatcher : IMessageBatcher
{
    public IEnumerable<Envelope> Group(IReadOnlyList<Envelope> envelopes)
    {
        var groups = envelopes
            .GroupBy(x => x.Message!.As<SubTaskCompleted>().TaskId);
        
        foreach (var group in groups)
        {
            var subTaskIds = group
                .Select(x => x.Message)
                .OfType<SubTaskCompleted>()
                .Select(x => x.SubTaskId)
                .ToArray();
            
            yield return new Envelope(
                new SubTaskBatch(group.Key, subTaskIds),
                group);
        }
    }

    public Type BatchMessageType => typeof(SubTaskBatch);
}
csharp
public record SubTaskCompleted(string TaskId, string SubTaskId);
public record SubTaskBatch(string TaskId, string[] SubTaskIds);

public class SubTaskBatcher : IMessageBatcher
{
    public IEnumerable<Envelope> Group(IReadOnlyList<Envelope> envelopes)
    {
        var groups = envelopes
            .GroupBy(x => x.Message!.As<SubTaskCompleted>().TaskId);
        
        foreach (var group in groups)
        {
            var subTaskIds = group
                .Select(x => x.Message)
                .OfType<SubTaskCompleted>()
                .Select(x => x.SubTaskId)
                .ToArray();
            
            yield return new Envelope(
                new SubTaskBatch(group.Key, subTaskIds),
                group);
        }
    }

    public Type BatchMessageType => typeof(SubTaskBatch);
}

Error Handling

错误处理

Retry Policies

重试策略

csharp
builder.Host.UseWolverine(opts =>
{
    opts.Policies.OnException<SqlException>()
        .RetryWithCooldown(50.Milliseconds(), 100.Milliseconds(), 250.Milliseconds());
    
    opts.Policies.OnException<TimeoutException>()
        .RetryTimes(3);
    
    opts.Policies.OnException<InvalidOperationException>()
        .Requeue();
    
    opts.Policies.OnAnyException()
        .MoveToErrorQueue();
});
csharp
builder.Host.UseWolverine(opts =>
{
    opts.Policies.OnException<SqlException>()
        .RetryWithCooldown(50.Milliseconds(), 100.Milliseconds(), 250.Milliseconds());
    
    opts.Policies.OnException<TimeoutException>()
        .RetryTimes(3);
    
    opts.Policies.OnException<InvalidOperationException>()
        .Requeue();
    
    opts.Policies.OnAnyException()
        .MoveToErrorQueue();
});

Circuit Breaker

熔断机制

csharp
opts.ListenToRabbitQueue("incoming")
    .CircuitBreaker(cb =>
    {
        cb.PauseTime = 1.Minutes();
        cb.FailurePercentageThreshold = 50;
        cb.MinimumThreshold = 10;
    });
csharp
opts.ListenToRabbitQueue("incoming")
    .CircuitBreaker(cb =>
    {
        cb.PauseTime = 1.Minutes();
        cb.FailurePercentageThreshold = 50;
        cb.MinimumThreshold = 10;
    });

Scheduled Messages

定时消息

Delayed Messages

延迟消息

csharp
public static IEnumerable<object> Handle(OrderCreated command)
{
    yield return new ProcessPayment(command.OrderId);
    yield return new ShipOrder(command.OrderId)
        .DelayedFor(30.Minutes());
}
csharp
public static IEnumerable<object> Handle(OrderCreated command)
{
    yield return new ProcessPayment(command.OrderId);
    yield return new ShipOrder(command.OrderId)
        .DelayedFor(30.Minutes());
}

Scheduled at Specific Time

指定时间调度

csharp
yield return new GenerateReport()
    .ScheduledAt(DateTime.Today.AddDays(1));
csharp
yield return new GenerateReport()
    .ScheduledAt(DateTime.Today.AddDays(1));

Using OutgoingMessages

使用OutgoingMessages配置

csharp
var messages = new OutgoingMessages();
messages.Delay(new Reminder(orderId), TimeSpan.FromHours(24));
messages.Schedule(new MonthlyReport(), DateTime.Today.AddMonths(1));
csharp
var messages = new OutgoingMessages();
messages.Delay(new Reminder(orderId), TimeSpan.FromHours(24));
messages.Schedule(new MonthlyReport(), DateTime.Today.AddMonths(1));

Request/Reply Pattern

请求/响应模式

Sending with Response Request

发送带响应的请求

csharp
public async Task<OrderStatus> GetOrderStatus(IMessageBus bus, Guid orderId)
{
    return await bus.InvokeAsync<OrderStatus>(new GetOrder(orderId));
}
csharp
public async Task<OrderStatus> GetOrderStatus(IMessageBus bus, Guid orderId)
{
    return await bus.InvokeAsync<OrderStatus>(new GetOrder(orderId));
}

Handler with Response

带响应的处理器

csharp
public record GetOrder(Guid OrderId);
public record OrderStatus(Guid OrderId, string Status);

public static class GetOrderHandler
{
    public static OrderStatus Handle(GetOrder query, IQuerySession session)
    {
        var order = session.Load<Order>(query.OrderId);
        return new OrderStatus(query.OrderId, order?.Status ?? "NotFound");
    }
}
csharp
public record GetOrder(Guid OrderId);
public record OrderStatus(Guid OrderId, string Status);

public static class GetOrderHandler
{
    public static OrderStatus Handle(GetOrder query, IQuerySession session)
    {
        var order = session.Load<Order>(query.OrderId);
        return new OrderStatus(query.OrderId, order?.Status ?? "NotFound");
    }
}

Middleware

中间件

Custom Middleware

自定义中间件

csharp
public class LoggingMiddleware
{
    public void Before(Envelope envelope, ILogger logger)
    {
        logger.LogInformation("Processing {MessageType}", envelope.MessageType);
    }

    public void After(Envelope envelope, ILogger logger)
    {
        logger.LogInformation("Completed {MessageType}", envelope.MessageType);
    }
}

builder.Host.UseWolverine(opts =>
{
    opts.Handlers.AddMiddleware<LoggingMiddleware>();
});
csharp
public class LoggingMiddleware
{
    public void Before(Envelope envelope, ILogger logger)
    {
        logger.LogInformation("Processing {MessageType}", envelope.MessageType);
    }

    public void After(Envelope envelope, ILogger logger)
    {
        logger.LogInformation("Completed {MessageType}", envelope.MessageType);
    }
}

builder.Host.UseWolverine(opts =>
{
    opts.Handlers.AddMiddleware<LoggingMiddleware>();
});

Transaction Middleware

事务中间件

csharp
builder.Host.UseWolverine(opts =>
{
    opts.Policies.AutoApplyTransactions();
});
csharp
builder.Host.UseWolverine(opts =>
{
    opts.Policies.AutoApplyTransactions();
});

Code Generation

代码生成

Pre-Generate Types

预生成类型

bash
dotnet run -- codegen write
Generated code appears in
./Internal/Generated/WolverineHandlers/
bash
dotnet run -- codegen write
生成的代码会保存在
./Internal/Generated/WolverineHandlers/
目录下

Configure for AOT/Trimming

配置AOT/裁剪适配

csharp
builder.Host.UseWolverine(opts =>
{
    opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Auto;
});
csharp
builder.Host.UseWolverine(opts =>
{
    opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Auto;
});

Multi-Tenancy

多租户

Conjoined Tenancy

联合租户模式

csharp
builder.Host.UseWolverine(opts =>
{
    opts.Policies.ConjoinedTenancy(x =>
    {
        x.TenantIdStyle = TenantIdStyle.PerRequest;
    });
});
csharp
builder.Host.UseWolverine(opts =>
{
    opts.Policies.ConjoinedTenancy(x =>
    {
        x.TenantIdStyle = TenantIdStyle.PerRequest;
    });
});

Publishing to Specific Tenant

发布消息到指定租户

csharp
await bus.PublishAsync(new OrderCreated(orderId), 
    new DeliveryOptions { TenantId = "tenant-1" });
csharp
await bus.PublishAsync(new OrderCreated(orderId), 
    new DeliveryOptions { TenantId = "tenant-1" });

Marten Side Effects

Marten副作用操作

csharp
public static IMartenOp Handle(CreateTodo command)
{
    var todo = new Todo { Name = command.Name };
    return MartenOps.Store(todo);
}

public static IMartenOp Handle(DeleteTodo command)
{
    return MartenOps.Delete<Todo>(command.Id);
}
csharp
public static IMartenOp Handle(CreateTodo command)
{
    var todo = new Todo { Name = command.Name };
    return MartenOps.Store(todo);
}

public static IMartenOp Handle(DeleteTodo command)
{
    return MartenOps.Delete<Todo>(command.Id);
}

Ancillary Stores (Modular Monolith)

附属存储(模块化单体)

csharp
public interface IPlayerStore : IDocumentStore;

builder.Host.UseWolverine(opts =>
{
    opts.Services.AddMartenStore<IPlayerStore>(m =>
    {
        m.Connection(connectionString);
        m.DatabaseSchemaName = "players";
    })
    .IntegrateWithWolverine();
});

[MartenStore(typeof(IPlayerStore))]
public static class PlayerMessageHandler
{
    public static IMartenOp Handle(PlayerMessage message)
    {
        return MartenOps.Store(new Player { Id = message.Id });
    }
}
csharp
public interface IPlayerStore : IDocumentStore;

builder.Host.UseWolverine(opts =>
{
    opts.Services.AddMartenStore<IPlayerStore>(m =>
    {
        m.Connection(connectionString);
        m.DatabaseSchemaName = "players";
    })
    .IntegrateWithWolverine();
});

[MartenStore(typeof(IPlayerStore))]
public static class PlayerMessageHandler
{
    public static IMartenOp Handle(PlayerMessage message)
    {
        return MartenOps.Store(new Player { Id = message.Id });
    }
}

Command Line Tools

命令行工具

Available Commands

可用命令

bash
dotnet run -- help           # List all commands
dotnet run -- describe       # Application description
dotnet run -- resources      # Resource management
dotnet run -- storage        # Message storage admin
dotnet run -- codegen write  # Pre-generate code
bash
dotnet run -- help           # 列出所有命令
dotnet run -- describe       # 查看应用描述
dotnet run -- resources      # 资源管理
dotnet run -- storage        # 消息存储管理
dotnet run -- codegen write  # 预生成代码

Best Practices

最佳实践

  1. Prefer pure functions - Business logic should be testable without mocks
  2. Use cascading messages - Return messages instead of injecting IMessageBus
  3. Keep call stacks short - Avoid deep service hierarchies
  4. Pre-generate code - Optimize cold starts in production
  5. Use compound handlers - Separate load/validate/handle logic
  6. Configure error handling - Let Wolverine handle retries and errors
  7. Use transactional outbox - Guarantee message delivery
  8. Batch when appropriate - Improve throughput for high-volume messages
  1. 优先使用纯函数 - 业务逻辑无需mock即可测试
  2. 使用级联消息 - 返回消息而非注入IMessageBus
  3. 保持调用栈简短 - 避免过深的服务层级
  4. 预生成代码 - 优化生产环境冷启动性能
  5. 使用复合处理器 - 分离加载/校验/处理逻辑
  6. 配置错误处理规则 - 让Wolverine自动处理重试与错误
  7. 使用事务发件箱 - 保证消息投递可靠性
  8. 合理使用批量处理 - 提升高吞吐量消息场景的处理效率

Anti-Patterns to Avoid

需要避免的反模式

  1. Injecting IMessageBus deep in call stack - Makes workflow hard to reason about
  2. Over-using constructor injection - Prefer method injection
  3. Ignoring transactional outbox - Can lose messages on failure
  4. Not pre-generating code - Slow cold starts in production
  5. Mixing too many concerns in one handler - Keep handlers focused
  6. Not configuring error handling - Messages end up in error queue unexpectedly
  1. 在调用栈深处注入IMessageBus - 导致工作流难以排查
  2. 过度使用构造函数注入 - 优先使用方法注入
  3. 忽略事务发件箱 - 故障时可能丢失消息
  4. 不预生成代码 - 生产环境冷启动缓慢
  5. 单个处理器混入过多职责 - 保持处理器功能单一
  6. 不配置错误处理 - 消息会意外进入错误队列