| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- Spring Batch
- 프록시 패턴
- 성능 개선
- 빌더 패턴
- 스케줄러
- 계산기
- 디자인 패턴
- 백엔드
- 로드밸런서
- 프로그래머스
- 김영한
- 배치
- 이펙티브 자바
- java
- spring boot
- 스프링 배치
- DB
- Effective Java
- lv1
- Til
- redis
- 코드카타
- 템플릿 메서드 패턴
- 추상클래스
- 토스
- 자바
- 스프링
- 트러블슈팅
- GoF 23
- Spring
- Today
- Total
김코딩
일정 관리 API 프로젝트 회고 본문
아 이제는 진짜 Spring에 대해서 배우고있구나
이번 프로젝트부터 Java가 아닌 Spring Boot를 이용하여 API를 만드는 것을 진행하였습니다.
1. 프로젝트를 시작하며
이번 프로젝트는 단순한 콘솔 애플리케이션이 아닌, Spring Boot를 기반으로 한 RESTful 일정 관리 API 구현 과제였습니다.
일정 등록/조회/수정/삭제 기능을 제공하고, 비밀번호 검증과 유효성 검사, 페이지네이션 등 웹 서비스의 기초적인 구조를 갖춘 API를 만드는 것이었습니다.
처음 개발을 진행하면서 가장 혼란스러웠던 건 바로 RequestDto와 ResponseDto를 어떻게 나눠야 하는가였습니다.
당시 저는 Dto에 대한 개념이 제대로 잡혀 있지 않아서, “그냥 엔티티에다가 저장하고, 다시 꺼내서 응답 보내면 되는 거 아니야?”라고 생각했습니다.
2. requestDto와 responseDto, 그리고 내 사고의 변화

요구사항 속 화면 예시를 유심히 보면서 깨달았습니다.
- 사용자가 작성해서 서버로 보내야 하는 값은 작성자명, 비밀번호, 제목, 내용
- 반면 서버가 사용자에게 보여주는 값은 id, 작성자명, 제목, 내용, 수정일
이걸 보고 나서 머릿속이 정리됐습니다.
- RequestDto에는 사용자가 보내는 정보인 memberId, password, title, contents
- ResponseDto에는 사용자에게 보여줄 정보인 id, memberId, title, contents, modifiedAt
특히 비밀번호는 절대 사용자에게 노출되면 안 된다는 걸 명확히 의식하게 되었고,
createdAt과 modifiedAt 중에 무엇을 보여줄지도 화면 관점에서의 의도를 고려하게 되었습니다. (사용자 입장에선 수정일만 보여줘도 충분하니까!)
이렇게 요구사항을 ‘기능’이 아닌 ‘데이터의 흐름’으로 바라보는 시각을 갖게 된 게, 이번 프로젝트에서 가장 크게 배운 점이었습니다.
3. Spring Boot에서의 예외처리
이번 프로젝트에서는 단순히 비즈니스 로직을 구현하는 것을 넘어, 에러 상황을 어떻게 다루고 사용자에게 전달할 것인가에 대한 고민이 많았습니다.
예를 들어 다음과 같은 케이스를 처리해야 했습니다:
- 비밀번호가 틀렸을 때 수정/삭제 요청이 들어올 경우
- 존재하지 않는 일정 ID로 조회가 요청될 경우
- 필수 필드(title, contents 등)가 비어 있는 상태로 요청이 들어올 경우
Spring Boot의 예외 처리 방식에 따라 아래와 같은 구조로 리팩터링을 진행했습니다:
1) 커스텀 예외 클래스 정의
public class InvalidPasswordException extends RuntimeException {
public InvalidPasswordException() {
super("비밀번호가 일치하지 않습니다.");
}
}
2) 전역 예외 처리 클래스 작성
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(InvalidPasswordException.class)
public ResponseEntity<Map<String, Object>> handleInvalidPassword(InvalidPasswordException e) {
Map<String, Object> body = new HashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", 400);
body.put("error", "Bad Request");
body.put("message", e.getMessage());
return ResponseEntity.badRequest().body(body);
}
}
이렇게 구성하고 나니, 예외가 발생하더라도 사용자에게는 일관된 포맷의 메시지가 내려가고,
내부적으로는 비즈니스 로직과 예외 처리 코드가 깔끔하게 분리되면서 유지보수도 쉬워졌습니다.
4. Spring에서의 유효성 검사 (@Valid)
이번 프로젝트에서는 처음으로 Spring에서 제공하는 유효성 검사 기능인 @Valid와 javax.validation 애노테이션들을 본격적으로 적용해보았습니다.
처음에는 단순히 DTO에 조건문을 넣고 검증하려 했지만, 생각보다 코드가 지저분해지고 중복도 많아졌습니다.
예를 들어 일정 생성 시, title, contents, password 모두 null이나 공백이 오면 안 되었고, title은 200자 제한까지 있었기 때문입니다.
@Valid와 애노테이션 기반 검증
Spring에서는 @Valid와 함께 다음과 같은 애노테이션을 통해 DTO 단에서 유효성을 검증할 수 있었습 니다.
@NotBlank(message = "제목은 필수입니다.")
@Size(max = 200, message = "제목은 200자 이하로 입력해주세요.")
private String title;
@NotBlank(message = "내용은 필수입니다.")
private String contents;
@NotBlank(message = "비밀번호는 필수입니다.")
private String password;
이걸 RequestDto에 선언해두고, 컨트롤러에서는 @Valid 한 줄만 붙이면 자동으로 검증이 수행됐습니다.
@PatchMapping("/{id}")
public ResponseEntity<ScheduleResponseDto> updateSchedule(@PathVariable("id") Long id,
@RequestBody @Valid ScheduleRequestDto requestDto) {
return new ResponseEntity<>(scheduleServiceV1.updateSchedule(id, requestDto.getTitle(), requestDto.getContents(), requestDto.getPassword()), HttpStatus.OK);
}
검증 실패 시 응답 예시
{
"timestamp": "2025-05-14T13:45:00",
"status": 400,
"error": "Validation Failed",
"message": "title: 제목은 필수입니다., password: 비밀번호는 필수입니다."
}
이 메시지는 MethodArgumentNotValidException을 전역 예외 처리기로 받아서 포맷팅해주는 방식으로 구현했습니다.
졸업작품을 진행할 때만 해도 “RESTful API가 뭐지?”라는 생각이 들었고, 그저 Controller에서 MVC 패턴을 활용해 뷰를 띄우는 데에만 급급했었습니다. 하지만 이번 프로젝트를 통해 REST API의 구조와 흐름에 조금 더 가까워졌다는 느낌을 받았습니다.
특히 Dto의 필요성과 역할, 그리고 @Valid를 활용한 유효성 검증 시스템에 대해 처음으로 진지하게 고민해볼 수 있었습니다.
이번 경험은 단순히 과제를 완성하는 것을 넘어, 신입 백엔드 개발자로서 한 걸음 더 나아간 계기가 되었다고 생각합니다.
'TIL' 카테고리의 다른 글
| 트러블슈팅 - 모든 에러가 500에러? 원인은 DTO 였다.. (0) | 2025.05.23 |
|---|---|
| 트러블슈팅 - 전체 일정 조회 시 N+1 문제 발생 (0) | 2025.05.21 |
| Kiosk 프로젝트 회고 (3) | 2025.05.01 |
| 둘 다 객체 못 만든다면서요? 그런데 왜 둘이나 있는 거죠?(인터페이스 VS 추상클래스) (0) | 2025.04.25 |
| 객체를 못 만드는 클래스가 있다고요? 그게 바로 추상클래스 (2) | 2025.04.23 |