예이린 통합 플랫폼 — 폴리글랏 MSA를 FastAPI 모듈러 모놀리스로 재편
2026.03 — 현재 · CTO · 아키텍처 리드, 기술 거버넌스와 협업 체계 총괄
Python 3.12, FastAPI, Pydantic v2, SQLAlchemy 2.0, PostgreSQL 16, Alembic, uv, Redis, gunicorn, GitHub Actions, LangGraph, ChromaDB
문제 정의
- 아동 돌봄 플랫폼의 백엔드가 저장소 6개(NestJS 1, FastAPI 3, 프론트 2)에 걸친 MSA로 성장 — 배포 유닛 9개, 데이터베이스 4개, 패키지 매니저 3종, JWT 구현 4벌, 서비스 간 배관 코드 약 3,185줄. 이를 운영하는 개발 인원은 1명.
- 성능 지표는 문제가 아니었음(공식 테스트 12/12 통과, 30일 가동률 99.87%). 문제는 경계의 개수에 비례해 커지는 변경 비용과, 시스템 지식의 최종 저장소가 한 사람의 기억이라는 구조적 리스크(bus factor 1).
- 목표를 "더 빠른 시스템"이 아니라 두 가지로 정의 — 조직이 완전히 통제하고 장기적으로 발전시킬 수 있는 기술자산, 그리고 새 구성원이 규칙을 외우지 않고도 안전하게 기여할 수 있는 협업 구조. 아키텍처 결정의 평가 기준을 개인의 생산성이 아니라 조직의 확장 가능성에 둠.

아키텍처 재편 — 경계를 배포 단위에서 패키지 선언으로
- 모듈러 모놀리스(Modular Monolith)를 채택하되, 경계 유지를 문서가 아닌 도구에 위임 — uv workspace 위에 도메인 패키지 19개와 앱 패키지 6개, 공유 커널 core로 재편.

- 도메인 패키지의
pyproject.toml에는 FastAPI가 존재하지 않음 — 선언하지 않은 것은 import할 수 없으므로, 도메인 레이어의 프레임워크 오염이 의존성 선언 수준에서 차단됨. - core는 전체 코드의 1.1%(1,179줄)로 억제 — 공유 커널의 비대화가 모듈러 모놀리스를 무너뜨리는 첫 번째 원인이라는 판단에서, Base 클래스·세션·설정·인증·예외 핸들러만 남김.

- 도메인 내부는 domain / application / infrastructure 3계층. 인터페이스는 ABC 대신
typing.Protocol(구조적 타이핑)로 선언해 도메인이 구현체의 존재 자체를 모르게 유지. 애플리케이션 계층은 CQRS를 가볍게 적용해 Command/Query Handler 497개로 구성. - 앱 패키지는 라우터·인증·응답 스키마·Depends 조립만 소유 — "도메인은 호출자를 모른다"는 소유권 원칙을 8종의 워크플로우 가이드와 훅으로 상시 검증.

의존성 주입과 트랜잭션 — 프레임워크 마법의 제거
- DI 컨테이너 라이브러리를 의도적으로 배제하고 순수
Depends함수 체인으로 구성 — provider 함수 452개를 직접 작성하는 비용을 수용하는 대신, 마법 없이 전부 grep 가능한 배선을 얻음. FastAPI의 요청 단위 의존성 캐시가 request-scoped 수명주기를 대체. - 트랜잭션 경계를 "요청 = 작업 단위(Unit of Work)"로 통일 — 세션 제너레이터 40줄이 commit/rollback을 소유하고 리포지토리는 flush만 수행. 도메인 경계를 넘는 원자성이 구조적으로 보장되며, 이 규칙은 core의 코드 구조 자체가 강제.

계약의 기계화 — OpenAPI를 단일 진실 원천으로
- 추출된 OpenAPI 스펙(518 paths)을 git에 커밋하고, CI가 매 빌드마다 재추출해 diff 발생 시 빌드를 실패시키는 드리프트 게이트 구축 — 스키마 어긋남이 런타임이 아니라 PR에서 발견됨.
- 프론트엔드 5개는 이 스펙에서 TypeScript 타입·Zod 스키마·TanStack Query 훅을 코드 생성으로 소비 — MSA 시절 주석으로만 존재하던 서비스 간 계약을 기계 검증으로 대체.
- 전 응답 camelCase(
CamelDto) 계약, 반환 타입 어노테이션 강제 등 규칙은 마이그레이션 중 실제로 겪은 사고에서 도출해 명문화.

컨벤션과 거버넌스 — 문서로만 지키는 규칙은 만들지 않는다
- 규칙마다 강제 수단을 함께 설계 — 네이밍·레이어 순수성·도구 단일화는 훅이 차단하고, API 계약은 CI가 게이트하며, 트랜잭션·PK(UUID v7)·soft delete는 core 믹스인이 구조로 강제.
- AI 페어 프로그래밍 시대에 맞춰 아키텍처 규칙을 27개의 패키지별 컨텍스트 문서와 8종의 워크플로우 가이드로 코드베이스 안에 상주시킴 — 규칙이 도구(사람이든 에이전트든)에게 직접 읽히는 구조.
- 스키마 이력은 Alembic 단일 체인(72 리비전, head 1개)으로 관리하고, 배포마다 ORM 메타데이터와 실제 DB를 대조하는 스키마 드리프트 검사를 수행. DB 스키마 문서는 tbls로 컬럼 comment에서 자동 생성.

