MVC(Model-View-Controller)는 소프트웨어 설계 패턴 중 하나로, 비지니스 로직과 UI를 분리하여 유지 보수성과 확장성을 높이는 구조이다.
※ 참고: DAO와 Repository는 같은 의미로 사용된다.
- DAO(Data Access Object): 전통적인 용어
- Repository: Spring Data JPA에서 사용하는 용어
View(뷰)
사용자에게 화면을 제공
모델의 데이터를 출력하고 입력을 받아 컨트롤러에 전달
ex ) HTML, JSP, Thymeleaf
Controller(컨트롤러)
사용자의 요청을 처리하고 모델과 뷰를 연결
요총을 모델에 전달하고, 모델에서 받은 데이터를 뷰에 전달
ex) Spring의 @Controller, @RestController
Spring MVC에서 Model은 데이터와 비즈니스 로직을 담당하는 핵심 요소다.
하지만 실무에서는 단순히 Model로만 처리하지 않고, 이를 Entity, DTO, Service, Repository 등으로 세분화한다.
👉 왜 이런 세분화가 필요할까?
이 글에서는 각 계층이 필요한 이유와 그 역할에 대해 설명한다.
Spring MVC의 Model은 원래 데이터와 비즈니스 로직을 담당하는 역할이다.
하지만 실무에서는 Model을 Entity, DTO, Service, Repository 로 세분화해서 사용한다.
이렇게 나누는 이유는 책임 분리를 지키고 유지보수성을 높이기 위해서이다.
@Entity
@Table(name = "users")
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private String password; // 보안상 노출되면 안 되는 필드
}
public class UserDTO {
private String name;
private String email;
public UserDTO(User user) {
this.name = user.getName();
this.email = user.getEmail();
}
}
Entity에는 비밀번호(password)가 있지만, DTO에서는 제외하여 보안을 강화할 수 있다
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserRepository userRepository;
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userRepository.findById(id).orElse(null));
}
}
컨트롤러는 단순히 요청을 받고, 데이터 처리와 비지니스 로직은 서비스에서 담당해야한다.
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserRepository userRepository;
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userRepository.findById(id).orElse(null);
if (user == null || !user.isActive()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
return ResponseEntity.ok(user);
}
}
이런 검증 로직은 컨트롤러가 아닌 서비스 계층에서 처리해야 한다.
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
JAPRepository를 사용하면 기본적인 CRUD 메서드를 제공한다. 데이터베이스와 직접 연결되는 Repository는 역할이 분리되어야 유지보수가 쉬움
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUserById(id));
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public UserDTO getUserById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
// 예: 닉네임이 없는 경우 기본값 처리
if (user.getName() == null || user.getName().isBlank()) {
user.setName("Guest");
}
return new UserDTO(user);
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Model이 실무에서 Entity, DTO, Service, Repository로 세분화되는 이유는 다음과 같다.
| 분리된 요소 | 이유 |
|---|---|
| Entity vs DTO | Entity를 그대로 사용하면 보안 문제 발생 → DTO를 통해 필요한 데이터만 전달 |
| Controller vs Service | Controller가 직접 Repository를 호출하면 역할이 혼재됨 →Service에서 비지니스 로직을 처리 |
| Service vs Repository | DB가 변경될 때 대응하기 위해 Repository 계층을 따로 둠 |