Kyle Brown (LinkedIn)

IBM Distinguished Engineer, CTO of Cloud Architecture, Cloud Lab Services

원문 (영어)

요즘 클라우드와 애자일 프로그래밍 커뮤니티에서는 모두들 마이크로서비스에 대해 이야기 하는 것 같습니다. 과거에는 REST같은 아키텍쳐가 개발자들의 마음을 사로잡았었고, 이제는 마이크로서비스가 새로운 강자로 떠오르고 있습니다. 마이크로서비스가 무엇이고 자바 개발자들은 왜 관심을 기울여야할까요?

이 시리즈에서 이에 대한 답을 하고, 앱을 왜 마이크로서비스로 마이그레이션 하는지 (1부) 설명하겠습니다. 데이터 리팩토링에 대한 내용은 2부에서 상세히 다루며, 마이크로서비스로의 마이그레이션을 하기위한 단계적 방법을 3부에서 알려드리겠습니다.

마이크로서비스는 무엇인가요?

마틴 파울러와 제임스 루이스가 작성한, 이제 고전이 된 논문에 마이크로서비스 아키텍처에 대한 가장 간단한 정의가 기술되어 있습니다: “하나의 아키텍처 접근 방식으로, 시스템은 작은 서비스들로 구성되어 구축되며, 각 서비스는 각자의 프로세스를 보유하고, 가벼운 프로토콜을 통해 통신한다”. 가장 핵심적인 개념은 각 마이크로서비스가 하나의 중복되지 않는 비지니스 기능을 나타낸다는 것입니다.

파울러는 마이크로서비스를 “제대로 된 서비스 지향”이라 언급하기도 하였습니다. 파울러와 루이스가 이야기하고, 다른 많은 사람들이 증명했듯이, 기업 컴퓨팅 부문은 그 결과가 안 좋은 대규모 SOA 프로젝트들로 어지럽혀져 있습니다. 마이크로서비스는 이런 추세를 되돌릴 수도 있을 것입니다. 우리는 마이크로서비스를 어디에 적용해야 하는 지를 알아야 하며, 좀 더 중요하게는 정리가 안 되거나 별로 좋지 않은 프로젝트에서 마이크로서비스가 효과가 있다는 점을 인식해야 합니다.

왜 마이크로서비스로 리팩토링을 하나요?

실리콘밸리의 많은 사람들의 믿음과는 다르게 모든 애플리케이션이 바닥부터 개발 가능한 것은 아닙니다. 기업은 많은 자바 코드와 개발자를 보유하고 있습니다. 따라서 모든 자바 코드를 버리고 새로운 런타임과 프로그래밍 언어로 새로 시작하는 것은 경제적으로 가능하지 않습니다.

그 대신 괜찮은 부분을 찾아 적합한 프레임워크 하에서 재사용하는 것이 더 좋습니다. 기존 앱을 마이크로서비스로 리팩토링하는 것이 현재의 시스템을 유지하면서 좀 더 적합한 개발 모델로 마이그레이션할 수 있는, 많은 경우 가장 좋고, 가장 현명한 방법입니다.

마이크로서비스로 어떻게 리팩토링 하나요?

프로그래밍 커뮤니티는 리팩토링을 “외부 동작을 유지하면서 하는 코드를 바꾸는 것”이라 정의합니다. 핵심은 외부 API를 동일하게 가져가면서 코드의 작동을 변경하거나 패키징을 변경하는 것입니다. 따라서 마이크로서비스로 리팩토링을 한다는 것은 앱이 하는 일은 변경하지 않고 마이크로서비스를 추가한다는 의미입니다. 앱에 새로운 기능을 추가하는 것은 아닙니다. 그러나 아마도 이것이 패키징 방식과, API의 노출방식은 바뀌게 됩니다.

마이크로서비스로의 리팩토링이 모든 앱에 다 적합한 것은 아니며, 또한 매번 성공할 수만은 없습니다. 그러나 리팩토링은 모든 것을 버릴 수 없을 때 한번 고려해볼 만한 가치는 있습니다. 세 가지 기본적 고려사항은 다음과 같습니다:

  • 앱이 어떻게 패키지 되었는가? (그리고 빌드되었는가?)
  • 앱 코드가 어떻게 작용하는가?
  • 백엔드 데이터 소스 구조가 다른 방식일 때 앱이 어떻게 데이터소스와 소통하는가?

 

Step 1. 앱을 새로 패키징하기

 

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

