Spring Boot로 웹 애플리케이션을 개발할 때 가장 먼저 이해해야 할 개념은 "계층 구조(Layered Architecture)"입니다. 이 글에서는 Controller, Service, Repository의 역할을 명확히 구분하고, 초보자가 자주 하는 실수와 그 해결법까지 상세히 설명합니다.
소프트웨어 아키텍처에서 계층을 나누는 이유는 책임 분리(SRP)와 유지보수성 향상에 있습니다. 각 계층은 다음과 같은 책임을 갖습니다:
API 엔드포인트를 정의하고, 요청(Request)을 받아 Service 계층으로 위임한 후 응답(Response)을 클라이언트에 반환합니다.
🚫 잘못된 예: Controller가 Repository를 직접 호출
@RestController
@RequestMapping("/members")
public class MemberController {
private final MemberRepository memberRepository; // 잘못된 의존
```
@PostMapping
public ResponseEntity<Void> create(@RequestBody Member member) {
memberRepository.save(member); // 비즈니스 로직 누락
return ResponseEntity.ok().build();
}
```
}
문제점: Controller가 비즈니스 로직을 직접 처리하면, 재사용성과 테스트성이 떨어지고, 로직이 분산되어 유지보수가 어려워집니다.
✅ 올바른 구조: Repository는 Service가 호출하며, Controller는 Service를 통해 기능을 수행합니다.
@RestController
@RequestMapping("/members")
public class MemberController {
private final MemberService memberService;
```
@PostMapping
public ResponseEntity<Void> create(@RequestBody MemberCommand.Create request) {
memberService.createMember(request);
return ResponseEntity.ok().build();
}
```
}
비즈니스 로직을 처리하는 중심 계층입니다. 트랜잭션 처리, 유효성 검사, 도메인 로직 수행 등을 이곳에서 담당합니다.
실수: 비즈니스 로직 없이 단순히 Repository만 호출하는 "Pass-through Service"
public class MemberService {
private final MemberRepository memberRepository;
```
public void createMember(MemberCommand.Create request) {
memberRepository.save(request.toEntity());
}
```
}
개선: 도메인 로직 추가, 예외 처리, 유효성 검증 등 책임을 명확히 부여합니다.
JPA 기반으로 DB 접근을 담당하는 계층입니다. 도메인 객체(Entity)를 저장하고 조회합니다.
계층마다 입력과 출력을 명확히 나누면, 각 계층의 역할이 분명해지고 변경에 유연해집니다.
이렇게 나누지 않으면, Controller에서 받은 JSON을 그대로 Entity로 변환하거나, Entity를 바로 반환하는 등 보안/유지보수/계약 측면에서 문제가 발생합니다.
아래는 Spring Boot의 전형적인 계층 구조 예시를 보여주는 코드입니다.
public class MemberCommand {
public record Create(String name, String email) {
public Member toEntity() {
return new Member(name, email);
}
}
}
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
```
protected Member() {}
public Member(String name, String email) {
this.name = name;
this.email = email;
}
```
}
public interface MemberRepository extends JpaRepository<Member, Long> {
}
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
```
@Transactional
public void createMember(MemberCommand.Create command) {
Member member = command.toEntity();
memberRepository.save(member);
}
```
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/members")
public class MemberController {
private final MemberService memberService;
```
@PostMapping
public ResponseEntity<Void> create(@RequestBody MemberCommand.Create request) {
memberService.createMember(request);
return ResponseEntity.ok().build();
}
```
}
이 예시를 통해 계층 구조가 실제로 어떻게 작동하는지 구체적으로 이해할 수 있습니다.
계층 구조는 단순히 폴더 구조만을 의미하지 않습니다. 각 계층의 책임을 명확히 구분함으로써, 유지보수성과 테스트 용이성을 확보할 수 있습니다. 초보일수록 잘못된 구조에 빠지기 쉬우니, 컨트롤러에서 직접 레포지토리를 호출하는 패턴은 반드시 피해야 합니다.
| Java 시간 처리 완벽 가이드: 서버 내부 포맷부터 프론트 통신까지 (0) | 2025.06.19 |
|---|---|
| 질문도 실력이다! 주니어 개발자를 위한 질문 꿀팁 (0) | 2025.06.19 |
| JPA 실무에서 자주 묻는 질문 Top 5 + 팀장님이 좋아하는 질문법 (0) | 2025.06.08 |
| redis-cli 사용법 기초부터 실전까지: KEYS vs SCAN, 데이터 삭제까지 (2) | 2025.06.07 |
| 초보 백엔드 개발자를 위한 HTTP 상태코드 (0) | 2025.06.01 |