728x90
반응형
목차
public class Member {
public void changePassword(String oldPw, String newPw) {
if(!matchPassword(oldPw)) throw new BaddPasswordException();
setPassword(newPw);
}
// 현재 암호와 일치하는지 검사하는 도메인 로직
public boolean matchPassword(String pwd) {
return passwordEncoder.matches(pwd);
}
private void setPassword(String newPw) {
if(isEmpty(newPw)) throw new IllegalArgumentException("no new password");
}
}
표현영역과 응용영역
- 도메인 영역이 제 기능을 하려면 사용자와 도메인을 연결해주는 매개체가 필요함
- 표현영역은 사용자 요청을 해석함
- 사용자가 원하는 기능 판별 후 응용서비스 실행
- 응용서비스 : 실제 사용자가 원하는 기능을 제공
@PostMapping("/member/join")
public ModelAndView join(HttpRequestServlet request) {
String email =request.getParameter("email");
String password = request.getParameter("password");
// 사용자 요청을 응용 서비스에 맞게 변환
JoinRequest joinReq = new JoinRequest(email, password);
//변환한 객체(데이터)를 이용해서 응용 서비스 실행
joinService.join(joinReq);
}
- 사용자가 입력한 파라미터를 응용서비스가 요구하는 객체를 생성해서 응용서비스 호출
- 응용서비스 실행 후 사용자에게 알맞은 형식으로 응답(HTML or JSON)
응용 서비스의 역할
- 사용자가 요청한 기능을 실행
- 리포지터리에서 도메인 객체를 가져와서 사용함
- 응용서비스는 도메인 영역과 표현영역을 연결해주는 창구 역할
public Result doSomeFunc(SomeReq req) {
// 1. repository에서 애그리거트 구함
SomeAgg agg = someAggRepository.findById(req.getId());
checkNull(agg);
// 2. 애그리거트의 도메인 기능 실행
agg.doFunc(req.getValue());
// 3. 결과를 리턴한다.
return createSuccessResult(agg);
}
- 주로 도메인 객체 간의 흐름을 제어함
- 응용서비스가 복잡할 경우 도메인 로직의 일부를 구현하고 있을 가능성이 높음
- 코드 중복, 로직 분산 등 코드 품질에 안 좋은 영향을 줄 수 있음
- 트랜잭션 처리담당
public void blockMembers(String[] blockingIds) {
if(blockingIds == null || blockingIds.length == 0) return;
List<Member> members = memberRepository.findByIdIn(blockingIds);
for(Member mem : members) {
mem.block();
}
}
- 한 번에 닫수 회원을 차단 상태로 변경하는 응용서비스
- blockMembers 메서드가 트랜잭션 없이 실행 될 경우 일부 Member만 차단되 일관성이 깨질 수 있음
public class ChangePasswordService {
public void changePassword(String memberId, String oldPw, String newPw) {
Member member = memberRepository.findById(memberId);
checkMemberExists(member);
member.changePassword(oldPw, newPw);
}
}
- 암호 변경 기능을 위한 응용서비스는 Member애그리거트와 관련 리포지터리를 이용해 도메인 객체간 실행 흐름을 제어함
public class Member {
public void changePassword(String oldPw, String newPw) {
if(!matchPassword(oldPw)) throw new BaddPasswordException();
setPassword(newPw);
}
// 현재 암호와 일치하는지 검사하는 도메인 로직
public boolean matchPassword(String pwd) {
return passwordEncoder.matches(pwd);
}
private void setPassword(String newPw) {
if(isEmpty(newPw)) throw new IllegalArgumentException("no new password");
this.password = newPw;
}
}
- 기존 암호를 올바로 입력했는지 확인하는 것은 도메인 핵심로직이므로 도메인영역에 구현해야함.
- 응용서비스에 구현할 경우
- 코드 응집성이 떨어짐 (데이터 조작이 도메인과 응용서비스 두 군데 위치)
- 여러 응용서비스에서 동일한 도메인 로직 구현 가능성이 높음
응용서비스의 구현
- 응용서비스의 크기
- 한 응용 서비스 클래스에 회원 도메인의 모든 기능 구현하기
- 구분되는 기능 별로(가입, 탈퇴, 암호변경, 초기화 등) 클래스 따로 구현하기
- 한 클래스에 구현시 중복제거가 가능하지만 클래스 크기가 커짐
- 연관성이 없는 코드가 함께 위치할 가능성이 높아서 코드 이해가 어려움
- 기능별로 서비스 클래스 구현 시 클래스 내 2~3개 기능 구현
- 클래스 개수는 많아지지만 코드 품질을 일정 수준으로 유지하는데 도움이 됨
- Helper클래스를 이용해서 동일한 로직을 모아놓고 코드 중복을 방지함
응용서비스의 인터페이스와 클래스
- 인터페이스가 필요한 상황
- 구현 클래스가 여러개인 경우(런타임에 구현 객체 교체 )
- 대부분 응용서비스는 런타임 교체 경우가 거의없고 대부분 구현 클래스가 하나임
- TDD 개발을 하고 표현 영역부터 개발할 경우 응용클래스가 존재하지 않으므로 필요할 수 있음
- 도메인 영역이나 응용영역 개발을 먼저할 경우 클래스가 먼저 만들어짐
- 표현영역 단위 테스트시 Mockito같은 테스트 도구 클래스에 대해서도 대역 객체 만들 수 있으므로 I/F가 없어도 되긴함
메서드 파라미터와 값 리턴
- 파라미터로 전달받을때 2개 이상이면 개별값보다는 클래스를 만들어서 전달
- 응용서비스에서 애그리거트 자체를 리턴하면 코딩은 편하지만 도메인의 로직실행을 응용서비스와 표현영역 두 곳에서 할 수 있게 됨
- 응용서비스, 표현영역에 기능 실행 로직을 분산시켜 코드 응집도를 낮추는 원인이됨
- 응용서비스는 표현 영역에서 필요한 데이터만 리턴 하는 것이 응집도를 높이는 확실한 방법
표현영역에 의존하지 않기
- 응용서비스 파라미터 타입을 결정할 때 표현 영역과 관련된 타입 사용X
- HttpServletRequest 나 HttpSession을 응용서비스 파라미터로 전달하면 안됨
- 응용서비스에서 표현영역에 대한 의존이 발생하면 응용서비스만 단독 테스트가 어려워짐
- 응용서비스가 표현영역 역할까지 대신하는 상황이 생길 수 있음
- HttpSession, 쿠키는 표현영역인데 응용서비스에서 변경할 경우 표현영역의 코드만으로 상태추적이 어려워짐
트랜잭션 처리
- 스프링 프레임워크가 제공하는 트랜잭션 관리 기능 이용시 트랜잭션 처리를 쉽게 할 수 있음
- @Transactional 적용된 메서드가 RuntimeException 발생시 롤백, 아니면 커밋
표현영역
- 표현영역의 책임
- 사용자가 시스템을 사용할 수 있는 흐름(화면)을 제공하고 제어
- 사용자의 요청을 알맞은 응용서비스에 전달하고 결과를 사용자에게 제공
- 사용자의 세션을 관리
값 검증
- 값 검증은 표현영역과 응용서비스 두 곳에서 모두 수행할 수 있음
- 표현영역 : 필수 값, 값의 형식, 범위 등을 검증한다.
- 응용서비스 : 데이터의 존재 유무와 같은 논리적 오류 검증
- 표현 영역에서 필수값 검증
- 스프링에서는 Validator 인터페이스를 제공하기 때문에 간결하게 줄일 수 있음
권한 검사
- 표현영역, 응용 서비스, 도메인 세 곳에서 권한 검사 수행 가능
- 표현 영역 : 인증된 사용자인지 아닌지 검사 (ex: 회원 정보 변경)
- URL을 처리하는 컨트롤러에 요청 전에 인증된 사용자의 웹요청만 컨트롤러에 전달
- 인증된 사용자가 아니면 로그인 화면으로 리다이렉트
- 서블릿 필터에서 사용자 인증 정보생성, 인증여부 검사
- URL 만으로 접근 제어할 수 없을 경우 응용서비스 메서드 단위로 권한 검사 수행
- 스프링 시큐리티는 AOP를 활용해 애너테이션으로 서비스 메서드 권한 검사 수행 기능 제공
- 도메인 객체 단위로 검사할 경우 (게시글작성자가 본인인지 확인하려면)
- 애그리거트 먼저 로딩 > 직접 권한 검사 로직을 구현해야함.
조회 전용 기능과 응용 서비스
- 응용서비스 코드가 단순히 조회기능만 호출할 경우
- 표현 영역에서 바로 조회기능을 사용해도됨
728x90
반응형
'책리뷰 > 도메인 주도 개발 시작하기(DDD핵심 개념 정리부터 구현까지)' 카테고리의 다른 글
도메인 주도 개발(DDD) 애그리거트 트랜잭션 관리 (0) | 2022.05.24 |
---|---|
도메인 주도 개발(DDD) 도메인 서비스 (0) | 2022.05.24 |
도메인 주도 설계(DDD) 스프링데이터 JPA를 이용한 조회 (0) | 2022.05.17 |
도메인 주도 개발(DDD) 리포지터리와 모델 구현 (0) | 2022.05.13 |
도메인 주도 설계(DDD) 애그리거트 (0) | 2022.05.11 |