본문 바로가기
독서/자바,스프링 개발자를 위한 실용주의 프로그래밍

7. 서비스

by Thinking 2024. 11. 12.

서비스의 역할은 크게 3가지 종류의 일을 해야 합니다.

1) 도메인 객체를 불러옵니다.

2) 도메인 객체나 도메인 서비스에 일을 위임합니다.

3) 도메인 객체의 변경 사항을 저장합니다.

 

7.1 Manager

스프링에서 서비스는 왜 서비스라고 부를까요? 컨트롤러는 '제어부', 리포지터리는 '저장소', 컴포넌트는 '구성 요소'입니다. 서비스는 '비즈니스 서비스를 처리하는 곳입니다'라는 답변은 충분한 답변이 되지 못합니다. @Service 애너테이션이 적힌 주석을 보면, 답이 나오죠.

1) @Service는 에릭 에반스의 DDD에서 영감을 받아 만들어진 애너테이션입니다.

2) 서비스는 J2EE 패턴 중 하나인 비즈니스 서비스 파사드처럼 사용될 수 있다.

 

스프링 서비스는 DDD에서 파생된 개념이라고 합니다. 이제 DDD를 알아보죠. DDD는 '도메인 주도 설계' 도메인을 중심에 놓고 소프트웨어를 설계하는 방법을 알려주는 개발 방법론입니다. 앞서 '도메인 엔티티'에서 도메인은 '비즈니스 영역이다'라고 설명했습니다. 도메인이란 비즈니스 영역이자, 우리가 해결하고 싶은 문제 영역입니다. 소프트웨어를 개발하는 이유가 곧 도메인에서 발생하는 문제를 해결하기 위함이죠.

 

예로 은행 시스템을 볼 때 도메인은 '은행'이라고 볼 수 있습니다. DDD 세상의 개발자는 도메인에 대해 잘 알아야합니다. 도메인 자체를 사전에 알고 있는 것이 아니라, 도메인을 분석하고 탐색할 수 있는 능력이 요구됩니다. 개발자가 해결하려는 비즈니스 영역을 이해하는 과정을 보고 '도메인 탐색'이라 부릅니다. 대부분의 도메인 탐색 과정은 '도메인 전문가'라고 불리는 집단과 소통하면서 이뤄집니다. 은행으로 치면 도메인 전문가는 '은행원'으로 볼 수 있죠.

 

도메인을 탐색하고, 탐색한 내용을 바탕으로 소프트웨어를 설계한다. 이것이 DDD의 주요 골자입니다. 서비스는 도메인 객체가 처리하기 애매한 '연산' 자체를 표현하기 위한 컴포넌트입니다. 서비스는 특정 연산을 수행하는 것 이상의 의미는 없는 모델 객체로 가장해서 나타나기도 한다. 이 같은 '행위자'는 이름 끝에 "Manager"와 같은 것이 붙는다. 일반적으로 클래스 이름의 접미어에 Manager가 나오면 접두어에 있는 모델을 관리하는 클래스를 뜻합니다.

 

모델에 관련된 부가적 논리 로직을 제공하는 공간으로 해석됩니다. 어떤 서비스도 어떤 모델과 강력하게 연관돼 있지만 모델과 관련된 부가적인 논리 로직을 갖고 있는 공간입니다. 서비스 클래스는 곧 매니저 클래스입니다. 로직 자체가 '연산'이라서 어떠한 객체도 갖고 있기 어려운 경우에 만들어지는 클래스가 서비스입니다.

 

스프링의 서비스 컴포넌트도 모두 아래와 같은 일을 처리합니다.

1) 저장소에서 데이터를 불러옵니다.

2) 네트워크 호출 결과를 정리해서 객체에 넘겨줍니다.

3) 저장소에 데이터를 저장합니다.

 

모두 그 자체로 '연산'입니다. 이를 도메인 객체에 넣기 어렵고, 애플리케이션이 가치를 전달하기 위해서는 이러한 코드가 반드시 필요하죠. 앞서 말한 내용을 정리해보죠.

1) 스프링의 서비스는 DDD의 서비스에서 유래됐다.

2) DDD에서 도메인은 비즈니스 영역이며 문제 영역이다.

3) DDD에서 서비스는 도메인 문제를 해결하기 위한 패턴 중 하나다.

4) 서비스는 객체가 처리하기에 애매한 연산 로직을 갖고 있는 컴포넌트다.

5) 도메인 개발에 필요하지만, 객체로 표현하기 애매한 로직을 처리하는 서비스를 '도메인 서비스'라 한다.

6) 애플리케이션 개발에 필요하지만, 객체로 표현하기 애매한 로직을 처리하는 서비스를 '애플리케이션 서비스'라 한다.

 

도메인 -> 비즈니스  로직을 처리, 도메인 역할을 수행하며 다른 도메인과 협력한다. Ex) User, Product, Coupon

도메인 서비스 -> 비즈니스 '연산' 로직을 처리, 도메인 협력을 중재하며 도메인 객체에 기술할 수 없는 연산 로직을 처리

