도메인이 잘 갖춰지지 않는다면 제대로 된 소프트웨어를 만들지 못한다.
응용 영역과 표현 영역이 사용자와 도메인을 연결해주는 매개체 역할을 한다.
표현 영역
은 사용자의 요청을 해석한다.응용 영역
은 실제 사용자가 원하는 기능을 제공한다.
표현 영역은 응용 서비스의 실행 결과를 사용자에게 알맞은 형식으로 응답한다.
사용자와의 상호작용은 표현 영역이 처리하기 때문에 응용 영역은 표현 영역에 의존하지 않는다.
응용 서비스는 단순히 도메인 객체 간의 흐름을 제어한다.
표현 영역의 입장에서는 응용 서비스는 도메인 영역과 표현 영역을 연결해 주는 역할을 한다. 응용 서비스가 복잡하면 도메인 로직의 일부를 구현하고 있을 가능성이 있다. 이는 코드 중복, 로직 분산 등 코드 품질에 안 좋은 영향을 줄 수 있다.
응용 서비스는 도메인의 상태 변경을 트랜잭션을 이용해 처리해야한다. 데이터 일관성을 지키기 위해 트랜잭션 범위에서 응용 서비스를 실행해야 한다.
응용 서비스는 애그리거트와 관련 리포지터리를 이용해 도메인 객체 간의 실행 흐름을 제어한다.
ex) 암호 변경 기능을 응용 서비스에서 구현한다면 Member 애그리거트와 MemberRepository 등 관련 리포지터리를 이용한다.
기존 암호를 올바르게 입력 했는지 확인하는 것은 핵심 도메인 로직이기 때문에 응용 서비스가 아닌 메인 애그리거트를 구현해야한다.
도메인 로직이 도메인 영역과 응용 서비스에 분산되면 다음과 같은 문제가 발생한다.
-
코드 응집성 저하 도메인 데이터와 그 데이터를 조작하는 도메인 로직이 서로 다른 영역에 위치하면 도메인 로직을 파악하기 위해 여러 영역을 분석해야한다.
-
로직 중복 현상
여러 응용 서비스에서 동일한 도메인 로직을 구현할 가능성이 높아진다.
코드 중복을 막기 위해 별도의 보조 클래스를 만들 수 있지만, 도메인 영역에 구현되어 있다면 응용 서비스는 그 기능을 사용하기만 하면 된다.
이와 같은 문제는 결과적으로 코드 변경을 어렵게 만든다. 소프트웨어의 중요한 경쟁 요소인 변경 용이성이 떨어진다는 것은 소프트웨어의 가치를 떨어뜨리는 것이다. 따라서 도메인 로직을 도메인 영역에 모아 코드 중복을 줄이고 응집도를 높여 가치를 높여야한다.
응용 서비스는 표현 영역에 의존하지 않아야 하고 크기가 작아야한다.
응용 서비스는 디자인 패턴 중 퍼사드 패턴과 같은 역할을 한다. 응용 서비스를 구현할 때 몇가지 고려할 사항이 있다.
보통 응용 서비스는 두 가지 방법 중 한 가지 방법으로 구현한다.
- 한 응용 서비스 클래스에 한 도메인의 모든 기능 구현하기
- 구분되는 기능별로 응용 서비스 클래스 따로 구현하기
하나의 클래스에 한 도메인과 관련된 모든 기능을 구현하면 코드 중복을 제거할 수 있다.
하지만 관련된 모든 기능이 하나의 클래스에 위치하면 연관성이 적ㅇ느 코드가 모여 가독성이 떨어지게된다.
하나의 클래스에 모이기 시작하면 분리가 필요한 상황조차 억지로 끼워넣게 된다.
이렇게 되면 코드 품질이 떨어지기 때문에 클래스 개수는 많아지더라도 각 기능별로 클래스를 분리해야한다.
또한 각 기능마다 동일한 로직을 구현할 경우 헬퍼(Helper) 클래스를 만들어 코드 중복을 방지할 수 있다.
응용 서비스를 구현할 때 인터페이스가 필요한 지에 대해 끊임없는 논쟁이 일어난다. 인터페이스가 필요한 상황은 구현 클래스가 여러개인 경우이다. 하지만 응용 서비스의 구현 클래스가 여러 개인 경우는 매우 드물고 런타임에 구현 객체를 교체할 일도 거의 없다.
따라서 꼭 필요한 상황이 아니라면 인터페이스를 만드는 것은 좋지 않은 선택이다.
테스트 주도 개발을 할 때는 주로 표현 영역부터 개발을 시작하게 된다.
이런 경우 미리 응용 서비스를 구현할 수 없으므로 응용 서비스의 인터페이스를 만들어야한다.
반대로 표현 영역이 아닌 도메인 영역이나 응용 영역의 개발을 먼저 시작하려면 응용 서비스 클래스가 먼저 만들어진다.
이런 경우 테스트 주도 개발을 하더라도 Mockito와 같은 테스트 도구를 이용해 인터페이스 없이 표현 영역을 테스트할 수 있다.
응용 서비스가 제공하는 메서드는 요구 사항을 구현하는 데 필요한 값을 파라미터로 전달받아야 한다.
각 값을 개별 파라미터로 전달받을 수도 잇고 별도 데이터 클래스를 만들어 전달받을 수도 있다.
응용 서비스에서 애그리거트 자체를 반환하면 편하게 구현할 수 있지만 응용 서비스와 표현 영역에서도 도메인 로직 실행을 분산시켜 응집도를 낮추게 된다.
표현 영역에서 필요한 데이터만 반환해 응집도를 높이는 것이 좋다.
응용 서비스의 파리미터 타입을 결정할 때 표현 영역과 관련된 타입을 사용하면 안된다.
표현 영역에 대한 의존이 발생하면 응용 서비스와 표현 영역의 결합도가 높아지게 된다.
따라서 서비스 메서드의 파라미터와 리턴 타입으로 표현 영역의 구현 기술을 사용하지 않아야 한다.
스프링 프레임워크 @Transactional을 이용해 쉽게 트랜잭션 처리를 할 수 있다.
표현 영역은 사용자와 응용 서비스를 연결해주는 역할을 한다.
표현 영역의 책임은 크게 3가지로 나뉜다.
- 사용자가 시스템을 사용할 수 있는 흐름(화면)을 제공하고 제어한다.
- 사용자의 요청을 알맞은 응용 서비스에 전달하고 결과를 사용자에게 제공한다.
- 사용자의 세션을 관리한다.
표현 영역은 사용자가 요청한 내용을 응답으로 제공하며, 응용 서비스를 이용해 표현 영역의 요청을 처리하고 그 결과를 전송한다.
응용 서비스의 완성도를 위해 모든 검증을 응용 서비스에서 하면 좋다.
원칙적으로 모든 값에 대한 검증은 응용 서비스에서 처리한다.
표현 영역이 아닌 응용 서비스에서 값의 유효성 검사를 하게 되면 사용자에게 좋지 않은 경험을 제공한다.
물론 응용 서비스에서 예외 목록을 모아 한 번에 표현 영역으로 반환할 수 있다.
표현 영역에서 스프링 프레임워크 Validation 기능을 사용해 간단히 처리할 수도 있다.
표현 영역에서 필수 값과 형식을 검사하면 응용 서비스에서 논리적 오류만 검사하면 된다.
- 표현 영역 -> 필수 값, 값의 형식, 범위 등을 검증
- 응용 서비스 -> 데이터의 존재 유무와 같은 논리적 오류를 검증
필자는 작성해야할 코드가 늘어나는 불편함이 있지만 응용 서비스의 완성도를 위해 응용 서비스에서 값 검증과 논리적인 검증을 모두한다.
표현, 응용, 도메인 영역에서 권함 검사를 할 수 있다.
권한 검사 자체는 복잡한 개념은 아니지만 시스템마다 권한의 복잡도가 다르다.
스프링 시큐리티 프레임워크는 유연하고 확장 가능한 구조를 가지고 있어 다양한 상황을 충족시킬 수 있다.
하지만 유연한 만큼 복잡하기 때문에 충분한 이해가 필요하다.
다음과 같은 영역에서 권한 검사를 수행할 수 있다.
- 표현 영역
- 응용 서비스
- 도메인
사용자의 인증 여부에 대해 검사한다,.
- 이 URL을 처리하는 컨트롤러의 웹 요청을 전달하기 전에 인증 여부를 검사해 인증된 사용자의 웹 요청만 컨트롤러에 전달한다.
- 인증도니 사용자가 아닐 경우 로그인 화면으로 리다이렉트 시킨다.
서블릿 필터는 사용자의 인증 정보를 생성하고 인증 여부를 검사한다.
인증 여부 뿐만 아니라 권한에 대해 URL별 권한 검사를 할 수 있다.
URL 만으로 접근 제어를 할 수 없는 경우 응용 서비스의 메서드 단위로 권한 검사를 수행해야 한다. AOP를 활용해 서비스 메서드에 대한 권함 검사를 할 수 있다.
개별 도메인 객체 단위로 권한 검사를 해야 하는 경우 구현이 복잡하다.
응용 서비스의 메서드 수준에서 권한 검사를 할 수 없어 직접 권한 검사 로직을 구현해야한다.
도메인 객체 수준의 권한 검사 로직은 도메인별로 다르기 때문에 프레임워크에 대한 높은 이해도가 필요하다.
프레임워킁 ㅔ대한 이해가 부족하다면 도메인에 맞는 권한 검사 기능을 직접 구현하는 것이 좋다.
무조건 표현 -> 응용 -> 조회 전용 기능이 아니어도 된다. 응용 서비스에서 조회 전용 기능을 사용하면 서비스 코드가 단순히 조회 전용 기능을 호출하는 형태로 끝날 수 있다.
이 경우 별도 트랜잭션이 필요하지 않고 추가적인 로직이 없기 때문에 표현 영역에서 바로 조회 전용 기능을 사용해도 문제가 없다.