Micro-Services Design Patterns
The core concepts and related design patterns
The core concepts and related design patterns
Command and Query Responsibility Segregation.
As definition, query that any method return values, while command that any method mutates states.
Segregate operations that read data from operations from that update data by using separate interfaces/services, one interface for reading and another one for writing.
This pattern can maximize performance, scalability, and security; support evolution of the system over time through higher flexibility; and prevent update commands from causing merge conflicts at the domain level.
It keeps SRP, by different implementation for each functionality, that simplify implementation, and increase maintainability, and deployment effort.
In traditional architectures, the same data model is used to query and update a database. That's simple and works well for basic CRUD operations. In more complex applications, however, this approach can become unwieldy.
Benefits of CQRS include:
Independent scaling. CQRS allows the read and write workloads to scale independently, and may result in fewer lock contentions.
Optimized data schemas. The read side can use a schema that is optimized for queries, while the write side uses a schema that is optimized for updates.
Security. It's easier to ensure that only the right domain entities are performing writes on the data.
Separation of concerns. Segregating the read and write sides can result in more maintainable and flexible models. Most of the complex business logic goes into the write model. The read model can be relatively simple.
Simpler queries. The application can avoid complex joins when querying by storing a materialized view in the read database.
Use Case:
Imagine you have ecosystem, that has 1500 reports, which includes:
Daily consolidated reports
Monthly consolidated reports
Yearly consolidated reports
Search reports for with different intervals
What will be the impact in case of building these reports on the production(primary) database?!!!
Implementation Approach:
Segregate between the Operation database and reporting database
Operation database to be the primary one
Reporting and analytics database to be the DR one
Old Approach
New Approach in CQRS
Load data on demand into a cache from a data store. This can improve performance and also helps to maintain consistency between data held in the cache and data in the underlying data store.
Applications use a cache to improve repeated access to information held in a data store. However, it's impractical to expect that cached data will always be completely consistent with the data in the data store. Applications should implement a strategy that helps to ensure that the data in the cache is as up-to-date as possible, but can also detect and handle situations that arise when the data in the cache has become stale.
Many commercial caching systems provide read-through and write-through/write-behind operations. In these systems, an application retrieves data by referencing the cache. If the data isn't in the cache, it's retrieved from the data store and added to the cache. Any modifications to data held in the cache are automatically written back to the data store as well.
For caches that don't provide this functionality, it's the responsibility of the applications that use the cache to maintain the data.
In mobile application, when caching solution data at the client side, it increases the availability, when disconnected from the internet, by getting the local cached version.
An application can emulate the functionality of read-through caching by implementing the cache-aside strategy. This strategy loads data into the cache on demand. The figure illustrates using the Cache-Aside pattern to store data in the cache.
You should set and maintain caching strategy:
When to cache, which is not suitable for real-time update transactions.
Duration
When to Update
Where to cache
In addition, using the CDN can be considered one of the solutions for this issue, as mentioned in the opposite figure.
The below graphs show the comparison between different caching DBs.
Create indexes over the fields in data stores that are frequently referenced by query criteria. This pattern can improve query performance by allowing applications to more quickly retrieve data from a data store
I'm wondering from this pattern, as there are a lots of what we can do with out mentioning them as pattern.
If the data store doesn't support secondary indexes, you can emulate them manually by creating your own index tables. An index table organizes the data by a specified key. Three strategies are commonly used for structuring an index table, depending on the number of secondary indexes that are required and the nature of the queries that an application performs.
How Large enterprises Apply this Patterns?
Early Architecture for the database model
Running the index advisor to collect the recommended indexes
Apply the recommended advisors incrementally
Periodically revisit the model for improvement, Which guarantee the full scanning of the database by the advisor, the following diagram presents the scanning interval to improve the database performance using the advisor(Daily, Weekly, Bi-Weekly, Monthly):
Generate prepopulated views over the data in one or more data stores when the data is formatted in a way that does not favor the required query operations. This pattern can help to support efficient querying and data extraction, and improve application performance.
Can be generated using logical database objects, like views, functions and stored procedures.
A key point is that a materialized view and the data it contains is completely disposable because it can be entirely rebuilt from the source data stores. A materialized view is never updated directly by an application, and so it's a specialized cache.
When the source data for the view changes, the view must be updated to include the new information.
But the same issue of caching snapshots, When to update the materialized view?
Using the event driven approach, when modifying the object itself, can leads to efficient performance, so as an example, you can create triggers on the insert,delete and update to refresh the materialized view, in order to have the latest version of data.
Reports
Analytics
Periodic synchronizations
For Refreshing the materialized view:
exec dbms_mview.refresh(‘TEST_MV’);
During the internet connectivity, you may face a connectivity problem, that may kill your process, starting from the initial, mid, and final stages.
The existence of this process is to Handle faults that may take a variable amount of time to rectify when connecting to a remote service or resource. This pattern can improve the stability and resiliency of an application.
It can be related and implemented with the retry pattern as you have to limit your retry pattern to prevent locks and infinitive calls.
As you have a delay response on the web, specially in chained services, you have to apply this pattern.
As We see, during the journey passes throw 3 states, Opened, Have opened, and finally closed, according to the different trials.
So, All what you need:
How many trial
Intervals between trials
How to automate this pattern
Within Java spring framework, this pattern can be applied by adding tag @EnableCircuitBreaker tag.
Resilience4j is designed to do the following:
Stop cascading failures in a complex distributed system like RTA system.
Protect the system from failures of dependencies over the network.
Control the latency of the system.
Recover rapidly and fail faster to prevent cascading.
Fall back and gracefully degrade when possible.
Enable near-real-time monitoring, alerting, and operational control.
We have two point of views:
POV 01: Create one backend per user interface. Fine-tune the behavior and performance of each backend to best match the needs of the frontend environment, without worrying about affecting other frontend experiences.
It may add rework effort, and complicate enhancements.
Also, you will have a huge number of deployment services.
Also it affects negatively the team responsibility.
This pattern can raise the issue of reusability of the solution, Right, but the development team should consider that at the internal behavior level of the microservices controllers, and detailed reusable components, but in order to keep the maintainability as a non-functional requirement for the large scale products that should use the architecture model, you should implement this pattern, specially, when serving multiple front end types.
POV 02:
Different point of view, is creating API gateway for different channels, which may you control the security per each channel, and can differentiate between them, which is more realistic for implementation, and puts the organization on the first step to API first approach.
Decouple backend processing from a frontend host, where backend processing needs to be asynchronous, but the frontend still needs a clear response.
In most cases, APIs for a client application are designed to respond quickly, on the order of 100 ms or less.
One solution to this problem is to use HTTP polling. Polling is useful to client-side code, as it can be hard to provide call-back endpoints or use long running connections. Even when callbacks are possible, the extra libraries and services that are required can sometimes add too much extra complexity.
Someone may ask about the benefits of this pattern against its complexity, the answer can briefly mentioned in the following points:
Increase responsiveness of the solution, that meets user experience
Enable user to execute different features till receiving the result of the asynchronous services.
The core features to implement this features, are embedder as example in Java script async/await calls, enable multi threading by different languages, and TAP (Task asynchronous pattern), the most trending approach.
Service will be published in different environments, dev, test, staging, and production, also, connecting to test database, and also production environment.
So, Moving configuration information out of the application deployment package to a centralized location.
This pattern can provide opportunities for easier management and control of configuration data, and for sharing configuration data across applications and application instances
It's also strongly related to runtime reconfiguration pattern, that decrease the shutdown time for the solution, specially the sensitive business domains like banking and airlines.
Will be valid for running servers, databases, storage, and integration.
This issue can also touch the Runtime reconfiguration design pattern, and both complement each others.
The important hints here are:
You are not living alone, you are in the context of ecosystem, so you need to know what surrounding you, at the ecosystem level, and at the friend modules
You need to unify the configuration at modules level
You need single responsibility for the configuration areas
You need the facility to reconfigure the solution in the runtime
So, Considering this pattern will lead to abstracted model for the external configuration, the prevent code injection with information that can be changed in the future, due to business need or customer business line.
The opposite pyramid represents the required co-worker design patterns to implement external configuration design pattern:
Runtime reconfiguration: to prevent restarting the services.
Cache aside: to prevent revisiting the DM
What To configure:
As a rule, you can configure anything that subject for change, as example:
Design Theme
Module
Features
Standards
Screens
Views
Filters
Reports/analytics
Business rules
Equations
Encryption keys
Integration touch points
Database settings
Delegate authentication to an external identity provider. This pattern can simplify development, minimize the requirement for user administration, and improve the user experience of the application.
Organizations can select its idp(identity provider) according to installed solutions in order to have single sign on process.
This model is often called claims-based access control. Applications and services authorize access to features and functionality based on the claims contained in the token.
Sample of identity providers;
Google, Facebook. Apple, Microsoft and Amazon Web Services (AWS)
Identity management solutions, like Keycloak, which support social media authentication, 2-Way-Authentication, OTP, and groups management.
Single sign-on in the enterprise
Federated identity with multiple partners
Federated identity in SaaS applications
Considered as security pattern, which most of microservices require to secure their APIs using it, and may be the first pattern they start with.
It should be used mainly from API gateways, in order to unify security process.
Use a token or key that provides clients with restricted direct access to a specific resource or service in order to offload data transfer operations from the application code.
This pattern is particularly useful in applications that use cloud-hosted storage systems or queues, and can minimize cost and maximize scalability and performance.
Strong encryption techniques should take place in order to complicate the decryption algorithm if attacker try to decrypt it.
This can be done by authorization tokens, and operation attributes that require authorization in order to reduce cost, and have higher degree of the security
Dr. Ghoniem Lawaty
Tech Evangelist @TechHuB Egypt