C
Spring Boot/계층구조/Lesson 07

Layered Architecture — *책임을 층으로 나누기*

30분·theory

Layered Architecture — *책임을 층으로 나누기*

🎯 이 lesson 을 읽고 나면

이 lesson 을 다 읽고 나면 아래 3가지를 자신 있게 할 수 있습니다.

  • ✅ Controller → Service → Repository 3계층 책임 분리
  • ✅ @Transactional 이 적용되어야 할 정확한 위치
  • ✅ DTO ↔ Entity 변환의 책임을 누가 가져야 하는지

학습 목표를 체크리스트로 두고 다 답할 수 있게 되면 lesson 을 닫으세요.

왜 *3 계층* 으로 나누나

핵심 한 줄

Spring 백엔드는 보통 Controller → Service → Repository 3계층으로 코드를 나눕니다. 각 계층이 명확한 책임 을 갖게 해서 유지보수 가능한 코드 를 만드는 표준.

각 계층의 역할

code
┌─────────────────────────────────────┐
│         @RestController              │  ← HTTP 받기·검증·DTO 변환
│  - @GetMapping·@PostMapping          │
│  - 요청 파라미터 검증                  │
│  - DTO → Service 입력 변환            │
└──────────────┬──────────────────────┘
               │ Service 호출
               ▼
┌─────────────────────────────────────┐
│           @Service                   │  ← 비즈니스 로직
│  - 트랜잭션 (@Transactional)         │
│  - 여러 Repository 조합               │
│  - 외부 API 호출 (메일·결제)          │
└──────────────┬──────────────────────┘
               │ Repository 호출
               ▼
┌─────────────────────────────────────┐
│         @Repository                  │  ← DB 접근
│  - JpaRepository·QueryDSL            │
│  - 단순 CRUD 또는 쿼리                │
│  - DB 외엔 아무것도 모름                │
└─────────────────────────────────────┘

Controller: HTTP 의 입출구. 요청 받기·검증·응답 만들기. 비즈니스 로직 X.

Service: 비즈니스 로직의 중심. 트랜잭션 단위. 여러 Repository·외부 API 조합.

Repository: DB 와의 대화. 쿼리만. 비즈니스 판단 X.

왜 이렇게 나누나

1. 책임 분리: 각 계층이 한 가지만 잘 합니다. Controller 가 DB 쿼리도 하면 뒤죽박죽.

2. 테스트 용이: Service 테스트할 때 진짜 Controller·Repository 없이 단위 테스트 가능. Mock 으로 대체.

3. 변경 격리: HTTP 가 GraphQL 로 바뀌어도 Controller 만 수정. Service·Repository 그대로.

4. 트랜잭션 경계: @TransactionalService 메서드 에만 붙이는 게 표준. Controller·Repository 에 붙이면 애매한 경계.

실전 예시

java
// === Controller ===
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @PostMapping
    public UserDto create(@RequestBody @Valid CreateUserDto dto) {
        return userService.create(dto);
    }
}

// === Service ===
@Service
@Transactional
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepo;
    private final EmailService emailService;

    public UserDto create(CreateUserDto dto) {
        // 비즈니스 규칙
        if (userRepo.existsByEmail(dto.email())) {
            throw new DuplicateEmailException();
        }
        // 저장
        User saved = userRepo.save(User.from(dto));
        // 외부 효과
        emailService.sendWelcome(saved.getEmail());
        // DTO 변환
        return UserDto.from(saved);
    }
}

// === Repository ===
public interface UserRepository extends JpaRepository<User, Long> {
    boolean existsByEmail(String email);
}

Controller 는 얇고, Service 는 비즈니스 규칙으로 두껍고, Repository 는 DB 접근만 — 깔끔한 구조.

DTO — 계층 간 데이터 운반 객체

각 계층은 같은 객체 를 직접 주고받지 않습니다. DTO (Data Transfer Object) 로 변환해서 전달.

  • CreateUserDto — 요청 본문
  • User — JPA 엔티티 (DB 매핑)
  • UserDto — 응답 객체

왜 분리하나?

  • 엔티티의 내부 구조 가 외부에 노출 안 됨 (보안·캡슐화)
  • 비밀번호 같은 민감한 필드 를 응답에서 자연스럽게 제외
  • API 변경이 엔티티에 영향 X (느슨한 결합)

Java 14+ 의 record 가 DTO 만들기에 환상적입니다:

java
public record CreateUserDto(@NotBlank @Email String email, @NotBlank String name) {}
public record UserDto(Long id, String email, String name) {
    public static UserDto from(User u) {
        return new UserDto(u.getId(), u.getEmail(), u.getName());
    }
}

흔한 안티 패턴

1. Controller 가 비즈니스 로직 함: 트랜잭션 깨짐·재사용 X
2. Service 가 HttpServletRequest 받음: HTTP 의존 으로 테스트·재사용 어려움
3. Repository 에 비즈니스 규칙: 같은 쿼리 다른 곳에 또 작성됨
4. 엔티티를 그대로 응답: 비밀번호 노출·내부 구조 외부 결합

한 번 정리

3 계층은 Spring 표준. 각 계층이 한 가지만 잘 하도록 책임을 나눕니다. DTO 로 계층 사이 데이터 운반 하면 결합도가 낮아집니다.

🤖 AI 에게 이렇게 요청해보세요

이 lesson 의 개념을 알면 AI 에게 구체적으로 지시할 수 있습니다. 막연한 "고쳐줘" 가 아니라 어휘를 가진 요청 — 그게 토큰 절약의 출발점입니다.

  • "이 Spring Boot 코드에 Layered Architecture — 책임을 층으로 나누기 패턴을 적용해줘"
  • "Layered Architecture — 책임을 층으로 나누기 관련 @SpringBootTest 통합 테스트 작성해줘"
  • "실무에서 Layered Architecture — 책임을 층으로 나누기 사용 시 주의할 함정 3가지 알려줘"

왜 이게 토큰을 줄이나

개념을 모를 땐 AI 답변을 받고도 "그게 뭐예요?" 를 다시 물어야 합니다. 그 "다시 물음" 이 토큰을 잡아먹습니다. 개념 한 번 익혀두면 대화가 한 번에 끝납니다.

Layered Architecture — 책임을 층으로 - Spring Boot