# 서비스 구현이나 컴포넌트 클래스를 작성할 때 필요한 비즈니스 패턴 ```text VO 생성에 도메인 지식이 필요한가? ├── YES → 정적 팩토리 패턴 (VO 내부에 캡슐화) └── NO └── 필드 계산 전에 조건 분기나 null 처리가 필요한가? ├── YES → 조건부 빌더 패턴 └── NO → 기본 빌더 패턴 ``` ## 각 패턴의 적용 조건 정적 팩토리 패턴 — VO가 생성 맥락을 알아야 할 때 - Entity → VO 변환처럼 출처가 명확한 경우 - 변환 규칙이 VO의 책임인 경우 - 여러 곳에서 동일한 변환 로직이 반복될 가능성이 있는 경우 조건부 빌더 패턴 — 조건에 따라 필드가 달라질 때 - 특정 필드를 조건부로 세팅해야 하는 경우 - 빌더 중간에 null 체크, 매핑, 계산이 끼어드는 경우 - 한 줄 체이닝으로 쓰면 가독성이 떨어지는 경우 ## 1. 정적 팩토리 패턴 (VO 내부 생성) Entity → VO 변환처럼 생성 맥락이 명확할 때, 변환 로직을 VO 내부에 캡슐화한다. - VO 내부에 `static` 메서드로 생성 로직을 정의한다. - 네이밍은 `from`, `of`, `create`를 사용한다. - 도메인 지식과 생성 로직을 VO 내부로 캡슐화한다. - VO와 강하게 연관된 생성 로직은 외부로 분리하지 않는다. ```java public class ServerInfoVO { public static ServerInfoVO fromEntity(ServerInfoEntity entity) { return ServerInfoVO.builder() .connectId(entity.getConnectId()) .userId(entity.getUserId()) .passwordOrToken(entity.getPasswordOrToken()) .type(entity.getType()) .uri(entity.getUri()) .createdDate(entity.getCreatedDate()) .updatedDate(entity.getUpdatedDate()) .build(); } } ``` ## 2. 조건부 빌더 패턴 빌더를 변수로 분리해 조건 처리와 값 가공을 명확하게 표현한다. 한 줄 체이닝 안에 조건 분기나 null 처리가 끼어드는 순간 이 패턴으로 전환한다. ```java // ✅ 권장 AlmIssueVO.AlmIssueVOBuilder builder = AlmIssueVO.builder() .fields(issueFieldData) .armsStateCategory(getMappingCategory(serverInfoVO, issueFieldData)); // 조건부 필드 if (serverInfoVO.isCloud()) { builder.priority(normalizePriority(issueFieldData.getPriority())); } else { builder.priority(issueFieldData.getPriority()); } return builder.build(); ``` ## 3. 가드 절 - 유효하지 않은 값은 초기에 throw로 처리한다. - 정상 흐름은 하단에 위치하도록 작성한다. ```java BbsEntity bbsEntity = Optional.ofNullable( esCommonRepositoryWrapper.findDocById(bbsDTO.getId()) ).orElseThrow(() -> new IllegalArgumentException("삭제된 게시글입니다.")); ``` ## 4 설명 변수 - 메서드 체이닝 결과나 복잡한 반환값은 변수로 분리한다. ```java @Override public String createWiki(WikiDTO wikiDTO) { WikiEntity savedEntity = esCommonRepositoryWrapper.save(wikiDTO.createEntity()); return savedEntity.generateIdWithVersion(); } ## 안티패턴 ```java // ❌ 조건 분기가 있는데 한 줄 체이닝 → 조건부 빌더로 전환 return SomeVO.builder() .field(condition ? computeA() : computeB()) .other(list.stream().filter(...).findFirst().orElse(null)) .build(); // ❌ Entity 변환인데 Service에 변환 로직 노출 → 정적 팩토리로 이동 SomeVO vo = SomeVO.builder() .id(entity.getId()) .name(entity.getName()) .build(); ```