Coding Standards

HTML 요청부터 데이터베이스까지, 프로젝트의 전체 흐름과 계층별 개발 규칙을 확인합니다.

표준은 아키텍처마다 따로 둔다. 도메인 드리븐에서 정한 규칙이 3계층에서도 통한다는 보장이 없기 때문이다 — Domain이 없는 구조에 「애그리거트 경계」를 들이밀 수는 없다. 아래 탭에서 아키텍처를 고르면 그 구조의 흐름과 표준만 보인다.

대상은 Spring Boot로 만드는 JSON API다. 서버가 HTML을 그리지 않기 때문에 화면 렌더링은 클라이언트가 맡고, 서버 쪽 표준은 진입점부터 시작한다. 각 탭의 표준은 서로 이어져 있고, 정한 순서대로 나열했다 — 뒤의 글은 앞의 글에서 정한 것을 전제로 삼는다.

깔려 있는 조건이 둘 더 있다. 여기서 코드를 주로 쓰는 것은 사람이 아니다 — Claude와 Codex와 Copilot이 같은 저장소에 파일을 직접 쓴다. 규칙을 도구 수만큼 배포하는 것도, 포맷을 도구로 강제하는 것도, 마이그레이션 파일명을 타임스탬프로 짓는 것도 여기서 갈렸다. 그리고 서비스는 하나고 인스턴스도 하나다. 조건이 다르면 그 자리에서 판단이 갈리므로, 갈리는 표준은 「먼저」 절에 그 조건과 조건이 뒤집혔을 때 어떻게 되는지를 적어 둔다. 갈리지 않는 표준에는 적지 않는다.

도메인 드리븐 아키텍처

최상위를 계층이 아니라 도메인으로 나누고, 도메인 하나가 api · application · domain · infra 네 겹을 갖는다

구조를 우리가 정할 수 있는 프로젝트에서 쓴다.

요청 한 사이클 — 그래프

사용자가 버튼을 누른 순간부터 데이터가 화면으로 돌아올 때까지. 계층을 누르면 아래 목록이 그 계층 표준만 남긴다.

요청 · 실선 응답 · 점선 원 크기 = 그 계층의 표준 수
  1. Client HTTP Request
  2. api · web XxxCommand / XxxQuery
  3. application XxxDomain
  4. domain XxxRepository 호출
  5. infra SQL
  6. Database

호출 방향과 의존 방향은 다르다. 요청은 위 그림대로 api에서 infra 쪽으로 흐르지만, 컴파일 의존은 infra가 domain을 향하는 반대 방향이다. application은 domain에 있는 Repository 인터페이스만 부르고, 그 구현이 infra에 있다는 사실을 모른다. 그 방향을 아래 계층 지도가 보여준다.

계층 지도 — 의존 방향

바깥 겹이 안쪽 겹을 부르고, 안쪽은 바깥을 모른다. 가운데가 domain이다.

ADAPTER application 13 domain 12Client Database 구현 api 22 web 1 infra 13 common 27

화살표는 늘 바깥에서 안쪽이다. 안쪽 겹은 바깥 겹의 이름을 모른다.

domain 표준 12

비즈니스 규칙을 담는다. 영속성도 웹도 모르는 POJO다. Repository 인터페이스를 여기서 정의한다.

application 표준 13

유스케이스의 실행 순서를 조율하고 트랜잭션 경계를 잡는다. 판단은 Domain에 맡긴다.

api · web · infra 표준 22 · 1 · 13

바깥 세계와 붙는 자리. 여기만 프레임워크를 안다. infra는 바깥에 있어도 domain의 인터페이스를 구현한다.

common 표준 27

어떤 계층도 아니다. 모든 패키지가 참조하되, domain이 쓰는 타입은 프레임워크에 직접·간접으로 닿지 않게 두어 의존 방향이 깨지지 않는다. 패키지가 아니라 타입 단위로 지킨다.

계층별 표준 — 목록형

번호는 참조 순서다. 한 줄에 제목과 요지, 계층만 둔다.

3계층 아키텍처

최상위는 도메인으로 나누고, 도메인 하나가 요청 처리 · 비즈니스 로직 · 데이터 접근 세 겹을 갖는다

