Search
📘

Domain Driven Design - Eric Evans

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
복사