Clean Architecture理解困惑:用户数据持久化场景技术问询
Hey there! Let's walk through this user input persistence scenario step by step—since you already grasp the core Clean Architecture rule (inner layers know nothing about outer ones, only interact via boundary interfaces), we'll map this directly to that principle to clear up your confusion.
First, let's recap the Clean Architecture layers from innermost to outermost: Entities → Use Cases → Interface Adapters → Frameworks & Drivers. Each layer only depends on the layer directly inside it, or on interfaces defined in inner layers.
Mapping Your Scenario to the Layers
Let's break down exactly how a user input (like a web form or console entry) flows to persistence while following the rules:
1. Innermost: Entities Layer
This is your core business domain—no external dependencies allowed. For your scenario, this would be something like a User entity, which holds only essential business properties and rules (e.g., email validation). It has zero knowledge of UI, databases, or any outer layer.
2. Next: Use Cases Layer
This is where you define your business use cases (like "create a user and save them") and the boundary interfaces that outer layers will implement.
The key here: you define an interface (let's call it UserRepository) in this inner layer with a method like save(User). The Use Case depends on this interface, but has no clue how it's implemented (that's for outer layers to handle). This keeps the inner layer decoupled from storage details.
3. Middle: Interface Adapters Layer
This layer acts as the "translator" between outer layers and inner layers:
- It takes raw input from the UI (like form data or console strings) and converts it into a format the Use Case understands (e.g., mapping form fields to a
Userentity or use case input DTO). - It implements the
UserRepositoryinterface defined in the Use Cases layer—here's where you write the actual persistence logic (e.g., SQL queries, MongoDB operations).
4. Outermost: Frameworks & Drivers Layer
This is your UI (web form, console) and any external tools (database drivers, web frameworks). The UI triggers an event (like a form submission), which calls a controller in the Interface Adapters layer to handle the flow.
Example Code Walkthrough (Python)
Let's make this concrete with simple, readable code:
Entities Layer
class User: def __init__(self, user_id: str, name: str, email: str): # Core business rule: validate email format if "@" not in email: raise ValueError("Invalid email address") self.user_id = user_id self.name = name self.email = email
Use Cases Layer
from abc import ABC, abstractmethod from entities.user import User # Boundary interface DEFINED IN THE INNER LAYER class UserRepository(ABC): @abstractmethod def save(self, user: User) -> None: pass # The actual use case, depends only on the interface class CreateUserUseCase: def __init__(self, user_repository: UserRepository): # Dependency injection: we get the interface implementation from outside self.user_repository = user_repository def execute(self, user_input: dict) -> User: # Business logic: generate ID, create valid User entity new_user = User( user_id=f"user_{hash(user_input['email'])}", name=user_input["name"], email=user_input["email"] ) # Call the boundary interface—we don't care how save works self.user_repository.save(new_user) return new_user
Interface Adapters Layer
from use_cases.user_repository import UserRepository from entities.user import User import sqlite3 # Implement the inner-layer interface with actual storage logic class SqlUserRepository(UserRepository): def save(self, user: User) -> None: conn = sqlite3.connect("app_db.db") cursor = conn.cursor() cursor.execute( "INSERT INTO users (id, name, email) VALUES (?, ?, ?)", (user.user_id, user.name, user.email) ) conn.commit() conn.close() # Controller to handle UI input and call the use case from use_cases.create_user_use_case import CreateUserUseCase class UserController: def __init__(self): # Inject the concrete repository into the use case self.create_user_use_case = CreateUserUseCase(SqlUserRepository()) def handle_submission(self, form_data: dict) -> str: try: created_user = self.create_user_use_case.execute(form_data) return f"Success! Created user: {created_user.name}" except ValueError as e: return f"Error: {str(e)}"
Frameworks & Drivers Layer (Console UI)
from interface_adapters.user_controller import UserController def run_console_ui(): controller = UserController() print("Create a new user:") name = input("Enter name: ") email = input("Enter email: ") result = controller.handle_submission({"name": name, "email": email}) print(result) if __name__ == "__main__": run_console_ui()
Key Takeaways
- The boundary interface (
UserRepository) is the critical link: it's defined in the inner Use Cases layer, so the inner layer never depends on outer-layer code. - The flow stays true to the rule: inner layers never know about outer layers—the Use Case only calls an interface, and the actual persistence logic is implemented in an outer layer that depends on the inner interface.
- If you later switch from SQL to MongoDB, or from a console UI to a web form, you only need to change the outer layers—your core business logic (Entities and Use Cases) stays untouched.
内容的提问来源于stack exchange,提问作者Thieri

