When building systems using event sourcing, commands can be handled either client side or exposed via an API and handled on the server side.
In this article I'll cover how I build command handling on the server, using c#.
As a quick reminder: Command handling is the common name for the steps on the left hand side of the cqrs architecture.
Roster Management
The scenario used for this example will be consistent with the client side examples, we are going to create a team roster for next season.
If you want to catch up on the scenario, check out the original description in the client side command handling post.
The command
As dotnet is a typed environment, we need to create a class for the create roster command that we'll expose through our API.
public class CreateRoster
{
public string RosterId { get; set; }
public string Name { get; set; }
}
The API
The command handler is exposed as an HTTP POST operation on an MVC controller.
public class RosterCreation : Controller
{
private readonly EventSourcedRepository _repository;
public RosterCreation(EventSourcedRepository repository)
{
_repository = repository;
}
[HttpPost()]
[Route("api/rosters/{id}")]
[Authorize]
public async Task<IActionResult> Handle([FromRoute] string id, [FromBody] CreateRoster cmd)
{
var aggregate = await _repository.Get<Roster>(cmd.RosterId);
aggregate.Create(User, cmd.Name);
await _repository.Flush();
return Ok();
}
}
The command handler has a dependency on EventSourcedRepository
which is a generic repository that mediates between the aggregate root Roster
and the underlying event source.
The repository creates a Roster
instance for the given roster id, loads any preexisting events, and passes those into the roster for restoration of its internal state.
Once restored, command handling is delegated to the aggregate root instance.
The aggregate root might emit events in as a result of handling the command, these events will be stored in the event source when the repository is flushed.
The aggregate root
Ultimately the aggregate root pattern is responsible for taking a decission based on the presented command.
It decides whether the command will be acted upon and how.
It communicates the resulting decission through an event, if the decission is considered business wise meaningful.
public class Roster : EventSourced,
IApply<RosterCreated>
{
public Roster(string id) : base(id){ }
public void Create(ClaimsPrincipal user, string name)
{
if (_name != null) return;
Emit(new RosterCreated()
{
Context = new Context
{
Id = Id,
What = nameof(RosterCreated),
When = DateTime.UtcNow,
Who = user?.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.Id)?.Value
},
RosterId = Id,
Name = name
}) ;
}
public void Apply(RosterCreated msg)
{
_name = msg.Name;
}
private string _name;
}
Usually the aggregate requires the decissions it has taken in the past as input for any future decission.
The IApply<T>
implementation allows to restore inner state based on previously emitted events.
These methods will be called via the EventSourced
base class in response to the EventSourcedRepository
loading and passing in historical events.
The Create
method first validates if it hasn't been invoked before, it does so by checking it's internal name property.
This check is purely for idempotency reasons and not considered meaningful, therefor it does not emit an event.
If it hasn't been invoked before, it emits a new RosterCreated
event.
For completeness, the RosterCreated
event has been defined with the following properties.
public class RosterCreated : SourcedEvent
{
public Context Context { get; set; }
public string RosterId { get; set; }
public string Name { get; set; }
}
As you can see, it also inherits from SourcedEvent
, this class contains some base properties required for the eventsourcing infrastructure to work.
Infrastructure
On the client side, I needed to create a custom event store and custom repository, just to coordinate loading and flushing events back to the API (more on this in a future post).
I also had to set a couple of properties, just because javascript isn't a typed language.
On the server however none of that is needed, all the infrastructure needs is a connectionstring to an azure table storage account and a table name.
Everything else can be derived from the type system and the base classes.
public static IServiceCollection AddEventSource(this IServiceCollection services)
{
var connectionString = Environment.GetEnvironmentVariable("CUSTOMCONNSTR_azure-storage-data");
var configuration = new EventsourcingConfiguration();
configuration.UseContainer(services);
var eventSource = new AzureTableStorageEventSource(connectionString, "Rosters");
configuration.UseEventSource(eventSource);
return services;
}
Startup
Plugging the infrastructure in happens at startup, typically in the ConfigureServices
callback provided by the asp.net mvc infrastructure.
public void ConfigureServices(IServiceCollection services)
{
services.AddEventSource();
services.AddMvc();
}
Wrapup
Handling a command on the server is a very common scenario, often combined with the store and forward pattern on the client though, and not with client side event sourcing.
Only when there are different clients with different online/offline characteristics it is possible that events emitted from the client need to be integrated with events emitted on the server.
Back to guide
This article is part of the building offline progressive web apps guide. Return to this guide to explore more aspects of building offline progressive web apps.