이전 글에서 Model을 Entity, DTO, Service, Controller로 세분화하는 이유에 대해 다뤘다.
이번에는 이 구조를 실제 프로젝트에 어떻게 적용하는지 정리하려한다.
Entity → DTO → Repository → Service → Controller 순서로 개발 진행.
@Entity
@Table(name = "community")
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
public class Community {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@Column(nullable = false, columnDefinition = "TEXT")
private String content;
private String author;
private boolean isHidden;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@PrePersist
public void prePersist() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
public void preUpdate() {
this.updatedAt = LocalDateTime.now();
}
}
@Data
public class CommunityRequestDTO {
private String title;
private String content;
private String author;
private boolean isHidden;
}
@Data @Builder
public class CommunityResponseDTO {
private Long id;
private String title;
private String content;
private String author;
private boolean isHidden;
private LocalDateTime createdAt;
}
@Repository
public interface CommunityRepository extends JpaRepository<Community, Long> {
List<Community> findByIsHiddenFalse();
}
@Service
@RequiredArgsConstructor
public class CommunityService {
private final CommunityRepository communityRepository;
public Long createPost(CommunityRequestDTO dto) {
Community post = Community.builder()
.title(dto.getTitle())
.content(dto.getContent())
.author(dto.getAuthor())
.isHidden(dto.isHidden())
.build();
return communityRepository.save(post).getId();
}
public List<CommunityResponseDTO> getAllPosts() {
return communityRepository.findByIsHiddenFalse()
.stream()
.map(this::toDTO)
.toList();
}
public void updatePost(Long id, CommunityRequestDTO dto) {
Community post = communityRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Post not found"));
post.setTitle(dto.getTitle());
post.setContent(dto.getContent());
post.setIsHidden(dto.isHidden());
communityRepository.save(post);
}
public void deletePost(Long id) {
communityRepository.deleteById(id);
}
private CommunityResponseDTO toDTO(Community post) {
return CommunityResponseDTO.builder()
.id(post.getId())
.title(post.getTitle())
.content(post.getContent())
.author(post.getAuthor())
.isHidden(post.isHidden())
.createdAt(post.getCreatedAt())
.build();
}
}
@RestController
@RequestMapping("/api/community")
@RequiredArgsConstructor
public class CommunityController {
private final CommunityService communityService;
@PostMapping
public ResponseEntity<Long> create(@RequestBody CommunityRequestDTO dto) {
Long id = communityService.createPost(dto);
return ResponseEntity.ok(id);
}
@GetMapping
public ResponseEntity<List<CommunityResponseDTO>> getAll() {
return ResponseEntity.ok(communityService.getAllPosts());
}
@PutMapping("/{id}")
public ResponseEntity<Void> update(@PathVariable Long id, @RequestBody CommunityRequestDTO dto) {
communityService.updatePost(id, dto);
return ResponseEntity.ok().build();
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
communityService.deletePost(id);
return ResponseEntity.ok().build();
}
}
Client (브라우저, 앱)
│
▼
[Controller]
- 요청 수신 (POST /api/community)
│
▼
[Service]
- 비즈니스 로직 처리
- Repository 호출
│
▼
[Repository]
- JPA로 DB 접근 (save, findById 등)
│
▼
[Database]
- 실제 데이터 저장 / 조회 / 삭제
│
▲
└── 최종 응답 반환
이번 글에서는 API를 개발하면서 MVC 패턴을 어떻게 적용하는지 알아보았다.
Entity부터 Controller까지 계층을 명확히 구분하고, 각 계층의 역할에 맞게 책임을 나누어 구현하면 유지보수성과 확장성을 모두 확보할 수 있다.
+ 프로젝트에서 만든 코드지만.. 아직도 API를 만드는건 어렵다.. 막상 하려면 Service에서 막히는 😂
열띠미 하자..!!