93. Case Study: Building a Ticket Booking System with gRPC
Case Study: Building a Ticket Booking System with gRPC
Welcome to an architectural journey of building a scalable and efficient ticket distribution system. In this article, I’ll break down the design and implementation of a ticket booking microservice using gRPC, from the underlying concepts all the way to ready-to-use code examples.
Background: When HTTP REST Starts to Hit Its Limits
As engineers, we often run into the performance limits of HTTP REST. As an organization grows, API traffic increases, real-time requirements rise, or services become more and more complex, RPC (Remote Procedure Call) solutions like gRPC become very appealing. gRPC, born out of Google, offers efficiency, performance, and service-to-service communication capabilities that are far superior to traditional REST protocols.
Case Study: An Event Ticket Booking System
Imagine you’re building a digital ticket booking system. In a microservice architecture, the key components you’ll need include:
- Order Service: Receives order requests and handles temporary payment holds.
- Inventory Service: Stores remaining ticket counts and performs atomic stock decrements.
- Notification Service: Notifies customers via email/WhatsApp/Push when a booking succeeds.
- Auth Service: Manages user authorization.
For this case study, we’ll integrate microservices 1 and 2 using gRPC.
Understanding the System Flow
To keep the booking process efficient and consistent across services, here’s the flowchart:
graph TD
A[Client] -->|Book Ticket| B(Pemesanan Service)
B --> |Check & Lock Ticket| C(Inventaris Service)
C --> |OK| B
B --> |Confirmed| A
B --> |Failed| A
The table below details the flow at each step:
| Step | Service | Description |
|---|---|---|
| Book Ticket | Client -> Order | Ticket purchase request |
| Check & Lock Ticket | Order -> Inventory | Checking and locking ticket stock |
| OK / Failed | Inventory -> Order | Confirmation of availability or lock failure |
| Confirmed/Failed | Order -> Client | Success/failure notification to the user |
Defining the Proto File: gRPC Ticketing
Inter-service communication with gRPC relies on a .proto file to define the contract. It’s simple: just two key methods, Check & Lock the ticket.
1syntax = "proto3";
2
3package ticketing;
4
5service InventoryService {
6 rpc CheckAndLock (TicketRequest) returns (TicketResponse) {}
7}
8
9message TicketRequest {
10 string event_id = 1;
11 int32 quantity = 2;
12}
13
14message TicketResponse {
15 bool success = 1;
16 string message = 2;
17}- CheckAndLock: Ensures the tickets are available and locks them temporarily.
A Quick Implementation (Golang)
1. Inventory Service
1// inventory_service.go
2type InventoryServiceServer struct {
3 mu sync.Mutex
4 tickets map[string]int // eventID -> remaining tickets
5}
6
7func (s *InventoryServiceServer) CheckAndLock(ctx context.Context, req *pb.TicketRequest) (*pb.TicketResponse, error) {
8 s.mu.Lock()
9 defer s.mu.Unlock()
10
11 stock := s.tickets[req.EventId]
12 if stock < int(req.Quantity) {
13 return &pb.TicketResponse{Success: false, Message: "Not enough tickets"}, nil
14 }
15 s.tickets[req.EventId] -= int(req.Quantity)
16 return &pb.TicketResponse{Success: true, Message: "Locked"}, nil
17}2. Order Service (gRPC Client)
1// order_service.go
2conn, _ := grpc.Dial("inventaris:50051", grpc.WithInsecure())
3client := pb.NewInventoryServiceClient(conn)
4
5resp, err := client.CheckAndLock(ctx, &pb.TicketRequest{
6 EventId: "EVT123",
7 Quantity: 2,
8})
9
10if err != nil || !resp.Success {
11 // handle booking failure
12}Multi-Client Simulation: The Race Condition Problem
The biggest challenge in a ticketing application is the race condition (two people checking out the same ticket almost simultaneously). Here’s a simple simulation (pseudocode):
1go func() {
2 // Client A
3 client.CheckAndLock(ctx, TicketRequest{EventId: "EVT123", Quantity: 2})
4}()
5go func() {
6 // Client B
7 client.CheckAndLock(ctx, TicketRequest{EventId: "EVT123", Quantity: 2})
8}()In the InventoryService, locking the stock with a mutex (mu.Lock()) keeps the process atomic and prevents overselling.
Advantages of gRPC in a Ticketing System
- Performance: gRPC uses Protobuf, which is faster and more bandwidth-efficient.
- Bidirectional Streaming: Supports two-way streaming, useful for real-time notifications to the client.
- Contract First API: Validation via the proto makes integration more disciplined and drastically reduces inter-service errors.
- Polyglot Friendly: Orders from a Go backend, inventory from Java, notifications from Python? It all works because gRPC has bindings for many languages.
Comparing Performance: REST vs gRPC
| Aspect | REST (JSON/HTTP) | gRPC (Protobuf/HTTP2) |
|---|---|---|
| Serialization | Slow, bulky JSON | Blazing fast, compact |
| Latency | 3–6x higher | Very low |
| Multilingual | Manual client needed | Automatic via codegen |
| Streaming | Hard/limited | Built-in, native |
| Concurrency | Manual locking, slow HTTP | HTTP/2 native multithreaded |
Reality on the Ground: Partial Failure & Retry Scenarios
gRPC offers deadline and retry features to automatically handle failed requests—features that are highly relevant in a ticket booking system that demands a high SLA.
1ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
2defer cancel()
3
4_, err := client.CheckAndLock(ctx, &pb.TicketRequest{...})
5if grpc.Code(err) == codes.DeadlineExceeded {
6 // retry logic
7}Engineering Tips for a gRPC Ticket System
- Timeout & Retry: Always set a retry strategy. Don’t let the client wait indefinitely.
- Distributed Lock: At large scale, use a distributed lock (for example, Redis redlock) so that stock stays consistent across many replicas.
- Observability: Take advantage of gRPC interceptors for tracing and logging.
- Interface Backward Compatibility: Don’t remove proto fields without a fallback/versioning strategy.
Conclusion
Applying gRPC in a ticket booking system delivers a boost in performance, data consistency, and scalability. It’s a great fit for polyglot microservices ecosystems that demand low latency and handle high traffic.
Where should you start? Try replicating the scenario above, run a benchmark against REST/HTTP, and enjoy a more reliable architecture for nationwide-scale ticket distribution systems.
References:
See you in the next post. Don’t forget to give it a 👏 and share if you found it helpful!