Skip to content

CHAPTER 7 Service Granularity #1615

@jongfeel

Description

@jongfeel

CHAPTER 7 서비스 세분도

모듈화modularity와 세분도granularity라는 용어를 혼동하는 사람이 많습니다.
사전에는 다음과 같이 정의돼 있습니다.

Modularity

사용상의 유연성과 다양성을 위해 표준화된 단위 또는 치수로 만듦

Granularity

더 큰 단위를 형성하는 많은 입자 중 하나로 구성하거나, 그렇게 보임

소프트웨어 아키텍처 관점에서는 두 용어를 구분할 필요가 있습니다.
모듈화는 시스템을 별도의 파트로 분해하는 것과 관련이 있고
세분도는 나누어진 개별 파트의 사이즈에 관한 것입니다.

서비스 사이즈를 객관적으로 측정하기 위한 메트릭 중 하나로, 서비스 문장 수를 세는 방법이 있습니다.
문장 수는 적어도 서비스가 무슨 일을 하는지 객관적으로 확인하는 데 도움이 됩니다.

서비스가 표출한 퍼블릭 인터페이스나 기능 수 역시 서비스 세분도 측정에 유용한 메트릭입니다.

이 두 메트릭은 어느 정도의 주관성과 가변성이 개입되지만, 가장 객관적으로 서비스 세분도를 측정/평가할 수 있는 방법입니다.

그림 7-1과 같이 서비스 세분도는 분해인과 통합인이 있습니다.
세분도 분해인은 어떤 경우에 서비스를 더 잘게 나눠야 하는가?
세분도 통합인은 어떤 경우에 서비스를 다시 합쳐야 하는가?
라는 질문에 각각 해법을 제시합니다.

많은 개발자는 세분도 통합인을 무시하면서 세분도 분해인에만 집중하는 실수를 하고 있습니다.

Image

Figure 7-1. Service granularity depends on a balance of disintegrators and integrators

7.1 세분도 분해인

세분도 분해인은 어떤 경우에 서비스를 더 잘게 나눠야 하는지에 관한 지침과 정당성을 제시합니다.
주요 세분도 분해인을 여섯 가지로 정리하면 다음과 같습니다.

  • Service scope and function
  • Code volatility
  • Scalability and throughput
  • Fault tolerance
  • Security
  • Extensibility

7.1.1 서비스 범위와 기능

서비스 범위와 기능을 분석할 때는 두 가지 차원을 함께 고려해야 합니다.
첫째, 서비스가 하는 일이 얼마나, 어떻게 서로 연관돼 있는지를 나타내는 응집도입니다.
둘째, 텀포넌트의 전체 사이즈입니다. 총문장 수나 서비스 진입점 수, 또는 이 둘을 모두 계산해서 측정합니다.

그림 7-2와 같이 알림 매체별로 3개의 단일 목적 서비스로 구분한 구조가 합리적인 것 같지만
고객 알림이라는 강력한 연관성을 갖고 응집돼 있으므로 서비스를 분해할 명분은 충분합니다.

Image

Figure 7-2. A service with relatively strong cohesion is not a good candidate for disintegration based on functionality alone

그림 7-3처럼 고객 서비스의 프로필, 프리퍼런스, 의견을 관리하는 세 서비스는 고객이라는 범위가 넓은 개념과 연관돼 있기 때문에 응집도는 비교적 약합니다.
그래서 3개의 서비스로 각각 나누는 것이 좋습니다.

Image

Figure 7-3. A service with relatively weak cohesion is a good candidate for disintegration

로버트 C 마틴Robert C. Martin은 SOLID 원칙 중 모든 클래스는 각자 어떤 프로그램 기능의 한 파트를 담당하는데, 이것을 캡슐화해야 한다.
모듈, 클래스, 함수가 제공하는 모든 서비스는 자신이 맡은 책임만 져야 한다는 단일 책임 원칙single-responsibility principle을 주장했습니다.
세분도 분해인도 이와 연관돼 있습니다.

서비스는 바라보는 사람의 시각에 따라 크게 달라질 수 밖에 없으므로 얼마나 잘게 나누는 것이 좋을지를 결정하려면 반드시 다른 세분도 분해인도 함께 고려해야 합니다.

7.1.2 코드 변동성

코드 변동성은 소스 코드를 변경하는 빈도를 말합니다. (이를 변동성 기반의 분해volatility-based decomposition라고도 합니다)
코드가 얼마나 자주 바뀌는지를 객관적으로 측정하면 서비스를 분해해야 하는 타당한 명분을 얻게 될 때가 많습니다.

