우리 팀의 백엔드 서버는 물리적으로는 1개의 서버(Monolith)이지만, 논리적으로는 customer와 admin 2개의 런타임으로 완벽히 분리되어 동작합니다. 각 도메인과 계층 간의 의존성이 꼬이지 않도록 아래의 디렉토리 구조와 계층별 규칙을 엄격히 준수해야 합니다. (위반 시 ArchUnit 테스트에서 빌드가 실패합니다.)
1. 전체 모듈 및 디렉토리 구조
기본 패키지 경로: src/main/java/site/holliverse
└── src
└── main
└── java
└── site
└── holliverse
├── HolliverseApplication.java
├── admin
│ ├── application
│ │ └── usecase
│ ├── domain
│ │ ├── model
│ │ └── policy
│ ├── query
│ │ ├── dao
│ │ └── jooq
│ └── web
│ ├── dto
│ ├── mapper (간단한 매핑 - 형식은 Request -> Entity)
│ └── assembler (복잡한 매핑 - ex.pagenaition과 같이 리스트 형태)
├── customer
│ ├── application
│ │ └── usecase (service -> domain > policy -> transaction (외부 api))
│ ├── domain
│ │ ├── (model)
│ │ └── policy (정책 - repo 호출 필요)
│ ├── persistence
│ │ ├── entity
│ │ └── repository
│ ├── query
│ │ ├── dao (데이터 받아 오는 객체 - dto같은거)
│ │ └── custom (JPA로 처리 힘든 조회 - querydsl 사용)
│ └── web
│ ├── dto
│ ├── controller
│ ├── mapper (간단한 매핑 - 형식은 Request -> Entity)
│ └── assembler (복잡한 매핑 - ex.pagenaition과 같이 리스트 형태)
├── infra
│ └── external
│ ├── s3
│ └── sms
└── shared
├── config
│ ├── jpa
│ ├── runtime
│ └── web
├── error
├── port
└── security
2. 계층별 역할 및 규칙 (Layered Architecture)
올바른 단방향 호출 흐름: Controller(web) → UseCase(application) → Domain/Repository(persistence) / Port(infra)
🌐 Web 계층 (web)
- **역할:** HTTP 요청/응답 처리, DTO 변환 (
Controller,Mapper,Assembler) - 규칙:
- **(중요)** DB 접근(Repository 직접 호출) 및 트랜잭션(
@Transactional), 외부 API 직접 호출을 절대 금지합니다. - DTO 변환 로직은
Mapper나Assembler클래스를 만들어 Controller 바로 옆에 둡니다. - Mapper 변환 시 DB 지연 로딩(Lazy Loading)을 유발하는 깊은 객체 탐색을 무지성으로 하지 않습니다.
- **(중요)** DB 접근(Repository 직접 호출) 및 트랜잭션(
⚙️ Application 계층 (application.usecase)
- **역할:** 서비스 흐름 제어, 트랜잭션 관리
- 규칙:
- 비즈니스 유스케이스 당 하나의 클래스를 작성합니다. (기존의 무거운 Service 역할)
- 데이터베이스 트랜잭션(
@Transactional)은 오직 이 계층에서만 시작하고 종료합니다. - 외부 연동(SMS 발송, S3 업로드 등)처럼 오래 걸리는 작업은 가급적 트랜잭션 밖으로 분리하여 DB 커넥션을 물고 있지 않게 합니다.
🧠 Domain 계층 (domain)
- **역할:** 핵심 비즈니스 모델, 정책(
Policy), 외부 연동 인터페이스(Port) 정의 - 규칙:
- Spring 프레임워크 의존성을 최소화하고 순수 자바 객체 위주로 설계합니다.
- Repository 주입이나 DB 접근 로직, Web(DTO) 의존성을 절대 두지 않습니다.
💾 Persistence / Query 계층
- **Persistence (
persistence):** JPA Entity와 Repository가 위치합니다. - **Query (
query):** 단순 조회가 아닌 복잡한 읽기 로직을 분리합니다.- **Customer:** Querydsl을 사용하여
custom패키지에서 해결합니다. - Admin: 통계 등 무거운 집계 쿼리는 jOOQ만 허용하며,
admin.query.dao에 둡니다. (Core 테이블 풀스캔 지양,analysis스키마 활용)
- **Customer:** Querydsl을 사용하여
🔌 Infra 계층 (infra)
- **역할:** Domain 계층에서 정의한
Port인터페이스의 실제 구현체 (예:S3UploaderAdapter) - 규칙:
- Customer나 Admin에서 Infra 구현체를 직접 의존해선 안 됩니다. (인터페이스만 의존)