도메인 주도 개발 시작하기: 6. 응용 서비스와 표현 영역

ParkIsComing·2023년 3월 27일
0

6.1 표현 영역과 응용 영역

  • 사용자와 도메인을 연결해주는 것은 표현영역과 응용 영역이다.
  • 응용 서비스
    • 응용서비스가 도메인 로직을 일부 구현하면 코드 중복, 로직 분산 등 코드 품질에 안 좋은 영향을 준다.
    • 트랜잭션 처리를 담당한다.(도메인의 상태 변경을 트랜잭션으로 처리)

6.2 응용 서비스의 역할

1) 🔥응용서비스에는 도메인 로직을 넣지 않는다.

도메인 로직을 도메인 영역과 응용 서비스에 무분별하게 구현하면 생기는 문제점

  1. 코드의 응집성이 떨어진다.
  2. 여러 응용서비스에서 동일한 도메인 로직을 구현할 가능성이 높아진다.

응용서비스는 다음과 같은 형태를 가진다.

public Result doSomeFunction(SomeReq req){

	// 1. 리포지터리에서 애그리거트를 구한다.
    SomeAgg agg = someAggRepository.findById(req.getId());
    checkNull(agg);
    
    // 2. 애그리거트의 도메인 기능을 실행한다.
    agg.doFunc(req.getValue());
    
    // 3. 결과를 리턴한다.
    return createSuccessResult(agg);
}

새로운 애그리거트를 생성하는 응용서비스는 다음과 같은 형태를 가진다.

public Result doSomeCreation(CreateSomeReq req){
	
    // 1. 데이터 중복 등 데이터가 유효한지 검사한다.
    validate(req);
    
    // 2. 애그리거트를 생성
    SomeAgg agg = createSome(req);
    
    // 3. 리포지터리에 애그리거트를 저장
    someAggRepository.save(
    
    // 4. 결과를 리턴한다.
    return createSuccessResult(newAgg);
}

예시코드로 확인해보자.

🔻응용서비스

public class ChangePasswordService {
    public void changePassword(String memberId, String oldPw, String newPw){ 
        Member member = memberRepository.findById(memberId);
        checkMemberExists(member);
        member.changePassword(oldPw, newPw);
    }
}

🔻도메인
기존 암호를 올바르게 입력했는지 확인하는 로직은 도메인에 있어야 한다.

public class Member {
    public void changePassword(String oldPw, String newPw){
        if(!matchPassword(oldPw)) throw new BadPasswordException();
        setPassword(newPw);
    }
    
    public boolean matchPassword(String pwd){
        return passwordEncoder.matches(pwd);
    }
    
    private void setPassword(String newPw){
        if(isEmpty(newPw)) throw new IllegalArgumentException("lno new password");
        this.password = newPw;
    }
}

6.3 응용서비스의 구현

  • 응용 서비스는 표현 영역과 도메인 영역을 연결하는 facade 역할을 한다.

1) 응용 서비스의 크기

응용 서비스를 구현하는 방식

  • 한 응용 서비스 클래스에 회원 도메인의 모든 기능 구현하기
    • 각 기능에서 동일한 로직을 위한 코드 중복을 제거하기 쉽다.
    • 한 서비스 클래스의 크기가 커진다.
  • 구분되는 기능별로 응용 서비스 클래스를 따로 구현하기
    • 각 클래스별로 필요한 의존 객체만 포함하므로 다른 기능을 구현한 코드에 영향을 받지 않는다.

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

  • 구현 클래스가 여러개 있다면 인터페이스를 구현하는 것이 좋지만, 그렇지 않다면 구현 클래스에 대한 간접 참조가 증가해서 전체 구조가 복잡해진다.
  • 인터페이스가 확실히 필요할 때 응용서비스에 대한 인터페이스를 작성하는 것이 좋다.

