Files
project-auth-server/docs/examples/design/mapper-separation.md
T

3.4 KiB

mapper separation 예시

좋은 예시 1: web request -> command 매핑

public final class UserWebMapper {

    public CreateUserCommand toCommand(CreateUserRequest request) {
        return new CreateUserCommand(
                request.email(),
                request.password(),
                request.name()
        );
    }
}

왜 좋은가:

  • HTTP request 구조를 application command 구조로만 번역한다
  • 비즈니스 정책을 결정하지 않는다

좋은 예시 2: domain -> response DTO 매핑

public final class UserResponseMapper {

    public UserResponse toResponse(User user) {
        return new UserResponse(
                user.id().value(),
                user.email().value(),
                user.name().value()
        );
    }
}

왜 좋은가:

  • 응답 계약만 만든다
  • repository/service 호출이 없다

좋은 예시 3: persistence entity -> domain 매핑 분리

public final class UserPersistenceMapper {

    public User toDomain(UserJpaEntity entity) {
        return User.restore(
                entity.getId(),
                entity.getEmail(),
                entity.getName(),
                entity.getEncodedPassword()
        );
    }

    public UserJpaEntity toEntity(User user) {
        return new UserJpaEntity(
                user.id().value(),
                user.email().value(),
                user.name().value(),
                user.password().encodedValue()
        );
    }
}

왜 좋은가:

  • persistence 구조와 domain 구조를 별도 경계에서 번역한다
  • JPA 세부가 domain으로 직접 새지 않는다

좋은 예시 4: update mapping을 명시적으로 분리

public interface UserPersistenceMapper {

    UserJpaEntity toNewEntity(User user);

    void updateEntity(User user, UserJpaEntity target);
}

왜 좋은가:

  • 생성과 수정의 계약이 다름을 드러낸다
  • side effect가 있는 매핑을 명시한다

나쁜 예시 1: 매퍼에서 repository 호출

public final class UserMapper {

    private final RoleRepository roleRepository;

    public User toDomain(UserRequest request) {
        Role role = roleRepository.findByName(request.roleName()).orElseThrow();
        return User.create(request.email(), role);
    }
}

문제:

  • 매퍼가 번역을 넘어 DB 조회까지 한다
  • 테스트와 책임 분리가 어려워진다

개선:

  • 호출자가 Role을 먼저 준비해서 전달한다

나쁜 예시 2: 매퍼에서 비즈니스 규칙 결정

public UserStatus toStatus(UserRequest request) {
    if (request.provider().equals("google")) {
        return UserStatus.ACTIVE;
    }
    return UserStatus.PENDING;
}

문제:

  • 상태 결정 정책이 매퍼에 숨어 있다
  • 단순 구조 변환이 아니라 비즈니스 의미를 만든다

개선:

  • status 결정은 application/domain 정책으로 이동

나쁜 예시 3: web + persistence + integration를 한 매퍼에 몰아넣기

public final class UserMapper {
    CreateUserCommand toCommand(CreateUserRequest request) { ... }
    UserJpaEntity toEntity(User user) { ... }
    ExternalUserPayload toPayload(User user) { ... }
}

문제:

  • 경계가 섞인다
  • 변경 이유가 달라 함께 진화하기 어렵다

개선:

  • UserWebMapper
  • UserPersistenceMapper
  • UserExternalMapper
  • 로 분리