[DDD] Factory Method & Repository
[DDD] Factory Method & Repository
Factory method
“객체를 어떻게 만드는지” 보다 “왜/언제 만들어 지는지” 를 드러내고 복잡한 생성과정을 감추는 역할
Factory method의 장점
- 생성 로직이 복잡한 경우
new User(username, email, password, …)같이 끝나지 않고 암호화, 중복체크, 값 객체 조립 같이 여러 단계가 필요할때
- 도메인 용어로 의도를 드러내고 싶을 때
- 생성자는 단순히 “객체를 만든다” 인데, 팩토리메서드는 회원등록(register), 예약생성(reserve), 주문발행(placeOrder) 같이 도메인행위를 들어낼 수 있음 = 읽는 사람을 편하게 읽히게 해줌!
- 여러 생성 방법이 있을 때
- 같은 엔티티라도 다르게 초기화가 필요할 수 있음
- 생성자 오버로딩 대신 팩토리메서드로 구분하면 명확해짐
- 생성자 노출을 제한하고 싶을 때
- 생성자를
protected나private으로 막아두고, 반드시 팩토리 메서드를 통해서만 객체가 만들어지도록 강제. - 잘못된 방식으로 객체가 만들어지는걸 방지
- 생성자를
Repository 사용
User.java (엔티티)
1
2
3
4
5
6
7
8
9
public static User register(String username, String email, String encryptedPassword, String nickname, String phone) {
return new User(
username,
email,
encryptedPassword,
nickname,
phone
);
}
Service 계층
1
2
3
4
5
6
7
8
9
10
@Override
public void registerUser(UserRegistrationRequest request) {
userValidator.RegisterValidate(request.username(), request.email());
String encoded = passwordEncoder.encode(request.password());
User user = User.register(request.username(), request.email(), encoded, request.nickname(), request.phone());
userRepository.save(user);
}
new를 사용했을 경우
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void registerUser(UserRegisterRequest request) {
//잘못된 사용 가능성: 암호화를 빼먹을 수 있음
//왜? 생성하는지 보는사람이 모를 수 있음
User user = new User(
request.username(),
request.email(),
request.getPassword(), // 평문 들어감 (보안 문제)
request.nickname(),
request.phone()
);
userRepository.save(user);
}
생성자는 객체 내부 구현 세부사항을 드러내는 반면, 팩토리 메서드는 도메인 언어로 행위를 드러내어 모델을 더 읽기 쉽게 만든다.
Repository
도메인 객체를 컬렉션처럼 다루게 해주고, DB 세부사항은 감추게 해준다.
Repository의 장점
- 도메인 객체 저장/수정 이 필요할때
- User, Order 같은 엔티티를 DB에 저장하거나 불러와야 할 때
- 도메인 계층은 DB를 몰라도되고 그냥 컬렉션에 넣고 꺼낸다는 느낌으로 사용
- 도메인 계층의 순수성 유지
- 엔티티가 SQL, JPA 같은 기술에 오염되지 않도록
- 서비스, 도메인 계층은 User를 저장한다. 라고만 표현
- 구체적인 DB 접근은 Spring Data JPA 구현체가 알아서
- 의도를 보여주는 쿼리메서드
findByEmail(String email)같은 메서드 시그니처 자체가 도메인 규칙을 드러냄
- 테스트 용이
- 리포지토리를 인터페이스로 두면, 테스트에서 가짜 구현(Mock, InMemoryRepo 등) 으로 갈아 끼울 수 있음
규격서 개정 DAO → Repository 리팩터링
기존 개정 로직
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
31
32
33
@Override
public SpcfShtMVo rvsnSpcfsht(SpcfShtMVo vo) {
// 원본 규격서 / 규격서 최종 규격서 여부 N 처리
updateOriginalSpcfSht(vo);
/*********************저장 METHOD***********************/
if(!this.spcfShtValidCheck(vo)){
throw new CustomException("해당 공정으로 등록된 품목 규격서가 존재합니다.");
}
// 개정 규격서 등록 SELECT INSERT , 개정 규격서 일련 번호 반환
SpcfShtMVo rvsnSpcfshtVo = createNewSpcfSht(vo);
vo.setRvsnSpcfshtSn(rvsnSpcfshtVo.getSpcfshtSn()); // 개정 규격서 일련번호
vo.setOriginSpcfshtSn(rvsnSpcfshtVo.getOriginSpcfshtSn()); // 기존 규격서 일련번호
// 개정 규격서 / 품목채취정보 SELECT INSERT
createNewItemColctInfo(vo);
// 개정 규격서 / 품목별 규격서 이력 저장
saveReversionSpcfshtHstry(vo);
// 개정 규격서 / 품목채취정보 SELECT INSERT
spcfShtMDao.insertNewItemColctInfo(vo);
// 개정 규격서 / 품목모니터링Point SELECT INSERT
createNewItemMonitrgPoint(vo);
// 개정 규격서 / 규격서-시험항목 및 변수 SELECT INSERT
createNewTestArtcls(vo);
// 개정된 규격서 조회
return spcfShtMDao.selectSpcfsht(vo);
}
- DAO를 직접 호출해 SQL에 의존
- 데이터 접근 로직과 비지니스 로직이 혼합 → 비지니스 로직이 DB작업 나열로 보임
insertNewItemColctInfo→ 구체적 구현에 의존
도메인 엔티티
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
@Table(name = "SY_ITEM_MONITRG_POINT")
public class SpcfShtMVo {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
private String wkroomAtPointSn;
private String spcfshtSn;
private String bplcCd;
private boolean last; // 최종 규격 여부
public SpcfShtMVo revise() {
this.last = false;
return new SpcfShtMVo(/* 필요한 값 복사/초기화 */);
}
}
레파지토리 인터페이스
1
2
3
4
5
6
@Repository
public interface SpecificationSheetRepository extends JpaRepository<SpcfShtMVo, Long> {
Optional<SpcfShtMVo> findById(Long id);
//커스텀 쿼리도 정의 가능
}
서비스 레이어
1
2
3
4
5
6
7
8
9
10
11
12
@Transactional
public SpcfShtMVo reviseSpecification(Long sheetId) {
SpcfShtMVo original = repository.findById(sheetId)
.orElseThrow(() -> new NotFoundException("규격서 없음"));
SpcfShtMVo revised = original.revise();
repository.save(original);
repository.save(revised);
return revised;
}
- 서비스 코드는 ‘저장’이라는 비즈니스 의도만 남아 코드량 줄어 → 가독성 🔺
- DB 세부사항은 Repository가 담당 → 책임 분리
- 테스트 시 InMemoryRepository 대체 가능 → 단위 테스트 쉬움
revise()로 도메인 의미가 코드에 드러남 → 의사소통 용이- 로직 변경 시 엔티티 내부만 수정하면 됨 → 유지보수성 🔺
DAO는 데이터 접근 세부사항을 드러내는 반면, 리포지토리는 도메인 언어로 의도를 드러내어 모델을 더 순수하게 만든다.
「도메인 주도 설계란 무엇인가? 쉽고 간략하게 이해하는 DDD」를 읽고
This post is licensed under
CC BY 4.0
by the author.