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 이용해서 서비스를 관리하는 것도 고려해 볼 만하다.

JPA Entity 는 왜 빈(empty) 생성자를 필요로 하는가?

jpa를 이용하여 엔티티를 작성하는 경우 간혹 비어있는 생성자를 만들어야 하는 경우가 있다.여기서 말하는 “간혹”이라는 경우는, 사용자가 자바에서 제공하는 기본 생성자를 사용하지 않고,생성자를 재정의 하는 경우인데, 이 때 재 정의하는 생성자가 파라미터를 필요로 하는 경우이다.


public Member(long memberId) {

this.memberId = memberId;
....
}

이렇게 생성자를 재정의 해 놓게 되면, 하이버네이트 등의 프레임워크에서 리플렉션을 이용해 해당 객체를 생성할 때 해당 객체(엔티티)의 기본 생성자를 호출하게 되는데 위와 같이 파라미터가 필요한 생성자로 재정의 한다면

Class<T>.newInstance

를 이용해서 해당 엔티티의 객체를 만들어 낼 수 없게 된다.

그래서, 사용자가 파라미터가 필요한 생성자를 재정의 하는 경우에는 반드시 파라미터가 없는 기본 생성자를 제공해 주어야 한다.

참고로 스프링 데이터를 사용하는 경우라면 위와 같은 파라미터가 필요한 생성자를 재정의하더라도 @PersistenceConstructor 어노테이션을 재정의한 생성자에 붙이게 되면 굳이 비어있는 생성자를 호출하지 않아도 된다.

@PersistenceConstructor
public Member(Long memberId) {
this.memberId = memberId;

….
}
참고로 jpa 2.0 스펙에 다음과 같이 명시되어 있다.

“엔티티는 반드시 파라미터가 없는 (no-arg) public 또는 protected 생성자가 있어야 한다.”

비난하기

비난하기
대학에 다니던 시절에 꽤 오랜시간을 일식집 주방에서 파트타이머로 근무 했었다. 일하던 기간동안 주방장이 몇 차례 바뀌었는데 이상하게도 새로 들어온 주방장은 꼭 이전 주방장이 만들어 놓았던 소스류의 맛이나 주방 시스템 체계등을 비난하면서 일을 시작하는 것이다. 
계속 근무하고 있던 입장인 나는 이전 주방장과 더 친밀한 관계를 가지고 있는데 그런 비난을 듣고 있자니 여간 불편한게 아니었다. 
더구나 비난의 대상이 되는 주방 시스템(일하는 체계)은 몇년동안 해당 환경에서 시행착오를 거치면서 최적화 된 시스템이었고, 새로 막 들어온 사람이라면 그 배경을 모르고서는 이해할 수 없는 여러가지 복잡한 이유를 바탕으로 구축된 체계였으며 비난하던 당사자도 결국에는 당신이 비난하던 그 비슷한 모습으로 시스템을 변화시켜 나가고는 했다.

 

지금 나는 소프트웨어 아키텍트로 일하고 있는데, 거의 대부분의 경우 먼저 만들어져 있는 아키텍처 기반에서 작업을 하는 경우가 많다. (새로운 주방에서 일을 시작하는 주방장처럼..)
보통 많은 아키텍트(나를 포함)들이 신규 사이트에 들어오면서 기존(as-is)의 아키텍처를 부정(비난)하는 것으로 부터 일을 시작하는 경우를 많이 본다.
모든 경우에 그런 것은 아니겠지만, 그렇게 함으로서 자신의 능력이 더욱 돋보일거라 생각하는 측면이 없지는 않을 것이다. 
얼마전 우연하게  근무하던 전 직장에서 수행한 프로젝트의 아키텍를 비난하는 얘기를 들은적이 있다. 