이전 전략과 운영
- 무중단 조건에서 스트랭글러 패턴(Strangler Fig)을 도메인 단위로 적용 — 하루 1~2개 도메인씩 "도메인 모델 → Command/Query → 인프라"의 동일한 리듬으로 13일간 이전하고 14일째 앱을 조립. 레거시 API 계약은 새 정규화 모델의 파생 필드로 보존해 프론트엔드 무수정 컷오버.
- 배포는 코드 하나·런타임 다섯: gunicorn 5개 그룹이 앱별 포트에서 독립 기동해 장애 격리와 개별 재시작을 유지. CI 5벌을 품질 게이트(ruff·pytest 836·OpenAPI 드리프트) 포함 단일 파이프라인으로 통합, 운영 표면 전체를 bash 350줄로 축소.
- Grafana·Loki·Alloy 기반 관측성 스택을 도입해 레거시에 부재하던 로그 파이프라인 구축.

협업 체계와 기술 리더십 — 사람을 통솔하는 대신 구조가 협업을 가능하게
- OpenAPI 계약을 백엔드-프론트엔드 협업의 공용어로 정착 — 프론트엔드 앱 5개가 스펙에서 타입·SDK·쿼리 훅을 코드 생성으로 소비하므로, 인터페이스 협의가 구두 조율이 아닌 스펙 diff 리뷰로 수렴. 기존 계약 변경을 금지하고 추가만 허용하는 additive-only 원칙을 ADR로 명문화해, 프론트엔드 작업이 백엔드 변경을 두려워하지 않는 환경 구축.
- 온보딩 비용을 구조로 흡수 — 패키지별 컨텍스트 문서 27개와 워크플로우 가이드 8종(엔드포인트 추가, 신규 도메인, 마이그레이션 등 작업 단위)이 코드베이스 안에 상주하고, 규칙 위반은 훅과 CI가 즉시 알림. "어디에 무엇을 작성해야 하는가"를 사람에게 묻지 않아도 되는 저장소를 지향.
- 소유권 경계를 미래의 팀 경계로 설계 — 도메인 패키지 단위의 소유권 원칙은 인원이 늘어날 때 그대로 업무 분담선이 되도록 설계. 실제로 주니어 기여자의 PR 리뷰 프로세스를 정례화해, 리뷰 기준이 개인 취향이 아닌 명문화된 컨벤션을 근거로 작동하는 문화를 마련.
- 의사결정을 조직 자산으로 기록 — 아키텍처 결정은 ADR로, 제품 방향은 PRD·로드맵 문서로, DB 스키마는 컬럼 comment에서 자동 생성되는 문서 사이트로 축적. 결정의 "왜"가 사람의 기억이 아니라 저장소에 남도록 유지.
결과
- 저장소 6 → 1, 배포 유닛 9 → 5, DB 4 → 1(단일 스키마 102 테이블), 환경변수 121 → 28, 인증 구현 4벌 → 160줄 1벌, 서비스 간 배관 3,185줄 → 0줄, CI 테스트 0 → 836개.
- 이전 후 4개월간 엔드포인트 62 → 666으로 확장(신규 서비스 3개 포함) — 같은 1인 리소스로 기능 속도가 유의미하게 상승. 동일 도메인 기준 프로덕션 코드 63% 감소.
- Spring·NestJS 진영의 엔터프라이즈 패턴(Layered Architecture, DDD 전술 패턴, CQRS, DI, Unit of Work, API Contract, 모듈 경계)을 파이썬 생태계로 전부 번역해 프로덕션에서 검증 — 이 경험을 PyCon 발표와 기술 회고 3부작으로 공유.


배움
- 아키텍처는 기술이 아니라 조직의 함수 — 경계의 개수는 팀이 감당할 수 있는 만큼만 유지하고, 나머지는 나중에 쪼갤 수 있는 형태로 남겨 두는 것이 작은 조직의 최적해.
- 시스템 지식을 사람의 기억에서 CI·훅·타입·커밋된 계약으로 이관하는 일이 리팩토링의 본질 — 그 이관이 끝나야 시스템이 개인기가 아닌 조직의 자산이 됨.
- 기술 리더십은 규칙을 말하는 일이 아니라 규칙이 실행되는 환경을 만드는 일 — 사람을 통솔하는 구조는 리더가 자리를 비우면 멈추지만, 협업을 가능하게 하는 구조는 리더 없이도 작동함.
- 경계는 선언만으로 완성되지 않음 — 크로스 도메인 조인의 편의가 경계를 조용히 침식하는 것을 실측으로 확인했고, 완화 장치(공개 join helper)와 다음 과제(import-linter)를 명시해 관리되는 부채로 유지.