새 프로젝트를 시작할 때마다 같은 삽질을 반복하고 있었다. 예전에 한 번 뚫어낸 설정인데도 시간이 지나 다시 접목하려 하면 같은 자리에서 또 헤맸다. 그 반복을 없애려고 만든 개인 아키텍처 시스템을, 개념 수준에서 한 편에 정리한다.
원래 네 편으로 나눠 적었는데, 네 편 모두 결국 한 가지를 위해 존재해서 한 편으로 합쳤다. 나중에 서비스로 떼어낼 자리를 지금 미리 정해 두는 것이다.
- 왜 만들었나 — 반복되는 환경 세팅을 스킬로 굳히기
- 포트 기반 baseline — 언어가 달라도 같은 골격
- 인프라 concern 조립 — 10개로 쪼개고 위상정렬로 필요한 것만
- MSA 즉시 접목 — 경계를 미리 그어두면 떼어내기가 쉽다
1. 왜 만들었나 — 반복되는 삽질을 스킬로 굳히기
목적부터 적어 둔다. 두 가지였다.
그 되풀이되는 삽질에 드는 시간이 아까웠다. 그래서 한 번 삽질해서 모든 게 제대로 돌아가도록 뚫어낸 과정을, 다시는 되풀이하지 않게 플러그인·스킬로 굳혀 두자는 것이 이 시스템의 첫 번째 목적이다.
두 번째 목적은 더 실용적이다. 개발자에게 환경 세팅은 가장 오래 걸리는 작업 중 하나다. 언어 골격 잡기, 인프라 깔기, CI/CD 붙이기, API 스펙 올리기 — 이 지루한 초기 작업을 미리 스킬로 만들어 두면, 새 프로젝트의 출발이 훨씬 가벼워진다.
이런 스킬은 이미 오래 연구되어 온 영역이라 좋은 오픈소스 프레임워크 스킬이 많다. 그래서 이 프로젝트는 그중 내게 필요한 것들만 골라 따와 취합한 ‘플러그인 스킬 모음 프로젝트’다. 이 글은 그 시스템을 개념 수준에서 정리한 시리즈의 1편이다.
무엇을 만들었나
한 줄로 말하면 Claude Code에서 자연어/슬래시 커맨드 한 줄로 도는 개인 아키텍처 패턴 플러그인·스킬 모음이다. 새 프로젝트를 시작할 때마다 반복하던 일 — 폴더 구조 잡기, 포트 경계 긋기, CI/CD 깔기, API 스펙 올리기, 배포 등록하기 — 을 매번 손으로 하면 사람마다·프로젝트마다 미묘하게 달라진다. 그 편차를 없애려고 규칙을 문서로 고정했다.
지향점 — “하나의 템플릿 규칙”

핵심은 개별 기능이 아니라 틀(template)을 강제하는 방식이다. 모든 패턴이 같은 프론트매터와 14개 섹션을 따른다. 그래서 언어가 달라도 패턴끼리 1:1로 비교되고, “이건 어디에 두지?”라는 질문에 매번 같은 답이 나온다.
| 모든 패턴이 공유하는 14 섹션 | |
|---|---|
| ①패턴명 ②목적 ③적용 대상 ④문제 상황 | ⑤구조 ⑥책임 분리 ⑦폴더 구조 ⑧코드 예시 |
| ⑨장점 ⑩단점 ⑪언제 쓰지 말지 | ⑫MSA에서의 역할 ⑬Clean/Hex 위치 ⑭확장 포인트 |
무엇을 자동화하나 (개요)
- 언어 패턴 — Go·Java·Node·React·Flutter를 같은 포트 기반 골격으로.
- 인프라 — 10개 concern을 의존성 위상정렬로 필요한 것만 조립.
- 연동(bridge) — 협업/디자인 도구를 코드화된 계약으로.
- 워크플로·표기법 — MVP 부트스트랩, 시연 녹화, 흐름·다이어그램을 버전 관리되는 계약으로.
한 가지 전제. 값과 설정은 전부 플레이스홀더로 두어, 어떤 조직 고유정보도 담기지 않게 했다. 공개 가능한 골격과 규칙만 스킬로 모아 둔 셈이다.
2. 포트 기반 baseline — 언어가 달라도 같은 골격
목적이 정해졌으니 골격 차례다. 여기서부터는 타협하지 않기로 한 규칙이 하나 있다.
이 시스템의 비협상 규칙은 하나다. 모든 패턴은 Clean Architecture + Hexagonal(포트 기반)을 기본 골격으로 삼는다. 언어가 Go든 Java든 Flutter든, 골격은 같다.
한 장으로 보는 골격

