How to Structure a Three-Tier Layered Architecture in Python
A mock three-tier architecture with presentation, business, and data layers that process a user request from input to response.
Python code
45 linesclass PresentationLayer:
def __init__(self, business_layer):
self.business = business_layer
def handle_request(self, user_id):
print(f"[Presentation] Received request for user {user_id}")
data = self.business.process_user(user_id)
print(f"[Presentation] Response: {data}")
return data
class BusinessLayer:
def __init__(self, data_layer):
self.data = data_layer
def process_user(self, user_id):
print(f"[Business] Processing user {user_id}")
if user_id <= 0:
return {"error": "Invalid user ID"}
user = self.data.get_user(user_id)
if user is None:
return {"error": "User not found"}
return {"name": user["name"], "age": user["age"]}
class DataLayer:
def __init__(self):
self.users = {
1: {"name": "Alice", "age": 30},
2: {"name": "Bob", "age": 25},
3: {"name": "Carol", "age": 35},
}
def get_user(self, user_id):
print(f"[Data] Fetching user {user_id}")
return self.users.get(user_id)
if __name__ == "__main__":
data_layer = DataLayer()
business_layer = BusinessLayer(data_layer)
presentation_layer = PresentationLayer(business_layer)
presentation_layer.handle_request(2)
presentation_layer.handle_request(99)
Output
[Presentation] Received request for user 2
[Business] Processing user 2
[Data] Fetching user 2
[Presentation] Response: {'name': 'Bob', 'age': 25}
[Presentation] Received request for user 99
[Business] Processing user 99
[Data] Fetching user 99
[Presentation] Response: {'error': 'User not found'}
How it works
Each class represents a distinct layer with a clear responsibility: the presentation layer handles input and output, the business layer contains the application logic, and the data layer manages persistence. Dependencies flow downward — the presentation layer receives a business layer instance, and the business layer receives a data layer instance, following the dependency inversion principle. The code uses simple print statements to trace the flow, which makes it easy to see how a request propagates through the tiers. This separation allows you to replace or mock individual layers without affecting the others, improving testability and maintainability.
Common mistakes
- Passing the data layer directly to the presentation layer, skipping the business logic
- Hardcoding user data inside the business layer instead of keeping it in the data layer
- Letting the presentation layer access data objects directly, breaking the layer boundaries
- Not validating inputs at the business layer before accessing the data layer
Variations
- Use dependency injection frameworks or constructor injection to wire the layers
- Convert each layer to an abstract class or interface to enforce contracts
Real-world use cases
- Web frameworks like Flask or Django scripts use an MVC-style split where views (presentation), models (data), and controllers (business) are separate modules.
- Microservice backends separate API endpoints, domain services, and database repositories to allow independent testing and scaling.
- Desktop GUI apps separate UI logic from business rules and database access for cleaner maintenance and unit testing.
Sponsored
More from System design patterns
- Build a BFF (Backend for Frontend) Mock Aggregator in Python medium
- Builder pattern for mocking complex objects in Python easy
- Circuit Breaker Pattern in Python: Closed, Open, and Half-Open States medium
- Create a Data Helper Class in Python easy
- Domain Driven Design Aggregate Root Example in Python medium
- Facade Pattern in Python with Mock Simplification medium
Keep learning
Related tutorials and quizzes for this topic.