Design Patterns

마이크로 서비스패턴 - 13장. 마이크로서비스로 리펙터링

FreeEnd 2022. 4. 24. 15:48
반응형

이 블로그는 마이크로서비스패턴 (길벗) 책 내용을 스터디를 위해 정리 한 내용 입니다.

책구매는 바로가기 에서 구매 가능합니다.



"스트랭글러 애플리케이션(Strangler application), 모놀리스테어 서비스를 하나씩 추출해 마이크로 서비스에 새기능으로 구현, 스트랭글러 애플리케이션을 점점 키우며 모놀리스를 고사시킨다."

 

  이번장에서는 마이크로서비스로의 리팩터링의 필요성, 스트랭글러 애플리케이션 개방 방법, 모놀리스와의 연계등 다양한 주제를 훑어 본다.

 

마이크로서비스 리팩터링 개요

  마이크로서비스로의 리팩터링은 모놀리식이 형편 없어서 가 아니라, 모놀리식 지옥에 빠져 갖가지 소프트웨어 개발 문제가 발생하기 때문이다.

 

모놀리스를 왜 리팩터링 하는가?

 마이크로서비스 아키텍처는 유지보수성, 테스트성, 확장성등 우수한 점이 많으나, 모놀리스를 마이크로서비스로 전환 시에 개발 리소스가 분산되는 어려움이 있다.

 

모놀리스의 지옥에 빠진 상황이라면 다음과 같은 문제점을 겪고 있을것이다.

  • 느린전달 : 애플리케이션을 이해,관리, 테스트가 어려워 생산성이 떨어짐
  • 버그투성이 소프트웨어 릴리즈 : 테스트가 결여되어 버그가 많이 발생됨
  • 나쁜 확장성 : 모듈간의 연관성이 깊어 확장하기 어려움

 느린전달의 이유는 개발 프로세스가 낙후됨이고, 버그는 수동테스트에 의존하므로 자동테스트를 도입해 개선 가능하다.

모놀리식도 개선 가능한 점이 많으므로, 간단한 방안 부터 시행해보고 그래도 문제가 해결되지 않을 경우 마이크로서비스로 전환을 고려 해야 한다.

 

모놀리스 옥죄기

 마이크로서비스로의 전환시, 완전히 뜯어 고치는 일은 리스크가 커서 실패할 확률이 많다.

마이크로서비스 전환시 수개월, 수년이 걸리는데, 이 기간에 레거시 시스템의 개발도 병행 해야 함으로, 에너지가 분산되고 목표는 점점 멀어질 것이다. 때문에 단계적 리팩토링이 필요하다. 바로 레거시 애플리케이션과 함께 실행 되면서 새로운 마이크로 서비스를 조금씩 빌드한 스트랭글러 애플리케이션을 개발 하는 것이다. 

 

이 리팩터링 작업은 수개월 ~ 수년은 족히 걸린다. 아마존닷컴은 2년이 걸렸다. 이 작업이 걸림돌이 된다면 그냥 모놀리식으로 서비스 해도 괜찮다.

 

값을 조기에 자주 검증

단계적 리팩터링은 투자에 따른 보상이 즉시적이다. 때문에 조금씩 리팩터링 하면서 새 기술스택 도입과 데브옵스 스타일로 개발/전달 하면 효과적이다. 

 애플리케이션에서 가장 가치가 큰 부분을 먼저 이전하면 더 효과적이다. 핵심 로직을 먼저 추출해 마이크로서비스로 리팩터링 하면, 해당 로직은 다른 서비스와 별개로 독립적으로 개발/수행 가능하며 그만큼 개발 속도는 더 빨리진다.

 이런 과정에서 가치가 일찍 전달되면서 경영진에게도 어필이 가능하다.

 

모놀리스 변경사항을 최소화

 모놀리스를 광범위하게 뜯어 고치는것은 시간도 많이 걸리고, 비싸고 위험하다.

 

기술 배포 인프라 : 모든것이 다 필요한 것은 아니다.

 모든 인프라를 초기에 한꺼번에 투자 할 필요는 없다. 처음에는 테스트자동화 배포 파이프라인은 꼭 필요하다. 나머지는 이후 경험해보고 필요시 결정하라.

 

 

모놀리스 -> 마이크로서비스 리팩터링 전략

마이크로서비스로 전환하는 3대 전략에 대해 소개한다.

 

