Patterns And Techniques in Service-Oriented Architecture
This article provides an overview of some techniques and patterns for developing applications in service-oriented architecture, together with external resources for more detail explanations. This is not a comprehensive references for all available techniques and patterns, I selected only some patterns that I find useful and have already been applied or will be applied in my projects, I use this article as a quick reminder. For more comprehensive resources, please visit the further readings section.
Application modelling
Domain Driven Design
Summary:
Domain-Driven Design is an approach to software development that centers the development on programming a domain model that has a rich understanding of the processes and rules of a domain. [1]
Define services corresponding to Domain-Driven Design (DDD) subdomains. DDD refers to the application’s problem space — the business — as the domain. A domain is consists of multiple subdomains. Each subdomain corresponds to a different part of the business. [1]
Change data capture (CDC) is a set of software design patterns used to determine and track the data that has changed so that action can be taken using the changed data.CDC is an approach to data integration that is based on the identification, capture and delivery of the changes made to data sources.[1]
Implement each business transaction that spans multiple services is a saga. A saga is a sequence of local transactions. Each local transaction updates the database and publishes a message or event to trigger the next local transaction in the saga. If a local transaction fails because it violates a business rule then the saga executes a series of compensating transactions that undo the changes that were made by the preceding local transactions. [1]
How to reliably/atomically update the database and publish messages/events?
A service that uses a relational database inserts messages/events into an outbox table (e.g. MESSAGE) as part of the local transaction. A separate Message Relay process publishes the events inserted into database to a message broker. [1]
An API gateway is a service which is the entry point into the application from the outside world. It’s responsible for request routing, API composition, and other functions. [1]
Enable an application to handle transient failures when it tries to connect to a service or network resource, by transparently retrying a failed operation. This can improve the stability of the application.[1]
A circuit breaker acts as a proxy for operations that might fail. The proxy monitors the number of recent failures that have occurred, and use this information to decide whether to allow the operation to proceed, or simply return an exception immediately.[1]
Back pressure: When one component is struggling to keep-up, the system as a whole needs to respond in a sensible way. It is unacceptable for the component under stress to fail catastrophically or to drop messages in an uncontrolled fashion. Since it can’t cope and it can’t fail it should communicate the fact that it is under stress to upstream components and so get them to reduce the load. [1]
Throttling: 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 will enable the system to continue functioning and meet any service level agreements (SLAs) that are in place. [3]
Service mesh is a dedicated infrastructure layer for facilitating service-to-service communications between microservices, often using a sidecar proxy. [1]
Istio, Shopee’s SPEX and Hashicorp’s Consul are some implementation of service mesh.
Asynchronous communication patterns
Publish-Subscribe
Summary:
Publish–subscribe is a messaging pattern where senders of messages, called publishers, do not program the messages to be sent directly to specific receivers, called subscribers, but instead categorize published messages into classes without knowledge of which subscribers there may be. Similarly, subscribers express interest in one or more classes and only receive messages that are of interest, without knowledge of which publishers there are.
What will the messaging system do with a message it cannot deliver? When a messaging system determines that it cannot or should not deliver a message, it may elect to move the message to a Dead Letter Channel. [1]
Implement an idempotent consumer, which is a message consumer that can handle duplicate messages correctly. Some consumers are naturally idempotent. Others must track the messages that they have processed in order to detect and discard duplicates. [1]
Cloud native computing is an software development approach that utilizes cloud computing to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds [3]
Cloud-Native Principles [1]:
If a project is cloud native, it uses immutable infrastructure, declarative apis, and microservices.
If infrastructure is immutable, it is easily reproduced, consistent, disposable, will have a repeatable deployment process, and will not have configuration or artifacts that are modifiable in place.
If a project has an efficient and repeatable deployment process, its process is versioned, automated, and has low overhead/coarse grained packaging.
If a project’s deployment is automated, configuration, environment, and artifacts are completely managed by a pipeline.
If a projects deployment is managed completely by a pipeline, the project’s environment is protected.
If a project’s environment is protected, it provides observability of the project’s internal components.
If a project’s uses declarative APIs, its configuration is declarative.
If a project’s configuration is declarative, it designates what to do, not how to do it.
If a project exists as a microservice, it is not monolithic, it is resilient, it follows 12-factor principles, and is discoverable.
If a microservice is resilient, it is self-healing and distributed.
or feature switches) are a software development technique that turns certain functionality on and off during runtime, without deploying new code. This allows for better control and more experimentation over the full lifecycle of features. [2]
Move configuration information out of the application deployment package to a centralized location. This can provide opportunities for easier management and control of configuration data, and for sharing configuration data across applications and application instances.
Co-locate a cohesive set of tasks with the primary application, but place them inside their own process or container, providing a homogeneous interface for platform services across languages.
Implement health monitoring by sending requests to an endpoint on the application. The application should perform the necessary checks, and return an indication of its status.
, aka distributed tracing, provides insight into the full lifecycles, aka traces, of requests to the system, allowing you to pinpoint failures and performance issues.