티스토리 뷰

728x90
반응형

표현 영역과 응용 영역


표현 영역은 사용자의 요청을 해석합니다. 사용자에의해 웹 브라우저로부터 받은 요청은 표현 영역에 전달되며 이 표현 영역은 URL, 파라미터, 쿠키, 헤더 등의 정보를 이용해서 사용자가 실행하고자 하는 기능을 판별합니다.

 

응용 영역은 사용자가 원하는 기능을 제공하는 영역입니다. 응용 영역은 표현 영역으로부터 데이터를 받아 사용자가 원하는 기능을 실행합니다.

 

응용 서비스의 역할


응용 서비스는 사용자가 요청한 기능을 실행합니다. 응용 서비스는 사용자의 요청을 처리하기 위해 리포지터리에서 도메인 객체를 가저와 사용합니다.

응용 서비스의 로직이 복잡하다면 도메인 로직의 일부를 구현하고 있을 가능성이 높으며 이로 인해 코드 중복, 로직 분산으로 인한 낮은 응집도를 가질 수 있습니다. 또한 응용 서비스는 트랜잭션 처리도 담당하며 일관성이 깨지지 않도록 주의해야 합니다.

 

💡 응용 서비스에 도메인 로직 넣지 않기

도메인 로직을 도메인 영역과 응용 서비스에 분산해서 구현하면 코드의 응집도가 낮아집니다. 또한 중복 코드가 발생할 우려가 있고 로직 파악시 여러 영역을 분석해야 합니다.

public class UserService {

    public void changePassword(String oldPassword, String newPassword) {
        User user = userRepository.findById(1L).orElseThrow();
        
        // 도메인 로직 노출
        if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
            throw new IllegalArgumentException();
        }
        
        user.changePassword(oldPassword, newPassword);
    }
}

 

💡 개선한 코드

@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Embedded
    private Password password;

    public User(Password password) {
        this.password = password;
    }

    public void changePassword(String oldPwd, String newPwd) {
        password.changePassword(oldPwd, newPwd);
    }
}

@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Password {

    private static final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    private String password;

    public Password(String password) {
        setPassword(password);
    }

    public void changePassword(String oldPwd, String newPwd) {
        if (!matchPassword(oldPwd)) {
            throw new IllegalArgumentException();
        }
        setPassword(newPwd);
    }

    private void setPassword(String password) {
        if (!StringUtils.hasText(password)) {
            throw new IllegalArgumentException();
        }
        this.password = passwordEncoder.encode(password);
    }

    private boolean matchPassword(String inputPassword) {
        return passwordEncoder.matches(inputPassword, this.password);
    }
}

public class UserService {

    public void changePassword(String oldPassword, String newPassword) {
        User user = userRepository.findById(1L).orElseThrow();
        user.changePassword(oldPassword, newPassword);
    }
}

 

응용 서비스의 구현


응용 서비스를 구현하는 방법에는 여러가지가 있으며 하나씩 알아보겠습니다.

 

💡 하나의 응용 서비스 클래스에 회원 도메인의 모든 기능 구현

 

  • 하나의 응용 서비스에 도메인의 모든 기능(CRUD)을 구현하면 중복 되는 코드는 private 메서드를 사용하여 메서드 추출 기법을 통해 중복 코드를 제거할 수 있습니다. 하지만 하나의 클래스는 여러 책임을 가지게 되고, 코드의 크기가 증가하게 됩니다.

💡 구분되는 기능별로 응용 서비스 클래스를 따로 구분하여 구현

 

  • 하나의 클래스는 2~3개의 기능을 구현하며 해당 기능 구현에 필요한 의존 객체만 필요로 함으로 다른 기능을 구현한 코드에 영향을 받지 않습니다. 다만 클래스의 개수가 많아질 수 있으며, 중복 코드 발생 시 유틸 클래스 등을 활용하여 중복되는 것을 방지해야 합니다.

💡 응용 서비스의 인터페이스와 클래스

 

  • UserService 인터페이스와 이를 구현한 UserService가 필요한지에 대한 논쟁입니다. 인터페이스가 필요한 상황은 구현 클래스가 여러개 존재하며 런타임 시에 구현 클래스를 교체해야할 때입니다. 그런데 응용 서비스는 런타임에 교체하는 경우가 거의 없으며, 인터페이스와 구현 클래스를 따로 관리하면 클래스만 많아지고 전체적인 구조가 복잡해집니다.
public interface UserService { }

public class UserServiceImpl implements UserService {

    ... 구현
}

 

값 검증


값 검증은 표현 영역과 응용 서비스 두 곳에서 수행할 수 있습니다. 

표현 영역에서의 값 검증은 사용자가 입력한 값이 올바른지 확인하는 역할을 수행합니다. 반면 응용 서비스에서의 값 검증은 ID 중복 여부와 같은 논리적 오류만 검사합니다.

 

책의 저자는 표현 영역에서는 필수 값 검증하고 응용 서비스에서는 논리적 오류 검증을 해도 괜찮다고 생각했는데 가능하면 응용 서비스에서 필수 값 검증과 논리적인 검증을 모두 하는 편이라고 합니다. 이렇게 했을 때 코드 량은 늘어나지만 응용 서비스의 완성도가 높아진다는 이점이 있다고 합니다.

 

 

 

 

 

 

 

 

728x90
반응형

'스터디 > 도메인 주도 개발 시작하기' 카테고리의 다른 글

바운디드 컨텍스트  (0) 2023.02.18
리포지터리와 모델 구현  (0) 2023.01.06
애그리게이트  (0) 2022.12.30
아키텍처 개요  (0) 2022.12.25
도메인 모델 시작하기  (0) 2022.12.14