Skip to content

CHAPTER 8 Reuse Patterns #1622

@jongfeel

Description

@jongfeel

CHAPTER 8 재사용 패턴

모놀리식 아키텍처와 달리 분산 아키텍처에서 공유 기능을 다룰 때는 여러 요소를 두루 살펴야 하므로 신경 쓸 일이 많습니다. (그림 8-1)

Image

Figure 8-1. Code reuse is a hard part of distributed architecture

분산 아키텍처에서 코드 재사용을 줄여야 하지만, 소프트웨어 개발에서 코드 재사용은 엄연한 현실이므로 반드시 짚고 넘어가야 할 주제입니다.

8.1 코드 복제

그림 8-2처럼 공유 코드를 각 서비스에 복사하면 공유 코드를 방지할 수 있습니다.
기발한 발상처럼 보이지만 실제로는 그렇지 않습니다.

Image

Figure 8-2. With replication, shared functionality is copied into each service

요즘은 코드 복제code replication을 많이 쓰지 않지만, 이 방법은 아직도 분산된 여러 서비스 간의 코드 재사용 문제를 해결하기 위해 쓰입니다.

대부분의 서비스에서 필요한 극히 정적인 일회성 코드는 복제하는 것이 유용할 수 있습니다.
예제 8-1의 자바 코드는 서비스 진입점에 해당하는 클래스입니다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ServiceEntrypoint { }

/* Usage:
@ServiceEntrypoint
public class PaymentServiceAPI {
   ...
}
*/

실제로 아무 기능도 안 하는 코드이지만, 간단한 애너테이션만으로 서비스 타입, 도메인, 경계 콘텍스트 등 서비스의 갖가지 메타데이터 애너테이션을 쉽게 정의할 수 있습니다.
이런 종류의 소스 코드는 정적이고 버그가 전혀 없기 때문에 복제하기에 좋습니다.

코드를 복제하면 경계 콘텍스트는 보존할 수 있지만, 코드 수정이 필요할 때 수정된 코드를 적용하기가 어렵습니다.
표 8-1은 코드 복제 기법의 트레이드오프를 정리했습니다.

Table 8-1. Trade-offs for the code replication technique

Advantages Disadvantages
Preserves the bounded context Difficult to apply code changes
No code sharing Code inconsistency across services
No versioning capabilities across services

8.1.1 어떤 경우에 사용하는가?

코드 복제는 결함이나 기능 변경 때문에 변경될 일이 거의 없는 일회성 클래스 또는 단순 정적 코드(애너케이션, 애트리뷰트, 단순 공통 유틸리티 등)에 적합합니다.
모놀리식 아키텍처에서 분산 아키텍처로 전환할 때 코드 복제 기법은 정적 공통 유틸리티 클래스를 대상으로 적용하는 게 좋습니다.

8.2 공유 라이브러리

공유 라이브러리shared library는 컴파일 타임에 서비스에 바인딩 되고 다른 여러 서비스가 사용하는 소스 코드가 담긴 외부 아티팩트 입니다.
(그림 8-3)

Image

Figure 8-3. With the shared library technique, common code is consolidated and shared at compile time

8.2.1 디펜던시 관리와 변경 관리Dependency management and change control

그림 8-4에서 단일 공유 라이브러리를 사용하므로 디펜던시 관리가 비교적 간단하지만 변경 관리는 그렇지 않습니다.
공유 라이브러리의 클래스 파일을 변경한다면 옛날 버전의 공유 라이브러리는 언젠가 구식화deprecation해 변경할 수 밖에 없습니다.
그러면 공유 라이브러리를 사용하는 모든 서비스를 다시 테스트하고 배포해야 하며, 전체 테스트 범위가 크게 증가합니다.

Image

Figure 8-4. Changes to coarse-grained shared libraries impact multiple services but keep dependencies low

그림 8-5와 같이 공유 라이브러리를 기능별로 더 작은 단위의 라이브러리로 세분화하면 변경 관리와 유지 보수성은 조금 나아지겠지만 디펜던시 관리가 지저분해지기 쉽습니다.

Image

Figure 8-5. Changes to fine-grained shared libraries impact fewer services but increase dependencies

변경 관리와 디펜던시 관리의 트레이드오프가 있으므로 단위가 큰 공유 라이브러리는 삼가해야 합니다.
디펜던시 관리 보다는 변경 관리에 더 신경을 쓰는 편이 낫습니다.

