6) 응용 서비스와 표현 영역

dbstmd·2024년 1월 1일
0

DDD

목록 보기
6/8

표현 영역과 응용 영역

  • 표현 영역
    표현 영역은 사용자의 요청을 해석한다. 그리고 응용 서비스를 실행한다.

  • 응용 영역
    실제 사용자가 원하는 기능을 제공한다.

표현 영역은 응용 서비스가 요구하는 형식으로 사용자 요청을 변환한다.
응용 영역은 기능 실행에 필요한 입력 값을 받고 실행 결과만 리턴하면 될 뿐이다.

응용 서비스의 역할

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

도메인 객체를 사용해서 사용자의 요청을 처리한다.

또한 트랜잭션 처리도 담당한다. 응용 서비스는 도메인의 상태 변경을 트랜잭션으로 처리해야 하기 때문에, 트랜잭션 범위에서 응용 서비스를 실행해야 한다.

도메인 로직 넣지 않기

만약 응용 서비스가 복잡하다면, 도메인 로직의 일부를 구현하고 있을 가능성이 높다.

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

사용자 암호 변경

@Entity
@Table(name = "member")
public class Member {
    @EmbeddedId
    private MemberId id;
 
    private String name;
    @Embedded
    private Password password;
    
    ...
 
 
    public void changePassword(String oldPw, String newPw) {
        if (!password.match(oldPw)) {
            throw new IdPasswordNotMatchingException();
        }
        this.password = new Password(newPw);
    }
 
}

암호가 일치하는지 여부 조회

기존 암호를 올바르게 입력했는지를 확인하는 것은 도메인의 핵심 로직이기 때문에, 응용 서비스에서 이 로직을 구현하면 안 된다.

  1. 코드의 응집성 저하
  2. 응용 서비스에서 동일한 도메인 로직 구현 가능성

응용 서비스의 구현

한 응용 서비스 클래스에 한 도메인의 모든 기능 구현하기

public class MemberService {
 
    private MemberRepository memberRepository;
 
    public void join(MemberJoinRequest joinRequest) {
        ...
    }
 
    public void changePassword(String memberId, String curPw, String newPw) {
        ...
    }
 
    public void initializePassword(String memberId) {
        ...
    }
 
    public void leave(String memberId, String curPw) {
        ...
    }
}
  • 장점
    도메인과 관련된 기능을 구현한 코드가 한 클래스에 위치하므로 코드 중복 제거
  • 단점
    클래스의 크기가 커진다.

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

public class ChangePasswordService {
 
    public void changePassword(String memberId, String curPw, String newPw) {
        Member member = memberRepository.findById(memberId);
        if (member == null) {
            throw new NoMemberException(memberId);
        }
        member.changePassword(curPw, newPw);
    }
}
  • 장점
    한 클래스에 관련 기능만 존재해 코드 품질을 일정 수준으로 유지한다.
  • 단점
    클래스의 개수가 많아진다.

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

응용 서비스에 인터페이스가 필요한가?

  • 구현 클래스가 여러 개인 경우
    인터페이스와 클래스를 습관적으로 따로 구현하면 소스 파일만 많아지고, 전체 구조가 복잡해진다. 따라서 인터페이스가 명확하게 필요하기 전까지는 응용 서비스에 대한 인터페이스를 작성하는 것이 좋은 선택이라고 볼 수 없다.

  • TDD에서 테스트를 작성하기 위해 인터페이스가 필요한 경우
    만약 표현 영역을 TDD로 먼저 개발한다면, 응용 서비스 구현이 존재하지 않으므로 인터페이스를 이용해서 테스트할 수 있다. 하지만 Mockito와 같은 테스트 도구는 클래스에 대해서도 테스트용 대역 객체를 만들 수 있기 때문에, 응용 서비스에 대한 인터페이스의 필요성을 약화시킨다.

필요할때 쓰면 될듯하다?! 저자는 최대한 지양하라는듯 하다.

메서드 파라미터와 값 리턴

public class ChangePasswordService {
 
    public void changePassword(ChangePasswordRequest req) {
        Member member = findExistingMember(memberRepository, req.getMemberId());
        member.changePassword(reg.getCurrentPassword(), req.getNewPassword());
    }
}

데이터가 여러 개인 경우 클래스 형태로 데이터를 전달해도 좋다.

표현 영역에 의존하지 않기

응용 서비스의 파라미터로 표현 영역과 관련된 타입을 사용하지 말자

public class AuthenticationService {
    public void authenticate(HttpServletRequest request) {
        String id = request.getParameter("id");
        String password = request.getParameter("password");
        if (checkIdPasswordMatching(id, password)) {
            HttpSession session = request.getSession();
            session.setAttribute("auth", new Authentication(id));
        }
    }
}

HttpServletRequest, HttpSession은 표현 영역의 구현이기 때문에, 이를 응용 서비스에 두면 표현 영역의 응집도가 개져서 유지 보수가 어려워진다. 또한 테스트도 힘들어진다.

응용 서비스 메서드의 파라미터와 리턴 타입에 표현 영역의 구현 기술을 사용하지 않아야 한다.

트랜잭션 처리

public class ChangePasswordService {
 
    @Transactional
    public void changePassword(ChangePasswordRequest req) {
        Member member = findExistingMember(memberRepository, req.getMemberId());
        member.changePassword(reg.getCurrentPassword(), req.getNewPassword());
    }
}

트랜잭션을 관리하는 것은 응용 서비스의 중요한 역할이다.
스프링이 제공하는 @Transactional 애노테이션을 사용하면 여러 상황에 대한 커밋과 롤백을 간단하게 보장받을 수 있다.

profile
개인 학습용

0개의 댓글