Files
cleveragents-core/docs/architecture/decisions/ADR-007-repository-pattern.md

2.6 KiB

ADR-007: Repository Pattern for Persistence

Status

Accepted

Context

The discovery phase identified complex persistence needs:

  • 122 data structures to persist
  • Multiple storage backends (PostgreSQL, MySQL, SQLite, in-memory)
  • Git-based diff storage
  • Need for testability with mock repositories
  • Transaction management across operations

Decision

We will implement the Repository pattern with abstract interfaces in the domain layer and concrete implementations in the infrastructure layer.

Domain Interface

# cleveragents.domain.repositories
from abc import ABC, abstractmethod
from typing import Optional, List

class PlanRepository(ABC):
    @abstractmethod
    async def create(self, plan: Plan) -> Plan:
        pass
    
    @abstractmethod
    async def get(self, plan_id: str) -> Optional[Plan]:
        pass
    
    @abstractmethod
    async def update(self, plan: Plan) -> Plan:
        pass
    
    @abstractmethod
    async def delete(self, plan_id: str) -> bool:
        pass
    
    @abstractmethod
    async def list_by_project(self, project_id: str) -> List[Plan]:
        pass

Infrastructure Implementation

# cleveragents.infrastructure.repositories
from sqlalchemy.ext.asyncio import AsyncSession

class SQLAlchemyPlanRepository(PlanRepository):
    def __init__(self, session: AsyncSession):
        self.session = session
    
    async def create(self, plan: Plan) -> Plan:
        db_plan = PlanModel.from_domain(plan)
        self.session.add(db_plan)
        await self.session.commit()
        return db_plan.to_domain()

Unit of Work Pattern

class UnitOfWork:
    def __init__(self, session_factory):
        self.session_factory = session_factory
    
    async def __aenter__(self):
        self.session = self.session_factory()
        self.plans = SQLAlchemyPlanRepository(self.session)
        self.contexts = SQLAlchemyContextRepository(self.session)
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            await self.rollback()
        await self.session.close()
    
    async def commit(self):
        await self.session.commit()
    
    async def rollback(self):
        await self.session.rollback()

Consequences

Positive

  • Clean separation between domain and persistence
  • Easy to test with in-memory implementations
  • Supports multiple storage backends
  • Transaction boundaries are explicit

Negative

  • More boilerplate code
  • Potential for anemic domain models
  • Mapping overhead between layers

References

  • Martin Fowler's Repository Pattern
  • Domain-Driven Design by Eric Evans