1) 새 기능을 서비스로 구현한다

 모놀리식을 마이크로로 전환할때, 새 기능을 마이크로서비스로 구현 하는것 부터 진행한다. 모놀리스에 새 기능을 추가하게 되면 비대해지고, 관리하기 어려워진다. 

 

새 서비스를 모놀리스에 연계

새 서비스와 모놀리스를 통합하는 두개의 요소는 다음과 같다.

  • API게이트웨이 : 새 기능은 새 서비스로, 구 기능은 모놀리스로 라우팅
  • 통합 글루 코드 : 새 서비스가 모놀리스 데이터 / 기능에 접근 할 수 있도록 통합글루를 구현한다.

통합 글루 코드는 스탠드어론 컴포너트가 아닌, IPC 를 이용한 서비스이다. 

 

 

새기능을 서비스로 구현하는 시점

다음의 경우에 대해서는 새 서비스로 구현하기 어려울 수 있다.

  • 의미있는 서비스라고 하기에는 기능 자체가 너무 작은 경우
  • 새기능이 기존 모놀리스코드에 너무 단단히 매여있는 경우
  • 데이터 일관성을 보장 하기 힘든 경우

 

2) 표현계층과 백엔드를 분리한다

 표현 계층을 비즈니스 로직과 데이터 접근 계층에서 분리하면서 덩치를 줄인다.

 

엔터프라이즈 애플리케이션의 세가지 계층은 다음과 같다.

  • 표현계층 : HTTP 요청을 처리해 웹 UI에 전달할 HTML 을 생성하는 모듈
  • 비즈니스 로직 : 복잡한 비즈니스 규칙이 구현된 모듈
  • 데이터 접근로직 : DB, 메시지 브로커등 인프라 서비스에 접근하는 모듈

비즈니스 로직에는 하나 이상의 퍼사드로 구성된 API 가 존재하는데 이 API 가 바로 모놀리스를 작은 단위로 쪼갤 수 있는 부분이다. 그러므로 표현계층과, 비즈니스 로직 이렇게 두개로 나눌 수 있다. 

 이렇게 나누면, 두 시스템이 독립적으로 개발, 배포될 수 있다. 또 나중에 개발 할 마이크로서비스가 호출 할 수 있는 원격 API 가 구분된다.

 

3) 기능을 여러 서비스로 추출한다

모놀리스를 서비스로 추출해야 할 기능은 다음 네덩이이다.

  • API 끝점이 구현된 인바운드 어댑터
  • 도메인로직
  • DB접근 로직 등이 구현된 아웃바운드 어댑터
  • 모놀리스의 DB 스키마

모놀리스에서 코드를 추출해 스탠드어론 서비스로 이전한다. API 게이트웨이는 요청에 따라, 새 기능은 마이크로 서비스로, 나머지는 모놀리스로 각각 라우팅한다. 

 모놀리스에서 서비스를 추출해가는 과정은 시간이 많이 걸린다. 따라서 신중하게 결정 해야 한다. 

새 서비스로 추출시 가장 중요하고 계속 발전하는 서비스를 먼저 추출하는 것이 좋다.

 

도메인 모델 분리

 서비스 경계에 있는 객체 레퍼런스를 분리하는것은 어렵다. 또, 모놀리스에 잔류한 클래스가 이전한 클래스를 찾거나, 이전한 클래스가 모놀리스를 참조할 수 있다. 이 문제를 해결하기 위해서는 DDD 의 애그리거트 관점으로 생각해야 한다. 애그리거트는 기본키로 서로를 참조하기때문이다.

 

DB 리팩터링

도메인 모델의 클래스는 대부분 필드가 DB스키마에 매핑 되어있다. 떄문에 데이터도 함께 이전해야 한다.

또, 엔티티를 나누려면 DB테이블도 분리 해야 한다.  가령 ORDER 테이블이라면 Delivery 엔티티를 추출해서 DELIVERY 테이블로 나눈다. 그런 다음 DELIVERY 테이블과 함께 Delivery 서비스로 이전한다.

 

변경 범위를 줄이기 위해 데이터를 복제

