Use Simple Data Structures Across Boundaries

Data crossing architectural boundaries should be simple, isolated data structures. Never pass entities, database rows, or framework objects across boundaries.

Incorrect (entity crosses boundary):

# domain/entities/user.py
class User:
    def __init__(self, id, email, password_hash, created_at):
        self.id = id
        self.email = email
        self.password_hash = password_hash  # Sensitive data
        self.created_at = created_at

# interface_adapters/controllers/user_controller.py
class UserController:
    def get_user(self, user_id):
        user = self.use_case.get_user(user_id)
        return jsonify(user.__dict__)  # Entity exposed to HTTP layer, leaks password_hash

Correct (DTOs cross boundaries):

# application/dto/user_response.py
@dataclass
class UserResponse:
    id: str
    email: str
    member_since: str  # Formatted for presentation

# application/usecases/get_user.py
class GetUserUseCase:
    def execute(self, user_id: str) -> UserResponse:
        user = self.repository.find_by_id(user_id)
        return UserResponse(
            id=user.id,
            email=user.email,
            member_since=user.created_at.strftime("%B %Y")
        )

# interface_adapters/controllers/user_controller.py
class UserController:
    def get_user(self, user_id):
        response = self.use_case.execute(user_id)
        return jsonify(asdict(response))  # Only safe, formatted data

When NOT to use this pattern:

  • Within the same architectural layer, entities can flow freely
  • Performance-critical paths may need optimized data transfer

Reference: Clean Architecture - Crossing Boundaries