애플리케이션 서비스 -> 애플리케이션 '연산' 로직을 처리, 도메인 저장소를 불러오고 도메인 서비스를 실행하며 도메인을 실행한다.

 

 

7.2 서비스 보다 도메인 모델

도메인이 모델로 발전할 수 있는 출발점에 있는 상태라는 것을 말하고 싶습니다. 도메인과 도메인 서비스는 이름으로 결정되는 것이 아니라 구분 짓는 것은 행동입니다. 구체적으로 스프링 개발자가 서비스를 어떻게 취급해야 좋은지 2가지 조언을 하겠습니다.

1) 서비스는 가능한 한 적게 만들고, 얇게 유지해야 합니다.

2) 서비스보다 풍부한 도메인 모델을 만들어야 합니다.

 

1)은 직관적으로 말하면, 로직의 길이가 최대한 짧은 상태를 유지하라는 의미입니다. 얇게 유지하라는 조언은 서비스에 있는 비즈니스 로직을 도메인 객체로 옮기라는 말과 같습니다. 서비스 코드를 작성할 때 현재 작성 중인 코드가 '기존 도메인 객체에 들어갈 수는 없는지' 그리고 '새로운 도메인 모델로 만들 수는 없는지' 고민해보는 것이 좋습니다.

 

개발 우선순위는 도메인 모델 > 도메인 서비스 > 애플리케이션 서비스 이처럼 생각하면 좋습니다. 이렇게 개발할 때 의존성이 명확하게 드러나기 때문에, 테스트 작성이 용이해집니다. 왜 용이할까요? 애플리케이션 서비스는 도메인 모델로 표현하기 까다로운 연산 로직을 갖고 있는 객체입니다. DB 연동같은 코드가 포함된 스프링 서비스 컴포넌트가 이에 해당하죠.

 

즉, 애플리케이션 서비스는 JPA와 관련된 저장소 코드나 네트워크 호출 같은 까다로운 외부 연동을 수행할 확률이 높죠. 그래서 테스트하기 어렵습니다. 도메인 모델이나 서비스는 특정 인프라에 의존하지 않아 테스트가 쉽죠.

 

 

7.3 작은 기계

서비스에 관한 새로운 시각을 소개해보죠.

1) 서비스는 한번 생성하면 여러 번 사용하지만 그 자신은 바꿀 수 없다.

2) 서비스는 작은 기계처럼 영원히 실행할 수 있다.

 

이 말은 서비스는 불변해야 한다는 것을 가리킵니다. 어던 가변 상태를 갖는 객체가 아니라 '계산식' 그 자체입니다. 스프링을 사용하다 보면 '서비스를 필드 주입이나 수정자 주입을 이용해서 초기화하지 말고 생성자 주입을 사용하라'라는 격언을 자주 접하죠. '서비스는 불변해야 한다'라는 말을 이해했다면 이 격언의 존재 이유를 알 수 있죠. 생성자 주입을 사용 하는 이유로 아래와 같죠.

1) 생성자 주입을 사용하면 명시적으로 의존성을 표현할 수 있다.

2) 생성자 주입을 사용하면 테스트가 쉬워진다.

3) 생성자 주입을 사용하면 의존성을 방지할 수 있다.

 

실은 더 근본적인 이유는 서비스는 원래 불변해야 하기 때문입니다. 불변성을 유지하고, 예측할 수 있는 컴포넌트가 돼야 합니다. 똑같이 움직이는 '기계'처럼 작동해야 하고, 얇고 작아야 합니다. 필드 주입을 고수하는 분들에게 아직 필드 주입을 사용하는 이유가 뭔가요? 하면 아래와 같이 대답하겠죠.

1) 생성자가 존재하는 것이 미관상 깔끔하지 않다.

2) 생성자 주입을 사용하면 필요한 컴포넌트가 생길 때마다 생성자에도 매개변수를 할당해야 해서 귀찮다.

3) 생성자 주입을 사용하면 순환 의존성을 못 만들어서 불편하다.

 

2) 해당 이유는 서비스 컴포넌트를 개발할 때 서비스에 무작정 의존성을 추가하지 말라는 신호입니다. 의존성을 관리한다는 측면에서 오히려 효과적이죠. 무작정 의존성을 추가할 것이 아니라 기존 컴포넌트로 해결할 수 없는지, 컴포넌트를 분리할 수 없는지 확인해봅시다. 3) 해당 이유는 실수로라도 순환 의존성을 만들지 못하게 합니다. 

 

 

7.4 조언

서비스와 관련된 행동을 조언해보죠.

1) 서비스의 멤버 변수는 모두 final로 만드세요.

2) 서비스에 세터가 존재한다면 지우세요.

3) 서비스는 반드시 생성자 주입으로 바꾸세요.

4) 서비스의 비즈니스 로직을 모두 도메인에 양보하세요.

5) 서비스를 얇게 유지하세요.