본문 바로가기
도메인 주도 설계

아키텍쳐에서 '서비스' 라는 용어 어디까지 알아보고 오셨나요?

by simplify-len 2023. 3. 17.

우리가 사용하는 '서비스' 라는 용어는 개발 생태계에서 많이 사용되는 용어입니다. 우리가 알고있는 전통적인 레이어드 아키텍쳐에서도 '서비스'라는 용어가 특히 Application 에서 빈번하게 사용됩니다. 

도메인 주도 설계에서 말하는 '서비스'에 대해서 설명하기 전에 전통적인 레이어드 아키텍처에 대해서 설명할 필요가 있다고 생각해 아래와같이 간략히 설명해보려 합니다.

그림 1. 전통적인 계층형 아키텍쳐

  그림 1에서 보이는 것과 같이 흔히 4가지의 계층으로 구성되어, Presentation/Application/Domain/Infra 로 구성되어있고, 의존성은 아래로 향합니다.

Presentation
표현계층에서는 Web 에 관련된 책임을 가지게 됩니다.

DDD 시작하기 책 일부 내용

객체 변환이나 JSON 변환 등의 대표적으로 Web에 대한 책임을 가지게됩니다.

Application(응용)
애플리케이션은 수행할 작업을 정의하는데, 표현력있는 도메인 객체가 문제를 해결할 수 있도록 하며, 애플리케이션은 그런 표현력있는 도메인 객체를 사용하는 클라이언트가 됩니다. 이곳은 레이어드 아키텍쳐에서 최대한 얇게 유지되어야 하며, 업무 규칙이나 지식은 포함되지 않습니다. 

그림 2 응용과 도메인 사이에 상호작용

그림 2를 보면 응용계층은 도메인의 Entity와 ValueObject를 사용합니다. 여기 응용 계층에서 서비스라는 용어가 등장합니다. 이 용어에 대해서는 뒤에서 좀 더 설명해볼게요.

Domain
업무개념과 업무상황에 관한 정보, 업무규칙을 표현하는 일을 책임집니다. 기술적인 세부사항은 인프라스트럭쳐에서 구현됩니다. 도메인 주도 설계에서는 도메인이라 하는 부분은 해결해야될 문제점이 있는 영역을 의미합니다. 

Domain 을 표현하기 위해 다양한 형태로 존재할 수 있습니다.

 

그렇다면 위에서 Application(응용) 계층을 왜 얋게 유지해야 하는 걸까요?

과거와 현재

먼저 반대로 두껍게 만든건 어떤 것일까요? 제가 생각하는 두꺼운 응용계층은 그림2에서 보여지는 엔티티와 값을 활용해 비지니스 로직을 구현된 형태를 말합니다. '당연하게 그렇게 하는거 아니야?' 라고 생각 한다면, 앞으로 말할 '왜 얋게 유지해야지' 에 대해서 납득이 안 될 수도 있습니다.

1. 응용 계층을 얋게 유지하지 않는다면, Application 계층은 복잡한 비지니스 로직이 몰리게 되어 결합도가 높고, 응잡도가 낮은 절차적인 프로세스 라는 함정에 빠지기 쉽습니다. 

 두껍다고 말할 수 있는 냄새로는 아래와같은 코드를 맞이할 경우입니다.(spring + java 으로 구성되었습니다)

@Slf4j
@Service
@RequiredArgsConstructor
public class ServiceImpl implements Service {

    private final XxxxxxxeRepository xxxxxxxeRepository;
    private final XxxxxxxdRepository xxxxxxxdRepository;
    private final XxxxxxxcRepository xxxxxxxcRepository;
    private final XxxxxxxbRepository xxxxxxxbRepository;
    private final XxxxxxxaRepository xxxxxxxaRepository;
    private final RedisTemporaryRepository redisRepository;
    private final QnaAlarmService qnaAlarmService;
    private final EmailSender emailSender;
    private final SlackAlarmService slackAlarmService;
    private final XxxxxxxService qnaxxxxxxxService;
    
    ...
    
    @Override
    public void somethingMethod1() {
		...
    }

    @Override
    public void somethingMethod2() {
		...
    }    
    

}

 이렇게 여러개의 Repository 를 한 서비스에서 사용하게 된다면, 결합도가 높아지고 응집도가 낮아질 것입니다. 이것을 측정하는 기준은 위 클래스 기준으로 ServiceImpl 클래스에서 의존성 주입된 복수의 클래스가 메서드 하나에서 얼마나 사용되는지? 만약 메서드 하나에 단 2개의 주입된 객체를 사용한다면, 나머지 8개의 주입된 객체를 상요하지 않는다면 그것이 바로 결합도가 높고 응집도가 낮아진다고 말할 수 있습니다.

 이런 방식으로 응용계층을 두껍게 가져가게 되면 점점 유지보수하기 어려운 코드를 만들게 됩니다.

2. 테스트 코드를 만들기 어렵고, 회귀적인 자동화 테스트를 실행시키기 어려워집니다.

왜 테스트 코드를 만들기 어렵다고 말할 수 있을까요? 다시 코드로 돌아가서 somethingMethod1 기능을 개발하기 위한 테스트 코드를 만들고 싶다면 어떻게 해야될까요? somethingMethod1 에 사용되는 주입된 객체를 모킹해야될 것입니다. 이때 somethingMethod1에서 사용되지 않는 불필요한 객체들까지 모킹해야만 합니다. 그렇기 때문에 테스트 코드를 만들기 어렵게 만드는 요인이 되고, 설정이 필요한 어떤 객체를 주입받았다면 그것에 맞는 설정까지 해줘야 될 것입니다.

