Micro-Services Design Patterns
The core concepts and related design patterns
The core concepts and related design patterns
Architecture in the old approach was focused on :
Monolithic
Mono comes from the single point, which means: The solution is one peace in deployment architecture, so you build the solution, as one unit, even, with reusable components, but you deploy all of them at once, and the shutdown includes the software as all.
SOA: Service oriented architecture
Service means: a facility for public demand.
Service Oriented Architecture is a software architecture pattern, which application components provide services to other components via a communications protocol over a network. The communication can involve either simple data passing or it could involve two or more services coordinating connecting services to each other. Services (such as RESTful Web services) carry out some small functions, such as validating an order, activating account, or providing shopping cart services.
There are 2 main roles in SOA,
A. Service provider
B. Service consumer.
A software agent may play both roles. The Consumer Layer is the point where consumers (human users, other services or third parties) interact with the SOA and Provider Layer consists of all the services defined within the SOA. The following figure shows a quick view of an SOA architecture.
3. Micro-services
Micro means small piece of some thing, then when We talk about Micro-Services, We mean that We have a small pieces of programmable web components, with the following set of characteristics:
•There is a team of developers working on the application
•New team members must quickly become productive
•The application must be easy to understand and modify
•You want to practice continuous deployment of the application
•You must run multiple copies of the application on multiple machines in order to satisfy scalability and availability requirements
•You want to take advantage of emerging technologies (frameworks, programming languages, and more)
There are core differences between micro-services architecture and other models, which can be mentions as following:
Granularity
Means the size of the component/service, micro-services are the smallest coherent pieces of the source code, grouped around the domain, which named DDD: Domain driven development.
So, Micro-service can be considered as a web based component, designed according to DDD.
Database per service
One of the key aspects of micro-services is to have database per each service, which keep SRP for each, and also for the team that responsible for the micro-service.
By another meaning, There are a single responsibility for database communication, which can be formed in schemas, or separate database, according to:
Data Model objects, like orders micro-services
Data models growth rate, like huge logging tables
Independence
Each service is self-managed, independent from others, so it can manage its relationship with others using EDA, which implemented by message queues like KAFKA, RabbitMQ, and Azure message queue.
Different Technology stacks
You can implement micro-services using different technology stack, which facilitate team utilization and reduce the resource availability risk.
Container based
As you may implement services using different technology stacks, and each technology stack has different operating systems, different components, so the container based architecture take the place to enable easier deployment, without the complications of the operating systems requirements, so it keep the development team focus on the production only.
There are main concepts in the containerization:
Cluster
Group of servers work together to provide high availability for specific solution
Includes all solutions and management tools that support the deployment and monitoring the solution
Node: The smallest computing HW element, which will be the actual configuration that host the environment, with CPU and memory configuration.
POD: High level structure that run containers, which running containers will share the same resources.
It includes the following:
The main container for the application
The sidecar container, that carry the responsibilities for collecting measurements and analytics about the main application container
Container: The deployable artifact/component
SCALING: Increase/decrease number of replicas (PODS) to handle the incoming requests according to traffic
Business domain centric/Single concern: With application of DDD, as We will mention in the next secti
SRP: Design for Single Responsibility : Each micro-service has its own responsibility, with no intersection with other one, which increases modularity, and single point of development and maintenance
API-First Design
API versioning
Use Asynchronous Communication When Possible
9. Use an API Gateway
10. Configuration Management
High cohesion/low coupling
Remove internal dependencies
communicate through event driven architecture
Service Autonomy
Totally standalone component without depending on another micro-service, which decrease the coupling, and increase cohesion
Totally standalone component without depending on another data model
DB Per Service: So micro-service architect can do the following:
Select database type (relational/non-relational)
Database provider: SQL/Oracle
Observability
Side-Car pattern
Logs classification model, which defines what to be logged from business and technical perspective.
Logging tool: like using EFK and ELK
So, database is totally independent, and no one can access it without accessing the micro-service.
In migrating monolithic application to micro-services architecture, teams are familiar with the database structure, and try to reduce the effort by direct access to the database objects, and conduct CRUD operations using triggers, or procedures.
This is violation to the micro-services architecture, as it will block the sunset process for the monolithic solution, in addition, killing the traceability for the micro-services architecture.
Deplorability
Event-Driven
In order to architect for modular solution, and for sure, micro-services architecture, the core issue is how can you slice your micro-services?
Here what We answer:
It's DDD: Domain drive design.
So what is domain?
Domain is a discrete part of the system that will help to solve specific problems, So you should have the following:
Contexts: The high level parts of the solutions, which may be:
Domain generic Model: which Serve the generic infrastructure for the model, from both business and technical perspective
Domain specific model
Business components: which is the target deliverables of the solution, like User management, Sales, and Purchasing modules
Technical components as enabler for the solution: which support the overall solution functionality, from technical perspective, like logging, reporting, and locking.
Entities and value : The definition of the expected entity objects, and the related values objects
Aggregator: Which aggregate entities, and values to treat specific unit, it can be used by the aggregator design patterns
Services: The related business and operations, which will be encapsulated in, Services, modules, or detailed controllers and implementation classes
Repositories: the storage, which you should apply database per service approach, and it can be:
Database level: Which moving forward to containerization approach of the database, to have separate, deployable component from the ecosystem, totally independent to enable standalone deployment
Schema level: Which schema is complex, that require different tables, and views to handle the business
Table level: Which represents the simplest data-model form
Events: What to publish, and what to subscribe, which should be applied using publisher subscriber design pattern
Anti-Corruption layer: to ensure that contexts are collaborated and interact without negative impacts or inconsistencies
Micro-services architecture is a key pillar of the MACH architecture, which consists of:
Micro-services: Which is the evolution of monolithic application as we mentioned, components around the domains, with single responsibilities, independent deployable packages.
API first: which you think about your API as a product, All functionalities are exposed, and can serve multiple channels, and considering integration with different stakeholders, so it's about complete coverage of your system, so everything you do from the interface, you can do it through the APIs, so, it's greater than the Micro-services architecture, it's business strategy.
Cloud native: let the cloud manage your infrastructure and deployment components and resources
Headless architecture: FE consumer is totally decoupled from the Backend, to have more engagement moving forward to Omni-channels.
Due to the software development journey, a lot of source code issues and problems have arise, with huge effort from developers over the world to take action to solve it.
Due to the best practices of solutions, it moved to be a well known solution for the problems, within specific context, which take the name of : Design pattern.
With the evolution of Cloud architecture, a parallel evolution take place for the cloud, and named with Cloud design patterns.
These design patterns are useful for building reliable, scalable, secure applications in the cloud.
Each pattern describes the problem that the pattern addresses, considerations for applying the pattern, and an example based on Microsoft Azure. Most patterns include code samples or snippets that show how to implement the pattern on Azure. However, most patterns are relevant to any distributed system, whether hosted on Azure or other cloud platforms.
Availability Patterns
Data Management Patterns
Design and Implementation Patterns
Management and Monitoring Patterns
Messaging Patterns
Security Patterns
Performance and Scalability Patterns
Resilience patterns
What Is Mecchano?
Mecchano model represents the collaboration between set of micro-services design patterns to achieve specific non-functional requirements, which you are looking to achieve within your solution, so, no one-only design pattern will be used, but collaborative work will take place.
Well-known Mechannos
It's custom combination according to the non-functional requirements that you need to achieve.
Configuration: to have NFR solution configurability, and extendibility, which you will need to implement:
External configuration design pattern
Realtime reconfiguration design pattern
Cache aside design pattern
Publisher subscriber design pattern
Performance: In order to have NFR fast responding solution, which requires implementing the following design patterns:
Cache aside
Materialized views
Indexed table
Resiliency, in order to achieve NFR for consistency, and stability of the solution
Security in order to have secured solution
Maturity Index Calculation
Accordingly, We can calculate the overall maturity index of the micro-service design pattern according to how it impacts the overall behavior, and then group them to different groups according to its rank in the maturity.
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
Strategic pattern, that guide the Smooth migration for existing system in order to cope with new trends and technologies.
It grows over the time, by start smooth migration process by replacing existing functionality with new application and services.
Which imply moving from monolithic, to microservices and migrating the existing business logic to the service layer.
Incrementally, the team should follow the following strategy:
Replace specific pieces of functionality with new applications and services
Create a façade that intercepts requests going to the backend legacy system. The façade routes these requests either to the legacy application or the new services. Existing features can be migrated to the new system gradually, and consumers can continue using the same interface, unaware that any migration has taken place.
Migrating business logic to the database programmable objects in order to prevent the redevelopment process
Procedures
Functions
No replacement of the existing database assets
Here, We can talk about the art of migration, how should we migrate, and when, which leads us to consider it as strategy pattern for moving forward from monolithic to microservices architecture.
So Here, We will call the following concepts:
SRP
DDD
Refactoring
Pitfalls:
One of major pitfalls while you are using strangler design pattern to migrate to micro-services architrcture :
It’s increasing the dependencies on the remaining unmigrated part of the monolithic solution, while you were able to put the same effort to apply it in the new architecture .
Enable an application to handle temporary failures when connecting to a service or network resource by transparently retrying the operation in the expectation that the failure is transient. This pattern can improve the stability of the application.
This can be occurred by saving failed TXN and event bus to be run when service available.
Also you should consider how many trials you are planning for, and apply circuit breaker pattern as needed.
You have different strategies:
Retry
Cancel
Retry after delay, with predefined trials count
Simplicity of the implementation differs from language to another, simply most of languages implement the pattern by looping the number of trials with internal sleep/wait in order to let the server release its resources.
In order to succeed finalizing the cycle of the closure, you should keep the status of the process/transaction, to enable another design pattern to work, like the scheduled job design pattern.
The opposite graph show the four pillar resiliency and stability patterns, that should be applied to improve the solution stability:
Retry Pattern: the existing pattern, how may times to try the same behavior, to prevent failure
Circuit Breaker: how to stop the unlimited trials for the same requests/jobs to prevent stuck and locks.
Scheduler Job: to compensate failed, and incomplete transactions due to the retry failure and circuit breaker
Leader election: at infrastructure level
Collaboration between all mentioned design patterns should take place, specially at the large scale solution.
Design an application so that it can be reconfigured without requiring redeployment or restarting the application. This helps to maintain availability and minimize downtime.
You can force reloading the configuration by all fron ends if you are caching the configuration, by using also the versioning of api calls.
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.
Enable an application to announce events to multiple interested consumers asynchronously, without coupling the senders to the receivers.
This pattern is the evolution of EDA from the desktop and web applications, but the events here due to external components, and frameworks, like KAFKA and AMQ.
In cloud-based and distributed applications, components of the system often need to provide information to other components as events happen.
Asynchronous messaging is an effective way to:
decouple senders from consumers
Avoid blocking the sender to wait for a response
However, using a dedicated message queue for each consumer does not effectively scale to many consumers. Also, some of the consumers might be interested in only a subset of the information.
So: How can the sender announce events to all interested consumers without knowing their identities?
Introducing asynchronous messaging subsystem that includes the following:
An input messaging channel used by the sender. The sender packages events into messages, using a known message format, and sends these messages via the input channel.
Sender is publisher, consumer is subscriber.
A mechanism for copying each message from the input channel to the output channels for all subscribers interested in that message.
The benefits of this pattern in to remove the complexity from the solution, and the coupling between components, in order to resolve the hexagonal architecture pattern.
So it's too important to the team to understand what's behind the pattern in order to utilize it.
Apache Kafka is a distributed streaming platform that enables users to publish and subscribe to streams of records, store streams of records, and process them as they occur. Kafka is most notably used for building real-time streaming data pipelines and applications and is run as a cluster on one or more servers that can span more than one data center. The Kafka cluster stores streams of records in categories called topics, and each record consists of a key, a value, and a timestamp.
Key characteristics of Apache Kafka include:
Event-based data flows as a foundation for (near) real-time and batch processing.
Scalable central nervous system for events between any number of sources and sinks. Central does not mean one or two big boxes in the middle but a scalable, distributed infrastructure, built by design for zero downtime, handling the failure of nodes and networks and rolling upgrades.
Integrability of any kind of application and system since technology does not matter. This enables you to connect anything: programming language, APIs like REST, open standards, proprietary tools and legacy applications.
Distributed storage because there is a lot of complexity behind this and a streaming platform simply has it built-in. This allows you to store the state of a microservice instead of requiring a separate database, for example.
An Enterprise Service Bus (ESB) is an architecture that includes a set of rules and principles for integrating applications together. These are specific integration tooling type. However, vendors who offer these solutions vary greatly in their offerings. The core concept of an ESB is that they enable application integration by putting a communication “bus” between them that lets the applications talk to the bus. This process provides a way for the tool to decouple systems from one another so they can communicate “freely.”
Apache Kafka and its ecosystem is designed as a distributed architecture with many smart features built-in to allow high throughput, high scalability, fault tolerance, and failover. Enterprise can integrate Kafka with ESB and ETL tools if they need specific features for specific legacy integration. An ESB or ETL process can be a source or sink to Apache Kafka like any other Kafka producer or consumer API. Currently, most of the top-rated integration tools also have a Kafka connector because the market drives them this way.
In Azure, We have Azure Service Bus, as enterprise service bus, fully managed enterprise message broker with message queues and publish-subscribe topics (in a namespace). Service Bus is used to decouple applications and services from each other, providing the following benefits:
Load-balancing work across competing workers
Safely routing and transferring data and control across service and application boundaries
Coordinating transactional work that requires a high-degree of reliability
Azure Service Bus is the same as Kafka, Which has the following features:
Messaging. Transfer business data, such as sales or purchase orders, journals, or inventory movements.
Decouple applications. Improve reliability and scalability of applications and services. Producer and consumer don't have to be online or readily available at the same time. The load is leveled such that traffic spikes don't overtax a service.
Load balancing. Allow for multiple competing consumers to read from a queue at the same time, each safely obtaining exclusive ownership to specific messages.
Topics and subscriptions. Enable 1:n relationships between publishers and subscribers, allowing subscribers to select particular messages from a published message stream.
Transactions
Message sessions
EDA Using KAFKA and Competing consumers design pattern
Enable multiple concurrent consumers to process messages received on the same messaging channel.
With multiple concurrent consumers, a system can process multiple messages concurrently to optimize throughput, to improve reliability, scalability and availability, and to balance the workload.
This can be achieved using message bus, like RabbitMQ and Azure message queue.
So: You are using Kafka to buffer the Queue by the producers, not to directly call, and the consumed by the consumers, to take from the buffer according dot FIFO approach.
Related pattern is the priority queue to prioritize the messages.
EDA Using KAFKA and Queue-based Load Leveling
Use a queue that acts as a buffer between a task and a service that it invokes in order to smooth intermittent heavy loads that may otherwise cause the service to fail or the task to timeout. This pattern can help to minimize the impact of peaks in demand on availability and responsiveness for both the task and the service.
That occurs when you have a lot of hits over the data storage, some nodes will receive timeout message.
Many solutions in the cloud involve running tasks that invoke services. In this environment, if a service is subjected to intermittent heavy loads, it can cause performance or reliability issues.
Refactor the solution and introduce a queue between the task and the service. The task and the service run asynchronously. The task posts a message containing the data required by the service to a queue. The queue acts as a buffer, storing the message until it's retrieved by the service.
This pattern provides the following benefits:
It can help to maximize availability because delays arising in services won't have an immediate and direct impact on the application, which can continue to post messages to the queue even when the service isn't available or isn't currently processing messages.
It can help to maximize scalability because both the number of queues and the number of services can be varied to meet demand.
It can help to control costs because the number of service instances deployed only have to be adequate to meet average load rather than the peak load.
Some services implement throttling when demand reaches a threshold beyond which the system could fail. Throttling can reduce the functionality available. You can implement load leveling with these services to ensure that this threshold isn't reached.
Use a gateway to aggregate multiple individual requests into a single request. This pattern is useful when a client must make multiple calls to different backend systems to perform an operation.
In the following diagram, the client sends requests to each service (1,2,3). Each service processes the request and sends the response back to the application (4,5,6). Over a cellular network with typically high latency, using individual requests in this manner is inefficient and could result in broken connectivity or incomplete requests.
In diagram 2, In the following diagram, the application sends a request to the gateway (1). The request contains a package of additional requests. The gateway decomposes these and processes each request by sending it to the relevant service (2). Each service returns a response to the gateway (3). The gateway combines the responses from each service and sends the response to the application (4). The application makes a single request and receives only a single response from the gateway.
The aggregator design pattern is a service that receives a request, subsequently makes requests of multiple services, combines the results and responds to the initiating request.
It's different than the api gateway, as api gateway is the first interface to service layer, and considered as business wrapper to the service layer.
Different strategies for communicating micro-services together
Route requests to multiple services or multiple service instances using a single endpoint. The pattern is useful when you want to:
Expose multiple services on a single endpoint and route to the appropriate service based on the request
Expose multiple instances of the same service on a single endpoint for load balancing or availability purposes
Expose differing versions of the same service on a single endpoint and route traffic across the different versions
It can be:
Multiple disparate services , Multiple instances of the same service and Multiple versions of the same service.
The solution is:
Place a gateway in front of a set of applications, services, or deployments. Use application Layer 7 routing to route the request to the appropriate instances.
With this pattern, the client application only needs to know about a single endpoint and communicate with a single endpoint. The following illustrate how the Gateway Routing pattern addresses the three scenarios outlined in the context and problem section.
Top three API Gateways
An API gateway's primary role is to connect API consumers and providers
The most commonly known API gateways that facilitate communication, traffic, security, versioning, and caching at the micro-service architecture level Are:
Spring Cloud
Kong
APISIX
Having API Gateway product is key pillar of your micro-service architecture solution, as it used for:
Routing: Create core routes for the deployed application
Upstream: virtual mapping to the actual nodes, that contains the solution, which will be used by the load balancer, Upstream consists of multiple nodes, that load balancer will use
Caching
Security
Versioning
Observability and monitoring
Implementation of different Deployment strategies, like Blue-Green, and canary
Types of Gateways
We have 2 types of gateways:
API Gateway: API Connect, Kong, and APISIX
Which take the responsibility for exposing the APIs to the public users
Micro-gateway: APIGee,WS02, Kong, Spring Cloud
Which take the responsibility for defining the routing between micro-services at the cluster level, that leads to the following benefits:
Reduce total solution cost of ownership
Reduce latency of traffic, for close-proximity[near to each others] services
Enhance security, and Reduce attack surface
SAGA is an English term, means, telling the story in depth, which We can translate that in Distributed transactions, you should have an orchestrator that know everything about your journey.
The Saga design pattern is a way to manage data consistency across microservices in distributed transaction scenarios. A saga is a sequence of transactions that updates each service and publishes a message or event to trigger the next transaction step. If a step fails, the saga executes compensating transactions that counteract the preceding transactions.
A transaction is a single unit of logic or work, sometimes made up of multiple operations. Within a transaction, an event is a state change that occurs to an entity, and a command encapsulates all information needed to perform an action or trigger a later event.
So, Involvement of message broker (Kafka or RabbitMQ) should take place.
Transactions must be atomic, consistent, isolated, and durable (ACID). Transactions within a single service are ACID, but cross-service data consistency requires a cross-service transaction management strategy.
In multiservice architectures:
Atomicity is an indivisible and irreducible set of operations that must all occur or none occur.
Consistency means the transaction brings the data only from one valid state to another valid state.
Isolation guarantees that concurrent transactions produce the same data state that sequentially executed transactions would have produced.
Durability ensures that committed transactions remain committed even in case of system failure or power outage.
You should have the following:
Orchestrator Micro-services, has the status of each micro-service
Orchestrator should have status about both:
The journey status
The status feedback, which can be saved locally in the orchestrator
How to compensate and rollback according to each response
How to retry, and conduct scheduler job
The benefit of SAGA:
Complexity centralization, instead of connected services
Architect for recoverability, instead of optimistic design, that all distributed transactions will succeed without issues
Transaction compensation, instead of data inconsistency issue
SAGA Types:
Choreography: Using message broker to send messages across all services, which increase the complexity of the communication between micro-services. and destroy the responsibility of each service .
the services choreograph the workflow among themselves without depending on an orchestrator or having direct communication between them.
When to use: Used in independent microservices, that can listen to external events, with full enabling to rollback status and data model changes.
Orchestrator : Which encapsulation of the responsibilities take place by one micro-service, that has well-understanding about the workflow, and distribute communication correctly according to business workflow, It's more suitable for large scale solutions, that your have clear understanding and responsibilities about both the business and micro-services responsibilities
When to use: Used when you have journey manager(orchestrator) that encapsulate the flow, and status of the behavior, which meet the largescale business scope, and support different micros-services design patterns.
It's important design pattern that enable you to undo the transaction, as all your micro-services layers should be enabled for compensation since it's distributed components, and no communication between them, so applying SOLID principles is hard to implement without such design patterns.
Undo the work performed by a series of steps, which together define an eventually consistent operation, if one or more of the operations fails. Operations that follow the eventual consistency model are commonly found in cloud-hosted applications that implement complex business processes and workflows.
It's fully connected with the following patterns:
Aggregation pattern, as the management service should take the lead for cancelling the effect to keep atomic transaction.
Saga Pattern
Design for atomic process should be considered as you have different model (distributed databases/services) on the web.
In order to make that, Existence of local storage, like KAFKA should take place, which make your persistence data models independent from updates till the finish of the model.
Example:
You have stock module
You have receiving transaction
you accumulate results in one figure, the total quantity.
you cant cancel the last transaction, or anyone else, since you do not have different between the total amount and the individual transacation.
The opposite graph show the four pillar resiliency and stability patterns, that should be applied to improve the solution stability:
Retry Pattern: the existing pattern, how may times to try the same behavior, to prevent failure
Circuit Breaker: how to stop the unlimited trials for the same requests/jobs to prevent stuck and locks.
Scheduler Job: to compensate failed, and incomplete transactions due to the retry failure and circuit breaker
Leader election: at infrastructure level
Collaboration between all mentioned design patterns should take place, specially at the large scale solution.
Anti corruption layer =
Integration Mapping Layer for Non/Near semantics
The Context of this design pattern is migration between two different solutions, which should be un-touchable till the end of the migration.
Isolate the different subsystems by placing an anti-corruption layer between them.
This layer translates communications between the two systems, allowing one system to remain unchanged while the other can avoid compromising its design and technological approach.
Implementation Approaches:
Timestamp or version columns: This is a CDC method best suited for data tables. Whenever a table row is updated, the timestamp or version column is automatically updated, too. CDC processes will periodically query data tables and pin-point changes by comparing versions or timestamps.
Change tracking in database engines: Some contemporary database systems have built-in change-tracking mechanisms. One such example is Microsoft SQL Server. The feature records changes to tables, a suitable method for tracking and capturing changes within the database engine.
Automation tools for Realtime Based: Which is connected to EDA approach, as We mentioned in publisher subscriber design pattern, like Debezium
Drawbacks
It may have negative impact by the following:
Latency
Additional development overheads
Wrong implementation, by mixing ignoring the application of Strangler Pattern, and go forward for reforming the solution.
Approaches:
Sunset: You will close the old solution, while you are migrating
Co-Working: Solution will be running together, which can require tool like debezium.
Context : Shared functionality and reusability
Offload shared or specialized service functionality to a gateway proxy.
This pattern can simplify application development by moving shared service functionality, such as the use of SSL certificates, from other parts of the application into the gateway.
Some features are commonly used across multiple services, and these features require configuration, management, and maintenance. A shared or specialized service that is distributed with every application deployment increases the administrative overhead and increases the likelihood of deployment error. Any updates to a shared feature must be deployed across all services that share that feature.
Solution
Offload some features into a gateway, particularly cross-cutting concerns such as certificate management, authentication, SSL termination, monitoring, protocol translation, or throttling.
The following diagram shows a gateway that terminates inbound SSL connections. It requests data on behalf of the original requestor from any HTTP server upstream of the gateway.
Benefits of this pattern include:
Simplify the development of services by removing the need to distribute and maintain supporting resources, such as web server certificates and configuration for secure websites. Simpler configuration results in easier management and scalability and makes service upgrades simpler.
Allow dedicated teams to implement features that require specialized expertise, such as security. This allows your core team to focus on the application functionality, leaving these specialized but cross-cutting concerns to the relevant experts.
Provide some consistency for request and response logging and monitoring. Even if a service is not correctly instrumented, the gateway can be configured to ensure a minimum level of monitoring and logging.
Protect applications and services by using a dedicated host instance that acts as a broker between clients and the application or service, validates and sanitizes requests, and passes requests and data between them. This can provide an additional layer of security, and limit the attack surface of the system.
Applications expose their functionality to clients by accepting and processing requests. In cloud-hosted scenarios, applications expose endpoints clients connect to, and typically include the code to handle the requests from clients. This code performs authentication and validation, some or all request processing, and is likely to accesses storage and other services on behalf of the client.
To minimize the risk of clients gaining access to sensitive information and services, decouple hosts or tasks that expose public endpoints from the code that processes requests and accesses storage. You can achieve this by using a façade or a dedicated task that interacts with clients and then hands off the request—perhaps through a decoupled interface—to the hosts or tasks that'll handle the request. The figure provides a high-level overview of this pattern.
The gatekeeper pattern can be used to simply protect storage, or it can be used as a more comprehensive façade to protect all of the functions of the application. The important factors are:
Controlled validation. The gatekeeper validates all requests, and rejects those that don't meet validation requirements.
Limited risk and exposure. The gatekeeper doesn't have access to the credentials or keys used by the trusted host to access storage and services. If the gatekeeper is compromised, the attacker doesn't get access to these credentials or keys.
Appropriate security. The gatekeeper runs in a limited privilege mode, while the rest of the application runs in the full trust mode required to access storage and services. If the gatekeeper is compromised, it can't directly access the application services or data.
This pattern is helpful for applications that:
handle sensitive information
expose services that require a high degree of protection from malicious attacks
perform mission-critical operations that can't be disrupted.
require request validation be performed separately from the main tasks, or to centralize this validation to simplify maintenance and administration
Implement functional checks within an application that external tools can access through exposed endpoints at regular intervals. This pattern can help to verify that applications and services are performing correctly.
It's a good practice, and often a business requirement, to monitor web applications and back-end services, to ensure they're available and performing correctly. However, it's sometimes more difficult to monitor services running in the cloud than it is to monitor on-premises services. For example, you don't have full control of the hosting environment, and the services typically depend on other services provided by platform vendors and others.
It can be against specific key metrics to check the solution service behavior over time.
Implement health monitoring by sending requests to an endpoint on your application. The application should perform the necessary checks and then return an indication of its status.
A health monitoring check typically combines two factors:
The checks (if any) that the application or service performs in response to the request to the health verification endpoint
The analysis of the results by the tool or framework that performs the health verification check
Typical checks that monitoring tools perform include:
Validating the response code. For example, an HTTP response of 200 (OK) indicates that the application responded without error. The monitoring system might also check for other response codes to give more comprehensive results.
Check integration provider healthy state: like SMS gateways, payment gateways, Governmental gateways, and business related gateways.
Checking the content of the response to detect errors, even when the status code is 200 (OK). By checking the content, you can detect errors that affect only a section of the returned web page or service response. For example, you might check the title of a page or look for a specific phrase that indicates that the app returned the correct page.
Access to database and its performance
Checking resources or services that are located outside the application. An example is a content delivery network that the application uses to deliver content from global caches.
Checking for the expiration of TLS certificates.
Measuring the response time
Validating the URL that a DNS lookup returns.
Sample Healthy checker Matrix
We have a core issue since We moved to database per each service, Who makes the change?!!!
Instead of storing just the current state of the data in a domain, use an append-only store to record the full series of actions taken on that data.
Use an append-only store to record the full series of events that describe actions taken on data in a domain, rather than storing just the current state, so that the store can be used to materialize the domain objects. This pattern can simplify tasks in complex domains by avoiding the requirement to synchronize the data model and the business domain; improve performance, scalability, and responsiveness; provide consistency for transactional data; and maintain full audit trails and history that may enable compensating actions.
The CRUD approach has some limitations:
CRUD systems perform update operations directly against a data store, which can slow down performance and responsiveness, and limit scalability, due to the processing overhead it requires.
In a collaborative domain with many concurrent users, data update conflicts are more likely because the update operations take place on a single item of data.
Unless there's an additional auditing mechanism that records the details of each operation in a separate log, history is lost.
The Event Sourcing pattern defines an approach to handling operations on data that's driven by a sequence of events, each of which is recorded in an append-only store. Application code sends a series of events that imperatively describe each action that has occurred on the data to the event store, where they're persisted. Each event represents a set of changes to the data (such as AddedItemToOrder).
Benefits
So, You can use the events, to materialize the views, without updating the data, and with the final process, you can update your data models.
Also improve the performance, and consistency, and enable replaying the events again.
A good solution to this problem is to use event sourcing. Event sourcing persists the state of a business entity such an Order or a Customer as a sequence of state-changing events. Whenever the state of a business entity changes, a new event is appended to the list of events. Since saving an event is a single operation, it is inherently atomic. The application reconstructs an entity’s current state by replaying the events.
Applications persist events in an event store, a database of events. The store has an API for adding and retrieving an entity’s events. The event store also behaves like a message broker. It provides an API that enables services to subscribe to events. When a service saves an event in the event store, it is delivered to all interested subscribers.
Finally:
Event Sourcing focuses on data persistence and providing a complete, auditable history of changes by storing events
Coordinate a set of actions across a distributed set of services and other remote resources, attempt to transparently handle faults if any of these actions fail, or undo the effects of the work performed if the system cannot recover from a fault.
This pattern can add resiliency to a distributed system by enabling it to recover and retry actions that fail due to transient exceptions, long-lasting faults, and process failures.
Sample of the Scheduler reviews:
Transactions to be compensated: since you make rules for the delay, and you customer can't wait, also if some transaction s failed, and you need to discover it.
Data governance: Some times, your data model violate the data governance rules, like zero and null values, invalid references, wrong data entry.
Periodic checks upon absence of EDA, mainly with third parties integration, so you should incrementally check the expected responses to complete scenarios, and update data models.
How Can We implement it:
Application schedule
DB Jobs
The opposite graph show the four pillar resiliency and stability patterns, that should be applied to improve the solution stability:
Retry Pattern: the existing pattern, how may times to try the same behavior, to prevent failure
Circuit Breaker: how to stop the unlimited trials for the same requests/jobs to prevent stuck and locks.
Scheduler Job: to compensate failed, and incomplete transactions due to the retry failure and circuit breaker
Leader election: at infrastructure level
Collaboration between all mentioned design patterns should take place, specially at the large scale solution.
Prioritize requests sent to services so that requests with a higher priority are received and processed more quickly than those of a lower priority. This pattern is useful in applications that offer different service level guarantees to individual types of client.
Applications can delegate specific tasks to other services, for example, to perform background processing or to integrate with other applications or services. In the cloud, a message queue is typically used to delegate tasks to background processing. In many cases, the order requests are received in by a service isn't important. In some cases, though, it's necessary to prioritize specific requests. These requests should be processed earlier than lower priority requests that were sent previously by the application.
A queue is usually a first-in, first-out (FIFO) structure, and consumers typically receive messages in the same order that they were posted to the queue. However, some message queues support priority messaging. The application posting a message can assign a priority and the messages in the queue are automatically reordered so that those with a higher priority will be received before those with a lower priority. The figure illustrates a queue with priority messaging.
As We see in the opposite image, the solution is to use the reorder pattern, to move according to the priority, so till having the higher priority, We should pay attention till end, Which is applicable is cases like the OTP authentication, which should be expired as example 2 minutes, or in money transfer, like 30 Seconds.
How to implement it?
In systems that don't support priority-based message queues, an alternative solution is to maintain a separate queue for each priority. The application is responsible for posting messages to the appropriate queue. Each queue can have a separate pool of consumers. Higher priority queues can have a larger pool of consumers that run on faster hardware than lower priority queues.
You can do the following;
Create application jobs (Platinum jobs) with lower intervals and large data sets for executing high priority tasks, as example (each 10 seconds, take 1000 records)
Create application jobs (Gold jobs) with lower intervals and large data sets for executing high priority tasks, as example (each 30 seconds, take 30 records)
Create application jobs (Silver jobs) with lower intervals and large data sets for executing high priority tasks, as example (each 50 seconds, take 20 records)
Another approach with Kafka
Create multiple partitions for Kafka topic according to topic importance
Partitions enable parallelism, which enable multiple agents for increasing the performance
Using a priority-queuing mechanism can provide the following advantages:
It allows applications to meet business requirements that require the prioritization of availability or performance, such as offering different levels of service to different groups of customers.
It can help to minimize operational costs. If you use the single-queue approach, you can scale back the number of consumers if you need to. High priority messages are still processed first (although possibly more slowly), and lower priority messages might be delayed for longer. If you implement the multiple message queue approach with separate pools of consumers for each queue, you can reduce the pool of consumers for lower priority queues. You can even suspend processing for some very low priority queues by stopping all the consumers that listen for messages on those queues.
The multiple message queue approach can help maximize application performance and scalability by partitioning messages based on processing requirements. For example, you can prioritize critical tasks so that they're handled by receivers that run immediately, and less important background tasks can be handled by receivers that are scheduled to run at times that are less busy.
This pattern is useful in scenarios where:
The system must handle multiple tasks that have different priorities, like OTP, and VIP customers
Different users or tenants should be served with different priorities.
Decompose a task that performs complex processing into a series of discrete elements that can be reused, and parallel processing.
This pattern can improve performance, scalability, and reusability by allowing task elements that perform the processing to be deployed and scaled independently.
A key advantage of the pipeline structure is that it provides opportunities for running parallel instances of slow filters, which enables the system to spread the load and improve throughput.
The filters that make up a pipeline can run on different machines, which enables them to be scaled independently and take advantage of the elasticity that many cloud environments provide. A filter that's computationally intensive can run on high-performance hardware, while other less-demanding filters can be hosted on less-expensive commodity hardware.
It can support the parallel process and resiliency, that decrease the shutdowns and increase availability.
It may produce level of complexity, So if it's simple to decompose your logic with need for that, go for this pattern.
Using the Pipes and Filters pattern together with the Compensating Transaction pattern is an alternative approach to implementing distributed transactions. You can break a distributed transaction into separate, compensable tasks, each of which can be implemented via a filter that also implements the Compensating Transaction pattern. You can implement the filters in a pipeline as separate hosted tasks that run close to the data that they maintain.
The Bulkhead pattern is a type of application design that is tolerant of failure. In a bulkhead architecture, elements of an application are isolated into pools so that if one fails, the others will continue to function. It's named after the sectioned partitions (bulkheads) of a ship's hull. If the hull of a ship is compromised, only the damaged section fills with water, which prevents the ship from sinking.
Partition service instances into different groups, based on consumer load and availability requirements. This design helps to isolate failures, and allows you to sustain service functionality for some consumers, even during a failure.
The benefits of this pattern include:
Isolates consumers and services from cascading failures. An issue affecting a consumer or service can be isolated within its own bulkhead, preventing the entire solution from failing.
Allows you to preserve some functionality in the event of a service failure. Other services and features of the application will continue to work.
Allows you to deploy services that offer a different quality of service for consuming applications. A high-priority consumer pool can be configured to use high-priority services.
Service Discovery pattern, is one of the most well-known largescale patterns, as in large scale solution, you have the following issues:
How can you deploy multiple nodes of the components to support huge requests from the end-user
How can You dynamically distribute the load across the mentioned nodes
How can you keep the highest performance for the solution
In order to achieve that in large scale solution, We have the following approaches:
Dedicated nodes, which is costly and cant not maintainable
Dynamic nodes, which is scalable, and enable the growth of the solution
So, in this model, We have to have the following:
Service Client: Which request the service to perform operation
Load Balancer: Which take the responsibility for routing the incoming traffic for each service, and you have one single point of communication, and take care, to which instance it should direct the request
Service registry: Within large scale solution, Load balancer should know the new IPs that come to the context, Service registry take place, and keep the actual details, like real address, to direct the call for it, with facility to cache the existing instances for the future use.
When service comes up, it register itself in the service registry
Scaling: Each scaling, new IPs comes to the service registry, store the mapping between services and its corresponding instances.
New Deployment: With new deployment, all instances released, and new one come to the service registry
Tools:
The most commonly used tools is:
ZooKeeper
Netflex Eureka
ETCD kubernetes
Benefits of the pattern:
Application of leader election design pattern: Which takes the most active and reliable service in the first fail of the service, Since it's stateless transaction, so each call is totally independent than the other one, so the same instance can serve multiple clients.
Higher availability, during the leader election, and service registry
Simplicity of architecture, which prevent thinking about the physical nodes, and growth
Scalability of the solution, which meets the largescale solutions
Select leader, if died, elect another one.
Coordinate the actions performed by a collection of collaborating task instances in a distributed application by electing one instance as the leader that assumes responsibility for managing the other instances.
This pattern can help to ensure that tasks do not conflict with each other, cause contention for shared resources, or inadvertently interfere with the work that other task instances are performing.
There are several strategies for electing a leader among a set of tasks in a distributed environment, including:
Selecting the task instance with the lowest-ranked instance or process ID.
Racing to acquire a shared, distributed mutex. The first task instance that acquires the mutex is the leader. However, the system must ensure that, if the leader terminates or becomes disconnected from the rest of the system, the mutex is released to allow another task instance to become the leader.
Implementing one of the common leader election algorithms such as the Bully Algorithm or the Ring Algorithm. These algorithms assume that each candidate in the election has a unique ID, and that it can communicate with the other candidates reliably.
The opposite graph show the four pillar resiliency and stability patterns, that should be applied to improve the solution stability:
Retry Pattern: the existing pattern, how may times to try the same behavior, to prevent failure
Circuit Breaker: how to stop the unlimited trials for the same requests/jobs to prevent stuck and locks.
Scheduler Job: to compensate failed, and incomplete transactions due to the retry failure and circuit breaker
Leader election: at infrastructure level
Collaboration between all mentioned design patterns should take place, specially at the large scale solution.
The deployment stamp pattern involves provisioning, managing, and monitoring a heterogeneous group of resources to host and operate multiple workloads or tenants. Each individual copy is called a stamp, or sometimes a service unit, scale unit, or cell. In a multi-tenant environment, every stamp or scale unit can serve a predefined number of tenants. Multiple stamps can be deployed to scale the solution almost linearly and serve an increasing number of tenants. This approach can improve the scalability of your solution, allow you to deploy instances across multiple regions, and separate your customer data.
Deployment stamps can apply whether your solution uses infrastructure as a service (IaaS) or platform as a service (PaaS) components, or a mixture of both. Typically IaaS workloads require more intervention to scale, so the pattern might be useful for IaaS-heavy workloads to allow for scaling out.
Because of the complexity that is involved in deploying identical copies of the same components, good DevOps practices are critical to ensure success when implementing this pattern. Consider describing your infrastructure as code, such as by using Bicep, JSON Azure Resource Manager templates (ARM templates), Terraform, and scripts. With this approach, you can ensure that the deployment of each stamp is predictable and repeatable. It also reduces the likelihood of human errors such as accidental mismatches in configuration between stamps.
Geodes
Deploy the service into a number of satellite deployments spread around the globe, each of which is called a geode. The geode pattern harnesses key features of Azure to route traffic via the shortest path to a nearby geode, which improves latency and performance. Each geode is behind a global load balancer, and uses a geo-replicated read-write service like Azure Cosmos DB to host the data plane, ensuring cross-geode data consistency. Data replication services ensure that data stores are identical across geodes, so all requests can be served from all geodes.
It's very important for goverenmental and IOT products, as it have a large volume of data that should be distributed.
Divide a data store into a set of horizontal partitions shards.
Each shard contains unique rows of information that you can store separately across multiple computers, called nodes. All shards run on separate nodes but share the original database's schema or design.
This pattern can improve scalability when storing and accessing large volumes of data, Like country citizens as an example.
It can be distributed by areas, that will enhance the performance, keep security, but will take effort in analytical models and solution upgrades.
It's a special type of distributed database, as alit manage different content, not different objects.
Sharding types
Hashing, Third Image
Directory Sharding Lookup, First Image
Geo-Sharding
Range, Second Image
You should consider the following:
1. Consolidation model
2. Analytical model
3. Sync intervals
Alternatives
Partitioning
Replication
Vertical scaling
Throttler: In the simplest form of API throttling, the throttler would be part of the API server, and it would monitor the number of API requests per second and minute, per user, or per IP address based on user authentication.
Rate limitter : is the practice of limiting the number of requests that can be made to an API within a specific time period.
Control the consumption of resources used by an instance of an application, an individual tenant, or an entire service. This pattern can allow the system to continue to function and meet service level agreements, even when an increase in demand places an extreme load on resources.
As a guide, do not provide information without request or exceeding the customer/user needs.
Processing consumes the traffic and become expensive than on prime model.
There're many strategies available for handling varying load in the cloud, depending on the business goals for the application. One strategy is to use auto-scaling to match the provisioned resources to the user needs at any given time. This has the potential to consistently meet user demand, while optimizing running costs. However, while auto-scaling can trigger the provisioning of additional resources, this provisioning isn't immediate. If demand grows quickly, there can be a window of time where there's a resource deficit.
An alternative strategy to auto-scaling is to allow applications to use resources only up to a limit, and then throttle them when this limit is reached. The system should monitor how it's using resources so that, when usage exceeds the threshold, it can throttle requests from one or more users.
This pattern should be used for governance, and known by infrastructure team, as it affects the operational cost, which is shared between the teams.
So, the advantages of this pattern is It controlling the cost, and enable preventing DDoS.
If your solution does not require throttling, do not enable it.
Preventing overloading of servers: Helps prevent overloading of servers by controlling the rate at which requests are received. By restricting the number of requests made within a certain time frame, you can maintain the stability and responsiveness of your servers.
Protecting against malicious attacks: Protects against malicious attacks, such as denial of service (DoS) attacks, which are intended to flood servers with excessive requests. By limiting the rate at which requests can be made, you can prevent these types of attacks from causing damage.
Managing resources and costs: Manages resources and costs by controlling the usage of APIs. By limiting the number of requests that can be made, you can use your resources in the most efficient way and avoid incurring unnecessary costs associated with excessive API usage.
Plan for extending your functionality beside your core one.
Deploy components of an application into a separate process or container to provide isolation and encapsulation. This pattern can also enable applications to be composed of heterogeneous components and technologies.
This pattern is named Sidecar because it resembles a sidecar attached to a motorcycle. In the pattern, the sidecar is attached to a parent application and provides supporting features for the application. The sidecar also shares the same lifecycle as the parent application, being created and retired alongside the parent.
Example:
Infrastructure API. The infrastructure development team creates a service that's deployed alongside each application, instead of a language-specific client library to access the infrastructure. The service is loaded as a sidecar and provides a common layer for infrastructure services, including logging, environment data, configuration store, discovery, health checks, and watchdog services.
Applications and services often require related functionality, such as monitoring, logging, configuration, and networking services. These peripheral tasks can be implemented as separate components or services.
If they are tightly integrated into the application, they can run in the same process as the application, making efficient use of shared resources. However, this also means they are not well isolated, and an outage in one of these components can affect other components or the entire application. Also, they usually need to be implemented using the same language as the parent application. As a result, the component and the application have close interdependence on each other.
A sidecar service is not necessarily part of the application, but is connected to it. It goes wherever the parent application goes. Sidecars are supporting processes or services that are deployed with the primary application.
The sidecar pattern is often used with containers and referred to as a sidecar container or sidekick container.
One of the primary benefit of the microservices model is its ability to organize development teams according to bounded contexts(sub-models).
In monolithic applications, shared database creates high coupling between different domains, if data is wrongly architected, it will be highly coupled, which may require downtime to upgrade.
While Monolithic application depends on on ACID principles to resolve consistency issues, Microservice approach is 'Share-Nothing', which may lead to de-normalized data models that cause duplicates and inconsistency.
There are different approaches, can be briefly determined as following:
Database Per service: Service can't access data models out of its domain responsibility
Pros: Loosely couples
Cons: Not straight forward, complicated.
Shared Database:
Pros: Straight forward, ability to apply ACID principles
Cons: Coupling between data models, And We can reply that EDA can be applied at the level of database, and decrease coupling using it
API composition: Check for data from the other services
Pros: Direct
Cons: Additional complexity, Service coupling, which is against microservice architecture.
CQRS & Event Sourcing: Which you should notify the object changes due to the CQRS pattern, and to notify listeners by changes using the EDA, which guarantee 'At least once' publishing to interested services
Pros: Support the integrity by adding database objects (Views, Functions, Procedures) with defined responsibility.
Cons: Complexity, and code duplicates
SAGA: relies on orchestrating a sequence of local transactions, which is responsible for building local transaction, but the revert of the transactions in case of failure will.
Pros: Good for services provided by different solution providers
Cons: Higher complexity
Database type: relational/non-relational
Since micro-services architecture is moving to largescale solutions, you should move forward to:
Normalized database: in order to meet relationships and enable reporting
DE-normalized database, as normalized database impact the database performance dramatically, due to the referential integrity constraints.
Non-relational database: Which enable scalability for the database, vertically and horizontally, without performance overheads.
Finally,
As and Architect, you are responsible about data integrity, using different technology domains(Microservice/relational databases/non-relational databases/AI, etc.), that direct you according to each context (Single/Multiple service provider) to implement the best fit approach to achieve the objective with minimal overheads and drawbacks.
This may be challenging, to have fully independent domains, that destroy relationships between objects, accept inconsistencies, require building healthy checkers to resolve it.
In addition, you should think about evaluating any approach, not only micro-service architecture, rather than applying everything, without considering the architecture trade-offs like:
Complexity
Consistency
Fit for purpose
Fit for use
API-First architecture/strategy is a new architecture approach the shift the mindset of software development for APIs to consider APIs as independent product, with different journeys in order to achieve business objectives, independent from the UX journey that may mislead/abuse the API architecture and design.
You can just imagine it if you just think that your final product is the APIs, and you will commercialize it alone, without UX.
It means, you have API as a product, so you have:
Product manager
Product Roadmap
And product lifecycle
As example, the target persona of your product (APIs) which will use your product, like mobile app, web app, and smart watch.
So, We are starting with the API, not the application.
So, It's about "How will you image the solution upon the API first, not how to build the API upon the design first solution".
Async Development
Reduce development Cost
Reduce time-to-market
Same like software engineering lifecycle, passing all journeys as the opposite diagram mentioning, considering that your product is APIs, including its documentation and journeys.
As a pre-requisites, We should build the culture, which implies the following principles:
Your API is a product
Foundational design, not ad hoc retrofit
Team collaboration and impact
API-first supports microservices
The API contract
Step 1: Create API Micro-Service architecture according to DDD, which each object has the following attributes:
Who Am I? I'm Employee Class
What I know? I know my code, name, birthdate, current salary, current role.
What I do? I can Create employee, delete employee, activate employee, deactivate employee, update employee, and block employee, and unblock employee
What is my state? like : blocked employee, Unblocked employee, Active employee and Inactive employee
Step 2: Determine key domains
Step 3: Determine each domain lifecycle
Step 4: Model your architecture domains
Step 5: Model your architecture sequence diagrams
Step 6: Model your architecture state-chart diagrams
Step 7: Develop CRUD APIs according to data models characteristics
Step 8: Create the journeys according to the sequence diagrams, with validating the pre-requisites
Step 9: Create APIs to validate product states
Step 10: Create Security model for the APIs
API Improvement model
As opposite figure, We have consumer and publisher, They are shared improvement the model, due to the required improvement after design, as a nature of any product lifecycle.
Are You API First Company?
Since you have the following characteristics, you can consider yourself as API-First company:
You have APIs that operate and maintain your data models
You are providing you APIs as independent product
You make APIs available to your customers and partners as a source of your revenue stream
You know how to Manage and discover your APIs
You have standardized processes to build APIs
Your APIs is independent from any UX design
API versioning is a practice in software development that involves managing and maintaining different versions of an Application Programming Interface (API). An API is a set of rules and protocols that allows one software application to interact with and request services or data from another software component, such as a web service or library. API versioning is essential to ensure that changes and updates to an API do not break existing clients or applications that rely on it.
Compatibility
Preventing Breaking Changes
Client Isolation
Sunset enabling
Business security and governance (Like creating API banking platform using API first, and then building the application upon)
So, it meet the strategic approach for the enterprise driven from the business transformation team, according to the following perspectives:
How will you build your upcoming products
How will your external entities integrate with your product
How will you monetize your product
How APIs will cover your overall business
Different deployment
Headers
URI routing
Level 01:
•Change management
•Control integration touch points by TL
•Announcement model for integration touch points
•Application of unit testing for integration packages
Level 02:
Apply selected Versioning streategy
Enable support period for version minors (Quarter based)
Backward compatibility according to release management process, which may be monthly, or quarterly or yearly
Quarterly code refactoring process to reset minor version backward compatibility
Handle legacy Versions : Responses and stoppage
Level 03:
Application of design patterns: Aggregation pattern, to reduce the touch service, which gives the team the freedom for changing the signature
API Catalog Model completeness, which should be deployed on API management platform, like SWAGGER, including its documentation
Level 04:
Provisioning model for monetization: which will be used in the access of authentication and authorization, for the given tokens, to enable who can do what, and enrich monetization of your exposed APIs
Dr. Ghoniem Lawaty
Tech Evangelist @TechHuB Egypt