Welcome to the final and most exciting module of the course! Over the past eleven modules, you've acquired all the necessary skills: you've mastered async Python, built gRPC services, containerized applications with Docker, and implemented observability. Now, it's time to synthesize this knowledge and build a complete, production-style microservices application from the ground up.
This lesson kicks off our end-to-end project: a distributed e-commerce system. We begin with the single most important phase of any microservices project—the design phase. Our goal today is to architect the system by defining its service boundaries, specifying the synchronous gRPC API contracts for inter-service communication, and designing the asynchronous event schemas that will enable resilient, decoupled workflows. Getting this blueprint right is the key to a scalable and maintainable system.
1. Decomposing the Monolith: Finding Service Boundaries
Remember our discussions in Module 3 about Domain-Driven Design (DDD)? We'll use its core concept, the Bounded Context, to break down our conceptual e-commerce monolith into a set of coherent, independent microservices. A bounded context is a logical boundary within which a specific domain model is well-defined and consistent. Each bounded context is a candidate for a microservice.
For a typical e-commerce application, we can identify several core business capabilities:
- User Management: Handling user accounts, profiles, and authentication.
- Order Management: Creating, retrieving, and updating customer orders.
- Payment Processing: Handling payment transactions for orders.
- Inventory Management: Tracking stock levels for products.
- Product Catalog: Managing product information, pricing, and categories (we will simplify this for our project and merge it conceptually with other services to focus on the core interactions).
This video provides a great refresher on using Bounded Contexts to identify these domains from a monolithic database model.
Using #bounded #context for breaking #database #model into #microservices using #domaindrivendesign
Watch this short video "Using Bounded Context for Breaking Database Model" from SPS Tech. It perfectly illustrates our first design step.
Start by watching the introduction to understand why this is the crucial first step. Next, see how to identify domains from a database schema. Finally, observe how these domains are grouped into bounded contexts, which then map to microservices. Note the Sales and Inventory contexts, which closely mirror our own plan.
Based on this approach, we will define the following five core services for our project:
- User Service: The authority on all things related to users. It will manage user creation, retrieval, and authentication (generating JWTs).
- Order Service: Responsible for the lifecycle of an order. It will handle order creation and status updates. It will act as a coordinator, communicating with other services.
- Payment Service: Handles all payment-related logic. Its core responsibility is to process payments for a given order and report success or failure.
- Inventory Service: Manages the stock levels of all products. It will provide functionality to check stock, reserve items, and release items.
- API Gateway: While not a domain service, this is our system's front door. It will be a FastAPI application that exposes a RESTful API to the outside world and communicates with the backend gRPC services.
The following diagram illustrates this principle of decomposition. Notice how a concept like a "User" can exist in different contexts with different attributes (e.g., as a Buyer in the Orders context or a Payer in the Payment context). This is the essence of a bounded context.