@ExtendWith(SpringExtension.class)
class ServiceImplTest {

    @InjectMocks
    ServiceImpl sut;
    
    @Mock
    XxxxxxRepository repository;
    
    ... more and more mocking...
    
    @Test
    void name() {
    }
}

3. 두껍게 만든 Application(응용) 계층은 쉽게 단일 책임 원칙을 위반하게 됩니다.

 단일 책임 원칙을 위반한다는 말이 무슨 의미일까요? 전체적인 아키텍쳐를 바라는 관점이 2가지가 있다고 생각합니다. 외부에서 아키텍쳐를 바라보는 관점과 도메인안에서 밖을 바라보는 관점이 있습니다.

그림 3. 얋게 만든 애플리게이션 VS 두껍게 만든 애플리케이션

 이때 외부에서 아키텍쳐를 바라보는 관점이란? 개발자가 아닌 이해관계자에게 우리의 아키텍쳐를 설명해줘야 할 때 할 수 있는 이야기라고 생각하고, 내부에서 아키텍쳐를 바라보는 관점이란 도메인 전문가와 개발자가 우리의 아키텍쳐를 어떻게 설계할 것인가? 대해서 논의한 후에 만들어진 모델링이라 말할 수 있을 것 같습니다.

그림3 에서 오른쪽에 AS-IS 에서 애플리케이션이 두껍게 유지할 경우, 외부관점과 내부관점이 충돌하게 됩니다. 외부관점에서 우리의 애플리케이션을 설명하는 것과 내부관점에서 설명하는 것이 동일한 것이 무슨 문제가 있는지 조금 더 설명한다면, 외부관점에서 우리의 애플리케이션에 대한 설명을 들어야 할 때, 어디까지 들어야 할까요? Redis, DB 구성 등의 불필요한 내용까지 알아야 될까요? 그렇지 않습니다. 이것은 관심사의 분리와도 관련이 있습니다. 알아야 할 것만 알아야 하며, 불필요한 정보는 제공할 필요가 없습니다.

내부 관점에서는 우리가 흔히 말하는 객체지향 프로그래밍에 대한 이해와 비슷합니다. 1개 이상의 표현력있는 도메인 객체가 서로 협력하는 형태로 서로에게 메세지를 전달합니다. 이렇게 함으로써, 캡슐화/재사용/다형성 등의 이점을 누리게 될 것입니다.

내/외부 관점에서 동일한 위치에 코드가 존재할 경우 SRP 을 쉽게 위반하게 됩니다.

그렇다면 서비스는 무엇일까요?

우리는 흔히 애플리케이션(응용) 계층에서 말하는 ‘서비스’만을 떠올리곤 합니다. 하지만 정말 그 서비스만이 ‘서비스’일까요? 그렇다면 왜 ‘서비스’라는 이름이 붙었을까요?

‘서비스’라는 용어는 단순히 계층을 구분하기 위한 것이 아니라, 객체 간의 관계무엇을 제공하는가를 강조하기 위해 사용됩니다. 즉, 서비스란 클라이언트에게 어떤 기능을 제공하는 객체 또는 모듈을 의미하며, 이 개념은 단지 응용 계층에만 국한되지 않습니다.

사실, 도메인 계층이나 인프라스트럭처 계층에도 서비스는 존재합니다. 다만, 이들 계층의 주 관심사가 서비스에 있지 않을 뿐입니다.

도메인 계층에서의 서비스

도메인 주도 설계(DDD)에서는 도메인 서비스라는 개념을 사용합니다. 도메인 서비스는 다음과 같은 특징을 가집니다:

  1. Entity나 Value Object의 일부가 아닌, 도메인 개념에 기반한 별도의 객체입니다. 예: TransferServiceAccount 간의 이체를 담당합니다.
  2. 하나의 엔티티로 표현할 수 없는 도메인 로직을 다룹니다. 도메인 간, 혹은 외부 요소와의 경계에서 주로 사용됩니다.
  3. 상태를 가지지 않으며, 연산 중심의 객체입니다.

도메인 서비스는 모델의 언어를 반영하는 연산의 인터페이스로 정의되며, 그 이름은 이해관계자들 간의 공통 언어가 되어야 합니다.

 

애플리케이션과 인프라스트럭처 계층의 서비스

  • 애플리케이션 서비스는 도메인 객체나 도메인 서비스를 활용해 도메인 간 메시지를 전달하거나, 트랜잭션과 같이 도메인과 무관한 프로세스 제어 역할을 수행합니다.
  • 인프라스트럭처 서비스는 외부 API, 메시징, 트랜잭션 등 기술적인 기능을 제공하며, DIP 원칙에 따라 도메인 서비스 인터페이스의 구현체 역할을 할 수 있습니다.

‘서비스’라는 단어는 다양한 문맥에서 사용되는 만큼, 그 의미를 정확히 파악하는 것이 중요합니다. 도메인 주도 설계에서 말하는 ‘서비스’는 우리가 일반적으로 알고 있는 것과는 다소 차이가 있습니다. 이 글이 그 간극을 조금이나마 좁히는 데 도움이 되었기를 바랍니다.

댓글