Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 31 additions & 16 deletions .claude/skills/simplemodule/references/cross-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,54 +34,69 @@ Other modules reference the Contracts project and inject `IProductContracts`:
<ProjectReference Include="..\..\..\Products\src\SimpleModule.Products.Contracts\SimpleModule.Products.Contracts.csproj" />
```

## Event Bus
## Event Bus (Wolverine)

For decoupled cross-module communication without direct dependencies:
For decoupled cross-module communication without direct dependencies. The
framework uses [WolverineFx](https://wolverinefx.net/) for in-process
messaging; inject `IMessageBus` from `Wolverine`.

### Define Events (in Contracts)

```csharp
public record OrderCreatedEvent(OrderId OrderId, UserId UserId, decimal Total) : IEvent;
```

`IEvent` is a marker interface in `SimpleModule.Core.Events` — it costs
nothing and makes "this type is a domain event" visible at the type level.

### Publish Events

```csharp
public class OrderService
{
private readonly IEventBus _eventBus;
using Wolverine;

public class OrderService(OrdersDbContext db, IMessageBus bus)
{
public async Task CreateOrderAsync(CreateOrderRequest request)
{
// ... create order ...

// Synchronous: waits for all handlers, collects exceptions
await _eventBus.PublishAsync(new OrderCreatedEvent(order.Id, order.UserId, order.Total));
// Fire-and-forget: enqueues on the local queue, returns immediately.
// Handler failures are isolated per handler chain.
await bus.PublishAsync(new OrderCreatedEvent(order.Id, order.UserId, order.Total));

// Fire-and-forget: returns immediately
_eventBus.PublishInBackground(new OrderCreatedEvent(order.Id, order.UserId, order.Total));
// Inline with response: waits for the handler, propagates the first failure.
await bus.InvokeAsync(new OrderCreatedEvent(order.Id, order.UserId, order.Total));
}
}
```

### Handle Events

Wolverine auto-discovers handlers by convention: class name ending in
`Handler` or `Consumer`, method named `Handle`/`HandleAsync`/`Consume`/
`ConsumeAsync`, first parameter is the message type, remaining parameters
are resolved from DI.

```csharp
public class OrderCreatedHandler : IEventHandler<OrderCreatedEvent>
public class OrderCreatedHandler(ILogger<OrderCreatedHandler> logger)
{
public async Task HandleAsync(OrderCreatedEvent @event, CancellationToken cancellationToken)
public Task Handle(OrderCreatedEvent evt, CancellationToken ct)
{
// React to the event (e.g., send notification, update stats)
return Task.CompletedTask;
}
}
```

No DI registration needed — Wolverine scans loaded assemblies at startup.
Non-conventional classes can opt in with `[WolverineHandler]`.

### Event Semantics
- All handlers execute sequentially in registration order
- Handler failures are isolated — other handlers still run
- After all handlers, collected exceptions throw as `AggregateException`
- Make handlers idempotent when possible
- For long-running work, use background jobs instead
- Wolverine routes by runtime type (`message.GetType()`), not the compile-time `T`
- Each handler chain runs in its own scope with its own exception isolation
- For retry/error-queue policies, use `[RetryNow(...)]` or `chain.OnException(...)`
- Make handlers idempotent when possible — messages may be re-run
- For long-running work, prefer `IBackgroundJobs` or Wolverine's scheduled send

## Permissions

Expand Down
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
<!-- Validation -->
<PackageVersion Include="FluentValidation" Version="12.1.1" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
<!-- Messaging / Event Bus -->
<PackageVersion Include="WolverineFx" Version="5.31.0" />
<PackageVersion Include="Scrutor" Version="7.0.0" />
<!-- Email -->
<PackageVersion Include="MailKit" Version="4.15.1" />
<!-- CLI -->
Expand Down
2 changes: 1 addition & 1 deletion cli/SimpleModule.Cli/Templates/ModuleTemplates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ public string ServiceClass(string moduleName, string singularName)
// Keep only the DbContext param, remove cross-module and infrastructure deps
var crossModuleTypes = _otherModuleNames
.Select(m => $"I{GetSingularName(m)}")
.Append("IEventBus")
.Append("IMessageBus")
.Append("ILogger<")
.ToList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace SimpleModule.Core.Entities;

/// <summary>
/// Aggregate root with audit tracking, soft delete, versioning, and domain events.
/// Domain events are automatically dispatched via <see cref="IEventBus"/> after SaveChanges.
/// Domain events are automatically dispatched via Wolverine's <c>IMessageBus</c> after SaveChanges.
/// </summary>
public abstract class AuditableAggregateRoot<TId> : FullAuditableEntity<TId>, IHasDomainEvents
{
Expand Down
2 changes: 1 addition & 1 deletion framework/SimpleModule.Core/Entities/IHasDomainEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace SimpleModule.Core.Entities;

/// <summary>
/// Entities implementing this interface can raise domain events that are automatically
/// dispatched via <see cref="IEventBus"/> after a successful SaveChanges.
/// dispatched via Wolverine's <c>IMessageBus</c> after a successful SaveChanges.
/// </summary>
public interface IHasDomainEvents
{
Expand Down
45 changes: 0 additions & 45 deletions framework/SimpleModule.Core/Events/BackgroundEventChannel.cs

This file was deleted.

46 changes: 0 additions & 46 deletions framework/SimpleModule.Core/Events/BackgroundEventDispatcher.cs

This file was deleted.

178 changes: 0 additions & 178 deletions framework/SimpleModule.Core/Events/EventBus.cs

This file was deleted.

Loading
Loading