Handler → (inbound port) → UseCase → (outbound port) → Repository/Adapter. 제어(흐름)는 오른쪽으로 흐르지만, 의존성은 포트(인터페이스)로 역전시켜 안쪽(UseCase·Domain)이 프레임워크를 전혀 모르게 한다. 이 원리 자체는 지난 Go 아키텍처 시리즈에서 코드로 다뤘다 — 이 글은 그 원리를 여러 언어에 일관되게 강제하는 방식에 대한 이야기다.
같은 개념, 언어별 관용구
| 개념 | Go | Java | Node | React | Flutter |
|---|---|---|---|---|---|
| Handler(구동) | Gin handler | @RestController | express route | hook/컴포넌트 | widget/bloc |
| UseCase | usecase impl | @UseCase | service | use-case hook | usecase |
| Repository(피구동) | GORM repo | JPA repo | prisma repo | api client | repository |
이렇게 Handler/UseCase/Port/Repository라는 같은 개념을 언어별 관용구로 1:1 매핑해 두면, 새 언어를 배우거나 팀을 옮겨도 “이 코드는 어느 자리”인지 헤매지 않는다.
왜 baseline을 하나로 고정했나
- 비교 가능성 — 같은 14-섹션 틀 위에서 언어별 패턴이 나란히 비교된다.
- 교체 용이성 — 프레임워크·ORM은 어댑터라 갈아 끼워도 UseCase는 그대로.
- MSA 준비 — 포트 경계가 이미 그어져 있어, 나중에 서비스로 떼어내기 쉽다(4편).
참고로 MVC를 섞은 스타일도 있지만, 나는 그걸 참조용 카운터-예시로만 두고 베이스라인과 명확히 구분한다. 3편에서는 이 골격 위에 얹는 인프라 자동화를 본다.
3. 인프라를 concern으로 쪼개고, 위상정렬로 필요한 것만
골격이 섰으면 그걸 어디에 올릴지가 남는다.
애플리케이션 골격(2편)이 섰으면, 그걸 굴릴 인프라가 필요하다. 나는 인프라를 하나의 덩어리로 보지 않고 10개의 concern으로 쪼갠 뒤, 필요한 것만 순서대로 깔리게 만들었다.
한 장으로 보는 조립

