Services are borrowed from SOA:
- Business domain centric: boundaries and seams defined by owning team or domain.
- High cohesion: follows the single-responsibility principle (Private): inputs, outputs and operations relate only to a single domain; and masks implementation details via encapsulation (Private).
- Enforces backwards compatibility, maybe by wrapping newer versions of the interface with compatible implementations.
- Autonomy: owns its domain.
- Owns its own storage.
- Resilience: degrade gracefully to default functionality in the event of error rate, delay or unavailability.
- Use timeouts for queries with external services.
- Interactions with other services should be stateless.
- Observability: centralised storage and alarming on key metrics such as error rate and performance and logging.
- Correlation ID uniquely identifies individual requests, allowing for fan-out analysis.
- Structured logging with defined error levels.
- Annotate all log and metric entries with attributes aiding discovery, e.g. instance ID, date/time.
- Automation: testing and environment setup should take place in a continuous integration environment.
- Provide immediate feedback to developers.
- Regression tests should cover different API versions.
- Automate delivery.
Transactions in a microservices system are distributed: a request can be a composition of multiple services' responses. This makes asynchronous programming models essential.
There are two primary approaches to performing work:
- Synchronous request/response, where a client awaits a response from a request in the same exchange.
- Asynchronous request/response, where a client may request that the output is written to a specific network location (e.g. blob storage) or continues to poll for the status, or allows another system to collect and process the response via a message bus or queue.
Communication usually takes place over lightweight, open communication protocols such as gRPC or REST, avoiding tight coupling around a client library.
Transparent over multiple transports?
The hosting of these services is characterised by centralised management:
- Containers running an individual process are an ideal fit.
- Serverless/FaaS removes the management of the services entirely, allowing scaling based on transaction volume.
- API gateways are API-optimised load balancers, providing a central entry point for clients and performing routing and authentication/authorisation.
- Service discovery
The circuit breaker pattern defines a way of detecting the failure of a service and backing off to allow it to recover, providing a solution to the thundering herd problem.
Moving toward microservices
- Get black box testing in place.
- Ensure the system is adequately observable such that you're able to identify faults post-migration.
- Split the application into bounded contexts, duplicating shared components where required.
- Identify seams in data storage and separate these out, such that each service owns its own storage. This might necessitate building new services that solely manage the storage.
- Migrate individual services.