서론
스프링에서는 @Controller, @Service, @Repository 등의 애노테이션을 사용하여 웹 계층을 분리하고 있다. 이 중 @Service는 사실상 @Component와 기능 차이가 없지만, 스프링에게 해당 클래스가 서비스 레이어 클래스라는 것을 알리는 역할을 한다고 한다. 그렇다면 Service Layer는 무슨 역할을 하는 걸까.
3 tier 아키텍처
보통 스프링 프로젝트는 3 tier 방식으로 구성한다.
프레젠테이션 계층
- 화면에 보여주는 기술을 사용하는 영역.
- 컨트롤러에서 사용자의 요청에 맞는 응답처리를 진행하며, HTML엔진(thymeleaf), HTML등이 담당하는 영역.
- Spring MVC 객체로, 컨트롤러, DispatcherServlet, 컨트롤러, 뷰, 모델이 포함된다.
서비스 계층
- 순수한 비지니스 로직을 담고 있는 영역.
- 이 영역의 설계는 고객의 요구사항과 정확히 일치해야 한다.
- 프레젠테이션 계층과 데이터 엑세스 계층의 중간다리 역할을 한다.
데이터 엑세스 계층
- 데이터를 어떤 방식으로 보관하고, 사용하는 가에 대한 설계가 들어가는 계층.
- 일반적으로 DBMS를 많이 사용하지만, 상황에 따라서 API 호출, NoSQL, Kafka 등의 기술이 포함될 수 있다.
- 데이터의 CRUD 연산을 수행하는 Mapper와 결과를 객체로 리턴하는 DAO을 포함한다.
오브젝트 중심 아키텍처
3 tier 아키텍처에 확장하여, Layer 간 데이터 전송 객체인 DTO와 특정 특정 도메인을 개념적으로 표현한 것인 Domain Model이 포함된 오브젝트 중심 아키텍처가 존재한다.
Web Layer
- 흔히 사용하는 Controller와 JSP 등의 뷰 템플릿 영역이다.
- 이외에도 filter, interceptor, ControllerAdvice 등 외부 요청과 응답에 대한 전반적인 영역이다.
- 3 tier 아키텍처의 프레젠테이션 계층과 유사하다.
Service Layer
- @Service에 사용되는 서비스 영역이다.
- 일반적으로 Controller와 DAO의 중간 영역으로 사용된다.
- @Transactional이 사용되어야 하는 영역이다.
- 3 tier 아키텍처의 서비스 계층과 유사하다.
Repository Layer
- DB와 같이 데이터 저장소에 접근하는 영역이다.
- DAO(Data Access Object)영역이다.
- 3 tier 아키텍처의 데이터 엑세스 계층과 유사하다.
DTOs
- DTO(Data Transfer Object)는 계층 간에 데이터 교환을 위한 객체다.
- Web Layer에서 사용될 객체나 Repositroy Layer에서 결과로 넘겨주는 객체 등이다.
Domain Model
- 도메인이라 불리는 개발 대상을 모든 사람이 동일한 관점에서 이해할 수 있고 공유할 수 있도록 단순화시킨 것을 도메인 모델이라고 한다. (ex. 택시앱에서 배차, 탑승, 요금 등은 모두 도메인)
- JPA의 경우 @Entity를 사용된 영역이 도메인 모델이다.
- 다만, 무조건 DB의 테이블과 관계가 있어야만 도메인은 아니다.
- VO(Value Object)처럼 값 객체들도 이영역 해당된다.
서비스 차원에서 - 3 tier 아키텍처 vs 오브젝트 중심 아키텍처
주문 취소에 대한 메서드가 있을 때, 각 아키텍처 방식으로 서비스 단에서 cancelOrder를 구현해보았다.
예시 - 3 tier 아키텍처
@Transactional
public Order cancelOrder(int orderId) {
// 1) 주문정보, 결제정보, 배송정보 조회
OrdersDto order = ordersDao.selectOrders(orderId);
BillingDto billing = billingDao.selectBilling(orderId);
DeliveryDto delivery = deliveryDao.selectDelivery(orderId);
// 2) 배송 취소 여부 확인
String deliveryStatus = delivery.getStatus();
// 3) 배송중이라면 배송 취소
if ("IN_PROGRESS".equals(deliveryStatus)) {
delivery.setStatus("CANCEL");
deliveryDao.update(delivery);
}
// 4) 각 테이블에 취소상태 갱신
order.setStatus("CANCEL");
ordersDao.update(order);
billing.setStatus("CANCEL");
billingDao.update(billing);
return order;
}
- Service에서 모든 비지니스 로직을 처리하기 때문에 구현이 매우 쉽고 단순하지만 구조가 복잡해질 수록 모듈화의 복잡도도 높아지게 된다.
- order, billing, delivery는 단순히 정보만 담겨있고, 모든 로직은 Service 단에 존재한다.
빈약한 도메인 오브젝트
- 단순히 정보만 담겨있는 오브젝트를 빈약한 (Anemic) 오브젝트라 함
- 도메인 오브젝트 기능이라 하면 도메인 오브젝트의 비즈니스 로직이라 볼 수 있음
- 빈약한 도메인 오브젝트의 경우 비즈니스 로직이 전부 서비스 계층에 있기 때문에 거대 서비스 계층구조와 비슷함
- 도메인 오브젝트는 독립적으로 존재하면서 일관된 구조의 정보를 담아서 전달하는데만 사용됨
- SQL에 의존적인 데이터 방식보다 유연하고 간결하지만, 서비스 계층의 메소드에 대부분의 비즈니스 로직이 있기 때문에 재사용성이 떨어지고 중복의 문제가 발생하기 쉬움
예시 - 오브젝트 중심 아키텍처
@Transactional
public Order cancelOrder(int orderId) {
// 1) 주문정보, 결제정보, 배송정보 조회
OrdersDto order = ordersDao.selectOrders(orderId);
BillingDto billing = billingDao.selectBilling(orderId);
DeliveryDto delivery = deliveryDao.selectDelivery(orderId);
// 2)배송 취소 여부 확인 - 3)배송중이라면 배송취소
delivery.cancel();
// 4)각 테이블에 취소상태 갱신
order.cancel();
biling.cancel();
return order;
}
- 객체에 객체가 수행해야 하는 업무를 분담시키는 것으로써 Service에서는 트랜잭션, 도메인 간의 순서보장의 역할만을 제공하고 비지니스 로직을 Domain에서 처리하는 방식이다.
- order, billing, delivery 각각에 cancel 기능이 담겨있어, 다른 Service에서도 재사용하기 쉬워지고, 코드 가독성이 높아졌다.
풍성한 도메인 오브젝트
- 정보 저장 뿐만이 아니라, 내부의 정보를 이용하는 기능도 가지고 있는 오브젝트를 풍성한 오브젝트라 한다.
- 데이터와 데이터를 사용하는 로직을 모아두기 때문에 응집도가 높음
- 어떤 비즈니스 로직은 특정 도메인 오브젝트와 깊은 관계가 있을 수 있음
- 해당 로직을 도메인 오브젝트에 넣고, 서비스 계층의 비스니스 로직에서 재사용하도록 함
결론
- Service는 비즈니스 로직을 담는 것이 아닌, 트랜잭션 처리와 도메인 모델간의 순서 보장의 역할을 부여하는 것을 지향해야 한다. 비즈니스 로직은 도메인 모델에 넣자.
- 이로써 얻는 이점은, 재사용성을 높이고 코드 가독성이 좋아진다.
출처
https://ksb-dev.tistory.com/m/210
https://incheol-jung.gitbook.io/docs/study/ddd-start/1
https://hongs-coding.tistory.com/119
토비의 스프링 3.1 Vol. 1 스프링의 이해와 원리 : http://www.yes24.com/Product/Goods/7516721
스프링 부트와 AWS로 혼자 구현하는 웹 서비스 : http://www.yes24.com/Product/Goods/83849117
'Spring' 카테고리의 다른 글
[Spring] Async (0) | 2022.10.28 |
---|---|
[Spring] 트랜잭션 (0) | 2022.10.19 |
[Spring] 왜 Spring 인가 (0) | 2022.08.14 |
[Spring Batch] Tasklet & Chunk 예시 (0) | 2022.07.13 |