Spring 프로젝트 구조와 계층 패턴 (Controller, Service, Repository, Entity)
Spring Framework를 사용하여 개발할 때, Controller, Service, Repository, Entity를 어떻게 구성할지 고민이 될 수 있다.
각 계층의 역할을 명확히 구분하여 구조를 설계하는 것이 중요한데,
이 글에서는 Spring 프로젝트에서 보편적인 계층 구조를 어떻게 설정하는지에 대해 소개해보겠다.
1. 프로젝트 구조
Spring 프로젝트는 보통 다음과 같은 구조로 구성된다.
src
└── main
└── java
└── com
└── example
└── projectname
├── controller
│ └── UserController.java
├── service
│ └── UserService.java
├── repository
│ └── UserRepository.java
├── entity
│ └── User.java
├── dto
│ └── UserDTO.java
└── exception
└── CustomException.java
각 계층에 대한 설명은 다음과 같다.
2. Controller (웹 요청 처리)
Controller는 HTTP 요청을 처리하고, 적절한 서비스를 호출하여 응답을 반환하는 역할을 한다.
주로 @RestController 또는 @Controller 어노테이션을 사용하여 정의한다.
예시
package com.example.projectname.controller;
import com.example.projectname.service.UserService;
import com.example.projectname.dto.UserDTO;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public void createUser(@RequestBody UserDTO userDTO) {
userService.createUser(userDTO);
}
@GetMapping("/{id}")
public UserDTO getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
}
- 주요 역할: 사용자의 요청을 받고 응답을 반환합니다. DTO를 사용하여 데이터를 주고받습니다.
3. Service (비즈니스 로직 처리)
Service는 실제 비즈니스 로직을 처리하는 계층이다.
컨트롤러는 서비스 계층을 호출하고, 서비스 계층은 리포지토리를 통해 데이터베이스와 상호작용한다.
예시
package com.example.projectname.service;
import com.example.projectname.dto.UserDTO;
import com.example.projectname.entity.User;
import com.example.projectname.repository.UserRepository;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void createUser(UserDTO userDTO) {
User user = new User(userDTO.getName(), userDTO.getEmail());
userRepository.save(user);
}
public UserDTO getUserById(Long id) {
User user = userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
return new UserDTO(user.getName(), user.getEmail());
}
}
- 주요 역할: 비즈니스 로직을 처리한다. 데이터베이스와의 상호작용은 Repository를 통해 이루어진다.
4. Repository (데이터베이스와의 상호작용)
Repository는 데이터베이스와의 CRUD 작업을 처리하는 계층이다.
Spring Data JPA를 사용하면, JpaRepository 인터페이스만 상속해도 기본적인 CRUD 기능을 사용할 수 있다.
예시
package com.example.projectname.repository;
import com.example.projectname.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 추가적인 쿼리 메서드를 작성할 수 있습니다.
}
- 주요 역할: 데이터베이스와의 CRUD 작업을 담당한다.
5. Entity (데이터베이스 테이블 매핑)
Entity는 데이터베이스 테이블과 매핑되는 클래스로, 데이터베이스의 한 행(row)을 객체로 표현한다.
@Entity 어노테이션을 사용하여 해당 클래스를 JPA 엔티티로 정의한다.
예시
package com.example.projectname.entity;
import javax.persistence.*;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// 기본 생성자, getter, setter
public User(String name, String email) {
this.name = name;
this.email = email;
}
// Getter, Setter 생략
}
- 주요 역할: 데이터베이스의 테이블과 매핑되는 객체이다.
6. DTO (Data Transfer Object)
DTO는 데이터를 전송하는 객체로, 엔티티를 클라이언트와의 데이터 교환에 적합한 형식으로 변환하여 사용한다.
주로 Controller에서 요청을 처리하거나 응답을 반환할 때 사용된다.
예시
package com.example.projectname.dto;
public class UserDTO {
private String name;
private String email;
// 생성자, getter, setter 생략
}
7. 예외 처리 (Global Exception Handler)
프로젝트에서 발생할 수 있는 예외를 처리하기 위한 방법으로, @ControllerAdvice 또는 @ExceptionHandler를 사용하여 글로벌 예외 처리를 할 수 있다.
예시
package com.example.projectname.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleRuntimeException(RuntimeException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
결론
Spring 프로젝트에서 Controller, Service, Repository, Entity는 각기 다른 역할을 가지고 있다.
각 계층을 잘 분리하여 구조화하면 유지보수성이 높고, 확장 가능한 시스템을 구축할 수 있다.
- Controller: HTTP 요청을 처리하고 응답을 반환
- Service: 비즈니스 로직을 처리
- Repository: 데이터베이스와의 상호작용
- Entity: 데이터베이스 테이블과 매핑
- DTO: 클라이언트와의 데이터 교환에 사용
이와 같은 구조를 통해 Spring 프로젝트를 효율적으로 설계하고 개발할 수 있다.
각 계층을 잘 분리하여 관리하면, 코드의 재사용성과 테스트 용이성 또한 향상된다.