layered-architecture

2 개의 포스트

소프트웨어 3.0 시대를 맞이하며 (새 탭에서 열림)

소프트웨어 3.0 시대는 자연어 프롬프트가 프로그램이 되는 시대이지만, LLM이 실질적인 업무를 수행하기 위해서는 이를 제어하고 연결하는 '하네스(Harness)'가 필수적입니다. Claude Code와 같은 최신 에이전트 도구들은 이러한 하네스의 역할을 하며, 그 내부 구조는 놀랍게도 우리가 익히 알고 있는 소프트웨어 1.0의 레이어드 아키텍처 원칙을 그대로 따르고 있습니다. 결국 좋은 에이전트를 설계하는 힘은 기존의 객체 지향 설계와 추상화 원칙을 얼마나 잘 적용하느냐에 달려 있습니다. **소프트웨어 1.0의 눈으로 본 에이전트 구조** * **Slash Command (Controller):** `/review`, `/refactor`와 같은 명령어는 사용자 요청의 진입점 역할을 하며, 특정 워크플로우를 트리거하는 컨트롤러와 유사합니다. * **Sub-agent (Service Layer):** 여러 기술(Skill)을 조합하여 복잡한 비즈니스 로직을 완성하며, 독립된 컨텍스트를 가져 서비스 계층이나 별도의 스레드처럼 동작합니다. * **Skills (Domain Component):** 단일 책임 원칙(SRP)에 따라 "코드 리뷰", "테스트 생성" 등 명확한 한 가지 역할만 수행하는 기능 단위입니다. * **MCP (Infrastructure/Adapter):** 외부 API나 데이터베이스와의 연결을 담당하며, 내부 로직이 외부 환경에 의존하지 않도록 추상화된 어댑터 역할을 합니다. * **CLAUDE.md (Configuration):** 프로젝트의 기술 스택과 코딩 컨벤션을 담는 파일로, `package.json`이나 `pom.xml`처럼 프로젝트의 고정된 원칙을 정의합니다. **에이전트 설계의 핵심: 질문과 판단의 위임** * **Exception에서 Question으로:** 전통적인 코드에서는 모든 예외를 미리 정의해야 하지만, 에이전트는 불확실한 상황에서 사용자에게 질문(HITL)을 던져 판단을 위임할 수 있습니다. * **질문의 기준:** 삭제나 배포처럼 되돌리기 어려운 작업이나 리스크가 큰 결정은 사용자에게 묻고, 안전하게 반복 가능한 작업은 에이전트가 스스로 처리하도록 설계해야 합니다. * **안티패턴의 답습:** 에이전트 설계에서도 특정 객체가 너무 많은 일을 하는 'God Agent'나 불필요하게 복잡한 호출 구조는 유지보수성을 떨어뜨리는 코드 스멜이 됩니다. **토큰 최적화와 효율적인 설계 전략** * **토큰은 곧 메모리:** 컨텍스트 윈도우(Context Window)를 작업 메모리로 인식해야 하며, 무분별한 파일 읽기나 복잡한 지침은 토큰 폭발(OOM과 유사)을 야기합니다. * **결정적 로직의 분리:** 브랜치 명명 규칙과 같이 판단이 필요 없는 단순 반복 작업은 프롬프트가 아닌 별도의 스크립트로 작성하여 실행하게 함으로써 토큰 소모를 줄여야 합니다. * **점진적 노출(Progressive Disclosure):** 수많은 Skill이 시스템 프롬프트를 점유하지 않도록, 진입점만 제공하고 세부 지식은 필요할 때 참조하게 만드는 '디미터의 법칙'을 적용해야 합니다. 소프트웨어 3.0 시대에도 개발자가 쌓아온 레이어 분리, 추상화, 인터페이스 설계 역량은 여전히 유효합니다. 도구는 LLM으로 바뀌었지만 응집도와 결합도를 고려한 좋은 설계 원칙을 유지할 때, 비로소 실무에서 신뢰할 수 있는 강력한 에이전트를 구축할 수 있습니다.

