본문 바로가기
개발/Spring

서비스 계층은 왜 필요할까?

by kadokok 2023. 4. 23.

미션을 하다가 리뷰어에게 이런 질문을 받은 적이 있습니다.

서비스가 꼭 있어야 할 이유에 대해서도 생각해 보면 좋을 것 같습니다. 컨트롤러에서 도메인과 Dao를 이용해 해결해도 되는데 서비스로 분리한 이유는 무엇일까요?

 

이번 미션에서 계층 구조를 분리할 때, 저는 Controller → Service → Repository → DAO → DB 의 형태로 계층 흐름을 설계해 주었는데요. 이런 식의 흐름을 만들게 된 이유는 단순히 계층화 아키텍처(Layered Architecture)에서의 Best Practice 라고 생각했기 때문이었습니다.

 

그러다가 막상 서비스 계층이 왜 필요한지에 대한 질문을 들었을 때, “남들 다 이렇게 만드니까요?” 라는 생각이 먼저 떠오르는 것이 스스로에게 꽤 부끄러웠습니다. 분명 서비스 계층이 필요한 이유가 있었을 텐데 말이죠.

 

그래서 이번 글을 통해 왜 서비스 계층이 필요한지 정리해보려고 합니다.

하지만 먼저 서비스 계층이 왜 필요한지부터 알아보기보다는, 이보다 더 근본적인 물음에 대해 정리해 볼 필요가 있을 것 같습니다.

 

왜 계층 구조를 분리하는가?

먼저 우리가 계층 구조를 분리하는 이유부터 생각해볼까요?

사실, 기능 동작에만 집중한다면 굳이 계층을 나눌 필요는 없습니다. 컨트롤러 내부의 요청 매핑 메서드 안에 필요한 모든 로직들을 넣어주면 되는 일이죠.

 

그렇지만 기능 동작만 구현하면 서비스 개발은 끝이 나는 건가요?

그렇지 않습니다. 서비스를 적절히 유지보수하면서, 서비스의 발전을 지속해 나가야 합니다.

 

이러한 과정 속에서 변경 사항에 대해 유연한 대처를 할 수 있어야 하고, 유지보수를 하는 데 있어 큰 어려움이 없어야 하죠. 이것이 우리가 계층 구조를 분리하는 주된 이유입니다.

 

여기서 우리가 계층 구조를 분리한다는 말은, 계층마다 가지는 관심사의 범위를 한정시킨다는 말과 같습니다. 관심사를 나누어서 계층마다의 책임과 역할을 분리시키는 것이죠.

 

그렇다면 계층 구조를 분리하는 게 어째서 유지보수에 도움이 될까요?

이는 우리가 원활한 협업과 유지보수를 하기 위해 객체지향 설계를 하는 맥락과 같습니다. 관심사를 분리하게 되면서 계층끼리의 책임과 역할이 나눠지게 되고, 각 계층이 가지는 성격에 맞는 코드들을 응집시킬 수 있습니다.

 

그렇다면 계층의 성격에 맞는 코드들이 응집되는 것이 왜 좋을까요?

① 협업의 관점에서 보면, 개발자들 간의 공통된 하나의 약속이 생깁니다.

예를 들자면, 컨트롤러 계층에는 클라이언트의 요청 매핑 메서드가 존재해야 하고, 레포지토리 계층에서는 DB 접근 로직들이 존재해야 한다는 것들이죠.

이러한 공통된 약속은 결국 개발자들끼리의 의사소통에 큰 도움이 됩니다. 우리가 코드 컨벤션에 신경 써야 하는 이유도 이러한 맥락이죠.

 

② 유지보수를 하는 개발자의 입장에서 보면, 하나의 가이드라인이 생기는 효과가 있습니다.

각 계층들의 성격을 이해했다면, "어떤 기능은 어떤 계층에 들어가야겠구나!" 하는 사고의 흐름이 빨라질 수 있습니다.

또한, “어떤 기능의 문제가 있다면 어떤 계층에 문제가 있을 수 있겠다!” 라는 추론을 할 수 있고, 이는 개발자가 들여다봐야 하는 코드의 범위를 어느 정도 한정시킬 수 있다는 뜻이기도 합니다. 이는 유지보수를 하면서 들어가는 리소스가 낭비되지 않는 데에 큰 도움이 될 수 있죠.

 

