Say you have a simple entity like so:
public class Todo
{
public Guid Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
And it's acted upon by a simple command like so:
// This is easily overridden, but *a* convention is that
// Wolverine will look first for a "TodoId" member in the message type,
// then go for "Id". But that can be overridden explicitly
// *Might* try to use strong typed identifiers later to "know" what to use
public record RenameTodo(Guid Id, string Name);
And lastly, a Wolverine message handler method for that command message like so:
public static Update<Todo> Handle(
// The first argument is always the incoming message
RenameTodo command,
// By using this attribute, we're telling Wolverine
// to load the Todo entity from the configured
// persistence of the app using a member on the
// incoming message type
[Entity] Todo todo)
{
// Do your actual business logic
todo.Name = command.Name;
// Tell Wolverine that you want this entity
// updated in persistence
return Storage.Update(todo);
}
The end result is:
- Having a synchronous, pure function for the business behavior that hopefully is very easy to test and clearly communicates its purpose
- Wolverine deals with the actual persistence tooling, whether that's Marten, the correct EF Core DbContext for the entity type, or RavenDb
- We didn't need any silly IRepository wrapper or separate projects for "loose coupling"