The fundamental reasoning and criteria for devising this architectural design was to answer: "**How can we best structure the application as to have independent, decoupled and easily testable and maintanble abstractions?"
This is a work that is inspired mainly from the ideas of Functional Core Imperative Shell, from hexagonal architecture's Ports and Adapters) and from Dependency Inversion principles. The goal is to achieve:
- A pure and isolated domain layer that implements business rules; fully covered in unit tests that are easy to write, run and maintain
- Map this isolated domain layer to a persistent store
- Minimize the amount of integration and e2e tests
- Decouple and easily abstracts integrations of 3rd parties (to adhere to sane SRP)
- Decouple service handlers in a event-driven โwhich enables us to migrate to a serverless infrastructure
-
Imutability:
Value Object
Object that is defined by its attributes. Which never mutate.
-
Identifiability:
Entity
Object whose attributes change over time. Has an identity.
-
Register:
Event
Represents something that has happened within the system.
-
Actions:
Command
Represents some work and command that the system must perform.
-
Consistency and invariants:
Aggregate
Object that semantically abstracts a business macro-entity. It is composed of Value Objects and Entities. It is domain's entrypoint and where public domain services lives. It enables that we consider an aggregate as a whole, a single unity and, hence, preserve expected consistency inside those boundaries. Each operation within this bounded context ends in a consitent and expected state.
Implements interfaces between the system and external world (I/O)
-
Persistent storage:
Repository
Couples with an aggregate to enable persistent storage. This adapter may implement a SQL, an S3, a DynamoDB or any other client connection.
-
Publishing:
Event Publisher
Publishes events in external message buses.
Orchestrates domain and adapter's workflows that are mapped to user stories.
-
Atomic operations:
Unit of Work
Enables decoupling of service and data layer. A unit of work acts as a single entrypoint to persitent storage. It holds, for each atomic operation, aggregate state.
-
Communication:
Message Bus
(barramento de mensagens)Maps commands and events to handlers. Enables decoupling within service layer through a chain of handlers.
-
Orchestration:
Handler
Workflow that must happen as a response to commands and events.
It is advisable to run this project under a specific environment running Python 3.7+
poetry
is being used to manage dependencies efficiently. Therefore, it's as simple as
$ poetry install
In order to update database to recent changes on declared metadata, we're using alembic
. By doing
alembic upgrade head
You should be able to reach most-recent state
TDD is being employed so in order to run and test implementations, abstracted to Makefile
:
$ make test
This is a self-study of the book Cosmic Python: Architectural Patterns.