8.2.2 버저닝 전략

공유 라이브러리는 무조건 버저닝을 하는 것이 상책입니다.
하위 호환성 보장, 높은 수준의 민첩성을 달성해 신속한 변화 대응이 가능하기 때문입니다.

공유 라이브러리는 가능한 한 세분화해 관리하되, 전체 시스템 공유 기능이 구현된, 단위가 큰 라이브러리는 지양해야 합니다.

표8-2는 이 기법의 트레이드오프를 정리한 것입니다.

Table 8-2. Trade-offs for the shared library technique

Advantages Disadvantages
Ability to version changes Dependencies can be difficult to manage
Shared code is compile-based, reducing runtime errors Code duplication in heterogeneous codebases
Good agility for code shared code changes Version deprecation can be difficult
Version communication can be difficult

8.2.3 어떤 경우에 사용하는가?

공유 코드의 변경 빈도가 비교적 낮은 안정된 환경에 적합합니다.
버저닝은 공유 코드 변경 시 높은 수준의 민첩성을 제공합니다.
컴파일 타임 바인딩이므로 성능, 확장성, 내고장성 등의 운영 특성에 영향을 끼치지 않습니다.

8.3 공유 서비스

그림 8-6과 같이 공유 서비스를 사용하면 개별 배포된 서비스에 공통 기능을 둬서 재사용을 방지할 수 있습니다.

Image

Figure 8-6. With the shared service technique, common functionality is made available at runtime through separate services

이 기법의 특징은 공유 코드가 상속inheritance이 아니라 조합composition의 형태여야 합니다.
이 문제는 아키텍처 관점에서 코드 재사용 기법에서 공유 서비스 기법을 설명할 때 의미가 있습니다.

8.3.1 변경 리스크

공유 기능을 변경하는 일은 양날의 검과 같습니다.
그림 8-7과 같이 각 서비스에 포함된 공유 코드를 수정해 서비스를 재배포하면 공유 기능 변경 작업이 끝납니다.

Image

Figure 8-7. Shared functionality changes are isolated to only the shared service

공유 서비스는 런타임 변경이 이뤄지는게 문제이므로 그림 8-8과 같이 전체 시스템이 문제가 생길 가능성이 존재합니다.

Image

Figure 8-8. Changes to a shared service can break other services at runtime

예제 8-3과 같이 API 엔드포인트를 버저닝 하는 방법이 있습니다.

Example 8-3. Discount calendar with versioning for shared service endpoint

app/1.0/discountcalc?orderid=123
app/1.1/discountcalc?orderid=123
app/1.2/discountcalc?orderid=123
app/1.3/discountcalc?orderid=123
latest change -> app/1.4/discountcalc?orderid=123

하지만 이 방법의 문제는 다음과 같습니다.

  • 할인 계산기 서비스를 사용하는 서비스가 정확한 버전을 가리키도록 바꿔야 함
  • 새 API 엔드포인트를 만들어야 하는 시점은 주관적임

또 다른 문제는 REST API를 가정했지만 메시징이나 gRPC등다른 프로토콜을 이용하는 방법도 있을 수 있습니다.
변경 관리를 하려면 버저닝 전략이 복잡해질 수 밖에 없습니다.

8.3.2 성능

서비스 호출이므로 네트워크 레이턴시가 발생하고 그로 인해 성능이 떨어집니다. (그림 8-9)

Image

Figure 8-9. Shared service introduces network and security latency

gRPC를 사용하면 네트워크 레이턴시를 줄일 수 있습니다.

8.3.3 확장성

또 다른 단점으로 서비스 사이즈에 맞게 공유 서비스도 사이징 해야 한다는 점입니다. (그림 8-10)

Image

Figure 8-10. Shared services must scale as dependent services scale

8.3.4 내고장성

그림 8-11과 같이 공유 서비스가 다운되면 재가동될 때까지 다른 서비스들의 작동이 멈추게 됩니다.

Image

Figure 8-11. Shared services introduce fault-tolerance issues

공유 서비스 기법은 경계 콘텍스트를 유지하면서 자주 변경되는 공유 코드에는 적합하지만
성능, 확장성, 가용성 등의 운영 특성 측면에서는 부정적입니다.
표 8-3은 이 기법의 트레이드오프를 정리한 것입니다.

Table 8-3. Trade-offs for the shared service technique

