A. 개요
JPA에 익숙 해져가며 가며 도메인 주도 개발(DDD, Domain Driven Development)도 조금 배워가던 몇 달 전 아직 DDD와 객체지향 (OOP, Object Oriented Programming)에 관련된 원칙을 따르는 코드를 만드는 것이 아직 어색해서 인지, 코드 리뷰 중 몇가지 취약점이 있을 수 있을 코드가 발견되었습니다.
여기에는 여러가지 이슈들이 있어서 하나씩 풀어 보려고 합니다.
B. 문제가 되던 부분 들과 코드 작성의 이유들
// 문제의 코드
public static Progress from(ProgressRegisterForm form) {
Help help = Help.createEmptyHelpWithOnlyId(form.getHelpId());
// -> 이 정적 팩토리 매서드는 빌더 패턴을 이용해 id만 들어가 있는 "불완전한 상태"의 Help를 생성한다.
return Progress.builder()
.helperId(form.getHelperId())
.help(help)
.build();
}
1. 주요 문제 (Main Problem)
객체를 불완전한 상태로 사용하여, 객체의 캡슐화를 떨어 뜨렸고, 현재로서는 정적 팩토리 매서드의 이름도 불분명한 상황이라 협업 상황에서 의도와 다르게 쓰일 수 있는 가능성이 높아 졌습니다.
- 이렇게 사용한 이유: 레이어드 아키텍처(Layered Architecture)에 맞도록 표현 계층의 Request 객체(예시에서의 이름은 form)가 응용영역에서 바로 사용되지 않도록 도메인 영역의 객체인 Progress로 변환시켜주고 싶었고, JPA의 연관관계를 사용하고 있었어서 Progress를 완전한 상태로 리턴 시키려면 Help가 필요했습니다. 아이러니하게도 객체를 완전한 상태로 리턴하기 위해 정작 메서드 내부에서는 객체를 불완전한 상태로 사용하였습니다.
- 이 경우에 대한 솔루션: 강한 결합을 사용하며 JPA연관관계를 사용하고 싶었다면 파라미터에 필요한 값들을 넘겨주어 응용계층에서 Progress를 생성하거나 파라미터에 값들이 너무 많다면 필요한 값들을 DTO를 만들어 응용계층에 넘겨 줄 수도 있었습니다. 이때는 불완전 객체 사용에 대한 주의가 부족했습니다.
Article1. 객체는 불완전하게 사용 되면 안된다.
Article2. 안정성을 더하는 불변객체
2. 빌더패턴 사용시 주의 점
빌더 패턴은 편리합니다. 특히 생성자가 많이 필요한 객체를 만들때 가독성을 높여 주어 뺴놓지 않고 필요한 필드값들을 주입할 수 있습니다. 그렇지만, 객체 생성이 너무 자유롭습니다. 특정한 목적이 아니라, 사용자의 입맛에 따라 객체를 쉽게 만들 수 있다 보니, 무분별하게 만들어진 객체는 쉽게 SRP(단일 책임 원칙, Single Responsibility Principle)를 위반하고 책임을 알 수 없고 의도와 다른 사용처가 생기기까지 하면 코드의 의도를 점점 파악하기 어렵게 만들 수 있습니다. 이를 위해 빌더를 Private이나 Protected로 접근 제한하고 의도를 분명히 할 수 있는 정적 팩토리 매소드를 통해 객체를 생성하려고 하고 있지만 메소드의 이름을 보면 어떤 비즈니스와 연관이 있는지 이 경우는 의도조차 명확하지 않았습니다.
3. JPA는 비즈니스가 아닌 기술의 영역에 속합니다.
구현에 집중하다 보니 기능 구현과 데이터 중심의 절차적 프로그래밍(PP, Procedural Programming)으로 개발을 할 때는 생각하지 못했던 부분입니다. 이전에 구현하던 코딩에서는 인프라스트럭처 계층으로 대표되는 기술의 영역과 비즈니스(도메인)의 영역이 섞이게 되어 있던 경우가 많아, 이 차이를 구분하지 못하고 있었습니다.
JPA는 편의상 도메인 계층에서 사용되기도 하지만 JPA규칙이 도메인 모델링에 방해가 되는 경우 도메인 영역의 모델과 JPA엔티티들은 1대1로 같은 로직으로 구현하기 어려운 경우도 있습니다. 이 경우 따로 비즈니스 로직의 도메인 모델은 도메인 계층에 구현하고 JPA엔티티들은 인프라 영역에 구현하는 것도 하나의 방법입니다. 이때도 JPA코드와 도메인 모델의 코드가 섞여 있다 보니, JPA의 구현방법에 맞추기 위해서 취약한 코드를 만들게 되었습니다. 사실 매서드의 이름 문제를 둘째 치고, 널 관리라도 잘 되었으면, 그래도 사용할만 했을 수도 있지만, 이떄는 의도를 가지고 널을 사용한게 아니라서 더욱 문제가 되었습니다.
Article 3. JPA는 레이어드 아키텍처에서 어디에 속할까?
Article 4. 레이어드 아키텍처 (Layered Architecture) feat. DDD
4. 모델 간의 연관관계를 강한 결합만으로 만들 필요는 없습니다.
이 부분도 조금 생소해서 놀랐던 부분인데, 모델링보다 JPA에 익숙하다 보니 엔티티를 공유하는 강한 연관관계에 익숙했습니다. 그러나 경우에 따라서는 id값만 가지고 있는 약한 결합으로 구현하는 것이 모델간 의존성을 낮추고 자율성을 높여 변경에 유연한 코드를 만들 수도 있습니다. 그래서 Progress와 Help는 경우에 따라 Progress의 로직이 복잡해지고 비즈니스와 생명주기가 달라지면, 서로의 모든 값을 지니고 있는 강한 결합이 아닌 id 값만 가지고 나중에 조회하여 쓰는 약한 결합으로 구현 하는 것도 가능합니다. 이는 기술 영역의 JPA 연관관계가 아닌 도메인 모델간의 연관관계를 고려하여 작성 되어야 합니다. JPA를 배운 후 JPA의 연관관계를 이용해 엔티티간 관계를 맷는 것을 너무 당연하게 여기고 있었습니다.
Article 5. DDD 관점에서 연관관계