이러한 사실들을 정리해 보면, 결국 계층 구조를 분리하는 이유는 생산성을 높이는 데 큰 도움이 되기 때문입니다.

 

서비스 계층은 왜 필요할까?

앞에서는 조금 근본적인 이야기를 했다면, 이제는 이번 글의 메인 주제인 서비스 계층의 존재 이유를 생각해 보죠.

서비스 계층은 왜 필요할까요? 크게 세 가지 정도로 정리해 볼 수 있을 것 같습니다. (중요도 순)

 

  1. 책임과 역할을 부여받은 하나의 계층으로써, 계층 간 관심사를 분리시킵니다.

제일 중요한 이유라고 생각합니다. 서비스 계층은 자신에 알맞은 책임과 역할에 집중합니다.

이로 인해 계층 간 관심사가 분리될 수 있고, 이에 대한 장점은 바로 위에서 보셨을 겁니다.

 

글 맨 처음에 언급했던 리뷰어의 말을 다시 가져와 볼까요?

컨트롤러에서 도메인과 Dao를 이용해 해결해도 되는데 서비스로 분리한 이유는 무엇일까요?

만약 컨트롤러 계층에서 도메인과 DAO 로직을 전부 처리하게 된다면, 컨트롤러의 책임이 너무 많아지게 되겠죠.

사용자의 요청을 적절히 매핑시켜야 하고, 터지는 예외에 대해 적절한 응답 코드를 내려줘야 하며, 도메인과 DAO 로직을 합작시켜 사용자의 행위 또한 묘사해야 합니다.

 

이는 컨트롤러 계층의 코드가 비대해질 뿐 아니라, 코드의 관심사가 섞여있는 탓에 협업을 하며 유지보수하는 개발자들의 혼란이 가중될 수밖에 없습니다.

 

  1. 사용자의 행위를 묘사할 수 있습니다.

서비스 계층은 자신에 알맞은 책임과 역할에 집중합니다.

바로 위에서 한 말입니다. 서비스 계층이 가지는 책임과 역할은 무엇일까요?

“하나의 행위를 묘사한다” 입니다. 이게 바로 서비스 계층의 정체성이죠.

 

서비스 계층에서는 어떤 하나의 행위에 대해 필요한 모든 로직을 처리합니다. 이를테면, DB 조회를 통해 영속화된 도메인 객체를 불러오고, 잘게 쪼개진 도메인 로직들을 하나의 행위 수준으로 적절히 묶어 실행하고, DB 수정을 통해 도메인의 변화를 다시 영속화시키는 식입니다.

 

이를 조금 더 구체적인 예시를 통해 알아볼까요?

사람이라는 도메인이 있고, 그 안에 포만감이라는 속성이 있다고 가정해 보죠. 그리고 밥을_먹는다() 라는 도메인 로직을 실행하게 되면 포만감이 찬다고 해보겠습니다. 그렇다면 서비스 계층에서의 사람이_밥을_먹는다() 라는 행위 안에는 다음과 같은 일련의 과정이 실행될 겁니다.

 

① A라는 사람의 포만감을 DB에서 조회한다.

② 조회된 포만감을 통해 A 사람 도메인 객체를 만든다.

A.밥을_먹는다() 를 호출한다.

④ DB에 있던 기존의 포만감 수치를 변화된 포만감 수치로 수정한다.

 

이런 식으로 서비스 계층은 하나의 행위를 로직으로 나타낼 수 있습니다. 그리고 이것이 서비스 계층이 가져야 할 책임과 역할이죠.

 

  1. 코드 중복을 없애면서 재사용성을 챙길 수 있습니다.

서로 다른 URL로 들어온 요청에 대해서 같은 행위가 필요한 경우가 있을 수 있습니다.

만약 서비스 계층이 나뉘어 있다면, 위의 경우에 서비스의 메서드를 재사용할 수 있을 것이고, 이로 인해 코드 중복을 없앨 수 있겠죠.


이렇게 크게 세 가지 정도로 정리를 해봤습니다.

앞의 내용에서도 잠깐 언급했지만, 제가 생각했을 때의 서비스 계층이 필요한 가장 큰 이유는 “계층 간 관심사 분리” 입니다.

그래서 서비스 계층의 존재 이유를 알아보기 전에, 그보다 더 근본적으로 중요한 “왜 계층 구조를 분리하는가?” 를 먼저 다루게 되었습니다.

 

글 봐주셔서 감사합니다.

댓글