변경 빈도의 객관적 수집 분석 자료는 다음과 같습니다.

  • SMS: 평균 6개월에 한 번
  • 이메일: 평균 6개월에 한 번
  • 우편물: 매주 한 번

우편물 알림 기능의 변경 빈도가 높은 이유는 우편 번호가 자주 바뀌기 때문인데
단일 서비스로 묶여 있다면 SMS, 이메일까지 모두 테스트 한 후 함께 재배포 해야 합니다.

그림 7-4 처럼 두 서비스로 나누면 우편 번호의 잦은 변경에 따른 영향도를 하나의 더 작은 서비스로 묶어둘 수 있습니다.

Image

Figure 7-4. An area of high code change in a service is a good candidate for disintegration

7.1.3 확장성/처리량

알림 서비스의 확장성 요건을 객관적 수치로 나타내면 다음과 같습니다.

  • SMS: 220,000회 / 분
  • 이메일: 500 회 / 분
  • 우편물 : 1회 / 분

단일 서비스로 되어 있다면 SMS 알림 기능을 확장한다면 이메일, 우편물 기능까지 불필요하게 확장해야 합니다.

그림 7-5처럼 세 서비스로 쪼개면 서비스별로 다양한 처리량 목표를 달성하도록 독립적으로 확장할 수 있습니다.

Image

Figure 7-5. Differing scalability and throughput needs is a good disintegration driver

7.1.4 내고장성

그림 7-6과 같이 이메일 서비스에서 가용 메모리가 고갈된다면 다른 서비스들이 함께 중단됩니다.

Image

Figure 7-6. Fault tolerance and service availability are good disintegration drivers

3개의 서비스로 분리한다면 일정 수준의 내고장성이 보장됩니다.

여기서 SMS와 우편물 서비스는 안정적으로 작동하니까 이 둘을 하나의 서비스로 합치는 방안을 생각할 수 있습니다.
앞서 코드 변동성에서는 우편물 알림 기능이 우편 번호 때문에 자주 변경되고 SMS, 이메일은 거의 바뀌지 않았습니다.
이렇게 서비스를 하나로 합쳤을 때 어떻게 이름을 지어야 할지도 고민일 수 있습니다.

서로 결합된 서비스 이름들에 대해 고민하면 서비스 범위와 기능 세분도 분해인을 다시 돌아보게 됩니다.
서비스가 하는 일이 많아서 이름을 짓기 어려우면 서비스를 더 나누는 방안을 검토해 볼 수 있습니다.

  • Notification Service → Email Service, Other Notification Service (poor name)
  • Notification Service → Email Service, Non-Email Service (poor name)
  • Notification Service → Email Service, SMS-Letter Service (poor name)
  • Notification Service → Email Service, SMS Service, Letter Service (good names)

나중에 소셜 미디어 알림 기능이 생긴다고 가정해보면 마지막 네이밍이 가장 적절합니다.
분해인과 무관하게 서비스를 분해할 때는 '남아 있는leftover' 기능 간에 강한 응집도가 형성되는지 항상 확인해야 합니다.

7.1.5 보안

그림 7-7의 고객 프로필 서비스는 두 가지 핵심 기능을 수행합니다.
첫째 기본 프로필 정보를 추가/수정/삭제하는 고객 프로필 관리 기능
둘째 신용카드를 추가/수정/삭제하는 고객 카드 관리 기능입니다.

고객 정보를 조회하려고 하면 신용카드 정보도 노출됩니다.
이를 두 서비스로 분리해서 단일 목적의 서비스로 구현하면 신용카드 정보의 보안성은 개선될 것입니다.

Image

Figure 7-7. Security and data access are good disintegration drivers

7.1.6 신장성

고객에게 추가 결제 수단을 제공하려면 현재 결제 서비스의 기능을 신장하기가 얼마나 쉬운지 확인해야 합니다.
통합 결제 서비스에 결제 수단을 추가하는 걸로 해결할 수 있지만, 앞으로 새로운 결제 수단이 추가될 때마다 전체 결제 서비스를 전부 테스트해야 하고 기존 결제 수단의 코드까지 재배포해야 합니다.

그림 7-8 처럼 통합 결제 서비스를 신용카드 처리, 기프트 카드 처리, 페이팔 거래 처리인 세 서비스로 분해합니다.
이러면 나중에 새로운 결제 수단이 추가돼도 다른 서비스와 독립적으로 개발, 테스트, 배포가 가능해집니다.

Image

Figure 7-8. Planned extensibility is a good disintegration driver

