API 설계 원칙

모든 API 는 크게 두가지 종류로 구분할 수 있다. 하나는 “정보를 조회”하는 목적의 API 이고, 하나는 “정보를 변경(생성)”하는 API 이다. 전자는 Query  method라고 하고, 후자는 Command method라고 부른다.

API를 설계할 때 이 두 가지 기준에 따라 적용되어야 하는 원칙(principle)같은 것이 있다. 당연하겠지만 Query method는 조회 결과에 해당하는 리턴값이 존재하겠지만, Command  method는 리턴값이 없는 void 타입으로 정의하자는 것이다. (HTTP (Rest) API 호출에서 Http Status 값은 해당 API의 리턴값으로 취급하지 않는다.)

 

Command method의 리턴값이 void가 아니게 되면 당장에는 큰 문제가 없을지도 모르지만, 향후에 성능이슈로 인해 해당 API를 비동기로 전환하게 되면 API를 사용하는 측에서는 그 변경에 대한 영향이 만만치 않다. 특히 많은 서비스로 분리되어 있는  MSA환경에서는 비동기로 API를 전환하고 기존 API 를 deprecate시키지 못하고 계속 끌고 가야 하는 상황이 생기게 된다.

 

그렇다면 void 타입의 Command method API를 호출하고 그 결과가 필요할 때는 어떻게 해야 할까?

 

첫 번째로 생각할 수 있는 방법은 callback 방식이다.  Requestor가 요청하는 command  message에 처리 결과를 받을 callback address 를 포함해서 요청하고, replier가 해당 요청을 처리한 후 callback address 에 결과를 push 해 주는 방법이다.

 