가장 좋은 시작점은 코드를 바꾸는 것이 아니고, 자바 앱의 패키징 구조를 보고 새로운 패키징 기법을 적용해보는 것입니다. 2000년대 초반, 로지컬 애플리케이션을 담기 위해 거대한 EAR 파일을 만들기 시작했습니다. 그리고 그 EAR 파일을 모든 웹스피어 애플리케이션 서버에 배포했습니다. 문제는 애플리케애션의 모든 코드가 같은 배포 스케줄과 같은 물리적 서버를 가진다는 데 있습니다. 무엇이라도 변경되면 모든 것들을 다시 테스트했어야 하고, 따라서 어떤 변경이라도 고려하기에는 그 경제적 대가가 너무 커졌습니다.

그러나 도커와 같은 컨테이너와, PaaS, 경량의 자바 서버인 Websphere Liberty 등을 통해 경제성이 향상되었으며, 따라서 패키징을 다시 고려해볼 수 있게 되었습니다. 다음의 세가지 원칙 적용을 시도해 보시기 바랍니다:

  1. EARs 분할하기: 모든 관련된 WARs를 하나의 EAR에 패키징하는 대신 분할하여 독립적인 WARs로 만듭니다. 이는 코드에 약간의 변화가 필요할 수도 있고, 또는 컨텐츠 루트를 변경할 경우 정적 컨텐츠에 일부 변경이 있을 수 있습니다.
  2. “서비스당 컨테이너” 원칙 적용: “서비스당 컨테이너” 패턴을 적용하고, 각각의 WAR를 각자의 Liberty 서버에 배포하는데 가급적 각자의 컨테이너 (예: 도커 컨테이너나 Bluemix의 인스턴트 런타임)내에 배포합니다. 그러면 각 컨테이너들은 독립적으로 확장 가능합니다.
  3. 독립적 구축, 배포 및 관리: 분리된 이후, 각각의 WAR를 자동화된 DevOps 파이프라인 (예: IBM DevOps Pipeline Service)을 통해 관리할 수 있습니다. 이를 통해 지속적 배포를 할 수 있는 장점이 있습니다.

세가지 원칙을 적용한 결과는 다음 그림과 같습니다:

fig1

 

Step 2. 코드 리팩토링하기

 

배포 전략이 독립 WAR 수준으로 내려 갔으므로, WAR들을 더 작은 레벨로 리팩터링 해 볼수도 있습니다. 다음과 같은 세가지 상황에서 코드를 리팩토링하여 독립적 마이크로서비스 패키징 방식으로 바꿔 볼 수 있습니다.

  • 1. 기존의 REST 또는 JMS 서비스: 리팩토링을 하기에 제일 쉬운 경우입니다. 기존 서비스가 이미 마이크로서비스 아키텍쳐에 적합하거나, 또는 적합하게 할 수 있는 경우입니다. 각 REST나 JMS 서비스를 WAR에서 분리해내고, 각 서비스를 각자의 WAR로 배포합니다. 이 단계에서 JAR 파일의 지원이 중복되는 것은 괜찮으며, 대부분 패키징의 문제입니다.
  • 2. 기존의 SOAP 또는 EJB 서비스: 기존 서비스가 있는 경우, 기능적 접근법 (예: Service Façade 패턴)으로 구현되었을 것입니다. 이 경우, 기능 기반 서비스 설계는 대부분의 경우 자산 기반 서비스 설계로 리팩토링이 가능합니다. 많은 경우 서비스 Façade 기능이 단일 객체에 대한 CRUD (생성, 검색, 업데이트, 삭제) 작업으로 써졌기 때문입니다. 만일 이렇다면, RESTful 인터페이스로 매핑하는 작업은 간단합니다: 단순히 EJB 세션의 빈(bean) 인터페이스를 JAX-WS 인터페이스로 다시 구현하면 됩니다. 이를 위해 개체 표현 방식을 JSON으로 변환해야 할 수 있습니다만 이는 일반적으로 별로 어렵지 않고, 특히 직렬화를 위해 JAX-B를 사용하는 경우는 용이합니다.

    만일 간단한 CRUD 작업 (예를 들어 계좌 이체)이 아닌 경우, 변형된 커맨드 패턴을 쓰는 RESTful 서비스 (예: /계정/이체와 같은 간단한 기능)를 구축할 때, 여러가지 다른 접근법을 사용할 수 있습니다.

    • 3. 간단한 서블릿/JSP 인터페이스: 많은 자바 프로그램은 데이터베이스 테이블에 연결되는 간단한 서블릿/JSP 프론트엔드입니다. “도메인 객체”가 없을 수 있으며, 특히 액티브 레코드 패턴 등의 디자인이었다면 더욱 그렇습니다. 이 경우 좋은 시작점은 먼저 도메인 계층을 생성하고, 이를 RESTful 서비스로 나타내는 것입니다. 도메인 주도 디자인 (Domain Driven Design)으로 도매인 객체를 식별한다면 누락된 도메인 서비스 계층을 파악하는데 도움이 됩니다. 구축을 완료하고 각 새로운 서비스를 각자의 WAR에 패키지 하면 신규 서비스 사용을 위해 기존의 서블릿/JSP 앱을 리팩토링하거나 완전히 새로운 인터페이스를 자바 스크립트, HTML5, CSS를 사용해 만들수 있으며, 또는 네이티브 모바일 앱을 구축할 수 있습니다.

 