어떤 통합된 기능이 필요해 추가/신설 계획이 있거나 그런 기능이 일반 도메인의 일부라는 사실을 미리 알고 있는 경우에만 세분도 분해인을 적용합니다.
알림 서비스의 경우 더 추가 전달 수단이 있을지는 미지수이지만 결제 수단은 계속 추가될 가능성이 높습니다.
비즈니스 맥락상 기능이 확장될 가능성이 있는지 추측하는 건 대체로 어렵습니다.
확신이 생기기 전에는 무작정 분해하는 것이 능사는 아닙니다.

7.2 세분도 통합인

세분도 통합인은 어떤 경우에 서비스를 다시 합쳐야 하는지(또는 애당초 서비스를 분리하지 말아야 하는지)에 대한 가이드라인과 명분을 제공합니다.
주요 세분도 분해인을 네 가지로 정리하면 다음과 같습니다.

  • Database transactions
  • Workflow and choreography
  • Shared code
  • Database relationships

7.2.1 데이터베이스 트랜잭션

그림 7-9와 같은 시나리오를 보면 고객 프로필 서비스와 패스워드 서비스로 나뉘어져 있습니다.

Image

Figure 7-9. Separate services with atomic operations have better security access control

이렇게 두 서비스로 분리돼 있으면 어느 정도 안전하게 패스워드 정보를 보호할 수 있습니다.

이제 그림 7-10의 신규 고객의 등록 프로세스는 프로필과 패스워드 정보가 프로필 서비스로 전달됩니다.
그리고 패스워드 정보는 패스워드 서비스에 전달합니다.

Image

Figure 7-10. Separate services with combined operations do not support database (ACID) transactions

문제는 ACID 트랜잭션을 적용할 수 없다는 것입니다.
패스워드 서비스가 작업 도중 실패하면 데이터 무결성이 깨지고 원래 프로필을 다시 삽입하는 등의 복잡한 복원처리가 불가피합니다.
따라서 비즈니스 관점에서 단일 작업 단위의 ACID 트랜잭션이 필요한 프로세스는 그림 7-11 처럼 한 서비스로 통합해야 합니다.

Image

Figure 7-11. A single service supports database (ACID) transactions

7.2.2 워크플로와 코레오그래피

서비스간 통신이 과도해지면 내고장성 이슈가 발생합니다.
그림 7-12에서 서비스들은 각각 통신하는데 서비스 C가 실패한다면 전이적 디펜던시로 나머지 서비스들이 모두 멈추게 됩니다.
전체 내고장성, 가용성, 신뢰성에 큰 타격을 받습니다.

Image

Figure 7-12. Too much workflow impacts fault tolerance

흥미로운 점은 내고장성은 세분도 분해인 중 하나였지만 서비스들 끼리 통신하는 경우는 내고장성 측면에서 얻는 건 없게 됩니다.
그래서 서비스를 분해할 때는 기능들이 서로 단단히 결합되어 있다면 서비스를 그냥 두는 것이 최선일 수도 있습니다.

전체 성능과 응답성 역시 세분도 통합인에 해당합니다.
그림 7-13에서 요청 1회당 네트워크 및 보안 레이턴시가 300ms라고 가정하면 UI 요청 한 번에 1,500ms의 추가 레이턴시가 발생합니다.
5개 서비스를 단일 서비스로 통합한다면 이 레이턴시는 사라질 것이고 성능과 응답성은 전반적으로 개선될 것입니다.

Image

Figure 7-13. Too much workflow impacts overall performance and responsiveness

경험적인 법칙에 따르면 다수의 서비스가 서로 통신하게 만드는 요청 수와 이러한 서비스 간 통신을 필요로 하는 요청의 중요성을 고려하는 것이 최선입니다.
예로 요청의 30%만 서비스간 워크플로가 필요하고 70%는 순수한 원자적 처리가 가능하다면 서비스를 분리한 상태 그대로 두는 것이 합리적입니다.
하지만 비율이 반대이고 전체 성능이 문제가 되는 상황이라면 서비스를 다시 합치는 방안을 진지하게 생각해 보는 게 좋습니다.

워크플로에 필요한 30%의 요청이 매우 빠른 응딥이 필요한 중요한 요청이라면 서비스를 다시 합하는 것이 타당할 수 있습니다.

그림 7-14처럼 신규 고객을 등록하려면 서비스 5개를 거쳐야 하는데 서비스 D가 실패해도 서비스 A, B, C의 데이터는 이미 커밋된 상태가 됩니다.

