Skip to content

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
Event is in the past and uses lowercase with underscores

Failure

EmailDispatch
Event is in the present and uses camelcase

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.

Read more...

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
{
    "uuid": "a8e83147-6957-5936-b508-5723ae47cebf", // (1)
    "fired_at": "2022-06-29T00:10:05.938545Z", // (2)
    "student": { // (3)
        "uuid": "04af83eb-8840-50ac-a235-f43343e20c86",
        "oid": 3942163,
        "firstname": "Mirna Waleska",
        "lastname": "Espiasanal Ponce",
        "login": "HNPSMIPDE3942163",
        "password": "*&^&^**(*",
        "email": "mwespinal1991@gxxxl.com",
        "country": "HN",
        "language": "es",
        "courses": [
            {
            "id": "192827",
            "language": "es",
            "enrolled_at": "2020-07-31",
            "started_at": "2020-08-03T00:00:00.000000Z",
            "finished_at": "2022-08-03T00:00:00.000000Z",
            "institutions": "UNEATLANTICO,UNINI-MX",
            "group": "FUNIBER",
            "status": "Activo",
            "event": "Al Día",
            "program": {
                "id": 1132,
                "abbreviation": "PSMIPDE",
                "name": "Máster en Intervención Psicológica en el Desarrollo y la Educación",
                "version": "2016-vEA-PPS-TFC"
            },
            "optatives": []
            }
        ],
        "created_at": "2021-09-02T00:15:14.140382Z",
        "_links": { // (4)
            "self": {
            "href": "http://academic-administration.fbr.group/sign-ups/students/04af83eb-8840-50ac-a235-f43343e20c86"
            }
        }
    }
}
  1. 🆔 The Event Unique Identifier (UUID).

  2. 📅 The timestamp of when the event was fired_at.

  3. 🧬 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.

  4. 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
{
    "uuid":"8e791511-cd02-436d-8e2d-309cc2f1a291",
    "fired_at":"2023-05-17 21:01:09",
    "transaction":{
        "uuid":"3091584e-9bf9-4494-b9b5-663ea627d8b8",
        "message":"Combinación de divisa y tarjeta no permitida",
        "code":"507",
        "gateway":{
            "uuid":"872d8bb1-59e7-42a4-ab2a-f3b29816f291",
            "platform":"Addon Payment"
        },
        "payer":{
            "uuid":"e7b188bf-8143-4b0d-b76d-389929fb9c10",
            "first_name":"Lesley Pereira",
            "last_name":"De Sousa",
            "phone":"09832049823",
            "email":"lesley.desousa@xxxxx.com"
        },
        "payment_method":{
            "uuid":"f59aa5c0-0e7d-41ff-a5de-1bfcb72e91bf"
        },
        "payment":{
            "amount":92,
            "currency":"EUR",
            "description":"Cuota PROG 29\/30",
            "reference":{
                "auth_user":null,
                "description":"Cuota PROG 29\/30",
                "payment_reference":"5562957"
            }
        }
    }
}

The Message broker.

"Dumb pipes, smart endpoints"

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.