Notation
•
이탤릭은 더 큰 수준의 프로젝트에서 삭제되는 지침이다.
•
볼드는 이전 수준에는 없다가 새로 추가되는 지침이다.
토이 프로젝트
•
이 프로젝트는 도메인 주도 설계의 목적과 용어를 차용한 클린 아키텍처를 적용한다.
스캐폴드 및 가이드
•
root
◦
.git/
◦
myproject
▪
domain/
•
xxx_entity (예를 들어: user_entity)
•
xxx_vo (예를 들어: user_vo)
•
xxx_irepo (예를 들어: user_irepo) - xxx_repo 에서 구현될 인터페이스, 반환 타입은 도메인 객체
•
xxx_igateway (예를 들어: user_igateway) - xxx_gateway 에서 구현될 인터페이스, 반환 타입은 도메인 객체
•
xxx_exception (예를 들어: user_exception)
▪
application/
•
xxx_service (예를 들어: user_service, … )
▪
interface/
•
xxx_controller (예를 들어: user_controller, … ) - Router & Response Model
▪
infra/
•
db_models - ORM 프레임워크로 작성한 DB 모델
•
xxx_repo (예를 들어: user_repo, … )
•
xxx_gateway (예를 들어: user_gateway, … )
▪
utils/
•
…
▪
settings.py
▪
main.py
◦
README.md
◦
pyproject.toml
◦
…
•
루트 디렉토리에서 python3 -m myproject.main 으로 백엔드 애플리케이션을 실행하는 flat 구조다.
•
애플리케이션 계층에서는 인터페이스를 타입으로 받은 뒤, 나중에 인프라 등 구현체를 주입받는다.
•
utils/ 에는 다양한 애플리케이션에서 공통으로 사용하는 유틸성 모듈을 저장한다. 예를 들어 다음과 같은 작업을 하는 모듈이 포함될 수 있다.
◦
repository에서 ORM 프레임워크를 통해 얻은 값을 도메인 객체로 변환하는 일
▪
e.g. SQLAlchemy의 inspect() 함수를 사용하여 repository의 return을 도메인 객체화.
•
API Schema 객체
Domain 객체 변환은 API Schema에 정의하되, Request/Response Pydantic Schema의 from_attributes=True기능을 사용하지 말고, 프로퍼티 매핑을 나열하는 방식으로 작성한다.
•
xxx_repo는 repository 패턴에 따라 구현한다. 반환값은 도메인 객체여야 한다.
기술 선택
•
pydantic은 v2 이상을 사용한다.
•
패키지와 의존성은 pyproject.toml과 uv를 통해 관리한다.
•
ORM 프레임워크은 SQLAlchemy를 사용하고 autocommit을 사용한다.
◦
ORM 클래스를 작성할 때 __repr__에 id와 name을 담아 디버깅을 용이하게 만든다.
•
Database는 Supabase를 사용한다.
모범 사례
•
id 타입은 UUID 대신 ULID를 사용한다.
•
vo는 frozen=True로 설정하여 immutable하게 만든다.
•
FastAPI의 Depends는 Annotated 폼 - arg: Annotated[Type, Depends(dependable)] - 으로 적는다.
•
Repository 구현체의 각 메서드는 @with_db_session로 데코레이션하여, try-finally 세션을 열고 닫는 것(db.close) 및 커밋을 보장해야 한다.
def with_db_session(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
db = get_db_session()
try:
result = func(self, db, *args, **kwargs)
db.commit()
return result
except Exception:
db.rollback()
raise
finally:
db.close()
return wrapper
class ExampleUserRepository:
@with_db_session
def create_user(self, db: Session, user: User):
user_orm = UserORM(**user.dict())
db.add(user_orm) # commit은 데코레이터가 자동 처리
Python
복사
•
환경 변수는 .env 파일에 정의하고, Pydantic의 BaseSettings를 상속받아 관리한다.
코드 스타일
•
typing은 python 3.12 이상의 규칙을 따른다. (e.g. Union 대신 | 연산자 사용)
•
google style로 작성한다(함수 인자별 개행, 함수 간 2줄 간격 등).
•
그 어떤 경우에도 주석(docstring “””, inline ‘#’)은 작성하지 않는다.
•
테스트는 나와 상의하고 작성한다. 이때 다음 질문에 너가 생각하는 이유를 제시하며 나에게 확인을 받는다.
◦
이때 나에게 구성요소들 중 어떤 것을 생성하고, 어떤 것을 (의존성)주입할 것인지
◦
각각의 요소에 어떤 테스트 더블을 사용할 것인지
중형 프로젝트
•
이 프로젝트는 도메인 주도 설계의 목적과 용어를 차용한 클린 아키텍처를 적용한다.
스캐폴드 및 가이드
•
root
◦
.git/
◦
myproject
▪
domain/
•
폴더명/ (예를 들어: user, login, … )
◦
entity
◦
vo
◦
repository
▪
xxx_irepo (예를 들어: user_irepo, … ) - xxx_repo 에서 구현될 인터페이스, 반환 타입은 도메인 객체
◦
gateway
▪
xxx_igateway (예를 들어: user_igateway) - xxx_gateway 에서 구현될 인터페이스, 반환 타입은 도메인 객체
◦
exception
◦
aggregate - entity와 vo를 결합하여 정의한 도메인 객체들
▪
application/
•
xxx_service (예를 들어: user_service, … )
▪
interface/
•
controllers/
◦
xxx_controller (예를 들어: user_controller, … ) - Router & Response Model
•
mappers/ - ORM 모델
도메인 모델
◦
...
▪
infra/
•
logging/ - 로깅 세팅
•
db_models/ - ORM 프레임워크로 작성한 DB 모델
◦
xxx (예를 들어: user_model, … )
•
repository/
◦
xxx_repo (예를 들어: user_repo, … )
•
gateway/
◦
xxx_gateway (예를 들어: user_gateway, … )
▪
utils/
•
…
▪
common/
•
settings.py
◦
test/ - integration test만 작성, unit test는 테스트하고자 하는 파일과 최대한 나란히 작성
◦
README.md
◦
pyproject.toml
◦
…
•
루트 디렉토리에서 python3 -m myproject.main 으로 백엔드 애플리케이션을 실행하는 flat 구조다.
•
애플리케이션 계층에서는 인터페이스를 타입으로 받은 뒤, 나중에 인프라 등 구현체를 주입받는다.
•
utils/ 에는 다양한 애플리케이션에서 공통으로 사용하는 유틸성 모듈을 저장한다. 예를 들어 다음과 같은 작업을 하는 모듈이 포함될 수 있다.
◦
repository에서 ORM 프레임워크를 통해 얻은 값을 도메인 객체로 변환하는 일
▪
e.g. SQLAlchemy의 inspect() 함수를 사용하여 repository의 return을 도메인 객체화.
•
API Schema 객체
Domain 객체 변환은 API Schema에 정의하되, Request/Response Pydantic Schema의 from_attributes=True기능을 사용하지 말고, 프로퍼티 매핑을 나열하는 방식으로 작성한다.
•
xxx_repo는 repository 패턴에 따라 구현한다. 반환값은 도메인 객체여야 한다.
•
entity, vo를 결합한 로직은 aggregate로 분리한다.
기술 선택
•
pydantic은 v2 이상을 사용한다.
•
패키지와 의존성은 pyproject.toml과 uv를 통해 관리한다.
•
ORM 프레임워크은 SQLAlchemy를 사용하고 autocommit을 비활성화한다.
◦
ORM 클래스를 작성할 때 __repr__에 id와 name을 담아 디버깅을 용이하게 만든다.
◦
마이그레이션 도구는 Alembic을 사용한다.
•
Database는 Supabase를 사용한다.
•
FastAPI 의존성 주입 모듈(Depends)은 controllers/ 이외 레이어에서 사용하지 않도록 한다.
•
의존성 주입 시 dependency-injector의 IoC 컨테이너를 사용한다(Depends + Provide).
◦
IoC 컨테이너에서는 주입하고자 하는 클래스의 작동 방식을 지켜보고 Factory, Singleton 등 적절한 프로바이더를 선택하여 사용한다.
◦
이때, 하위에도 @inject 데코레이션하여 의존성이 주입됨을 정확히 알린다.
•
테스트 프레임워크는 pytest를 사용한다.
모범 사례
•
id 타입은 UUID 대신 ULID를 사용한다.
•
vo는 frozen=True로 설정하여 immutable하게 만든다.
•
FastAPI의 Depends는 Annotated 폼 - arg: Annotated[Type, Depends(dependable)] - 으로 적는다.
•
Repository 구현체의 각 메서드는 @with_db_session로 데코레이션하여, try-finally 세션을 열고 닫는 것(db.close) 및 커밋을 보장해야 한다.
def with_db_session(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
db = get_db_session()
try:
result = func(self, db, *args, **kwargs)
db.commit()
return result
except Exception:
db.rollback()
raise
finally:
db.close()
return wrapper
class ExampleUserRepository:
@with_db_session
def create_user(self, db: Session, user: User):
user_orm = UserORM(**user.dict())
db.add(user_orm) # commit은 데코레이터가 자동 처리
Python
복사
•
환경 변수는 .env 파일에 정의하고, Pydantic의 BaseSettings를 상속받아 관리한다.
코드 스타일
•
typing은 python 3.12 이상의 규칙을 따른다. (e.g. Union 대신 | 연산자 사용)
•
google style로 작성한다(함수 인자별 개행, 함수 간 2줄 간격 등).
•
그 어떤 경우에도 주석(docstring “””, inline ‘#’)은 작성하지 않는다.
•
테스트는 나와 상의하고 작성한다. 이때 다음 질문에 너가 생각하는 이유를 제시하며 나에게 확인을 받는다.
◦
이때 나에게 구성요소들 중 어떤 것을 생성하고, 어떤 것을 (의존성)주입할 것인지
◦
각각의 요소에 어떤 테스트 더블을 사용할 것인지
대형 프로젝트
•
이 프로젝트는 도메인 주도 설계의 목적과 용어를 차용한 클린 아키텍처를 적용한다.
스캐폴드 및 가이드
•
root
◦
.git/
◦
myproject
▪
bounded-context이름/ (예를 들어: user, …)
•
domain/
◦
폴더명/
▪
entity
▪
vo
▪
repository
•
xxx_irepo (예를 들어: user_irepo, … ) - xxx_repo 에서 구현될 인터페이스, 반환 타입은 도메인 객체
▪
gateway
•
xxx_igateway (예를 들어: user_igateway) - xxx_gateway 에서 구현될 인터페이스, 반환 타입은 도메인 객체
▪
exception
▪
aggregate - entity와 vo를 결합하여 정의한 도메인 객체들
•
application/
◦
xxx_service (예를 들어: user_service, … )
•
interface/
◦
controllers/
▪
xxx_controller (예를 들어: user_controller, … ) - Router & Response Model
◦
mappers/ - ORM 모델
도메인 모델
▪
…
•
infra/
◦
logging/ - 로깅 세팅
◦
db_models/ - ORM 프레임워크로 작성한 DB 모델
▪
xxx (예를 들어: user_model, … )
◦
repository/
▪
xxx_repo (예를 들어: user_repo, … )
◦
gateway/
▪
xxx_gateway (예를 들어: user_gateway, … )
•
utils/
◦
…
•
common/
◦
settings.py
◦
…
▪
bounded-context이름/ (예를 들어: login, …)
•
위의 user bounded context와 동일한 구조
▪
utils/
▪
common/ - 다중 bounded-context간에 재사용되는 스크립트나 간단한 도구 등
•
settings.py
•
…
◦
test/ - integration test만 작성, unit test는 테스트하고자 하는 파일과 최대한 나란히 작성
◦
README.md
◦
pyproject.toml
◦
…
•
루트 디렉토리에서 python3 -m myproject.main 으로 백엔드 애플리케이션을 실행하는 flat 구조다.
•
애플리케이션 계층에서는 인터페이스를 타입으로 받은 뒤, 나중에 인프라 등 구현체를 주입받는다.
•
utils/ 에는 다양한 애플리케이션에서 공통으로 사용하는 유틸성 모듈을 저장한다.
•
API Schema 객체
Domain 객체 변환은 API Schema에 정의하되, Request/Response Pydantic Schema의 from_attributes=True기능을 사용하지 말고, 프로퍼티 매핑을 나열하는 방식으로 작성한다.
•
xxx_repo는 repository 패턴에 따라 구현한다. 반환값은 도메인 객체여야 한다.
◦
복잡한 로직이 포함된 변환의 경우에는 Mapper을 사용한다.
기술 선택
•
pydantic은 v2 이상을 사용한다.
•
패키지와 의존성은 pyproject.toml과 uv를 통해 관리한다.
•
ORM 프레임워크은 SQLAlchemy를 사용하고 autocommit을 비활성화한다.
◦
ORM 클래스를 작성할 때 __repr__에 id와 name을 담아 디버깅을 용이하게 만든다.
◦
마이그레이션 도구는 Alembic을 사용한다.
•
Database는 Supabase를 사용한다.
•
FastAPI 의존성 주입 모듈(Depends)은 controllers/ 이외 레이어에서 사용하지 않도록 한다.
•
의존성 주입 시 dependency-injector의 IoC 컨테이너를 사용한다(Depends + Provide).
◦
IoC 컨테이너에서는 주입하고자 하는 클래스의 작동 방식을 지켜보고 Factory, Singleton 등 적절한 프로바이더를 선택하여 사용한다.
◦
이때, 하위에도 @inject 데코레이션하여 의존성이 주입됨을 정확히 알린다.
•
테스트 프레임워크는 pytest를 사용한다.
모범 사례
•
id 타입은 UUID 대신 ULID를 사용한다.
•
vo는 frozen=True로 설정하여 immutable하게 만든다.
•
도메인 레이어에서는 Pydantic을 사용하지 않는다.
•
FastAPI의 Depends는 Annotated 폼 - arg: Annotated[Type, Depends(dependable)] - 으로 적는다.
•
Repository 구현체의 각 메서드에서는 IoC를 통해 Context 클래스를 주입받아 try-finally 세션을 열고 닫는 것(db.close) 및 커밋을 은닉하면서도, Context를 쉽게 교체할 수 있게 하여 테스트를 용이하게 한다.
from contextlib import contextmanager
from typing import Generator
class DatabaseContext:
def __init__(self, session_factory):
self.session_factory = session_factory
@contextmanager
def get_session(self) -> Generator[Session, None, None]:
session = self.session_factory()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
class Container(containers.DeclarativeContainer):
db_engine = providers.Singleton(
create_engine,
settings.database_url
)
db_session_factory = providers.Factory(
sessionmaker,
autocommit=False,
autoflush=False,
bind=db_engine
)
db_context = providers.Factory(
DatabaseContext,
session_factory=db_session_factory
)
user_repository = providers.Factory(
SQLAlchemyUserRepository,
db_context=db_context
)
class SQLAlchemyUserRepository(UserRepositoryInterface):
def __init__(self, db_context: DatabaseContext):
self.db_context = db_context
def create_user(self, user_data: UserCreate) -> User:
with self.db_context.get_session() as db:
user = User(**user_data.dict())
db.add(user)
return user
def get_by_email(self, email: str) -> Optional[User]:
with self.db_context.get_session() as db:
return db.query(User).filter(User.email == email).first()
Python
복사
•
환경 변수는 .env 파일에 정의하고, Pydantic의 BaseSettings를 상속받아 관리한다.
코드 스타일
•
typing은 python 3.12 이상의 규칙을 따른다. (e.g. Union 대신 | 연산자 사용)
•
google style로 작성한다(함수 인자별 개행, 함수 간 2줄 간격 등).
•
그 어떤 경우에도 주석(docstring “””, inline ‘#’)은 작성하지 않는다.
•
테스트는 나와 상의하고 작성한다. 이때 다음 질문에 너가 생각하는 이유를 제시하며 나에게 확인을 받는다.
◦
이때 나에게 구성요소들 중 어떤 것을 생성하고, 어떤 것을 (의존성)주입할 것인지
◦
각각의 요소에 어떤 테스트 더블을 사용할 것인지
parse me : 언젠가 이 글에 쓰이면 좋을 것 같은 재료을 보관해 두는 영역입니다.
from : 과거의 어떤 원자적 생각이 이 생각을 만들었는지 연결하고 설명합니다.
1.
•
앞의 글에서는 이 프롬프트가 프로그램 설계의 어느 단계에 사용될 수 있는지가 나타나 있다.
2.
•
이미 사람들이 널리 사용하며 잘 정의된 용어로 작성된 프롬프트는 강력하다.
3.
•
앞의 글은 이 글에서 다루는 프롬프트가 추구하는 개념적 배경을 다룬다.
4.
•
앞의 글에는 Repository, Gateway, DTO, Mapper의 개념과 점진적 복잡성 추가와 관련된 내용이 작성되어 있다.
5.
•
앞의 글은 “Repository 구현체의 각 메서드는 @with_db_session로 데코레이션하여, try-finally 세션을 열고 닫는 것(db.close) 및 커밋을 보장해야 한다”는 아이디어를 담고 있다.
6.
•
앞의 글은 “Repository 구현체의 각 메서드에서는 IoC를 통해 Context 클래스를 주입받아 try-finally 세션을 열고 닫는 것(db.close) 및 커밋을 은닉하면서도, Context를 쉽게 교체할 수 있게 하여 테스트를 용이하게 한다”는 아이디어를 담고 있다.
7.
•
앞의 글은 pytest가 unittest보다 나은 여러 사례들 중 하나를 언급한다.
supplementary : 어떤 새로운 생각이 이 문서에 작성된 생각을 뒷받침하는지 연결합니다.
1.
None
opposite : 어떤 새로운 생각이 이 문서에 작성된 생각과 대조되는지 연결합니다.
1.
None
to : 이 문서에 작성된 생각이 어떤 생각으로 발전되거나 이어지는지를 작성하는 영역입니다.
1.
None
ref : 생각에 참고한 자료입니다.