Image

Figure 7-14. Too much workflow impacts reliability and data integrity

그러면 보상 트랜잭션을 걸어 롤백하거나 재시작을 위한 트랜잭션의 중단 지점에 대한 상태 값을 저장해야 하므로 데이터 일관성과 무결성에 심각한 문제가 될 수 있습니다.
운영 측면에서 데이터 일관성과 무결성이 그 무엇보다 중요한 경우에는 서비스를 다시 통합하는 방법을 생각해 보는 것이 좋습니다.

7.2.3 공유 코드

그림 7-15의 5개 서비스는 모두 도메인 기능의 공통 코드베이스를 공유합니다.
공유 라이브러리를 잘 버저닝하면 민첩성과 하위 호환성이 보장됩니다.
하지만 공유 라이브러리를 변경할 일이 생기면 5개 서비스는 모두 함께 변경하고, 테스트하고, 배포돼야 합니다.
이럴거면 5개 서비스를 단일 서비스로 합쳐서 여러번 배포하지 말고 버전이 다른 공유 라이브러리를 사용해도 버전을 동기화하지 않은 채 놔두는 것이 더 현명한 선택일 수도 있습니다.

Image

Figure 7-15. A change in shared code requires a coordinated change to all services

전체 서비스가 사용하는 로깅, 감사, 인증, 인가, 모니터링 같은 인프라 관련 횡단 기능은 서비스를 다시 합쳐 모놀리식 아키텍처로 돌아가야 할 동인으로는 어울리지 않습니다.
아래 가이드라인을 통해 공유 코드를 세분도 통합인으로 고려할지 판단할 수 있습니다.

Specific shared domain functionality

공유 도메인 기능은 비즈니스 로직이 구현된 공유 코드입니다.
이 비중이 높다면 세분도 통합인의 대상이 됩니다.

Frequent shared code changes

공유 코드가 자주 변경될 수 밖에 없다면, 공유 코드를 사용하는 서비스들을 통합하는게 바람직합니다.

Defects that cannot be versioned

비즈니스 기능을 모든 서비스에 동시에 반영해야 할 때가 많다면 변경을 단순화하기 위해 서비스를 다시 합치는 방안을 고민할 좋은 타이밍입니다.

7.2.4 데이터 관계

그림 7-16에서 통합 서비스는 기능 A, B, C와 해당 데이터 테이블 관계를 가집니다.
실선은 테이블 쓰기인 오너십을 가지고 점선은 읽기 전용 액세스를 나타냅니다.

Image

Figure 7-16. The database table relationships of a consolidated service

표 7-1은 기능과 테이블간의 작업을 매핑한 것입니다.

Table 7-1. Function-to-table mapping

Function Table 1 Table 2 Table 3 Table 4 Table 5 Table 6
A owner owner owner owner
B owner access
C access owner

그림 7-17처럼 세 서비스별로 나누고 그게 맞게 데이터 테이블을 경계 콘텍스트에 있는 각 서비스에 연결해야 합니다.

Image

Figure 7-17. Database table relationships impact service granularity

이제 경계 콘텍스트 때문에 서비스 B는 테이블 5를 직접 쿼리할 수 없고 서비스 C도 테이블 3을 직접 쿼리할 수 없습니다.
그래서 서비스간에 통신을 통해 데이터를 요청해야 하고 서비스간 통신이 불가피합니다.

7.3 적정 균형점 찾기

표 7-2, 7-3은 분해인과 통합인을 각각 정리해 보여줍니다.

Table 7-2. Disintegrator drivers (breaking apart a service)

Disintegrator driver Reason for applying driver
Service scope Single-purpose services with tight cohesion
Code volatility Agility (reduced testing scope and deployment risk)
Scalability Lower costs and faster responsiveness
Fault tolerance Better overall uptime
Security access Better security access control to certain functions
Extensibility Agility (ease of adding new functionality)

Table 7-3. Integrator drivers (putting services back together)

Integrator driver Reason for applying driver
Database transactions Data integrity and consistency
Workflow Fault tolerance, performance, and reliability
Shared code Maintainability
Data relationships Data integrity and correctness

아키텍트는 이 표를 바탕으로 트레이드오프 문장을 작성하고 프로덕트 오너 또는 프로젝트 스폰서와 함께 의논해 결론을 내릴 수 있습니다.

예제 시나리오 1

아키텍트: 출시 시기 vs 무결성 및 일관성
프로젝트 스폰서: 무결성 및 일관성

예제 시나리오 2

