
Our Journey from Flask to FastAPI
Before diving into our current architecture, it's worth sharing our journey. Like many Python web applications, we started with Flask. Flask served us well in the early days - it's simple, well-documented, and has a great ecosystem. However, as our application grew, we faced several challenges:
Async Limitations: Flask's synchronous nature became a bottleneck, especially when dealing with multiple database queries and external API calls.
Schema Validation: We found ourselves writing lots of manual validation code for request/response data.
API Documentation: Maintaining up-to-date API documentation required significant effort.
Type Safety: As our codebase grew, we wanted better type hints and validation.
FastAPI addressed all these pain points:
Built-in async support with modern Python async/await syntax
Automatic request/response validation with Pydantic
OpenAPI documentation out of the box
First-class support for Python type hints
The migration from Flask to FastAPI not only improved our performance but also gave us an opportunity to rethink our architecture. That's when we adopted the feature-first approach.
Traditional vs. Feature-First Architecture
When building FastAPI applications at scale, one of the most crucial decisions is how to organize your code. While many tutorials show a layer-first approach, we've found that a feature-first architecture leads to better maintainability, clearer boundaries, and improved developer experience.
Let's look at how these approaches differ:
Traditional Layer-First Structure
Feature-First Structure
Why Feature-First Makes Sense
1. Domain Cohesion
In a feature-first architecture, all code related to a specific feature lives together. This means when you're working on the user management feature, everything you need is in the users
directory. Let's look at a complete feature implementation:
2. Clear Boundaries
Each feature folder is a self-contained module with clear responsibilities. This makes it easier to:
Understand the scope of each feature
Maintain separation of concerns
Prevent unwanted dependencies between features
Test features in isolation
3. Scalability Benefits
As your application grows, feature-first architecture provides several advantages:
Easier Microservices Migration: Each feature folder can become its own microservice with minimal refactoring.
Independent Development: Teams can work on different features without stepping on each other's toes.
Simplified Deployments: Features can be deployed independently if needed.
4. Developer Experience
The feature-first approach significantly improves developer experience:
Reduced Context Switching: No need to jump between multiple folders when working on a feature
Clear Ownership: Teams can own entire features rather than layers
Faster Onboarding: New developers can understand features in isolation
Better Code Reviews: Changes for a feature are contained in one place
Handling Shared Code
One common question is how to handle shared code in this architecture. Here's our approach:
The core
directory contains shared utilities, configurations, and dependencies that multiple features might need.
Best Practices
Keep Features Independent
Minimize cross-feature dependencies
Use events or well-defined interfaces for feature communication
Think twice before sharing code between features
Consistent Internal Structure
Each feature should follow the same structure (router, service, schema)
Use consistent naming conventions
Maintain similar patterns across features
Clear Feature Boundaries
Each feature should have a clear, single responsibility
Avoid creating "utility" features
If multiple features need similar functionality, consider if it belongs in
core
Documentation
Each feature should have its own README.md
Document feature-specific configuration and dependencies
Include feature-level testing instructions
Testing in Feature-First Architecture
The feature-first approach makes testing more straightforward:
Each feature has its own tests, making it easy to:
Run tests for specific features
Maintain test data and fixtures close to the tests
Achieve high test coverage for critical features
How do other teams do it?
Many major tech companies use feature-first (also called vertical slice) architecture in their applications, though they might call it by different names. Here are some notable examples:
Netflix
Uses a similar feature-based organization in their microservices
Each service is organized around business capabilities/features
This inspired their famous "Netflix Stack" architecture
Spotify
Known for their "Squad" model where teams own entire features
Their code organization mirrors their team structure (Conway's Law)
Features are organized as independent "tribes"
Microsoft
Uses feature-based organization in many of their .NET applications
Has been promoting "vertical slice architecture" through their documentation
Jimmy Bogard (Microsoft MVP) heavily advocates for this approach
Facebook
Their React codebase is organized by features
Mobile app structure follows feature-first patterns
Each feature team owns their entire stack
Amazon
"Two Pizza Teams" model naturally led to feature-based code organization
Each service is typically organized around specific business capabilities
Teams own features end-to-end
However, it's important to note that:
Most companies don't strictly follow just one pattern
They often adapt and blend different architectural approaches
The implementation varies based on team size and business needs
They might use different architectures for different parts of their systems
Conclusion
Feature-first architecture is a powerful way to organize FastAPI applications. It provides clear boundaries, improves developer experience, and makes your codebase more maintainable as it grows. While it might take some time to adjust if you're used to layer-first architecture, the benefits become clear as your application scales.
Remember, the goal of any architecture is to make development easier and more maintainable. Feature-first architecture achieves this by keeping related code together and establishing clear boundaries between different parts of your application.
Consider adopting this approach in your next FastAPI project, especially if you anticipate it growing beyond a few simple endpoints. The initial investment in organization will pay dividends as your application evolves.
About the Author
Founder at Gridlines