sxngt

예이린 통합 플랫폼 — 폴리글랏 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).
  • 목표를 "더 빠른 시스템"이 아니라 두 가지로 정의 — 조직이 완전히 통제하고 장기적으로 발전시킬 수 있는 기술자산, 그리고 새 구성원이 규칙을 외우지 않고도 안전하게 기여할 수 있는 협업 구조. 아키텍처 결정의 평가 기준을 개인의 생산성이 아니라 조직의 확장 가능성에 둠.
레거시 MSA 전체 지도

아키텍처 재편 — 경계를 배포 단위에서 패키지 선언으로

  • 모듈러 모놀리스(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의 코드 구조 자체가 강제.
요청 한 개의 DI 여행

계약의 기계화 — OpenAPI를 단일 진실 원천으로

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

컨벤션과 거버넌스 — 문서로만 지키는 규칙은 만들지 않는다

  • 규칙마다 강제 수단을 함께 설계 — 네이밍·레이어 순수성·도구 단일화는 훅이 차단하고, 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 기반 관측성 스택을 도입해 레거시에 부재하던 로그 파이프라인 구축.
CI·배포 파이프라인

협업 체계와 기술 리더십 — 사람을 통솔하는 대신 구조가 협업을 가능하게

  • 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)를 명시해 관리되는 부채로 유지.