Introduction
So far, we have organized our application into multiple files, such as routes and models, making the project easier to navigate. However, our route functions still perform multiple responsibilities. A typical endpoint may:- Receive the HTTP request.
- Validate the incoming data.
- Execute business logic.
- Read or update data.
- Return the HTTP response.
Why Modularize?
Consider the following endpoint.- Receiving the HTTP request.
- Validating the input.
- Applying business rules.
- Saving the employee.
- Returning the response.
Before Modularization
- Handles the HTTP request.
- Executes business logic.
- Accesses the data store.
- Returns the HTTP response.
After Modularization
The same request is divided into dedicated layers.Layer Responsibilities
This design follows the Single Responsibility Principle (SRP).
Benefits
A layered architecture makes the application:- Easier to understand
- Easier to maintain
- Easier to test
- Easier to extend
- Easier to debug
- Easier to reuse
- Replacing the in-memory dictionary with PostgreSQL requires changes only in the Repository.
- Adding new business rules requires changes only in the Service.
- The Controller continues to expose the same HTTP APIs.
What We Will Build
In this chapter, we will gradually refactor our Employee Management System into a modular FastAPI application.
By the end of this chapter, our Employee Management System will follow the same architecture used in most production FastAPI applications.
Step 1: Define the Models
Before implementing the Router, Service, and Repository layers, let’s define the models used by our Employee Management System. Each layer works with data in a different way, so instead of using a single model everywhere, we’ll define separate models for different responsibilities.- Request Models validate data received from clients.
- Business Models represent the application’s working data.
- Response Models control the data returned to clients.
Project Structure
Model Flow
Why Different Models?
Each model has a different responsibility.
For example, when a client creates a new employee, they only send:
Task
Create the following file.Solution
employee.py
employee.py
What We Have So Far
Our application now has a clear separation between incoming data, internal processing, and outgoing data.EmployeeBusiness objects.
Step 2: Repository Layer
The Repository is responsible for interacting with the application’s data source. It acts as a bridge between the Service Layer and the data source, hiding the implementation details of how data is stored or retrieved. At this stage, our data source is an in-memory dictionary. Instead of creating the data inside the Repository, we’ll inject it through the constructor. This technique is called Constructor Injection, one of the most common forms of Dependency Injection (DI). Later in this chapter, FastAPI’sDepends() will perform this injection automatically.
Project Structure
Responsibilities
The Repository is responsible for:- Reading employee data.
- Creating new employees.
- Updating existing employees.
- Deleting employees.
- Converting raw data into
EmployeeBusinessobjects.
- Validate requests.
- Apply business rules.
- Return HTTP responses.
Constructor Injection
Instead of creating the data source itself, the Repository receives it from outside.Task
Create the following files.database.py and inject it into the Repository using the constructor.
Solution
database.py
database.py
employee_repository.py
employee_repository.py
Using the Repository
For now, we manually inject the data source while creating the Repository.EMPLOYEES) is supplied through the constructor rather than being created inside the Repository.
What We Have So Far
Depends), allowing FastAPI to create and inject the Repository automatically.
Step 3: FastAPI Dependency Injection
In the previous step, we manually created the Repository by passing the data source through its constructor.Depends(). Instead of creating objects ourselves, we describe how to create them, and FastAPI automatically creates and injects them whenever they are needed.
Dependency Flow
Task
Create a dependency provider that constructs and returns anEmployeeRepository.
Solution
dependencies.py
dependencies.py
Using the Dependency
Instead of creating the Repository manually:RepositoryDep is now a reusable dependency that can be injected into the Service layer.
What We Have So Far
EmployeeRepository through dependency injection and implement the application’s business logic.
Step 4: Service Layer
The Service layer contains the application’s business logic. It acts as an intermediary between the Router and the Repository. Instead of directly accessing the Repository, the Router delegates the request to the Service, which applies business rules and coordinates data operations.Project Structure
Responsibilities
The Service is responsible for:- Implementing business rules.
- Coordinating Repository operations.
- Creating Business Models from Request Models.
- Returning Business Models to the Router.
- Handle HTTP requests or responses.
- Read or write data directly.
- Know how the data is stored.
Service Flow
Constructor Injection
The Service depends on the Repository. Instead of creating it internally, it receives the Repository through its constructor.Task
Create the following file.Solution
employee_service.py
employee_service.py
Register the Dependency
Create a dependency provider for the Service.dependencies.py
dependencies.py
What We Have So Far
- The Service works with Request Models and Business Models.
- The Repository works only with Business Models.
- Neither layer knows anything about HTTP requests or responses.
Step 5: Router Layer
The Router is the entry point of every HTTP request. Its responsibility is to receive requests, validate the incoming data, delegate the work to the Service, and return the appropriate response. The Router should not contain business logic or data access code.Project Structure
Responsibilities
The Router is responsible for:- Defining API endpoints.
- Receiving HTTP requests.
- Validating Request Models.
- Calling the Service.
- Returning Response Models.
- Generate employee IDs.
- Apply business rules.
- Access the data source directly.
Request Flow
Task
Create the following file.Solution
employee_router.py
employee_router.py
Register the Router
Finally, register the Router with the FastAPI application.main.py
main.py
What We Have So Far
- Router handles HTTP requests and responses.
- Service implements business logic.
- Repository manages data access.
- Database stores the application’s data.
Depends().