# CQRS and domain events

> How commands, queries and events flow through Wolverine, and the transactional outbox that makes messaging reliable.

## Commands and queries

Slicekit uses Wolverine as its mediator and message bus. A **command** changes state; a **query** reads
it. Both are messages, dispatched to a handler discovered by convention.

```csharp
// command
await bus.InvokeAsync(new SendInvoice(invoiceId));

// query
var invoice = await bus.InvokeAsync(new GetInvoice(invoiceId));
```

Handlers are static methods that take the message plus whatever services they need as parameters.
Dependencies are injected per call, so handlers stay easy to test in isolation.

## Raise vs publish

The two verbs are not interchangeable, and keeping them distinct keeps the model honest:

- **Aggregates raise events.** Raising records a fact inside the domain; it does not send anything.
- **Dispatchers publish events.** After the change is committed, raised events are published to
  handlers and to the outbox.

```csharp
// inside the aggregate
project.Raise(new ProjectArchived(project.Id));

// the infrastructure publishes it after SaveChanges
await publisher.PublishAsync(raisedEvent);
```

## The transactional outbox

Publishing an event and committing the database change must not drift apart. Wolverine's outbox
stores outgoing messages in the same transaction as the state change, then relays them to RabbitMQ
once the transaction commits. If the process dies mid-flight, the relay picks up where it left off:
no lost messages, no double sends.

## Integration events

Some events cross service boundaries. Those are published to RabbitMQ as **integration events** and
consumed by handlers, in this app or another. The outbox guarantees at-least-once delivery: an event
is never lost, and consumers are written idempotent so a rare redelivery is harmless.

## Handling an event

A handler subscribes simply by accepting the event type:

```csharp
public static class WhenProjectArchived
{
    public static async Task Handle(ProjectArchived e, AppDbContext db, CancellationToken ct)
    {
        // react: revoke access, send a notification, update a read model…
    }
}
```

## Where audit fits

Audit events ride the same logging pipeline rather than a bespoke table; they are emitted through
Serilog and shipped to Loki. Retention is an operations concern, not an application one. See
[observability](/docs/observability).