아키텍트: 데이터 일관성 vs 보안성
프로젝트 스폰서: 보안성

예제 시나리오 3

아키텍트: 확장성 vs 응답성
프로젝트 스폰서: 응답성

7.4 한빛가이버 사가: 티켓 배정 세분도

ADR: 티켓 배정 및 전달 서비스를 통합

Context

티켓이 생성되고 접수되면 전문 기사가 배정되며, 해당 기사의 모바일 앱으로 티켓을 전달해야 한다.
이 두 기능을 각각 별도의 서비스로 나눠 구현하거나, 티켓 배정 및 전달을 모두 처리하는 단일 통합 티케 배정 서비스를 구현해야 한다.

Decision

티켓 배정 및 전달 기능이 모두 포함된 단일 통합 티켓 배정 서비스를 구현한다.
티켓은 배정과 동시에 전문 기사에게 전달되므로 분질적으로 배정과 전달 두 기능은 서로 단단히 결합돼 있으며 상호 의존적이라 할 수 있다.
또 이 두 기능은 똑같이 확장을 해야 하므로 두 서비스 간에 처리량 차이가 없으며 배압back pressure 역시 필요 없다.
두 기능은 서로에게 완전히 의존하는 관계여서 내고장성 또한 두 기능을 분리하는 동인이 아니다.
두 기능을 개별 서비스로 만들면 두 서비스 간의 워크플로가 필요한데, 이는 나중에 성능, 내고장성, 신뢰성 측면에서 문제가 될 것이다.

Consequences

정기적으로 발생하는 전문 기사 배정 알고리즘 변경과 가끔 한번 발생하는 전달 메커니즘 변경이 일어나면, 두 기능 모두 테스트와 배포 작업이 필요하고 그에 따른 리스크는 불가피하다.

7.5 한빛가이버 사가: 고객 등록 세분도

그림 7-19는 세 가지 고객 등록 방안을 모두 나타낸 것입니다.

Image

Figure 7-19. Options for customer registration

논의를 통해 단일 통합 도메인 서비스로 결정한 ADR은 다음과 같습니다.

ADR: 고객 관련 기능을 통합 서비스로 구현

Context

고객이 한빛가이버 지원 플랜을 조회하려면 먼저 시스템에 고객 등록을 해야 하며, 그 과정에서 고객은 프로필 정보, 신용카드 정보, 패스워드정보, 그인한 제품 목록을 전달해야 한다.
이 요구사항은 단일 통합 고객 서비스로 구현할 수도 있고, 기능별로 서비스를 나눠 구현할 수도 있으며, 민감 데이터와 비민감 데이터 두 가지로 구분해 2개 서비스로 구현할 수도 있다.

Decision

프로필, 신용카드, 패스워드, 구입 제품 정보를 모두 처리하는 단일 통합 고객 서비스를 구축한다.
고객 등록과 가입 해지는 하나의 원자적 작업 단위로 처리해야 한다.
단일 서비스로 구현하면 ACID 트랜잭션을 걸 수 있어 문제가 없지만, 개별 서비스로 분리하면 하나의 원자적 작업 단위로는 처리할 수 없다.
API 레이어와 서비스 메시에서 토토이즈 보안 라이브러리를 사용하면 민감 정보에 대한 보안 리스크를 줄일 수 있다.

Consequences

API 게이트웨이와 서비스 메시 양쪽에 토토이즈 보안 라이브러리를 이용해서 보안 액세스를 적용한다.
프로필 정보, 신용카드, 패스워드, 구입 제품 정보를 관리하는 소스 코드가 변경될 경우, 단일 서비스이므로 테스트 항목이 많고 배포 리스크가 커진다.
결하된 기능은 한 단위로 확장돼야 한다.
프로덕트 오너, 보안 전문가와 만나 논의한 결과 트랜잭션이 먼저냐 보안이 먼저냐 하는 이야기를 했다.
고객 기능을 개별 서비스로 분리하면 보안은 가오하되나 고객 등록, 가입 해지 시 필수적인 모 아니면 도(all-or-nothing) 성격의 데이터베이스 트랜잭션은 지원되지 않는다.
그러나 토토이즈라는 커스텀 보안 라이브러리를 사용하면 보안에 대한 우려를 어느 정도 불식시킬 수 있다.

Metadata

Metadata

Assignees

Labels

2026Software Architecture: The Hard Parts소프트웨어 아키텍처: The Hard Parts - 분산 아키텍처를 위한 모던 트레이드오프 분석

Projects

Status

Done

Relationships

None yet

Development

No branches or pull requests

Issue actions