도메인 드리븐을 쓸 수 없는 자리. 외주처럼 구조를 우리가 정할 수 없는 일감에서는 네 겹으로 나누고 package-private으로 막는 방식을 강제할 수 없다.

요청 한 사이클 — 그래프

브라우저 요청이 Controller와 Service를 거쳐 Repository에 닿고, 결과는 반대로 돌아온다.

요청 · 실선 응답 · 점선 원 크기 = 그 계층의 표준 수
  1. Browser HTTP Request
  2. api · web Request DTO
  3. service 조회 조건 · 저장 객체
  4. repository SQL Query
  5. Database

패키징은 도메인 드리븐과 같다 — 최상위를 도메인으로 나누고 계층은 그 안에 둔다(order/api, order/service, order/repository). 다른 것은 그 안이 몇 겹이냐다. Repository, DAO, MyBatis Mapper는 모두 데이터 접근 책임을 수행한다. 기술 스택에 따라 이름과 구현 방식은 달라질 수 있어서, 이 셋을 서로 다른 계층으로 나누지 않고 하나로 묶었다. domain이 없으므로 Repository 인터페이스로 의존을 뒤집지 않고, 호출 방향과 의존 방향이 같다. 첫 칸의 api와 web은 계층이 둘이라는 뜻이 아니다 — 같은 Presentation 계층의 패키지 둘이고, 나가는 것이 JSON이냐 HTML이냐만 다르다.

계층 쌓임 — 위에서 아래로만

domain이 없어 의존을 뒤집지 않는다. 호출 방향과 의존 방향이 같다.

Browser

Presentation Layer

Business Layer

Data Access Layer

Database

화살표는 위에서 아래로만 간다. 부르는 방향이 곧 의존 방향이다.

Presentation Layerapi

HTTP 요청을 받아 입력값을 확인하고, 적절한 Service를 호출한 뒤 JSON 응답을 생성한다.

Business Layerservice

비즈니스 처리 순서를 조율하고, 트랜잭션 범위 안에서 Repository 또는 Mapper를 호출한다.

Data Access Layerrepository

Database에 접근하여 데이터를 조회하고 저장하며, 데이터 접근 구현을 상위 계층에서 분리한다.

이 구조의 계층별 책임, 요청 예시, 의존 규칙은 3계층 아키텍처 가이드에 따로 정리했다.

계층별 표준 — 목록형

번호는 참조 순서다. 3계층에서 통하는 표준만 남았다.

프론트엔드 아키텍처

앱이 둘이다. 보이는 값은 한 벌로 나누고, 만드는 것은 각자 만든다

Next.js 로 화면을 만드는 자리. 지금은 공개 사이트(client)와 관리자(admin) 둘이 돌고, 둘은 Docker 빌드 컨텍스트가 갈려 있어 서로의 파일을 못 읽는다.

아직 계층이 없다

폴더는 app · components · lib · styles로 나뉘어 있지만, 무엇이 무엇을 부를 수 있는지는 정한 적이 없다.

app/?components/?lib/?styles/

계층이 없다. app · components · lib · styles 로 폴더는 나뉘어 있지만 그것이 계층인지, 무엇이 무엇을 부를 수 있는지, 컴포넌트와 레이아웃을 무엇으로 가르는지가 정해진 적이 없다. 실제로 Header 는 components/ 루트에 있고 Footer 는 components/sections/ 안에 있다 — 같은 레이아웃 조각인데 사는 곳이 다르다.

정하지 않은 것을 그려두면 다음에 읽을 때 정한 것으로 착각한다. 그래서 계층이 정해질 때까지 흐름도 자리는 비워둔다. 빈틈은 저장소의 GitHub 이슈에 올려뒀다.

표준끼리의 관계 — 그래프

계층 대신, 먼저 정한 표준들이 서로를 어떻게 전제로 삼는지 본다. 화살표는 링크를 건 글 → 받은 글.

링크를 건 글 → 받은 글 원 크기 = 이어진 수 회색 원 = 아직 다른 표준과 안 이어짐

글 하나를 누르면 그 글에 기댄 표준만 남는다.

표준 — 목록형

계층을 정하기 전에도 성립하는 것들이다.

계층이 없어서 계층 필터도 없다 — 이름으로 찾는다