그 당시에는 내가 역량이 많이 부족하기 했지만, 사실 비난 받았던 이유는 그런 측면이 아니었다. 내가 그 시스템을 구축하던 당시의 상황과 배경정보를 전혀 고려하지 않고 현재의 상황에 대입해서 비난을 하고 있다고 생각하니 화도 많이 나기도 했지만 억울하기도 한 생각이 들었다.
그러면서 들었던 생각은, 그 동안 내가 비난했던 많은 레거시들도 지금의 그 모습을 갖게 되기 까지는 그 때 당시의 당면한 많은 문제들을 해결하고, 수많은 이해 관계 당사자들의 요구사항을 만족시키기 위해 나보다 훨씬 훌륭한 사람들이 깊은 고민을 한 결과일지도 모른다는 것이다. 
그래서 되도록 이미 만들어진 어떤 결과를 평할 때에는 지금의 기준에 비추어 보아 부족한 부분이 많이 보이더라도, 무턱데고 비난하기 보다는 그 모습을 갖게 된 그 당시의 시대적(?)상황을 이해해 보려고 노력하는 편이다. (하지만 아무리 그러려고 해도 도저히 이해하지 못할 경우도 많이 경험하곤 한다. -_-)
전임 주방장의 고충을 잘 이해하고 나면, 훨씬 더 훌륭한 주방장이 될 수도 있지 않을까? 혹은 내 비난이 부메랑이 될 수도 있을 거라는 그런 생각이 드는 오늘이다.

Single Responsebility Principle

오늘 DZONE에서 온 뉴스레터에 SRP(Single Responsibility Principle) 에 대한 언급이 있었는데 개인적으로는 조금 신선한 내용이었습니다.
아티클에서는 SRP 의 Responsibility 가 무엇이냐를 얘기하고 있는거 같은데 그 내용중, 내가 구현한 Class나 Method가 SRP를 준수하고 있는지 알 수 있는 방법을 소개하고 있습니다.
클래스나 매서드가 SRP를 준수하고 있는지 알려면 그 클래스나 매서드에 이름을 붙여보라고 얘기하고 있는데, 간결하게 이름을 붙이기 쉽다면 그 클래스나 매서드는 SRP를 준수하고 있을 가능성이 높고 그렇지 않다면 담당하고 있는 기능이 복잡해서 그럴것이라는 의견입니다.
어찌보면 당연한 얘기처럼 들리기도 하지만, 이렇게 작명행위를 통해 내가 지금 설계의 원칙을 잘 유지하고 있는지 여부를 쉽게 확인할 수 있겠다는 접근은 저에게는 신선하네요. 🙂

 

원본링크

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

객체지향의 사실과 오해

요즘 조영호 님의 “객체지향의 사실과 오해”라는 책을 보고 있는데, 아직 중간정도 밖에 보고 있지않지만 책 내용중 어떤 부분때문에 좀 당혹스러웠다. 
사실 이 책은 토비님이 조영호님과 인터뷰하는 동영상을 보고 “객체지향은 현실을 모방하는게 아니고 은유한 것이다”라는 내용에 호기심이 생겨 구매하여 읽게되었다. 
그동안 내가 직접 모델링을 하거나, 후배들에게 모델링에 대해서 교육을 할때도 현실 세계를 모방하려했고 또 그렇게 가르쳐왔던 터라 내가 그동안 잘못 알고 있었던가라는 생각에 책을 구입해서 자세히 읽어봐야겠다라는 생각이 들었다. 
일단 책을 읽으면서 든 생각은 책에서 얘기하는 내용이 너무 좋아 한번 감탄하고, 저자분의 뛰어난 식견과 지식의 깊이에 대해 다시 놀라고, 객체지향이라는 주제에 대서 본인의 사상적틀을 완성한것으로 보아 정말 대가임에 틀림없구나 하는 생각이 들었다. (사실 부끄럽지만 토비님 동영상을 보기전까지는 저자분에 대해서 잘 모르고 있었음)

일단 결론부터 얘기하자면 나는 여전히 객체를 모델링할때 “현실세계를 모방”할 것이고 후배들에게도 그렇게 하도록 조언할 것이다. 
책에서는 전화기는 스스로 통화버튼을 누를 수 없고, 계좌는 스스로 이체를 할 수 없으므로 객체로 표현된 전화기나 계좌는 현실세계를 모방한게 아니고 조금 참조해서 세로운 세계를 창조한 것이라고 얘기한다. 그런 면에서 볼때 현실세계를 표현하는 객체는 현실세계를 모방하는 것이 아니고 은유하는 것이라고 말한다. 
나는 이렇게 단정하는 것에는 문제가 있다고 생각한다. 