Advantages Disadvantages
Good for high code volatility Versioning changes can be difficult
No code duplication in heterogeneous codebases Performance is impacted due to latency
Preserves the bounded context Fault tolerance and availability issues due to service dependency
No static code sharing Scalability and throughput issues due to service dependency
Increased risk due to runtime changes

8.3.5 어떤 경우에 사용하는가?

고도의 폴리글랏 환경이나 공유 기능의변경 빈도가 높은 경우에 유용합니다.
공유 라이브러리 보다는 좀 더 민첩하게 반영할 수 있지만 공유 기능을 필요로 하는 서비스의 런타입 부수 효과의 리스크는 신중하게 따져볼 필요가 있습니다.

8.4 사이드카와 서비스 메시

마이크로서비스 아키텍처에서 디커플링을 위해 구현 중복을 어느 정도 수용했는데 공통 기능에 대해 처리하기 위해 알리스테어 코크번Alistair Cockburn이 육각형 아키텍처hexagonal architecture라고 명명한 아키텍처 패턴에 기반한 사이드카 패턴sidecar pattern을 제시했습니다.

Image

Figure 8-12. The Hexagonal pattern separated domain logic from technical coupling

이 패턴은 아미크로서비스 전에 등장했지만 데이터 충실도를 제외하고 비슷한 부분이 많습니다.
육각형 아키텍처에서 데이터베이스는 플러그인 가능한 어댑터인데 도메인 주도 설계 사상에 따라 데이터 스키마와 트랜잭션성은 내부에 있어야 합니다.
사이드카 패턴은 기술(인프라) 로직과 도메인 로직을 분리한다는, 육각형 아키텍처와 동일한 개념을 응용한 것입니다.

사이드카의 구현부는 팀별로 공유하거나 별도의 인프라 팀이 관리합니다.
아키텍트가 모든 서비스에 사이드카를 두리고 했다면, 서비스 플레인을 통해 부착된 사이드카가 모든 서비스를 통틀어 일관된 운영 인터페이스 역할을 합니다. (그림 8-14)

Image

Figure 8-14. When each microservice includes a common component, architects can establish links between them for consistent control

모든 서비스가 사이드카 컴포넌트를 갖고 있다면 그림 8-15와 같은 서비스 메시 구조가 됩니다.
각 서비스 우측에 있는 사이드카 박스끼리 서로 연결되 메시가 형성되는 것입니다.

Image

Figure 8-15. A service mesh is an operational link among services

사이드카 패턴을 잘 활용하면 관리자 그룹이 폴리글랏 환경이 과도하게 발생하지 않도록 제약할 수 있습니다.
서비스 메시의 일관성을 이용해 여러 종류의 플랫폼에 대해 인프라 및 각종 횡단 관심사를 지원하는 경우가 많습니다.
사이드카 패턴은 도메인에서 운영 기능을 디커플링하는 좋은 방법이며, 특정 종류의 커플링을 해소할수 있는 직교적 재사용 패턴orthogonal reuse pattern 입니다.
마이크로서비스 아키텍처는 도메인 중심으로 구성되지만 운영 커플링은 모든 도메인에 두루 적용되므로, 사이드카 패턴을 이용하면 아키텍처 레이어를 넘나드는 횡단 관심사를 따로 일관되게 격리할 수 있습니다.

표 8-4와 같이 사이드카 패턴은 멋진 추상화이지만 트레이드오프는 있습니다.

Table 8-4. Trade-offs for the Sidecar pattern / service mesh technique

Advantages Disadvantages
Offers a consistent way to create isolated coupling Must implement a sidecar per platform
Allows consistent infrastructure coordination Sidecar component may grow large/complex
Ownership per team, centralized, or some combination

8.4.1 어떤 경우에 사용하는가?

사이드카 패턴과 서비스 메시는 분산 아키텍처에서 횡단 관심사를 고루 퍼뜨리는 깔끔한 방법으로 GoF의 디자인 패턴에 나오는 데코레이터 패턴과 아키텍처 개념은동일합니다.

8.5 한빛가이버 사가: 공통 인프라 로직

ADR: 운영 커플링에 대해 사이드카 패턴 적용

Context

우리 마이크로서비스 아키텍처의 각 서비스는 공통의 일관된 운영 기능을 필요로 한다.
하지만 그렇다고 각 팀에 책임을 떠넘기면 비일관성 및 조정 이슈가 생길 것이다.

Decision

