본문으로 건너뛰기

안동민 개발노트

본문 시작

BoardConfig 객체 조립

객체 생성과 구현 선택을 구성 루트에 모아 회원가입·로그인 서비스가 공유하는 객체 그래프를 한곳에서 읽습니다.

생성자 주입을 적용하면 서비스는 구현을 선택하지 않습니다.

프로그램 시작 지점에서 객체를 만들고 연결하는 코드를 한곳에 모읍니다.

BoardConfig는 업무를 수행하지 않고 객체 그래프만 만듭니다.

구성 루트가 객체 생성과 연결을 한곳에 모은다

BoardConfig만 보면 애플리케이션에서 실제로 쓰는 구현을 한눈에 확인할 수 있다.

  1. Adapters

    repository·hasher 생성

  2. Service

    역할을 생성자로 연결

  3. Entrypoint

    controller에 service 전달

  4. Lifecycle

    공유 객체와 종료 책임


구성 루트

board-core/src/main/java/board/BoardConfig.java
package board;

import board.member.*;

public final class BoardConfig {
    private final MemberRepository members =
            new MemoryMemberRepository();
    private final PasswordHasher passwordHasher =
            new TestPasswordHasher();

    public MemberRegistrationService memberRegistrationService() {
        return new DefaultMemberRegistrationService(
                members, passwordHasher);
    }

    public LoginService loginService() {
        return new LoginService(members, passwordHasher);
    }
}

회원가입과 로그인은 같은 회원 저장소와 해시 구현을 사용합니다.

두 서비스가 서로를 직접 생성하지 않고 구성 루트가 각각 완성된 객체를 제공합니다.


실행 코드

BoardApplication.java
public final class BoardApplication {
    public static void main(String[] args) {
        var config = new BoardConfig();
        var registration = config.memberRegistrationService();
        var login = config.loginService();

        Member member = registration.register(new SignUpCommand(
                "member@example.com", "안동민", "secret-1234"));
        long loginMemberId = login.authenticate(
                "member@example.com", "secret-1234");

        System.out.println("registered=" + member.id());
        System.out.println("login=" + loginMemberId);
    }
}

main은 Map 저장 방식이나 해시 문자열 형식을 모릅니다.

실행 진입점은 완성된 사용 사례를 호출할 뿐입니다.

객체 생성 순서가 실행 전 의존관계를 완성한다

구성 단계가 끝난 뒤에는 서비스가 null이나 미완성 의존성을 만날 이유가 없어야 한다.

  1. 1
    MemoryMemberRepository 생성

    공유 저장소 인스턴스

  2. 2
    TestPasswordHasher 생성

    첫 운영 정책

  3. 3
    DefaultMemberRegistrationService 생성

    두 역할을 생성자로 연결

  4. 4
    register 호출

    완성된 객체 그래프 사용


객체 생성과 업무 실행 구분

객체책임바뀌는 이유
DefaultMemberRegistrationService중복 확인·해시·저장 순서회원가입 규칙 변경
MemoryMemberRepository메모리에 회원 저장저장 기술 변경
PasswordHasher 구현비밀번호 해시·비교보안 정책 변경
BoardConfig구현 선택과 연결실행 환경 변경

이 표의 변경 이유가 섞이면 다시 경계를 검토합니다.

구성 영역과 사용 영역의 책임을 분리한다

변경 이유가 다른 코드를 한 클래스에 두지 않으면 테스트와 교체 범위가 작아진다.

영역결정변경 이유
구성어떤 구현을 연결할지환경·정책 변경
사용회원가입 순서와 규칙업무 요구 변경
인프라어떻게 저장할지DB·캐시 변경

테스트 구성

테스트는 BoardConfig를 통하지 않고 필요한 가짜를 직접 전달할 수 있습니다.

반대로 실제 실행 그래프가 올바른지는 구성 테스트 한 곳에서 확인합니다.

BoardConfigTest.java
@Test
void 회원가입과_로그인은_같은_회원_저장소를_사용한다() {
    var config = new BoardConfig();
    config.memberRegistrationService().register(new SignUpCommand(
            "member@example.com", "회원", "secret-1234"));

    assertThat(config.loginService().authenticate(
            "member@example.com", "secret-1234")).isPositive();
}
두 진입점이 같은 저장소를 받아야 한 애플리케이션이 된다

등록과 조회가 서로 다른 저장소 인스턴스를 받으면 타입은 같아도 객체 그래프는 끊어진다.

  1. Registration

    회원 저장

  2. Query

    같은 상태 조회

  3. Identity

    동일 instance 전달

  4. Boundary

    구성 루트가 공유 범위 결정

다음 문서에서는 구성 메서드가 매번 새 저장소를 만들 때 발생하는 공유 실패와 객체 수명을 다룹니다.