Coding Standards

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

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

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

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

도메인 드리븐 아키텍처

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

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

이 페이지는 사용자 요청이 UI, HTTP, Application, Domain, Repository 및 Database를 거쳐 처리되는 흐름과 각 계층에서 지켜야 하는 코딩 규칙을 설명합니다.

의존성은 외부 계층에서 내부 계층으로 향하며, Domain 계층은 UI, Database, Framework와 같은 외부 기술에 직접 의존하지 않습니다.

요청 한 사이클

사용자가 버튼을 누른 순간부터 데이터가 화면으로 돌아올 때까지, 요청이 어느 계층을 어떤 순서로 거치는지다. 계층 카드를 누르면 맨 아래 목록이 그 계층의 표준만 남긴다.

Request · 실선 Response · 점선

Request 사용자의 동작이 Database에 닿기까지

Clientapi · webapplicationdomaininfraDatabase순서로 흐른다. 계층을 고르면 아래 목록이 그 계층의 표준만 남긴다.

Response 조회한 데이터가 화면으로 돌아오기까지

  1. Database
  2. infra
  3. domain
  4. application
  5. apiweb
  6. Client

돌아오는 길에는 ResultSet, XxxEntity → XxxDomain, XxxDomain / XxxView, ServiceResult<XxxDomain>, HttpApiResponse<XxxResponse> 순서로 타입이 바뀐다. 각 계층은 자기 타입만 알고, 옆 계층의 타입은 모른다.

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

계층 지도

위 그림이 호출 순서라면 이 지도는 의존 방향이다. 최상위는 계층이 아니라 도메인으로 나누고, 도메인 하나가 아래 네 겹을 갖는다.

의존성은 바깥쪽에서 안쪽으로 향한다

Adapter 바깥 세계와 붙는 자리 · 여기만 프레임워크를 안다
Application 유스케이스의 순서와 트랜잭션 경계
Domain 아무 쪽도 가리키지 않는다
common 겹 바깥에 있다 · 어떤 계층도 아니다

어떤 계층도 아니다. 프레임워크에 의존하지 않는 것만 두기 때문에 모든 패키지가 참조해도 의존 방향이 깨지지 않는다.

겹 바깥 우리가 짜지 않는 것

ClientDatabase는 요청 흐름에는 있지만 이 지도에는 겹으로 들어오지 않는다. 우리 코드가 아니기 때문이다. 이 둘과 맞닿는 규칙은 각각 apiinfra가 진다.

Domain은 UI, Database, Framework의 구체적인 구현을 알지 못한다. 바깥 겹이 안쪽 겹을 부르고, 안쪽은 바깥을 부르지 않는다. infra가 바깥에 있는데도 domain을 향하는 이유가 이것이다 — 구현이 추상을 의존하지, 그 반대가 아니다.

계층별 표준

번호는 참조 순서다. 날짜순이 아니라, 앞 표준이 뒤 표준의 전제가 되는 순서로 놓았다.

표준 45개를 모두 보는 중

3계층 아키텍처

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

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

3계층 아키텍처는 요청 처리, 비즈니스 로직, 데이터 접근의 책임을 각각 분리하는 구조입니다. 브라우저의 요청은 Controller와 Service를 거쳐 Repository 또는 MyBatis Mapper에 전달되고, 처리 결과는 반대 방향으로 반환됩니다.

요청 한 사이클

사용자가 버튼을 누른 순간부터 데이터가 화면으로 돌아올 때까지, 요청이 어느 계층을 어떤 순서로 거치는지다. 계층 카드를 누르면 맨 아래 목록이 그 계층의 표준만 남긴다.

Request · 실선 Response · 점선

Request 사용자의 동작이 Database에 닿기까지

Browserapi · webservicerepositoryDatabase순서로 흐른다. 계층을 고르면 아래 목록이 그 계층의 표준만 남긴다.

Response 조회한 데이터가 화면으로 돌아오기까지

  1. Database
  2. repository
  3. service
  4. apiweb
  5. Browser

돌아오는 길에는 Query Result, Entity / Record, 결과 객체, HTTP Response 순서로 타입이 바뀐다. 각 계층은 자기 타입만 알고, 옆 계층의 타입은 모른다.

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

계층을 가리지 않는 규칙

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

계층별 표준

번호는 참조 순서다. 날짜순이 아니라, 앞 표준이 뒤 표준의 전제가 되는 순서로 놓았다.

표준 32개를 모두 보는 중

프론트엔드 아키텍처

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

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

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

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

여기서도 성립하는 표준

계층을 정하기 전에도 성립하는 것들이다. 두 앱이 무엇을 나눠 갖고 무엇을 따로 갖는지는 계층과 무관하게 먼저 정해야 했다.