CS 상식/디자인패턴

[DDD] 엔티티에 비즈니스 로직 작성하는 것에 대한 나의 생각

SparkIT 2025. 3. 21. 12:18

최근 OOP와 DDD 관련된 서적을 읽다 궁금한 점이 생겼습니다. 바로 엔티티 클래스와 서비스 클래스에 관한 내용인데요. 저는 기존에 개발 시 엔티티 클래스는 단순 데이터베이스와 매핑되는 클래스로만 이용했습니다. 그리고 모든 비즈니스 로직은 서비스 클래스에 구현했죠. 하지만 이럴 경우 엔티티가 단순한 데이터 구조체로만 사용된다는 아쉬움이 발생합니다. 그래서 도메인 주도 개발이라는 개념이 부상했습니다. 도메인 주도 개발 관점에서는 복잡한 비즈니스 로직만 서비스 클래스에 넣고 도메인 모델(엔티티)과 관련된 비즈니스 로직은 도메인 모델 클래스에 구현해 이런 아쉬움을 해결합니다.

저도 이런 내용을 토대로 저의 코드 일부를 수정해보려 했는데요. 이때 의문점이 생겼습니다. 내용은 아래와 같습니다.

 

엔티티에 비즈니스 로직 넣기. 과연 효율적인가?

유저 닉네임을 수정하는 예시를 통해 간단하게 설명하겠습니다.

 

적용 전

// 서비스 클래스

export class UserService {

	...
    
    // 닉네임 수정
    async updateNickname(userId: number, newNickname: string) {
        try {
            await this.userRepository.update({ id: userId }, { nickname: newNickname });
            return;
        } catch (error) {
            throw error;
        }
    }
}

저는 기존에 위와 같은 형식으로 서비스 코드를 작성했습니다. 위의 경우 한 번의 UPDATE 쿼리문을 실행하여 데이터베이스에서 id값이 일치하는 데이터의 nickname을 수정합니다.

 

적용 후

// 엔티티 코드

@Entity()
export class User {
	@PrimaryGeneratedColumn({ type: 'int' })
	id: number;

	@Column({ type: 'varchar', length: 50, unique: true })
	nickname: string;

	// 닉네임 수정
	public updateNickname(newNickname: string) {
		return (this.nickname = newNickname);
	}
}
// 서비스 클래스

export class UserService {

	...
    
    // 닉네임 수정
    async updateNickname(userId: number, newNickname: string) {
        try {
            const user = await this.userRepository.findOneBy({ id: userId });
            user.updateNickname(newNickname);
            await this.userRepository.save(user);
            return;
        } catch (error) {
            throw error;
        }
    }
}

위와 같이 유저의 닉네임을 수정하는 비즈니스 로직을 유저 엔티티에 옮겼습니다. 이럴 경우 역할이 더 분명히 구분되는 느낌이 있어 객체 지향 프로그래밍에 적절한 방식이라고 생각됩니다. 하지만 여기서 문제가 생겼습니다. 위아 같이 쿼리가 더 많이 생긴다는 것인데요. 기존에는 쿼리문 1개만(UPDATE 한 번) 사용되었지만, 현재는 2번의 쿼리가 사용됩니다. SELECT 쿼리문(this.userRepository.findOneBy)이 먼저 사용되어 User 객체를 불러와야하고, 이를 닉네임 수정 로직을 거친 후 다시 UPDATE(this.userRepository.save) 쿼리문으로 데이터베이스에 적용해야합니다. 그럼 성능면에서는 더 느려진 것이 아닐까요?

 

엔티티에 비즈니스 로직을 넣는 것이 더 효율적인 경우는 없나?

위에서 설명드린 예시처럼 DDD 방식을 통해 엔티티에 비즈니스 로직을 넣는 것이 코드 관점에서는 클래스별 역할과 책임이 더 명확하고 세세하게 분리되어 좋다고 생각하지만 성능면으로 보았을 때는 오히려 문제가 되는 부분도 발생한다고 생각합니다. 불필요한 쿼리가 추가될 수 있기 때문입니다. 하지만 개발 생산성 부분으로 생각했을 때 단순한 CRUD 작업 이외의 복잡한 비즈니스 로직이 존재할 경우 엔티티에 비즈니스 로직을 넣는 것이 더 좋은 경우도 발생한다고 생각합니다. 아래는 제가 가정해본 상황입니다.

가정)
유저별로 '포인트(rankPoint)'와 '등급(rank)'이라는 데이터가 존재한다. 이때 해당 포인트는 다양한 상황에 관여한다. 예를 들어, 유저가 새로운 게시물(post)을 작성하면 +10 포인트를 부여받고, 다른 댓글(comment)을 작성하면 +20 포인트를 받는다. 그리고 포인트가 일정 수준에 도달할때마다 유저의 등급이 바뀐다. 예를 들어 50포인트를 모으면 브론즈, 80포인트를 모으면 실버 ...

이런 경우라면 결국 유저의 포인트를 올리는 과정에 유저의 등급도 증가 여부도 체크해야합니다. 그럼 애초에 유저의 포인트 증가 요청이 들어왔을 때 바로 유저 포인트 증가에 관한 UPDATE 쿼리 하나만 수행할 수는 없습니다. 먼저 SELECT로 유저를 조회하고, 유저의 포인트를 증가한 후 해당 범위를 통해 등급 증가 여부를 확인합니다. 만약 등급이 증가했다면 등급에 대한 증가도 적용합니다. 여기까지 마무리되면 그제서야 UPDATE 쿼리로 유저 정보를 반영하겠죠. 이런 경우라면 유저의 포인트를 증가시키는 로직을 엔티티 클래스에 작성하는 것이 코드를 더 쉽게 유지보수할 수 있게 만들 것입니다.

 

마무리

결국 모든 디자인 패턴 혹은 개발 방법론이 그렇듯 정답은 없는 것 같습니다. 개발 초반에는 우선 자신이 생각하기에 좋은 방식으로 개발하다가, 중간중간 리팩토링 시간을 가지며 상황에 맞게 더 좋은 방식과 구조로 수정하는 것이 가장 좋을 듯합니다.