By Book - Steve Jobs - Design is not something what looks or feels like, rather it's about how it works. While designing Microservice, one should focus on its inner and outer architecture. Inner defines how you design a microservice/how it works within itself and Outer talks about how it communicates with other microservices.
This post attempts to discuss Microservices Design Principles & Data Design.
Design Principles:
At it's core key elements in microservices design are -Time to Production (Automation), Scalability (Layered, Separation of Concerns, High Cohesion, Loose Coupling), Resiliency (Timely Timeouts, Retry, Circuit Breaker, Load Shedding, Fallback etc) and Complexity Localization.
- High Cohesion & Loose Coupling
- Resilience
- Observability
- Automation
- These two concepts are related of a system design. A highly cohesive system is naturally loosely coupled.
- High Cohesive system means, group all related or interdependent functionalities together, into one system.
- A microservice architecture must be highly cohesive and loosely coupled.
- Timely Timeouts -
- In Microservices, anything over network is fallible, hence we should not wait indefinitely expecting response. I.e. Do not indefinitely wait to timeout, if no response, else it will degrade system.
- Prefer to have timeouts, neither long nor short
- Well if timeout is reached, should you return error - No, rather we should log and return some meaningful meaning message .
- Circuit Breakers -
- Stop making requests after a certain threshold of failures reached.
- It's like electric system, where if flow increases then it drops the connection to avoid further damage.
- I.e. If our microservice keeps timing out at a given endpoint then there is no point in keep trying, rather break the system in a nicely fashion.
- Bulkheads -
- Bulkheads are used in ships to build water compartments, so that if one is full, passengers can use other.
- Same Bulkhead patterns is applied here - Do not have single thread pool for all outbound endpoints, rather plan one thread per endpoint.
- Steady State -
- This pattern adhere to designs which allows your system to run in a steady state for long time. Could be thru Automated Deployments, Clearing Log Files to avoid growing them indefinitely, Clear Cache before growing them enormous etc.
- Fail Fast -
- Make decision early to fail, if you know the request is going to fail/rejected. E.g. If load balance knows there is a failed node, then there is no point in sending request again and again.
- Even Circuit Breakers can also be used to implement Fail Fast Strategy.
- Let it Crash -
- It's like doctor decide to cut the leg, before it damages entire body.
- This strategy believes to abandon a broken sub-system, to preserve the overall stability of the system. E.g. Remove Failed Node, Remove Failed Endpoint etc
- Load Shedding -
- Application should shed load when the system starts performing behind SLA.
- Load shedding drops some proportion of load by dropping traffic as the server approaches overload conditions.
- Load shedding can be like Reduce Queue size, Introduce Caching, Notifying Load Balance that application is not ready to accept more requests etc.
- Fallback -
- Sometimes a request is going to fail no matter how many times you retry. The Fallback policy lets you return some default or perform an action - Like paging an admin, scaling a system or restarting a service.
- Keep track of throughput of each microservice
- Number of Success/Failed requests
- Utilization of CPU, Memory etc.
- Business related metrics (e.g SLA)
Microservices Data Design:
At it's core, microservices are built as Autonomous Entities and should have control over the data layer that they operate on. This essentially means that microservices cannot depend on a data layer that is owned by another entity. Thus to build autonomous services, it's required to have an Isolated Persistent Layer for Each microservice separately.
This section attempts to show patterns/practices for transforming centralized or
shared database-based enterprise applications to microservices that are based
on decentralized databases.
In Monolithic applications, usually has single centralized database (or a few) is shared among multiple applications and services. Below shows an example of online retail application - Where all services of the retail system share a centralized database (Retail DB).
In centralized database it's quite trivial to model a complex transactional scenario that involves multiple tables. Most RDBMS support such capabilities out of the box.
Despite such advantages, it has serious drawbacks, which does not allow to build autonomous and independent microservices.
- Single point of failure,
- Potential performance bottleneck due to heavy application traffic directed into a single database,
- Tight dependencies between applications, as they share same database tables.
- Easily modify the database schema without worrying about the external consumers of the database.
- No external applications can access the database directly.
- Allows freedom to select the technology to be used as the persistent layer of microservices.
- Different microservices can use different persistent store technologies, such as RDBMS, NoSQL or other cloud services.
- Sharing data between microservices and
- Implementing Foreign Key Concept
- Sharing Static Data
- Data Composition
- Transactions - Major Problem!
- Identify the shared table and identify the business capability of data stored in that shared table.
- Move the shared tabled to a New dedicated database and on top of that database,
- Create a New service (with business capability) identified in the previous step.
- Remove all direct database references and only allow them to access the data via the services
- Using Synchronous Lookups
- Using Asynchronous Events
- Shared Static Data
1). Using Synchronous Lookups: Used to access the data owned by other services. This technique is quite trivial to understand and at the implementation level, you need to write extra logic to do an external service call. We need to keep in mind that, unlike databases, we no longer have the referential integrity of a foreign key constraint. This means that the service developers have to take care of the consistency of the data that they put into the table. For example, when you create an order you need make sure (possibly by calling the product service) that the products that are referred from that order actually exist in the Product Table.
2).Using Synchronous Lookups - TBD
C). Shared Data: This is like Country, State etc data. Two approaches:
- Add another microservices with the static data would solve this problem, but it is overkill to have a service just to get some static information that does not change over time.
- Hence, sharing static data is often done with shared libraries for example, if a given service wants to use the static metadata, it has to import the shared library into the service code.
D). Data Composition: Composing data from multiple tables/entities and creating different views is a very common requirement in data management. With monolithic databases (RDBMS), it's very easily to build the composition of multiple tables using joins and SQL statements.
However, in the microservices per DB approach, building data compositions becomes very complex, as we no longer can use the built in constructs such as joins to compose data as data is distributed among multiple databases owned by different services.
Therefore, with a microservices architecture we need to think better solution:
- Composite Services Or
- Client Side Mash-Ups
Hope this helps.
No comments:
Post a Comment