Event Sourcing on Azure – part 3: command validation

David Guida
Cloud Computing
March 2, 2021

Last time we saw how we can use CosmosDB and ServiceBus to store the events for our Aggregates. It’s not a full solution and there are still some gray areas, but I think we covered most of the ground.

However, every respectable application needs to validate the data before processing and storing it. We can’t just create a new Customer in our system because somebody told us to. What if we got bad input data?

I wrote already about Command Validation in CQRS in the past. Well, that was almost 4 years ago. It’s ancient, but the idea stands still.

For SuperSafeBank I decided to take a more agile approach and start by adding validation code directly in the Command Handlers. Why? Well because I want to keep things simple, that’s it.

So, let’s go back to our Create Customer command:

public class CreateCustomer
	public Guid CustomerId { get; }
	public string FirstName { get; }
	public string LastName { get; }
	public string Email { get; }

For the sake of the example, let’s say that the only thing we want to make sure is that the email address is unique across the system. We are not doing any validation on the format though.

Command Validation should make sure the business rules are satisfied. The other “basic” concerns like ranges, formats and so on, should be handled before it by creating the proper Value Objects.

The Command includes also a pre-populated Customer ID. We don’t want to rely on the Persistence layer to give it back to us because CQRS Commands should be almost fire-and-forget. Command execution won’t return any result. It’s either they work or they immediately throw.

But we need an ID back, so an option would be to generate a random GUID when we create the Command:

public async Task Create(CreateCustomerDto dto, CancellationToken cancellationToken = default)
	if (null == dto)
		return BadRequest();
	var command = new CreateCustomer(Guid.NewGuid(), dto.FirstName, dto.LastName, dto.Email);
	await _commandHandler.Process(command, cancellationToken);
	return CreatedAtAction("GetCustomer", new { id = command.Id }, command);

Now, another thing we need is a Customer Emails service. Something basic, responsible of just storing emails and checking if one exists already:

public interface ICustomerEmailsService
	Task ExistsAsync(string email);
	Task CreateAsync(string email, Guid customerId);

We’ll write an implementation based on CosmosDB, using the Email address as Partition Key.

The final step is to connect the dots and add the validation to the Command handler:

public class CreateCustomerHandler : INotificationHandler
	private readonly IEventsService _eventsService;
	private readonly ICustomerEmailsService _customerEmailsRepository;

	public async Task Handle(CreateCustomer command, CancellationToken cancellationToken)
		if (await _customerEmailsRepository.ExistsAsync(command.Email)){
			var error = new ValidationError(nameof(CreateCustomer.Email), $"email '{command.Email}' already exists");
			throw new ValidationException("Unable to create Customer", error);

		var customer = new Customer(command.Id, command.FirstName, command.LastName, command.Email);
		await _eventsService.PersistAsync(customer);
		await _customerEmailsRepository.CreateAsync(command.Email, command.Id);

Since we’re nice people, we can configure our system to capture ValidationExceptions and return them to the user in the proper format. Andrew Lock wrote a very good post about Problem Details, showing how to leverage a Middleware to handle them.

Now, in an ideal world this could be enough. But what happens if an error occurs when we store the customer email? We already have persisted the events, but not saving the email means that we might get past the validation with the same address. This will result in two customers with the same email being created, which would break our business rules.

So how can we handle this? One option is to add Transaction support and rollback the whole Handler execution if things go south. For more details, you can take a look at the Two-Phase-Commit technique or the Outbox Pattern.

The next time we’ll see what happens to the Aggregate Events once a Command is executed.


David Guida

Passionate, hard-working, and innovative software engineer with a strong desire for continuous learning and improving.
Over 15 years of experience in different domains, including Healthcare, Finance, and large-scale e-Commerce.

Microsoft MVP for Developer Technologies, confident on back-end and front-end tech stacks including .NET, NodeJs, PHP, Angular, React, as well as extensive experience on SQL, MongoDB, Azure, and GCP.

Everything is "just a tool". The secret is to know which one best suits your needs.

Related Posts

Newsletter BrazilClouds.com

Thank you! Your submission has been received!

Oops! Something went wrong while submitting the form