Loading...
Loading...
Build .NET applications with WolverineFX for messaging, HTTP services, and event sourcing. Use when implementing command handlers, message handlers, HTTP endpoints with WolverineFx.HTTP, transactional outbox patterns, event sourcing with Marten, CQRS architectures, cascading messages, batch message processing, or configuring transports like RabbitMQ, Azure Service Bus, or Amazon SQS.
npx skill4agent add wshaddix/dotnet-skills csharp-wolverinefxefcore-patternscsharp-coding-standardshttp-client-resiliencebackground-servicesaspire-configuration<PackageReference Include="Wolverine" />
<PackageReference Include="WolverineFx.Http" /><PackageReference Include="WolverineFx.Marten" /><PackageReference Include="WolverineFx.RabbitMQ" />
<PackageReference Include="WolverineFx.AzureServiceBus" />
<PackageReference Include="WolverineFx.Kafka" />
<PackageReference Include="WolverineFx.AmazonSQS" />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);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);
}
}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)
);
}
}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;
}[WolverineGet("/users/{id}")]
public static Task<User?> GetUser(int id, IQuerySession session)
=> session.LoadAsync<User>(id);[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);
}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);
}
}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();
}
}[WolverineGet("/orders/{id}")]
public static Order GetOrder([ReadAggregate] Order order) => order;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);
}[AggregateHandler]
public static (UpdatedAggregate, Events) Handle(
MarkItemReady command,
Order order)
{
var events = new Events();
events.Add(new ItemReady(command.ItemName));
return (new UpdatedAggregate(), events);
}builder.Services.AddMarten(opts =>
{
opts.Connection(connectionString);
}).IntegrateWithWolverine();
builder.Host.UseWolverine(opts =>
{
opts.Policies.AutoApplyTransactions();
});[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();
}builder.Host.UseWolverine(opts =>
{
opts.UseRabbitMq("host=localhost")
.AutoProvision()
.AutoPurgeOnStartup();
opts.PublishAllMessages()
.ToRabbitExchange("wolverine.events", exchange =>
{
exchange.ExchangeType = ExchangeType.Topic;
});
});builder.Host.UseWolverine(opts =>
{
opts.UseAzureServiceBus(asbConnectionString)
.AutoProvision()
.ConfigureQueue(q => q.MaxDeliveryCount = 5);
});builder.Host.UseWolverine(opts =>
{
opts.UseAmazonSqs(sqsConfig)
.AutoProvision();
});builder.Host.UseWolverine(opts =>
{
opts.BatchMessagesOf<SubTaskCompleted>(batching =>
{
batching.BatchSize = 500;
batching.TriggerTime = 1.Seconds();
});
});public static class ItemBatchHandler
{
public static void Handle(Item[] items, IRepository repository)
{
foreach (var item in items)
{
repository.Process(item);
}
}
}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);
}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();
});opts.ListenToRabbitQueue("incoming")
.CircuitBreaker(cb =>
{
cb.PauseTime = 1.Minutes();
cb.FailurePercentageThreshold = 50;
cb.MinimumThreshold = 10;
});public static IEnumerable<object> Handle(OrderCreated command)
{
yield return new ProcessPayment(command.OrderId);
yield return new ShipOrder(command.OrderId)
.DelayedFor(30.Minutes());
}yield return new GenerateReport()
.ScheduledAt(DateTime.Today.AddDays(1));var messages = new OutgoingMessages();
messages.Delay(new Reminder(orderId), TimeSpan.FromHours(24));
messages.Schedule(new MonthlyReport(), DateTime.Today.AddMonths(1));public async Task<OrderStatus> GetOrderStatus(IMessageBus bus, Guid orderId)
{
return await bus.InvokeAsync<OrderStatus>(new GetOrder(orderId));
}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");
}
}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>();
});builder.Host.UseWolverine(opts =>
{
opts.Policies.AutoApplyTransactions();
});dotnet run -- codegen write./Internal/Generated/WolverineHandlers/builder.Host.UseWolverine(opts =>
{
opts.CodeGeneration.TypeLoadMode = TypeLoadMode.Auto;
});builder.Host.UseWolverine(opts =>
{
opts.Policies.ConjoinedTenancy(x =>
{
x.TenantIdStyle = TenantIdStyle.PerRequest;
});
});await bus.PublishAsync(new OrderCreated(orderId),
new DeliveryOptions { TenantId = "tenant-1" });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);
}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 });
}
}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