Day 55 - Response
이 글은 2026년 05월 20일 작성된 글입니다.
오늘은 ApplicationContext의 동적 빈 탐색 개선과 REST API 삭제/작성 요청 처리, RsData 응답 구조까지 정리했다.
1. ApplicationContext 하드코딩 제거
기존에는 ApplicationContext 내부에서 특정 클래스 이름이나 생성 방식을 하드코딩하는 부분이 있었다.
이를 ClsUtil을 활용하는 방식으로 변경하여
코드를 더 유연하게 만들었다.
ClsUtil.construct(cls, args);- 하드코딩 감소
- 동적 객체 생성 가능
- IoC 컨테이너 구조 개선
2. 테스트용 패키지 이동
com.ll.domain.testPost.testPost 패키지를
테스트 폴더로 옮길 수 있게 되어 구조를 정리했다.
테스트 전용 클래스는 실제 애플리케이션 코드와 분리하는 것이 좋다.
- 테스트 코드와 운영 코드 분리
- 프로젝트 구조 명확화
3. Reflections 라이브러리 추가
패키지 스캔을 위해 org.reflections:reflections 라이브러리를 추가했다.
implementation("org.reflections:reflections:0.10.2")
implementation("ch.qos.logback:logback-classic:1.5.12")이 라이브러리를 사용하면 특정 패키지 하위에서 어노테이션이 붙은 클래스를 자동으로 찾을 수 있다.
4. @Component 계열 클래스 스캔
@Component가 붙은 클래스를 스캔하여
빈 후보로 수집했다.
Reflections reflections = new Reflections("com.ll", TypesAnnotated);
Map<String, Class<?>> clsMap = reflections
.getTypesAnnotatedWith(Component.class)
.stream()
.filter(cls -> !cls.isAnnotation())
.collect(
LinkedHashMap::new,
(map, cls) -> map.put(Ut.str.lcfirst(cls.getSimpleName()), cls),
Map::putAll
);@Service, @Repository처럼
@Component를 기반으로 한 어노테이션도 함께 탐색할 수 있다.
5. 빈 이름 규칙
스프링 빈 이름 규칙처럼 클래스명의 첫 글자를 소문자로 바꾸어 빈 이름을 만들었다.
MemberService -> memberService이를 위해 lcfirst 유틸을 구현했다.
public static String lcfirst(String str) {
if (str == null || str.isEmpty()) return str;
char firstChar = str.charAt(0);
if (Character.isLowerCase(firstChar)) return str;
return Character.toLowerCase(firstChar) + str.substring(1);
}6. annotatedClasses 함수 구현
특정 패키지 하위에서 특정 어노테이션을 가진 클래스를 수집하는 함수를 구현했다.
Map<String, Class<?>> annotatedClasses =
ClsUtil.annotatedClasses("com.ll", Component.class);테스트에서는 다음 빈들이 잘 수집되는지 확인했다.
- testFacadePostService
- testPostService
- testPostRepository
7. ApplicationContext 동적 빈 검색
이제 ApplicationContext는 더 이상 하드코딩 방식이 아니라 패키지를 스캔해서 동적으로 빈을 찾도록 변경했다.
applicationContext.genBean("testPostService");내부적으로는 스캔된 클래스 목록에서 빈 이름에 맞는 클래스를 찾아 객체를 생성한다.
8. 댓글 삭제 API 구현
REST API에서 댓글 삭제 기능을 구현했다.
/api/v1/posts/1/comments/2/delete처음에는 개발 편의를 위해 GET 방식으로 호출할 수 있게 만들었다.
하지만 삭제는 서버 상태를 변경하는 작업이므로 원칙적으로는 GET이 아니라 DELETE 메서드를 사용하는 것이 맞다.
9. orphanRemoval과 더티체킹
댓글 삭제 과정에서 orphanRemoval = true와 더티체킹을 활용했다.
@OneToMany(mappedBy = "post", orphanRemoval = true)
private List<PostComment> comments;부모 엔티티에서 자식 엔티티를 제거하면 JPA가 변경을 감지해서 삭제 쿼리를 실행한다.
단, 더티체킹은 트랜잭션 안에서 동작하므로
@Transactional이 필요하다.
10. 조회 메서드 readOnly 트랜잭션
조회 전용 API에는 readOnly = true 옵션을 적용했다.
@Transactional(readOnly = true)조회 작업임을 명확히 표현할 수 있고, 불필요한 변경 감지를 줄일 수 있다.
11. resultCode와 msg
조회가 아닌 요청에는 처리 결과를 명확하게 알려주는 응답이 필요하다.
{
"resultCode": "200-1",
"msg": "댓글이 삭제되었습니다."
}resultCode는 HTTP 상태코드와 세부 구분코드를 함께 표현한다.
200-1- 200: 성공
- -1: 세부 구분 코드
12. RsData 클래스 도입
조회 API를 제외한 요청 응답에 사용할 공통 응답 객체를 만들었다.
public class RsData<T> {
private String resultCode;
private String msg;
private T data;
}사용 예시:
- 글 작성 응답
- 글 수정 응답
- 글 삭제 응답
- 댓글 작성 응답
- 댓글 삭제 응답
13. data 필드 추가
resultCode와 msg만으로 부족한 경우를 위해
data 필드를 추가했다.
RsData<PostDto>응답 결과와 함께 추가 데이터를 담을 수 있게 되었다.
14. RsData 제네릭 적용
처음에는 data 타입을 Object로 처리했지만,
이후 제네릭을 적용했다.
RsData<PostDto>
RsData<PostCommentDto>
RsData<Void>제네릭을 사용하면 응답 데이터 타입을 더 명확하게 표현할 수 있다.
15. RsData
응답에 추가 데이터가 필요 없는 경우에는
RsData<Void>를 사용한다.
RsData<Void>데이터 없이 resultCode와 msg만 전달할 때 적합하다.
16. 글 삭제 API 구현
글 삭제 기능도 구현했다.
/api/v1/posts/1/delete글 삭제 시 관련 댓글은
CascadeType.REMOVE 설정 덕분에 함께 삭제된다.
17. Postman 사용
Postman을 사용해 REST API를 직접 호출하고 테스트했다.
예시 요청:
GET /api/v1/posts
GET /api/v1/posts/2
GET /api/v1/posts/2/comments
GET /api/v1/posts/2/comments/4Postman을 사용하면 브라우저로 호출하기 어려운 DELETE, POST 요청도 쉽게 테스트할 수 있다.
18. DELETE 메서드 적용
삭제 요청을 GET에서 DELETE 방식으로 변경했다.
기존:
GET /api/v1/posts/1/delete변경:
DELETE /api/v1/posts/1REST API에서는 삭제 작업에 DELETE 메서드를 사용하는 것이 더 자연스럽다.
19. POST 요청과 @RequestBody
글 작성 API는 POST 요청으로 처리하고, 요청 본문은 JSON 형태로 받도록 변경했다.
{
"title": "제목",
"content": "내용"
}@PostMapping
public RsData<PostDto> write(@RequestBody PostWriteReqBody reqBody) {
}@RequestBody는 JSON 요청 본문을 자바 객체로 변환해준다.
20. REST API와 BindingResult
REST API에서는 @Valid를 사용하더라도
BindingResult를 직접 사용하는 경우가 많지 않다.
폼 기반 서버 렌더링에서는 검증 실패 후 다시 폼 화면을 보여줘야 하지만, REST API에서는 보통 에러 응답을 JSON으로 내려준다.
21. ReqBody 네이밍
기존 PostWriteForm이라는 이름을
PostWriteReqBody로 변경했다.
public record PostWriteReqBody(
String title,
String content
) {
}REST API에서는 폼보다는 요청 본문을 의미하는
ReqBody라는 이름이 더 정확하다.
✅ 정리
- Reflections 라이브러리를 사용해 @Component 계열 클래스를 동적으로 수집할 수 있었다.
- ApplicationContext에서 하드코딩을 줄이고 패키지 스캔 기반 빈 생성을 구현할 수 있었다.
- 댓글 삭제에서는 orphanRemoval과 더티체킹, 트랜잭션의 관계를 이해할 수 있었다.
- RsData를 도입하면서 REST API 응답 구조를 공통화할 수 있었다.
- DELETE, POST, @RequestBody를 사용하면서 REST API다운 요청 구조에 가까워졌다.