Spring/blobnom Kotlin+Spring으로 다시 짜기

blobnom Kotlin+Spring으로 다시 짜기 (1) - Member 애그리거트 (작성 중)

pggggggggh 2025. 11. 12. 17:22
class Member(TimestampMixin, Base):
    __tablename__ = "members"

    id = Column(Integer, primary_key=True)
    handle = Column(String, index=True)
    role = Column(Enum(Role), nullable=False, default=Role.MEMBER)
    email = Column(String)
    password = Column(String, nullable=False)

    rating = Column(Integer, default=1200)

    users = relationship("User", back_populates="member")
    owned_rooms = relationship("Room", back_populates="owner", foreign_keys="[Room.owner_id]")
    registered_contests = relationship("ContestMember", back_populates="member")
    practice_sets = relationship("PracticeMember", back_populates="member")

 

기존의 FastAPI 코드에서 Member는 위와 같이 구현되어 있다.

Member 애그리거트 안에는 엔티티 Member(루트), User / VO Email, Password를 구현할 것이다. Rating은 VO로 넣을지 말지 조금 더 고민해야 할 것 같다.

@Entity
@Table(name = "members")
class Member(
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    val id: Long,

    @Column
    val email: String,

    @Column
    var handle: String,

    @Column
    @Enumerated(EnumType.STRING)
    var role: Role,

    @Column
    var password: Password,

    @Column
    var rating: Int,
)

 

원칙대로라면 Domain 계층에 JPA 어노테이션을 붙이는 것은 엄연한 DIP 위반이다.

하지만, JPA 의존성이 변경될 가능성이 낮고, 매 엔티티마다 Repository/Entity를 Domain 계층에 하나, Infra 계층에 하나씩 생성하는 것은 1인 개발인 현 상황을 고려했을 때 너무 오버로드가 클 것 같다고 판단했다.

도메인 주도 개발 시작하기(최범균 저)에도 '구현의 편리함은 DIP가 주는 다른 장점(변경의 유연함, 테스트가 쉬움)만큼 중요하기 때문에 DIP의 장점을 해치지 않는 범위에서 응용 영역과 도메인 영역에서 구현 기술에 대한 의존을 가져가는 것이 나쁘지 않다고 생각한다'는 내용이 있다.

 

Password VO는 다음과 같이 구현되었다.

@JvmInline
value class Password(val hashedPassword: String) {
}

 

VO를 일반적인 data class로 정의하고, @AttributeOverride를 활용하여 매핑을 해줄 수도 있지만, 필드 하나를 감쌀 때에는 Kotlin이 제공하는 value class를 활용할 수 있다. 애초에 value class는 컴파일 시에 기반 타입으로 대체되기 때문에, JPA가 이해하는 데 아무런 문제가 생기지 않는다. (https://kotlinlang.org/docs/inline-classes.html)