e t i m e i m p o r t d a t e f r o m c o u p o n i m p o r t c r e a t e _ b i r t h d a y _ c o u p o n s f r o m m e s s a g i n g i m p o r t s e n d _ m e s s a g e t e m p l a t e = ' ' ' { c o u p o n . c u s t o m e r . n a m e } 님 생일 축하합니다. 선물로 생일 쿠폰을 드립니다. { c o u p o n . c o d e } ' ' ' t o d a y = d a t e . t o d a y ( ) c o u p o n s = c r e a t e _ b i r t h d a y _ c o u p o n s ( t o d a y ) f o r c o u p o n i n c o u p o n s : m e s s a g e = t e m p l a t e . f o r m a t ( c o u p o n = c o u p o n ) r e s u l t = s e n d _ m e s s a g e ( c o u p o n . c u s t o m e r . p h o n e _ n u m b e r , m e s s a g e )
t e t i m e i m p o r t d a t e i m p o r t j s o n f r o m u r l l i b . r e q u e s t i m p o r t u r l o p e n t e m p l a t e = ' ' ' { c o u p o n [ c u s t o m e r ] [ n a m e ] } 님 생일 축하합니다. 선물로 생일 쿠폰을 드립니다. { c o u p o n [ c o d e ] } ' ' ' t o d a y = d a t e . t o d a y ( ) c o u p o n s = j s o n . l o a d ( u r l o p e n ( ' h t t p s : / / c o u p o n - s e r v i c e / c o u p o n s / b i r t h d a y / ' , d a t a = j s o n . d u m p s ( { ' d a t e ' : t o d a y . i s o f o r m a t ( ) } ) ) ) f o r c o u p o n i n c o u p o n s : p h o n e _ n u m b e r = c o u p o n [ ' c u s t o m e r ' ] [ ' p h o n e _ n u m b e r ' ] r e s u l t = u r l o p e n ( ' h t t p s : / / m e s s a g i n g - s e r v i c e / m e s s a g e s / ' , d a t a = j s o n . d u m p s ( { ' p h o n e _ n u m b e r ' : p h o n e _ n u m b e r , ' m e s s a g e ' : t e m p l a t e . f o r m a t ( c o u p o n = c o u p o n ) , } ) ) . c o d e
e t i m e i m p o r t d a t e f r o m c o u p o n i m p o r t c r e a t e _ b i r t h d a y _ c o u p o n s f r o m m e s s a g i n g i m p o r t s e n d _ m e s s a g e t e m p l a t e = ' ' ' { c o u p o n . c u s t o m e r . n a m e } 님 생일 축하합니다. 선물로 생일 쿠폰을 드립니다. { c o u p o n . c o d e } ' ' ' t o d a y = d a t e . t o d a y ( ) c o u p o n s = c r e a t e _ b i r t h d a y _ c o u p o n s ( t o d a y ) f o r c o u p o n i n c o u p o n s : m e s s a g e = t e m p l a t e . f o r m a t ( c o u p o n = c o u p o n ) r e s u l t = s e n d _ m e s s a g e ( c o u p o n . c u s t o m e r . p h o n e _ n u m b e r , m e s s a g e )
t e t i m e i m p o r t d a t e i m p o r t j s o n f r o m u r l l i b . r e q u e s t i m p o r t u r l o p e n t e m p l a t e = ' ' ' { c o u p o n [ c u s t o m e r ] [ n a m e ] } 님 생일 축하합니다. 선물로 생일 쿠폰을 드립니다. { c o u p o n [ c o d e ] } ' ' ' t o d a y = d a t e . t o d a y ( ) c o u p o n s = j s o n . l o a d ( u r l o p e n ( ' h t t p s : / / c o u p o n - s e r v i c e / c o u p o n s / b i r t h d a y / ' , d a t a = j s o n . d u m p s ( { ' d a t e ' : t o d a y . i s o f o r m a t ( ) } ) ) ) f o r c o u p o n i n c o u p o n s : p h o n e _ n u m b e r = c o u p o n [ ' c u s t o m e r ' ] [ ' p h o n e _ n u m b e r ' ] r e s u l t = u r l o p e n ( ' h t t p s : / / m e s s a g i n g - s e r v i c e / m e s s a g e s / ' , d a t a = j s o n . d u m p s ( { ' p h o n e _ n u m b e r ' : p h o n e _ n u m b e r , ' m e s s a g e ' : t e m p l a t e . f o r m a t ( c o u p o n = c o u p o n ) , } ) ) . c o d e
직렬화만 지원. 모든 바인딩이 C++ 로 구현된 Cap'n Proto 런타임 라이브러리를 C FFI 로 붙여서 구현함. 메모리 복사 없는 직렬화는 Cap'n Proto 의 최고 세일즈 포인트. 그런데 문제는 C/C++/Rust 정도가 아니면 아무래도 상관 없다는 것. 메모리 복사 비용이 문제인데 파이썬을 쓸까요? … … … …
직렬화만 지원. 모든 바인딩이 C++ 로 구현된 Cap'n Proto 런타임 라이브러리를 C FFI 로 붙여서 구현함. 메모리 복사 없는 직렬화는 Cap'n Proto 의 최고 세일즈 포인트. 그런데 문제는 C/C++/Rust 정도가 아니면 아무래도 상관 없다는 것. 메모리 복사 비용이 문제인데 파이썬을 쓸까요? 반면 메모리 복사 없는 직렬화를 위한 구현 전략이 바인딩의 인터페이스 설계에 중요한 영향을 주었다. 파이썬에서는 필요 없지만 Cap'n Proto 의 메모리 복사 없는 직렬화를 구현하는데 필요했던 인터페이스/개념들과 씨름해야 한다.
Cap'n Proto 자료형이 파이썬의 t y p e 객체가 아님. 필드에 대해 g e t a t t r ( ) / s e t a t t r ( ) / h a s a t t r ( ) 작동 안함. 필드가 비어있거나 없을 경우 A t t r i b u t e E r r o r 가 아닌 C FFI 아래쪽서 올라온 Cap'n Proto 의 오류가 튀어나옴. 객체 없이 딕셔너리로만 프로그래밍하는 기분. 사실상 다른 언어를 쓰는 경험.
통신하자! 하지만 구현 시간의 대부분을 다음과 같은 껍데기 작업에 쓰게 됐다. HTTP 요청 해석. JSON 해석. 유효성 검사. ( 시각인데 RFC 3339 형식이 아니라거나…) 유효하지 않을 경우, 적절한 오류로 응답. 유효할 경우, 애플리케이션 내부의 적절한 자료형으로 번역. 시각이면 d a t e t i m e . d a t e t i m e 으로, 사용자면 U s e r 로…
페이스북이나 라인, 카카오 같이 소비자 제품이 아니고, 따라서 규모도 상대적으로 훨씬 작다. 사업의 성장에 비례해서 제품이 빠르게 복잡해지긴 하지만, 처리하는 통신량이나 계산량이 빠르게 폭발하지는 않는다. 따라서 스포카에서는 Cap'n Proto 같은 성능을 위한 설계보다는 애플리케이션 구현을 간편하게 할 수 있는 설계가 더 유용하다.
, d e c i m a l , i n t 3 2 , i n t 6 4 , f l o a t 3 2 , f l o a t 6 4 t e x t , b i n a r y d a t e , d a t e t i m e b o o l , u u i d , u r i options ( t ? ), lists ( [ t ] ), sets ( { t } ), maps ( { k : v } )
n e y : d e f _ _ i n i t _ _ ( s e l f , a m o u n t : D e c i m a l , c u r r e n c y : C u r r e n c y ) : s e l f . a m o u n t = a m o u n t s e l f . c u r r e n c y = c u r r e n c y d e f s e r i a l i z e ( s e l f ) - > M a p p i n g [ s t r , s t r ] : r e t u r n { ' a m o u n t ' : s t r ( s e l f . a m o u n t ) , ' c u r r e n c y ' : s t r ( s e l f . c u r r e n c y ) , }
e n t i f i e r : d e f s e r i a l i z e ( s e l f ) : r e t u r n { } c l a s s P h o n e N u m b e r ( I d e n t i f i e r ) : d e f _ _ i n i t _ _ ( s e l f , p h o n e _ n u m b e r : s t r ) : s e l f . p h o n e _ n u m b e r = p h o n e _ n u m b e r d e f s e r i a l i z e ( s e l f ) : r e t u r n { ' t y p e ' : ' p h o n e _ n u m b e r ' , ' p h o n e _ n u m b e r ' : s e l f . p h o n e _ n u m b e r , * * s u p e r ( ) . s e r i a l i z e ( ) } c l a s s Q r C a r d ( I d e n t i f i e r ) : d e f _ _ i n i t _ _ ( s e l f , c a r d _ i d : u u i d . U U I D ) : s e l f . c a r d _ i d = c a r d _ i d d e f s e r i a l i z e ( s e l f ) : r e t u r n { ' t y p e ' : ' q r _ c a r d ' , ' c a r d _ i d ' : s t r ( s e l f . c a r d _ i d ) , * * s u p e r ( ) . s e r i a l i z e ( ) }
a ), 러스트 ( e n u m ), 스위프트 ( e n u m ) 등 이미 대수적 자료형 비슷한 것이 제공되는 언어에서는 자연스럽게 번역( 는 것을 목표로) 합니다. u n i o n i d e n t i f i e r = p h o n e - n u m b e r ( t e x t p h o n e - n u m b e r ) | q r - c a r d ( u u i d c a r d - i d ) ;
a ), 러스트 ( e n u m ), 스위프트 ( e n u m ) 등 이미 대수적 자료형 비슷한 것이 제공되는 언어에서는 자연스럽게 번역( 는 것을 목표로) 합니다. d a t a I d e n t i f i e r = P h o n e N u m b e r { p h o n e N u m b e r : : T e x t } | Q r C a r d { u n i q u e I d : : U U I D } d e r i v i n g ( E q , O r d , S h o w , R e a d )
a ), 러스트 ( e n u m ), 스위프트 ( e n u m ) 등 이미 대수적 자료형 비슷한 것이 제공되는 언어에서는 자연스럽게 번역( 는 것을 목표로) 합니다. e n u m I d e n t i f i e r { c a s e P h o n e N u m b e r ( S t r i n g ) c a s e Q r C a r d ( N S U U I D ) }
( d a t a ), 러스트 ( e n u m ), 스위프트 ( e n u m ) 등 이미 대수적 자료형 비슷한 것이 제공되는 언어에서는 자연스럽게 번역( 는 것을 목표로) 합니다. 자바나 파이썬처럼 대수적 자료형이 없는 일반적인 객체 지향 언어에서는 서 브타입 관계로 번역됩니다.
파이썬처럼 대수적 자료형이 없는 일반적인 객체 지향 언어에서는 서 브타입 관계로 번역됩니다. c l a s s I d e n t i f i e r : . . . c l a s s P h o n e N u m b e r ( I d e n t i f i e r ) : . . . c l a s s Q r C a r d ( I d e n t i f i e r ) : . . .
" _ t y p e " : " i d e n t i f i e r " , " _ t a g " : " p h o n e _ n u m b e r " , " p h o n e _ n u m b e r " : " + 8 2 1 0 - 1 2 3 4 - 5 6 7 8 " }
" _ t y p e " : " i d e n t i f i e r " , " _ t a g " : " q r _ c a r d " , " c a r d _ i d " : " 5 d 6 4 5 6 a d - d 5 e 3 - 4 5 6 f - 9 d 0 b - 6 3 b f 0 4 7 9 b 6 e a " }
r r e n c y ( e n u m . E n u m ) : k r w = ' k r w ' j p y = ' j p y ' u s d = ' u s d ' # . . . 사실 이렇게 직접 할 필요는 없습니다. p i p i n s t a l l i s o 4 2 1 7
다르기 때문입니다. 보다시피 단순 문자열로 다뤄집니다. " k r w " 반면 공용체로 만들 경우 객체가 한겹 생기게 됩니다. { " _ t y p e " : " c u r r e n c y " , " _ t a g " : " k r w " } 물론 이는 효율만을 위한 기능은 아닙니다. 그보다는, 자유 문자열이었던 필드 를 몇 종류로만 한정시키거나, 그 반대의 방향으로 스키마를 리팩토링할 때 하 위호환성을 유지하기 위해서 씁니다.
직렬화되면 b o x e d 와 r e c o r d 는 다르게 표현됩니다. b o x e d 일 경우 " A m e s s a g e . " r e c o r d 일 경우 { " _ t y p e " : " m e s s a g e " , " b o d y " : " A m e s s a g e . " }
r e c o r d 의 관계는 e n u m 과 u n i o n 의 관계와 유사 전자는 후자로 일반화 가능 하지만 직렬화했을 때의 표현이 달라서 별도 키워드로 통신하는 프로그램들을 한번에 배포할 수 없는 경우가 많음 하위호환성을 유지하며 스키마를 고쳐나갈 수 있게 하기 위한 방편
이름 정의는 양면을 갖는다. r e c o r d m o n e y / m o n e y ( d e c i m a l a m o u n t / a m o u n t , c u r r e n c y c u r r e n c y / c u r r e n c y , ) ; 이름 정의는 사람을 위한 앞면과 직렬화를 위한 뒷면으로 이뤄진다.
r e c o r d m o n e y / p r i c e ( d e c i m a l a m o u n t / m o n e y , c u r r e n c y c u r r e n c y / t y p e ) ; 위 레코드의 값을 직렬화하면 아래처럼 표현된다. { " _ t y p e " : " p r i c e " , " m o n e y " : " 9 . 9 9 " , " t y p e " : " u s d " }
n / c u r r e n c i e s . n r m 파일에 정의된 c u r r e n c y 타입 임포트: i m p o r t i 1 8 n . c u r r e n c i e s ( c u r r e n c y ) ; 같은 이름의 타입을 중복해서 임포트하면 오류. 조용히 덮어쓰는 것이 아니라 오류로 중복된 두 항목을 알려줌. 임포트 사이에 순환(cycle) 이 있을 경우도 오류. 역시 오류 메세지로 순환 경로를 보여준다: i 1 8 n . l o c a l e s - > i 1 8 n . c u r r e n c i e s - > i 1 8 n . c o u n t r i e s - > i 1 8 n . l o c a l e s
) 한 ( 마이크로) 서비스가 제공하는 기능들을 메서드의 목록으로 명세. s e r v i c e p d f - s e r v i c e ( b l o b r e n d e r - u r i ( u r i u r i ) , b l o b r e n d e r - h t m l ( t e x t h t m l ) , ) ;
) 서비스는 타겟 언어의 추상 인터페이스로 컴파일된다. c l a s s P d f S e r v i c e ( S e r v i c e ) : d e f r e n d e r _ u r i ( s e l f , u r i : s t r ) - > b y t e s : r a i s e N o t I m p l e m e n t e d E r r o r ( ) d e f r e n d e r _ h t m l ( s e l f , h t m l : s t r ) - > b y t e s : r a i s e N o t I m p l e m e n t e d E r r o r ( )
a s s P d f S e r v i c e I m p l ( P d f S e r v i c e ) : d e f r e n d e r _ u r i ( s e l f , u r i : s t r ) - > b y t e s : . . . r e t u r n . . . d e f r e n d e r _ h t m l ( s e l f , h t m l : s t r ) - > b y t e s : . . . r e t u r n . . .
띄운다: f r o m w s g i r e f . s i m p l e _ s e r v e r i m p o r t m a k e _ s e r v e r f r o m n i r u m . r p c i m p o r t W s g i A p p a p p = W s g i A p p ( P d f S e r v i c e I m p l ( ) ) m a k e _ s e r v e r ( ' ' , 8 0 8 0 , a p p ) . s e r v e _ f o r e v e r ( ) 파이썬의 경우 n i r u m . r p c . W s g i A p p 을 통해 서비스를 평범한 WSGI 앱으 로 만들 수 있으므로, 평소에 쓰던 WSGI 서버( 예: Gunicorn, uWSGI 등) 로 배포할 수 있다.
r o m p d f _ s e r v i c e i m p o r t P d f S e r v i c e _ C l i e n t p d f _ s e r v i c e = P d f S e r v i c e _ C l i e n t ( ' h t t p : / / l o c a l h o s t : 8 0 8 0 / ' ) p d f _ b y t e s = p d f _ s e r v i c e . r e n d e r _ h t m l ( ' ' ' < h 1 > P y C o n A P A C 2 0 1 6 에 오신 것을 환영합니다! < / h 1 > ' ' ' ) w i t h o p e n ( ' p y c o n . p d f ' , ' w b ' ) a s f : f . w r i t e ( p d f _ b y t e s )
s e t u p . p y 파일을 포함. JS 라면 p a c k a g e . j s o n 파일을 포함. 니름 IDL 을 고치는 사람이 아니라면, 니름 컴파일러를 설치하지 않아도 되게 하는 것이 목표였기 때문. 최근의 프론트엔드 도구들이 프론트엔드를 전혀 고치지 않는 동료에게도 node.js, npm, webpack 등을 설치하게 하는 것이 불편하다 느꼈고, 니름은 그런 불편을 피하게 하고 싶었다.
다른 프로젝트에서 많이 쓰이는 이름이고 검색도 잘 안됨. 오픈 소스로 공개하고 싶었기 때문에 검색도 잘 되길 바랐다. 그러다가 이영도의 소설 눈물을 마시는 새에 나오는 니름을 떠올림. 텔레파시와 꽤 비슷한 개념이고, 한국어 소설에 나왔기 때문에 다른 프로젝트에서 쓰인 적도 없음. 로마자 표기로는 nirum 사용. ( 로마자 표기법으로는 “nireum” 이지만 글자수를 줄이고 싶었음.)