그 이유를 말하기 전에 먼저 저자께서 현실세계와 객체간의 관계를 표현하는데 사용된 두가지 개념인 “모방” 과 “은유”에 대해서 그 의미를 명확하게 할 필요가 있다. 
먼저, 나는 객체는 현실세계를 은유해서 표현한다는 의견에 동의할수가 없다. 
책에서도 얘기하고 있지만 은유는 “실제로 적용되지 않는 한 가지 개념을 이용해 다른 개념을 서술하는 대화의 한 형태”라고 정의할 수 있다. 예를 들어 “내 마음은 호수요”, “부장님은 화나면 완전 호랑이야”라고 얘기하는 것은 “마음과 호수”, “부장님과 호랑이”는 사실 아무 관계도 없지만 호수와 호랑이의 대표적 속성을 차용해서 내 마음과 부장님의 성격을 좀 더 이해하기 쉽게 표현하려고 한 것이다. 
소프트웨어를 만드는 우리가 쉽게 접하는 은유(메타포)로 표효하는 대표적인 것들이 있는데, 바로 socket, client, server, shell 등과 같은 것들이다. 이렇게 표현함으로 해서 그러한 것들이 무엇을 하는 것인지 구구절절 설명하지 않아도 적절한 은유덕분에 그 의미를 파악하는게 어렵지 않다. 
하지만 이것들이 현실세계의 소켓과 노예와 고객, 껍데기등을 객체로 표현한 것은 아니지 않은가?
객체는 오히려 현실세계의 모습에 상당히 많은 부분을 의존하고 있으면 어플리케이션(시스템)이라는 세계에서만 가능한 특별한 몇몇 능력을 더 부여받을 수 있는 것이라고 생각한다. 
여기에서 말하는 “현실세계에 많은 부분을 의존하고” 있는 것이 바로 현실세계를 모방하는 것이라고 나는 생각한다. 모방이라는 행위는 특정한 대상을 배끼거나 옮겨놓는 것을 말하는데, 그렇게 한다고 해서 모방한 것이나 피모방된 것이 반드시 동등(equal)한 관계에 있는 것은 아니라고 생각한다. 
다시 말하면, 모방하는 과정에서 원래의 대상을 특정할 수 있는 한계를 유지하는 한, 필요에 따라 일정부분은 가감이 될 수 있는 것이라고 본다. 따라서 객체를 통해 현실세계의 실상을 특정할 수 있다면 그것은 모방이라고 부르는게 더 적절한것 같다는 생각이다. 
우리가 찰흙으로 사람의 형상을 빚을 때, 사람보다 팔다리가 좀 이상하게 길어도, 머리가 유난히 커도 그 조형물이 인간을 은유한다고 하지 않고 모방해서 만들었다고 하는게 적절한 것처럼 객체도 그 객체가 표현하고자 하는 대상을 은유할게 아니라 모방하는 것이 그 객체의 책임과 역활을 명확하게 이해시킬 수 있으리라 생각한다. 
이러한 이유들로 나는 여전히 객체가 현실세계를 모방하도록 할 것이다. 

물론 어디까지나 나의 개인적인 견해이며, 이 견해가 이 책을 폄하하는 것이 아니라는 사실을 알아주었으면 한다. 이런 나의 개인적 의견에도 불구하고 이 “객체지향의 사실과 오해”라는 책은 내가 그 됨됨이를 논하기에는 너무 훌륭한 책이라는 사실에는 변함이 없다. 
기회가 된다면 저자분을 직접 만나뵙고 밚은 얘기를 나눌 수 있으면 좋겠다. 

혹시 이 글을 조영호님께서 보시게 된다면 “저는 책 때문에 조영호님 열렬한 팬이 됐습니다” 하고 얘기하고 싶습니다. ㅋㅋ

JDK1.5에서 Apache CXF 돌리기

현재 프로젝트의 자바 컴파일환경 표준은 1.5, 런타임은 1.6이다.

웹서비스 관련 기능을 테스트 해보기 위해 apache의 cxf를 사용하려 하는데, Class Version 에러가 난다.

