도메인
응용서비스 관련 내용을 진행하기 전에 도메인에 대해 간단하게 정리해보자. 응용서비스에서 가장많이 사용하게 되는 부분은 도메인이다.
도메인에서 담당하는 역할은 도메인 내부에 있어야한다. 특히 도메인의 데이터를 조작하는 경우에는 도메인을 사용하는 응용서비스 영역에 배치되게 되면 응용서비스 영역에서 사용할 때마다 중복코드가 발생할 간으성이 크다. (도메인 관련 validation 체크도 도메인 내부에서 진행하는 것을 추천)
응용서비스
응용서비스는 표현영역과 도메인 영역을 연결하는 매개체 역할을 하는데 이는 디자인 패터의 파사드(Fasade)패턴과 같다.
응용서비스의 크기
응용서비스는 보통 다음 두가지 방법 중 한가지 방식으로 구현한다.
1. 한 응용서비스 클래스에 회원 도메인의 모든 기능을 구현한다.
-> 한 곳에 위치하여 중복코드가 방지된다.
-> 코드의 크기가 커지기 때문에 연관성이 적은 코드가 한 클래스 내부에 모두 위치하게 되는 문제가 발생하 수 있다.
2, 구분되는 기능별로 응용서비스 클래스를 따로 구현한다.
-> 코드 품질이 일정부분 유지가 가능하다.
-> 코드간에 의존성이 줄어든다.
-> 필요한 코드가 발생할 때 마다 비슷한 코드가 계속 생성될 가능성이 있다.
결론적으로 선택에 따라 다르지만, 구현되는 응용서비스 클래스를 별도로 만드는게 나중에 가서는 조금 더 효율적인 코딩방식이 된다. (의존성이 적어 확장이 용이)
응용서비스의 인터페이스와 클래스
인터페이스를 만들고 그를 구현하거나 추상 클래스를 만들어 상속해서 클래스를 구현하는게 올바른 코딩 방식인가 하는 논재가 있다. 일반적으로 내부가 비슷한 속성을 가진 객체를 같은 인터페이스로 구현하여 전달받고자 할 때 많이 사용된다. 하지만 이렇게 동일한 인터페이스가 필요한 경우는 드물고 인터페이스와 클래스를 별도로 구분하여 매번 생성하면 코드의 양이 많아지고 구현하는 클래스에 대한 간접 참조가 증가해서 전체 구조만 복잡해지는 문제가 발생할 수 있기때문에 인터페이스가 명확하기전까지는 응용서비스에 대한 인터페이스를 작성하는 것이 좋은 설계라고만 할 수 없다.
응용서비스 메서드의 파라미터와 리턴 값
응용서비스의 파라미터 타입에 표현 영역과 관련된 타입을 주면 안된다. 예를 들어 HttpServletRequest, HttpSession등은 응용 서비스에 파라미터로 전달되면 안된다. 만약 이렇게 서비스에 Request정보가 전달 될 경우 해당 서비스를 표현영역이 있어야만 사용이 가능하게 되어 단독으로 테스트를 하거나 확장하기가 어려워진다.
애거리거트 트랜잭션 관리
데이터의 일관성을 관리하기 위해서 트랜잭션을 사용한다. 일반적으로 트랜잭션을 생각하면 작업이 도중에 실패할경우 해당 트랜잭션에 엮여있는 모든 작업을 롤백한다고만 알고 있다. 하지만 다른 의미로 어떤 동작을 수행하고 있을 때 다른 스레드에서 동작을 수행하고 있는 도메인의 데이터를 수정하지 못하도록 하는 것도 트랜잭션 관리의 하나이다.
선점(Pessimistic) 잠금
선전잠금은 먼저 애그리거트를 구한 스레드가 애그리거트 사용이 끝날때 까지 다른 스레드가 해당 애그리거트를 수정하는 것을 막는 방식이다. JPA의 EntityManger의 find() 메서드를 사용하여 LockModeType.PESSIMISTIC_WRITE을 전달하면 해당 엔티티와 매팽된 테이블을 이용해서 선전잠금을 적용할 수 있다.
entityManager.find(Member.class, memberId, LockModeType.PESSIMISTIC_WRITE);
하지만 선점잠금의 경우 예상가능하게 교착상태에 빠질 가능성이 크다. 예를 들어 스레드1이 A 도메인으 잠금하고 스레드2가 도메인 B를 잠금하고 있을 때 스레드1이 B 도메인을 잠그려고 하고 스레드2가 도메인 A를 잠그려고 시도하면 이는 서로 무한정 대기하게 되는 데드락 상태가 되어버린다. 이런경우를 대비해서 find메서드에 4번째 인자로 대기 시간을 지정할 수 있다. (단 DBMS마다 지원하는 경우가 있고 지원하지 않는 경우가 있다.)
Map<String, Object> hints = new HashMap();
hints.put(“javax.persistence.lock.timeout”, 1000);
entityManager.find(Member.class, memberId, LockModeType.PESSIMISTIC_WRITE, hits);
비선점 잠금
선점 방식에도 단점이 있다. 바로 같은 애거리거트를 수정하는 것이 아니라 특정 애거리거트를 사용하는 부분과 수정하는 부분이 겹치면 문제가 발생한다. 예를 들어보면 사용자의 집 주소를 수정하고 있을 때 배송서비스가 사용자의 집 주소 정보를 가져가서 배송을 한다고 생각해보자. 이 경우 배송이 시작해버리고 사용자 주소는 변경해봐야 소용이 없어진다.
이를 위해서 사용할 수 있는것이 버전 정보이다. 만약 사용자가 정보를 수정하고 있을 때 버전이 9였다. 그리고 사용자 정보를 배송서비스가 접근하면서 버전정보가 10이 되었다. 그 다음 사용자 정보를 수정을 저장하려고 할 때 현재 버전정보가 10이기 때문에 오류가 발생하게 만드는 방식이다.
이는 JPA에서 entity에 @Version 속성만 하나 지정해주면 된다. 그럼 @Transactional 애노테이션을 지정하고 작업을 진행하면 되고 만약 애그리거트의 데이터를 조작하려고 할 때 OptimisticLockingFailureException이 발생하면 이는 버전이 바뀐것이라고 판단하면 된다.
출처 : DDD Start 도메인 주도 설계 구현과 핵심 개념 익히기 (출판 지앤선, 저자 최범균)
'DDD' 카테고리의 다른 글
우아한 객체지향 후기 및 정리 (0) | 2019.10.12 |
---|---|
DDD. 애거리루트 정리 (0) | 2019.05.16 |
DDD. DIP 의존 역전 원칙 (0) | 2019.05.06 |
DDD. 도메인 주도 개발 시작 (0) | 2019.05.03 |