home / skills / martinffx / claude-code-atelier / architecture

architecture skill

/plugins/atelier-python/skills/architecture

This skill helps you design Python applications with a functional core, layered architecture, and domain models to separate business logic from IO.

This is most likely a fork of the atelier-python-architecture skill from martinffx
npx playbooks add skill martinffx/claude-code-atelier --skill architecture

Review the files below or copy the command above to add this skill to your agents.

Files (4)
SKILL.md
5.1 KB
---
name: python:architecture
description: Python application architecture with functional core, effectful shell, DDD, and data modeling. Use when designing application layers, separating pure business logic from IO, defining domain models, implementing validation, or structuring bounded contexts.
user-invocable: false
---

# Python Application Architecture

Modern Python application architecture following functional core / imperative shell pattern, Domain-Driven Design, and type-safe data modeling.

## Core Principle: Functional Core / Imperative Shell

Separate pure business logic from side effects:

- **Functional Core**: Pure functions, business logic, no IO
- **Imperative Shell**: Coordinates external dependencies, handles side effects

See [references/functional-core.md](references/functional-core.md) for detailed patterns and examples.

## Layered Architecture

Follow bottom-up dependency flow:

```
Router/Handler → Service → Repository → Entity → Database
```

Each layer depends only on layers below.

**Responsibilities:**
- **Entity**: Domain models, validation, business rules, data transformations (fromRequest, toRecord, toResponse)
- **Repository**: Abstract storage interface, returns domain entities
- **Service**: Business workflows, orchestrates entities and repositories
- **Router/Handler**: HTTP handling, delegates to services

## Domain Models

### Entity Example

```python
from dataclasses import dataclass
from uuid import UUID
from decimal import Decimal

@dataclass
class Order:
    """Entity - has identity and encapsulated behavior"""
    id: UUID
    customer_id: UUID
    total: Decimal
    status: str

    def apply_discount(self, rate: Decimal) -> None:
        """Business rule - encapsulated in entity"""
        if self.status == "pending":
            self.total = self.total * (1 - rate)

    @classmethod
    def from_request(cls, req, customer_id: UUID) -> "Order":
        """Transform API request → entity"""
        return cls(id=uuid4(), customer_id=customer_id, total=Decimal("0"), status="pending")

    def to_response(self):
        """Transform entity → API response"""
        return {"id": self.id, "total": self.total, "status": self.status}
```

### Value Object Example

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class Money:
    """Value object - immutable, no identity"""
    amount: Decimal
    currency: str

    def add(self, other: "Money") -> "Money":
        if self.currency != other.currency:
            raise ValueError("Cannot add different currencies")
        return Money(self.amount + other.amount, self.currency)
```

See [references/ddd.md](references/ddd.md) for aggregates, bounded contexts, and domain services.

## Repository Pattern

Abstract storage behind interface:

```python
from abc import ABC, abstractmethod
from typing import Optional

class OrderRepository(ABC):
    """Abstract repository - interface only"""

    @abstractmethod
    def get(self, order_id: UUID) -> Optional[Order]:
        pass

    @abstractmethod
    def save(self, order: Order) -> None:
        pass

class PostgresOrderRepository(OrderRepository):
    """Concrete implementation"""

    def get(self, order_id: UUID) -> Optional[Order]:
        record = self.session.get(OrderRecord, order_id)
        return Order.from_record(record) if record else None

    def save(self, order: Order) -> None:
        record = order.to_record()
        self.session.merge(record)
        self.session.commit()
```

## Data Modeling

- **dataclasses**: Domain models and internal logic (lightweight, standard library)
- **Pydantic**: API boundaries (validation, JSON schema, OpenAPI)
- **Entity transformations**: `from_request()`, `to_response()`, `from_record()`, `to_record()`

See [references/data-modeling.md](references/data-modeling.md) for validation patterns, Pydantic features, and transformation examples.

## Best Practices

1. **Pure functions first** - Write business logic without IO dependencies
2. **Entity encapsulation** - Keep business rules inside entities
3. **Repository abstraction** - Hide storage details, work with domain entities
4. **Validate at boundaries** - Use Pydantic at API edges, simple validation in entities
5. **Immutable value objects** - Always use `frozen=True`
6. **Single Responsibility** - Each layer has one reason to change
7. **Dependency direction** - Always depend on abstractions, not implementations

## Anti-Patterns

❌ **Anemic Domain Model** - Entities with only getters/setters, all logic in services
❌ **Transaction Script** - All logic in service layer, entities just data
❌ **Leaky Abstraction** - Repository exposing database details
❌ **God Object** - Entity with too many responsibilities
❌ **Mixed Concerns** - Business logic calling IO directly

For detailed examples, patterns, and decision trees, see the reference materials:
- [references/functional-core.md](references/functional-core.md) - Core vs shell separation
- [references/ddd.md](references/ddd.md) - DDD patterns, aggregates, bounded contexts
- [references/data-modeling.md](references/data-modeling.md) - dataclasses, Pydantic, transformations

Overview

This skill teaches modern Python application architecture using a functional core / imperative shell pattern combined with Domain-Driven Design and type-safe data modeling. It shows how to separate pure business logic from IO, structure layered dependencies, and design entities, value objects, repositories, and service boundaries. The goal is maintainable, testable applications with clear validation and transformation points.

How this skill works

The skill inspects and codifies layer responsibilities: pure domain Entities and Value Objects, Repository interfaces for storage, Services for orchestration, and Router/Handler for IO and error handling. It prescribes using dataclasses for core domain models, Pydantic at API boundaries, and explicit transformation methods (from_request, to_record, to_response) to move data between layers. Emphasis is placed on keeping side effects in the imperative shell and pure functions in the core.

When to use it

  • Designing a new Python application that needs clear separation of concerns
  • Refactoring a codebase with mixed IO and business logic
  • Defining domain models, validation, and transformation strategies
  • Implementing repositories and database abstraction layers
  • Preparing code for rigorous testing and predictable business rules

Best practices

  • Put business logic into pure functions and methods on entities, not in IO code
  • Encapsulate behavior inside entities; use immutable value objects for simple data types
  • Expose storage via abstract repositories; keep implementations behind interfaces
  • Validate data at external boundaries with Pydantic and perform light domain validation inside entities
  • Keep dependency direction bottom-up: handlers → services → repositories → entities
  • Provide explicit transformation methods (from_request/to_record/to_response) for clarity and testability

Example use cases

  • Order processing service where Order entity enforces discounts and invariants, repositories handle persistence
  • API endpoint using Pydantic models for request validation then mapping to dataclass entities
  • A billing domain using Money value objects to prevent currency mixing and ensure immutability
  • Migrating a monolithic transaction script into services that orchestrate pure-domain functions
  • Implementing multi-bounded-context system with separate aggregates and repositories per context

FAQ

How do I test business logic under this architecture?

Test pure entity methods and functions directly with unit tests; mock or use in-memory repository implementations for service-level tests so IO remains isolated.

When should I use dataclasses vs Pydantic?

Use dataclasses for core domain models and internal logic for minimal runtime cost. Use Pydantic at API boundaries for validation, parsing, and OpenAPI/JSON-schema needs.