우리는 백엔드 개발을 배울 때, 단순히 '3-Layer 아키텍처를 써라.' 라는 이야기를 듣고, 컨트롤러-서비스-리포지토리를 작성하지만,
무작정 XxxService 계층을 도입하면서 정작 서비스 계층에 어떠한 로직을 넣어야 하는지에 대한 고민에는 당황하기 십상이다.
이 서비스 계층의 등장 배경에 대해 완전히 이해할 때, 어떠한 로직을 서비스 계층에 넣고, 어떠한 로직을 도메인 엔티티에 넣을지 이해할 수 있을 것이다.
'도메인' 이란?
domain = 정의역
소프트웨어 공학에서 '도메인'은 해결하고자 하는 문제의 영역을 의미하며, 특정 비즈니스 영역을 구현하게 되었을 때, 그 영역에만 존재하는 지식과 논리를 의미한다.
예시로는 다음과 같은 것들이 있다.
- 결제 도메인
- 계정 도메인
- 물류 도메인
도메인 모델과 트랜잭션 스크립트
크게 도메인 논리를 저장하는 방식은 세가지로 분류할 수 있다.
- 도메인 모델 패턴
- 테이블 모듈 패턴
- 트랜잭션 스크립트 패턴
이중에서 (특히 Spring 개발자에게) 중요한, 도메인 모델 패턴과 트랜잭션 스크립트 패턴에 대해 이야기해보려 한다.
트랜잭션 스크립트
트랜잭션 스크립트는 프레젠테이션 레이어(표현 계층)에서 입력을 받고, 유효성 검사와 계산을 통해 입력을 처리한 다음, 데이터베이스에 데이터를 저장하고, 다른 시스템에서 작업을 호출하는 프로시저다.
그런 다음 필요에 따라 응답을 구성하고 서식을 지정하는 계산을 더 수행하고 추가 데이터로 프레젠테이션에 응답한다.
기본 구성은 사용자가 수행할 각 작업마다 프로시저를 하나씩 만드는 것이다.
즉, 이 작업은 비즈니스 트랜잭션마다 스크립트 하나를 만드는 패턴으로 볼 수 있다.
모든 코드를 인라인 프로시저로 만들라는 것은 아니다. 코드를 서브루틴으로 분리하고, 이러한 서브루틴을 여러 다른 트랜잭션 스크립트에서 공유하는 것이다.
소매 시스템을 예로 들면 체크아웃, 장바구니에 상품 추가, 배송 상태 표시 등에 대한 각각의 트랜잭션 스크립트가 있을 수 있다.
트랜잭션 스크립트에는 여러 장점이 있다.
- 대부분의 개발자가 이해할 수 있는 간단한 절차적 모델이다.
- 데이터 원본 계층(SQL, 레코드)과 함께 사용하기에 적합하다.
- 트랜잭션 경계를 설정하기가 쉽다.
- DB의 트랜잭션 기능은 작업 묶기를 위한 어마어마한 축복임을 잊지 말자.
- 이 트랜잭션 경계가 설정하기 쉽기 때문에, 복잡한 비동기 작업의 롤백 로직을 고민하지 않아도 된다.
하지만 아쉽게도 단점이 매우 치명적인데, 중복을 해결하기 어렵고 중복을 찾아내기는 더더욱 어려워진다.
이는 절차지향 프로그래밍이 가지고 있던 단점을 그대로 상속받는 형태이기도 하다.
도메인 모델
도메인 모델은 비즈니스 로직에 포함된 다양한 용어와 기능을 그대로 코드에 반영하는 패턴이다.
먼저 도메인에 있는 명사를 바탕으로 도메인과 비슷한 모델을 구축한다.
예를 들어, 임대 시스템을 위해 임대, 자산 등에 대한 클래스를 만들 수 있다.
유효성 검사와 계산을 처리하는 논리는 이 도메인 모델에 넣을 수 있으므로 배송 객체는 배송료를 계산하는 논리를 포함할 수 있다.
수수료를 계산하는 루틴이 아직 있을 수 있지만 이러한 프로시저는 도메인 모델 메서드로 금방 위임할 수 있다.
이는 객체지향 패러다임으로의 '완전한 전환'을 의미한다.
객체지향 패러다임의 위대함은 설명하기 입이 아프니, 다음 링크로 설명을 대체하겠다.
[객체지향 패러다임] OOD와 GRASP 패턴, 웹 개발에서의 객체지향 적용
이 게시글을 검색해서 탐색했다는 것은, 객체지향 패러다임 중에서도 OOD와, 그 중간에 사용되는 GRASP 패턴의 목적에 대해 찾아보고자 했다고 가정한다.실제로 코드를 짜면서 쉽게 알게되는 부분
dev.go-gradually.me
PoEAA는 이때 발생하는 도메인 모델 패러다임과 DB의 레코드 기반 관계형 패러다임의 불일치를 효과적으로 해결하는 ORM(ex: JPA)의 역할과 핵심 기능에 대해 다룬다.
서비스 계층이란?
도메인 논리를 처리하는 일반적인 방법은 도메인 계층을 '둘'로 나누는 것이다.
이때 서비스 계층이 등장하게 된다.
서비스 계층은 기반이 되는 도메인 모델이나 테이블 모듈 위에 배치하며, 다음과 같은 이점을 얻기 위해 사용한다.
- 명확한 API를 제공하기
- 트랜잭션 제어 기능 주입
- 보안 기능 주입
즉, 스프링의 어노테이션으로 넣는AOP와 같은 기능들은 서비스 계층에 담기 좋다.
하지만 서비스 계층을 '아무렇게나 사용'하게 되면, 너무 많은 로직을 넣게 되기 십상이다.
따라서 서비스 계층을 사용할 때는 여기에 얼마나 많은 동작을 넣을지 결정하는 것이 아주 중요하다.
가장 소극적 사례는 서비스 계층을 'Facade'로 만들고 모든 실제 동작을 도메인 엔티티에 넣은 다음, 서비스 계층이 파사드에 대한 호출을 하위 객체로 전달하게 하는 것이다.
이러한 서비스 계층은 유스 케이스를 반영해서 구성된, 사용하기 쉬운 API를 제공한다.
또한 트랜잭션 래퍼와 보안 검사를 추가하기도 편리한 지점이다.
즉, 이 방식을 권장한다.
이와 반대되는 극단적 사례는 대부분의 비즈니스 논리를 서비스 계층 안의 트랜잭션 스크립트에 넣는 것이다.
이 경우 기본 도메인 객체는 아주 간단하며, 기본 도메인 객체가 도메인 모델인 경우, 데이터베이스와 일대일이므로 활성 레코드 같은 간단한 데이터 원본 계층(엔티티를 데이터를 저장하기 위한 단순 DTO로 사용)을 사용할 수 있다.
하지만 위에서 말했듯이, 트랜잭션 스크립트 내에 모든 도메인 로직을 삽입하는 것은 코드의 중복을 늘리는 썩 좋지는 않은 방법이다.
여기서 말하고자 하는 바는 서비스 객체에 비즈니스 논리를 절대로 넣지 말라는 것이 아니라, 이를 고정적인 계층으로 만들 이유는 없다는 것이다.
절차형 서비스 객체는 논리를 팩터링하는 아주 유용한 방법이기도 하지만 필자는 이를 아키텍처의 필수 계층으로 활용하기보다는 필요에 따라 이용하는 편이다.
즉, 보통의 경우에는 사용할 필요가 없다고 가정하며, 애플리케이션에 꼭 필요하다고 판단될 때만 최대한 간소화된 서비스 계층을 사용한다.
본 내용은 마틴 파울러의 엔터프라이즈 애플리케이션 아키텍처 패턴 도서를 참고하여 작성되었습니다.
엔터프라이즈 애플리케이션 아키텍처 패턴 - 예스24
이 책은 『엔터프라이즈 애플리케이션 아키텍처 패턴』의 재출간판이다. 『리팩토링』의 저자로도 잘 알려진 마틴 파울러는 이 책에서 기업용 애플리케이션을 개발할 때 직면하는 갖가지 까다
www.yes24.com
'CS Repository > 엔터프라이즈 애플리케이션 아키텍처 패턴' 카테고리의 다른 글
[PoEAA] 성능 관련 용어 (0) | 2025.08.29 |
---|---|
[PoEAA] 백엔드 개발자의 역할 - 소프트웨어 아키텍처와 엔터프라이즈 애플리케이션의 의미 (0) | 2025.08.29 |