당근페이 백엔드 아키텍처가 걸어온 여정. Money라는 하나의 작은 프로젝트부터 수십 개의 서비스를 하나의… (새 탭에서 열림)

당근페이 백엔드 아키텍처는 서비스의 급격한 성장과 조직의 확장에 발맞춰 계층형, 헥사고날, 그리고 클린 아키텍처 기반의 모노레포 형태로 끊임없이 진화해 왔습니다. 초기에는 빠른 기능 출시를 위해 단순한 구조를 채택했으나, 비즈니스 복잡도가 증가함에 따라 의존성 관리와 코드 응집도를 높이기 위해 구조적 제약을 강화하는 방향으로 발전했습니다. 결과적으로 아키텍처는 기술적 부채를 해결하는 수단을 넘어, 대규모 팀이 협업하며 지속 가능한 성장을 이뤄낼 수 있는 기반이 되었습니다. ### 초기 성장을 견인한 계층형 아키텍처 (Layered Architecture) * **빠른 실행력 중심:** 2021년 당근페이 출시 초기, 송금 서비스의 신속한 시장 진입을 위해 `Controller-Service-Repository`로 이어지는 직관적인 3계층 구조를 사용했습니다. * **성장통의 발생:** 서비스가 커지면서 송금, 프로모션, FDS 등 다양한 기능이 하나의 계층에 뒤섞였고, 서비스 간 순환 참조와 강한 결합이 발생해 코드 변경의 영향 범위를 예측하기 어려워졌습니다. * **기술 부채의 축적:** 모든 비즈니스 로직에 프레임워크 기술(Spring)이 깊숙이 침투하면서 테스트 작성이 까다로워지고, 순수 도메인 로직만 분리해 관리하기 어려운 구조적 한계에 직면했습니다. ### 구조적 제약을 통한 응집도 향상 (Hexagonal Architecture) * **외부 구현과의 분리:** 도메인 규칙을 중심에 두고 UI, DB, 외부 API 등 인프라 영역을 포트와 어댑터를 통해 분리하여 프레임워크에 의존하지 않는 POJO 중심의 설계를 지향했습니다. * **모듈 역할의 세분화:** 프로젝트를 핵심 규칙을 담은 `domain`, 사용자 시나리오 단위의 `usecase`, 실제 입출력을 담당하는 `adapter` 모듈로 재구성하여 의존성 방향을 한곳으로 모았습니다. * **재사용성과 테스트 용이성:** 유스케이스 단위로 로직이 응집되면서 REST API뿐만 아니라 이벤트 컨슈머, 배치 잡 등 다양한 진입점에서 동일한 비즈니스 로직을 안전하게 재사용할 수 있게 되었습니다. ### 규모 확장에 대응하는 클린 아키텍처와 모노레포 * **모노레포 도입의 배경:** 머니, 포인트, 빌링 등 도메인이 늘어남에 따라 여러 저장소를 관리하는 비용이 증가했고, 이를 효율적으로 통합 관리하기 위해 하나의 저장소에서 여러 서비스를 운영하는 모노레포 구조를 채택했습니다. * **계약 기반의 모듈 분리:** 각 도메인을 `contract(인터페이스)`와 `impl(구현체)` 모듈로 쪼개어 의존성 규칙을 강제했습니다. 다른 모듈은 `contract`만 참조하게 하여 불필요한 내부 구현 노출을 차단했습니다. * **빌드 성능 및 생산성 최적화:** Gradle의 `api`와 `implementation` 구성을 활용해 컴파일 시점의 의존성을 제어함으로써, 대규모 프로젝트임에도 불구하고 빌드 시간을 단축하고 변경 영향도를 최소화했습니다. 아키텍처에는 정답이 없으며, 조직의 규모와 비즈니스의 현재 단계에 가장 적합한 형태를 선택하는 것이 중요합니다. 당근페이의 사례처럼 초기에 과도한 설계를 지양하되, 서비스 성장 속도에 맞춰 구조적 제약을 단계적으로 도입함으로써 기술 부채를 통제하고 개발 생산성을 유지하는 전략을 권장합니다.