1. 도약을 향하여
“핵심적인 내 용은 아마도 2, 3, 9, 14장일 것이다.”
- 서문 중
가. 용어 정리
•
도약: 모델의 변경에 따라 복잡성이 크게 줄고 융통성과 표현력이 높아지는 현상입니다.
•
심층모델: 도약에 따른 결과로, 도메인 전문가의 주요 관심사와 핵심 도메인 지식을 알기 쉽게 표현하는 모델입니다.
•
리팩터링: 어플리케이션 기능을 수정하지 않고 않고 내부 설계를 개선하는 것입니다. 사전에 모든 설계적 판단을 내리지 않고, 기능을 유지한채 설계 변경에 따라 구조를 개선합니다.
•
명세: 객체가 특정 기준에 대한 만족여부를 확인하는 목적으로 정의한 객체입니다.
나. 요약
•
도약에 이르기 위한 핵심 작업으로 두 가지가 있습니다. 하나는 잠재되어 있는 암시적 개념이 드러나도록 도메인 지식에 민감해지는 것입니다. 다른 하나는 복잡한 절차적 로직을 명료하게 수정함으로써 복잡함 속에 누락된 개념을 찾는 것입니다.
flowchart TD O[도약] A[암시적 개념의 명시화] r[리팩터링] A --> r r --> A r --> O
Mermaid
복사
다. 통찰력을 향한 리팩터링
•
리팩터링은 통찰 리팩터링과 기술적 리팩터링으로 나눌 수 있습니다. 모델에 대한 통찰을 높일 수 있는 리팩토링은 기술적인 패턴으로 얻을 수 없습니다. 모델에 대한 설계적 개선 의지가 있어야 합니다. 더불어 깊은 생각, 경험, 재능을 바탕으로 지속적으로 시도할 때서야 비로소 좋은 도메인 모델, 통찰력이 묻어난 모델로 개선할 수 있습니다.
flowchart TD r{리팩터링} r -->|도메인 모델의 설계적 개선 동기| a(통찰 리팩터링) r -->| 기술적 동기 | b(기술적 리팩터링) a -->|깊은 생각 + 경험 + 재능| c(좋은 도메인 모델) b -->|구조화된 패턴| d(깔끔한 코드)
Mermaid
복사
라. 암시적 개념의 명시화
•
암시적인 개념을 구현할 때 도메인 지식을 깊이 있게 이해해야 합니다. 유비쿼터스 언어에서 암시적 개념이 위치하는 지점에 집중해야 합니다. 도메인에 대한 이해를 높이기 위해 관련 문헌을 참고하여 도메인에 대한 깊이 있는 관점을 가져야 합니다.
•
더불어 복잡한 절차적 로직이 존재하는 곳에서 누락된 개념을 찾아봐야 합니다. 복잡한 로직이 명료하지 못한 이유 중 누락된 개념을 명시하지 못했기 때문에 특정 객체가 지나치게 비대해졌을 가능성이 있습니다.
flowchart TD A[암시적 개념의 명시화] A --> B1[도메인 지식에 민감해지는 것] A --> B2[복잡한 로직 수정에 다른 누락된 개념 발견] B1 --> |언어에 귀 기울여라| C1(ubiquitous language 정제 및 설계 향상) B1 --> |어색한 부분을 조사하라| C2(다양한 아이디어와 모델 시도) B1 --> |모순점에 대해 깊이 고민하라| C3(모델 심층화) B2 --> |특정 목적으로 객체의 사용가능성 검증|D1[도메인 모델 간소화] B2 --> |컬렉션 내 객체 선택|D2[도메인 모델 간소화] B2 --> |요구사항에 따른 새로운 객체 생성 명시|D3[도메인 모델 간소화]
Mermaid
복사
2. 명세(Specification)
가. 일반
•
정의: 대상 객체가 특정 기준을 만족하는지 판단하는 객체입니다.
•
사용 배경: 업무 규칙의 다양성과 조합이 도메인 객체의 기본 의미를 압도할 때 사용해야 합니다.
나. 사용법
•
명세는 술어와 유사한 명시적인 값객체(VO)로 정의되어야 합니다.
•
명세의 기본적인 사용법은 명시된 기준을 만족여부를 검사하는 것입니다.
•
명세의 사용 목적으로 ‘객체의 사용 가능성 검증’, ‘컬렉션 내 객체 선택’, ‘요구사항에 따른 객체 생성’이 있습니다.
다. 예시
•
‘객체의 사용 가능성 검증’ 예시
@Service
class CodeVerificationService(
private val repository: CodeVerificationRepository
) {
private val spec = CodeVerificationSpec()
@Transactional
fun verify(cmd: VerificationCodeCommand) {
val codeVerification = repository.get(VerificationId(cmd.verificationId)) ?: throw BadCredentialException()
if (spec.isSatisfiedBy(codeVerification, cmd.code)) {
throw BadCredentialException()
}
codeVerification.verify()
}
}
data class CodeVerificationSpec {
fun isSatisfiedBy(verification: CodeEmailVerification, verificationCode: String): Boolean {
return verification.isExpired() || verification.inValid(verificationCode) || verification.verified()
}
}
Kotlin
복사
•
‘요구사항에 따른 객체 생성’ 예시
fun main() {
val imageSpec = ImageSpecification("1080p", "png", requiredTags = listOf("default"))
val imageFactory = ImageFactory()
val image = imageFactory.createImage(imageSpec)
}
class ImageFactory {
fun createImage(spec: Specification<Image>): Image {
val defaultResolution = "1080p"
val defaultFormat = "png"
val defaultTags = listOf("default")
val candidate = Image(defaultResolution, defaultFormat, defaultTags)
if (spec.isSatisfiedBy(candidate)) {
return candidate
} else {
throw IllegalArgumentException("Cannot create an image that satisfies the given specification.")
}
}
}
data class ImageSpecification(
private val requiredResolution: String,
private val requiredFormat: String,
private val requiredTags: List<String>
){
fun isSatisfiedBy(candidate: Image): Boolean {
return candidate.resolution == requiredResolution &&
candidate.format == requiredFormat &&
candidate.userTags.containsAll(requiredTags)
}
}
Kotlin
복사