개발 가이드 백엔드 VO (Value Object) 작성 가이드
최종 수정:

VO (Value Object) 작성 가이드

요청/응답 VO 작성 규칙 및 Lombok 컨벤션

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/ 위치 확인
AI 문서 검색

현재 페이지 내용을 기반으로 질문하세요.