
생성 수정
IBM * 레드햇 AX 과정 · 10강
클린·헥사고날·DDD — 추상화를 풀스택 구조로
2026년 5월 25일(월) ~ 5월 29일(금)
8. ⭐ CLEAN HEXAGONAL DDD — 세 아키텍처가 한 폴더 트리에
8.1 폴더 구조
지금까지 배운 클린 아키텍처와 헥사고날, 3주차에서 이름만 나왔던 DDD가 하나의 프로젝트 트리에서 만납니다.
PROJECT_ROOT/
├── adapter/ ← 헥사고날 (Hexagonal)
│ ├── __pycache__/
│ ├── inbound/ →|
│ └── outbound/ |←
├── app/ ← 클린 (Clean)
│ ├── __pycache__/
│ ├── dtos/
│ ├── ports/ 🔒↔🔒 Port Gate
│ │ ├── input/ →|
│ │ └── output/ |←
│ └── use_cases/
│ └── __init__.py
└── domain/ ← DDD (Domain-Driven Design)
세 최상위 폴더가 각각 하나의 아키텍처 사조를 담당합니다.
| 폴더 | 사조 | 담당 | 축 2에서의 위치 |
|---|---|---|---|
adapter/ | 헥사고날 | 기술과 맞닿는 바깥 껍질 | 가장 바깥 |
app/ | 클린 | 유스케이스와 포트 (계약과 흐름) | 중간 |
domain/ | DDD | 순수한 비즈니스 개념 | 가장 안쪽 |
하위 폴더의 역할
| 경로 | 정체 | 추상/구상 |
|---|---|---|
adapter/inbound/ | Driving Adapter — 라우터, CLI | 구상 |
adapter/outbound/ | Driven Adapter — DB 리포지토리 구현체 | 구상 |
app/dtos/ | 계층 간 데이터 전달 그릇 | (데이터) |
app/ports/input/ | Input Port — 진입 계약 | 추상 |
app/ports/output/ | Output Port — 출구 계약 | 추상 |
app/use_cases/ | 비즈니스 흐름 구현 | 구상(비즈니스적) |
domain/ | 엔티티·값 객체·도메인 규칙 | (핵심) |
💡 3주차 9장에서 배운
__init__.py가 여기서 다시 나옵니다.app/use_cases/__init__.py는 빈 파일이어도 "이 폴더는 파이썬 패키지다" 라는 선언 자체가 역할입니다. 트리의__pycache__/는 파이썬이 자동으로 만드는 컴파일 캐시이므로.gitignore대상입니다.
8.2 CORE PRINCIPLES — 핵심 원칙
- 🔊 Adapter Pattern 사용 (Adapter Pattern Used) — 1.4절에서 배운 GoF 어댑터 패턴이 아키텍처 규모로 확대된 것입니다.
- 문을 관리함 (Manages Gates) — Ports와 Adapters가 게이트웨이 역할을 합니다.
8.3 ⭐ Port Gate — "바로 가는 곳에 문을 세운다"
이 구조의 규칙은 한 줄입니다.
❌ 금지: bound (external) ──✕──► use_case (core)
⭕ 대신: bound (external) → adapter → input_port (gate) → use_case (core)
⭐ "바로 가는 곳에 Port라는 문을 세워 통과"
외부에서 코어로 직접 가려는 경로를 막고, 그 자리에 Port라는 문을 세워 반드시 통과하게 합니다. 이 행위 자체가 의존성 역전을 심는 것(Placing dependency inversion) 입니다.
| 문을 세워 얻는 것 | 설명 |
|---|---|
| 교체 가능성 | 문의 규격만 맞으면 문 바깥은 뭐든 갈아끼울 수 있습니다 (OCP) |
| 테스트 가능성 | 문 바깥에 가짜를 세워도 코어는 눈치채지 못합니다 (LSP) |
| 오염 차단 | 문 안쪽에 FastAPI·SQLAlchemy 같은 단어가 들어오지 못합니다 (의존성 규칙) |
8.4 🗂 SOLID 중 ISP 해당
이 구조에서 특별히 강조되는 SOLID 원칙은 ISP(Interface Segregation Principle, 인터페이스 분리 원칙) 입니다.
왜 하필 ISP인가?
ports/아래를input/과output/으로 갈라 놓았기 때문입니다.- 만약 하나의 거대한
Port인터페이스에 진입 메서드와 저장 메서드를 다 넣었다면, 라우터는 쓰지도 않는save()에 의존하게 되고, DB 리포지토리는 쓰지도 않는handle_request()에 의존하게 됩니다. - 각 어댑터가 자신에게 꼭 필요한 최소한의 진입점에만 결합 — 그것이 ISP이고, 그래서 문이 하나가 아니라
input/과output/두 개입니다.
⚠️ 흔한 실수:
ports/폴더를 만들어놓고 그 안에 파일을 하나만 두는 것. 문을 세우긴 했는데 문이 하나면 ISP가 아닙니다. 들어오는 문과 나가는 문은 반드시 나눕니다.
8.5 실제 파일로 보는 한 바퀴
이번 주 실습에서 만든 흐름을 이 구조에 대입하면 이렇습니다.
adapter/inbound/api/v1/james_router.py [구상] ← HTTP 요청 수신, DTO로 변환
↓ 호출
app/ports/input/james_use_case.py [추상] ← "이 시스템은 무엇을 할 수 있는가"
↑ 구현
app/use_cases/james_command.py [구상] ← 비즈니스 흐름 실행
↓ 호출
app/ports/output/james_repository.py [추상] ← "이 시스템은 무엇을 저장해야 하는가"
↑ 구현
adapter/outbound/.../james_pg_repository.py [구상] ← 실제 PostgreSQL 접근
화살표 방향을 다시 보세요. ↓ 호출 과 ↑ 구현 이 번갈아 나옵니다.
↓는 제어의 흐름입니다.↑는 의존성의 방향입니다.
이것이 7.4절에서 배운 "흐름과 의존성이 어긋난다" 를 파일 경로로 본 모습입니다.
9. ⭐ 추상화하는 근본적인 이유
9.1 한 문장
지금까지 Port, 인터페이스, 추상 클래스를 계속 만들었습니다. 그런데 왜 만드는가? 답은 하나입니다.
⭐ "변화의 중심에서 내 코드를 보호하기 위해서."
9.2 구상(How)과 추상(What)
| 구상 (Concretion) | 추상 (Abstraction) | |
|---|---|---|
| 질문 | How — 어떻게 하는가 | What — 무엇을 하는가 |
| 예 | "MySQL에 연결해서 유저 테이블에 인서트한다" | "유저 정보를 저장한다" |
| 성격 | 언제든지 바뀔 수 있는 기술적 세부사항 | 비즈니스의 본질, 변하지 않는 규칙 |
| 수명 | 짧음 (기술 트렌드에 따라) | 김 (사업이 존재하는 한) |
소프트웨어는 반드시 변합니다. 만약 내 핵심 로직이 상대방의 구상 클래스를 직접 바라보고 있다면(의존하고 있다면), 상대방 코드가 바뀔 때 내 코드도 도미노처럼 깨집니다.
중간에 추상화라는 방파제를 세워두면, 방파제 뒤의 구체적인 기술이 어떻게 바뀌든 내 핵심 로직은 안전하게 보호됩니다.
[ 파도 = 기술 변화 ] MySQL → PostgreSQL → MongoDB
███████ 방파제 = 추상(Port) ███████
[ 항구 = 내 비즈니스 로직 ] "유저 정보를 저장한다" ← 파도가 쳐도 그대로
💡 방파제는 파도를 없애는 게 아니라 막는 것입니다. 기술 변화를 막을 수는 없지만, 그 변화가 어디까지 들어오게 할지는 우리가 정합니다. 그 선을 긋는 행위가 추상화입니다.
9.3 파이썬으로 추상 만들기 — abc 모듈
파이썬에서는 abc(Abstract Base Class) 모듈로 추상 클래스를 만듭니다.
from abc import ABC, abstractmethod
class JamesRepository(ABC):
"""Output Port — 코어가 선언하는 저장 계약."""
@abstractmethod
async def save_all(self, records: list[dict]) -> int:
"""탑승자 레코드를 저장하고 저장된 건수를 반환한다."""
...
@abstractmethod
async def find_page(self, page: int, size: int) -> list[dict]:
"""페이지 단위로 탑승자 목록을 조회한다."""
...
| 요소 | 하는 일 |
|---|---|
ABC | 이 클래스를 추상 기반 클래스로 만든다. 직접 인스턴스화할 수 없게 된다 |
@abstractmethod | "이 메서드는 자식이 반드시 구현해야 한다"고 문법적으로 강제한다 |
... (또는 pass) | 본문 없음. 여기엔 How가 오면 안 된다 |
구현체는 이렇게 됩니다.
class JamesPgRepository(JamesRepository): # Outbound Adapter [구상]
def __init__(self, session):
self.session = session
async def save_all(self, records: list[dict]) -> int:
# 여기서만 SQLAlchemy / PostgreSQL 이라는 단어가 등장한다
...
async def find_page(self, page: int, size: int) -> list[dict]:
...
⚠️ 자식이 메서드를 안 만들면? 인스턴스를 만드는 순간
TypeError: Can't instantiate abstract class ...가 납니다. 이 에러는 버그가 아니라 안전장치입니다.
9.4 ⭐ 추상화가 나타나는 5곳 — 전부 같은 목적
이번 주에 배운 것들이 서로 다른 이름을 달고 있지만 전부 추상화입니다.
① 객체지향의 추상화 (Abstraction)
- 개념 — 복잡한 현실 세계에서 불필요한 세부 사항을 제거하고, 우리 시스템에 필요한 핵심 본질(행동)만 추출하여 표준 규격을 만드는 것입니다.
- 파이썬 구현 —
abc모듈의ABC와@abstractmethod로 "이 메서드는 자식이 반드시 구현해야 한다"고 문법적으로 강제하는 추상 클래스를 만듭니다.
② SOLID의 의존성 역전 원칙 (DIP)
- 개념 — 고수준 모듈(비즈니스 로직)과 저수준 모듈(DB, 파일, 외부 API)이 서로를 직접 바라보지 말고, 둘 다 추상화(인터페이스)에 의존해야 한다는 설계 규칙입니다.
- 목적 — 하위 기술(저수준)의 변경이 상위 비즈니스(고수준)를 흔들지 못하게 소스코드의 의존 방향을 역전시킵니다.
③ 클린 아키텍처의 포트 (Port)
- 개념 — 소프트웨어의 내부(비즈니스 영역)와 외부(인프라 영역)를 넘나들 때, 경계선에 세워두는 추상 클래스 통로입니다.
- 목적 — 내부 영역이 외부의 구체적 기술(웹 프레임워크, 특정 DB 라이브러리)에 오염되는 것을 막기 위해, 내부 영역이 정의한 추상 클래스(Port)를 외부 영역(Adapter)이 상속받아 구현하도록 격리합니다.
④ GoF 디자인 패턴 (예: 전략 패턴)
- 개념 — 실행 중에 상황에 따라 구체적인 알고리즘(구상)을 동적으로 갈아 끼울 수 있도록, 알고리즘들을 하나의 추상 클래스로 묶어두는 실전 설계 공식입니다.
- 목적 — 새로운 기능(새로운 구상 클래스)이 추가되더라도 기존의 클라이언트 코드를 단 한 줄도 수정하지 않기 위함입니다 (OCP 달성).
⑤ 데이터베이스(RDB)의 상속 — 식별 관계
- 개념 — 클래스의 상속 개념을 테이블 구조로 바꿀 때, 자식 테이블이 부모 테이블의 기본키(PK)를 그대로 물려받아 자신의 PK이자 외래키(FK)로 사용하는 구조입니다.
- 목적 — 데이터의 중복을 막고, 데이터베이스 레벨에서 부모-자식 간의 물리적 연결성(무결성) 을 보장합니다.
| # | 자리 | 추상에 해당하는 것 | 구상에 해당하는 것 |
|---|---|---|---|
| ① | 객체지향 | 추상 클래스 (ABC) | 구현 클래스 |
| ② | SOLID | 인터페이스 | 고수준·저수준 모듈 |
| ③ | 클린 아키텍처 | Port | Adapter |
| ④ | GoF 전략 패턴 | Strategy 인터페이스 | 구체 전략들 |
| ⑤ | RDB 식별관계 | 부모 테이블의 PK | 자식 테이블 |
⭐ 다섯이 전부 "변할 것과 안 변할 것을 갈라 놓고, 안 변할 쪽에 계약을 두는" 동일한 동작입니다. 그래서 하나를 이해하면 나머지 넷이 저절로 읽힙니다. 새 용어를 만나면 "여기서 안 변하는 쪽은 무엇인가" 를 물으세요.
9.5 한 줄 요약
추상화로 규격을 정의하고, DIP 원칙에 따라 의존성을 꺾고, 클린 아키텍처(Port)나 패턴으로 코드를 격리하는 것 — 모두 "구체적인 기술(구상)이 바뀌어도 내 핵심 로직을 안 터지게 만들겠다" 는 하나의 목적을 위한 수단들입니다.
10. 스코프 계층 구조 — 같은 원리를 함수 크기로 축소하기
10.1 왜 스코프가 아키텍처 장에 나오는가
지금까지 우리는 시스템 크기의 경계(Port, 계층, 어댑터)를 배웠습니다. 그런데 함수 하나 안에도 똑같은 경계가 있습니다. 이름만 다를 뿐입니다.
- 시스템 경계에서는 그것을 Port라고 부릅니다.
- 함수 경계에서는 그것을 매개변수(Parameter) 라고 부릅니다.
이 장의 판별 축 5(좁은 스코프 ↔ 넓은 스코프)가 여기서 쓰입니다.
10.2 동심원 — 최내곽에서 최외곽으로
🌐 외부층: 컨텍스트 · 전역 변수 · 환경 변수
┌──────────────────────────────────────┐
│ 📚 중간층: 클래스 · 필드 │
│ ┌──────────────────────────────┐ │
│ │ 중심: 메서드 { } │ │
│ │ └─ 로컬 변수 │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────┘
위성: 매개변수 ( ) / 추상 클래스
❶ 최내곽: 메서드 & 로컬 변수
- 로컬 변수 (Local Variable) — 함수·메서드 블록
{ }내부에서만 유효한 가장 좁은 스코프. 3주차에서 배운 대로 Stack 영역에 있고 함수가 끝나면 사라집니다. - 메서드 (Method) — 클래스 내부의 함수. 로컬 변수를 감싸는 경계선 역할을 합니다.
❷ 중간층: 클래스, 필드, 인스턴스 변수
- 클래스 (Class) — 데이터(필드)와 행위(메서드)를 하나로 묶는 틀입니다.
- 필드 (Field) = 인스턴스 변수 (Instance Variable) — 클래스 내부에 선언되어, 객체(인스턴스)가 살아있는 동안 유지되는 변수입니다.
⚠️ 보완해야 할 지점: '필드'와 '인스턴스 변수'를 별개처럼 그리는 경우가 있는데, 엄밀히 말하면 인스턴스 변수는 필드의 한 종류입니다. 필드에는 인스턴스 변수 말고도 클래스 변수(정적 변수, static) 가 포함됩니다.
종류 소속 개수 3주차 메모리 3영역 인스턴스 변수 각 인스턴스 인스턴스마다 하나씩 Heap 클래스 변수 클래스 자체 전체에 하나 Method Area
❸ 외부층: 컨텍스트, 전역 변수, 환경 변수
- 컨텍스트 (Context) — 애플리케이션이 실행되는 '문맥'이나 '상태'를 의미합니다. 프레임워크 레벨에서 관리되는 경우가 많아 클래스 바깥에 위치합니다.
- 전역 변수 (Global Variable) — 애플리케이션 프로세스 전체에서 접근 가능한 변수입니다.
- 환경 변수 (Environment Variable) — 운영체제(OS)나 컨테이너 레벨에서 주입되는 변수입니다. 소스 코드(전역 변수)보다 더 바깥쪽인 시스템 레벨에 존재합니다. DB 접속 정보 등이 여기 해당합니다.
⭐ 환경 변수가 전역 변수보다 바깥인 이유 — 전역 변수는 코드 안에 있어 코드를 고쳐야 값이 바뀝니다. 환경 변수는 코드 밖에 있어 코드를 안 고치고도 바뀝니다. 그래서 개발 환경과 운영 환경에 다른 값을 넣을 수 있습니다. 4.2절에서
core/가 Docker와 연결되는 이유가 이것입니다.
10.3 프레임워크별 실제 매핑 (Next.js & FastAPI)
이 계층 구조가 실제 웹 기술 스택에서 어떻게 구현되는지 매핑한 표입니다. 이번 주에서 가장 실용적인 표입니다.
| 개념 (스코프 층) | 프레임워크 A (Next.js / Frontend) | 프레임워크 B (FastAPI / Backend) |
|---|---|---|
| 환경 변수 / DB | .env.local (API 키, 백엔드 주소) | .env (PostgreSQL 접속 URI, JWT 비밀키) |
| 컨텍스트 | React Context API, Next.js Middleware | FastAPI의 Request 객체, Dependency Injection Context |
| 전역 변수 | Zustand/Redux 스토어, 전역 window 객체 | Python 모듈 파일 레벨에 선언된 변수 (App 인스턴스 등) |
| 클래스 / 필드 | React 클래스 컴포넌트 또는 Custom Hook / Class | Pydantic 데이터 모델 (Schema), SQLAlchemy ORM 클래스 |
| 메서드 / 로컬 변수 | 컴포넌트 내부 함수, useState 지역 변수 | 라우터 함수(@app.get) 내부의 비즈니스 로직 및 변수 |
💡 이 표는 "이 값을 어디에 둬야 하지?" 에 답할 때 씁니다. 이 요청에서만 쓰는 값 → 로컬 변수 / 이 객체가 사는 동안 유지 → 필드 / 요청 전체를 관통 → 컨텍스트 / 환경마다 달라야 함 → 환경 변수. 필요한 것보다 넓은 스코프에 두는 것이 거의 모든 버그의 시작입니다.
10.4 외곽 키워드 배치 — 매개변수와 추상 클래스
동심원의 바깥에 따로 붙는 두 개념이 있습니다. 위치가 중요합니다.
매개변수 (Parameter)
- 위치 — 메서드(함수)의 관문 역할을 하므로, '메서드'의 경계선(괄호 안) 에 위치합니다.
- 외부에서 값을 받아 내부 로컬 변수처럼 활용되는 징검다리입니다.
추상 클래스 (Abstract Class)
- 위치 — 일반 '클래스'의 바로 위 상위 개념으로 배치됩니다.
- 구체적인 클래스들을 만들기 위한 '설계도(규격)' 역할을 하며, FastAPI 등에서는 서비스 레이어의 인터페이스를 정의할 때 주로 씁니다.
⭐ 여기서 두 개념의 대칭이 드러납니다.
무엇의 관문인가 무엇을 통제하는가 매개변수 메서드의 관문 어떤 값이 들어올 수 있는가 추상 클래스 클래스의 관문 어떤 구현이 들어올 수 있는가 하나는 값의 게이트, 하나는 타입의 게이트입니다. 둘 다 게이트입니다.
10.5 백엔드와 프론트엔드는 완전히 같은 구조다
이 계층을 백엔드와 프론트엔드에 각각 적용해보면 놀랄 만큼 대칭입니다.
TOP LAYER — SYSTEM & CONTEXT
- CONTEXT (컨텍스트) / GLOBAL VARIABLE (전역 변수) — 공유 데이터(
system_time,user_session), 어디서나 접근 가능
| 층 | BACKEND (서버 사이드) | FRONTEND (클라이언트 사이드) |
|---|---|---|
| 정의하는 것 | 구조 & 행위 (structure & behavior) | 구조 & 상호작용 (structure & interactions) |
| 1. 설계도 | CLASS (클래스) — blueprint/schema | CLASS/COMPONENT — UI element blueprint |
| 2. 실체 | INSTANCE (인스턴스) — object (in RAM/DB) | INSTANCE/COMPONENT — rendered object (in DOM) |
| 2-a. 속성 | INSTANCE VARIABLE — object properties | INSTANCE VARIABLE (props/state) — component data |
| 2-b. 행동 | METHOD (메서드) — actions/functions | METHOD (event handler) — actions (click, hover) |
| 2-c. 임시값 | LOCAL VARIABLE — temporary data, Stack → 짧은 수명 | LOCAL VARIABLE (local state) — temporary UI data → 짧은 수명 |
중앙 — DATA FLOW: Database ↔ DATA FLOW ↔ JSON Data
⭐ 핵심: 백엔드와 프론트엔드가 완전히 같은 구조로 대칭을 이룹니다. 다른 것은 실체가 어디에 그려지느냐뿐입니다(백엔드는 RAM/DB, 프론트엔드는 DOM). 3주차의 "클래스는 디스크, 인스턴스는 메모리"가 프론트엔드에도 그대로 적용됩니다. DOM이 프론트엔드의 메모리입니다.
11. ⭐ 매개변수 = 관문(Gate)
11.1 모래시계 도식
두 개의 격리된 스코프(방)가 하나의 제한된 통로를 통해 소통하는 구조입니다. 모래시계를 떠올리면 정확합니다.
┌──────────────────────────────────┐
│ ❶ 상단 스코프 (함수 A) │
│ 로컬 변수 x = 10 │
│ 내부 연산 x + 2 → 12 │
└──────────────┬───────────────────┘
│
┅┅┅┅┅┅┅┅┅┅┅┅┅┅│┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅┅ ← ❷ 중앙 격리선 (메모리 불혼합)
(⊗) ← 매개변수 = 유일한 통로
│
┌──────────────┴───────────────────┐
│ ❸ 하단 스코프 (함수 B) │
│ 새 로컬 변수 x ← 12 │
│ 내부 연산 x + 5 → 17 │
│ → 외부로 반환 (Return) │
└──────────────────────────────────┘
❶ 상단 스코프 (함수 A의 로컬 스코프)
- 로컬 변수 (Local Variable)
x— 함수 내부에서 독립적으로 존재하는 변수입니다. 외부 영역에서는 이 변수에 직접 접근할 수 없습니다. - 내부 연산 (
+1또는+2) — 해당 스코프 내에서 비즈니스 로직을 거쳐 데이터가 가공됩니다.
❷ 중앙 경계선과 매개변수 (Parameter)
- 중앙 격리선 (점선) — 두 스코프가 완전히 분리되어 있어 메모리가 섞이지 않음을 뜻합니다.
- 매개변수 (Parameter) — 하단 스코프가 외부 데이터를 안전하게 받아들이기 위해 열어둔 유일한 통로(관문) 입니다. 상단 스코프의 데이터는 이 관문을 통과해야만 경계선을 넘을 수 있습니다.
❸ 하단 스코프 (함수 B의 로컬 스코프)
- 새로운 로컬 변수 — 관문을 통해 들어온 데이터를 이 방만의 새로운 로컬 변수
x에 할당합니다. ⭐ 이름은 상단과 같을지라도 완전히 독립된 별개의 메모리 공간입니다. - 후속 연산 (
+5) — 전달받은 값으로 로직을 수행한 뒤 외부로 반환(Return) 합니다.
주황색 점선이 프로그램의 실행 흐름(Control Flow) 이자 데이터의 이동 경로입니다.
⚠️ 가장 많이 하는 착각: "이름이 같으니까 같은 변수겠지." ❌ 이름은 그 방 안에서만 통하는 호칭입니다. 방이 다르면 완전히 다른 메모리입니다. 이걸 놓치면 "함수 안에서 값을 바꿨는데 밖에서는 안 바뀌어요"가 끝없이 반복됩니다.
11.2 🐍 FastAPI (Backend) 코드 매핑
상단 함수에서 연산된 값을 매개변수를 통해 하단 비즈니스 로직 함수로 전달하는 흐름입니다.
# 하단 스코프 (함수 B): 관문(매개변수 x)을 열어두고 대기
def calculate_next_value(x: int): # 초록색 관문 (매개변수)
result = x + 5 # 하단 내부 연산 (+5)
return result # 주황색 화살표 아웃풋
# 상단 스코프 (함수 A / 라우터)
@app.get("/process")
def process_data():
x = 10
temporary_value = x + 2 # 상단 내부 연산 (+2) -> 12
# 관문(매개변수)을 통해 하단 함수로 데이터 주입
final_output = calculate_next_value(temporary_value)
return {"result": final_output}
calculate_next_value 안의 x와 process_data 안의 x는 이름만 같고 아무 관계가 없습니다. 관계를 맺어주는 것은 오직 호출 시점에 괄호 안으로 넘긴 값 하나뿐입니다.
11.3 ⚛️ Next.js / React (Frontend) 코드 매핑
부모 컴포넌트가 자식 컴포넌트에게 Props(매개변수) 를 통해 데이터를 단방향으로 하향 전달하는 구조와 정확히 일치합니다.
// 하단 스코프: 자식 컴포넌트 (Child Component)
// 부모가 데이터를 던져줄 관문(Props)인 x를 정의함
function ChildComponent({ x }) { // 초록색 관문 (매개변수/Props)
const localCalc = x + 5; // 하단 내부 연산 (+5)
return <div>{localCalc}</div>;
}
// 상단 스코프: 부모 컴포넌트 (Parent Component)
export default function ParentComponent() {
const x = 10;
const passedValue = x + 2; // 상단 내부 연산 (+2)
// 자식이 열어둔 관문(x)에 가공된 데이터를 흘려보냄
return <ChildComponent x={passedValue} />;
}
⭐ React의 props가 왜 읽기 전용인지가 여기서 설명됩니다. props는 관문을 통과해 들어온 값이라 자식이 고쳐도 부모의 메모리는 바뀌지 않습니다(방이 다르니까요). 그래서 React는 "props를 수정하지 말라"고 못 박습니다. 수정해봐야 소용이 없기 때문입니다.
11.4 아키텍처적 의의 — 결합도(Coupling) 완화
이 도식이 보여주는 가장 중요한 프로그래밍 원칙은 느슨한 결합(Loose Coupling) 입니다.
하단 스코프는 상단 스코프의 내부 로직이나 변수 명을 알 필요가 전혀 없으며, 오직 관문(매개변수)으로 들어오는 데이터의 규격만 맞으면 독립적으로 작동합니다.
이는 코드의 재사용성을 높이고 모듈별 단위 테스트를 쉽게 만드는 핵심 기반이 됩니다.
11.5 ⭐ 매개변수와 Port는 같은 것이다
이제 이번 주의 마지막 연결이 완성됩니다.
| 매개변수 (Parameter) | 포트 (Port) | |
|---|---|---|
| 크기 | 함수 하나 | 시스템 경계 |
| 무엇을 가르는가 | 두 개의 스코프 | 코어와 인프라 |
| 통로의 규격 | 타입 (x: int) | 인터페이스 시그니처 |
| 통과 안 하면 | 접근 불가 (스코프 에러) | 접근 금지 (아키텍처 규칙 위반) |
| 얻는 것 | 느슨한 결합 | 느슨한 결합 |
| 다른 쪽을 알아야 하는가 | ❌ 규격만 알면 됨 | ❌ 규격만 알면 됨 |
8.3절의 "바로 가는 곳에 Port라는 문을 세워 통과" 와 11.1절의 "매개변수만이 유일한 통로" 는 완전히 같은 문장입니다. 배율만 다릅니다.
⭐ 그래서 이번 주의 결론은 이렇습니다. 프로그래밍이란, 서로 다른 두 세계 사이에 문을 세우고 그 문의 규격을 정하는 일이다. 문이 함수 크기면 매개변수, 클래스 크기면 추상 클래스, 시스템 크기면 Port입니다.
12. 풀스택 3계층 — Next.js → FastAPI → PostgreSQL
12.1 전체 흐름
이번 주 내내 다룬 계층 구조가 실제 기술 스택으로 내려오면 이렇게 됩니다.
Next.js --🔒 HTTP(S) API REQUESTS--> FastAPI --DATABASE QUERIES (ORM)--> PostgreSQL
◄──────── DATA RESPONSE ─────────
12.2 각 층의 책임
| 계층 | 기술 | 책임 |
|---|---|---|
| 1. FRONTEND / UI LAYER | Next.js | User Interface & UX / SSR(서버 사이드 렌더링) & Static Generation / API Routes (선택적) |
| 2. BACKEND / LOGIC LAYER | FastAPI | High-Performance Asynchronous Python(3주차의 비동기) / Business Logic & Core Functionality / AI Model & Data Processing(예: Pandas) / Authentication & Authorization / 자동 API 문서(Swagger) |
| 3. DATABASE LAYER | PostgreSQL | RDBMS / ACID Compliance(3주차 7.2절) / SQL / Scalable Data Storage |
12.3 이 조합의 이득 (Key Benefits)
- Python Ecosystem for AI & Data — 백엔드 언어가 곧 AI 언어입니다. 모델을 다른 서버로 보낼 필요가 없습니다.
- Fast API Development — 타입 힌트만 쓰면 검증·문서가 따라옵니다.
- Robust Data Storage — 트랜잭션과 무결성을 DB가 책임집니다.
12.4 이 그림을 이번 주 개념으로 다시 읽기
| 층 | GoF 분류 (1장) | CQRS (2장) | 클린 아키텍처 (6장) | 스코프 (10장) |
|---|---|---|---|---|
| Next.js | 행위 (Behavioral) | Command·Query 발신 | Frameworks & Drivers | .env.local / Context / props |
| FastAPI | 생성 (Creational) | Command·Query 분기 | Interface Adapters + Use Cases | .env / Request / 라우터 로컬 변수 |
| PostgreSQL | 구조 (Structural) | Write DB | Frameworks & Drivers | (외부 시스템) |
⭐ 같은 시스템을 네 개의 서로 다른 자를 대고 재본 것입니다. 이 표를 그릴 수 있다면 이번 주 내용은 다 이해한 것입니다.
13. 실습 — 이번 주에 손으로 한 것
이번 주 실습의 목표는 하나입니다. "타이타닉 CSV 파일 하나를, 헥사고날 구조를 통과시켜 DB까지 보내라." 파일 하나 올리는 데 왜 이렇게 폴더가 많이 필요한지 — 그걸 몸으로 겪는 것이 실습의 본질입니다.
13.1 1단계 — 인바운드 어댑터부터 (05-27)
가장 바깥부터 시작합니다. 아직 Port도 Use Case도 없이 라우터 하나만 만듭니다.
AI에게 준 지시
@file:titanic_command_router.py 를 헥사고날 구조로 연결해줘.
스키마의 타입은 전부 str 로 하고, 컬럼명 sex 를 gender 로 변형해줘.
두 가지 결정의 이유
| 결정 | 이유 |
|---|---|
타입을 전부 str 로 | CSV에서 올라온 값은 전부 문자열입니다. 어댑터 단계에서 섣불리 타입을 바꾸면, 파싱 에러가 어댑터에서 터져 원인 파악이 어려워집니다. 타입 변환은 안쪽(유스케이스·도메인)에서 합니다. |
sex → gender | 외부 데이터의 컬럼명을 우리 도메인의 언어로 번역하는 것입니다. 이것이 Interface Adapter 계층의 '통역' 역할입니다 (6.3절 ③). |
⭐ 외부 세계의 단어를 그대로 코어에 들이지 않는다 — 이것이 어댑터가 존재하는 이유입니다. DDD 용어로는 보편적 언어(Ubiquitous Language) 를 지키는 일입니다.
13.2 2단계 — 파일 업로드 엔드포인트 붙이기 (05-28)
@sym:james_router 에 프리픽스 "/titanic/james" 로 fileupload 를 추가해서
POST 로 컬럼 데이터를 받고 sex → gender 로 변환해줘.
그런데 프론트에서 호출했더니 fetch 메소드가 누락되어 붙지 않았습니다. 엔드포인트 /titanic/james/upload 를 라우터 메소드에 직접 연결합니다.
@james_router.post("/upload")
async def upload_titanic_file(file: UploadFile = File(...)):
"""Titanic CSV 파일을 업로드하고 Sex 컬럼을 gender로 변환합니다."""
records = _parse_csv_file(file)
return {"count": len(records), "records": records}
이 코드에서 읽어야 할 것
| 요소 | 의미 |
|---|---|
@james_router.post | POST — 상태를 바꾸는 요청. CQRS의 Command 쪽 (2.3절) |
UploadFile = File(...) | FastAPI가 제공하는 파일 수신 방식. ...(Ellipsis)는 필수 값이라는 뜻 |
async def | 3주차에서 배운 비동기. 파일 I/O는 대기 시간이 있으므로 비동기가 효과적입니다 |
_parse_csv_file | 앞의 _ 는 파이썬 관례로 "이 모듈 내부에서만 쓰는 함수" 라는 표시 |
반환값 {"count": ..., "records": ...} | ⚠️ 이 시점의 코드는 아직 CQRS 원칙을 어기고 있습니다. Command인데 데이터를 통째로 반환합니다. |
⚠️ 마지막 항목이 중요합니다. 처음부터 완벽하게 짜지 않습니다. 먼저 동작하게 만들고, 그 다음 구조로 옮깁니다. 이번 주 실습이 정확히 이 순서입니다.
13.3 3단계 — 인바운드 → 인풋 포트 → 유스케이스 (05-28)
이제 라우터에 뭉쳐 있던 로직을 단계별로 안쪽으로 밀어 넣습니다.
인바운드 → 인풋 포트 → 유스케이스로 데이터를 단계별로 이동시켜줘.
james_use_case.py → james_command.py
| 파일 | 자리 | 추상/구상 |
|---|---|---|
adapter/inbound/api/v1/james_router.py | Inbound Adapter | 구상 |
app/ports/input/james_use_case.py | Input Port | 추상 |
app/use_cases/james_command.py | Use Case | 구상(비즈니스적) |
이 순서로 옮기는 이유 — 라우터에 로직이 있으면 웹 없이는 테스트할 수 없습니다. 유스케이스로 옮기면 FastAPI를 띄우지 않고도 함수 호출 한 번으로 검증됩니다.
13.4 4단계 — 추상 클래스를 실제로 쓰기 (05-29)
3단계까지는 파일만 나눠 놓고 정작 추상 클래스는 쓰지 않은 상태였습니다. 그걸 고칩니다.
추상클래스 미사용 → 사용으로 전환해줘.
use_cases/james_command.py 에 ports/input/james_use_case.py 를 두고,
adapter/inbound/api/v1/james_router.py 에서 전달한 데이터를
ports/output/james_repository.py 로 이동시켜줘.
완성된 한 바퀴 (8.5절의 그림이 여기서 실현됩니다)
james_router.py [구상] Inbound Adapter
↓ 호출
james_use_case.py [추상] Input Port ← ABC + @abstractmethod
↑ 구현
james_command.py [구상] Use Case
↓ 호출
james_repository.py [추상] Output Port ← ABC + @abstractmethod
↑ 구현
james_pg_repository.py [구상] Outbound Adapter
⭐ "파일만 나눠 놓는 것"과 "추상 클래스를 쓰는 것"은 다릅니다. 파일만 나누면 라우터가 여전히 유스케이스 구현체를 직접 import 하므로 의존성이 역전되지 않습니다. 추상 클래스를 넣어야 비로소 라우터가 Port만 보고 호출하게 되고, 그때 7.4절의 화살표가 꺾입니다.
13.5 프론트엔드 — 목록 화면과 페이지네이션 (05-28)
"1. 데이터 수집" 아래에 "2. 탑승자 목록" 을 추가하고,
페이지당 50명씩 페이지네이션 해줘.
왜 필요한가 — 타이타닉 데이터는 891행이고 실무 데이터는 89만 행일 수 있습니다. 페이지네이션은 처음부터 넣는 게 맞습니다.
💡 CQRS 관점에서 페이지네이션은 Query 쪽의 전형적 요구입니다. "화면에 맞춰 잘라서 준다"는 비즈니스 규칙이 아니라 표현 요구이므로 Command 모델에 넣으면 안 됩니다.
13.6 프론트엔드 — 네비바와 사이드바 (05-27)
- 레슨 네비바 클릭 시 서브 메뉴 + 왼쪽 사이드바가 나오는 구조로 만듭니다.
- F12로 모바일 환경을 켰을 때 사이드바가 깨지면, 햄버거 형태로 변형해달라고 요청합니다.
💡 F12 디바이스 모드는 배포 전 필수 검증입니다. 데스크톱에서만 확인하고 배포하면 사용자의 절반 이상이 깨진 화면을 봅니다.
13.7 백엔드에서 CSV 파일 제거하기 (05-27)
2.9절에서 개념으로 배운 것을 실제로 적용합니다.
하는 일
- 백엔드 폴더 안에 있던
.csv파일을 삭제합니다. - 데이터를 파일이 아니라 업로드 → DB 저장 경로로 바꿉니다.
- 요청마다 필요한 DB 세션은
Depends로 받습니다.
from fastapi import Depends
@james_router.get("/passengers")
async def list_passengers(page: int = 1, size: int = 50, session=Depends(get_session)):
...
Depends(get_session) 는 요청이 들어올 때 세션을 만들고 응답이 나갈 때 정리합니다. 요청과 요청 사이에 아무것도 남기지 않는다 — 이것이 무상태(Stateless)입니다.
13.8 배포 — Vercel 도메인 연동 (05-26)
⚠️ 도메인을 붙일 때 자주 놓치는 지점입니다.
디폴트 도메인과
www도메인이 함께 연결되어 있어야 합니다.
example.cloud만 연결하고www.example.cloud를 빼먹으면, 사용자가 습관적으로www.를 붙여 들어왔을 때 아무것도 안 뜹니다.- Apex 도메인(
example.cloud)과 WWW 도메인(www.example.cloud)을 둘 다 등록하고, 하나를 대표(primary)로 지정한 뒤 나머지를 리다이렉트 시키는 것이 표준 구성입니다.
13.9 실습 체크리스트
| # | 확인 항목 | 통과 기준 |
|---|---|---|
| 1 | app/ 안의 파일에 fastapi, sqlalchemy import가 있는가 | 없어야 통과 (6.2절) |
| 2 | ports/input/ 과 ports/output/ 이 나뉘어 있는가 | 나뉘어야 통과 (8.4절 ISP) |
| 3 | Port 클래스가 ABC를 상속하고 @abstractmethod가 붙었는가 | 붙어야 통과 (9.3절) |
| 4 | 라우터가 Use Case 구현체를 직접 import 하는가 | 하면 실패 (13.4절) |
| 5 | POST 엔드포인트가 큰 데이터를 통째로 반환하는가 | 하면 재검토 (2.1절) |
| 6 | 백엔드 폴더에 .csv, .json 같은 상태 파일이 남아 있는가 | 남으면 실패 (2.9절) |
| 7 | 모바일 뷰(F12)에서 레이아웃이 깨지는가 | 깨지면 실패 (13.6절) |
| 8 | www 도메인이 연결되어 있는가 | 안 되어 있으면 실패 (13.8절) |
14. 이 주의 읽을거리와 그 맥락
14.1 아키텍처
| 자료 | 왜 이 주에 나왔는가 |
|---|---|
| CQRS 패턴 🔗 https://junuuu.tistory.com/891 | 2장의 근간. "읽기 빈도가 쓰기보다 훨씬 높을 때, 쓰기용 모델을 조회에 그대로 쓰면 정규화 테이블 조인 연산으로 성능이 나빠진다"는 문제의식이 그대로 이 글에서 나옵니다. |
| Port & Adapter 패턴을 왜 사용하는가 🔗 https://codingjw.tistory.com/216 | 7장. "왜(Why)"에 집중한 글입니다. 구조를 먼저 배우면 "이걸 왜 이렇게까지"라는 반발이 생기는데, 이 글이 그 지점을 답합니다. |
| DIP는 Dependency Injection이 아니라 Dependency Inversion 🔗 https://guhonga.tistory.com/128 | 5.7절의 정정. 수업 중 실제로 혼동이 있었고, 바로잡은 자료입니다. DIP(원칙) ≠ DI(기법) 입니다. |
| SOLID 원칙 — 카카오뱅크 기술 블로그 🔗 https://tech.kakaobank.com/posts/2411-solid-truth-or-myths-for-developers/ | 5장. 제목이 "개발자를 위한 SOLID, 진실 혹은 신화"입니다. 원칙을 교조적으로 적용했을 때 생기는 문제까지 다루므로, 원칙을 배운 직후에 읽어야 균형이 맞습니다. |
| 퍼사드(Facade) 패턴 🔗 https://refactoring.guru/ko/design-patterns/facade | 1.4절. 불어로 '정면'이라는 어원부터 짚어야 오래 남습니다. gateway/ 폴더의 정체가 이것입니다 (4.4절). |
14.2 DB · Python
| 자료 | 왜 이 주에 나왔는가 |
|---|---|
| ORM(Object-Relational-Mapping)이란 🔗 https://velog.io/@mpfo0106/ORMOject-Relational-Mapping-Framework-%EB%9E%80 | 7.3절 5단계(Outbound Adapter)에서 실제로 쓰는 기술입니다. 3주차에서 배운 "객체(메모리)와 개체(DB)"를 잇는 다리가 ORM입니다. |
| 데이터 모델링 기본 — 2NF와 3NF | 3.6절. CQRS의 Write DB가 "정규화되어 있다"는 말을 이해하려면 정규화 단계를 알아야 합니다. 2NF는 부분적 종속 제거, 3NF는 이행적 종속 제거입니다. |
14.3 AI 개발 환경의 변화
하네스 영상 — "부탁과 구조, 이 차이가 핵심" 🔗 https://youtu.be/6MYZ7fMhKPY
이 영상이 이번 주에 나온 것은 우연이 아닙니다.
- 부탁 — "이 기능 좀 만들어줘". 결과가 매번 다릅니다. AI가 어디에 코드를 놓을지 알 수 없습니다.
- 구조 — "
app/ports/input/에 Input Port를 만들고,app/use_cases/에 구현체를 만들어줘". 결과가 항상 같습니다.
3주차의 하네스(Harness) 개념이 여기서 아키텍처와 만납니다. 아키텍처는 AI에게 주는 하네스입니다. 폴더 구조가 명확하면 AI가 헤매지 않습니다. .cursorrules 파일이 프로젝트 루트에 있는 이유가 이것입니다 (4.1절 트리).
삼성전자·SK하이닉스 클로드 개발 투자 — 개발 툴을 클로드 코드로 전환
대기업이 개발 툴 전체를 AI 코딩 도구로 전환한다는 소식입니다. 이 뉴스의 의미는 "AI가 코드를 짠다"가 아니라 "AI가 코드를 짜는 것을 전제로 조직의 개발 프로세스를 재설계한다" 는 뜻입니다. 그 전제 아래서 개발자에게 남는 일은 구조를 정하고 경계를 긋는 일 — 정확히 이번 주에 배운 것입니다.
14.4 커리어 — 해커톤 · 공모전
이번 주에 정리된 마감 일정입니다. 아키텍처를 배운 직후에 나오는 이유는 포트폴리오에 넣을 결과물을 만들 시점이기 때문입니다.
| 대회 | 일정 | 링크 |
|---|---|---|
| 한컴 코드게이트 | ~7/8 | 🔗 https://eduhancom.com/codegate |
| 2026 공공기관 AI 혁신 챌린지 | ~8/28 | 🔗 https://aichallenge4all.or.kr/competitions/public-ai |
| JB금융그룹 해커톤 | ~6/12 | |
| 삼성전자 SCPC (AI 파트) | 6/11 ~ 7/10 | 🔗 https://research.samsung.com/scpc |
| 과학기술정보통신부 | 6/1 ~ 8/28 | |
| 문화체육관광부 | 4/27 ~ 6/26 | 🔗 https://www.culture.go.kr/digicon/ |
| 방산 AI | ~6/14 | |
| 디지털 헬스케어 | ~6/29 |
14.5 커리어 — KDT 수료자 채용 기업 지원금
청년일자리도약장려금 — KDT(K-Digital Training) 수료자를 채용하는 기업이 받는 지원금입니다. 구직할 때 "저를 채용하면 회사가 지원금을 받습니다" 라고 말할 근거이므로 정확히 알아두세요.
| 항목 | 내용 |
|---|---|
| 지원 규모 | 1인당 1년 최대 720만원 (월 60만원 × 6개월 이상 정규직) |
| 신청 주체 | 상시근로자 5인 이상 우선지원대상기업 |
| 신청처 | 관할 고용센터 |
| 청년 요건 ① | 만 15~34세 |
| 청년 요건 ② | 미취업 상태 |
| 청년 요건 ③ | 아래 취업애로청년 요건 중 1개 충족 |
취업애로청년 요건 (하나만 충족하면 됨)
- 4개월 이상 연속 실업
- 고졸 이하
- 최종학교 졸업 후 고용보험 가입 12개월 미만
- (그 외 고시된 요건들)
부록 A. 용어 사전
| 용어 | 영문 | 상위 범주 | 정의 |
|---|---|---|---|
| CQRS | Command Query Responsibility Segregation | 아키텍처 패턴 | 명령(쓰기) 모델과 조회(읽기) 모델을 분리하는 패턴 |
| 명령 | Command | CQRS 구성요소 | 상태를 변경하는 작업. 결과 데이터를 반환하지 않음 |
| 질의 | Query | CQRS 구성요소 | 상태를 변경하지 않는 조회. 부작용 없음 |
| 최종 일관성 | Eventual Consistency | 일관성 모델 | 지금은 다를 수 있지만 결국 같아지는 상태 |
| DQL | Data Query Language | SQL 분류(목적별) | SELECT. 조회 전용 |
| DML | Data Manipulation Language | SQL 분류(목적별) | INSERT·UPDATE·DELETE. 데이터 상태 변경 |
| DDL | Data Definition Language | SQL 분류(대상별) | CREATE·ALTER·DROP·TRUNCATE. 구조 정의 |
| DCL | Data Control Language | SQL 분류(대상별) | GRANT·REVOKE. 권한 관리 |
| TCL | Transaction Control Language | SQL 분류(대상별) | COMMIT·ROLLBACK. 트랜잭션 제어 |
| 무상태 | Stateless | 서버 성질 | 요청 사이에 서버가 아무 상태도 보관하지 않음 |
Depends | — | FastAPI 기능 | 요청마다 의존 객체를 만들고 정리하는 의존성 주입 장치 |
| SOLID | — | 설계 원칙 모음 | 객체지향 설계 5원칙의 머리글자 |
| SRP | Single Responsibility Principle | SOLID | 모듈은 하나의 변경 이유(액터)만 가져야 함 |
| OCP | Open-Closed Principle | SOLID | 확장에는 열려 있고 수정에는 닫혀 있어야 함 |
| LSP | Liskov Substitution Principle | SOLID | 하위 타입은 상위 타입을 안전하게 대체해야 함 |
| ISP | Interface Segregation Principle | SOLID | 안 쓰는 메서드에 의존하지 않도록 인터페이스를 쪼갬 |
| DIP | Dependency Inversion Principle | SOLID | 고수준·저수준 둘 다 추상에 의존해야 함 |
| 클린 아키텍처 | Clean Architecture | 아키텍처 | 동심원 4계층, 의존성은 안쪽으로만 |
| 의존성 규칙 | The Dependency Rule | 클린 아키텍처 규칙 | 모든 의존성은 안쪽 원을 향한다 |
| 엔티티(클린) | Entities | 클린 계층 | 가장 안쪽. 프레임워크 무관한 핵심 업무 규칙 |
| 유스케이스 | Use Cases | 클린 계층 | 애플리케이션 특화 업무 규칙. 시나리오 흐름 |
| 인터페이스 어댑터 | Interface Adapters | 클린 계층 | 통역 계층. Controller·Presenter·Gateway |
| 프레임워크와 드라이버 | Frameworks & Drivers | 클린 계층 | 가장 바깥. 교체 가능한 세부 기술 |
| 헥사고날 아키텍처 | Hexagonal Architecture | 아키텍처 | 포트와 어댑터로 코어를 외부에서 격리 |
| 포트 | Port | 추상 | 코어가 정의한 계약(인터페이스) |
| 어댑터 | Adapter | 구상 | 포트를 특정 기술로 구현한 것 |
| Input Port | Inbound Port | 포트 | 외부가 코어를 호출할 때의 진입 계약 |
| Output Port | Outbound Port | 포트 | 코어가 외부에 요청할 때의 출구 계약 |
| Inbound Adapter | Driving Adapter | 어댑터 | 외부 요청을 받아 Input Port를 호출 (라우터·CLI) |
| Outbound Adapter | Driven Adapter | 어댑터 | Output Port를 구현 (DB 리포지토리·파일 로더) |
| 기술적 구상 | — | 구상의 종류 | 어댑터. 어떤 기술로 할지를 기술 |
| 비즈니스적 구상 | — | 구상의 종류 | 유스케이스. 어떤 순서·규칙으로 할지를 기술 |
| 제어의 흐름 | Flow of Control | 런타임 개념 | 실행 시 누가 누구를 호출하는가 |
| 의존성의 방향 | Dependency Direction | 컴파일타임 개념 | 코드가 누구를 import 하는가 |
| Port Gate | — | 설계 관행 | 직행 경로를 막고 포트라는 문을 세워 통과시키는 것 |
| DDD | Domain-Driven Design | 설계 방법론 | 비즈니스 도메인을 코드 구조의 중심에 두는 방법론 |
abc | Abstract Base Class | 파이썬 모듈 | 추상 클래스를 만드는 표준 모듈 |
ABC | — | 파이썬 클래스 | 상속하면 추상 기반 클래스가 됨 |
@abstractmethod | — | 파이썬 데코레이터 | 자식이 반드시 구현하도록 강제 |
| 추상 | Abstraction | 설계 개념 | What. 무엇을 하는가. 변하지 않는 계약 |
| 구상 | Concretion | 설계 개념 | How. 어떻게 하는가. 변하는 기술 세부사항 |
| 스코프 | Scope | 언어 개념 | 이름이 유효한 범위 |
| 로컬 변수 | Local Variable | 스코프 | 함수·메서드 안에서만 유효 |
| 필드 | Field | 스코프 | 클래스에 선언된 변수 (인스턴스 변수 + 클래스 변수) |
| 인스턴스 변수 | Instance Variable | 필드의 한 종류 | 인스턴스마다 하나씩 |
| 컨텍스트 | Context | 스코프 | 애플리케이션 실행 문맥·상태 |
| 전역 변수 | Global Variable | 스코프 | 프로세스 전체에서 접근 가능 |
| 환경 변수 | Environment Variable | 스코프 | OS·컨테이너 레벨에서 주입. 코드 바깥 |
| 매개변수 | Parameter | 관문 | 스코프 사이의 유일한 통로 |
| 느슨한 결합 | Loose Coupling | 설계 성질 | 서로의 내부를 모른 채 규격만으로 연결됨 |
| 전략 패턴 | Strategy | 행위 패턴 | 알고리즘을 캡슐화해 교체 가능하게 함 |
| 싱글톤 패턴 | Singleton | 생성 패턴 | 인스턴스가 하나만 존재하도록 보장 |
| 어댑터 패턴 | Adapter | 구조 패턴 | 비호환 인터페이스를 연결 |
| 퍼사드 패턴 | Facade | 구조 패턴 | 복잡한 서브시스템에 단순한 정면 하나를 제공 |
부록 B. 스스로 확인하기
먼저 답해보고, 막히면 옆의 절을 펼치세요.
| # | 질문 | 답이 있는 곳 |
|---|---|---|
| 1 | 프론트=행위 / 백엔드=생성 / DB=구조 매핑이 성립하는 이유는? | 1.1 |
| 2 | CQRS의 핵심 한 줄을 그대로 쓸 수 있는가? | 2.1 |
| 3 | 기존 CRUD의 두 가지 한계는? | 2.2 |
| 4 | CQRS 1단계와 2단계의 차이는? | 2.4~2.5 |
| 5 | 최종 일관성을 써도 되는 곳과 안 되는 곳의 기준은? | 2.6 |
| 6 | SELECT만 DQL이고 나머지가 DML인 이유는? | 3.2 |
| 7 | alembic/이 DDL이고 apps/가 DML·DQL인 이유는? | 4.2 |
| 8 | SRP의 '액터'란 무엇인가? | 5.3 |
| 9 | DIP와 DI는 무엇이 다른가? | 5.7 |
| 10 | 클린 아키텍처의 의존성 규칙을 한 문장으로? | 6.2 |
| 11 | 클린 4계층을 안쪽부터 나열할 수 있는가? | 6.3 |
| 12 | Use Case가 코어 안에 있으면서도 '구상'인 이유는? | 7.2 |
| 13 | 제어의 흐름과 의존성의 방향이 어긋나는 지점은 정확히 어디인가? | 7.4 |
| 14 | 전략 패턴·DIP·IoC·DI·포트&어댑터의 공통 구조는? | 7.5 |
| 15 | ports/를 input/과 output/으로 나누는 근거가 되는 SOLID 원칙은? | 8.4 |
| 16 | 추상화하는 근본 이유를 한 문장으로? | 9.1 |
| 17 | 추상화가 나타나는 5곳을 열거할 수 있는가? | 9.4 |
| 18 | 환경 변수가 전역 변수보다 바깥인 이유는? | 10.2 |
| 19 | 함수 안팎의 x가 이름이 같아도 다른 이유는? | 11.1 |
| 20 | 매개변수와 Port가 같은 것인 이유는? | 11.5 |
부록 C. 자주 하는 실수
| 실수 | 바로잡기 |
|---|---|
| "DIP = Dependency Injection" | ❌ Dependency Inversion(의존 역전) 입니다. DI는 별개의 기법 |
| "DI를 쓰면 DIP가 지켜진다" | ⚠️ 구상 클래스를 주입하면 DI는 맞지만 DIP는 위반 |
| "Use Case는 코어 안에 있으니 추상이다" | ❌ 비즈니스적 구상입니다. 추상은 Port |
| "Port는 바깥이 만들어서 코어에 준다" | ❌ 코어가 선언합니다. 이 방향이 곧 DIP |
| "제어가 흐르는 쪽으로 import 하면 된다" | ❌ 흐름과 의존성은 어긋납니다. Outbound 구간에서 화살표가 꺾입니다 |
| "폴더만 나누면 헥사고날이다" | ⚠️ 추상 클래스를 안 쓰면 라우터가 구현체를 직접 import 합니다. 역전이 안 일어납니다 |
"ports/ 폴더에 파일 하나 두면 됨" | ⚠️ input/과 output/을 나눠야 ISP입니다 |
| "명령(Command)도 데이터를 리턴하는 게 편하다" | ⚠️ 성공/실패 또는 ID만 반환하는 게 원칙 |
| "CQRS는 무조건 DB 두 개" | ❌ 1단계는 클래스만 분리합니다. 대부분은 여기서 끝내도 됩니다 |
| "최종 일관성이 최신 기술이니 다 적용" | ❌ 잔액·재고엔 쓰면 안 됩니다. 틀려도 되는 시간이 있느냐가 기준 |
"TRUNCATE 했다가 ROLLBACK 하면 되지" | ❌ TRUNCATE는 DDL이라 대체로 롤백 불가. 되돌리려면 DELETE |
| "정규화는 무조건 많이 할수록 좋다" | ⚠️ 조인이 늘어납니다. Read 쪽은 일부러 역정규화 합니다 |
| "그 계층엔 그 GoF 분류만 쓴다" | ❌ 금지 규칙이 아니라 무게중심입니다 |
| "SRP = 클래스는 한 가지 일만" | ⚠️ 정확히는 변경 이유(액터)가 하나여야 한다는 뜻 |
| "LSP는 상속만 잘하면 지켜진다" | ⚠️ 반환값의 의미와 예외까지 같아야 지켜집니다 |
| "백엔드에 CSV 두면 편한데 왜 지우나" | ⚠️ 서버를 늘리는 순간 어느 서버에 있는지 알 수 없게 됩니다 |
"함수 밖 x를 함수 안에서 고치면 밖도 바뀐다" | ❌ 별개의 메모리입니다. 이름만 같습니다 |
| "값은 넓은 스코프에 두면 편하다" | ⚠️ 필요한 것보다 넓은 스코프가 거의 모든 버그의 시작입니다 |
| "환경 변수와 전역 변수는 같은 것" | ❌ 전역 변수는 코드 안, 환경 변수는 코드 밖입니다 |
"usecases, services, orchestrators 섞어 써도 됨" | ⚠️ 하나로 통일하세요. 안 그러면 답 없는 논쟁이 매주 반복됩니다 |
| "Vercel에 도메인 하나만 붙이면 끝" | ⚠️ 디폴트 도메인과 www 를 함께 연결해야 합니다 |
| "AI에게 '이 기능 만들어줘'라고 하면 됨" | ⚠️ 부탁이 아니라 구조를 주세요. 폴더 경로까지 지정하면 결과가 안정됩니다 |
부록 D. 날짜별 색인
이 장의 내용이 실제 수업에서 어느 날 다뤄졌는지입니다.
| 날짜 | 다룬 내용 | 이 장의 절 |
|---|---|---|
| 5/25 (월) | (휴강) | — |
| 5/26 (화) | 웹 3계층 ↔ GoF 3분류 매핑(행위·생성·구조), 옵저버·전략·싱글톤·팩토리·프록시·어댑터·퍼사드, CQRS 패턴 전반(CRUD 한계, Command/Query, 1·2단계 분리, 이벤트 동기화, 최종 일관성), 정규화 2NF/3NF, Vercel 도메인 연동, 해커톤 일정, 청년일자리도약장려금 | 1, 2.1 |
| 5/27 (수) | SQL 명령어 체계(목적별 DQL·DML / 대상별 DDL·DCL·TCL), CRUD 매핑, "상태변화가 중심", 백엔드 CSV 제거 = 무상태(Sticky Session·장애극복·Depends·JWT·Redis), 퍼사드 어원, 헥사고날 이름 충돌 정리(usecases/application_services/orchestrators, gateways/middlewares/auth), ORM, 타이타닉 인바운드 어댑터 스키마(str·sex→gender), 네비바·사이드바·모바일 햄버거, 하네스 영상 | 3, 2.9, 1.4, 4.5, 13.1, 13.6, 13.7, 14.3 |
| 5/28 (목) | 프로젝트 디렉터리 ↔ 개념 매핑(alembic=DDL / apps=DML·DQL / core=Docker), 서비스 코드네임과 gateway, SOLID 5원칙과 클린 아키텍처 연계, 클린 아키텍처 의존성 규칙·4계층, DIP 실전 결합(UserRepository), 헥사고날 추상/구상(Ports·Adapters·Use Cases), Inbound/Outbound(Driving/Driven), 제어의 흐름 ≠ 의존성의 방향, 5단계 흐름도, Next.js→FastAPI→PostgreSQL 풀스택, Port&Adapter 읽을거리, 타이타닉 업로드 엔드포인트·유스케이스 이동·페이지네이션 | 4, 5, 6, 7.1 |
| 5/29 (금) | 추상화하는 근본 이유(방파제, How/What), abc·ABC·@abstractmethod, 추상화가 나타나는 5곳, DIP 정정(Inversion≠Injection), 카카오뱅크 SOLID 글, CLEAN HEXAGONAL DDD 폴더 구조(adapter/app/domain, Port Gate, ISP), 스코프 계층 구조와 Next.js/FastAPI 매핑, 매개변수=관문(모래시계), 백엔드-프론트엔드 대칭, 추상클래스 전환 실습, 해커톤 일정, 대기업 AI 개발툴 전환 | 9, 5.7, 8, 10, 11, 13.4, 14.1, 14.3~14.4 |
부록 E. Gemini / NotebookLM 프롬프트
복습 문제 받기
이 5주차 교재만 근거로 8문제를 내줘. 비전공자 기준으로.
"CQRS 핵심 한 줄"과 "제어의 흐름 ≠ 의존성의 방향"은 반드시 포함해줘.
답은 바로 주지 말고, 내가 답한 다음에 채점하고
틀린 이유를 교재의 어느 절을 근거로 설명해줘. 교재에 없는 내용은 꺼내지 마.
인포그래픽 ① — 이 장의 중심
「7.4 제어의 흐름 ≠ 의존성의 방향」을 인포그래픽 1장으로 만들어줘.
- 가로로 5개 박스: Inbound Adapter / Input Port / Use Case / Output Port / Outbound Adapter
- 위쪽에 "제어의 흐름" 화살표를 왼→오른쪽 한 방향으로
- 아래쪽에 "의존성의 방향" 화살표를 그리되, Port 두 곳에서 화살표가 안쪽으로 꺾이게
- 각 박스에 [추상] / [구상] 태그를 색으로 구분
- 전문용어는 한글(영문) 병기
인포그래픽 ② — CQRS 전체
「2. CQRS」를 한 장으로 만들어줘.
- 왼쪽에 Client, 오른쪽으로 Command Path와 Query Path 두 갈래
- Command Path 끝에 Write DB(RDB·정규화), Query Path 끝에 Read DB(NoSQL·역정규화)
- 두 DB 사이에 "이벤트 동기화 (최종 일관성)" 점선 화살표
- 하단에 핵심 한 줄: "질의는 상태를 바꾸지 않고, 명령은 결과를 반환하지 않는다"
- 1단계(논리적 분리)와 2단계(물리적 분리)를 왼쪽에 세로 띠로 표시
인포그래픽 ③ — 추상화 5곳
「9.4 추상화가 나타나는 5곳」을 한 장으로 만들어줘.
- 가운데 큰 원에 "변화의 중심에서 내 코드를 보호하기 위해"
- 주위에 5개 위성: 객체지향 추상화 / SOLID DIP / 클린 아키텍처 Port / GoF 전략 패턴 / RDB 식별관계
- 각 위성에 "추상에 해당하는 것"과 "구상에 해당하는 것"을 두 줄로
- 다섯 위성이 전부 같은 목적이라는 점이 시각적으로 드러나게
막힐 때
5주차에서 "___" 가 이해가 안 돼.
이 교재의 판별 축 5개(추상/구상, 안쪽/바깥쪽, 읽기/쓰기, 구조정의/데이터조작, 좁은스코프/넓은스코프)만
써서 다시 설명해줘. 교재에 없는 새 비유는 만들지 마.
3주차와 이어붙이기
3주차 교재의 "전략 패턴 = DIP = IoC = DI = 포트&어댑터" 부분과
5주차 교재의 7.5절을 나란히 놓고,
다섯 개념이 각각 어느 '층위'에 속하는지 표로 정리해줘.
그리고 세 주차에 걸쳐 같은 개념이 어떤 이름으로 다시 나왔는지 추적해줘.
다음 장 연결 확인
이 교재에서 "앞으로 다시 만납니다"라고 표시된 부분을 모두 찾아서,
이번 장의 어떤 개념이 나중에 어떤 이름으로 다시 나오는지 표로 정리해줘.
특히 '어댑터 패턴', '퍼사드', 'Repository' 세 단어를 추적해줘.
LangTailor