DB 리팩터링에서 가장 큰 장애물은, DB를 참조하고 있는 클라이언트가 새 스키마를 사용하도록 전체 변경하는 일이다. 이런 작업은 영향범위도 크고, 작업량도 많아서 한번에 작업하기 힘들대. 때문에, 전이 기간동안 원본 스키마를 유지하고 원본 스키마와 신규 스키마를 동기화 하는 트리거 등을 사용 한다. 그러면 작업시간도 보장 받을 수 있고, 조금씩 새 스키마로 이동 할 수 있다.

 

어떤 서비스를 언제 추출하나

 시간별로 구획된 아키텍처 정의부터 시작하는 것이 좋다. 1~2주 정도 짧은 시간을 들여 어떤 아키텍처가 이상적인지 생각해보고, 알맞는 서비스를 정의한다.  그런뒤, 본격적으로 모놀리스를 나눈다.

 

 다음은 서비스 추출 순서를 정하는 전략 두가지이다.

첫번째, 모놀리스의 개발을 동결하고, 요건이 있을 때마다 서비스를 추출하는 것이다. 모놀리스의 버그를 잡지 말고, 필요한 서비스를 추출해서 고쳐 개발하는것이다.

 

두번째, 서비스 추출시 기대되는 혜특을 모듈별로 순위를 매기는 것이다. 다음은 추출하는것이 도 이로운 경우이다.

  • 개발가속화 : 개발 일정상, 개발 분량이 많을 것으로 예상 되는 파트를 서비스로 전환한다.
  • 성능, 확장성, 신뢰성 문제해결 : 성능, 확장성에 문제가 있거나 의심스러운 부분은 전환 할 만한 가치가 있다.
  • 다른 서비스로 추출할 수 있게 만듦 : 먼저 추출하면 디펜던시상 다른 서비스분리가 단순해지는 경우.

 

서비스와 모놀리스간 협동 설계

 마이크로서비스 전환과정에서 모놀리스와 서비스는 협동하며 수행된다. 서비스가 모놀리스에 접근하거나, 모놀리스가 서비스에 접근해 필요한 데이터를 가지고 와야 한다. 여기서 중요한건 두 서비스간에 데이터 일관성 유지이다. 두 서비스는 SAGA 로 데이터 일관성을 맞추어야 한다.

 

 서비스와 모놀리스간의 상호작용은 통합 글루코드가 관장한다. 

 

통합 글루 설계

통합 글루코드는 모놀리스와 서비스 양쪽에 구성한다. 한쪽에는 REST 클라이언트, 다른 한쪽에는 웹 컨트롤러를 만들어 통합 글루를 구성한다. 

 

통합 글루 API 설계

 통합 글루를 설계시 도메인 로직에 어떤 API 를 제공할지 결정 해야 한다. 

서비스의 비즈니스 로직은 통합 글루가 어떤 IPC 로 데이터를 가지고 오는지 알 필요가 없으므로, IPC를 인터페이스로 캡슐화 하여 제공한다. 모놀리스의 비즈니스 로직은 통합글루가 어떻게 구현했든 알필요 없이 단순히 호출해 사용하면 된다.

 

상호 작용 스타일과 IPC 선택

 통합 글루에서 중요한건 모놀리스와 서비스가 협동할 수 있게 해주는 상호작용 스타일과 IPC를 선택하는 일이다.

한쪽에 데이터가 필요해 조회 해야 한다면 레포지토리를 구현한 어댑터로 데이터 제공 시스템의 API 를 호출한다.

조회API 를 이용하는 방식은 단순해 편리하지만 요청 개수가 많아지면 효율이 나빠진다. 그래서 일반적으로 API 는 잘 사용하지 않는다. 

 때문에 데이터 레플리카 (CQRS 뷰) 를 사용한다. 각 시스템의 이벤트를 구독해 각 데이터를 개별 DB에 관리해 조회를 하면 프로바이더를 반복해서 조회하는 쿼리 오버해드를 줄일 수 있다. 단, 모놀리스에 이벤트 발행 로직을 개발하는것은 쉽지 않다.

 수정시에도 SAGA 나 이벤트를 사용해 양쪽 데이터베이스의 일관성을 유지한다.

 

부패-방지 계층 구현

 마이크로서비스를 완전히 새로 개발 할 경우, 전혀 다른 클래스명, 필드명, 필드값등을 가지게 될 것이다. 모놀리스와 서비스가 서로 소통하려면 DDD 에서 말하는 ACL(Anti-Corruption Layer, 부패-방지 계층)  을 구현 해야한다.

 

 부패-방지 계층은 상이한 두 도메인이 상대방을 더럽히지 않도록 변환해주는 소프트웨어 계층이다.

