csharp-wolverinefx
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWolverineFX 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
相关技能
- - Entity Framework Core patterns for data access
efcore-patterns - - Modern C# patterns (records, pattern matching)
csharp-coding-standards - - Polly resilience patterns (complementary)
http-client-resilience - - Hosted services and background job patterns
background-services - - .NET Aspire orchestration
aspire-configuration
- - 用于数据访问的Entity Framework Core模式
efcore-patterns - - 现代C#模式(记录类型、模式匹配)
csharp-coding-standards - - Polly弹性模式(互补工具)
http-client-resilience - - 托管服务与后台任务模式
background-services - - .NET Aspire编排配置
aspire-configuration
Core Principles
核心原则
- Low Ceremony Code - Pure functions, method injection, minimal boilerplate
- Cascading Messages - Return messages from handlers instead of injecting IMessageBus
- Transactional Outbox - Guaranteed message delivery with database transactions
- Code Generation - Runtime or pre-generated code for optimal performance
- Vertical Slice Architecture - Organize code by feature, not technical layers
- Pure Functions for Business Logic - Isolate infrastructure from business logic
- 低冗余代码 - 纯函数、方法注入、最小化样板代码
- 级联消息 - 从处理器返回消息而非注入IMessageBus
- 事务发件箱 - 结合数据库事务保证消息投递可靠性
- 代码生成 - 运行时或预生成代码实现最优性能
- 垂直切片架构 - 按功能而非技术层级组织代码
- 业务逻辑使用纯函数 - 隔离基础设施与业务逻辑
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 writeGenerated 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 codebash
dotnet run -- help # 列出所有命令
dotnet run -- describe # 查看应用描述
dotnet run -- resources # 资源管理
dotnet run -- storage # 消息存储管理
dotnet run -- codegen write # 预生成代码Best Practices
最佳实践
- Prefer pure functions - Business logic should be testable without mocks
- Use cascading messages - Return messages instead of injecting IMessageBus
- Keep call stacks short - Avoid deep service hierarchies
- Pre-generate code - Optimize cold starts in production
- Use compound handlers - Separate load/validate/handle logic
- Configure error handling - Let Wolverine handle retries and errors
- Use transactional outbox - Guarantee message delivery
- Batch when appropriate - Improve throughput for high-volume messages
- 优先使用纯函数 - 业务逻辑无需mock即可测试
- 使用级联消息 - 返回消息而非注入IMessageBus
- 保持调用栈简短 - 避免过深的服务层级
- 预生成代码 - 优化生产环境冷启动性能
- 使用复合处理器 - 分离加载/校验/处理逻辑
- 配置错误处理规则 - 让Wolverine自动处理重试与错误
- 使用事务发件箱 - 保证消息投递可靠性
- 合理使用批量处理 - 提升高吞吐量消息场景的处理效率
Anti-Patterns to Avoid
需要避免的反模式
- Injecting IMessageBus deep in call stack - Makes workflow hard to reason about
- Over-using constructor injection - Prefer method injection
- Ignoring transactional outbox - Can lose messages on failure
- Not pre-generating code - Slow cold starts in production
- Mixing too many concerns in one handler - Keep handlers focused
- Not configuring error handling - Messages end up in error queue unexpectedly
- 在调用栈深处注入IMessageBus - 导致工作流难以排查
- 过度使用构造函数注入 - 优先使用方法注入
- 忽略事务发件箱 - 故障时可能丢失消息
- 不预生成代码 - 生产环境冷启动缓慢
- 单个处理器混入过多职责 - 保持处理器功能单一
- 不配置错误处理 - 消息会意外进入错误队列