아키텍처

도메인 로직(2) : 서비스 계층

kujaHn 2021. 10. 28. 11:39

서비스 계층의 등장

도메인 로직을 선택하고 코드를 짯다면 이제는 이 로직들을 처리해야 합니다. 잘 아시겠지만 도메인 로직만으로 어플리케이션을 실행할 수 없습니다. 이 객체를 사용하는데 필요한 데이터를 DB로 부터 가져와야 하며, 로직을 처리한 결과를 다시 한번 DB에 넣어주는 과정과 후처리를 해주는 과정이 필요합니다. 이렇게 도메인 로직 전 후의 과정의 전체적인 플로우를 애플리케이션 로직이라고 합니다.

 

이러한 로직들은 어디에 넣어야 할까요? 일단 도메인 로직은 아닙니다. 도메인 로직은 항상 순수하게 도메인 로직 그 자체로 존재(캡슐화)해야 합니다. 그렇지 않으면 의존관계(Dependency)가 생기게 되어(응집도 감소, 결합력 증가) 후 유지 보수에 치명적인 결함을 만들게 됩니다. 도메인 계층의 캡슐화는 필수입니다!

 

이러한 고민 끝에 도메인 로직을 둘로 쪼개어 새로운 계층을 만들게 되는데 이 계층이 바로 서비스 계층입니다!

 

그렇게 되어 도메인 로직은 캡슐화가 완성되어 재사용성이 높아지게 되고 서비스 계층은 애플리케이션 경계에 위치해 도메인 로직의 결과를 애플리케이션 로직(Transaction.begin( ), Trainsaction.commit( ), Transaction.rollback( ))을 사용해 외부(프레젠테이션 계층)와 상호작용을 실시합니다.

 

말이 어려운데 결국 여러 서비스들을 하나의 계층으로 그룹화해 재사용성, 유지 관리성을 높이자는 말입니다.

 

 

결론 

도메인 로직 패턴에서 설명한 도메인 모델 방식은 객체지향을 사용해 로직들의 재사용성을 높이는 것을 타깃으로 발전했습니다. 하지만 순수 도메인 객체 클래스에 애플리케이션 로직과 도메인 로직 두가지가 존재하면 두 가지 문제점이 생기게 됩니다.

 

1. 도메인 객체 클래스가 특정 애플리케이션 로직를 구현하고 특정 애플리케이션 패키지를 사용하면 도메인 객체 클래스를 다른 애플리케이션에서 재사용하기 어려워진다.

 

이 문제점의 핵심은 도메인 객체 클래스가 '특정' 대상을 목표로 구현되었다는 것이 문제가 됩니다. (의존관계 형성)

이렇게 되면 공통되는 로직들을 빼오기가 매우 어렵게 됩니다.  

  • 도메인 로직 : 순수한 도메인 로직
  • 애플리케이션 로직 : 애플리케이션 역할을 처리 (Transaction.begin( ), .commit( ), .rollback( ))

 

2. 도메인 로직과 애플리케이션 로직을 동일한 클래스에 넣으면, 나중에 분리할 필요가 있을 때 다시 구현하기 어렵다.

 

동일 클래스에 넣게되면 필연적으로 의존관계가 형성이 됩니다. 다른 한 종류와 관련된 로직이 비대해지게 되거나, 따로 필요한 일이 있어 분리를 해야 할 때 이미 너무 강하게 결합되어 있어 분리가 어렵게 됩니다.

 

이러한 이유들로 인해 서비스 계층은 각 유형의 비즈니스 로직을 별도의 계층으로 분리함으로써 계층화의 일반적인 장점을 제공하고, 순수 도메인 객체 클래스를 애플리케이션 간에 재사용하기 쉽게 만들어 줍니다.

 

구현

구현의 핵심은 서비스 계층에 얼마나 많은 동작을 넣을지 결정하는 것입니다.

이를 기준으로 도메인 파사드 방식과, 작업 스크립트, 컨트롤러-엔티티 방식으로 나뉩니다.

도메인 파사드 방식

도메인 파사드 방식은 서비스 계층을 도메인 모델 위에서 씬 파사드의 집합으로 구현하게 됩니다.

파사드를 구현하는 클래스(서비스 계층)는 애플리케이션 로직을 제외한 로직들을 전혀 구현하지 않고(씬 파사드) 최대한 가벼운 형태로 유지를 하며, 도메인 모델에서 모든 비즈니스 로직을 구현합니다. (동작을 가장 적게 넣는 소극적인 사례)씬 파사드는 클라이언트 계층이 애플리케이션과 상호작용하기 위한 작업 집합과 경계를 형성하는 역할을 담당서비스 계층의 가장 기본적인 역할만을 하게 됩니다.

 

트랜잭션 래퍼와 보안 검사를 추가하는데 유리한 방식입니다.

 

작업 스크립트 방식

작업 스크립트 방식에서는 서비스 계층을 리치 클래스의 집합으로 구현합니다.

리치 클래스는 애플리케이션 로직뿐 아니라 비즈니스 로직 또한 구현되어 있습니다., 도메인 로직은 캡슐화된 도메인 객체 클래스에 위임하게 됩니다. 서비스 계층의 클라이언트에 제공되는 작업은 여러 스크립트로 구현되며, 이러한 스크립트는 연관된 논리의 특정 주제 영역을 정의하는 한 클래스에 포함됩니다.

 

