Unit 2
Service Design
Microservice Boundaries, API design for Microservices,
Data and Microservices, Distributed Transactions and
Sagas, Asynchronous Message-Passing and
Microservices, dealing with Dependencies, System
Design and Operations: Independent Deployability,
More Servers, Docker and Microservices, Role of
Service Discovery, Need for an API Gateway, Monitoring
and Alerting. Adopting Microservices in Practice:
Solution Architecture Guidance, Organizational
Guidance, Culture Guidance, Tools and Process
Guidance, Services Guidance
Microservice Boundaries
• Defining microservice boundaries is a key aspect of
designing a microservices architecture.
• The boundaries determine how a system is divided
into smaller, independently deployable services.
• Identifying appropriate boundaries ensures that
each microservice is loosely coupled, highly
cohesive, and able to evolve independently.
• Some common approaches to determining
microservice boundaries are :
1. Domain-Driven Design (DDD)
Concept: DDD is one of the most popular methodologies for defining microservice
boundaries. The core idea is to break down a system based on the domain it
operates in.
Boundaries: Each microservice should represent a bounded context in the domain. A
bounded context is a conceptual boundary within which a specific domain model
applies. Within this context, entities, services, and concepts are cohesive and have
clear definitions.
Advantages: This approach ensures that each microservice is focused on a single part
of the business and can evolve without affecting other services. It also helps
maintain consistency in terminology and behavior within the service boundaries.
2. Business Capabilities
Concept: A microservice can be defined around a specific business capability or
function.
Boundaries: The service boundaries should align with major business functions or
features, such as user management, payment processing, inventory, and so on.
Advantages: This ensures the service is business-centric, easy to understand, and
focused on solving a particular problem or task. Each service can evolve
independently as the business needs change.
3. Data Ownership and Consistency
Concept: Microservices should have clear ownership of specific data to ensure they
are decoupled and can evolve independently.
Boundaries: A microservice owns its own data store and enforces data consistency
within its boundaries. If a service needs to interact with other services' data, it
should do so via well-defined interfaces or APIs.
Advantages: This reduces tight coupling between services, improving their autonomy
and scalability. It also helps maintain data consistency and integrity.
4. Communication Protocols (Synchronous vs Asynchronous)
Concept: The way microservices communicate also affects their boundaries.
Boundaries: Microservices using synchronous communication (like
REST(Representational State Transfer) or gRPC(cross-platform Remote
Procedure Call ) ) might need to be tightly coupled, while those using
asynchronous communication (like event-driven systems with message
queues or pub/sub systems) can be more loosely coupled.
Advantages: This separation of communication styles can help ensure the
system is resilient and scalable, with services able to evolve
independently.
Let’s break down the concept of defining microservice boundaries with a simple example. Imagine
you're building an e-commerce platform.
Example Scenario: E-commerce System
An e-commerce platform typically includes several features, such as:
Product Management – Managing products, inventory, and product details.
Order Management – Handling orders placed by customers, including payment processing.
Customer Management – Managing customer accounts, authentication, and profiles.
Shipping – Managing the shipment of products to customers.
Now, let's think about how to divide this system into microservices, focusing on defining boundaries
between them.
1. Domain-Driven Design (DDD)
In DDD, we think about the system as a collection of bounded contexts. Each bounded context
corresponds to a part of the system that operates in its own model and rules.
Example:
Product Management can be its own service. The product data (like product name, price, etc.) is
owned and managed by this service.
Order Management could be another service. This service would handle the logic related to processing
orders, payments, and generating invoices.
Customer Management might also be a separate service, responsible for managing user accounts,
profiles, and authentication.
These services will be independently deployable and loosely coupled from each other. For example, if
you need to update the way orders are processed, you can do so without affecting product
information or customer data.
2. Business Capabilities
In a business-oriented approach, each microservice aligns with a major business capability.
Example:
Product Catalog Service: Handles everything related to the product catalog, such as adding
products, viewing product details, and managing inventory.
Order Service: Responsible for processing customer orders, applying discounts, and triggering
payment workflows.
Shipping Service: Manages the shipment of products, tracking, and delivery status.
Each service focuses on a single business capability. For instance, the Shipping Service doesn’t
need to worry about how orders are processed, just how to ship them once the order is placed.
3. Data Ownership and Consistency
Each service should own its data to prevent dependency on other services' databases, which
ensures autonomy.
Example:
Product Management service has its own database for product information.
Order Management service has a separate database for tracking orders.
Customer Management has its own database for user accounts.
This separation allows each service to manage its own data and evolve independently. For
example, if the Customer Management team wants to switch to a new authentication system,
they can do it without worrying about how orders are processed or how products are
managed.
4. Communication Between Microservices
Even though microservices are independent, they still need to
communicate. For example, when a customer places an order, the
Order Service needs to communicate with the Inventory Service to
check product availability, and then it might need to interact with the
Shipping Service to create a shipment once the order is confirmed.
Example of communication:
Order Service might use REST APIs to communicate with the Product
Service (for checking product availability) and Shipping Service (for
arranging delivery).
Alternatively, the services might communicate asynchronously through
event-driven architecture (using a message queue or event bus) to
avoid blocking one service while waiting for another.
API design for Microservices
• API design for microservices is crucial because
the way microservices communicate with each
other directly impacts the overall system’s
performance, scalability, and maintainability.
• Well-designed APIs enable microservices to
interact efficiently while ensuring loose
coupling, autonomy, and clear boundaries
between services.
Two practices in crafting APIs for
microservices are
1. Message Oriented
• Share information between components .
• Expose general entry points into a
component (eg- an IP address and port
number ) and receive task – specific
messages at the same time .
• Complex system as collection of services
exchanging messages over a wire.
In a message-oriented architecture (MOA), components of a system communicate with each
other by sending messages over a network, usually via message brokers or queues, rather than
direct method calls. This pattern is often used in distributed systems where services interact
asynchronously by exchanging messages.
Let’s break this down with a real-world example based on a microservices-based E-commerce
System.
Example: E-commerce System Using Message-Oriented Architecture
In this system, you have multiple services such as Order Management, Inventory Management,
Shipping, and Payment Processing. These services need to communicate with each other
asynchronously to process tasks without direct coupling.
Let’s say these services interact using a message queue like RabbitMQ, Kafka, or Amazon SQS.
Components
Order Management Service
Inventory Management Service
Shipping Service
Payment Service
Message Broker (e.g., RabbitMQ, Kafka)
Communication Workflow with Messages
Step 1: Order Creation
When a customer places an order through the Order Management Service, it needs to
communicate with the Inventory Management Service and Payment Service. However,
instead of making direct synchronous HTTP calls, the Order Management Service publishes
an orderCreated message to a message queue.
Step 2: Message Published to the Queue
The Order Management Service publishes a message like:
{
"eventType": "OrderCreated",
"orderId": "12345",
"customerId": "6789",
"items": [ { "productId": "abc", "quantity": 2 },
{ "productId": "xyz", "quantity": 1 }
],
"totalAmount": 150.00
}
This message is sent to a message queue ,where it waits for
consumers to process it.
Step 3: Inventory Management Service Consumes the Message
The Inventory Management Service subscribes to the orderCreated message. When the
message is published to the queue, the Inventory Management Service consumes it and
checks if the products in the order are available in inventory. If there’s enough stock, it
decrements the inventory quantity.
Example message received by Inventory Management Service:
{
"eventType": "OrderCreated",
"orderId": "12345",
"customerId": "6789",
"items": [
{ "productId": "abc", "quantity": 2 },
{ "productId": "xyz", "quantity": 1 }
]
}
If inventory is available, it might send a confirmation message:
{
"eventType": "InventoryConfirmed",
"orderId": "12345",
"status": "success"
}
Step 4: Payment Service Consumes the Message
Similarly, the Payment Service listens for messages related to new orders. When it receives the orderCreated
message, it processes the payment for the order. Once the payment is processed successfully, it publishes a
paymentProcessed message.
Example of a paymentProcessed message:
{
"eventType": "PaymentProcessed",
"orderId": "12345",
"paymentStatus": "success",
"paymentId": "7890" }
Step 5: Shipping Service Consumes the Message
Once the payment is processed successfully, the Shipping Service can then listen for the paymentProcessed message.
When the message is received, the Shipping Service processes the shipping and sends a shippingConfirmed
message.
Example of a shippingConfirmed message:
{
"eventType": "ShippingConfirmed",
"orderId": "12345",
"shippingStatus": "shipped", “
shippingId": "5678"
}
Step 6: Customer Notified
Once all services have processed the necessary tasks, the Order Management Service or a separate Notification
Service can send a final confirmation message or notify the customer via email or SMS.
2.Hypermedia – driven
• Messages also contain descriptions of possible
actions (eg- links and forms)
• Responses in the Hypertext Application
Language format.
• Data and actions encoded in HTML format .
• Also control metadata.
• Hypermedia APIs are more like the human
Web:evolvable,adaptable,versoning free.
• Hypermedia-driven architecture (often
referred to as HATEOAS, or "Hypermedia As
The Engine Of Application State") is a design
approach where not only data is exchanged
between services but also descriptions of
possible actions (links, forms) that the client
can take.
• This allows for a more evolvable, adaptable,
and version-free API, similar to how the World
Wide Web works.
• Example: Hypermedia-Driven API for an E-
Commerce System
• Let’s assume we have a Product Management
Service that exposes information about
products available in the system.
• Instead of returning just product details, the
service will return a response that includes
not only the product data but also possible
actions the client can take.
1. Product Retrieval (GET /products/{productId})
When a client (such as a web or mobile app) requests information about a product, it doesn’t just get the product details—it gets a
hypermedia response that includes links to related resources and actions.
Request:
GET /products/123
Response (in Hypermedia format):
{
"productId": "123",
"name": "Smartphone",
"price": 499.99,
"description": "A high-quality smartphone with 64GB storage.",
"inventoryQuantity": 50,
"links": [
{ "rel": "self",
"href": "/products/123" },
{ "rel": "update",
"href": "/products/123/edit",
"method": "PUT" },
{ "rel": "delete",
"href": "/products/123",
"method": "DELETE" },
{ "rel": "buy",
"href": "/orders",
"method": "POST",
"description": "Place an order for this product" } ]
}
Data and Microservices
• In a microservices architecture, data management and how data is
handled across services play a crucial role in ensuring the system's
scalability, maintainability, and flexibility. Unlike traditional monolithic
systems, where a single database might handle all data, microservices often
require a distributed approach to data storage and management.
• In a microservices architecture, each service typically manages its own
data store, and this concept is often referred to as database per service.
This approach supports the key principles of microservices like
independence, loose coupling, and autonomy.
Key Concepts in Data Management for
Microservices
• Database per Service:
Each microservice is responsible for its own database. This ensures that services can
evolve independently without impacting each other.
For example, the Order Service may have its own relational database (e.g., MySQL),
while the Inventory Service may use a NoSQL database (e.g., MongoDB).
This isolation allows each service to choose the database that best fits its
requirements and ensures data ownership by the service.
• Data Consistency:
One of the biggest challenges in microservices is ensuring data consistency across
services.
Microservices commonly use eventual consistency, which is the idea that, although
the system may be temporarily inconsistent due to the independent nature of
services, it will eventually reach a consistent state.
Techniques like saga patterns and event sourcing are often used to handle
consistency across services.
• Distributed Transactions and Eventual Consistency:
In a monolithic system, you might rely on ACID (Atomicity,
Consistency, Isolation, Durability) transactions to
ensure data integrity. However, microservices often
need to handle distributed transactions.
For distributed systems, eventual consistency is more
appropriate, meaning changes in one service will
propagate to others but with a slight delay.
Event-driven architecture is often used to achieve
eventual consistency. For example, when an order is
placed, an event like OrderCreated is emitted and
consumed by other services (e.g., inventory, payment),
which update their respective data stores.
• CQRS (Command Query Responsibility Segregation):
CQRS is a pattern used in microservices where reads and writes are handled
by separate models. For example, the Order Service might have a model
that handles write operations (creating, updating orders) and a separate
model for reading order data (retrieving details about orders).
This separation can help improve performance, as you can optimize the read
model differently from the write model, and it can be useful in scenarios
where data is often queried in different ways.
• Data Duplication:
Microservices might end up with some data duplication. For instance,
multiple services might need some of the same data. For example, both
the Order Service and Shipping Service might need to know the
customer's name and address. Instead of querying a shared database,
each service can maintain its own copy of this data, and synchronization
can happen through events or APIs.
While data duplication might seem inefficient, it supports the autonomy of
each service and avoids tight coupling.
Example Scenario of Data in Microservices
Let’s consider a E-commerce System with multiple microservices and databases:
1. Order Service:
Database: MySQL or PostgreSQL
Data: Stores order details such as order ID, customer information, order items, order status,
payment status, etc.
Operations: Handles order creation, order updates, order cancellations.
2. Inventory Service:
Database: MongoDB or Cassandra (NoSQL)
Data: Stores product details such as product ID, stock quantity, price, etc.
Operations: Manages inventory, reduces stock when an order is placed, updates stock when
restocked.
3. Payment Service:
Database: Could use a lightweight relational database (e.g., SQLite for
simplicity) or NoSQL for transaction records.
Data: Stores payment transactions, payment status, payment method,
etc.
Operations: Handles payment processing, updates payment status, and
processes refunds.
4. Shipping Service:
Database: PostgreSQL or MongoDB
Data: Stores shipping details such as shipping ID, shipping status, tracking
number, delivery address.
Operations: Manages the shipping process, updates shipping status,
tracks deliveries.
Benefits of Data in Microservices
Autonomy and Scalability:
Each service is responsible for its own data, which means they are
autonomous and can scale independently.
Failure Isolation:
If one service’s database is down, other services continue to
operate independently without being impacted.
Optimized Data Stores:
Each service can choose the most suitable type of database for its
requirements. For example, the Order Service might need strong
ACID transactions, while the Inventory Service might prioritize
speed with a NoSQL database.
Flexibility:
Microservices can evolve independently, so if the Payment Service
needs to switch from SQL to NoSQL, it can do so without
affecting other services.
Challenges in Data Management for Microservices
Data Consistency:
Ensuring consistency across multiple services can be challenging, especially when
you need to maintain strong consistency (i.e., ACID properties) in a distributed
environment.
Distributed Transactions:
Handling distributed transactions across microservices can be complex. Patterns
like sagas (which break a large transaction into smaller compensatable steps)
are often used to manage this.
Data Duplication:
While each service has its own data store, data duplication can occur across
services, which requires mechanisms to keep data synchronized (e.g., event-
driven updates).
Querying Across Services:
Querying data that spans multiple services can be complex. In some cases, a API
Gateway or GraphQL layer is used to aggregate data from different services.
Distributed Transactions and Sagas