Cosmic FastAPI is a template for creating a FastAPI project following the Cosmic Python guidelines.
- Cosmic FastAPI
This template includes FastAPI
using the fastest Pydantic V2
model validation. Designed for Event-Driven
Architecture (EDA) and Domain-Driven Design (DDD). Providing a clean and simple structure to start a new project. With
the addition of pre-commit
hooks to ensure code quality. And ready to be used in a CI/CD
GitHub Workflows pipeline.
Variables prefixed with FASTAPI_
are used to configure the API UI.
Name | Description | Default Value |
---|---|---|
FASTAPI_DEBUG | Debug Mode | False |
FASTAPI_PROJECT_NAME | Swagger Title | API GATEWAY |
FASTAPI_PROJECT_DESCRIPTION | Swagger Description | ... |
FASTAPI_PROJECT_LICENSE | License info | ... |
FASTAPI_PROJECT_CONTACT | Contact details | ... |
FASTAPI_VERSION | Application Version | template.version |
FASTAPI_DOCS_URL | Swagger Endpoint | /docs |
Variables prefixed with UVICORN_
are used to configure the server.
Name | Description | Default Value |
---|---|---|
UVICORN_HOST | Server Host | '127.0.0.1' |
UVICORN_PORT | Server Port | 8000 |
UVICORN_LOG_LEVEL | Log Level | 'info' |
UVICORN_RELOAD | Enable/Disable Reload | False |
As our application gets bigger, we’ll need to keep tidying our directory structure. The layout of our project gives us useful hints about what kinds of object we’ll find in each file. We can use this to navigate our codebase more easily.
.
├── settings.py
├── domain #(1)
│ ├── commands.py
│ ├── events.py
│ ├── schemas.py
│ └── models.py
├── service_layer #(2)
│ └── services.py
├── adapters #(3)
│ ├── orm.py
│ └── repository.py
├── entrypoints #(4)
│ ├── __init__.py
│ └── monitor.py
└── tests
├── __init__.py
├── conftest.py
├── unit
├── integration
└── e2e
└── test_monitor.py
- (1). Domain, from Domain Driven Architecture.
- Commands, from Command Query Responsibility Segregation (CQRS). Commands are the messages that change the state of the system.
- Events, from Event Sourcing. Events are the messages that describe a change in the system.
- Schemas, from FastAPI, data models or data structures that are used to define the shape or structure of data that your API receives or returns.
- Models, represent the domain entities, business objects of interest.
- (2). The service layer will be distinguished. What is the difference between a domain service and a service layer?
- Application service (our service layer) ts job is to handle requests from the outside world and to orchestrate an operation.
- Domain Service. This is the name for a piece of logic that belongs in the domain model but doesn't sit naturally inside a stateful entity or value object. For example, if you were building a shopping cart application, you might choose to build taxation rules as a domain service.
- (3). Adapters, it comes from ports and adapters terminology. This will fill up with any other abstractions around external I/O. Strictly speaking, you would call these secondary adapters or driven adapters, or sometimes inward-facing adapters.
- (4). Entrypoints are the places we drive our application from. In the official ports and adapters terminology, these are adapters too, and are referred to as primary, driving, or outward-facing adapters.
We may even consider splitting our models, schemas, events and commands into separate packages and files if they get too big.
Commands
, Events
, Schemas
and Models
are the building blocks of our Domain.
First, we define our domain models. These are the objects that represent the business concepts we’re working with. They should be as simple as possible, and contain only the attributes that are essential to the business, utilizing the business jargon. The idea is that, if you were to show these models to a non-technical person, but someone who understand the business process, they would be able to understand what the application does. Models encapsulate the behavior, state, and business rules that govern the application. Models can encompass entities, aggregates, and sometimes even Value Objects.
Entities are objects that have distinct identities that run throughout their lifecycle. In other words, an entity is defined not just by its attributes, but also by a unique identifier that differentiates it from other entities of the same type. Entities are mutable and can have their attributes modified while maintaining the same identity. They are often used to represent real-world objects or concepts that have an ongoing existence.
For example, in an e-commerce system, a "Product" can be an entity. Each product has a unique identifier, and its attributes (such as name, description, price) can change without changing its identity.
A Value Object is a concept from Domain-Driven Design (DDD). It's an object that represents a descriptive aspect of the
domain with no conceptual identity. In other words, a Value Object
is defined solely by its attributes, and two Value
Objects with the same attributes are considered equal. They are immutable and can be thought of as "flyweight" objects
that are shared whenever their values are the same.
Value Objects can be part of a Model
. In fact, they often enhance the expressiveness and maintainability of Models.
Value Objects help to define attributes with semantic meaning and encapsulate their validation and behavior. In some
cases, a Model might consist of one or more entities and Value Objects that work together to represent and manage the
business logic and data.
Aggregates are clusters of related entities
and value objects
that are treated as a single unit. The aggregate is
the boundary within which changes are managed and consistency is maintained. One entity within the aggregate is
designated as the "aggregate root."
All interactions with the aggregate are done through this root entity. This helps ensure that the integrity and
consistency of the data is maintained within the aggregate.
For example, in the case of an e-commerce system, a "Shopping Cart" could be an aggregate. The shopping cart would be composed of multiple line items (entities representing products in the cart) and possibly other related information. All changes to the items in the cart would be managed through the shopping cart aggregate root.
Entities can be part of an aggregate, and an aggregate often includes one or more entities and possibly value objects. Aggregates define the transactional boundaries and consistency rules within the domain. They encapsulate business rules and enforce invariants to ensure that the data remains in a valid and consistent state.
Schemas are used to define the structure and validation rules for the input and output data of your API. Schemas help ensure that data is correctly formatted and adheres to specific criteria before being processed. They are often used to validate request payloads and to define the shape of the data returned from API endpoints.
In this project we use Pydantic
to define our schemas. Pydantic is a library
that provides runtime checking and validation.
Commands
and Events
are the building blocks of our Event Driven Architecture. You can consider both as simple
dataclasses, as they have no behaviour.
Both commands and events are often used in software architectures to promote separation of concerns, modularity, and extensibility. By encapsulating actions or occurrences into discrete objects, it becomes easier to reason about the system and make changes without impacting other parts of the codebase.
In an API, we can use commands
to represent requests from clients to perform certain actions. These commands may need
to be validated before they can be processed. Once validated, they can be executed by an appropriate handler or service.
Following this idea, we can use events
to represent our API responses. For this, we can associate both as kind
of schemas
.
Commands represent actions or requests to be performed by a system. They typically encapsulate a specific intent or operation that needs to be executed. In software development, commands are often used in conjunction with a command pattern or a similar architectural pattern to decouple the sender of the command from its execution.
For example, in a web application, a command might be used to represent a user's request to update their profile information. The command object would contain the necessary data to carry out the update operation, and it would be executed by an appropriate handler or service.
Events represent notifications or signals that something has happened within a system. They convey information about a specific occurrence and are often used for communication between different components or modules of an application. Events are typically used in event-driven architectures or publish-subscribe patterns.
When an event occurs, it can be published to an event bus or a similar mechanism. Other components that have subscribed to that event can receive and react to it accordingly. This allows for loose coupling and enables different parts of the system to respond to events without direct dependencies on each other.
For example, in an e-commerce application, an event might be triggered when a new order is placed. Subscribed components, such as inventory management or shipping modules, can then react to this event by updating their respective states or initiating further actions.
The main idea behind Clean Architecture is to create a separation of concerns in software systems, allowing for easier maintenance, testing, and scalability. The core principle of Clean Architecture is the dependency rule, which states that dependencies should always point inward towards the core of the application and not outward towards external frameworks or tools. This helps keep the core of the application independent and flexible.
Clean Architecture typically consists of several layers, each with a specific responsibility:
- Entities: These are the core business objects and rules.
- Use Cases (Interacts): These encapsulate the business logic and orchestrate interactions between entities.
- Interfaces (Gateways): These define the interfaces that allow the use cases to interact with external data sources or systems.
- Frameworks and Drivers: These are the outermost layers that deal with the infrastructure, such as databases, web frameworks, UI, etc.
In our project, we can easily observe the different layers according to our directory structure. It's an architecture that "screams": by its naming conventions, we can easily understand what is the responsibility of each module.
This project uses make
as an adaptation layer.
Run make help
to see all available commands.
This package uses poetry for dependency management.
Install poetry in the system site_packages
. DO NOT INSTALL IT in a virtual environment itself.
To install poetry, run:
pip install poetry
-
Clone the repository
git clone "[email protected]/tomasanchez/cosmic-fastapi.git"
-
Install dependencies
cd cosmic-fastapi && poetry install
Note that poetry doesn't activate the virtual environment for you. You have to do it manually. Or prefix subsequent the commands with
poetry run
You can view the environment that poetry uses with
poetry env info
To activate run:
poetry shell
-
Activate pre-commit hooks (Optional)
Using pre-commit to run some checks before committing is highly recommended.
To activate the pre-commit hooks run:
pre-commit install
To run the checks manually run:
poetry run pre-commit run --all-files
The following checks are run:
black
,flake8
,isort
,mypy
,pylint
.
-
Run:
poetry run python -m template.main
-
Go to http://localhost:8000/docs to see the API documentation.
You can run the tests with:
poetry run pytest
or with the make
command:
make test
To generate a coverage report add --cov src
.
poetry run pytest --cov src
Or with the make
command:
make cover
To update the dependencies run:
poetry update
This project is licensed under the terms of the MIT license unless otherwise specified. See LICENSE
for
more details or visit https://mit-license.org/.
This project was designed and developed by Tomás Sánchez <[email protected]>.
Deeply inspired by FastAPI-MVC following Cosmic Python guidelines for project structure.
If you find this project useful, please consider supporting its development by sponsoring it.