각 클래스는 애플리케이션 서비스를 형성하여 이름이 서비스로 끝나는 경우도 흔합니다. (MemberService, ItemService..)

서비스 계층은 이러한 여러 애플리케이션 서비스 클래스로 이뤄지며, 이러한 서비스 클래스는 각자의 역할과 공통 동작을 추상화하고 계층 상위 형식을 확장해야 합니다.

 

컨트롤러-엔터티 방식

도메인 모델과 트랜잭션 스크립트를 혼용할 때 채택하는 방식으로, 한 트랜잭션이나 유스 케이스(use case : 기능의 단위)에 적용되는 로직을 트랜잭션 스크립트에 넣는 방식입니다. 이를 컨트롤러나 서비스라고 부릅니다.

둘 이상의 유스 케이스에서 사용되는 동작은 엔터티라는 도메인 객체에 배치하게 됩니다.

 

흔히 사용하는 방식인데 저자는 별로 추천하지 않는 방법이라고 합니다. 이유는 다음과 같습니다.

  • 많은 중복 코드 유발
  • 도메인 모델을 사용하기로 했으면 왠만하면 도메인 모델만 사용하자

예외적으로 행 데이터 게이트웨이&트랜잭션 스크립트를 사용하는 설계로 개발을 시작했을 때 이 방법을 사용하나

이 방법을 고수하는 것 보다 중복되는 동작들을 행 데이터 게이트웨이로 옮겨 활성 레코드를 사용하는 도메인 모델로 교체하라고 제안하고 있습니다.

 

결론

서비스 계층에 대한 사용은 많은 개발자들의 의견이 갈리고 있습니다.

필수 계층으로 생각하지 않고 필요할 때 최대한 간소화한 서비스 계층을 사용하는 개발자가 있는가 하면, 항상 서비스 계층에 많은 양의 로직을 넣는 리치 서비스 계층을 만드는 개발자도 있기 때문에 논리적인 판단에 따라 사용하는 것이 좋을 것 같습니다!

 


 

번외1: 원격 호출에 대한 고려

서비스 계층 클래스의 인터페이스는 클라이언트 계층에 제공되는 애플리케이션 작업의 집합을 선언하기 때문에 서비스 계층 클래스는 원격 호출에 적합합니다. 하지만 원격 호출은 먼저 객체 분산이라는 비용을 치루어야 합니다.

서비스 계층 메서드 시그니처를 데이터 전송 객체를 받게 변경하기 위해서는 상당한 양의 추가 작업이 필요합니다.

특히 도메인 모델이 복잡한 업데이트를 위한 리치 UI를 사용하는 경우에는 심사숙고하셔야 합니다.

 

우선 로컬로 호출할 수 있고 도메인 객체를 받는 메서드 시그니처를 사용하는 서비스 계층으로 시작하는 것이 좋습니다. 원격이 꼭 필요할 때는 서비스 계층에 원격 파사드를 넣거나, 서비스 계층 객체에서 원격 인터페이스를 구현하면 됩니다.

 

애플리케이션에 웹 기반 UI나 웹 서비스 기반 통합 게이트웨이가 있는 경우, 비즈니스 논리를 서버 페이지나 웹 서비스와 별도의 프로세스로 실행해야 한다는 법은 없기 때문에 같은 장소에 배치하는 방식으로도 확장성을 저해하지 않으며 개발 시간과 런타임 응답 시간을 상당히 많이 개선할 수 있습니다.

 

 

번외2: 서비스 및 작업 식별

서비스 계층 경게에 필요한 작업을 식별하는 과정은 간단합니다. 이러한 작업은 서비스 계층 클라이언트의 필요성에 의해 결정되는데, 가장 중요하고 우선적인 서비스 계층 클라이언트는 일반적으로 사용자 인터페이스입니다.

사용자 인터페이스는 사용자 애플리케이션으로 수행하려는 유스 케이스를 지원하도록 설계되므로 서비스 계층 작업을 식별하는 시작점은 애플리케이션의 유스 케이스 모델과 사용자 인터페이스 설계입니다.

 

유스 케이스는 도메인 객체를 대상으로 하는 CRUD인 경우가 많습니다. 그리고 이 유스 케이스와 서비스 계층 작업 간에는 거의 항상 일대일 대응 관계가 있습니다.

 

 

마치며

서비스 계층은 논리 계층을 체계화하기 위해 패턴으로, 스크립트와 도메인 객체 클래스를 결합하고 둘의 장점을 활용하게 해 줍니다.

서비스 계층 구현에는 얼마나 많은 동작을 넣을지에 따라 나뉘게 되며 정도가 없기 때문에 논리적인 판단에 따라 선택을 해야 합니다.

이 패턴의 핵심은 어떠한 변형을 사용하든지 애플리케이션의 비즈니스 로직의 구현을 캡슐화하고 다양한 클라이언트가 이 로직들을 일관성 있게 호출할 수 있도록 기반을 마련한다는 것이었습니다!

 

 

참고자료

도서 : 엔터프라이즈 애플리케이션 아키텍처 패턴 [마틴 파울러]