Design Principles¶
Microservice architecture¶
The backed is built on a microservice architecture:
- In Majority we call each microservice "Area".
- Each Area has its own code repository
- An Area consists of one or a few "physical" services, that are developed and deployed together.
- Each Area is responsible for one subdomain of Majority's business domain.
- Each Area has its own data storage (database, blob storage etc).
- Each Area belongs to one of the teams, and has a developer as its owner.
Communication¶
- Apps communicate with the Areas mainly through REST API
- Some communication happens through Firebase real-time database.
- Areas communicate with each other through events or REST API.
- Events are the preferred way
- Synchronous user flows where latency and feedback are important are an exception
- The API used by Apps, and the API used by other Areas are separate, and has their own separate contracts.
Events¶
- Event based communication happen in two ways:
- Events that inform other interested parties that something happened.
- Only a single area should publish a specific event, but many can subscribe.
- Naming pattern:
<SomethingHappened>Eventi.e.TransactionCompletedEvent
- Messages that tell another party to do something.
- Only a single area should listen for a specific message, but more than one Area can publish.
- Naming pattern:
<DoSomething>Messagei.eBlockLedgerAccountMessage
- Events that inform other interested parties that something happened.
- The event/message producer and consumer can be the same Area for area-internal processes.
- Events/messages can be scheduled to arrive at a future time
- Events/messages can be published in a session, meaning they have guaranteed order within a scope such as
UserId. - We do not use other messaging patterns.
Event Design¶
- In general, events/messages are "fat". They carry enough data that the consumer does not need to do a callback to get additional info.
- While being "fat", events should not be overweight. I.e, 1MB is overweight.
- Events should include data useful for the Data Warehouse, even if it's not used by other Areas.
- Avoid too granular events, e.g.
CardChangedEventfor all kinds of changes to a card is better than separate events for when card become default, when it becomes non-default etc.- Delta / Separate status change events? More discussion to clarify this point.
Deadletters¶
- Our vision is to have 0 deadletters.
- If deadletter happens:
- Investigate why it happened.
- Try to fix the underlying issue
- Its important to also propagate any state, i.e. by re-queueing the deadletter to let the handlers process it again.
- If the deadletter is not resent, it should be removed.
- The area owner(s) is responsible to minimize the amount of deadletters "produced" by the area, and to clean up deadletters created by the area.
Data Storage¶
- Each Area has its own data store (database, secrets store, blob storage).
- Each area should normally only have one instance of each storage, e.g. one key vault, one storage account etc.
- Each Area is the owner of the data related to its subdomain. I.e. Mpay area owns a Majority Payment, but the underlying transactions involved are owned by Transactions area.
- For resilience, decoupling and performance reasons, an Area can store copies of data owned by other Areas.
- Typical example is users. I.e Mpay area has a partial copy of the User table, built from events published by User Area.
This data copy does not necessarily include the full object (like all user properties in case of user)