FastAPI Clean Architecture
Level: Junior+
Today we are going to set up a basic application, based on the FastAPI Framework as an interface.
History
Since I started my journey in web development, almost all the projects I’ve seen have been based on publically available tutorials. We don’t have to blame developers here, as the developers most likely will choose the easiest way they find on SO. The main problem here is the tutorial writer, who eventually says to our developer “This is the correct way to use the Framework X”.
Most of the tutorials are focused on the tool, they use and not the project, which uses it. It looks like the tutorial on “how to 3D-print a car gearbox” which starts explaining “how to use a 3D printer” and completes with “how to change a plastic filament”. And 0 words about the gearbox we want to print.
In the current article, we will focus on the application we build and not the tool we use.
Clean architecture basics
To be as simple as possible, I will take all the terms and guidelines and squeeze out a few simple rules, which will help you get rid of the project structure.
So here they are:
- The application consists of four layers: infrastructure, interface, application and data (database in most cases).
- The infrastructure layer contains modules and configuration, related to docker, git, running scripts, etc.
- The interface layer contains a framework application code, schemas, routers and another web API-related stuff.
- The application layer contains our pure Python logic, business rules mapping, etc.
- The data layer contains the database connector, models and external data sources.
For those who like to look and not to read:
Time to code
In order to use the above guidelines inside our projects, we will create a template file structure (feel free to use it in each project You start).
Basic structure
/ <- root of the project
/app/ <- custom business logic
/data/ <- data source connectors and models
/infrastructure/ <- requirements.txt, Dockerfile, etc.
/interface/ <- FastAPI main.py,auth.py, routers, schemas
Full template
/
/app/
/module_x/
/module_y/
/data/
/database.py
/models/
/module_a.py
/module_b.py
/interface/
/auth.py
/main.py
/routers/
/domain_1.py
/domain_2.py
/schemas/
/domain_1.py
/domain_2.py
/infrastructure/
Dockerfile
requirements.txt
run_wex.sh
Explanation
Focus on things matter
The main focus of such a structure is to show that the business logic is on the same level of importance as the FastAPI interface, database connection and infrastructure AND they are easily interchangeable. We can change the framework (ex. FastAPI > Flask), the infrastructure (ex. docker > docker-compose), database connection (SQLAlchemy > DjangoORM) or even the database engine (Postgres > SQLite). The most important and independent part here will be your app with all the business logic inside it.
Cut horizontally, not vertically
If you follow the way separation is done, you’ll note that the app is divided by the core technical responsibility first (architecture layers). Just look at how the code, related to the project environment setup is stored separately from the main.py and database connection. In most of the DIY Petshop tutorials, they show you how to cut the whole architecture by the user domain/need which leads us to the SAME architecture in multiple places called apps: one set of routers, models, and schemas for the User app, another set of the gut for the Pet app and the third one for the shop cart app.
Django’s app paradigm you know is dead
When you start working with the Django Framework, the documentation can mislead you to the wrong point of view around the app development approach. In the first steps, you try to create everything inside one app. After a few months, you try to separate your projects with the apps aka Posts in posts-app, Pets are in pet-app. Then you can come to the point to keep the whole pet shop with all the related things in the pet-shop-app.
I’ve tried all of those approaches and each of them failed. The problem lies down deep inside the missing thing: each of the project layers can have a domain separation and separation on one level is not the same on the other level.
Let me explain what I’m talking about. Look at the template above. Did you notice that the app, router, schema, and model modules are not the same? That’s because this is not always the truth.
Example at the end
Project: Internet Shop
Main business enitites: Store, Client, Cart, Order
Main API entities: Store entities with the amount, Client personal data, Client shipping address, Cart of the client, Cart items list with totals, Order related to the client and converted from the card, Item image and other properties.
Main database entities: StoreEntity, StoreEntityType, StoreEntityAmount, Client, ClientShippingAddress, ClientPersonalData, Cart with the owner and amounts/list of items, order related to the card, etc.
The task with a star
How would the Django developer split it: Store app, Card app, Client app, Order app, etc? But where do we store relations between apps? It's not quite correct to store relational table connections in one of the apps. Especially in case, the business domain makes the relation between tables more important than the table by itself.
Correct solution
That is why instead of trying to divide the thing, which can’t be divided, we do it on each level separately. The app will have Clients, Store, Cart and Order modules. The database will have all the main and intermediate tables. Routers will have Client, ClientCard, StoreItem, ClientOrder and other modules. And the schemas will repeat the routers, as they are directly related.
Last words
I hope, you’ll find the above approach helpful in your home and production-ready projects. Feel free to comment and continue the discussion below.