The project is written using the FastAPI Python framework and Poetry dependency manager. PostgreSQL was chosen as the relational database for the project.
Access the API at: http://localhost:8000
You can test the endpoints using:
- Swagger UI (built-in at
/docs) - Postman (or any API client at:
/v1/projects/create_new,/v1/projects/list,/v1/projects/edit/{project_id},/v1/projects/delete/{project_id})
-
Environment variables in Git:
- I added
docker-compose.ymlwith environment variables to Git on purpose. This is a test assignment and I did it to simplify the review process - In production environments, sensitive credentials should never be committed to Git
- Use
.envfiles (added to.gitignore) and secrets management tools instead (like Azure Key Vault)
- I added
-
Database as Docker volume:
- I stored PostgreSQL data (
postgres_data) as a Docker volume. This simplifies setup and makes it easy to run locally - In real-world scenarios, the database should run on a separate managed resource (e.g., Azure Database for PostgreSQL and etc.)
- Databases should not be containerized in production for reliability, backup, and scaling reasons
- I stored PostgreSQL data (
-
TLS protocol In a production environment, web applications must use HTTPS to ensure secure communication via the TLS protocol.
Project Registry System follows SOLID principles and best practices. A layered architecture was chosen to create maintainable and scalable code.
The project has folowing layers:
-
API layer with versioning (
api/v1/): For each API version it handles HTTP requests/responses and consists of the following components:- routes/: Endpoints definition;
- schemas/: Pydantic models for data validation and serialization
- dependencies/: Dependency injection setup to initialize service class and repository class
Responsibilities: Request validation, routing, and response serialization
-
Buisness Logic Layer (
services/): Encapsulates core application logic. It consists of service classes (e.g.,ProjectsService). This layer implements business rules, validates request logic according to these rules, and raises custom exceptions in case of failures. Responsibilities: Business rules, validation, and orchestration -
Data Process Logic (
db): It implemnets database communication logic It has following components:- models.py: SQLAlchemy ORM models;
- repositories/: Classes with data access;
- interfaces/: Interfaces for repository classes to separate data logic and buisness logic;
- connection.py: Databse connaction (angine, session factory) managment;
- decorators.py: Session handling, Retry logic handling
Responsibilities: All SQL queries and database operations. Uses decorators to align with the DRY (Don't Repeat Yourself) principle.
-
Configuration Layer (
config/): Handles application configuration, including environment variables and settings management. Responsibilities: Configuration and environment management -
Cross-Cutting Concerns (
utils/): Shares helpers, utilities, and handlers across all layers:- exceptions.py: All customer exceptions;
- exception_handler.py: Customer exception handler;
- logging.py: Project logging logic;
- base_schema.py: Pydantic base api response schema Responsibilities: Logging, error handling, and common utilities
- Separation of Concerns: Each layer has a single responsibility
- Dependency Injection: Services injected via FastAPI's Depends()
- Data Access Object Pattern: to separate buisness logic and data acess logic
- API Versioning: /v1/ allows future API evolution
- ORM Pattern: SQLAlchemy models separate from Pydantic schemas
- Decorator Pattern: Session management via decorators, retry logic managment via decorators
I didn't finish implementing all tests. I created unit tests for the following layers:
Service layer: Business logic validationAPI layer: Schema validations (Pydantic models)
As these layers form the functional core of the project, I believe testing both of them provides sufficient unit test coverage.
To run unit tests: poetry run pytest tests/unit/ -v
To implement integration and end-to-end tests properly, the following setup is required:
- Test database: Create a separate PostgreSQL database for testing
- AsyncClient (for E2E tests): To simulate HTTP requests to test endpoints
- Database fixtures (for both integration and E2E tests): test engine, session factory, test sessions
Integration Tests Integration tests should verify that multiple components work together correctly:
- Test the service layer with real database connections
- Test the complete dependency chain: API dependencies - Service - Repository - Database
- Verify that services interact correctly with the repository and persist data to the database
E2E Tests E2E tests should verify complete user workflows through the HTTP API:
- Success scenarios to test:
- Create operation: Verify status code (200) and response message:
Project created successfully - Update operation: Verify status code (200) and response message:
Project {project_id} was successfully updated with new data - Delete operation: Verify status code (200) and response message:
Project {project_id} was successfully deleted - List all projects operation: Verify status code (200), data structure and response message:
Projects retrieved successfully
- Failure scenarios to test:
- Database connection failures (should retry 3 times and trigger customer exception
RepositoryError) (404, "Database error.") - Invalid input data (422, validation errors)
- Duplicate project name insertion (trigger customer exception
ProjectAlreadyExistsError) (400, "Project alreday exists error.") - Non-existent project update/delete (trigger customer exception
ProjectNotFoundError) (400, "Project not found error.") - Missing required fields (422, validation errors)
Decision I decided not to rush the implementation of integration and E2E tests, but to document the testing strategy and requirements for future implementation. This ensures that when these tests are added, they will be comprehensive and follow a clear plan.
I would use an authentication provider such as Azure Entra ID to implement two-factor authentication with OAuth2.
- Registration
- The user registers with all required user data, creating a valid username and password
- These credentials are registered in Azure Entra ID (or another provider)
- User profile metadata can be stored in the database
- Login
- User fills in credential fields
- The login request redirects to Azure Entra ID for authentication
- If credentials are valid, Azure Entra ID returns:
Access Token (JWT): Valid for 5 minutes
Refresh Token: Valid for 24 hours (or longer)
The application stores these tokens securely like httpOnly cookies
- Token Management
- JWT access token should be attached to the Authorization header of each API request
- Create a dependency that extracts and validates the JWT token from request headers
- Create a middleware which manages JWT token refresh for every request using the refresh token. If the refresh token is also expired, it redirects the user to the login page
- Decline/reject requests with invalid, expired, or missing tokens
- Cloud Architecture
Small to Medium Scale (< 1M requests)
If the project doesn't have more than a million requests and lacks strict non-functional requirements (SLA/SLC), for example, Azure Web App for Containers is a suitable choice. The benefits:
- Web App supports multiple deployment slots (e.g., dev, staging, preprod, prod) for safe deployments
- CI/CD pipelines are automatically configured when using Azure Web App
- Built-in autoscaling and load balancing handle moderate traffic
Medium to Large Scale (> 1M requests)
It is possible to split the monolithic backend into microservices (for example, by domain) and create an API Gateway (Azure API Management or similar) to:
- Route requests to appropriate services
- Handle rate limiting, authentication, and caching
- Provide a unified API interface
Large Scale & Complex Scenarios
It is possible to use Azure Kubernetes Service (AKS) or similar container orchestration platform to:
- Automatically create service replicas based on load
- Orchestrate containers across multiple nodes
- Handle zero-downtime deployments with rolling updates for replicas and etc.
Other cloud desicions which will improve relaibility of the project:
Database: Cloud managed services (Azure SQL Server or other) with automatic backups and high availability
Monitoring: Set up Application Insights for observability and alerts
Security: Azure Key Vault for secrets management and Azure Entra ID for authentication
- CI/CD
For CI/CD pipelines, it is possible to use Azure DevOps, GitHub Actions, GitLab CI/CD, or similar tools.
The strategy in the build job:
- Run code quality checks (formatter with Black, linter with Flake8)
- Run tests with pytest
- Build the container image
- Push the container to the registry
The strategy in the deploy job:
- Authenticate to the cloud resource (for Azure, it is necessary to first create a Service Principal)
- Deploy the application using the previously built container image
- Networking
- Network Segmentation:
Create two fully isolated Virtual Networks (VNets) to separate environments:
Production (PRD) - for production and preproduction environments
Acceptance/Test (ACC) - for development and testing environments
- Security Configuration:
Database:
Accessible only via private/internal IP address (no public exposure) Deployed in a dedicated database subnet Protected by Network Security Groups (NSG) allowing only web app subnet traffic
Web Application:
Has both public IP (for user access) and private IP (for internal communication) Deployed in a dedicated web app subnet Uses Application Gateway or Load Balancer for public access