Apache CXF 공식 사이트에서는 jdk 1.5 환경에서 cxf를 사용하려면 2.6.X를 사용하라고 가이드를 하고 있다.

(하지만 왠만하면 1.7이상으로 업그레이드 하라고 “강하게”권고 하고 있다. 요즘 세상에 1.5라니…ㅜㅜ)

여튼 그래서 maven의 dependency 설정에 2.6.16 으로 버전을 수정했지만 ClassVersion에러가 계속 난다.

혹시나 해서 Apache CXF 홈페이지에서 CXF 런타임 2.6.16을 다운로드 받아서 사용했더니 제대로 된다.

(http://cxf.apache.org/download.html 에서 다운로드 받을 수 있다.)

예측건데, maven central repository에는 1.6으로 컴파일한 cxf 1.6.16이 올라가 있고, apache cxf 사이트에는 1.5로 컴파일한 2.6.16버전을 올려둔게 아닌가 싶다. 여튼 2.6.16 버전을 받아서 테스트 해 보니 jdk1.5에서 제대로 동작하는 것을 확인!

OSX 에서 Google Cloud Platform 인스턴스에 ssh 접속하기

구글 Cloud Platform에 인스턴스를 만들고 작업을 위해 터미널로 접속을 할 필요가 있다.
osx(맥) 환경에서 어떻게 터미널을 이용해서 어떻게 접속하는지 간단하게 정리해 본다.

  • 기본적인 사항은 여기 참고 : https://cloud.google.com/compute/docs/console
  • 맥은 터미널에서 아래 명령어로 gcloud sdk를 설치해야 함 ( 윈도우 사용자는 https://cloud.google.com/sdk/
  •  $curl https://sdk.cloud.google.com | bash 
  • 위 명령어로 gcloud 를 설치하면 설치과정 마지막에 환경변수를 세팅할 rc파일을 물어보는데, default 가 .bashrc 라서 .profile 파일로 지정을 해 주어야 제대로
  • 아래 명령어로 gcloud workspace를 생성해야 한다.(터미널에서..)
  •  gcloud config set project YOUR_PROJECT_ID 
  • 다음과 같이 gcloud ssh 로 클라우드 머신에 접속해서 사용하면 됨.
  •  gcloud compute ssh YOUR_APPLICATION_NAME --zone us-central1-a 
  • 위에서 –zone 옵션을 주었는데, 같은 프로젝트로 여러 인스턴스를 만들었다면 zone 옵션은 필수다.
  • 다른 SSH 로 접속 하려면 아래 여기 참고 : https://cloud.google.com/compute/docs/console

dJango Template 에서 settings 값 사용하기

dJango 를 이용해서 만들고 있는 어플리케이션이 html view 와 rest api ( json )을 모두 지원하게 하려고 한다. 요청 URL 의 내용을 보고, api 요청인지, view 요청인지 판단하여 분기처리를 하기 위해 rest 처리용 urls 와 화면용 urls 를 분리하였다.

url(r'^api/product/', include('products.api_urls', namespace='product_api')),
url(r'^view/product/', include('products.view_urls', namespace='product_view')),

요청 url 이 /api/ 로 시작하면 api_urls 에 매핑되어 있는 RestAPI 용 view 가 실행이 되고, /view/ 로 시작하면 화면 출력용 view 가 호출되면서 지정한 template 에 의한 화면이 출력되게 된다.

따라서, template 에서 사용하는 링크는 /view/를 포함하고 있어야 화면간에 이동(link)을 할 수 있게 된다.

        <ul>
            
                <li><a href="/view/product/6/">testName1</a></li>
            
                <li><a href="/view/product/4/">testName</a></li>
            
                <li><a href="/view/product/3/">testName</a></li>
            
        </ul>
    

문제는 화면 요청을 특정하는 ‘/view/’ 라는 문자열을 하드코딩하지 않고, settings.py 에 아래와 같이 정의해 놓고,

VIEW_URL_CTX = '/view/'
API_URL_CTX = '/api/'

template 에서는 <a href="{{ VIEW_URL_CTX }}product/... > 와 같은 식으로 사용하고 싶었다.

하지만 template 에서는 사용자가 settings 에 정의한 값을 꺼내어 사용할 수 없었다. 그럴법도 한것이, 만약 대규모 프로젝트라면 settings.py 에는 DB 접속 정보부터 각종 설정에 이르는 민감하고 중요한 내용들이 들어있기 때문에 자유롭게 해당 값을 읽어서 사용할 수 있다면 문제가 될 소지가 많다.

설정은 모두 settings에 모아두고 그 값을 template 에서 사용하기 위해서는 어떻게 해야 할까?
context processor도 하나의 방법이 될 수 있지만 필자는 dJango 의 custom tag를 사용해서 위 문제를 해결하기로 했다.

그럼 custom tag를 만드는 방법을 알아보도록 하자.

먼저 custom tag를 만들기 위해서는 ‘templatetags’ 디렉토리를 만들어야 한다. 정확하게 얘기하면 __init__.py 가 포함되어 있는 python pacakge 를 만들어야 한다. 패키지 이름도 반드시 ‘templatetags’ 이어야 한다.

패키지를 만들었다면 패키지 custom tag를 구현할 py파일을 생성한다. 이때 생성하는 py 파일의 이름은 자유롭게 지정해도 좋다.

dJango 의 custom tag 는 tag 메소드를 정의하는 방법(argumemt의 갯수)에 따라 tag 의 형태가 많이 달라지므로 필요한 형식에 따라 주의깊게 tag를 정의해야 한다.

필자는 tag를 <a href=””{{ TAG_NAME }}”> 처럼 사용하고 싶기 때문에(즉,tag에 전달하는 argumement가 없다) assignment_tag를 사용하기로 했다. ( tag의 다양한 유형은 https://docs.djangoproject.com/en/1.7/howto/custom-template-tags/ 를 참고하도록 하자.)

from django import template

from django.conf import settings



register = template.Library()




from django import template
from django.conf import settings

register = template.Library()

@register.assignment_tag(takes_context=True)
def get_view_url_ctx(context):
    return getattr(settings, 'VIEW_URL_CTX', '')

@register.assignment_tag(takes_context=True)
def get_api_url_ctx(context):
    return getattr(settings, 'API_URL_CTX', '')

위와 같은 내용으로 templatetags/product_tags.py를 만들었다.

위 파일에서는 django.conf 의 settings를 import 하여 사용하고 있다. tag 구현체에 decorator를 달아서 tag유형을 assignment_tag 로 지정하였다.

이렇게 custom tag를 만들었으면 이제 template 에서 사용할 수 있다. template 에서 custom tag를 사용하려면 tag 파일을 load 해야 한다.template 파일의 시작하는 부분에 {% load your_template_tag_file_name %} 과 같이 적어준다.

필자의 환경에서는 abstract base template 을 정의해 놓고, 각각의 어플리케이션의 화면들이 base template을 상속하는 형태로 되어 있기 때문에 최상위 template파일에 아래와 같이 적어주었다.

{% load product_tags %}

이렇게 template에서 tag파일을 로딩하면 이제 tag를 사용할 수 있게 되는데, tag는 {% %} 안에 tag 이름과 argument를 적어서 사용하게 된다. 하지만 필자는 마치 settings에 정의한 global 변수처름 {{ TAG_NAME }} 과 같이 사용하고 싶어 alias를 지정해서 사용하기로 했다.

{% get_view_url_ctx as VIEW_URL_CTX %}
{% get_api_url_ctx as API_URL_CTX %}

이렇게 하면 template 파일에서 아래와 같이 {{ }} 안에 tag 를 사용할 수 있게 되고, 마치 settings에 정의한 값을 그대로 template 에서 사용하는 것처럼 보이게 할 수 있다.

{% block content %}
    {% if latest_product_list %}
        <ul>
            {% for product in latest_product_list %}
                <li><a href="{{ VIEW_URL_CTX }}product/{{ product.product_id }}/">{{ product.product_name }}</a></li>
            {% endfor %}
        </ul>
    {% else %}
        <p>등록된 상품이 없습니다 </p>
    {% endif %}

    <div>
        <img src="{{ STATIC_URL }}product/images/200ok.jpg"/>
    </div>
{% endblock %}