
생성 수정
IBM * 레드햇 AX 과정 · 12강
Pydantic과 복사 — 코드에서 설계 원칙 검증하기
2026년 6월 1일(월) ~ 6월 5일(금)
7. Pydantic 2.0 — 문지기를 만드는 도구
7.1 왜 Pydantic인가
Pydantic은 파이썬의 타입 힌트(Type Hints)를 기반으로 작동하는 데이터 검증 및 설정 관리 라이브러리입니다.
파이썬의 타입 힌트는 원래 주석입니다. 실행에 아무 영향이 없습니다.
def f(age: int): ...
f("스물") # 파이썬은 아무 말도 하지 않는다
Pydantic은 이 주석을 진짜 규칙으로 승격시킵니다. 그래서 5.3의 "문지기(Schema)"를 만드는 도구가 됩니다.
Pydantic 2.0의 가장 큰 변화는 성능입니다.
핵심 검증 엔진을 러스트(Rust) 기반의 pydantic-core 로 재작성하여 성능이 5~50배 대폭 향상되었습니다.
경계마다 검증을 거는 아키텍처에서는 이 속도가 곧 서비스의 응답 속도입니다.
7.2 2.0의 네 가지 모델
| 모델 | 언제 쓰는가 | 비고 |
|---|---|---|
BaseModel | Key-Value 쌍의 딕셔너리({}) 형태 데이터를 정의하고 검증 | 가장 표준적인 클래스 |
RootModel | 데이터의 최상위(Root) 구조가 객체가 아니라 단일 값이나 리스트([]) 일 때 감싸기 위해 사용 | 2.0 신규. 1.x의 __root__ 설정을 대체 |
BaseSettings | 시스템 환경변수나 .env 파일을 로드하고 검증 | 2.0부터 코어에서 제외 → pydantic-settings 별도 설치 |
TypeAdapter | 별도의 클래스 정의 없이 임의의 파이썬 타입을 즉석에서 검증 | 2.0 신규 |
from pydantic import BaseModel, RootModel, TypeAdapter, ConfigDict
class PassengerSchema(BaseModel): # ① 표준 — 딕셔너리 형태
passenger_id: str
name: str
gender: str
class PassengerList(RootModel[list[PassengerSchema]]): # ② 최상위가 리스트일 때
pass
adapter = TypeAdapter(list[int]) # ④ 클래스 없이 즉석 검증
adapter.validate_python([1, 2, 3])
⚠️ BaseSettings 는 2.0에서 pip install pydantic-settings 가 따로 필요합니다.
1.x 코드를 그대로 옮기면 ImportError 가 납니다. 이 장의 실습에서 .env 를 읽을 때 만나는 첫 번째 함정입니다.
7.3 클래스 레벨 변경점 두 가지
① Config 클래스 → model_config = ConfigDict(...)
기존 class Config: 하위 클래스 선언 방식 대신, model_config = ConfigDict(...) 라는 딕셔너리 형태의 속성을 지정하는 방식으로 전면 변경되었습니다.
# ❌ Pydantic 1.x
class PassengerSchema(BaseModel):
passenger_id: str
class Config:
frozen = True
populate_by_name = True
# ✅ Pydantic 2.0
class PassengerSchema(BaseModel):
model_config = ConfigDict(frozen=True, populate_by_name=True)
passenger_id: str
② 네이밍 컨벤션 통일 — model_ 접두사
기존 기본 메서드들이 파이썬 내장 명칭과 충돌하는 것을 방지하기 위해 model_ 접두사로 통일되었습니다.
| Pydantic 1.x | Pydantic 2.0 | 하는 일 |
|---|---|---|
.dict() | .model_dump() | 모델 → 딕셔너리 |
.json() | .model_dump_json() | 모델 → JSON 문자열 |
.parse_obj() | .model_validate() | 딕셔너리 → 모델 (검증 포함) |
.parse_raw() | .model_validate_json() | JSON 문자열 → 모델 |
.copy() | .model_copy() | 모델 복제 |
class Config | model_config = ConfigDict(...) | 설정 |
⭐
.dict()가 왜 문제였는가. 파이썬에는 이미dict라는 내장 타입이 있습니다. 모델에dict라는 필드를 만들고 싶으면 메서드와 이름이 충돌합니다.model_접두사를 붙이면 "이건 Pydantic의 것" 이라는 네임스페이스가 생겨 충돌이 사라집니다. 8장의.model_copy()도 같은 규칙입니다.
7.4 Schema를 어디에 두는가
이 수업의 폴더 규칙입니다.
adapter/inbound/api/
├── v1/ ← 라우터 (Inbound Adapter)
│ ├── james_router.py
│ ├── rose_router.py
│ └── walter_router.py
└── schemas/ ← 스키마 (경계 검증)
├── james_router_schema.py
├── rose_router_schema.py
└── walter_router_schema.py
⭐ Schema는 app/ 이 아니라 adapter/inbound/ 아래에 있습니다. 이게 핵심입니다.
Schema는 바깥세상(HTTP·JSON)의 형식을 아는 물건이므로 바깥쪽 계층에 있어야 합니다.
핵심(app/use_cases/)이 Pydantic을 알게 되는 순간, "프레임워크에 의존하지 않는 순수한 코어"라는 전제가 무너집니다.
8. Clone과 Copy — 같은 단어, 두 세계
3주차에서 Git 관점으로 한 번 다뤘습니다. 여기서는 프로그래밍 관점을 붙여 완성합니다. 두 관점 모두 축이 하나입니다: "딸려오는가, 안 딸려오는가."
8.1 프로그래밍 / 메모리 관점 — 객체 복제
카피 (Copy / 얕은 복사 — Shallow Copy)
- 객체의 가장 바깥쪽 껍데기만 새로 생성하고, 내부의 참조형 데이터(알맹이)는 원본의 메모리 주소를 그대로 가리킵니다.
- 복사본 내부의 중첩 객체를 수정하면 원본 데이터도 함께 변경되는 부작용이 있을 수 있습니다.
- 파이썬 예시:
copy.copy(),list.copy(), 슬라이싱([:])
클론 (Clone / 깊은 복사 — Deep Copy)
- 원본 객체뿐만 아니라 그 객체가 참조하는 하위의 모든 중첩 객체들까지 메모리상에 전부 새로 복제합니다.
- 원본과 복사본이 완전히 독립되므로 어느 한쪽을 수정해도 서로 영향을 주지 않습니다.
- 파이썬 예시:
copy.deepcopy()
import copy
original = {"person": {"name": "Walter"}, "fare": "7.25"}
shallow = copy.copy(original)
shallow["person"]["name"] = "James"
print(original["person"]["name"]) # James ← 원본이 바뀌었다 ⚠️
deep = copy.deepcopy(original)
deep["person"]["name"] = "Rose"
print(original["person"]["name"]) # James ← 원본은 그대로 ✅
⭐ 이게 DTO에
frozen=True를 거는 이유와 직접 연결됩니다. 얕은 복사로 넘긴 DTO를 Repository가 수정하면, Interactor가 들고 있는 원본도 같이 바뀝니다. 어디서 값이 바뀌었는지 추적이 불가능해집니다. 불변으로 만들면 이 사고 자체가 일어나지 않습니다.
8.2 깃(Git) / 버전 관리 관점
카피 (Copy / Download ZIP)
- 원격 저장소(GitHub 등)의 특정 시점 소스 코드 파일들만 압축 파일 형태로 다운로드하는 행위입니다.
- 버전 관리 이력인
.git폴더가 포함되지 않으므로, 수정 내역 추적이나 원격 저장소로의push가 불가능합니다.
클론 (Git Clone)
- 원격 저장소의 모든 소스 코드는 물론, 과거 커밋(Commit) 히스토리, 브랜치 정보 등 저장소 전체를 통째로 내 컴퓨터로 복제해 오는 명령어입니다.
- 로컬 저장소와 원격 저장소가 동기화 및 연결되어 변경 사항 추적이 가능하며 본격적인 협업과
push가 가능합니다.
8.3 두 관점을 한 축으로
| Copy | Clone | |
|---|---|---|
| 축 | 껍데기만 | 속까지 전부 |
| 메모리 관점 | 얕은 복사 — 내부는 원본과 공유 | 깊은 복사 — 내부까지 전부 새것 |
| 파이썬 | copy.copy(), [:] | copy.deepcopy() |
| Git 관점 | Download ZIP — .git 없음 | git clone — 히스토리 전부 |
| 결과 | 원본과 얽혀 있음 | 원본과 독립 |
| 위험 | 한쪽을 고치면 다른 쪽도 바뀜 | 없음 (대신 비용이 큼) |
💡 왜 항상 깊은 복사를 쓰지 않는가: 비쌉니다. 중첩이 깊으면 전부 새로 만들어야 하므로 메모리와 시간이 그만큼 듭니다. 3주차의 연관관계/의존관계와 같은 판단입니다. 둘 중 무엇이 좋고 나쁜 것이 아니라, 필요한 곳에 맞게 쓰는 것입니다.
9. 실전 — DIP·OCP 위반 한 줄 해부 ⭐
이 장의 결론입니다. 지금까지 배운 모든 것이 한 줄의 코드 위에서 만납니다.
9.1 문제의 코드
# 라우터(인바운드 어댑터) 내부 코드
walter: WalterRoasterUseCase = WalterRoasterInteractor()
walter.introduce_myself(schema)
1.5절에서 "의도는 좋다"고 했던 그 표기입니다. 실제로 돌아갑니다. 로그도 잘 찍힙니다. 그런데 이 코드는 SOLID를 두 개 위반하고, 헥사고날의 경계를 하나 무너뜨립니다.
9.2 위반 ① — DIP (의존성 역전 원칙, Dependency Inversion Principle)
- 원칙: 고수준 모듈(Router)은 저수준 모듈(Interactor)에 의존해서는 안 되며, 둘 다 추상화에 의존해야 합니다.
- 문제점: 코드 좌변에는
WalterRoasterUseCase라는 추상(설명서) 을 바라보도록 선언했으나, 우변에서WalterRoasterInteractor()라는 구상(실제 제품) 을 직접 생성(인스턴스화) 하고 있습니다. 결과적으로 라우터가 구체적인 비즈니스 로직 구현체에 강하게 결합되었습니다.
walter: WalterRoasterUseCase = WalterRoasterInteractor()
# ✅ 추상을 본다 ❌ 구상을 직접 만든다
왼손으로는 설명서를 들고, 오른손으로는 공장을 돌리고 있는 상태입니다.
타입 힌트를 아무리 추상으로 써도, import 문에 구현체 이름이 들어가는 순간 라우터는 구현체를 아는 것입니다.
from app.use_cases.walter_roaster_interactor import WalterRoasterInteractor
# ↑ 이 import 한 줄이 이미 결합이다
5주차에서 정정한 문장이 여기서 쓰입니다. DIP는 Dependency Injection이 아니라 Dependency Inversion(의존 역전)입니다. Injection은 기법이고, Inversion은 원칙입니다. 원칙을 지키기 위한 기법이 주입입니다.
9.3 위반 ② — OCP (개방-폐쇄 원칙, Open-Closed Principle)
- 원칙: 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 합니다. 기존 코드를 수정하지 않고 기능을 확장할 수 있어야 합니다.
- 문제점: 향후 로직이 개선된
NewWalterRoasterInteractor나 테스트를 위한MockWalterRoasterInteractor로 변경해야 할 때, 외부에서 교체할 수 없습니다. 라우터 소스 코드를 직접 열어서 우변의 생성자 코드를 수정해야 하므로 OCP를 위배합니다.
실제로 무슨 일이 벌어지는가
| 상황 | 이 코드에서는 | 올바른 구조에서는 |
|---|---|---|
| 로직 개선판으로 교체 | 라우터 파일을 연다 | 조립소 한 줄만 바꾼다 |
| 테스트용 가짜로 교체 | 라우터 파일을 연다 | 테스트가 가짜를 주입한다 |
| A/B 테스트로 두 구현 병행 | 불가능 | 조건에 따라 다른 것을 주입 |
| 구현체 이름 변경 | 라우터가 깨진다 | 라우터는 아무 영향 없음 |
⚠️ "라우터 파일을 연다"가 왜 문제인가. 라우터에는 URL, 상태 코드, 인증 같은 전혀 다른 관심사가 함께 들어 있습니다. 비즈니스 구현체를 바꾸려고 그 파일을 여는 순간, 관계없는 코드를 실수로 건드릴 위험이 생깁니다. AI에게 맡길 때는 이 위험이 더 커집니다. 결합도가 높으면 AI가 엉뚱한 곳을 건드립니다.
9.4 위반 ③ — 헥사고날 아키텍처 레이어 경계 붕괴
- 인바운드 어댑터(Router)는 코어 비즈니스 영역의 인터페이스(UseCase) 규격만 알아야 합니다.
- 내부 구현체가 무엇인지 어댑터가 직접 지정하고 제어하는 순간 레이어 간의 독립성과 경계가 무너집니다.
2.2의 매핑표로 돌아가 봅시다.
[ Inbound Adapter ] ─▶ ( Inbound Port ) ─▶ [ Use Case ]
라우터 여기까지만 알아야 함 여기를 알면 안 됨
라우터가 WalterRoasterInteractor 를 아는 순간, 화살표 하나를 건너뛴 것입니다.
점선(추상)을 통과하지 않고 실선(구상)으로 직행했습니다. 문틀을 놔두고 벽을 뚫은 셈입니다.
9.5 올바른 해결 방향 — 의존성 주입 (Dependency Injection)
문제를 해결하려면 라우터가 직접 객체를 생성하여 가져오게 만들지 말고, 외부에서 주입받도록 제어권을 넘겨야 합니다.
세 단계로 읽으면 됩니다.
- 추상화 유지 — 라우터는 오직 설명서(
WalterRoasterUseCase) 규격만 쥐고 대기합니다. - 의존성 주입 — FastAPI의
Depends또는 DI 컨테이너가 외부에서 조립된 실제 제품(WalterRoasterInteractor)을 라우터의 파라미터로 툭 던져줍니다. (DIP 준수) - 결합도 완화 — 라우터는 제품이 어디서 어떻게 만들어졌는지 전혀 모른 채, 설명서에 정의된 기능(
introduce_myself)만 호출하여 사용합니다.
이 방식을 사용하면 비즈니스 로직 구현체가 백번 바뀌어도 인바운드 라우터 코드는 단 한 줄도 수정할 필요가 없어집니다. (OCP 준수)
고치기 전
# adapter/inbound/api/v1/walter_router.py ❌
from app.use_cases.walter_roaster_interactor import WalterRoasterInteractor
from app.ports.input.walter_roaster_use_case import WalterRoasterUseCase
@walter_roaster_router.get("/myself")
async def introduce_myself():
schema = WalterRoasterSchema()
print(f"[Router] {schema}")
walter: WalterRoasterUseCase = WalterRoasterInteractor() # ← 직접 생성
return walter.introduce_myself(schema)
고친 뒤
# dependencies/walter_roaster_dependency.py ← 의존성 조립소
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
from core.database import get_db
from app.ports.input.walter_roaster_use_case import WalterRoasterUseCase
from app.use_cases.walter_roaster_interactor import WalterRoasterInteractor
from adapter.outbound.pg.walter_roaster_pg_repository import WalterRoasterPgRepository
def get_walter_roaster_use_case(
db: AsyncSession = Depends(get_db),
) -> WalterRoasterUseCase: # ← 리턴 타입은 포트(추상)
repository = WalterRoasterPgRepository(db)
return WalterRoasterInteractor(repository)
# adapter/inbound/api/v1/walter_router.py ✅
from fastapi import Depends
from app.ports.input.walter_roaster_use_case import WalterRoasterUseCase
from dependencies.walter_roaster_dependency import get_walter_roaster_use_case
@walter_roaster_router.get("/myself")
async def introduce_myself(
walter: WalterRoasterUseCase = Depends(get_walter_roaster_use_case),
):
schema = WalterRoasterSchema()
print(f"[Router] {schema}")
return walter.introduce_myself(schema)
⭐ 달라진 점은 딱 하나입니다.
= WalterRoasterInteractor() 가 = Depends(get_walter_roaster_use_case) 로 바뀌었습니다.
좌변은 그대로입니다. 1.5절에서 예고한 대로, 왼쪽은 두고 오른쪽을 지웠습니다.
그리고 라우터의 import 목록에서 Interactor 라는 단어가 사라졌습니다. 이게 진짜 증거입니다.
9.6 의존성 조립소 — DIP 팩토리의 3원칙
주입할 물건을 조립하는 곳을 이 수업에서는 의존성 조립소(DIP 팩토리) 라고 부릅니다. 규칙은 셋입니다.
| # | 원칙 | 위반하면 |
|---|---|---|
| 1 | 라우터는 구현체(JamesDirectorPgRepository)를 몰라야 한다 | 인바운드 어댑터가 아웃바운드 어댑터를 알게 됨 — 경계 붕괴 |
| 2 | 리턴 타입은 구현체가 아닌 포트(JamesDirectorUseCase)로 선언한다 | 조립소를 통해도 결국 라우터가 구상을 봄 — DIP 위반 유지 |
| 3 | 세션은 core의 get_db 에서 AsyncSession 으로 주입받는다 | 조립소가 직접 커넥션을 만듦 — 세션 수명 관리가 무너짐 |
# core/database.py
from collections.abc import AsyncGenerator
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
engine = create_async_engine(DATABASE_URL, pool_pre_ping=True)
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with AsyncSessionLocal() as session:
yield session # ← 요청이 끝나면 자동으로 정리된다
5주차에서 배운 무상태(Stateless) 원칙이 여기서 쓰입니다. FastAPI의
Depends는 요청마다 DB 세션·인증 객체를 만들고 정리하므로 무상태에 최적입니다. 세션을 전역 변수로 들고 있으면 서버를 두 대로 늘리는 순간 문제가 터집니다.
9.7 판별 체크리스트 — 내 코드가 맞는지 확인하는 다섯 질문
| # | 질문 | 아니라면 |
|---|---|---|
| 1 | 라우터 파일의 import 에 Interactor / PgRepository 가 있는가? | 있으면 DIP 위반 |
| 2 | app/use_cases/ 안의 파일이 fastapi, sqlalchemy 를 import 하는가? | 하면 코어 오염 |
| 3 | 조립소 함수의 리턴 타입이 구현체 이름인가? | 맞으면 원칙 2 위반 |
| 4 | 구현체를 갈아끼울 때 열어야 하는 파일이 라우터인가? | 맞으면 OCP 위반 |
| 5 | DB 없이 유스케이스만 테스트할 수 있는가? | 못 하면 경계가 뚫린 것 |
⚠️ AI가 생성한 코드는 DIP를 절반만 적용하는 경우가 매우 많습니다. 타입 힌트는 포트로 잘 써놓고, 정작 우변에서 구현체를 그대로 만드는 코드를 자주 냅니다. 생성된 코드를 위 5개 질문으로 검수하는 것이 이 주의 실질적인 작업입니다.
10. 실습 — 이번 주에 손으로 한 것
10.1 빈 파일 일괄 생성 — 프랙탈 구조의 준비
폴더 구조를 먼저 만들고 내용을 채우는 순서입니다. 3주차의 프랙탈 구조(같은 모양이 크기만 바꿔 반복) 원칙을 폴더에 적용합니다.
api/v1 폴더를 참조해서 api/schemas 아래에
접미사를 _schema.py 로 바꾼 빈 파일을 생성해줘.
결과는 이렇게 됩니다.
adapter/inbound/api/
├── v1/
│ ├── james_router.py
│ ├── rose_router.py
│ └── walter_router.py
└── schemas/ ← 방금 만들어진 빈 파일들
├── james_schema.py
├── rose_schema.py
└── walter_schema.py
💡 왜 빈 파일부터 만드는가: 구조가 먼저 눈에 보여야 "어디에 무엇을 쓸지" 가 정해집니다. 파일을 만들면서 동시에 내용을 채우면, AI가 폴더 구조를 임의로 바꿔 버리는 일이 생깁니다.
10.2 라우터 통합 — gateway 만들기
__init__.py 에서 APIRouter() 로 titanic_router 를 만든 뒤
include_router(james_router / rose_router / walter_router) 로 하나로 묶어줘.
코드는 3.4절에 있습니다. 이 작업의 정체가 퍼사드 패턴 적용이라는 것을 알고 하는 것과 모르고 하는 것은 다릅니다.
10.3 스키마 → DTO 변환
app/dtos/james_director_dto.py 를 만들어줘.
- Port 와 Booking 은 역정규화할 것
- country 컬럼은 삭제할 것
- Person 은 PersonCommand 로
- Booking + Port 는 BookingCommand 로
- 컬럼 타입은 전부 str 로 통일할 것
규칙별 근거는 5.8절에 정리돼 있습니다.
10.4 데이터가 끝까지 흐르는지 확인
성공 판정 기준: CSV → 라우터 → 유스케이스 → 레포지토리 까지 데이터가 이동하며 각 단계에서 로그가 찍히면 성공입니다.
# 각 계층 진입부에 로그를 심는다
print(f"[Router] {schema}")
print(f"[Interactor] {command}")
print(f"[Repository] {command}")
💡 4.4에서 본 CoR의 단점 대응책과 같은 원리입니다. 흐름이 여러 조각으로 나뉘면, 로그가 유일한 눈입니다.
10.5 월터 자기소개 라우터
월터 트리거를 자기소개 라우터로 변경해줘.
@walter_roaster_router.get("/myself") 에서
WalterRoasterSchema() 로그를 찍고
walter : WalterRoasterUseCase = WalterRoasterInteractor() 로
introduce_myself(schema) 를 호출해줘.
⚠️ 이 프롬프트대로 만들어진 코드가 9장에서 해부한 그 코드입니다. 먼저 동작하는 것을 만들고, 그다음에 구조를 고칩니다. 순서가 틀린 게 아니라 그게 순서입니다. 다만 고치는 단계를 건너뛰면 안 됩니다.
10.6 SQLAlchemy 2.0 + Alembic 최신 모델
ORM Entity를 쓸 때 버전에 따라 문법이 완전히 다릅니다. 옛날 블로그를 보고 따라 하면 반드시 막힙니다.
과거 (1.x 스타일)
from sqlalchemy import Column, Integer, String
class Passenger(Base):
__tablename__ = "passengers"
id = Column(Integer, primary_key=True)
name = Column(String(50))
최신 (2.0 스타일)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class Passenger(Base):
__tablename__ = "passengers"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(50))
무엇이 달라졌는가: Mapped[...] 타입 힌트가 들어왔습니다.
이제 에디터가 컬럼 타입을 안다는 뜻입니다. Pydantic 2.0이 타입 힌트를 규칙으로 승격시킨 것과 같은 시대 흐름입니다.
SQLAlchemy 버전 연표
| 버전 | 출시 | 무엇이 바뀌었나 |
|---|---|---|
| 1.0 | 2015-04-16 | Column(...) 전통 문법 |
| 1.4 | 2021 | 과도기 — 2.0 스타일을 미리 쓸 수 있게 열어둠 |
| 2.0 | 2023-01-26 | 타입힌트 Mapped/mapped_column 및 AsyncIO 표준화 |
클라우드 DB 연결의 기준: 커넥션 풀링 + Async 입니다. 3주차에서 배운 대로, DB 조회는 전형적인 I/O 대기이고 클라우드 DB는 네트워크 왕복 시간이 크기 때문입니다.
10.7 2.0 준수 체크
작성한 코드가 정말 2.0 방식인지 확인한 결과입니다. 아래 셋은 올바른 2.0 패턴입니다.
| 요소 | 하는 일 |
|---|---|
async_sessionmaker | 비동기 세션 팩토리. 1.x의 sessionmaker(class_=AsyncSession) 을 대체 |
pool_pre_ping | 커넥션을 꺼내기 전에 살아 있는지 한 번 찔러봄. 클라우드 DB의 유휴 연결 끊김 대응 |
run_sync(Base.metadata.create_all) | 동기 API를 비동기 컨텍스트 안에서 안전하게 실행 |
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
💡
pool_pre_ping=True가 서버리스 DB에서 특히 중요합니다. 3주차에서 배운 콜드 스타트 — 잠들어 있던 서버가 깨어나는 동안 기존 커넥션은 이미 죽어 있을 수 있습니다.
10.8 클린 아키텍처 안에서 테이블 자동 생성 + 데이터 insert
이번 주의 최종 결과물입니다. CSV 한 줄이 아키텍처 전체를 통과해 DB에 들어갑니다.
CSV 파일
→ [Router] 업로드 수신, Schema 로 검증
→ [Schema] 형식 확인 (gender 로 변환된 상태)
→ [Interactor] 비즈니스 규칙 적용, DTO 로 변환
→ [DTO] PersonCommand / BookingCommand
→ [Repository] (추상) save() 호출
→ [PgRepository] (구상) ORM Entity 생성
→ [ORM Entity] Passenger, Booking
→ [PostgreSQL] INSERT
확인 순서
- 애플리케이션 기동 시
create_all로 테이블이 생성되는가 - 업로드 요청이 200으로 돌아오는가
- 각 계층의 로그가 순서대로 찍히는가
- DB 콘솔에서 실제 행(row)이 보이는가
10.9 의존성 조립소 만들기
9.5~9.6절의 코드가 이 실습의 결과물입니다. 순서는 이렇습니다.
core/database.py에get_db()를 만든다 (AsyncSession을yield)dependencies/폴더에 기능별 조립 함수를 만든다- 조립 함수의 리턴 타입을 포트로 선언한다
- 라우터 파라미터에
Depends(...)를 건다 - 라우터에서 구현체
import를 전부 지운다
⚠️ 5번을 안 하면 앞의 4단계가 무의미합니다. import 가 남아 있으면 결합도 남아 있습니다.
10.10 환경변수 구성
프로젝트 루트에 .env 를 두고 아래 세 값을 채웁니다.
GEMINI_API_KEY=
NEXT_PUBLIC_API_BASE_URL=https://127.0.0.1:8000
DATABASE_URL=
| 키 | 쓰는 곳 | 주의 |
|---|---|---|
GEMINI_API_KEY | 백엔드에서 LLM 호출 | 3주차의 싱글톤 키메이커로 관리 |
NEXT_PUBLIC_API_BASE_URL | 프론트엔드가 백엔드를 부를 주소 | NEXT_PUBLIC_ 접두사가 붙으면 브라우저에 노출됩니다. 비밀값을 절대 넣지 마세요 |
DATABASE_URL | SQLAlchemy 엔진 생성 | 비동기 드라이버(postgresql+asyncpg://)인지 확인 |
⚠️ https://127.0.0.1:8000 에서 막히면 http:// 로 바꿔 보세요.
로컬 개발 서버는 기본적으로 인증서가 없는 http 로 뜹니다. https 로 부르면 브라우저가 먼저 연결을 거부합니다.
3주차의 진단 순서와 같습니다 — 연결이 안 되면 코드를 고치기 전에 주소부터 봅니다.
⚠️ 환경변수를 고쳤으면 서버를 재시작해야 합니다. 이것도 3주차에서 이미 만난 함정입니다.
10.11 VSCode 들여쓰기 확대
계층이 깊어지면 들여쓰기 폭이 좁아 구조가 눈에 안 들어옵니다.
Ctrl + , → 검색창에 indent 입력 → 값을 20 으로 변경
💡 이건 취향 문제가 아닙니다. 헥사고날처럼 계층이 많은 구조에서는 들여쓰기가 곧 계층 깊이의 시각적 신호입니다. 넓게 보면 "이 코드가 너무 깊다"가 바로 보입니다.
11. 이 주의 읽을거리와 그 맥락
11.1 AI 이후의 소프트웨어 — 하네스(Harness) 시대의 개막
이 주의 첫날 자료입니다. 3주차의 하네스 자료에서 이어집니다.
하네스의 정의
하네스 = 모델을 감싸고 실제 행동을 가능하게 하는 실행 구조 구성: 프롬프트 + 도구 + 상태 + 권한 + 평가 + 복구 공식:
에이전트 = 모델 + 하네스
이게 왜 이 주에 나왔는가 — 하네스와 헥사고날은 같은 그림입니다.
| 하네스 | 헥사고날 |
|---|---|
| 모델(LLM)을 날것으로 두지 않는다 | 핵심 로직을 외부 기술에 직접 노출하지 않는다 |
| 가이드(Feedforward) — CLAUDE.md, 규칙, 문서 | 입력 포트 — 들어오는 것의 규격을 정함 |
| 센서(Feedback) — 테스트, 린터, 로그, 리뷰 | 출력 포트 — 나가는 것의 규격을 정함 |
| 감싸는 이유: 같은 실수를 구조적으로 못 하게 | 감싸는 이유: 기술 변경이 구조적으로 핵심에 못 닿게 |
마틴 파울러의 가이드(Guide)와 센서(Sensor) 두 축이 정확히 입력 포트와 출력 포트에 대응합니다.
| 가이드 — 행동 전 방향 설정 | 센서 — 행동 후 결과 체크 |
|---|---|
| 피드포워드(Feedforward) | 피드백(Feedback) |
| Principles & Rules (코딩 표준, 아키텍처 원칙) | Logs & Browser (실행 결과·런타임 에러) |
| CFRs (보안·성능·가용성 요구사항) | Review Agents (다른 AI 리뷰, 정적 분석) |
| How-tos & Ref Docs | Tests (유닛/통합 테스트) |
"가이드와 센서, 둘 다 있어야 제대로 된 하네스다." 가이드만 있으면 잘못된 길로 갔을 때 바로잡을 방법이 없고, 센서만 있으면 시행착오가 너무 많아 비효율입니다.
🔗 https://martinfowler.com/articles/harness-engineering.html 🔗 https://www.samsungsds.com/kr/insights/ai-harness-framework-in-the-multi-ai-era.html 🔗 https://brunch.co.kr//@sungdairi/91
⭐ 이 주의 실습 전체가 하네스 구축이었습니다. "AI에게 코드를 시킨다"가 아니라 "AI가 지킬 수밖에 없는 구조를 먼저 만든다" 가 이 주의 작업이었습니다. 폴더를 프랙탈로 통일하고, 포트와 어댑터의 이름 규칙을 정하고, 조립소를 한 곳에 모은 것 — 전부 가이드입니다. 각 계층에 로그를 심고 로그가 순서대로 찍히는지 확인한 것 — 그게 센서입니다.
11.2 엉클 밥의 SOLID
5주차에서 다섯 원칙을 배웠고, 이 주에는 그것을 실제 코드에서 위반 사례로 잡아내는 훈련을 했습니다.
🔗 https://tech.kakaobank.com/posts/2411-solid-truth-or-myths-for-developers/ 🔗 https://guhonga.tistory.com/128 (DIP는 Injection이 아니라 Inversion)
이 장에서 실제로 쓴 원칙은 셋입니다.
| 원칙 | 이 장의 어디 | 무엇을 지켰나 |
|---|---|---|
| SRP (단일 책임) | 4.3 포인트 A·B | 유스케이스가 인증·검증 로직으로 오염되지 않게 |
| OCP (개방-폐쇄) | 4.2, 9.3 | 정책 추가 시 기존 코드를 안 열게 |
| DIP (의존성 역전) | 9.2, 9.5 | 라우터가 구현체를 모르게 |
나머지 둘(LSP·ISP)은 다음 주에 데이터 모델(DTO·ORM·VO·Entity)의 치환성 문제와 함께 다시 나옵니다.
앞으로 다시 만납니다 LSP(리스코프 치환 원칙) 는
Schema / DTO / ORM / VO / Entity가 같은 계약을 지키며 서로 교체 가능한가의 문제로 다시 등장합니다.
11.3 자바 Getter/Setter에 관한 레딧 질문
"자바는 왜 필드를 private 으로 감추고 getX() / setX() 를 따로 만드는가?" 라는 질문이 이 주에 나왔습니다.
이게 왜 이 주의 자료인가 — 캡슐화가 곧 이 장의 주제이기 때문입니다.
- Getter/Setter는 필드를 직접 열어주는 대신 문(門)을 만든 것입니다.
Repository(추상)를 두고PgRepository(구상)를 감춘 것과 완전히 같은 동작입니다.- 규모만 다릅니다. Getter/Setter는 필드 하나 단위의 포트이고,
Repository는 저장소 전체 단위의 포트입니다. - 3주차에서 배운 프랙탈 구조 — 같은 모양이 크기만 바꿔 반복되는 것 — 의 또 다른 사례입니다.
⚠️ 다만 파이썬에서는 Getter/Setter를 그대로 흉내 내지 않습니다.
파이썬에는 @property 가 있어서, 필요해질 때 문을 만들어도 호출하는 쪽 코드가 안 바뀝니다.
그래서 파이썬에서는 처음부터 get_x() 를 만들지 않는 것이 관례입니다. 언어마다 관례가 다릅니다.
11.4 우버, 클로드 코드 과다 지출에 월 한도 도입
우버가 AI 코딩 도구 지출에 1인당 월 한도(약 230만 원) 를 걸었다는 소식입니다.
이게 왜 이 주의 자료인가 — 토큰은 아키텍처 문제이기 때문입니다.
- 결합도가 높은 코드는 AI가 더 많은 파일을 읽어야 수정할 수 있습니다. 컨텍스트가 커지고 토큰이 늘어납니다.
- 결합도가 낮으면 AI가 파일 하나만 열고 고칠 수 있습니다.
- 즉 좋은 아키텍처는 이제 돈으로 직접 환산됩니다.
- 3주차의 모노레포 논의(AI가 전체 맥락을 봐야 한다)와 짝을 이루는 이야기입니다. 전체를 볼 수 있게 하되, 고칠 때는 한 곳만 열면 되게 만드는 것이 목표입니다.
11.5 타이타닉 생존자 ERD
이번 주 실습 데이터의 개체 관계도(ERD) 를 공유했습니다.
이게 왜 이 주의 자료인가: 5.3에서 본 세 개의 그릇 중 마지막인 ORM Entity 는 ERD 없이는 만들 수 없기 때문입니다.
Person,Booking,Port세 개체와 그 관계가 그대로 ORM 클래스가 됩니다.- 3주차의 식별 관계(실선) / 비식별 관계(점선) 구분이 여기서
ForeignKey설정으로 내려앉습니다. - 그리고 5.8의 "Port·Booking은 역정규화" 규칙은 ERD를 봐야 이해됩니다. DB에서는 나뉘어 있는 것을, 유스케이스에서는 합쳐서 다룬다는 뜻입니다.
부록 A. 용어 사전
| 용어 | 영문 | 상위 범주 | 정의 |
|---|---|---|---|
| 추상 | Abstraction | 코드 약속 | "무엇을 할지"만 정하고 "어떻게"는 비워둔 것. 설명서 |
| 구상 | Concretion | 코드 약속 | "어떻게"를 실제로 채워 넣어 실행 가능한 것. 실제 제품 |
| 추상 클래스 | Abstract Base Class (ABC) | 파이썬 클래스 | abc.ABC 를 상속. 직접 인스턴스화 불가 |
| 추상 메서드 | Abstract Method | 메서드 | @abstractmethod 로 표시. 자식이 반드시 구현해야 함 |
| 오버라이딩 | Overriding | 상속 동작 | 부모의 메서드를 자식이 다시 정의하는 것 |
| 헥사고날 | Hexagonal Architecture | 아키텍처 | 핵심을 가운데 두고 포트·어댑터로 외부를 연결 |
| 포트 | Port | 추상 | 도메인이 정의한 "무엇을 할지"의 명세 |
| 어댑터 | Adapter | 구상 | 포트를 특정 기술로 실제 구현한 것 |
| 인바운드 어댑터 | Inbound Adapter (Driving) | 어댑터 | 외부 요청을 받아 내부 형식으로 변환. 라우터·컨트롤러 |
| 입력 포트 | Inbound Port | 포트 | 외부가 코어로 진입하는 인터페이스. ~UseCase |
| 유스케이스 | Use Case | 계층 | 핵심 비즈니스 규칙이 사는 곳. 외부 의존성 없음 |
| 인터랙터 | Interactor | 구상 | 유스케이스(입력 포트)의 구현체 |
| 출력 포트 | Outbound Port | 포트 | 코어가 외부를 부를 때 쓰는 인터페이스. ~Repository |
| 아웃바운드 어댑터 | Outbound Adapter (Driven) | 어댑터 | 출력 포트를 실제 기술로 구현. ~PgRepository |
| 게이트웨이 | Gateway | 구상 | 백엔드 진입 경로를 하나로 모은 단일 창구 |
| 퍼사드 | Facade | 구조 패턴 | 복잡한 서브시스템 앞에 단순한 창구 하나를 세움 |
| 책임 연쇄 | Chain of Responsibility (CoR) | 행위 패턴 | 처리 객체를 사슬로 엮어 못 하면 다음으로 넘김 |
| 핸들러 | Handler | CoR 구성요소 | 체인의 한 마디. 처리하거나 넘기거나 |
| 디폴트 핸들러 | Default Handler | CoR 구성요소 | 체인 끝까지 아무도 처리 못 했을 때 대응하는 마지막 마디 |
| 프록시 | Proxy | 구조 패턴 | 같은 얼굴로 접근을 제어하는 대리자 |
| 레이어드 아키텍처 | Layered Architecture | 아키텍처 | Router–Interactor–Repository–DB 로 층을 나눈 구조 |
| 라우터 | Router | 계층 | HTTP 요청 진입점. 1차 검증과 상태 코드 담당 |
| 리포지토리 | Repository | 계층·추상 | 데이터 접근을 추상화해 인프라를 격리 |
| 스키마 | Schema | 자료구조 | 바깥에서 들어온 데이터의 형식을 검증하는 그릇 |
| DTO | Data Transfer Object | 자료구조 | 계층 간 전달만을 위한 순수 데이터 그릇 |
| ORM 엔티티 | ORM Entity | 자료구조 | DB 테이블과 1:1로 매핑되는 객체 |
| 커맨드 | Command | DTO 유형 | "이걸 해라"는 의도를 담은 DTO |
| 역정규화 | Denormalization | DB 기법 | 나뉜 테이블을 의도적으로 합쳐 조회를 단순화 |
| 제어의 흐름 | Control Flow | 개념 | 코드가 실행되는 순서 |
| 의존성의 방향 | Dependency Direction | 개념 | 누가 누구를 알고 있는가. 항상 안쪽을 향해야 함 |
| 추상 공간 | — | 이 교재 용어 | input 포트와 output 포트 두 칸. 모든 의존성이 향하는 곳 |
| 의존성 주입 | Dependency Injection (DI) | 기법 | 필요한 객체를 밖에서 넣어주는 것 |
| 의존성 조립소 | DIP Factory | 이 교재 용어 | 주입할 구현체를 조립하는 전용 모듈 |
Depends | — | FastAPI 기능 | 요청마다 의존 객체를 만들고 정리해주는 주입 장치 |
| 다중 상속 | Multiple Inheritance | 파이썬 기능 | 여러 부모 클래스를 동시에 상속 |
| 믹스인 | Mixin | 클래스 유형 | 단독 사용이 아니라 기능만 주입하기 위한 클래스 |
| 데이터 클래스 | Data Class | 클래스 유형 | @dataclass. 데이터 저장용 구조체 |
| 슬롯 | __slots__ | 파이썬 기능 | 인스턴스 속성을 미리 못 박아 메모리를 아끼는 장치 |
| Pydantic | — | 라이브러리 | 타입 힌트 기반 데이터 검증·설정 관리 |
BaseModel | — | Pydantic 모델 | 딕셔너리 형태 데이터를 검증하는 표준 클래스 |
RootModel | — | Pydantic 모델 | 최상위가 리스트·단일값일 때 감싸는 모델 (2.0 신규) |
BaseSettings | — | Pydantic 모델 | 환경변수·.env 로드. pydantic-settings 로 분리 |
TypeAdapter | — | Pydantic 도구 | 클래스 없이 임의 타입을 즉석 검증 (2.0 신규) |
ConfigDict | — | Pydantic 설정 | class Config 를 대체하는 설정 딕셔너리 |
model_dump() | — | Pydantic 메서드 | 1.x의 .dict() 를 대체 |
| SQLAlchemy | — | 라이브러리 | 파이썬 대표 ORM |
Mapped | — | SQLAlchemy 2.0 | 컬럼에 타입 힌트를 붙이는 표기 |
mapped_column | — | SQLAlchemy 2.0 | 1.x의 Column(...) 을 대체 |
async_sessionmaker | — | SQLAlchemy 2.0 | 비동기 세션 팩토리 |
pool_pre_ping | — | SQLAlchemy 옵션 | 커넥션을 쓰기 전에 살아 있는지 확인 |
| Alembic | — | 도구 | SQLAlchemy용 DB 스키마 마이그레이션 도구 |
| 얕은 복사 | Shallow Copy | 복제 방식 | 껍데기만 새로 만들고 내부는 원본과 공유 |
| 깊은 복사 | Deep Copy | 복제 방식 | 중첩된 내부까지 전부 새로 복제 |
| 무상태 | Stateless | 서버 성질 | 요청 사이에 상태를 서버가 들고 있지 않음 |
| 하네스 | Harness | 실행 구조 | 모델을 감싸 실제 행동을 가능하게 하는 구조 |
| 가이드 | Guide (Feedforward) | 하네스 요소 | 행동 전에 방향을 정하는 규칙·문서 |
| 센서 | Sensor (Feedback) | 하네스 요소 | 행동 후 결과를 확인하는 테스트·로그·리뷰 |
부록 B. 스스로 확인하기
먼저 답해보고, 막히면 옆의 절을 펼치세요.
| # | 질문 | 답이 있는 곳 |
|---|---|---|
| 1 | 추상 클래스는 왜 메모리에 인스턴스로 올릴 수 없는가? | 1.2 |
| 2 | 헥사고날의 일곱 칸 중 추상은 몇 개이고 어느 것인가? | 2.1, 2.2 |
| 3 | usecase(구현) → ABC 는 무엇을 뜻하는가? | 2.3 |
| 4 | output 포트의 정식 이름은 무엇인가? | 2.3 |
| 5 | 이 아키텍처의 3대 이점을 한 문장으로 묶으면? | 2.5 |
| 6 | gateway와 Route는 무엇이 다른가? | 3.3 |
| 7 | 책임 연쇄를 붙일 수 있는 세 지점은? | 4.3 |
| 8 | CoR의 세 가지 단점과 각각의 대응책은? | 4.4 |
| 9 | Facade와 CoR은 왜 반대 방향인가? | 4.5 |
| 10 | Schema·DTO·ORM Entity는 각각 누구의 언어인가? | 5.3 |
| 11 | 응답이 돌아올 때 데이터는 몇 번 변환되는가? | 5.4 |
| 12 | 제어의 흐름과 의존성의 방향은 왜 반대인가? | 5.5 |
| 13 | 「추상 공간」이 감싸는 두 칸은 무엇인가? | 5.5 |
| 14 | DTO에 @dataclass 를 쓰는 세 가지 이유는? | 5.7 |
| 15 | 왜 컬럼 타입을 전부 str 로 통일했는가? | 5.8 |
| 16 | bool 은 왜 상속할 수 없는가? | 6.4 |
| 17 | Pydantic 2.0에서 .dict() 는 무엇으로 바뀌었나? | 7.3 |
| 18 | BaseSettings 는 2.0에서 어떻게 됐는가? | 7.2 |
| 19 | Schema 폴더는 왜 app/ 이 아니라 adapter/inbound/ 아래인가? | 7.4 |
| 20 | 얕은 복사가 위험한 이유는? | 8.1 |
| 21 | walter: WalterRoasterUseCase = WalterRoasterInteractor() 는 무엇을 위반하는가? | 9.2~9.4 |
| 22 | 이 코드를 고친 뒤 라우터에서 사라져야 하는 것은? | 9.5 |
| 23 | 의존성 조립소의 3원칙은? | 9.6 |
| 24 | pool_pre_ping 은 왜 필요한가? | 10.7 |
| 25 | 하네스의 가이드/센서는 헥사고날의 무엇에 대응하는가? | 11.1 |
부록 C. 자주 하는 실수
| 실수 | 바로잡기 |
|---|---|
| "타입 힌트를 포트로 썼으니 DIP를 지킨 것" | ❌ 우변에서 구현체를 생성하면 위반입니다. import 목록을 보세요 |
"Repository 는 DB에 접근하는 클래스" | ⚠️ Repository(추상)는 아무것도 안 합니다. DB에 붙는 건 PgRepository(구상) |
| "포트는 구현이 없으니 안 만들어도 됨" | ❌ 포트가 없으면 어댑터를 갈아끼울 자리가 없습니다 |
| "gateway = Route" | ❌ gateway는 퍼사드(여럿→하나), Route는 어댑터(형식 변환) |
| "책임 연쇄를 쓰면 무조건 좋다" | ⚠️ 디버깅이 어려워집니다. 로깅과 디폴트 핸들러가 세트입니다 |
| "체인 끝까지 안 걸리면 그냥 통과" | ❌ 요청이 조용히 사라집니다. 디폴트 핸들러를 반드시 두세요 |
| "Schema와 DTO는 결국 같은 것" | ❌ Schema는 바깥의 언어, DTO는 내부 약속의 언어입니다 |
| "DTO 하나를 끝까지 들고 가면 편함" | ⚠️ DB 컬럼이 바뀌면 프론트까지 깨집니다 |
| "요청 스키마를 응답에도 재사용" | ⚠️ password 같은 필드가 응답에 새어 나갑니다 |
| "DTO에 편의 메서드 하나쯤은 괜찮다" | ❌ 넣는 순간 도메인 모델입니다. 분리하세요 |
| "클린 아키텍처 화살표 = 데이터 흐름" | ❌ 코드 관계(아는 관계) 입니다. 데이터는 양방향으로 흐릅니다 |
| "폴더를 네 개로 나눴으니 클린 아키텍처" | ⚠️ 폴더 이름보다 의존 관계가 중요합니다 |
"app/use_cases/ 에서 SQLAlchemy를 import 해도 됨" | ❌ 코어 오염. 이 순간 세 가지 이점이 전부 사라집니다 |
"추상 메서드에는 self 생략 가능" | ⚠️ 이 수업은 붙이는 것으로 통일합니다. 자식과 시그니처를 맞춰야 하기 때문 |
"bool 도 상속할 수 있다" | ❌ TypeError. True/False 는 싱글톤이라 금지돼 있습니다 |
"Pydantic 2.0에서 class Config 쓰면 됨" | ❌ model_config = ConfigDict(...) 입니다 |
".dict() 로 딕셔너리 변환" | ⚠️ 2.0에서는 .model_dump() 입니다 |
"BaseSettings 는 pydantic에 있음" | ❌ 2.0부터 pydantic-settings 로 분리됐습니다 |
"SQLAlchemy는 Column(Integer, ...) 로 쓴다" | ⚠️ 그건 1.x입니다. 2.0은 Mapped[int] = mapped_column(...) |
| "얕은 복사도 복사니까 안전" | ❌ 중첩 객체는 원본과 같은 메모리를 봅니다 |
"git clone 대신 ZIP 받아도 똑같다" | ❌ .git 이 없어 push 도 히스토리 추적도 불가능합니다 |
"로컬은 https://127.0.0.1:8000" | ⚠️ 개발 서버는 보통 http 입니다. 인증서가 없으면 연결이 거부됩니다 |
| "AI가 만든 DI 코드는 그대로 쓰면 됨" | ⚠️ DIP를 절반만 적용하는 경우가 많습니다. 9.7 체크리스트로 검수하세요 |
부록 D. 날짜별 색인
이 장의 내용이 실제 수업에서 어느 날 다뤄졌는지입니다.
| 날짜 | 다룬 내용 | 이 장의 절 |
|---|---|---|
| 6/1 (월) | 헥사고날 일곱 칸 전체 매핑, usecase(구현)→ABC / output→repository 해석, 3대 이점, gateway = Facade, 책임 연쇄 3대 적용 포인트와 트레이드오프, 빈 파일 일괄 생성 프롬프트, 라우터 통합(titanic_router), 하네스 시대의 개막 | 2, 3, 4, 10.1~10.2, 11.1 |
| 6/2 (화) | SOLID 학습, 스키마 → DTO 변환 규칙, DTO는 @dataclass 가 정석, frozen=True, 강한 검증은 Pydantic, 추상 메서드에도 self, 인스턴스명:추상타입=구상생성자() 표기, CSV→라우터→유스케이스→레포지토리 로그 확인, 타이타닉 ERD, 자바 Getter/Setter | 1.4 |
| 6/4 (목) | 레이어드 아키텍처 4계층, Schema/DTO/ORM Entity 경계, 역방향 응답 흐름, 제어의 흐름 ↔ 의존성 방향, 클린 아키텍처 5계층과 「추상 공간」, 파이썬 상속 가능 클래스 분류, bool 상속 금지, __slots__, Pydantic 2.0 네 모델과 ConfigDict·model_ 접두사, Clone vs Copy, 월터 자기소개 라우터, SQLAlchemy 2.0 + Alembic, 버전 연표, 2.0 준수 체크, 테이블 자동 생성 + insert, VSCode 들여쓰기, 우버 월 한도 | 5, 6, 7, 8, 10.5~10.8, 10.11, 11.4 |
| 6/5 (금) | DIP·OCP 위반 한 줄 해부, 헥사고날 경계 붕괴, Depends 주입 해결, 의존성 조립소(DIP 팩토리) 3원칙, get_db 와 AsyncSession, .env 구성 | 1.5, 9, 10.9~10.10 |
부록 E. Gemini / NotebookLM 프롬프트
복습 문제 받기
이 6주차 교재만 근거로 8문제를 내줘. 비전공자 기준으로.
"추상 vs 구상", "헥사고날 일곱 칸", "제어의 흐름 vs 의존성의 방향"은 반드시 포함해줘.
답은 바로 주지 말고, 내가 답한 다음에 채점하고
틀린 이유를 교재의 어느 절을 근거로 설명해줘. 교재에 없는 내용은 꺼내지 마.
인포그래픽 ① — 헥사고날 전체 매핑
「2.1 한 줄 흐름도」와 「2.2 전체 매핑표」를 합쳐 가로 흐름 인포그래픽 1장으로 만들어줘.
- 왼쪽부터 Front end / inbound / input / use case / output / outbound / database 일곱 칸
- 추상(input, output)은 점선 테두리, 구상은 실선 테두리로
- 각 칸 아래에 [표준 명칭] / [실제 구현 컴포넌트 예시]를 두 줄로
- 아래쪽에 "usecase(구현) → ABC" 와 "output → repository" 를 화살표로 표시
- 전문용어는 한글(영문) 병기
인포그래픽 ② — 세 개의 그릇
「5.1 네 개의 층과 세 개의 그릇」과 「5.3 세 개의 그릇」을 세로 흐름도 1장으로 만들어줘.
- Router → Interactor → Repository → DB 를 세로 상자로
- 상자 사이 화살표에 Schema / DTO / ORM Entity 를 라벨로
- 각 라벨 옆에 "누구의 언어인가"(바깥·내부약속·DB)를 색으로 구분
- 오른쪽에 역방향(Response) 흐름을 얇은 화살표로 같이
인포그래픽 ③ — 책임 연쇄 3대 적용 지점
「4.3 세 곳에 붙일 수 있다」를 인포그래픽 1장으로 만들어줘.
- 위에서 아래로 포인트 A(인바운드 프리필터) / B(유스케이스 정책) / C(아웃바운드 다중 데이터)
- 각 포인트의 체인 흐름을 사슬 모양 화살표로
- 오른쪽에 「4.4 트레이드오프」 표를 장점/단점 2열로 붙여줘
인포그래픽 ④ — 위반 한 줄 해부
「9. 실전 — DIP·OCP 위반 한 줄 해부」를 before/after 비교 1장으로 만들어줘.
- 왼쪽: walter: WalterRoasterUseCase = WalterRoasterInteractor()
아래에 위반 ①DIP ②OCP ③레이어 경계 붕괴를 빨간 라벨로
- 오른쪽: Depends(get_walter_roaster_use_case) 버전
아래에 조립소 3원칙을 초록 라벨로
- 가운데에 "좌변은 그대로, 우변만 바뀐다"를 크게
막힐 때
6주차에서 "___" 가 이해가 안 돼.
이 교재의 판별 축 5개(추상/구상, 진입/진출, 흐름/방향, 직접생성/주입, Facade/CoR)만
써서 다시 설명해줘. 교재에 없는 새 비유는 만들지 마.
미국식 분류로 다시 정리하기
이 교재에 나온 비유(설명서와 제품, 백화점 정문, 결재 라인, 문지기)를 전부 걷어내고,
"이것은 무엇의 한 종류인가 / 형제 개념과 무엇으로 갈리는가" 형식으로만
포트·어댑터·게이트웨이·핸들러·스키마·DTO·ORM 엔티티를 다시 정의해줘.
내 코드 검수하기
아래 코드를 6주차 교재의 「9.7 판별 체크리스트」 다섯 질문으로만 검수해줘.
질문마다 ✅/❌ 와 근거가 되는 코드 줄 번호를 붙이고,
❌ 인 항목은 교재 9.5의 형태로 고친 코드를 보여줘. 교재에 없는 라이브러리는 쓰지 마.
[여기에 라우터 파일과 조립소 파일을 붙여넣기]
앞 장과 연결 확인
6주차 교재에서 "앞에서 배운", "3주차", "5주차" 라고 표시된 부분을 모두 찾아서,
이전 장의 어떤 개념이 이번 장에서 어떤 이름·어떤 파일로 다시 나타났는지 표로 정리해줘.
컬럼은 [이전 장 개념 / 이번 장에서의 이름 / 해당 절] 로.
다음 장 연결
이 교재에서 "앞으로 다시 만납니다"라고 표시된 부분을 모두 찾아서,
다음 주에 무엇을 배우게 되는지 예습 목록을 만들어줘.
각 항목에 이번 장의 어느 절을 먼저 복습하고 가야 하는지도 표시해줘.
LangTailor