📝 Introduction
In this post, we’ll share an example architecture for Ticketmaster – a popular digital ticket-selling platform for concerts, sports, and other live events. We’ll design this as software architects and create four hierarchical diagrams: Context, Container, Component, and Code (not sure what these are? Read this). We’ll annotate the building blocks and highlight user interaction (data flow) within the system using IcePanel Flows.
For keen-eyed system design readers, we’ll first set the scope of the architecture by defining the behaviour of the system (functional requirements) and the qualities the system should have (non-functional requirements). Afterwards, we’ll go through each layer of the C4 model.
You can view the final architecture at this link: https://s.icepanel.io/DWnaysJ3cbCQqg/k40S
🔎 Scope
Functional requirements are simple. For a ticket-selling platform, users should be able to:
- Search for events by keywords (title, artist, venue, etc.).
- View event details and available tickets.
- Purchase tickets for an event.
For non-functional requirements, the system should be:
- 99.99% available for events search and viewing tickets.
- Strongly consistent for ticket selling to prevent double-booking.
- Highly performant with low latency.
- Scalable to handle large traffic spikes for popular events.
Let’s start with the first diagram, Context.
Level 1 - Context
This high-level view defines the main actors interacting with our internal system (Ticketmaster) and the external systems it depends on. In this case, we have two main actors:
- User: who searches for events and buys tickets.
- Admin: who adds, removes, and updates events (e.g., changing dates, adding more tickets, or modifying venues).
In this view, we also have three external systems that our internal system depends on:
- Payment Provider (e.g., Stripe, PayPal): Responsible for processing payment transactions and managing the complete payment lifecycle. Our ticketmaster system does not handle sensitive card details, it only creates an internal booking reference to track transaction status. When a user initiates a purchase, they are redirected to the payment provider’s secure interface where card details are collected and processed.
- Email Provider (e.g., Twilio): Manages all transactional email delivery to users, including order confirmations, e-tickets, reminders, etc. While this could be modeled as a “Container” within our system (e.g., using AWS SES), for simplicity, we’ll offload this to a third-party service that sits outside our deployment boundary. Our system publishes notification events to an internal queue, which our notification service then forwards to the external email provider.
- Identity Provider (e.g., Google OAuth, Auth0): Provides federated authentication and single sign-on (SSO) capabilities, allowing users to authenticate using their existing social media or enterprise accounts. Our system receives verified identity tokens and uses them to create or authenticate user sessions without handling passwords directly.
Also worth pointing out is that we have a third “hidden” actor: web crawlers. These are bots that scan the internet, visit websites, crawl all the information on those sites, and store the data for various purposes (e.g., LLM training). In our example, we may have bots visiting Ticketmaster to collect event details, pricing information, and venue data. There are defensive mechanisms and protocols to tell bots how to interact with our website (e.g., via robots.txt, rate limiting, and CAPTCHA), however, this is outside the scope of this post.
Level 2 - Container
This diagram is where we model a collection of independently deployable or runnable applications or data stores that are essential for the overall software system to function. This could be a web application, server, datastore, or a serverless function like AWS lambda.
We’ll design this system using a microservices architecture. This approach gives us several benefits that align with our non-functional requirements, such as:
- Improved scalability: Each service (or container) can scale up or down based on demand and resource usage.
- Isolated failures: Problems in one service won’t bring down the whole system.
- Faster deployments: We can update individual services without redeploying everything.
Our system is composed of the following Containers:
- Events Service: Manages event creation, updates, and retrieval, and serves event information to users.
- Booking Service: Orchestrates the ticket reservation process, manages seat selection, and coordinates with payment processing.
- Payment Service: Integrates with the payment gateway, tracks transaction statuses, and handles payment confirmations and refunds.
- User Service: Manages user registration, profile updates, and authentication.
- Notification Service: Sends emails, SMS messages, and push notifications for bookings, updates, and reminders.
- Events Database: Stores event details, venue information, ticket inventory, pricing, and booking records.
- Events Cache: Caches frequently accessed event data to improve performance.
- Ticket Locking Cache: Maintains temporary seat reservations (with a time-to-live) to prevent double-booking during the checkout process.
In this architecture, we use the following technologies:
- AWS API Gateway: A scalable and secure entry point for all API requests.
- AWS EC2: Web service that provides reliable and secure compute for the different microservices.
- AWS Lambda: A lightweight, serverless compute service that is cost-effective, event-driven, and automatically scales on demand.
- PostgreSQL: An ACID-compliant relational database that ensures data integrity and strong consistency. Ideal for financial transactions, like in a ticketing system.
- Redis: A fast in-memory cache that improves response times and reduces database load.
- Elasticsearch: A powerful full-text search engine optimized for complex queries, ideal for searching events and tickets efficiently.
We’ve designed three common data flows using IcePanel. Check out these flows and play them step by step to see how our system works.
- Searching on Ticketmaster: https://s.icepanel.io/DWnaysJ3cbCQqg/TPlD
- Viewing event details: https://s.icepanel.io/DWnaysJ3cbCQqg/1ewS
- Booking a ticket: https://s.icepanel.io/DWnaysJ3cbCQqg/yipZ
Level 3 - Component
In the C4 model, a component is a grouping of related functionality encapsulated behind a well-defined interface. For example, a collection of classes behind an interface. Let’s look at the Components in Ticketmaster.
1. Events Service
The diagram below illustrates how user requests to view event details flow through the system in the Events Component. The API Gateway routes incoming requests to the EventController, which acts as the main entry point for handling event-related operations. Before querying the database, the controller checks the RedisClient to see if the requested event data is already stored in the Events Cache (Redis). If the data exists in the cache, it’s returned immediately to ensure faster response times. If not, the EventRepository retrieves the event details from the Events Database (PostgreSQL), after which the RedisClient updates the cache with the new data for future requests.
2. Booking Service
The BookingController operates as the central orchestrator for the Booking Component. It receives booking requests from users via RESTful APIs. When a booking request arrives, the controller checks the InventoryManager to verify ticket availability and reserve seats. It uses the Ticket Lock datastore (Redis) to temporarily hold seats during checkout, preventing double-booking. The controller retrieves event and pricing information from the BookingRepository, which connects to the Events Database (PostgreSQL). Once ready to purchase, the controller sends payment details to the “Payment Service” Component for processing. In case of high traffic on the website, the Waiting Queue manages incoming requests upstream and processes users in batches, sending them to the BookingController.
3. Payment Service
The Payment Service handles all financial transactions in our ticketing system. When the Booking Service initiates a payment, the PaymentController receives the request and coordinates the end-to-end payment flow. The controller delegates transaction processing to the PaymentGateway, which communicates with external payment providers like Stripe to charge customers, verify transactions, and process refunds when needed. Meanwhile, the PaymentRepository manages all payment data, storing transaction records in the payments table. After successful payments, the PaymentController calls the PaymentRepository to update the payment status and confirm the ticket reservation, then pushes a reservation event to the Notifications Queue so customers receive immediate payment confirmations via email or SMS.
Let’s go one level deeper with the source code in the Code layer.
Level 4 - Code
This is where we can view implementation details at the code level. We don’t recommend creating extensive diagrams for this; instead, we link directly to the code. However, here’s the general structure of these components for reference. We’ll briefly go over two Components: Booking and Events.
Code Classes in “Events Component”
This is the core business logic on how we serve event data to the user. The EventController class provides a set of RESTful APIs to view more information about the event, e.g., description, date, category, venue, tickets, etc. The EventRepository class serves as the data layer for the events database. Any queries to the database go through this interface. The RedisClient manages the state of the cache, from storing event objects via event IDs as keys, to reading from cache for faster response time. These classes have roughly the following methods:
EventController
- GET /events - List all events with pagination
- GET /events/:id - Get detailed information for a specific event
- GET /events/:id/tickets - Get available tickets for an event
EventRepository
- getEvents(page, limit, filters) - Query events from database with pagination
- getEventById(id) - Retrieve a single event by its unique identifier
- getTicketsByEventId(eventId, page, limit) - Fetch tickets associated with an event
RedisClient
- readEventFromCache(eventId) - Attempt to retrieve cached event data
- writeEventToCache(eventId, eventData, ttl) - Store event data in cache with time-to-live
Code Classes in “Booking Component”
This component orchestrates the ticket reservation flow. The BookingController exposes RESTful APIs for users to check ticket availability, initiate bookings, and retrieve booking details. The InventoryManager coordinates with the Ticket Lock (Redis) to implement distributed locking, ensuring that tickets cannot be double-booked during concurrent reservation attempts. Once a ticket is successfully locked, the BookingRepository persists the reservation details to the database and coordinates with downstream services to complete the payment flow.
These classes have roughly the following methods:
BookingController
- POST /bookings - Create a new booking and initiate reservation
- GET /bookings/:id - Retrieve booking details and status
- POST /bookings/:id/confirm - Confirm booking after successful payment
- DELETE /bookings/:id - Cancel a booking and release locked tickets
InventoryManager
- checkAvailability(eventId, ticketIds) - Verify if requested tickets are available
- reserveTickets(bookingId, ticketIds, ttl) - Lock tickets temporarily (default 5 minutes)
- releaseTickets(bookingId, ticketIds) - Release ticket locks if booking fails or expires
- confirmReservation(bookingId) - Finalize ticket reservation after payment
BookingRepository
- createBooking(userId, eventId, ticketIds, amount) - Create new booking record
- getBookingById(bookingId) - Retrieve booking details
- updateBookingStatus(bookingId, status) - Update booking state (pending, confirmed, cancelled)
- expireBooking(bookingId) - Mark booking as expired after TTL
These are two code examples for the final layer in the C4 model. Other class structures worth highlighting include the user sign-in/sign-up flow, third-party payment flow, and search flow.
Conclusion
In this post, we designed a ticket-selling platform using the C4 model on IcePanel. We began with the core requirements and modeled the system from the top down, starting with the Context layer, followed by the Container, Component, and finally the Code layer.
Let me know which system you’d like to see modeled next on IcePanel!
📚 Resources
- https://www.ticketmaster.co
- https://aws.amazon.com/api-gateway
- https://aws.amazon.com/sqs
- https://www.forbes.com/advisor/business/software/best-payment-gateways
- https://www.elastic.co/elasticsearch
- https://en.wikipedia.org/wiki/robots.txt
- https://redis.io/open-source
- https://aws.amazon.com/microservices
- https://aws.amazon.com/pm/lambda