일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- SQL쿡북
- MariaDB
- vue.js
- CleanCode
- 클린코드
- 이팩티브 자바
- AWS
- DDD
- react
- 이펙티브 자바
- 자료구조
- 인덱스
- 스프링부트와AWS로혼자구현하는웹서비스
- 도메인 주도 개발 시작하기
- AWS RDS
- 인프런백기선
- 알고리즘분석
- jpa
- 혼공SQL
- java
- 인프런김영한
- 네트워크
- mysql
- 자바예외
- 자바스터디
- aop
- 자바
- 기술면접
- 알고리즘
- 이펙티브자바
- Today
- Total
기록이 힘이다.
[도메인 주도 개발 시작하기] 6. 응용 서비스와 표현 영역 본문
표현 영역과 응용 영역
표현 영역의 코드는 다음과 같이 폼에 입력한 요청 파라미터 값을 사용해서 응용 서비스가 요구하는 객체를 생성한 뒤, 응용 서비스의 메서드를 호출한다.
@PostMapping("/member/join")
public ModelAndView join(HttpServletRequest request){
String email = request.getParameter("email");
String password = request.getParameter("password");
//사용자 요청을 응용 서비스에 맞게 변환
JoinRequest joinReq = new JoinReqeust(email, password);
//변환한 객체(데이터)를 이용해서 응용 서비스 실행
joinService.join(joinReq);
...
}
사용자와의 상호작용은 표현 영역이 처리하기 때문에 응용 서비스는 표현 영역에 의존하지 않는다. 응용 영역은 사용자가 웹 브라우저를 사용하는지, REST API를 호출하는지, TCP 소켓을 사용하는지 여부를 알 필요가 없다. 단지, 응용 영역은 기능 실행에 필요한 입력 값을 전달받고 실행 결과만 리턴하면 될 뿐이다.
응용 서비스의 역할
응용 서비스는 사용자(클라이언트)가 요청한 기능을 실행한다. 응용 서비스는 사용자의 요청을 처리하기 위해 리포지터리에서 도메인 객체를 가져와 사용한다.
public Result doSomeFunc(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 newAgg = createSome(req);
//3.리포지터리에 애그리거트를 저장한다.
someAggRepository.save(newAgg);
//4.결과를 리턴한다.
return createSuccessResult(newAgg);
}
응용 서비스가 복잡하다면 응용 서비스에서 도메인 로직의 일부를 구현하고 있을 가능성이 높다. 응용 서비스가 도메인 로직을 일부 구현하면 코드 중복, 로직 분산 등 코드 품질에 안 좋은 영향을 줄 수 있다.
도메인 객체 간의 실행 흐름을 제어하는 것과 더불어 응용 서비스의 주된 역할 중 하나는 트랜잭션 처리이다. 응용 서비스는 도메인의 상태 변경을 트랜잭션으로 처리해야 한다.
도메인 로직 넣지 않기
public class ChangePasswordService{
public void chagePassword(string memberId, String, oldPw, String newPw){
Member member = memberRepository.findById(memberId);
checkMemberExists(member);
member.changePassword(oldPw, newPw);
}
}
도메인 로직을 도메인 영역과 응용 서비스에 분산해서 구현하면 코드 품질에 문제가 발생한다.
- 코드의 응집성이 떨어진다는 것이다. -> 여러 영역을 분석해야 한다
- 여러 응용 서비스에서 동일한 도메인 로직을 구현할 가능성이 높아진다.
응용 서비스의 구현
- 한 응용 서비스 클래스에 회원 도메인의 모든 기능 구현하기
-
중복 로직이 있을경우 private method를 사용하여 중복 로직을 제거할 수 있는 장점이 있다.
-
- 구분되는 기능별로 응용 서비스 클래스를 따로 구현하기 (필자는 이 방법을 더 선호함)
- 클래스 개수는 많아지지만 이전과 비교해서 코드 품질을 일정 수준으로 유지하는 데 도움이 된다.
- 클래스의 기능이 분산되어 중복해서 동일한 코드를 구현할 가능성이 있다.
응용 서비스의 인터페이스와 클래스
- 구현 클래스가 다수 존재하거나 런타임에 구현 객체를 교체해야 할 경우이다.
- 표현 영역에서 단위 테스트를 위해 응용 서비스 클래스의 가짜 객체가 필요할 경우 (Mockito를 사용할 경우엔 필요없음)
보통 런타임에 이를 교체하는 경우가 거의 없을 뿐만 아니라 한 응용 서비스의 구현 클래스가 두 개인 경우도 매우 드물다. 이런 이유로 인터페이스와 클래스를 따로 구현하면 소스 파일만 많아지고 전체 구조만 복잡해지는 문제가 발생한다. 따라서, 인터페이스가 명확하게 필요하기 전까지는 응용 서비스에 대한 인터페이스를 작성하는 것이 좋은 설계라고는 볼 수 없다
표현 영역에 의존하지 않기
- 응용 서비스에서 표현 영역에 대한 의존이 발생하면 응용 서비스만 단독으로 테스트하기가 어려워진다.
- 표현 영역의 구현이 변경되면 응용 서비스의 구현도 함께 변경해야 하는 문제도 발생한다.
- 더 나쁜 문제는 응용 서비스가 표현 영역의 역할까지 대신하는 상황이 벌어질 수도 있다는 것이다.
@Controller
@RequestMapping("/member/changePassword")
public class MemberPasswordController{
@PostMapping
public String submit(HttpServletRequest request){
try{
//응용 서비스가 표현 영역을 의존하면 안 됨!
changePasswordService.changePassword(request);
}catch(NoMemberException ex){
//알맞은 익셉션 처리 및 응답
}
}
}
트랜잭션 처리
스프링과 같은 프레임 워크가 제공하는 기능을 적극 사용하는 것이 좋다. (@Transactional)
표현 영역
- 사용자가 시스템을 사용할 수 있는 흐름(화면)을 제공하고 제어한다.
- 사용자의 요청에 알맞은 응용 서비스에 전달하고 결과를 사용자에게 제공한다.
- 사용자의 세션을 관리한다.
값 검증
값 검증은 표현 영역과 응용 서비스 두 곳에서 모두 수행할 수 있다. 원칙적으로 모든 값에 대한 검증은 응용 서비스에서 처리한다.
응용 서비스에서 각 값이 존재하는지 형식이 올바른지 확인할 목적으로 익셉션을 사용할 때의 문제점은 사용자에게 좋지 않은 경험을 제공한다는 것이다. (순차적으로 필드를 검사하기 때문에 사용자 입력 값에 대한 전반적인 형식 오류 결과를 내어줄 수 없다) 이를 해결하기 위해 표현 영역에서 값을 검사하면 된다.
응용 서비스를 사용하는 표현 영역 코드가 한 곳이면 구현의 편리함을 위해 다음과 같은 역할을 나누어 검증을 수행할 수도 있다.
- 표현 영역: 필수 값, 값의 형식, 범위 등을 검증한다.
- 응용 서비스: 데이터의 존재 유무와 같은 논리적 오류를 검증한다.
필자는 응용서비스의 완성도가 높아지는 이점이 있어 응용 서비스에서 값 오류를 검증하는 편이다.
권한 검사
- 표현 영역
- 인증된 사용자 여부 검사
- 접근 제어를 하기에 좋은 위치가 서블릿 필터이다.
- 응용 서비스
- URL 만으로 접근 제어를 할 수 없는 경우 응용 서비스의 메서드 단위로 권한 검사를 수행해야 한다.
- 스프링 시큐리티는 AOP를 활용해서 다음과 같이 권한 검사를 수행할 수 있다.
public class BlockMemberService {
private MemberRepository memberRepository;
@PreAuthorize("hasRole('ADMIN')")
public void block(String memberId) {
Member member = memberRepository.findById(memberId);
if (member == null) throw new NoMemberException();
member.block();
...
}
}
- 도메인
- 도메인 단위로 권한 검사를 해야 하는 경우는 다소 구현이 복잡해진다. 예를 들어, 게시글 삭제는 본인 또는 관리자 역할을 가진 사용자만 할 수 있다고 해보자. 이 경우 게시글 작성자가 본인인지 확인하려면 게시글 애그리거트를 먼저 로딩해야한다. 즉, 응용 서비스의 메서드 수준에서 권한 검사를 할 수 없기 때문에 다음과 같이 직접 권한 검사 로직을 구현해야 한다.
public class DeleteArticleService {
public void delete(String userId, Long articleId) {
Article article = articleRepository.findById(articleId);
checkArticleExistence(article);
permissionService.checkDeletePermission(userId, article);
article.markDeleted();
}
...
스프링 시큐리티와 같은 보안 프레임워크를 확장해서 개별 도메인 객체 수준의 권한 검사 기능을 프레임워크에 통합할 수도 있다.
'IT서적 > 도메인 주도 개발 시작하기' 카테고리의 다른 글
[도메인 주도 개발 시작하기] 8. 애그리거트 트랜잭션 관리 (0) | 2023.07.29 |
---|---|
[도메인 주도 개발 시작하기] 7. 도메인 서비스 (0) | 2023.07.29 |
[도메인 주도 개발 시작하기] 5. 스프링 데이터 JPA를 이용한 조회 기능 (0) | 2023.07.28 |
[도메인 주도 개발 시작하기] 4. 리포지터리와 모델 구현 (0) | 2023.07.27 |
[도메인 주도 개발 시작하기]3. 애그리거트 (0) | 2023.07.26 |