Serverless CQRS/ES on Azure
Serverless cloud computing gained a lot of popularity recently. The advantages are obvious - low cost and fast scalability. However, most companies use serverless just for simple things like sending emails or transformation/validation pipelines.
Today, I would tell you how to use serverless for complex scenarios, like CQRS and Event Sourcing (if you do not know what is it, search for Greg Young), below is a simplified view of my latest project design. It is simplified to fit in an article but still shows a concept.
It may look over-complicated at first glance, but actually, if you use microservices approach everything should be automated anyway, event deployments of azure functions are more reliable and faster compared to regular kubernetes or service fabric apps.
Let`s take a look inside.
Each DDD bounded-context deployed to its own resource group and developed within separate repository as usual. It allows sharing knowledge across teams and reuses a similar approach of microservices development across the entire platform by replicating Microservice architecture and SDKs. There are two main integration points, the outside world through API Management and cross-service communication using Event Grid.
- GraphQL Request authenticated by API Management sends to azure function "1" or "7" using the control flow policy. Function "1" handles all GraphQL mutations requests and transforms them into an internal command contract envelope and sends it to "2 " commands queue.
- Commands queue has one subscriber using functions trigger - Command Handler. We need this queue to store failed commands in dead-letter store and analyze later to identify issues and to handle load spikes.
- Command handler - the most complex part of any CQRS project, it is restoring aggregate root from the event store "4" using command aggregate Id as partition key or creates a new one in case of creating command. Then, it applies command onto an aggregate root that emits event sourcing events. Event sourcing events transactionally stored in the event store table by batch operation, all events have partition key of aggregate, in the same operation in case of a successful store we emit an integration event to Event Grid "4".
- Event Grid and Event store always handled in one operation to keep the system consistent, events published to the grid only after a successful store, in case of any issue data is rolled back, no events emitted except operation failure signal.
- Event handler functions contain many event handlers that handle domain-specific integration events and events from related domains to pre-compile denormalized query data in table storage.
- Fully denormalized query store with exact mapping to GraphQL queries allows efficiently get all required data by one request.
- GraphQL queries redirected by API Management control flow are handled here, it reads partition or row based on GraphQL Query data and then post-process data with GraphQL.NET to remove unnecessary fields.
Deployment - every component automated with azure resource management templates and Azure DevOps pipelines, and it has three major parts.
- Platform components - cross-cutting system components, Event Grid, logging & app insights, a key vault with secrets, deployed and managed in separate platform repo.
- Gateway - essentially, system entry point, shared across all services. SignalR for GraphQL subscriptions and API Management for services and front-end resources like CDN.
- Microservice - actual service, deployed in separate resource groups with provisioning of Event Grid subscription and API management control flow policies.
I do not want to make it boring-long so would stop here. If you are interested in some aspects of this architecture, like testability, latency or consistency just let me know in comments, and I would prepare the next article about it. Thank you for reading!
Cloud Architect | Microsoft MVP | Platform Engineer | DevOps
5 年Thanks for the article! It would be great to read about testing.