ACL 은 모놀리스의 도메인이 서비스 도메인을 더럽히지 못하게 하는데 목적이 있다. 때문에 ACL 을 구현한 클래스는 모놀리스의 언어와 서비스의 공용언어를 변환 해주어야 한다.

 모놀리스를 REST로 호출하는 서비스는 호출하는 요청값과 응답값을 ACL 로 변환 해줘야 하며, 모놀리스의 이벤트를 구독하는 서비스도 역시 ACL 로 적절히 언어를 변환 해야 한다.

 

모놀리스가 도메인 이벤트를 발행/구독하는 방법

 모놀리스가 이벤트를 발행하는 방법은 두가지이다.

  • 서비스가 사용하는것과 동일한 이벤트 발행 장치를 적용하는 방법
    엔티티를 변경하는 코드를 모두 찾아내어 이벤트 발행 API 를 삽입 하는 방식이다. 하지만 이렇게 모든 변경코드를 찾아내기는 쉽지 않고, Stored Procdure 로 구현된 로직은 이벤트 발행 자체가 불가능하다.

  • DB수준에서 이벤트를 발행 하는 방법
    트랜잭션 로그 테일링 (DB의 트랜잭션 로그를 분석해 데이터를 연동하는 방식. CDC 등) 이나 폴링을 사용한다. 이 방법은 간편하지만, DB수준에서만 이벤트를 발행할 뿐이고, 고수준의 비즈니스 이벤트는 발행 하기 어렵다.

 

서비스와 모놀리스에 걸쳐 데이터 일관성 유지

 서비스와 모놀리스가 데이터의 일관성을 유지하는 것은 상당히 어렵다. 두 서비스가 모두 수정작업이 이루어지기때문이다. 더군다나 모노리스에 사가로직을 추가하는 것은 어려운 일이다. 

 하지만, 모놀리스 로직이 피봇 트랜잭션이거나, 재시도 가능 트랜잭션 이면 어렵지 않다. 또, 보상 트랜잭션을 수행 할 필요가 없도록 피봇 트랜잭션 이후로 순서를 조절하면 된다.

 

보상 트랜잭션을 지원하도록 모놀리스를 고치기는 어렵다.

기존 모놀리스를 마이크로서비스 보다 먼저 호출 하는 프로세스의 경우 모놀리스 로직에 상태값을 추가하여, 서비스 로직이 오류가 발생했을 경우 보상 트랜잭션으로 모노리스의 상태값을 수정해야한다. 이렇게 되면 모놀리스의 프로세스를 수정하게 되고, 관련 로직을 모두 고쳐야 할텐데, 시간도 많이 걸릴 뿐더러 개발 리소스도 낭비가 되게 된다. 

 

사가 적용시 모놀리스는 보상 트랜잭션을 지원할 필요가 없다.

 서비스 추출시에 모놀리스에 보상 트랜잭션을 구현할 필요가 없는 사가를 설계하는 것도 가능한다. 모놀리스 트랜잭션이 사가의 피봇 트랜잭션이 되게 하면 된다. 

 

서비스 추출 순서를 조정하면 보상 트랜잭션을 모놀리스에 구현하지 않아도 된다.

 

인증/인가 처리

 모놀리스에 적용된 보안 메커니즘이 마이크로서비스에 맞게 조정 되어야 한다. 

모놀리스의 로그인 핸들러를 약간만 수정하면 된다. 모놀리스의 로그인 핸들러가 인증후 쿠키정보를 반환하면, 브라우저에서는 이후 요청에 이 쿠키를 포함해 전송한다. 이때, API 게이트웨이는 쿠키를 검증후 Authorization 요청 헤더에 넣어 마이크로 서비스를 호출한다. 각 서비스는 쿠키 검증후 사용자 정보를 추출한다.

 

 

 

 

새기능을 서비스로 구현: 배달 실패한 주문처리

배달 지연 서비스 설계

배달 지연 서비스를 위한 통합 글루 설계

 

모놀리스 분해: 배달 관리 추출

현행배달 관리 기능

배달 서비스 개요

배달 서비스의 도메인 모델 설계

배달 서비스의 통합 글루 설계

배달 서비스와 상호 작용할 수 있게 모놀리스를 변경

 

반응형