7장 - 서비스
- 도메인 서비스가 무엇인지?
- 스프링에서 서비스는 왜 서비스인지?
- 서비스 컴포넌트에 추가적으로 기대하는 역할이나 책임은 무엇인지?
서비스의 컴포넌트의 역할 크게 3가지
- 1 도메인 객체를 불러오기
- 2 도메인 객체나 도메인 서비스에 일을 위임하기
- 3 도메인 객체의 변경 사항을 저장하기
스프링에서의 Service
@Service의 자바독 요약
- @Service는 에릭 에반스의 DDD에서 영감을 받아 만들어진 애너테이션
- 서비스는 J2EE 패턴 중 하나인 비즈니스 서비스 파사드처럼 사용될 수 있음
DDD (도메인 주도 설계)
DDD에서 정의하는 '서비스'란? -> 연산을 위한 컴포넌트
- DDD에서의 서비스 컴포넌트 (에릭에반스가 한 말)
- 자신의 본거지를 엔티티나 값객체에서 찾지 못하는 중요한 연산이 있다. 일부는 본질적으로 사물이 아닌 활동이나 행동인데
- 우리의 모델링 패러다임이 객체이므로 그러한 연산도 객체와 잘 어울리게끔 노력해야함.
- 이것이 스프링 서비스
- 서비스는 도메인 객체가 처리하기 애매한 '연산'자체를 표현하기 위한 컴포넌트
로직을 도메인 세상 속 객체에 녹이기 어려운 상황?
- ex) 상품(Product), 쿠폰(Coupon), 사용자(User)의 마일리지(Mileage)라는 도메인이 있고
- 가격 = 상품가격 - (상품가격x쿠폰최대할인율)-사용자마일리지
- 서비스에 있는 비즈니스 로직을 도메인 객체가 처리하게 적용해보기
//예시 1
user.calculatePrice(cupons, product);
//예시 2
coupon.calculatePrice(user, product);
//예시 3
product.calculatePrice(user, coupons);
- 하지만 가격을 계산하는 로직은 모든 도메인 객체가 처리하기 애매함.
- 이러한 로직을 능동적인 객체에 표현하는 것 자체가 어렵기 때문
- 가격 계산 로직 같은 것은 '연산'-> 객체로 표현되기 어렵고 '계산식'과 같은 형태로 표현되는 것이 오히려 자연스러움.
객체로 표현하기 어려운 '연산' 로직 해결법 - 새로운 클래스를 만들고 거기에 밀어 넣기
@Service
@RequiredArgsConstructor
public class ProductService{
PriceManager priceManger = new PriceManager(); //서비스가 서비스(매니저)를 호출하는 상황
priceManager.calculate(user, product, coupons);
}
- Manager 클래스가 곧 서비스.
- 이따금 서비스는 특정 연산을 수행하는 것 이상의 의미는 없는 모델 객체로 가장해서 나타나기도 한다.
- 이같은 행위자는 끝에 Manager와 같은 것이 붙는다 (에릭 에반스)
- ...Manager가 나오면 접두어에 있는 모델은 관리하는 클래스..
- 마찬가지로 서비스도 어떤 모델과 강력하게 연관돼 있지만 모델과 관련된 부가적인 논리 로직을 갖고 있는 공간임
사실 스프링 서비스가 연산이다 (서비스 == 매니저 == 연산)
- 스프링 컴포넌트에서 말하는 서비스도 위에서 알아본 Manager 와 동일하다
- 스프링 서비스는 로직 자체가 연산이라서 어떠한 객체도 갖고 있기 힘들어서 만들어진 클래스.
- 1. 저장소에서 데이터를 불러온다.
- 2. 네트워크 호출 결과를 정리해서 객체에 넘겨준다.
- 3. 저장소에 데이터를 저장한다.
- 이러한 로직 모두 그 자체로 '연산'. 그래서 이를 도메인 객체에 넣기 어려움.
- 애플리케이션에 꼭 필요하지만 '연산' 그 자체로는 어떠한 객체도 갖고 있기 애매해서 만들어지는 별도의 클래스
- Service 또는 Manager임. -> 스프링 서비스 컴포넌트의 정체
- 스프링 서비스는 로직 자체가 연산이라서 어떠한 객체도 갖고 있기 힘들어서 만들어진 클래스.
서비스가 서비스를 호출한다? (Service->Manager)
- 하나는 스프링 컴포넌트를 이용해서 만든 ProductService
- 하나는 계산 로직을 표현하기 위해 만든 PriceManager
- 서비스가 서비스를 실행하고 있음
- 양쪽 모두 어떤 도메인 객체로 표현하기 애매한 연산 로직을 모아둔 클래스이지만 성격은 다르다
- PriceManager는 도메인 시스템을 구축하기 위해 존재
- 가격을 계산한다는 점과 가격을 계산하는 비즈니스 업무 규칙을 갖고 있으므로 '도메인'에 가까운 로직
- ProductService는 @Service 애너테이션으로 만들어진 ProductService는 애플리케이션이 돌아가기 위해 필요한 연산을 갖고 있는 서비스
- 어플리케이션의 실행에 초점에 맞춰 개발된 서비스
- 도메인 개발에 필요하지만 객체로 표현하기 애매한 로직을 처리하는 서비스를 '도메인 서비스'라고 한다
- 애플리케이션 개발에 필요하지만 객체로 표현하기 애매한 로직을 처리하는 서비스를 '애플리케이션 서비스'라고 함
도메인 vs 도메인 서비스 vs 애플리케이션 서비스
도메인 (도메인 로직)
- 비즈니스 로직을 처리
- 도메인 역할을 수행
- 다른 도메인과 협력
- ex) User, Product, Coupon
도메인 서비스 (도메인 연산)
- 비즈니스(도메인) '연산' 로직을 처리
- 도메인 협력을 중재
- 도메인 객체에 기술할 수 없는 연산 로직을 처리
- ex) PriceManager
애플리케이션 서비스 (어플리케이션 연산)
- 애플리케이션 '연산' 로직을 처리
- 도메인을 저장소에서 불러옴
- 도메인 서비스를 실행
- 도메인을 실행
- ex) ProductService
@Service의 자바독 요약 2
- 'J2EE의 비즈니스 서비스 파사드 패턴처럼 사용할 수 있다'
- -> == 도메인과, 도메인 서비스의 파사드처럼 사용할 수 있다.
도메인 VS 도메인 서비스
- 위의 도메인 서비스(ProductManager)는 도메인(Cashier)로 발전될 수 있음
- -> 도메인과 도메인 서비스를 구분짓는 것은 행동으로 결정..
객제지향 개발자의 서비스 컴포넌트 조언
- 서비스는 가능한 한 적게 만들고, 얇게 유지해야함
- 서비스보다 풍부한 도메인 모델을 만들어야함
- -> 서비스에 있는 비즈니스 로직을 도메인 객체로 옮겨라..!
- -> 서비스 코드를 작성할 때는 현재 작성 중인 코드가 '기존 도메인 객체에 들어갈 수는 없는지'
- -> 혹은 '새로운 도메인 모델로 만들수는 없는지' 고민
개발 우선순위
- 도메인 모델 > 도메인 서비스 > 애플리케이션 서비스
- -> 테스트하기 쉽다
- -> 도메인 모델은 도메인 서비스보다 테스트하기 쉽고, 도메인 서비스는 애플리케이션 서비스보다 테스트하기 쉽다.
작은 기계 - 서비스 컴포넌트가 생성자 주입해야하는 주된 이유 -> 불변성
- 서비스에 관한 새로운 시각
- -> '한 번 생성하면 여러 번 사용하지만 그 자신은 바꿀 수 없다. 생명주기도 매우 단순하다. 한 번 생성하면 특정 작업을 하는 작은 기계처럼 영원히 실행할 수 있다. 이러한 객체를 서비스라고 함'
- -> 불변성
- 객체로 표현하기 애매한 연산 로직의 집합이 서비스
- 서비스는 어떤 가변 상태를 갖는 객체가 아니라 '계산식'그 자체 -> 그러니 서비스는 불변.
- -> 생성자 주입
- -> 생성자 주입의 주된 이유는 서비스는 원래 불변하기 떄문.
서비스 컴포넌트를 위한 행동 조언
- 서비스 멤버변수 모두 final
- 서비스에 세터 지우기
- 서비스 생성자 주입
- 서비스 비즈니스 로직을 도메인에 양보
- 서비스를 얇게 유지
출처)
'컴퓨터 과학 > [책] 자바스프링 개발자를 위한 실용주의 프로그래밍' 카테고리의 다른 글
자바/스프링 개발자를 위한 실용주의 프로그래밍 5장 - 순환참조 (1) | 2024.09.24 |
---|---|
자바/스프링 개발자를 위한 실용주의 프로그래밍 4장 - SOLID (0) | 2024.07.19 |
자바/스프링 개발자를 위한 실용주의 프로그래밍 3장 - 행동 (0) | 2024.07.18 |
자바/스프링 개발자를 위한 실용주의 프로그래밍 2장 - 객체의 종류 (VO, DTO, DAO, 엔티티) (0) | 2024.07.08 |
자바/스프링 개발자를 위한 실용주의 프로그래밍 1장 - 절차지향과 비교하기 (0) | 2024.07.08 |