concern은 platform · persistence · messaging · identity · workflow · observability · ci · delivery · migration · secrets 로 나뉜다. 각 concern은 manifest.yaml에 의존성과 산출물을 선언한다.
# concern manifest (개념)
depends_on: [platform, secrets]
provides:
patterns: [...] # 이 concern이 제공하는 아키텍처 패턴
skills: [...] # 초기화/배포 스킬
commands: [...] # 슬래시 커맨드
env:
required: [<GCP_PROJECT_ID>, <DOMAIN>]
optional: [...]
그래서 /infra-init --with persistence,delivery 처럼 필요한 concern만 지정하면, depends_on을 위상정렬해서 platform·secrets 같은 선행 concern부터 순서대로 깔아 준다. “무엇을 깔면 무엇이 따라오는가”가 코드가 아니라 문서로 고정되는 셈이다.
무엇까지 자동화되나
| 영역 | 내용 |
|---|---|
| 플랫폼/배포 | Kubernetes 초기화, GitOps(ArgoCD App-of-Apps / ApplicationSet), GitHub Actions 워크플로 셋 |
| 메시징/영속성 | 브로커 프로비저닝, DB 마이그레이션 파이프라인 |
| API 스펙 | OpenAPI를 단일 진실 공급원으로 두고 다중 싱크로 내보내는 파이프라인 |
| 시크릿 | External-Secrets 연동, 프로파일은 플레이스홀더 템플릿으로만 커밋 |
모든 예시 값은 <GCP_PROJECT_ID>·<DOMAIN> 같은 플레이스홀더다. 실제 값은 깃 추적에서 제외된 로컬 프로파일에만 둔다.
4. 경계를 미리 그어두면 MSA로 바로 접목된다
그리고 앞의 셋이 전부 이 마지막 절을 위해 있다.
시리즈를 관통하는 하나의 목적이 있다. 지금은 모듈러 모놀리스로 편하게 개발하되, 경계가 뚜렷해지는 순간 곧바로 마이크로서비스로 떼어낼 수 있게 만드는 것. 이 시스템의 포트/어댑터 규칙은 결국 그 “떼어내기”를 쉽게 하려고 존재한다.
한 장으로 보는 전환

왼쪽은 지금의 모습 — 하나의 프로세스 안에 여러 UseCase가 이미 포트로 경계 지어져 있다. 오른쪽은 전환 후 — 경계가 굳으면 각 UseCase 묶음이 독립 서비스가 되고, 서비스 사이는 비동기 이벤트 버스(MQ)로 잇는다. 핵심은, 이 전환에서 UseCase·Domain 코드는 거의 바뀌지 않는다는 점이다. 새로 생기는 건 메시지큐 어댑터와 서비스 경계뿐이다.
전환을 떠받치는 것들
- 포트 경계(2편) — 서비스로 자를 선이 이미 그어져 있다.
- 메시지큐 어댑터 — Consumer는 구동 어댑터(HTTP 핸들러 자리), Publisher는 outbound port. 이 짝은 Go 아키텍처 #2에서 코드로 다뤘다.
- 이벤트/보상 트랜잭션 — 서비스 간은 이벤트로 잇고, 실패는 Saga 보상으로 되돌린다. EDA·MSA 편의 원리 그대로.
- 패턴마다 MSA 역할 명시 — 14-섹션 중 “MSA에서의 역할”이 있어, 각 패턴이 서비스 경계에서 어떻게 놓이는지 미리 적어 둔다.
네 조각을 한 문장으로
정리하면 이 시스템은 “모놀리스로 시작하되 언제든 MSA로 자랄 수 있게 경계를 먼저 그어 두자”는 하나의 원칙을, 언어·인프라·배포 전반에 일관되게 새긴 것이다.
| 층 | 고정한 규칙 | 그래서 얻는 것 |
|---|---|---|
| 문서 | 모든 패턴이 같은 프론트매터 + 14 섹션 | 언어가 달라도 1:1 비교 |
| 코드 | Clean + Hexagonal(포트 기반) baseline | 프레임워크 교체가 어댑터 교체로 끝남 |
| 인프라 | 10개 concern + depends_on 위상정렬 | 필요한 것만 순서대로 조립 |
| 확장 | 패턴마다 “MSA에서의 역할” 명시 | 분리 시점에 UseCase·Domain 무변경 |
시작은 단순했다. 한 번 삽질해 뚫어낸 환경 세팅을 두 번 반복하지 않도록 플러그인·스킬로 굳히고, 잘 연구된 오픈소스 프레임워크 스킬 중 필요한 것만 골라 모으는 것. 그렇게 취합한 ‘플러그인 스킬 모음’을 앞으로도 계속 손보며 키워 갈 생각이다.
답글 남기기