강의

멘토링

커뮤니티

인프런 커뮤니티 질문&답변

정라잇님의 프로필 이미지
정라잇

작성한 질문수

토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1

Member 도메인 코드 개선

코틀린에서 value class 적용 시 문제

작성

·

38

0

안녕하세요 코틀린으로 현재 강의를 수강하고 있는 수강생입니다.

 

현재 자바로 작성된 코드를 보고 설명과 함께 어떤 이유로 이런 코드를 작성한 것인지 생각하며, 코틀린으로 이 개념을 적용하면 어떻게 작성할 수 있을지 DDD와 클린 아키텍처를 코틀린 문법 활용하여 구상하는 연습 중입니다.

 

현재 Member 도메인 코드 개선 강의에서 value class 적용하여 필드의 값이 바뀌는 문제(email자리에 nickname이 오더라도 같은 String이라 컴파일 에러가 안 남)를 해결하려 시도했습니다

 

package org.example.splearn.domain

@JvmInline
value class Email(
    val value: String,
)

@JvmInline
value class Nickname(
    val value: String,
)

@JvmInline
value class PasswordHash(
    val value: String,
)

class Member private constructor(
    val email: Email,
    var nickname: Nickname,
    var passwordHash: PasswordHash,
    var status: MemberStatus,
) {
    fun activate() {
        check(status == MemberStatus.PENDING) {
            "회원이 PENDING 상태가 아닙니다"
        }
        this.status = MemberStatus.ACTIVATE
    }

    fun deactivate() {
        check(status == MemberStatus.ACTIVATE) {
            "회원이 ACTIVE 상태가 아닙니다"
        }
        this.status = MemberStatus.DEACTIVATED
    }

    fun verifyPassword(
        password: String,
        passwordEncoder: PasswordEncoder,
    ): Boolean = passwordEncoder.matches(password, this.passwordHash.value)

    fun changeNickname(nickname: String) {
        this.nickname = Nickname(nickname)
    }

    fun changePassword(
        password: String,
        passwordEncoder: PasswordEncoder,
    ) {
        this.passwordHash = PasswordHash(passwordEncoder.encode(password))
    }

    fun isActive(): Boolean = this.status == MemberStatus.ACTIVATE

    companion object {
        fun create(
            memberCreateRequest: MemberCreateRequest,
            passwordEncoder: PasswordEncoder,
        ): Member =
            Member(
                email = memberCreateRequest.email,
                nickname = memberCreateRequest.nickname,
                passwordHash =
                    PasswordHash(
                        passwordEncoder.encode(memberCreateRequest.password.value),
                    ),
                status = MemberStatus.PENDING,
            )
    }
}

강의대에서는 static 메소드인 of에서 MemberCreateRequest를 파라미터로 사옹하고 있습니다. 코틀린이라 companion object를 사용했구요

 

그러던 중 "헥사고날 아키텍처의 특성을 고려하면 의존성 외부 로직인 dto가 내부로 향해야 하고 따라서 도메인이 dto에 의존하는 것이 괜찮을까" 하는 의문이 들었습니다.

 

companion object {
        fun create(
            email: Email,
            nickname: Nickname,
            password: String,
            passwordEncoder: PasswordEncoder,
        ): Member =
            Member(
                email = email,
                nickname = nickname,
                passwordHash =
                    PasswordHash(
                        passwordEncoder.encode(password),
                    ),
                status = MemberStatus.PENDING,
            )
    }

그래서 코드를 수정해 보면 이런 식으로 수정해 볼 수 있을 것 같습니다.

 

이에 대해서 토비님 의견이 어떠신지 여쭙고 싶습니다

답변 2

0

토비님의 프로필 이미지
토비
지식공유자

이 부분은 강의에서 제가 자세히 설명을 드린 것인데요. MemberCreateRequest는 DTO가 아니라 도메인 오브젝트입니다. 도메인 모델을 설계할 때, 회원이 등록되기 위해서 이런 정보가 반드시 충족되어야 한다라는 모델의 개념을 표현한 일종의 값 객체입니다.

값을 담아서 전달하는 오브젝트를 무조건 DTO라고 부르면 그래서 혼란이 생기기 쉬운 것 같습니다. MemberCreateRequest를 도메인의 구성 요소로 두었기 때문에 Member가 이를 참조하는 것에 아무 문제가 없습니다.

물론 설계하기 나름이라 MemberCreateRequest는 도메인 밖에 존재하는 DTO로 만들고, Member를 호출할 때 파라미터로 풀어도 좋습니다. 다만 강의에서 사용한 자바에는 코틀린처럼 파라미터가 길어질 때 혼란이나 실수를 줄일 수 있는 named parameter가 없죠. 그래서 MemberCreateRequest를 만들고 이를 복제하는 코드를 static method 안에 두어서 자칫 순서가 바뀌거나 항목이 추가되었을 때 실수를 막기 좋도록 구성한 것입니다.

코틀린이라면 named parameter로 생성자 또는 companion object의 메소드를 호출하는 식으로 코드를 만들어도 됩니다. 저도 코틀린을 쓸 때 그런 스타일로 개발해본 적이 있습니다.

0

안녕하세요, 인프런 AI 인턴입니다.
정라잇님께서 제기하신 문제에 대해 코틀린을 활용한 도메인 모델 개선 관점에서 설명드리겠습니다.

헥사고날 아키텍처의 철학을 따르는 경우, 도메인 모델은 외부 시스템에 의존적이지 않아야 합니다. 이 경우 Member 도메인이 외부 DTO에 의존하면, 도메인의 순수성을 침해할 수 있습니다. 따라서, 초기 생성 시 필요한 객체는 도메인 내부에 정의된 다른 클래스를 통해 직접 전달받는 것이 좋습니다.

companion objectcreate 메서드에서 MemberCreateRequest 대신 도메인 객체 (Email, Nickname, 등)를 직접 받아들이도록 수정하신 것은 좋은 결정입니다. 이는 의존성을 명확하게 하고 도메인의 독립성을 지켜줍니다. 아래와 같은 사항을 유의하시면 좋겠습니다:

  1. 도메인 객체는 필요한 필드를 직접 받아 생성하도록 하여 응집도를 높입니다.
  2. 외부 입력, 즉 DTO와 같은 것은 애플리케이션 계층이나 다른 외부 계층에서 도메인 모델로 변환되도록 처리할 수 있습니다.

최종적으로, 도메인 모델을 최대한 외부 의존성에서 분리하고, 비즈니스 로직에만 집중할 수 있도록 설계하는 것이 목표입니다. 정리하자면, 현재 수정 방향은 적절하며 도메인의 독립성을 강화해 줄 수 있습니다.

추가적인 참고 자료:
Spring Data JPA - Nullability in Kotlin-based Repositories
Spring Data JDBC - Kotlin support

정라잇님의 프로필 이미지
정라잇

작성한 질문수

질문하기