2. Designing Synchronous Communication: gRPC API Contracts
With our services defined, we must design their communication contracts. For synchronous, request-response interactions, we will use gRPC. As you learned in Module 5, these contracts are defined in .proto files. A well-designed .proto file is a clear, unambiguous, and programming-language-agnostic definition of a service's API.
A gRPC contract consists of two main parts:
service: A collection of RPC methods.message: The data structures used as inputs (requests) and outputs (responses) for those methods.
Let's outline the API for our UserService. It needs to handle user creation and authentication.
user.proto (Initial Draft)
syntax = "proto3";
package ecommerce.v1;
// The user service definition.
service UserService {
// Creates a new user.
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
// Authenticates a user and returns a JWT.
rpc AuthenticateUser(AuthenticateUserRequest) returns (AuthenticateUserResponse);
}
// ---- Messages ----
message User {
string id = 1;
string email = 2;
string display_name = 3;
}
message CreateUserRequest {
string email = 1;
string password = 2;
string display_name = 3;
}
message CreateUserResponse {
User user = 1;
}
message AuthenticateUserRequest {
string email = 1;
string password = 2;
}
message AuthenticateUserResponse {
string token = 1; // The JWT
}
This is a good start. It's clear and follows best practices like using request/response wrapper messages. Now, consider the OrderService. When a user wants to create an order, the OrderService will need to:
- Validate the user ID.
- Check if the products are in stock (
InventoryService). - Calculate the total price (we'll keep this simple).
- Create the order in its own database.
- Initiate the payment process (
PaymentService).
Its CreateOrder RPC might look something like this:
order.proto (Excerpt)
service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
}
message OrderItemInput {
string product_id = 1;
int32 quantity = 2;
}
message CreateOrderRequest {
string user_id = 1;
repeated OrderItemInput items = 2;
}
message CreateOrderResponse {
string order_id = 1;
string status = 2;
}
To see what a comprehensive, production-grade set of .proto files for an e-commerce system looks like, study the following example.
Complete Example: E-commerce API
This section of the "How to Define Protocol Buffers" guide provides an excellent, detailed .proto definition for an e-commerce platform.
Please read the entire code block under the heading "Complete Example: E-commerce API". As you read, focus on: Service Boundaries: Notice how the example is split into a ProductService and an OrderService, each with its own set of messages and RPCs. Message Design: Look at the structure of Product, Order, OrderItem, and PaymentInfo. See how they use various data types, enums, and nested messages. RPC Definitions: Analyze the request and response wrappers for methods like CreateOrder and ListProducts. Note the use of Pagination for list methods. This example is a fantastic reference for the contracts we will be building.
3. Designing Asynchronous Communication: Event Schemas
Not all interactions should be synchronous RPC calls. To create a resilient and decoupled system, we'll use an event-driven approach for certain workflows. For example, when an order is successfully paid for, the PaymentService shouldn't have to synchronously call the OrderService and potentially a NotificationService. Instead, it can simply publish an OrderPaid event to a message broker (like Redis Pub/Sub, which we'll use). Other interested services can then subscribe to this event and react accordingly.
Just as we define schemas for our RPCs, we must define schemas for our events. This ensures that producers and consumers agree on the event structure. We can use Protobuf for this as well! Even if the event is transmitted as JSON over the message bus, the .proto file serves as the definitive schema.
Let's design a schema for two key events:
events.proto
syntax = "proto3";
package ecommerce.v1.events;
import "google/protobuf/timestamp.proto";
// Published by OrderService when a new order is created.
message OrderCreatedEvent {
string order_id = 1;
string user_id = 2;
repeated OrderItem items = 3;
int64 total_price_cents = 4;
google.protobuf.Timestamp created_at = 5;
message OrderItem {
string product_id = 1;
int32 quantity = 2;
int64 unit_price_cents = 3;
}
}
// Published by PaymentService when an order is successfully paid.
message OrderPaidEvent {
string order_id = 1;
string payment_id = 2;
google.protobuf.Timestamp paid_at = 3;
}
With these event schemas, we can build decoupled workflows:
- A
NotificationServicecould subscribe toOrderPaidEventto send a confirmation email to the user. - The
InventoryServicecould subscribe toOrderCreatedEventto tentatively reserve stock.
4. The Complete System Architecture
Let's put all these pieces together into a high-level architecture diagram. This will be our map for the rest of the project.

In this diagram, you can see:
- API Gateway: The entry point that translates external REST calls to internal gRPC calls.
- Microservices: Our five services (
User,Order,Payment,Inventory, and potentially others likeCatalogorDiscount). - gRPC Communication: The solid lines representing direct, synchronous service-to-service calls.
- Message Broker (Event Bus): The central component (labeled RabbitMQ here) that enables asynchronous, event-driven communication.
- Databases: Each service has its own dedicated database, enforcing the "database-per-service" pattern.
Conclusion
In this crucial first lesson of our project, we have laid the architectural groundwork for our e-commerce platform. We didn't write any code, but we made the most important decisions that will guide all the code to come.
Key Takeaways:
- Service Boundaries: We decomposed our system into five core microservices (
User,Order,Payment,Inventory,API Gateway) using the principles of Domain-Driven Design and Bounded Contexts. - gRPC API Contracts: We designed initial
.protofiles to define the synchronous, request-response communication between services, establishing a clear and strongly-typed API. - Async Event Schemas: We defined schemas for asynchronous events (
OrderCreatedEvent,OrderPaidEvent) which will enable us to build resilient, decoupled, and scalable workflows using a message broker. - Architectural Blueprint: We have a clear architectural diagram that serves as our guide for implementation.
Preview of the Next Lesson
With our design in place, it's time to start building. In the next lesson, we will implement our first service from scratch: the User Service. You will write the gRPC service implementation in async Python, set up its database, and implement JWT-based authentication. Let the coding begin