refactor: migrate to DDD architecture with Dishka DI
Complete architectural refactoring from simple MVC to Clean Architecture/DDD pattern: Domain Layer: - Add entities (Post, BaseEntity) with business logic - Add value objects (Title, Content, Slug) with validation - Add repository interfaces (PostRepository) - Add domain exceptions Application Layer: - Add use cases (CreatePost, GetPost, UpdatePost, DeletePost, ListPosts, PublishPost) - Add DTOs for data transfer - Add TransactionManager interface Infrastructure Layer: - Add SQLAlchemy models and async database connection - Add SQLAlchemyPostRepository implementation - Add Dishka DI container with providers - Add error handlers and middleware Presentation Layer: - Add FastAPI routes with Dishka integration - Add Pydantic schemas - Add dependency injection using FromDishka[T] Other Changes: - Remove old flat structure (api/, common/, core/, modules/) - Add hatchling build system for package scripts - Add blog CLI command - Update AGENTS.md with new architecture docs - All 48 tests passing, mypy clean, ruff clean
This commit is contained in:
134
AGENTS.md
134
AGENTS.md
@@ -2,6 +2,7 @@
|
||||
|
||||
## Stack
|
||||
- Python 3.13+, FastAPI, pydantic, uvicorn
|
||||
- SQLAlchemy 2.0 (async), aiosqlite
|
||||
- Package manager: `uv`
|
||||
- CI: Woodpecker (lint, test, type on push/PR to `dev`)
|
||||
|
||||
@@ -20,25 +21,124 @@ uv run blog # Start dev server (port 8000)
|
||||
## Pre-commit order
|
||||
`ruff check --fix` → `ruff format` → `isort` → `mypy`
|
||||
|
||||
## Architecture
|
||||
## DDD Architecture
|
||||
|
||||
### Layer Structure
|
||||
```
|
||||
app/
|
||||
main.py # Entry point, uvicorn.run(app_factory)
|
||||
core/config.py # Settings from .env via pydantic-settings
|
||||
core/exceptions.py
|
||||
common/error_handler.py
|
||||
api/v1/
|
||||
modules/
|
||||
├── domain/ # Domain Layer - business logic, no dependencies
|
||||
│ ├── entities/ # Domain entities (Post, User, etc.)
|
||||
│ │ ├── base.py # Base entity class
|
||||
│ │ └── post.py # Post entity with business logic
|
||||
│ ├── value_objects/ # Value objects (Title, Content, Slug)
|
||||
│ │ ├── base.py
|
||||
│ │ ├── title.py
|
||||
│ │ ├── content.py
|
||||
│ │ └── slug.py
|
||||
│ ├── repositories/ # Repository interfaces (abstract)
|
||||
│ │ ├── base.py
|
||||
│ │ └── post.py
|
||||
│ └── exceptions.py # Domain exceptions
|
||||
│
|
||||
├── application/ # Application Layer - use cases
|
||||
│ ├── dtos/ # Data Transfer Objects
|
||||
│ │ └── post.py
|
||||
│ ├── interfaces/ # Abstract interfaces (UoW)
|
||||
│ │ └── unit_of_work.py
|
||||
│ └── use_cases/ # Use cases (CQRS-like)
|
||||
│ ├── create_post.py
|
||||
│ ├── get_post.py
|
||||
│ ├── update_post.py
|
||||
│ ├── delete_post.py
|
||||
│ ├── list_posts.py
|
||||
│ └── publish_post.py
|
||||
│
|
||||
├── infrastructure/ # Infrastructure Layer - external concerns
|
||||
│ ├── config/ # Configuration
|
||||
│ │ └── settings.py
|
||||
│ ├── database/ # Database connection & ORM models
|
||||
│ │ ├── connection.py
|
||||
│ │ └── models.py
|
||||
│ ├── repositories/ # Repository implementations
|
||||
│ │ ├── post.py # SQLAlchemyPostRepository
|
||||
│ │ └── unit_of_work.py # SQLAlchemyUnitOfWork
|
||||
│ ├── di/ # Dependency Injection
|
||||
│ │ └── container.py
|
||||
│ └── middleware/ # Exception handlers
|
||||
│ └── error_handler.py
|
||||
│
|
||||
├── presentation/ # Presentation Layer - API
|
||||
│ ├── api/ # FastAPI routes
|
||||
│ │ ├── v1/ # API version 1
|
||||
│ │ │ ├── __init__.py
|
||||
│ │ │ └── posts.py # Posts endpoints
|
||||
│ │ ├── deps.py # FastAPI dependencies
|
||||
│ │ └── __init__.py
|
||||
│ └── schemas/ # Pydantic schemas
|
||||
│ └── post.py
|
||||
│
|
||||
└── main.py # Application entry point
|
||||
|
||||
tests/
|
||||
unit/
|
||||
integration/
|
||||
e2e/
|
||||
api/
|
||||
├── unit/ # Unit tests (domain, use cases)
|
||||
│ ├── domain/ # Domain layer tests
|
||||
│ ├── application/ # Application layer tests
|
||||
│ └── infrastructure/ # Infrastructure tests
|
||||
├── integration/ # Integration tests (DB, repos)
|
||||
├── api/ # API endpoint tests
|
||||
└── e2e/ # End-to-end tests
|
||||
```
|
||||
|
||||
## Key conventions
|
||||
- All commands use `uv run` prefix
|
||||
- pytest: asyncio_mode=auto, coverage on `app/`
|
||||
- mypy: strict=true with pydantic plugin
|
||||
- isort: black profile, filter_files=true
|
||||
- `.env` loaded by pydantic-settings (not in repo)
|
||||
## Key Conventions
|
||||
|
||||
### Dependency Rule
|
||||
- Domain layer has **NO dependencies** on other layers
|
||||
- Application layer depends only on Domain
|
||||
- Infrastructure depends on Domain and Application
|
||||
- Presentation depends on all other layers
|
||||
|
||||
### Testing
|
||||
- **Unit tests**: Test domain logic without DB/external services
|
||||
- **Integration tests**: Test repository implementations with real DB
|
||||
- **API tests**: Test endpoints with mocked use cases
|
||||
- **E2E tests**: Full workflow testing
|
||||
|
||||
### Code Patterns
|
||||
- Use **dataclasses** for entities and value objects
|
||||
- Use **frozen dataclasses** for value objects (immutable)
|
||||
- Use **Unit of Work** pattern for transactions
|
||||
- Use **Repository** pattern for data access
|
||||
- Use **Dependency Injection** via FastAPI's Depends()
|
||||
|
||||
## DDD Concepts Used
|
||||
|
||||
### Entities
|
||||
- Have identity (UUID)
|
||||
- Mutable state
|
||||
- Business logic methods (publish, update_title, etc.)
|
||||
- Example: `Post` entity
|
||||
|
||||
### Value Objects
|
||||
- Immutable
|
||||
- Defined by attributes
|
||||
- Validated on creation
|
||||
- Examples: `Title`, `Content`, `Slug`
|
||||
|
||||
### Aggregates & Repositories
|
||||
- `Post` is an aggregate root
|
||||
- `PostRepository` interface in Domain
|
||||
- `SQLAlchemyPostRepository` implementation in Infrastructure
|
||||
|
||||
### Domain Events
|
||||
- Placeholder for future implementation
|
||||
- Can be added via event bus in application layer
|
||||
|
||||
## Configuration
|
||||
- `.env` file loaded by pydantic-settings
|
||||
- Settings available via `app.infrastructure.config.settings`
|
||||
|
||||
## Database
|
||||
- SQLAlchemy 2.0 with async support
|
||||
- SQLite by default (aiosqlite)
|
||||
- Tables auto-created on startup
|
||||
- Use `init_db()` and `close_db()` in lifespan
|
||||
|
||||
Reference in New Issue
Block a user