Chapter 24 - Spring Microservices Flashcards
Monolithic architecture
Negative features:
1) One major problem is that the application is overwhelmingly complex. It’s simply too large for any single developer to fully understand. As a result, fixing bugs and implementing new features correctly becomes difficult and time consuming. What’s more, this tends to be a downwards spiral. If the codebase is difficult to understand, then changes won’t be made correctly. You will end up with a monstrous, incomprehensible big ball of mud.
2) The larger the application, the longer the start‑up time is. If developers regularly have to restart the application server, then a large part of their day will be spent waiting around and their productivity will suffer.
3) Imagine you have a large e-commerce website like Amazon. If you need to update a small feature, such as changing the way product recommendations are displayed, you have to redeploy the entire website, not just the recommendation feature.
4) Monolithic applications can also be difficult to scale when different modules have conflicting resource requirements. For example, one module might implement CPU‑intensive image processing logic and would ideally be deployed in Amazon EC2 Compute Optimized instances. Another module might be an in‑memory database and best suited for EC2 Memory‑optimized instances. However, because these modules are deployed together you have to compromise on the choice of hardware.
5) all modules are running within the same process, a bug in any module, such as a memory leak, can potentially bring down the entire process
6) can’t rewrite whole code to another framework
Solution of the Monotholic problems is?
Solutions with Microservices
Microservices architecture addresses these issues by breaking down the application into smaller, independent services. Each service can be developed, deployed, and scaled independently. For example, in the e-commerce scenario, the recommendation feature could be a separate microservice. Updating it wouldn’t require redeploying the entire website, leading to quicker and safer deployments.
Each service instance is a Docker container
Rather than sharing a single database schema with other services, each service has its own database schema.
Difference between microservices and SOA (Service oriented Architecture)
Key Differences
Service Granularity:
SOA: Larger, coarse-grained services.
Microservices: Smaller, fine-grained services.
Communication:
SOA: Often relies on ESBs with protocols like SOAP.
Microservices: Uses lightweight protocols like HTTP/REST and messaging queues.
Governance:
SOA: Centralized governance and management.
Microservices: Decentralized governance, with each service independently managed.
State Management:
SOA: Can be stateful.
Microservices: Preferably stateless, with external state management.
Deployment and Scaling:
SOA: Services are typically part of a larger, monolithic deployment unit, making independent scaling challenging.
Microservices: Each service can be independently deployed and scaled.
Flexibility and Agility:
SOA: Less flexible due to centralized control and larger service granularity.
Microservices: Highly flexible and agile, enabling continuous deployment and faster iterations.
Technology Stack:
SOA: More standardized, often using traditional enterprise technologies.
Microservices: Polyglot, allowing the use of diverse technologies best suited for each service.
Microservices Synchronous Communication
In synchronous communication, a predefined source service address required, where exactly to send the request, and BOTH the service (caller and callee) should be up and running at the moment. Though Protocol may be synchronous, I/O operation can be asynchronous where the client need not necessarily wait for the response. This is a difference in I/O and Protocol. The common request-response approach common to web API includes REST, GraphQL, and gRPC.
Microservices ASynchronous Communication
In the case of asynchronous communication, callers need not have the specific destination of the callee. Handling multiple consumers at a time becomes relatively easy (as services may add up consumers). Moreover, the message queues up if the receiving service is down & proceeds later when they are up. This is particularly important from the perspective of loose coupling, multi-service communication, and coping up with partial server failure. These are determining factors for inclining microservices towards Async communication. Asynchronous protocols like MQTT, STOMP, AMQP are handled by platforms like Apache Kafka Stream, RabbitMQ.
Message
A message is like a little package of information that is sent to a specific place. It contains instructions on what needs to be done. These messages are sent through different ways, like texting or emailing.
Imagine a message as a letter you send to a friend. The mailbox is like the queue where the letter waits until your friend reads it. Once your friend gets the letter, they react to it by responding or taking action.
In a message-driven system, it’s like having friends waiting for your letters. They stay ready to act when they receive your message, otherwise they wait quietly.
event
Sure, here’s a simpler explanation:
An event is like a notification that something has happened. For example, when you order something online, an event might be created to show that the order was requested.
Listeners are like people who are waiting to hear about these events. They are connected to the sources of events and will be notified when an event occurs.
There are two main types of events:
1. Domain Events: These are events related to the business domain, like when an order is requested or when a credit is reserved. These events are important for understanding the history of what has happened in the business.
2. Change Events: These are events generated by the database to indicate a change in state, like when data is updated or deleted. These events are useful for tracking changes in the database.
example:
Interaction:
The OrderPlaced event triggers the InventoryUpdate event indirectly, as the inventory needs to be updated based on the items ordered.
The InventoryUpdate event does not directly interact with the OrderPlaced event, but it is a consequence of the order being placed.
Event streamers are tools that help manage these events. They store events in a durable and persistent way, ensuring that they are not lost even if something goes wrong. The processor, in this case, is like a traffic cop, simply routing the events to where they need to go. This approach avoids the need for complex integration platforms and allows the client or services to handle the logic related to these events.
Event streamers are durable, persistent, fault-tolerant without any idea of the consumers. In such a case the processor is dumb (in a sense it acts as a message router only) and the client/services own the domain-centric logic making dump processor and active clients. This avoids complex integration platforms such as ESB used in traditional SOA design.
smart endpoints and dumb pipes VS dump processor and active clients
Smart Endpoints and Dumb Pipes
Smart Endpoints: In this context, “smart” endpoints refer to the microservices themselves. They are responsible for implementing the business logic and processing data.
Dumb Pipes: “Dumb” pipes refer to the communication channels between microservices. This means that the communication mechanisms (such as HTTP, messaging queues, etc.) should be simple and straightforward, without adding complexity or logic to them.
Implication: The idea is that instead of relying on complex middleware or communication frameworks to handle interactions between microservices, the focus should be on making the microservices themselves intelligent and capable of handling their own logic.
Dump Processor and Active Clients
Dump Processor: This seems like a typo and likely should be “Dumb Processor.” In this context, a “dumb processor” refers to a component that simply routes messages or events from one place to another without any understanding of the message’s content. It acts as a simple mediator, similar to a network switch that forwards packets without interpreting them.
Active Clients: “Active clients” refer to the microservices or clients that consume the events or messages. These clients are responsible for interpreting the messages and taking appropriate actions based on them.
Implication: This approach offloads the complexity of message processing from the middleware or processor to the clients, allowing for more flexibility and decentralization in the system architecture.
finally both are same!!!!!!
Saga Pattern — Maintaining Atomicity Across Multiple Services
Distributed Transaction Scenario
Example: Consider an e-commerce application where placing an order involves checking the customer’s credit limit, verifying the item’s availability in inventory, and then processing the payment.
Challenge: A single ACID transaction is not feasible across these services due to their distributed nature.
Saga
Definition: A saga is a sequence of local transactions that together form a larger, coordinated transaction spanning multiple services.
Purpose: Ensures that the overall transaction is completed successfully or rolled back atomically.
Failure Handling: If any local transaction fails, a series of compensating transactions is executed to undo the changes made by preceding transactions, ensuring consistency.
Choreography-Based Saga
Concept: Participants (services) in the saga exchange events to coordinate their local transactions without a centralized controller.
Advantages: Decentralized control leads to simpler implementation and better scalability.
Example: After the order service places an order, it publishes an “OrderPlaced” event. The inventory service listens for this event and reserves the item. If successful, it publishes an “ItemReserved” event, which the payment service then listens for to process the payment.
Orchestration-Based Saga
Concept: A centralized controller (orchestrator) coordinates the saga by telling the participants what local transactions to execute.
Advantages: Provides a clear, centralized view of the saga’s progress and allows for easier monitoring and management.
Example: The orchestrator first tells the order service to place an order. Once done, it instructs the inventory service to reserve the item, and finally, it tells the payment service to process the payment.
Summary
Saga: A way to manage distributed transactions across multiple services.
Choreography-Based Saga: Participants communicate directly to coordinate transactions without a central controller.
Orchestration-Based Saga: A central controller coordinates the saga by instructing participants on what transactions to execute.
A saga is a sequence of local transactions that updates each service and publishes a message/event to trigger the next local transaction. In case of failure of any of the local transactions, saga executes series of compensating transactions that undo changes made by preceding local transactions thereby preserving atomicity.
Two-Phase Commit
Two-Phase Commit Protocol
Prepare Phase: All participants (services) in the transaction are asked to prepare to commit the transaction. This involves ensuring that they can commit the transaction if instructed to do so.
Commit Phase: If all participants are able to prepare successfully, a commit message is sent to all participants, instructing them to commit the transaction. If any participant fails to prepare, a rollback message is sent to all participants, instructing them to abort the transaction.
Limitations in Microservices Architecture
Synchronous Nature: The two-phase commit protocol is synchronous, meaning that all participants must be available and respond in a timely manner. In a microservices architecture with potentially many services, this can lead to performance issues and increased latency.
Retry Strategies
Retry – The source application can immediately retry to send the request to the service. If the specific fault is unusual or rare, the probability of success when repeating the request is very high.
Retry after a delay – The source application can retry to send the request to the cloud service after a period of time (that normally increases exponentially). This is a common practice when the failure event is due to reasons such as cloud service busy and so on.If the fault is caused by one of the more commonplace connectivity or busy failures, then the application has to wait for some time and try again.
Sliding Retry – The source application will retry repeatedly based on schedule and keeps adding an incremental delay in subsequent tries. For example, the retry logic may multiply the waiting period of 60 seconds by increment a try count from 1 to the number of tries and so on. This helps in reducing the overall number of retries.
Retry With Jitter – The sliding retry and the exponential backoff retry add a predictable sequence in their retry and backoff timelines. If there are multiple calls to the service at the same time and the same retry policy is applied then all the calls will retry and back off at the same time. To prevent this we need to add a certain randomness to the retry logic. This can be one by introducing a jitter to the retry policy. The jitter is a random calculation that results in different retry and backoff timelines for various calls.
Cancel – The source application can cancel the request to the cloud service and throw an exception. This is a common practice when the failure is not transient or is likely to be unsuccessful if repeated.
Retry Pattern
The retry pattern is an extremely important pattern to make applications and services more resilient to transient failures. A transient failure is a common type of failure in a cloud-based distributed architecture. This is often due to the nature of the network itself (loss of connectivity, timeout on requests, and so on). Transient faults occur when services are hosted separately and communicate over the wire, most likely over a HTTP protocol. These faults are expected to be short-lived. Repeating such a request that has previously failed could succeed on a subsequent attempt.
Testing Microservices:
Microservices are developed with business-oriented APIs to encapsulate core business capabilities, using the principle of loose coupling to minimize dependencies.
Testing microservices is different from testing monolithic systems due to the distributed nature of microservices architecture.
Why Test Microservices?:
Testing ensures peace of mind and helps avoid problems caused by a domino effect in a distributed environment with many moving parts.
In a system with multiple teams deploying multiple times a day, proper testing helps prevent side effects and substantial rollbacks.
The Testing Pyramid:
Describes different types of tests and their coverage in a pyramid structure, with unit tests at the bottom (cheap, fast, and easy to develop) and end-to-end tests at the top (expensive, slow, and complex).