Serverless: An Example System Architecture

Storming events and applying autonomous service patterns

John Gilbert
5 min readJul 26, 2020

I was recently asked to do a brief presentation describing how I would put together a system architecture for a hypothetical online movie ticketing system. In this post I have adapted that content slightly to share it as a post.

Its an event driven system, so I started with some event storming to scope out a set of features that are simple enough for a presentation, with minimal noise, yet reasonably comprehensive. Then I zero in on the actors, because understanding the actors is key to creating a system architecture that can adapt to change. Then I apply three major patterns to arrive at a set of autonomous services.

Here is the content from the slides.

Event Storming — The Domain Events

EventFirst Thinking forces an inversion of responsibility that makes systems flexible and reactive

Event Storming — The Actors

Single Responsibility Principle: a module should be responsible to one, and only one, actor

Autonomous Service Patterns

Each pattern is geared for a different kind of actor

  1. Backend for Frontend (BFF)
  2. External Service Gateway (ESG) — the anti-corruption layer
  3. Control Service — complex event processing, orchestration and more

All connected through the event hub

Event Hub

The heart of the system

  • Upstream services publish domain events to an AWS EventBridge bus
  • The bus routes events to the appropriate AWS Kinesis stream
  • Downstream services consume domain events from a stream
  • The event lake stores all events in perpetuity in S3 and Elasticsearch via AWS Firehose
  • Events can be replayed from the lake

Presentation Tier

  • SPA Micro-Frontend
  • Served via AWS CloudFront from S3
  • Separate sites for Customer and Merchant

Show Mgt BFF

  • Used by the Merchants SPA
  • Manages domain entities: Theaters, Movies and Shows
  • Entities are stored in DynamoDB following the single table pattern
  • Access is provided by an API Gateway and GraphQL function
  • The trigger function consumes from the DynamoDB stream and publishes ShowScheduled domain events to the event hub

Shows BFF

  • Used by the Customers SPA
  • It allows customers to browse show times and movie information
  • This data is static and public, so it is stored in S3 and served from AWS CloudFront following the CQRS pattern
  • The listener function consumes ShowScheduled events and stores the transformed data in S3
  • A Lambda@Edge function publishes SearchedShows and ViewedShow events to the event hub for click stream tracking

Inventory Control

  • The listener function consumes ShowScheduled, TicketReserved, ReservationExpired, TicketPurchased and InventoryUpdated domain events
  • The events are correlated and collated in a micro event store using DynamoDB
  • The trigger function consumes from the DynamoDB stream, calculates on the correlated events and produces InventoryUpdated snapshot events, following the Event Sourcing pattern and ACID 2.0 principles

Reservation BFF

  • Used by the Customers SPA
  • The listener function consumes InventoryUpdated domain events
  • The events are correlated and collated in a micro event store using DynamoDB
  • The reserve function retrieves the events and calculates availability following the Event Sourcing pattern and ACID 2.0 principles
  • If available, a TicketReserved event is stored with a TTL and an oplock is updated
  • The trigger function publishes TicketReserved and ReservationExpired domain events to the hub

Payment ESG

  • Used by the Customers SPA
  • Integrates with the 3rd party payment processor, such as Stripe
  • During checkout the SPA requests the secret
  • Then the SPA confirms the payment, so that all PCI data remains in the scope of the payment processor
  • The webhook receives the external payment success event
  • Then it publishes a TicketPurchased domain event to the event hub

Email ESG

  • The egress function consumes TicketPurchased events from the event hub
  • Then sends email with calendar invite and movie information

Ticket Dispenser BFF

  • Used by the Kiosk
  • The listener function consumes TicketPurchased events and stores tickets in DynamoDB following the CQRS pattern
  • To redeem a ticket the Customer inserts their card into the dispenser kiosk
  • The confirm purchase command is invoked to validate that the card purchased a ticket
  • If so the ticket status is updated and the ticket information is return
  • The trigger function publishes TicketDispensed domain events to the hub

Architecture Enables Change

Of course, there are plenty more details, such as GraphQL schema and the single table pattern, algorithm specifics, OIDC with AWS Cognito, keeping data lean with DynamoDB TTL, envelop encryption and much more. But, it is in these details that we find most of the churn in a system’s design.

The role of architecture is to enable change, by defining fortified boundaries around things that change together, so that we can control the scope and impact of any given change. The key to defining these boundaries is understanding that people (i.e. actors) drive change.

Your users drive changes to the frontend, the owners of external systems drive changes that are out of our control and your business dictates the rules and processes that glue everything together. The autonomous service patterns are designed to support these different kinds of actors, so that each service is responsible to a single actor.

Once these fortified boundaries are in place, then teams are free to experiment and iterate on the details. The serverless-first approach allows them to focus on these experiments.

For more thoughts on serverless and cloud-native checkout the other posts in this series and my books: Software Architecture Patterns for Serverless Systems, Cloud Native Development Patterns and Best Practices and JavaScript Cloud Native Development Cookbook.

--

--

John Gilbert

Author, CTO, Full-Stack Cloud-Native Architect, Serverless-First Advocate