VO (Value Object) 작성 가이드
기본 원칙
- Entity를 Controller에서 직접 반환하지 않는다.
- 요청 VO:
{목적}Req접미사 (예:PostCreateReq) - 응답 VO:
{목적}Res접미사 (예:PostDetailRes) - 위치:
domain/{도메인}/vo/패키지
요청 VO (Req)
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PostCreateReq {
@NotBlank(message = "제목은 필수입니다.")
@Size(max = 200, message = "제목은 200자 이내여야 합니다.")
private String title;
@NotBlank(message = "내용은 필수입니다.")
private String content;
@NotBlank(message = "카테고리는 필수입니다.")
private String category;
}
규칙:
@Getter,@NoArgsConstructor,@AllArgsConstructor사용- Bean Validation 어노테이션으로 서버사이드 검증
@Builder불필요 (Controller에서 Jackson이 역직렬화)
응답 VO (Res)
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PostDetailRes {
private Long id;
private String title;
private String content;
private String category;
private LocalDateTime createdAt;
// Entity → VO 변환 정적 팩터리 메서드
public static PostDetailRes from(Post post) {
return PostDetailRes.builder()
.id(post.getId())
.title(post.getTitle())
.content(post.getContent())
.category(post.getCategory())
.createdAt(post.getFrstRegistDt())
.build();
}
}
규칙:
@Getter,@Builder,@NoArgsConstructor,@AllArgsConstructor사용from(Entity)정적 팩터리 메서드로 변환 로직 캡슐화- Service에서
.map(PostDetailRes::from)패턴으로 간결하게 사용
목록 응답 VO
목록 조회 시에는 상세 조회보다 적은 필드를 반환하는 별도 VO를 만든다.
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PostListRes {
private Long id;
private String title;
private String category;
private LocalDateTime createdAt;
public static PostListRes from(Post post) {
return PostListRes.builder()
.id(post.getId())
.title(post.getTitle())
.category(post.getCategory())
.createdAt(post.getFrstRegistDt())
.build();
}
}
시간 필드 타입
| 상황 | 타입 |
|---|---|
| 일반 날짜/시간 | LocalDateTime |
| 타임존 포함 | OffsetDateTime |
| 날짜만 | LocalDate |
// LocalDateTime — 시간대 없이 저장
private LocalDateTime createdAt;
// OffsetDateTime — 타임존 포함 (외부 API 연동 시)
private OffsetDateTime publishedAt;
Lombok 규칙 요약
| 클래스 종류 | 적용 어노테이션 |
|---|---|
| Entity | @Getter, @NoArgsConstructor(access = PROTECTED), @SuperBuilder |
| 요청 VO (Req) | @Getter, @NoArgsConstructor, @AllArgsConstructor |
| 응답 VO (Res) | @Getter, @Builder, @NoArgsConstructor, @AllArgsConstructor |
| View Model | @Getter, @AllArgsConstructor (간단한 경우) |
금지 사항:
@Data사용 금지 (equals/hashCode 오버라이드 위험)@AllArgsConstructor단독 사용 금지 (필드 순서 의존 위험)- Entity에
@Setter사용 금지 (불변성 유지)
실제 프로젝트 VO 예시
// domain/digest/vo/DigestNewsListRes.java
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DigestNewsListRes {
private Long id;
private String subject;
private LocalDateTime createdAt;
public static DigestNewsListRes from(DigestNews news) {
return DigestNewsListRes.builder()
.id(news.getId())
.subject(news.getSubject())
.createdAt(news.getFrstRegistDt())
.build();
}
}
VO 체크리스트
- [ ] 요청 VO:
Req접미사,@Getter @NoArgsConstructor @AllArgsConstructor - [ ] 응답 VO:
Res접미사,@Getter @Builder @NoArgsConstructor @AllArgsConstructor - [ ]
from(Entity)정적 팩터리 메서드 포함 - [ ] Bean Validation 어노테이션으로 서버사이드 검증 (요청 VO)
- [ ]
@Data사용 금지 - [ ]
domain/{도메인}/vo/위치 확인