두 번째 방법은, EIP에서 제시하는 Request & Reply Pattern을 사용하는 것이다. ( http://www.enterpriseintegrationpatterns.com/patterns/messaging/RequestReply.html ) 간단하게 말하면 요청과 응답을 모두 message event로 전달하는 방식이다. 이 방법을 조금 확장하면 requestor 와 replier가 모두 publisher, subscriber 가 되는 EDA기반의 CQS 패턴을 생각해 볼 수 있게 된다. Publisher 는 subscriber가 누구이건 관심없이 이벤트를 발행하고, 관심있는 subscriber가 이를 구독해 가되, 자신에게 필요한 이벤트를 필터링 할 수 있는 Pipe & Filter 패턴을 함께 사용하는 것이다.

 

자칫하면 오히려 시스템의 복잡성이 커질 것 같기는 하지만, 관심영역을 잘 분리해 낼 수 있다면 효과적으로 단순함을 유지시킬 수도 있을 것 같다.

 

지금 사내의 복잡한 monolithic legacy 시스템을 micro service 로 전환하는 작업을 하고 있는데 아키텍처 스타일로 여러가지를 고민하고 있다. 이벤트 기반의 비동기 방식을 큰 줄기로 가져가고 싶은데 구세력의 저항이 만만치 않다. ㅋㅋ

RESTful API Design 하기

오늘 페이스북에 누군가가 올린  “RESTFul is dead… ” 라고 시작하는 자극적인 글이 보여 눈길이 갔다. 뭔가 새로운 방식이라도 나왔나? 하는 마음에 읽어봤는데.. 2014년도 10월 경에 InfoQ 에 올라왔던 글에 대한 개인적인 생각을 올려놓으신 듯 하다. ( 다시 찾아보니 찾을 수가 없음 )

원글은 : https://www.infoq.com/articles/web-api-rest

여튼, 그래서 나는 RESTful API 설계에 대해 어떤 생각을 가지고 있는지 잠깐 고민하는 시간을 가질 수 있게 되었다.

사실 원문위 주요 내용은 real restful way 를 구현하기 위해 RESTful  API 을 설계하면서 고려해야 할 사항과, 피해야 하는 사항들을 몇가지 예를 들어 설명하고 그 중, Programtic 방식이란 것에 대해서 설명하고 추천하는 내용인 것 같다.

사실 나도 RESTful API를 설계하면서 지키려고 노력하는 나만의 원칙이 몇가지 있다.

  1. 리소스와 행위를 명시적이고 직관적으로 분리하기.
    • 리소스는 URI로 표현되는데 리소그가 가르키는 것은 명사(noun)이어야 한다.
      ( http://mysite.com/books/1)
    • 행위는 Httpd Method 로 표현하고, POST(생성), PUT(기존 entity 전체를 변경), DELETE(삭제), PATCH(기존 entity의 일부 상태만 변경), GET(조회) 을 분명한 목적으로 사용한다.
  2.  Message 는 Header 와 Body 를 명확하게 분리해서 사용 ( 메시지 추상화를 포함한..)
    • Entity 에 대한 내용은 body에 담고, 어플리케이션 서버가 행동할 판단의 근거가 되는 컨트롤 정보는 header에 담는다.( API의 버전 정보, 응답받고자 하는 MIME 타입 등..)
    • 여기에서 header 와 body는 httpd header 와 http body 로 나눌수도 있고, http body에 들어가는 json 구조를 분리할수도 있다. 어떤 환경에서는 요청이 지나는 hop 중에 하나가 http header 정보를 다 지우고 자기의 정보로 채우는 것도 있어서 그런 상황이라면 http header에 중요한 컨트롤 정보를 담는것이 위험할 수도 있다.
  3. API 버전을 관리한다.(되도록 하위 호환성을 확보한다)
    • 어찌 보면 당연한 얘기겠지만, 영원불멸한 것은 없고 환경은 항상 변하기 때문에 API 에 signature도 변경될 수 있다.
    • 내가 hosting 하고 있는 서비스에 디펜던시를 가지고 있는 모든 시스템이 나의 변경계획에 따라 모두 같이 변화할 수 없기 때문에 내가 특정 API를 변경할 때에는 반드시 하위호환성을 보장해야 한다. ( 이렇게 하다보면 결과적으로 불용자원-더 이상 사용하지 않는 API- 을 어떠한 기준으로 판단해서 제거해야 하는지에 대한 고민도 뒤따라온다.)
  4. 서버와 front client가 같은 방식을 사용해서 요청하도록 한다.
    • 예를 들어 브라우저는 form submit으로 보내고, 서버에서는 json  형태로 보내는 식으로 분리하지 말고, front client 와 서버 모두가 form 으로 보내거나,  json 으로 보내든 방식은 하나로 통일한다.
    • 당연한 얘기지만 이게 지켜지지 않고 있는 환경을 많이 봤다.
      당연한 얘기라고 생각했지만 환경마다 사람마다 선호하는 방식이 있겠다는 생각이 든다. 스프링을 사용한다면 스프링에서 요청 Entity 의 속성을 자동 바인딩 해주니 사실 이 부분은 form 과 json 모두를 그 환경에서 편한 방식으로 사용해도 괜찮을 것 같다는 생각이 든다.
    • 하지만 위 2번 항목에서 JSON으로 메시지 구조를 추상화 한다면 JSON 방식으로 통합하는 것도 고려해야 할 문제다.
  5. API 명세 문서는 항상 자동으로 현행화 할 수 있는 방안을 마련해 놓는다.
    • 프로젝트를 빌드할 때나, 혹은 주기적으로 batch 스케줄러를 돌리는 방법을 활용하던 API 명세에 대한 문서는 항상 자동으로 현행화 상태를 유지할 수 있게 한다. 이걸 사람이 수작업으로 하게 한다면 거의 100% 현행화 되지 않고, 곧 쓸모없는 문서가 되어 버린다.
    • 문서는 API release 용과, 개발 staging 용이 구분되어서 관리되어야 한다.

이 외에도 여러가지 원칙들이 있겠지만, 나는 적어도 위 5가지는 API 설계를 하면서 가급적 최 우선적으로 지키려고 노력하는 항목들이다.

위에서 언급한 InfoQ 의 아티클에서 제시하는 내용중 위 5가지와 겹치는 부분도 있지만, 중요하게 언급하고 있는 내용은 이른바 “Progmatic Rest” 방식이다.

예를 들어 아래 “ORDER”라는 서비스에 대한 응답 메시지의 형태를 보자.

<pre><code><?xml version="1.0" encoding="UTF-8"?>
<serviceDescription xml:base="http://om.example.com">
   		<link rel="all" href="/orders/" />
   		<link rel="received" href="/orders/received/" />
   		<link rel="accepted" href="/orders/accepted/" />
   		<link rel="rejected" href="/orders/rejected/" />
   		<link rel="cancelled" href="/orders/cancelled/" />
   		<link rel="fulfilled" href="http://om.archive.com/orders/" />
   		<link rel="cancellations" href="/cancellations/" />
   		<link rel="reports" href="/reports/" />
</serviceDescription>
   { "serviceDescription" : {
      "base": ",
      "links": [
         { "rel": "all", "href": "/orders/" },
         { "rel": "received", "href": "/orders/received/" },
         { "rel": "accepted", "href": "/orders/accepted/" },
         { "rel": "rejected", "href": "/orders/rejected/" },
         { "rel": "cancelled", "href": "/orders/cancelled/" },
         { "rel": "fulfilled", "href": "/orders/fulfilled/" },
         { "rel": "cancellations", "href": "/cancellations/" },
         { "rel": "reports", "href": "/reports/" }
      ]
      }
}</code></pre>

응답형태가 매우 복잡하게 보이지만,  해당 서비스가 어떤 resource를 제공하는지 그 목록을 리소스 URI가 포함된 서비스 문서 ( 웹 서비스의 홈페이지 같은..)로  제공하자는 것이다. 이렇게 하면 Client는 특정 resource 주소를 알기 위해 사전에 다른 웹 서비스를 호출하거나, 클라이언트 코드에 리소스 URI에 대한 하드코딩을 하지 않아도 되고, 결론적으로 서비스 제공자가 어떤 이유에 의해서건 provider 입장에서 서비스 URI 변경에 대한 부담이 적어진다는 이야기 인것 같다.

하지만, 저런 형태의 서비스 description 을 제공한다고 해도 과연 URI 를 직접 하드코딩 하는 경우가 없어질지 의문이다.  아니, 모든 클라이언트가 URI를 하드코딩하지 않는다는 확식이 있으니  API URI를 내 임의로 바꿀 수 있을까? 엄청 통제가 잘 되고 커뮤니케이션이 잘 이루어 지고 있는 조직이라면 어쩌면 가능할 수도 있겠지만, 여태까지 내가 경험했던 모든 조직에서는 사실 불가능하다고 생각이 든다.오히려 저런 서비스 description 페이지가 현행화 되지 않아 혼란을 주는 경우가 생기지는 않을까?

특정 서비스(리소스)의 위치(URI)를 동적으로 제공할 필요가 있거나, 특정 기준(예를 들어 버전)에 따라 라우팅을 해야할 필요가 있다면,  API Gateway Pattern 을 사용하거나,  NetFlix 의 Eureka 같은 제품을 이용한 Service Registry Pattern 이용해서 서비스를 관리하는 것도 고려해 볼 만하다.

MSA 에서 분산된 원격 서비스간 트랜잭션 관리 방법

MSA 에서 분산된 원격 서비스간 트랜잭션 관리 방법

지난 주말에 KSUG세미나에서 최범균 님의 “몇가지 도메인 구현 이야기” 강연 중 이벤트 부분 ( http://www.slideshare.net/madvirus )을 들으면서 한동안 고민하던 MSA 환경에서 분산된 서비스간의 트랜잭션 관리를 위한 방법으로 활용하면 좋겠다는 생각이 들어서 생각을 정리해 본다.

굳이 꼭 MSA라는 거창한 단어를  갖다 붙이지 않더라도, 서로 다른 두개의 플랫폼간에 서비스들을 orchestration하여 사용할 때 트랜잭션 처리가 정말 만만치 않다. 특히 내가 일하고 있는 금융 도메인에서는 이 트랜잭션이라는 것이 비니지스 프로세스에 굉장히 중요한 부분을 차지하고 있기 때문에 이 주제를 가지고 architecturing하는 것이 간단한 문제가 아니라고 생각한다.

MSA라는 아키텍처 패러다임이 근래에 많이 회자되고 있고, 누구는 buzzword라고도 얘기하고.. 자칫 이 주제에 대해서 잘못 얘기했다가 물어 뜯기는 사람들을 많이 봐와서 섣불리 얘기를 꺼내기 쉽지 않지만.. 뭐 나같은 사람이 하는 얘기 누가 깊게 관심가질까 하는 마음에 간단히 정리해 보고자 한다.

MSA의 특성을 얘기할 때 개인적으로 제일 중요하고 어려운것이라고 생각하는 것이 마틴파울러가 MSA의 9가지 특성을 얘기하면서 언급하는 “Decentralized Data Management”이다.

(마틴파울러가 얘기하는 9가지 특성은 “이렇게 해야한다”가 아니고 MSA를 하다보면 이러한 특성을 갖게 된다고 라고 이해하는 것이 옳다. 자세한 얘기는 나중에 따로 다루도록 하는 것으로…)

DDM에 대해서 이해를 하려면 그 반대 개념인 CDM ( Centralized Data Management )부터 이해할 필요가 있다. 사실 CDM 이라는 단어보다 ‘트랜잭션 관리’라고 얘기하는 것이 훨씬 친숙할 것이라고 생각된다. 아주 작은 어플리케이션을 개발하더라도 트랜잭션 없이 개발하는 것은 거의 사실상 불가능하지 않을까. 아주 오래전이야 이 트랜잭션에 대한 제어를 개발자가 한땀한땀 코딩을 해서 더 어려운 점이 있었지만 요즘에서야 프레임워크에서 알아서 다 해주니 비지니스의 무결성을 보장해 주는 좋은 도구인 이 트랜잭션관리 기능을 쓰지 않을 이유가 없다.

예를 들면 쇼핑몰에서 물건을 구입하려고 했을 때 상품을 장바구니에 담고 결제를 시도하게 되는데 이 때 어떤 이유에서는 결제하는 과정에서 오류가 발생하게 된다면 상품의 재고를 하나 복원(롤백)시켜야 할 필요가 있다. 이렇게 결제하는 기능과 상품의 재고를 하나 감소시키는 것은 하나의 트랜잭션으로 묶어서 관리하게 되고 이런 것을 DDM 의 반대 개념으로 생각해 볼 수 있다.

최근 MSA가 이슈가 되면서 위에 예시로 든 “결제” , 기능과 “상품” 기능은 서로 다른 팀에서 각각의 컴포넌트로 개발되어 독립된 환경에 배포되어 서비스하는 것을 미덕으로 여기고 있다.

이때 중요한 것이 MSA의 특성 중 하나인 DDM을 적용하게 되는데, 이렇게 한다는 것은 곧 물리적인 DB도 따로 분리하는 것이 좋고 당연히 두개의 어플리케이션 간에는 의존성을 제거해야 마땅하다.

에릭에반스가 DDD에서 얘기하는 Bounded Context도 이러한 개념이다.

자, 그럼 다 좋은데 이 상황에서 우리가 앞에서 얘기했던 결제가 성공하면 상품의 재고를 하나 감소시키고, 실패했을 때 상품의 재고를 하나 증가(롤백)시키는 것은 어떻게 해야 할까? 결제와 상품 두개의 서비스는 서로 언어도 통일되어 있지 않을 가능성도 있고 DB도 서로 다른 제품을 사용할 가능성이 많다(Polyglot). 당연히 하나의 트랜잭션으로 묶는 것을 구현하기 쉽지 않다. 사실 거의 불가능한다.

이런 경우 MSA전문가들이 제시하는 “원론적인” 몇몇 얘기들 중 하나가 보상 트랜잭션을 만들어야 한다는 것이다. ( 첨부터 트랜잭션으로 묶을 필요가 없도록 서비스를 디자인 하라 라고 하는 것은 개인적으로 현실과 너무 동떨어진 얘기인 것 같고, XA같은 것을 이용해서 이기종 분산 DB를 트랜잭션으로 묶어보라고 하는 것도 실제 XA를 적용하면서 얼마나 고생하는지 잘 봐온 터라 아직은 별로 고려하고 싶지 않다. 더구나 최근에는 DB같은 인프라스트럭쳐에도 의존관계를 되도록 느슨하게 가져가는 것이 권장되고 있다.)

즉, 보상 트랜잭션(compensating operations)이 무슨 얘기인고 하니, 상품쪽 서비스에서는 상품의 재고를 하나 감소시키는 API를 제공해다고 하면 이에 대한 보상 트랜잭션으로 상품의 재고를 하나 증가시는 API를 제공하는 것이고, 결제가 실패하는 경우 이 보상 트랜잭션을 호출하라는 것이다.

일견하면 API를 두개씩 (혹은 두배로) 만들어야 하니 귀찮기는 해도 MSA로 얻어지는 장점을 생각한다면 썩 괜찮은 방법인것도 같다.

하지만 실제 구현을 하려다 보면 상품의 재고를 하나 증가시키는 보상트랜잭션을 호출하다가 오류가 나면 어떻게 하지? 라는 상황을 만나게 된다.뭐 억지로 한다라고 하면 로그를 모아놓고 배치처리를 하는 방법도 있고, 성공할때까지 계속 호출하는(헉.. 정말로?)방법도 있을 수 있다.하지만 뭐든 별로 우아하지 않아 보인다.

자! 그럼 이렇게 하나의 트랜잭션을 묶고 싶은 분산된 서비스간의 보상 트랜잭션을 우아하게 처리하려면 어떻게 해야 할까? 이렇게 하나로 묶고 싶은 두개의 기능이 꼭 “실시간” 으로 동기화 처리 되지 않아도 된다고 하면 “역활과 책임”의 관점에서 썩 괜찮은 방법이 있다. (사실 이런 경우 수초의 지연은 사용자에게 거의 실시간으로 받아들여지기도 한다.)

“역할과 책임”의 관점에 볼때, 위 예시에서 보면 결제라고 하는 상위 서비스에서 상품이라고 하는 하위 서비스를 사용하고 있는데, 상품의 재고를 늘리고 줄이는 것과 그 과정에서 발생할 수 있는 오류를 적절하게 처리하는 것을  상품 서비스의 역할과 책임이라고 하자.  ( 좀 야비한가? 뭐 세상이 다 그런거 아니겠는가? )

그럼 “결제”의 입장에서 어떤 방식으로 결제가 성공하거나 실패했을 때 상품의 재고량에 대한 역할과 책임을 상품이 갖게 할 수 있을까?

“결제” 어플리케이션에서는 “결제”에 같이 묶고자 하는 다른 서비스 ( 재고 감소, 포인트 적립 등..) 트랜잭션에 대한 이벤트를 “결제” 의 boundary안에 있는 DB에 저장하고 (여기까지는 결제트랜잭션과 하나의 트랜잭션으로 묶어야 한다), DB에 저장된 이벤트들을 “상품”서비스에 전달해 주던지, 가져하게 하는 것이다. 이렇게 발생한 하위 이벤트(상품 재고, 포인트 적립등)들을 “결제”서비스의 context안에 저장하는 것 까지는 “결제”서비스의 역할과 책임으로 보고 저장된 이벤트들을 실제로 처리하는 역할과 책임은 상품이나 결제의 하위 서비스들이 가져가게 하자.

이렇게 “결제” boundary에 쌓이게 되는 이벤트들을 “상품”이 처리하게 하는 방법은 두가지로 생각해 볼 수 있다. 먼저 “결제”가 “상품”에게 이벤트를 전달하는 방식과- event forwarder-  “상품”이 “결제”로 부터 이벤트를 가져하는 방식-event diapatcher-으로 생각해 볼 수 있다.

먼저, 첫번째 방식은 “결제”가 “상품”에게 이벤트를 전달하는 방식은 “결제”이벤트가 발생할 때마다 “상품”에게 이벤트를 전달함으로써 실시간에 가깝게 처리될 수 있고 상태관리를 하지 않아도 된다는 장점이 있지만, 앞서 고민했던 “결제”가 “상품”에게 이벤트를 전달하다가 실패하는 경우 “결제”입장에서 어떻게 처리해야 할지 고민이 될 수 밖에 없다.

두번째 방법은 “상품”이 “결제”로부터 주기적으로 이벤트의 목록을 요청해서 받아가는 방식인데, “상품”이 이벤트를 어디까지 처리했는지 상태관리를 할 필요가 있고, 오류가 발생한 경우 재시도 정책등을 “상품”에서 가져갈 수 있는 장점이 있다. 상태 관리를 할 필요가 있다는 말은 “상품”이 어디까지 이벤트를 처리했는지 알고 있어야 하고 “결제”에 요청할 때는 처리하고자 하는 범위(인덱스)를 같이 말해줘야 한다.

즉, “14987번 이후에 발생한 모든 이벤트를 주세요”  또는 “5000번 이후에 발생한 이벤트 10개를 주세요” 나 ,실패한 것을 재처리 힐 필요가 있다면 “14876번 이벤트를 주세요”라고 요청 할 수 있다.

어떻게 보면 “결제”라는 서비스를 위해 “상품”이라는 서비스가 너무 많은 일을 해야 하는 것이 아닌가 라고 생각될 수 있지만, “상품의 재고량에 대한 정확한 정보를 전달”하는 역할과 책임은 “결제” 서비스가 아닌 “상품”서비스에 있다고 볼 수 있다.

실제 적용하게 되면 또 어떤 다른 문제가 생길지 모르지만 ,  이벤트들을 저장하는 형태(데이터 구조)나, 이벤트를 가져가고 전달해 주는 것들에 대한 표준을 정하고, 공통된 기능으로 만들어 사용하면 꽤나 유용하게 사용될 것 같은 생각이 든다.

참고한 사이트

http://www.slideshare.net/madvirus

http://martinfowler.com/articles/microservices.html

Docker 에 대한 생각

최근의 S/W환경을 보면 서비스는 점점 더 복잡해 지고 있고, 기술도 다양하고, 외부 환경도 급격하게 변화하고 있다.전통적인 Monolithic Architecture 환경에서는 이런 변화에 발빠르게 대응하는 것이 사실상 불가능 하다고 볼 수 있다. 이런 복잡하고도 빠르게 변하는 환경에 대응하기 위해 많은 시도들이 있어 왔으며, 최근에는 MSA(Micro Service Architecture)라는 사상이 주목을 받고 있다.

MSA라는 것은 독립적이고 단순한 서비스로 전체 서비스를 구성하고, 그에 따라 독립적인 팀이 각 서비스의 주인(개발과 운영에 대한 책임,권한을 모두 가짐)이 되는 것을 말한다.(마틴 파울러 아저씨의 블로그http://martinfowler.com/articles/microservices.html 에 보면 MSA의 개념에 대해 잘 정리가 되어 있다.) 이와 같이 서비스를 micro화 하게 되면 , 분산환경을 통한 scale-out 확장에도 용이하고 monolithic 방식에 비해 배포에 대한 부담이 줄어든다(전체 서비스에 영향을 주지 않고 배포를 할 수 있음). 장애가 발생해도 그 영향의 범위를 국소적으로 제한시킬수 있게 된다.

이렇게 MSA 사상을 도입하면 복잡성과 빠른 변화에 대응하는 것은 가능해 지지만 그에 비해 변경 및 추가된 어플리케이션을 build하고 테스트하고, 배포(ship)하고, 실행시키는 것은 쉬운 문제는 아니다.

분산 환경에 배포되어 있는 많은 machine들이 모두 동일한 환경을 가지고 있으리라는 보장이 없기 때문에 어플리케이션을 분산환경에 배포를 하기 위해서는 환경정보를 잘 확인하고 배포를 해야 한다. 자칫하다가는 장애를 만들어 내기 일쑤다. 개발환경과 테스트 환경, 운영환경이 모두 다를 것이기 때문에 어플리케이션이 동일하게 동작하리라는 보장을 하기가 힘들어 진다.

이런 배경에서 “어디에서나 동일하게 동작하는 소스코드”를 제공하기 위해서 등장한 기술이 Docker라고 볼 수 있다. Docker는 개발자가 개발한 어떤 어플리케이션이든(언어에 관계없이) 그 어플리케이션과  그 어플리케이션이 돌아가게 하는 최소한의 디펜던시만 Container에 담아 shipping할 수 있게 한다. 이런 작업을 “Dockerized”라고 부르게 되는데, 이렇게 Dockerized가 되면 어떤 환경에서도 – OSX, Windows, Redhat, Ubunto, 노트북, 서버, 클라우드 등- 동일하게 동작하는 것을 보장할 수 있다. 구글에서 돌아가는 모든 어플리케이션은 모두 container에 담겨져 있으면 이 Container갯수만 약 20억개 정도 된다고 한다.

이런 개념은 얼핏 보면 VMs과 별로 다르지 않은 것처럼 보이기도 하는데 아래 그림을 보면 그 차이를 이해할 수 있다.

개인 PC에서 개발하면서 개발환경에 웹서버, was, DB등을 설치해 본 경험이 있을 것이다. 당연하지만 이 개발환경에서의 path라든지 각종 시스템 파라미터 등은 테스트 환경이나 운영환경에서와 다를 수 밖에 없으므로 이런 환경의 차이로 인해 “내 환경에서만 동작하는 어플리케이션”이라는 비아냥을 받게 될때도 있을 것이다.

Docker를 이용하여 개발에 필요한 환경들을 모두 Container에 담아, 그 이미지를 QA에게 전달해서 테스트를 부탁할 수 있다. 테스트가 완료가 되면 그 이미지를 그대로 운영환경에 배포해서 바로 서비스를 시작 할수 있게 된다. Docker 이미지는 Git 처럼 버전관리가 가능하기 때문에 장애가 발생했을 경우 이전 버전으로 쉽게 롤백도 가능하다.

Docker Hub(hub.docker.com)에 가보면 여러가지 Docker 이미지가 공개되어 있는 것을 확인해 볼 수 있다. MySql 를 비롯해서 Redis, NginX 등 많은 제품들이 Docker Container로 제공되고 있으며 이 이미지들을 사용한다면 여러분의 노트북에서나 클라우드 환경의 서버에서나 동일하게 동작하는 것을 보장받을 수 있을 것이다.

Docker를 사용하기 위해서는 JVM처럼 각 플랫폼마다 별도로 제공되는 Docker플랫폼을 받아서 설치를 해야 한다. https://docs.docker.com/installation/#installation 에 보면 지원하고 있는 플랫폼을 확인해 볼 수 있는데, Amazon EC2와 Google Cloud Platform 이 눈에 띈다.

Docker 이미지를 Docker Hub에서 내려받고(pull), 수정내용을 반영(commit)하고 다시 Docker Hub로 올리는(Push) 과정은 Git과 거의 동일하다고 볼 수 있다.

Docker 에 패키징되어 있는 어플리케이션은 docker라는 명령을 통해 접근할 수 있으며 가이드 https://docs.docker.com/userguide/ 를 통해 사용방법은 크게 어렵지 않게 익힐수 있을거라 보여진다.

Docker로 인해 앞으로 S/W개발의 많은 모습들이 달라지게 되지 않을까 하는 생각이 든다.