서비스 메시 형태의 사이드카 컴포넌트를 사용해서 공통적인 운영 커플링을 통합한다.
공유 인프라 팀은 서비스 팀에 제공할 사이드카를 소유/관리한다.
즉, 서비스 팀은 공유 인프라 팀의 고객인 셈이다.
사이드카는 다음과 같은 서비스를 제공한다.

  • 모니터링
  • 로깅
  • 서비스 디스커버리
  • 인증
  • 인가

Consequences

각 팀은 부적절한 커플링을 일으키지 않도록 도메인 클래스를 사이드카에 추가하지 않는다.
서비스 팀은 각자 공유 인프라 팀에 요청해 공통 운영 라이브러리를 사이드카에 배포한다.

8.6 코드 재사용: 어떤 경우에 가치 있는가?

대형 보험회사의 여러 팀은 관심 있어 하는 고객 정보가 다릅니다.
하지만 전사 고객 정보를 한 서비스로 그림 8-17과 같이 통합했습니다.

Image

Figure 8-17. Unifying on a centralized Customer service

언뜻 보면 합리적인 것 같지만, 아키텍처적으로는 두 가지 이유에서 완벽한 실패입니다.

첫째, Customer 같은 핵심 엔티티에 관한 모든 사내 정보를 한 곳에 두면, 모든 도메인과 시나리오를 처리하게 돼 그만큼 복잡해지고 결과적으로 아주 단순한 기능조차 사용하기 어려워질 것입니다.
둘째, 아키텍처가 취약해집니다. 어느 한 팀이 변경되면 모든 팀이 문제가 생깁니다.

재사용에는 두 가지 중요한 측면이 있습니다.
첫 번째는 추상화입니다. 아키텍트와 개발자는 주로 추상화를 통해 재사용하기에 알맞은 후보를 물색합니다.
두 번째는 변경 빈도입니다. 얼마나 자주 바뀌는지에 따라 재사용의 효용과 가치가 결정됩니다.

NOTE
재사용은 추상화를 통해 발생하지만, 변경 빈도가 낮을 때 가치가 있습니다.

8.6.1 플랫폼 기반의 재사용

대부분 플랫폼이 사내 재사용 타겟이라는점에 동의합니다.
하지만 낮은 변경 빈도가 아니면 타당하지 않습니다.
API는 호출부와 아주 느슨하게 결합하도록 설계할 수 있기 때문에 API를 깨뜨리지 않고도 내부적으로 상세 구현을 엄청나게 자주 바꿀 수 있습니다.

8.7 한빛가이버 사가: 공유 도메인 기능

Image

Figure 8-18. Option using a shared Ticket Data service for common database logic for the Sysops Squad ticketing services

Image

Figure 8-19. Option using a shared library for common database logic for the Sysops Squad ticketing services

ADR: 공통 티케팅 데이터베이스 로직은 공유 라이브러리로 구현

Context

티케팅 기능은 티켓 생성, 티켓 배정, 티켓 완료라는 세 서비스로 나눌 수 있는데, 이들 모두 데이터베이스 조회/수정 로직을 대부분 공통 코드로 처리한다.
이는 공유 라이브러리와 공유 데이터 서비스, 두 가지 방법으로 구현할 수 있다.

Decision

공통 티케팅 데이터베이스 로직은 공유 라이브러리를 사용해 구현한다.
공유 라이브러리를 쓰면 고객 대면 서비스인 티켓 생성 및 배정 서비스의 성능, 확장성, 내고장성을 높일 수 있다.
우리가 확인한 결과, 공통 데이터베이스 로직이 구현된 코드는 변경 빈도가 비교적 낮은 상당히 안정된 코드이고, 서비스는 어차피 테스트와 재배포가 필요하므로 일반적인 데이터베이스 로직은 변경 리스크가 낮은 편이다.
공통 데이터베이스 로직의 변경이 필요한 경우, 모든 서비스를 재배포할 필요가 없도록 라이브러리를 적절히 버저닝하면 된다.
공유 라이브러리를 사용하면 서비스 커플링과 디펜던시를 낮추고 HTTP 트래픽과 전체 대역폭을 절약할 수 있다.

Consequences

공유 DLL 파일에 구현된 공통 데이터베이스 로직이 변경되면 티켓 서비스의 테스트 및 재배포가 불가피하므로 티케팅 기능의 민첩성은 전반적으로 떨어질 것이다.
각 서비스 인스턴스는 알아서 데이터베이스 커넥션 풀을 관리해야 할 것이다.

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