Architecture Guidelines
This guide defines a list of architecture patterns and guidelines to apply when designing microservices. When implementing an Event-driven microservices architecture. there is one big rule of thumb:
Always prefer Coreography over Orchestration
This means that is always preferred to "communicate" with other services (bounded contexts) via asynchronous messaging (events) over synchronous commands (APIs).
This pattern allows the services to be loosely coupled, but highly cohesive.
Software Architecture
Micro-services
Each microservice is relatively small: 1) Easier for a developer to understand 2) The IDE is faster making developers more productive 3) The application starts faster, which makes developers more productive, and speeds up deployments
Tactical Architecture: Your weapon of choice
It IS RECOMMENDED to adhere to Hexagonal, Onion or Clean Architecture and/or Tactical DDD Patterns when you are implementing complex business needs.
Bounded-context specific architecture
If a low business complexity Microservice is being developed, a simple MVC architecture style IS RECOMMENDED
Each microservice MUST have an architecture pattern based on the needs of the specific business bounded context it implements.
Database per Service
Database per-service
Using a database per service has the following benefits: 1) Helps ensure that the services are loosely coupled. 2) Each service can use the type of database that is best suited to its needs.
Ownership
Each Microservice MUST have its own database(s).
The database (and the selected engine) MUST be selected, owned, managed, monitored and controlled by the development team.
Shared Infrastructure
In certain cases a common database server could be used by different tenants (usually to reduce costs). Each tenant MUST manage their own database(s) schema(s) and monitor the specific performance of their database(s)
Engines
The database used SHOULD be a Relational (SQL) or Non-relational (NoSQL).
It is RECOMMENDED to use open-source/cloud database engines as:
- mySQL
- PostgreSQL
- Cloud SQL
- Elasticsearch
- MongoDB
- Neo4Js
- Cloud Firestore
Database Access
The only way to access a Microservice Data MUST be through its own API.
A Microservice MUST NOT share its database under any circumstances. This is considered an Anti-Pattern and brings coupling issues.
RESTful APIs
Design
Every Microservice MUST implement a JSON RESTful API.
You SHOULD avoid creating an API that simply mirrors the internal structure of the service database.
It is RECOMMENDED to adhere to an API-First or API Design First Approach.
It is RECOMMENDED to organize the API around resources.
It is RECOMMENDED to follow the Microsoft API Best Practices Guidelines as reference:
OpenAPI standard
Each Microservice MUST implement an API Contract.
The REST API contract MUST adhere to OpenAPI 3.0 conventions.
You MUST declare an OpenAPI 3.0 manifest.
General Conventions
You MUST use HTTP Response Codes correctly.
You SHOULD implement HATEOAS to enable navigation between resources. HAL is RECOMMENDED and JsonAPI is OPTIONAL.
The API MUST conform to HTTP semantics.
The API SHOULD implement versioning
You SHOULD avoid requiring resource URIs more complex than collection/item/collection.
A microservice API MUST NOT render HTML views or any type of formatted data oriented to a specific consumer.
Resource naming conventions
Resources MUST be nouns, not verbs. This nouns MUST be in plural
English always MUST be used in all resources.
You MUST use the lower-case-with-dashes convention to name a resource.
You MUST use HTTP Verbs and don't implement verbs in the resources.
Examples
Correct
/emails/
Lowercase, noun, in english
Correct
/client-snapshots/
Lowercase, nouns, dash-separated words, in English
Incorrect
/getCorreos/
Camelcase, verb and Noun Combination, in Spanglish
Events
Event-Driven Architecture
Events express business intentions
In short, domain events help you to express, explicitly, the domain rules, based in the ubiquitous language provided by the domain experts.
A Microservice MUST suscribe and produce events to to propagate committed transactions and updates to additional subsystems, whether they are other microservices, Bounded Contexts or even external applications.
Each Microservice MUST implement an Event Bus Adapter in its infrastructure layer.
Each Microservice MUST use a RabbitMQ Adapter to connect to the Shared Message Broker.
AsyncAPI standard
A microservice that publishes or consumes events MUST implement an AsyncAPI contract and you MUST declare an AsyncAPI manifest.
The async events contract MUST adhere to AsyncAPI 2.6 (or later) conventions.
Default Exchange
Each microservice MUST publish integration events to the exchange named:
sagittarius-a
This ensures that the broker propagates the events to each registered queue using a "Fanout" mechanism (it just broadcasts all the messages it receives to all the queues it knows)
Queues naming conventions
Queue names MUST be lowercase.
You SHOULD NOT add the "queue" suffix or prefix to the queue name
English MUST be used in all queues and domain names.
Each Microservice MUST create a queue based on a combination of the Main Domain and the service name.
The microservice (and queue name) is RECOMMENDED to be based on the subdomain it implements.
Pattern
Why use this pattern
This usually represents the Main Bounded Context and a (Sub) Bounded Context and allows that similarly named events can be published from different services.
domain-name.subdomain-name
Examples
teaching-action.students-grades
sales.email-accounts-validations
Integration Events naming conventions
What is an Integration Event?
An integration event is, something that happened in the domain that you want other parts (or Bounded Contexts) to be aware of. The notified parts usually react somehow to the events.
Integration Events MUST be lowercase
Integration Events always MUST be expressed as something that has happened in the past.
English MUST be used in all integration events.
You MUST use the lower_case_with_underscores convention to name an event.
Event Type Do's and Dont's
Success
email_dispatched
Failure
EmailDispatch
Message Properties Conventions
Why use the type property?
The type property on messages is an arbitrary string that helps applications communicate what kind of message that is. It is set by the publishers and the value is any domain-specific string that publisher and consumers agree on.
If the Event has an unique identifier (UUID), this MUST be placed in the message_id message property
Each event message MUST declare a Delivery Mode (2 por "persistent", 1 for "transient). Usually client libraries handle this property automatically as a boolean or a enum,
Each event message MUST include its signature as the message "type" property.
The message type MUST have the following pattern:
Pattern
domain-name.subdomain-name.event_name
Examples
teaching-action.students-grades.grade_processed
sales.email-accounts-validations.email_confirmed
It is RECOMMENDED that the app_id message property is used to place the microservice name producing the event. This SHOULD be the same name as the queue, following the domain-name.subdomain-name pattern.
If the Event message needs to include additional headers this is OPTIONAL and the logic behind them SHOULD be declared and handled by the publisher.
An Event MUST define the Content Type property as application/json
Events Content Conventions
An Event content MUST be a valid JSON string.
All integration events MUST have an Unique Identifier (UUID) AND a Fired At (DateTime)
- The Unique Identifier (UUID) MUST be present in the payload as a field uuid.
- The Fired At MUST be an ISO-8601 DateTime formatted as YYYY-MM-DDTHH:mm:ss.sssZ and MUST be present in the payload as a field fired_at.
However all this MUST's are not necessary when an event is only consumed by the same service.
It is RECOMMENDED that the Event follows the same Model Schema defined in the OpenAPI REST contract.
Event content examples
This is an event payload of a recently-added student from the sign-ups service bounded context.
| academic-administration.sign-ups.student_added | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |
-
The Event Unique Identifier (UUID).
-
The timestamp of when the event was
fired_at. -
Generally it's a good practice that the key of the field describes the entity that is being included in the payload. The data follows the same Model Schema defined in the OpenAPI contract.
-
the payload MAY include links with the HAL API standard.
This is an event payload of a rejected order of the payments gateway bounded context.
| collection.online-payment.payment_order_rejected | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
The Message broker.
Applications built from microservices aim to be as decoupled and as cohesive as possible - they own their own domain logic and act more as filters in the classical Unix sense.
The Central Message Broker MUST act as a simple event messages distributor.
The communication protocol between your services SHOULD be as simple as possible, only in charge of transmitting data without transforming it. All the magic will happen in the endpoints themselves – they receive a request, process it, and emit a response in return.