Step 3. 데이터 리팩토링하기

 

앞의 세가지 경우에 정의된 작은 서비스의 구현과 리패키징을 완료하면, 마이크로서비스 도입에서 가장 어려운 부분이고 앱의 구축 기반인 데이터 구조를 리팩터링해야 합니다. 본 시리즈 2부에서는 이 부분을 더 깊게 다룰 예정이지만, 간단한 경우에 적용할 수 있는 3가지 규칙은 다음과 같습니다:

  1. 데이터 고립시키기: 사용하는 코드가 어떤 데이터베이스 테이블을 보는 지 살펴보는 것부터 시작합니다. 사용되는 테이블이 다른 테이블로부터 독립되어 있거나 몇개의 테이블을 조인하여 작고 고립된 “섬”같이 만들수 있다면 이를 전체에서 분리하기만 하면 됩니다. 분리가 완료되면 서비스에 어떤 옵션이 맞을 지 선택할 수 있습니다.

    예를 들어 SQL을 사용하는 경우, 기존오라클과 같은 무거운 기업용 데이터베이스에서 좀더 작고 자립적인 MySQL의 사용을 고려할 수 있을 것입니다. 또는 현재 SQL DB를 NoSQL DB로 바꾸는 것을 생각해 볼 수 있습니다. 이에 대한 답변은 데이터에 어떤 종류의 쿼리를 실행하는 가에 달려 있습니다. 대부분 기본 키 (Primary Key)에 대한 간단한 쿼리를 사용한다면 문서 DB나 키-밸류 (key-value) DB가 적합할 것입니다. 이와 반대로 복잡하게 결합되어 있는 쿼리들을 사용한다면 (예를 들어 어떤 쿼리를 사용하는 지가 예측 불가능하다면), SQL을 계속 사용하는 것이 최선의 선택일 것입니다.

  2. 배치로 데이터 업데이트 하기: 단지 몇 개의 관계만 있고 NoSQL DB로 이관을 결정했다면 현 DB에 배치로 업데이트가 가능한 지 살펴봅니다. 테이블간의 관계가 매번 최신으로 유지되지 않아도 된다면 시간을 고려하지 않아도 됩니다. 이런 많은 경우엔 몇 시간에 한번씩 데이터를 SQL 에서 덤프하고 NoSQL 에 로딩하는 배치를 돌리는 것으로 충분할 수 있습니다.
  3. 테이블 비정규화하기: 다른 테이블과 여러 개의 관계가 있을 경우 테이블을 비정규화 하는 방식으로 리팩토링을 할 수도 있습니다. 이런 이야기를 꺼내는 것만으로도 많은 DB 관리자은 신경이 곤두설 것입니다. 하지만 잠시 한 발 물러서서 애초에 데이터베이스 정규화가 왜 시작되었는지 생각해 봐야 합니다. 정규화는 중복을 없애 디스크 공간을 절약하기 위한 것이었으며, 예전에는 디스크 공간이 비쌌기 때문입니다. 그러나 이제는 상황이 달라졌습니다. 쿼리에 걸리는 시간 최적화가 중요하며, 비정규화가 이를 달성하기 위한 직접적인 방법입니다.

결론

마이크로서비스로 리팩토링하는 것이 무엇인지, 그리고 접근 방식을 선택할 때 어떤 요소를 고려해야 하는 지 살펴보았습니다. 좋은 소식은 코드를 리팩토링 하는 것은 생각만큼 어렵지는 않다는 것이고, 많은 경우 실제로 아주 간단합니다. 복잡한 케이스는 그리 흔치 않습니다.

본 시리즈 2부에서는 데이터 구조가 마이크로서비스를 앱에 적용하는데 있어 어떻게 영향을 미치는 지 좀 더 깊이 알아보겠습니다.

 

[출처] https://developer.ibm.com/kr/cloud/bluemix/2016/04/13/cl-refactor-microservices-bluemix-trs-1/