Search
🌍

클린 아키텍처를 유지하며 FastAPI 코드 작성을 LLM에게 위임하기 위한 프롬프트

🚀 prev note
♻️ prev note
a0_4.1_1. [entry] title: 생성형 AI와 포멀하고 딱딱한 소프트웨어공학의 조합
3_3_8.1. title: LLM 시대에 디자인패턴과 아키텍처에 익숙해진다는 것은 LLM과 공유하는 유비쿼터스 언어를 가지는 것이다.
3_3_8.1.1. title: 클린 아키텍처는 소프트웨어가 풀고자 하는 문제를 기술 스택에서 보호하기 위해 고안된 계층(layered) 아키텍처이다. 이 아키텍처는 도메인 주도 설계(DDD)라는 또다른 개념과 호환성이 좋다.
3_3_8.1.1.1. title: 인터페이스 계층은 외부에서 내부에 접근하는 방법, 내부에서 외부에 접근하는 방법을 관장한다. Repository와 Gateway는 접근 방법을 분리하는 개념이고, DTO와 Mapper은 데이터 변환을 분리하는 개념이다. ORM을 활용하면 DTO와 Mapper의 복잡성을 최소화할 수 있다.
🚀 next note
♻️ next note
관련 임시노트
15 more properties
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__idname을 담아 디버깅을 용이하게 만든다.
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__idname을 담아 디버깅을 용이하게 만든다.
마이그레이션 도구는 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__idname을 담아 디버깅을 용이하게 만든다.
마이그레이션 도구는 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.
이미 사람들이 널리 사용하며 잘 정의된 용어로 작성된 프롬프트는 강력하다.
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 : 생각에 참고한 자료입니다.