3) 메서드 파라미터와 값 리턴

  • Spring MVC 같은 웹 프레임워크는 웹 요청 파라미터를 자바 객체로 변환하는 기능 제공.
  • 따라서 응용 서비스에 여러 요청 파라미터를 전달해야 한다면 별도의 클래스를 사용해 전달하는 것이 좋다.
  • 응용서비스에서 애그리거트 자체를 리턴할 경우, 도메인 로직을 응용 서비스와 표현 영역 모두에서 실행할 수 있게 된다. -> 코드 응집도를 낮추는 원인

4) 🔥 표현 영역에 의존하지 않기

  • 응용 서비스의 파라미터로 표현 영역과 관련된 타입을 사용하면 안된다.
  • 서비스 메서드의 파라미터와 리턴 타입으로 표현 영역의 구현 기술을 사용하지 않는다.

예를 들어, authenticate 메서드가 서비스 단에 있다고 하면 파라미터를 다음처럼 사용하면 안된다.

public void authenticate(HttpServletRequest reqest){
//생략
}

5) 트랜잭션 처리

  • @Transactional을 사용하면 된다.
  • @Transactional이 적용된 메서드가 RuntimeException을 발생시키면 트랜잭션 롤백, 아니면 커밋

6.4 표현 영역

  • 표현 영역의 책임
    • 사용자가 시스템을 사용하르 수 있는 화면을 제공, 제어
    • 사용자의 요청을 적절한 응용 서비스에 전달 + 결과를 사용자에게 리턴
    • 사용자의 세션 관리

6.5 값 검증

  • 표현 영역과 응용 서비스 두 곳에서 모두 수행할 수 있다.
    ex) checkEmpty() 같은 메서드
  • 원칙적으로 모든 값에 대한 검증은 응용 서비스에서 수행
  • 표현 영역은 잘못된 값이 있으면 사용자에게 알리고 값을 다시 입력 받아야 한다.
  • 응용 서비스에서 exception을 이용해 값 검증을 할 경우, 무엇이 잘못된 경험인지 알 수 없어 좋지 않은 사용자 경험을 제공한다. 따라서 응용서비스에서 에러코드를 모아 하나의 익셉션으로 발생시키는 방법도 있다.
  • 표현영역과 응용서비스에서 값 검사 역할을 나누어 수행할 수도 있다. 답은 없다!!
    • 표현 영역 -> 필수 값, 값의 형식, 범위 등을 검증
    • 응용 서비스 -> 데이터의 존재 유무와 같은 논리적 오류를 검증

6.6 권한 검사

권한 검사를 위해 주로 Spring Security를 사용하는데, 상황에 따라 시스템에 맞는 권한 검사 기능을 선택하는 것이 유지보수에 유리하다.

어떤 프레임워크를 사용하는가를 떠나서 보통 다음 세 곳에서 권한 검사를 수행한다.

  1. 표현 영역
  2. 응용 서비스
  3. 도메인

표현영역에서의 권한 검사

  • 기본적으로 인증된 사용자인지 아닌지 검사한다.
  • 접근 제어를 하기 좋은 위치가 Servlet Filter이다.

응용서비스에서의 권한 검사

  • url만으로 접근 제어를 할 수 없는 경우 응용 서비스의 메서드 단위로 권한 검사 수행
  • 스프링 시큐리티의 @PreAuthorize 어노테이션을 사용하면 서비스 메서드에 대한 권한 검사가 가능하다

도메인에서의 권한 검사

  • 직접 도메인에 맞는 권한 검사 로직을 구현해야 한다.

6.7 조회 전용 기능과 응용 서비스

  • DAO를 구현하여 표현 영역에서 응용 서비스 없이 조회 전용 기능에 접근하게 만들 수 있다.
  • 컨트롤러 -> 조회 응용 서비스 -> 조회 전용 기능(DAO/리포지터리)의 순서로 접근하는 것이 아니라
    컨트롤러 -> 조회 전용 기능(DAO/리포지터리)의 순서로 접근하는 것도 가능하다는 뜻이다.

0개의 댓글