본문 바로가기

DDD/도메인 주도 설계 철저 입문

팩토리 패턴 - 「도메인 주도 설계 철저 입문」 9장

본 포스트 시리즈는 「도메인 주도 설계 철저 입문」책을 요약한 내용입니다.

 

 

이전 발행 글 보기

 

더보기

2024.09.02 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 1장

2024.09.04 - [DDD/도메인 주도 설계 철저 입문] - 값 객체란? - 「도메인 주도 설계 철저 입문」 2장 (1)

2024.09.05 - [DDD/도메인 주도 설계 철저 입문] - 값 객체의 장점 - 「도메인 주도 설계 철저 입문」 2장 (2)

2024.09.09 - [DDD/도메인 주도 설계 철저 입문] - 도메인 엔티티란? - 「도메인 주도 설계 철저 입문」 3장 (1)

2024.09.30 - [DDD/도메인 주도 설계 철저 입문] - 도메인 서비스란? - 「도메인 주도 설계 철저 입문」 4장

2024.09.30 - [DDD/도메인 주도 설계 철저 입문] - 레포지토리란? - 「도메인 주도 설계 철저 입문」 5장 (1)

2024.09.30 - [DDD/도메인 주도 설계 철저 입문] - 레포지토리 구현 방법과 테스트 방법 - 「도메인 주도 설계 철저 입문」 5장 (2)

2024.09.30 - [DDD/도메인 주도 설계 철저 입문] - 레포지토리 구현 시 팁 - 「도메인 주도 설계 철저 입문」 5장 (3)

2024.10.05 - [DDD/도메인 주도 설계 철저 입문] - 유스케이스란? - 「도메인 주도 설계 철저 입문」 6장 (1)

2024.10.05 - [DDD/도메인 주도 설계 철저 입문] - 파사드 패턴으로 유스케이스 구현하기 - 「도메인 주도 설계 철저 입문」 6장 (2)

2024.10.06 - [DDD/도메인 주도 설계 철저 입문] - 유스케이스의 응집도와 구현 - 「도메인 주도 설계 철저 입문」 6장 (3)

2024.10.06 - [DDD/도메인 주도 설계 철저 입문] - 소프트웨어의 유연성을 위한 의존 관계 제어 - 「도메인 주도 설계 철저 입문」 7장

2024.10.13 - [DDD/도메인 주도 설계 철저 입문] - 소프트웨어 구현 - 「도메인 주도 설계 철저 입문」 8장

 

09장 복잡한 객체 생성을 맡길 수 있는 '팩토리 패턴'

 

 

 

팩토리란?

팩토리란 객체 생성 과정이 정리된 객체다. 객체를 생성하는 것은 보통 생성자의 역할이나, 생성자 안에서 다른 객체를 생성해야 하는 다음과 같은 상황 (즉, 객체 생성 과정이 다소 복잡한 상황)에서는 생성을 팩토리가 대신하게 하는 것을 고려해볼 수 있다. 

import java.util.UUID;

public class User {
    private final UserId id;
    private UserName name;

    // 사용자를 최초 생성할 때 실행되는 생성자 메서드
    public User(UserName name) {
        if (name == null) {
            throw new IllegalArgumentException("name cannot be null");
        }

        // 식별자로 UUID를 사용한다
        this.id = new UserId(UUID.randomUUID().toString());
        this.name = name;
    }

    // 사용자 객체를 복원할 때 실행되는 생성자 메서드
    public User(UserId id, UserName name) {
        if (id == null) {
            throw new IllegalArgumentException("id cannot be null");
        }
        if (name == null) {
            throw new IllegalArgumentException("name cannot be null");
        }

        this.id = id;
        this.name = name;
    }
}

 

`User(UserName name)` 생성자 내부에서 UserId를 새로 생성하고 있다. 따라서 User의 생성을 팩토리에게 위임하는 것이 낫다.

 

 

팩토리 구현

팩토리를 구현할 때는 테스트 등을 고려하여 인터페이스로 정의하는 것이 좋다. 

public interface IUserFactory {
    User create(UserName name);
}

 

 

DB를 통해 시퀀스를 가져오는 UserFactory

public class UserFactory implements IUserFactory {

    @Override
    public User create(UserName name) {
        String seqId = null;

        // JDBC를 사용한 DB 연결을 통해 다음 시퀀스를 가져옴
        seqId = (...생략...)

        UserId id = new UserId(seqId);
        return new User(id, name);
    }
}

 

 

테스트에서 쓸 수 있는 인메모리 Factory

public class InMemoryUserFactory implements IUserFactory {
    // 마지막으로 발행된 식별자
    private int currentId = 0;

    @Override
    public User create(UserName name) {
        // 사용자를 생성할 때마다 1씩 증가
        currentId++;

        return new User(
            new UserId(Integer.toString(currentId)),
            name
        );
    }
}

 

 

팩토리를 메소드로 구현하기

클래스 내부 데이터를 이용해 객체를 생성해야할 때는 팩토리를 메소드로 구현하면 된다. 예를 들어 User클래스의 멤버 변수인 UserId를 가지고 Team 객체를 생성해야 한다고 해보자. 그럴 땐 User의 메소드로 Team 팩토리를 구현하면 된다. 굳이 Team 팩토리를 User의 메소드로 구현하는 이유는, Team이 필요로하는 UserId를 Getter로 노출시키고 싶지 않기 때문이다. 

 

public class User {
    // 외부로 공개하지 않아도 된다
    private final UserId id;
    private UserName name;

	(...생략...)

    // 팩토리 역할을 하는 메서드
    public Team createTeam(TeamName teamName) {
        return new Team(id, teamName);
    }
}

 

 

핵심 정보라고 할 수 있는 유저의 아이디를 외부에 노출시킬 필요가 없다면 은폐하는 것이 바람직하다. 이럴 경우 객체의 불변성(immutability)을 보장하여 Thread-safe하고, 여러 곳에서 동일한 객체를 사용할 때 발생할 수 있는 동시성 문제를 예방할 수 있다. 

 

 


 

 

요약

1. 객체를 생성하는 과정이 다소 복잡한 경우, 팩토리로 객체를 생성하는 것이 좋다.

2. 팩토리를 생성할 때는 느슨한 결합을 고려해 인터페이스로 정의하는 것이 좋다. 

3. 객체 내부 정보를 이용해 생성해야 하는 경우, 내부 정보를 노출 시키기 보다 팩토리를 메소드로 구현하는 것이 좋다.