마이크로서비스를 넘어 서버리스 아키텍처가 유행하는 요즘(2017년) ,  보다 작은 모듈단위로 강력한 힘을 가질 수 있는 golang 은 구글의 막강한 지원을 등에 업고 큰 힘을 발휘하고 있는거 같습니다. 실제 언어 순위를 매기는 각종 지표에서도 Go 는 파죽지세로 위로 솟구쳐 올라가고 있습니다. 개인적으로는 쓸데없이 복잡하다고 느끼는 소위  객체지향 언어들에 대한 염증(사실 OOP디자인을 사용하는것도 때와 시기가 있는데..무조건 적용하려고 하면.. 부작용이 생기겠지요) 과 함께 스크립트 언어들이 떴는데, (함수형도 뜨긴하지만 주력은 절대 될 수 없습니다..장담~)  그 스크립트 언어에 없는 강력함을 갖춘 언어가 Go 라서 그런거 같습니다. 


즉 심플함 + 강력함 + 구글의지원 (미래보장) 


아래는 요즘 흥미롭게 읽어 본 글이 있에 대한 소개입니다. (2014년에 있었던 전쟁인듯?) 

객체지향 설계가 오히려 복잡성을 가중시키고 독이 된 다고 주장하는 사람들 중에 굉장한 프로불편러(?)의 이야기?
번역은 못해드려요. 글이 산만하고 제가 완전히 이해를 못함....OTL 
(심심할 때 영어 공부삼아서 도전해 보세요 ㅋㅋ) 아래 반박 링크 글도 필수적으로 읽어보세요. 명료하니깐.. 양쪽 모두 공감 할 만한 내용입니다.



Person A: “No Scotsman ever steals.”

A: 스코트랜드 사람은 남의 것을 훔치지 않지. ( 객체지향or함수형은..... 하지) 

Person B: “I know of a Scotsman who stole.”

B: 남의 것을 훔치는 스코트랜드 사람을 나는 아는데 (  객체지향or함수형은 이게 안좋던데.) 


Person A: “No True Scotsman would ever steal.”

A:  "진정한" 스코트랜드 사람이라면 훔치지 않어  ( "진정한" 객체지향or함수형을 니가 몰라서 그래) 


Person B:  ㅡ.ㅡ;; 


객체지향은 반드시 없어져야 할 비용만 높은 재앙이다. 

위 처럼 말하는 사람은 전혀 객체지향에 대한 이해가 없는 사람이다. 




이 전쟁을 끝내러 왔다 - 샹크스 (1)


농담이구요. 


앞으로 글은 아래와 같이 3가지 다른 내용으로 간략히 쓰여질 것입니다. 지금 까지 추상적 대화만 있다고 생각해서 이 글을 쓰는 목적인 알고리즘 그 자체에 대한 이야기가 있습니다. 게운하게 기술이야기로 털고 가자는거죠. 따라서 3번만 필독 혹은 먼저 읽는것을 추천합니다. 이번 알고리즘 이야기의 끝은 알고리즘으로~


  1. 인간,개발자
  2. vollfeed 님의 토론에 나왔던 흥미로운 기술 이야기 
  3. 아하~! 알고리즘 


1. 인간/개발자 

레고

레고를 만드는데 있어서, 이것 저것 직접 다양한 집과 비행기등을 만들어보며, 즐거움을 느끼는 어린이가 있습니다. 그 어린이의 동생은 집을 만들라고 하면 이것 저것 가져다 붙히다가 이내.. 집이 아닌걸 보고 울고 맙니다. 그 어린이의 친구는 만들어진 집을 보며, 이 부분은 이렇게 끼워맞추었더라면 더 빨리 만들 수 있었을 텐데라고 말합니다. 개인적으로 이것 저것 만들 수 있고 즐기는 창조적인 능력을 지닌 어린이가 더 부러우며, 효율성을 찾아내는 친구는 옆에 두고서 가끔 의견을 듣고 싶어집니다.



야구

어떤 야구 주루 코치가 있습니다. 항상 달리기를 강조합니다. 달리기는 야구의 기본이라며, 달리기 하라고 재촉합니다.달리기를 잘하면 니들의 연봉이 달라지고 대우가 달라질꺼라 말합니다. 어느날은 투수코치와 투수진들이 회의하는 곳에 가서 또 강조합니다.. " 달라기는 기본이라고 그것에 따라서 니들 계급이 달라진다고 " , 투수코치는 말합니다. "아 물론 달리기도 하고 있습니다만 , 지금 투구폼에 대해서 좀 더 집중하고 있다고.."  그리고 나서 지명 대형홈런타자들이 모인 곳에 가서 또 말합니다. "달리기를 잘해야한다고.." ....

아마 그는 진정성으로 그런말을 하는 것일 수도 있지만, 주루코치이기 때문에 자신의 밥벌이, 자신의 우물안에서 세상을 바라보기 때문일 수도 있지 않을까? 


개발자들 

대한민국에서 (다른나라는 잘모르겠고) 개발자가 되는것은 비교적 어렵지 않습니다. 따라서 다양한 능력의 사람들이 개발자라는 직업 타이틀을 공통으로 가지고 있습니다.  

의사라는 직업을 가지려면 "면허" 가 있어야하며, 검사도 마찬가지입니다. 하지만 개발자는 그런게 없지요. 그래서 그런지 계급을 나누려는 사람들이 있습니다. 자기가 좀 더 공부한것에 대한 보상심리리고 할까요? 

C 를 갓 배운 대학생은 포인터에 대해서 이해한 후에, 세상을 다 가진 느낌입니다. 그리고 자신이 포인터를 알고 있다는 것에 대해 자부심을 가지죠. 좋습니다. 근데 어느날 그 친구는 포인터가 없는 다른 언어를 하는 사람들을 발견하고는 말합니다. " 포인터가 없는 언어는 언어가 아냐" 라고 자신의 위치를 격상시키려고 노력 합니다.  이런 모습은 비단 포인터 뿐만 아니라 (C 가 가장 단골 소재지만) , 알고리즘도 마찬가지입니다. 알고리즘을 좀 더 많이 공부한 사람은 이렇게 말합니다. "문제 해결을 위해 알고리즘은 너무 중요해, 모두가 해야해" , 인텔CPU 를 공부한 학생은 이렇게 말합니다. "저레벨을 안다는것, 뿌리를 안다는 것은 너무 중요해, 모두가 여기서 파생됬기 때문이지. 모두 알아야해" 

사실 어느정도는 이해해줄만합니다. 하지만 그것을 이용하여 자신의 신분을 상승시키고 싶은, 자신이 우월한 위치에 서고 싶은 군상들은 눈쌀을 찌푸리게 합니다.

" 포인터 없는 언어하는 니들은 B 급 단순코더, 난 개발자"
" CPU 스펙도 안읽어본 니들은 B급 단순코더, 난 개발자"
" The Art of Computer Programming","TCP/IP illustrated" 도 읽어보지 않은 니들은 단순코더"  
" 컴파일러 / 인터프리터 못만들어 본 니들은 단순코더"
" 소형 OS 못만들어본 니들은 단순코더" 
" 대형프로젝트 설계 못해본 니들은 단순코더" 
" 자료구조 라이브러리 직접 만들어 쓰지 않는 니들은 단순코더"
" 수학,영어를 못하면 단순 코더" 
..

.. 좀 더 범위를 벗어나 보면 ..

.. 더 범위를 벗어나 보면 ..

"기술들을 간파하여 창조적 비지니스모델을 만드는 CEO or (기획자) 앞에서는 딥러닝 주구장창 반복적으로 돌리던지, 좋은 라이브러리 만들고 , 최적화, OO설계 잘 한다고 해 바짜 개발자 니들은 나의 톱니바퀴중 하나"  

ㅡ.ㅡ;; 


"자신이 공부한것에 대해, 쉽게 설명해주고 공유하려는 모습" 과 "기술적 측면에서 맞고 틀림을 정확히 분간"하려는 모습에서 존경심,존중심이 생기지,  저렇게 계급화 하려는 저열한 모습에서는 말하는이, 듣는이 모두 아무것도 건질게 없습니다. 아주 찰나의 우쭐함 뿐이겠지요. 



2. vollfeed 님의 토론에 나왔던 흥미로운 기술이야기

다양한 이야기들이 있지만 개인적으로 흥미로웠던것은

 "알고리즘이 문제 해결 전략을 찾는 것에 초점을 맞춘다면, 디자인 패턴을 그 전략을 좀더 모듈화 된 구조로 만드는 것에 초점이 놓이고 있습니다. Flyweight패턴과 Dynamic Programming의 공통점은 없는지요? Back Tracking 전략과 Memento패턴 및 Chain of Responsibility는요?"

이 내용이다. 개인적으로 vollfeed 님 전체 글에서 가장 건질게 있지 않나 했던 부분이라 생각한다. 다른 급 나누는 이야기는 저열해서( 내가 심하게 받아드린다고 생각하는 분도 있음을 인정한다. 하지만 난 더 비판적이다.)  언급할 필요는 없고, "알고리즘은 기본이며, 항상 공부하고 생각해보자" 라는 취지의 주제는 공감하나 너무 당연한거라..재미가 없었다.  자 이런 반감과 공감만 가지고 알고리즘 논쟁을 끝마치기에는 너무 얻는게 없다.


따라서 이번글과 다음글에서는 알고리즘 그 자체에 대해서 좀 생각하는 시간을 갖어보자. 남는게 있어야 할게 아닌가? 


 Flyweight패턴과 Dynamic Programming의 공통점?


동적프로그래밍은 무엇인가?

먼저 동적프로그래밍은 간단히 말해서, "공간을 사용해서, 시간을 단축시키자" 이다, "반복" 되는 시간 낭비를 없애는것인데 다음 그림을 보자. 동적프로그래밍의 적용 예는 최근에 HMM (머신러닝의 한 종류로 주로 시계열 분석에 사용됨) 을 하면서 본 viterbi 알고리즘의 예로 설명한다.


자 Rainy 는 비가 온날이고, Sunny 는 해가 쨍쨍한 날이다. 
비-비-비 가 온날 다음에 비가 올 확률을 계산 하고 
비-비-비 가 온날 다음에 해가 뜰 확률을 계산 할 경우
처음 부터 다시 계산 할 필요 없이 "비-비-비 같은 어차피 동일한 부분에 대한 계산 값을" 미리 저장(공간사용)해두고, 그 계산 값을 다시 계산하는 시간의 낭비를 막는 내용이다. 

즉 "반복되는 계산(시간)을 공간을 이용하여 줄이자"

그럼 Flyweight 패턴은 무엇인가? 

플라이웨이트급,웰터급 같은 복싱용어를 들어 보았는가? 그렇다 플라이웨이트급은 몸무게가 가벼운사람들의 그룹(경량급)을 말한다. 이것을 통해 이 패턴의 의도를 유추 할 수 있을 것이다.

즉 동일하거나 유사한 객체들 사이에 가능한 많은 데이터를 서로 공유하여 사용하도록 하여 메모리 사용량을 최소화(경량화)하는 Gof 디자인패턴 중 하나이다.  그림으로 쉽게 풀어 보면 (실제 패턴은 좀 더 OO 구조적이다.객체는 자신을 경량화 하기  위해 필요한 속성들을 내부에 몌모리로 가지고 있는것이 아니라 외부에서 레퍼런스로 공수한다. 기억하자 객체는 속성,멤버변수 보관자가 아니라 어떤 서비스를  제공하냐로 의미를 가진다.)




어느 소설에서 Hello 라는 글자가 나올 때마다 메모리를 할당해 줄 필요가 없다. 기존에 Hello 를 담고 있는 메모리가 있다면 Factory 에서 그 메모리를 가르키게 참조만 시켜주면 된다. 

혹여나 소설가가 그 중 한 부분을 HI 라고 고치면 그 때서야 Deep Copy 를 해서 다른 메모리를 생성해주면 된다(Copy on Write기법)  

그렇다, 보다시피 Flyweight 패턴은 간단히 말하면 "공간을 절약 시켜주는 패턴이다" , 반복되는 동일한 Value 에 대해 따로 반복적으로 만들지 말자. 따라서 결론적으로 Flyweight패턴과 Dynamic Programming은 시,공간적인 측면에서는 상관이 없으며, 다만 굳이 매칭하려고 한다면 "반복" 이라는 글짜가 눈에 들어오게 마련이다. "반복되는 계산을 줄여줌", "반복되는 할당을 없애줌" 

 Back Tracking 전략과 Memento패턴 및 Chain of Responsibility

이것은 간단히 정리하면  어떤 상태를 저장해 두는것이다. Memento 경우 현재 상태를 저장한 후에 그 상태로 되돌아가기 위한 패턴이다. 편집기에서 Undo/Redo 를 생각해보면 이해하기 쉽다. (참고로 Undo/Redo에는 커맨드패턴을 더 많이 사용한다) 이것은 갈림길에서 한쪽길을 탐색하다가, 다시 갈림길로 돌아오기 위해 갈림길이란 상태를 저장해 두는 모습을 떠오르게 한다. 또한 길을 찾을때 이 갈래의 길 저 갈래의 길을 순회하면서 찾기 때문에 , 다양한 (WAS에서) 각자 다른 책임을 지고 있는 filter 함수를 순회하는 느낌의 Chain of Responsibility 패턴과 일부분 비슷하 뉘앙스를 굳이 찾을 수 있다. 

 3. 아하~! 알고리즘 

내가 좋아하는 수학책중에 하나인 "아하~! 물리수학" 이라는 책의 제목에서 따왔다. 그 책의 저자는 암기식으로 수학을 공부하는 모습을 비판하며, 직접 그 수식이 어떻게 활용되고, 의도가 무엇이고, 어떻게 생겨난것인지에 대해 알려주어, 독자가 감탄사가 나와서, 진정한 이해를 하게 만들려는 책인데 (사실 그래도 수학이어서인지 수식이 가득하다.) 그 제목을 패러디 하여 보았다. 

내용은 내가 연례 행사 (2) 쯤으로 매년 읽곤하는 알고리즘 책인 "생각하는 프로그래밍" 챕터8 에서 따왔다. 이 좋은 책 중에서도 가장 좋아하는 챕터이며, 아하~~~ 라는 말이 가장 어울리는 장이다.

사실 알고리즘 논쟁은 그냥 논쟁에서 그치지말고, 이런 아하~~ 경험을 통해 마무리하는게 좀 남는게 있지 않을까 하여 읽어본 사람에게는 다시 떠올리는 기회를, 안읽어본 분들(알고리즘 자체에 관심이 없었던 분들) 에게는 소개차원에서 글을 써보려한다. 

브라운대학의 Ulf Grenander 는 2차원 패턴매핑문제를 해결하려는 중에 , 2차원은 너무 복잡해서 1차원으로 축소하여 통찰을 얻고자했다. 아래 소개할 알고리즘을 1번 방식으로 해결했으나, 너무 느리다고 판단해서 즉시 알고리즘 2번을 개발하였다.이 문제를 1977년 Shamos 에게 이 문제를 설명했고, Shamos 는 하룻밤 사이에 알고리즘 3을 디자인했다. 그 후 Shamos 가 이 책의 저자(존 벤틀리:스탠포드대학교) 에게 알려주었을 때, 우리는 그 알고리즘이 가능한 최상의 알고리즘이라고 생각했으며 다른 연구원들 역시 그러하였다. 후에 카네기 멜론 세미나에서 이 문제를 여러 다른 사람들에게 설명했을때, "통계학자" 인 Jay Kadane 은 1분만에 알고리즘 4를 만들었다. 

아래 각 알고리즘의 실행시간 표를 확인해보자.

 

 알고리즘1

알고리즘2 

알고리즘3 

알고리즘4 

 10^3

 1.3초

 10ms

 .4ms

 .05ms

 10^4

 22분

 1초

 6ms

 .5ms

 10^5

 15일

 1.7분

 78ms

 5ms

 10^6

 41년

 2.8시간

 .94초

 48ms

아하~~우와~~~  41년 걸리는게 있는 반면 1초도 안걸리는 알고리즘이 있다. 

앞으로 이 알고리즘을 같이 풀어 볼 것이지만, 대부분의 개발자들은 알고리즘2로 개발 할 것으로 생각되며, 사실 책에는 알고리즘 4를 어렵다고 써놓았지만, 알고리즘3보다는 훨씬 쉬울거 같다. 데이터를 진득히 오래 바라본다면 말이다. 

놓치기 쉬운 포인트는 통계학자가 풀었다고 책에 간단히 언급되어 있지만, 개인적으로 이 부분에서 아주 크게 감동받았다. 이유가 무엇이냐면, 알고리즘 3는 사실 분할정복알고리즘을 사용하였는데 기초로 공부하지 않은 이상 맨 머리로 생각해내는것은 힘들다고 본다. 그래서 알고리즘에 정통한 개발자라면 분할정복 알고리즘을 통해 해결 할 수 있으리라는 냄새를 맡고 바로 연구에 착수 했을 것이다. 그래서 결국 만들어 낸 알고리즘은 O(nlogn) 수준이다. 꽤 훌륭하다. 알고리즘 열심히 공부한 보람이 있다.  

하지만 통계학자 (즉 알고리즘을 배우지 않았다고 예상해보는) 그는 분할정복을 통해 해결하지 않고, 그 데이터를 면밀히 보았을 것이다. 여기에는 어떤 패턴이 있고, 어떻게 계산되면 쉽게 풀릴 수 있을 것인지를..그 결국 알고리즘 4 (O(n)) 라는 아주 명쾌하고 엄청난 알고리즘을 생각해 낸것이다. 알고리즘 전문가들 컴퓨터 전문가들을 제치고 말이다.

이 부분은 요즘 데이터분석을 하고 있는 나에게 큰 영감을 주었다. 이 책을 처음 산 아주 옛날에는 미쳐 생각하지 못했던 "통계학자" 라는 단어가 최근에 박힌것이다. "통계학자" 보다는 사실 "데이터를 진지하게 관찰하는 사람" 으로 말이다.

물론 정상적인 독자님들 경우" 통계학자가 컴퓨터과학자보다 더 낫다" 라고 주장하는게 아니란 것 쯤은 잘 아실듯하다.이제 변죽을 울려 놓았으니, 그 알고리즘을 풀어보자.


문제

위와 같은 1차원 배열이 있다고 할때, 어디서 부터 시작해서 어디까지 끝나는 경우 그 구간을 합 할때 가장 큰 값을 갖을까? 위의 예에서는 59에서 시작해서 97에 끝나는 경우가 가장 큰 값을 갖는다.


알고리즘1 

maxsofar = 0
for i = [0, n)
    for j = [i, n)
        sum = 0
        for k = [i, j]
            sum += x[k]
        /* sum is sum of x[i..j] */
        maxsofar = max(maxsofar, sum)

즉 처음 0부터 시작해서 0~1, 0~2 이런식으로 계산되고, 그 후에 하나 증가해서 1~2, 1~3  이렇게도 순회한다.
i 가 한바퀴 돌고 (n) 
j 가 한바퀴 돌고 (n)
후에 k 가 또 한 바퀴 돌아서 (n)

O(n^3) 의 알고리즘이 되었다. 이렇게 모든 경우를 다 탐색해서 문제를 풀 수 있었다. 
하지만 너무 오래걸린다. 너무너무~~


알고리즘2

maxsofar = 0
for i = [0, n)
    sum = 0
    for j = [i, n)
        sum += x[j]
        /* sum is sum of x[i..j] */
        maxsofar = max(maxsofar, sum)

알고리즘1에서의 문제는 대부분의 개발자라면 쉽게 무엇이 병목이 었는지 확인 할 수 있을 것이다. 즉 페인트공 문제로써, 기존에 계산해 놓았던 값을 사용안하고, 계속 처음부터 다시 계산한것이 실수 였던 것이다.1,2,3,4,5,6 에 대한 더하기를 할 때 

1+2+3 =6 이 이미 계산되어 나왔는데 1+2+3+4 = 10 을 계산 할 때 첨부터 계산한 것이다. 

그냥 6 + 4 하면 되는데 말이다. 위의 코드는 이런 방식으로 해결 한 코드이다.

즉 저장공간을 할당하여, 반복 계산을 막은것이다. 굉장히 이해가 쉽게 되며, 대부분의 개발자들이 이렇게 해결 했을거 같은 해결책이다.


알고리즘3 

float maxsum3(l, u)
    if (l > u) /* zero elements */
        return 0
        
    if (l == u) /* one element */
        return max(0, x[l])
    
    m = (l + u) / 2
    /* find max crossing to left */
    lmax = sum = 0
    for (i = m; i >= l; i--)
        sum += x[i]
        lmax = max(lmax, sum)
    
    /* find max crossing to right */
    rmax = sum = 0
    for i = (m, u]
        sum += x[i]
        rmax = max(rmax, sum)

    return max(lmax+rmax,
                maxsum3(l, m),
                maxsum3(m+1, u));

이제 O(nlogn) 알고리즘을 보도록하자. 사실 이 알고리즘을 생각 해 내는것은 너무 어렵긴하다.
분활정복의 냄새를 맡는것은 누구나 떠 올릴 법하지만, 실제 문제풀이 말이다. 

즉 어차피 이렇게 a 와 b 로 나누어도, 각각을 알고리즘2 처럼 계산하면 같은 시간이 나오는거 아냐? 어떻게 분할정복이 엄청난 힘들 발휘하지? 라고 말이다.

위의 코드를 보면, a 와 b 를 분할 한뒤에 알고리즘 2 방식을 따른게 아니라, 그냥 다시 또 쪼갰다, 끝까지 쪼개어서 알고리즘2 처럼 O(n^2) 이 아니라, O(n) 을 만들었다. 분활정복하면 O(n) 이 될 것이라고 냄새를 맡았다는게 훌륭하다. 결국 O(n) 에 쪼갠만큼(logN)을 곱하면 이 알고리즘의 성능이 된다. 즉 O(nlogn)이 된것이다.


알고리즘4

대망의 알고리즘4 이다. O(n) 알고리즘, 즉 오직 데이터의 갯수만큼만 시간이 필요한 알고리즘이다. 끝판왕이다.

int maxContiguousSubsequence(int[] a){
  int maxSoFar = 0;
  int maxEndingHere = 0;
  for(int i = 0; i < a.length; i++){
    maxEndingHere = Math.max(maxEndingHere + A[i], 0);
    maxSoFar = Math.max(maxSoFar, maxEndingHere);
  }
  return maxSoFar;
}

for 문이 하나이다. 즉  데이터의 싸이즈 만큼만 순회하고 끝!! 따라서 그냥 n 만큼만 계산하는것이다.
이제 저 알고리즘을 이해해보자. 아래와 같은 데이터가 있다.

문제를 풀기 전에 데이터를 계속 뚜러지게 바라보자. 패턴인식에서는 데이터를 많이 보면 볼 수록 해답을 얻을 수 있다.  자~~ 어디서 부터 어디까지 더해야지 가장 큰 값이 나올것인가..~~ 어느 중간지점을 살펴보기도 할 테지만 힌트를 주면 중간지점부터 볼 필요가 전혀 없다. 이것은 그냥 앞에서 부터 계산하면 답이 나오는 문제이다.

1. 54 와 -64를 더한다. 
2. -10이네? 0 보다 작으면 무시하자. 
3. 6부터 시작한다.
4. 6과 24를 더한다. 30이 나온다. (저장) 
5. 30과 -91을 더한다. -61이네? 0보다 작으니깐 무시하자.
6. 74부터 시작. 어랏 74가 끝이네?

답은 74 


정말 명쾌하고 간단하다. -.-;;  저걸 누군가가, 분할정복알고리즘으로 짜놓았다면  성능은 for 문 2번 돌린것보다는 좋아졌지만 정말 이해하기 어려운 코드였을 것이다.(3) 

이상으로 아하~ 알고리즘을 살펴보았습니다.

이 예를 통하여 얻었으면 하는것은 분활정복 알고리즘도 아니고, 공간을 낭비해서 시간을 벌자라는 것도 아니고,아하~~ 입니다. 몇십년 계산이 걸리는것이 1초도 안되서 해결되는 구나 하는 놀라움과 재미일 뿐이죠. 놀라움과 재미를 느끼면 그 이후의 것들은 알아서 하게 될 것으로 생각합니다. "공부를 하라는 훈계질, 급을 나누는 저열한 짓" 은 필요 없죠...


마지막으로

독자님들 소프트워어 개발을 하는데 있어서 아하!! 상황을 많이 만나서  더 짜릿한 개발자의 삶이 되길 바랍니다.^^




1) 나는 원피스를 한번도 읽어보지 않았지만, 캐릭터들은 알고있다. 매력적인 캐릭터 매력의 힘이란 대단하다.

2) 내가 매년 연례행사로 읽는 책이 2권이 있다. 첫째 . 생각하는 프로그래밍  둘째. 홀럽의 실용주의 디지인패턴

3) 글의 의도와 관련하여 몇몇 제외된 코드가 있습니다. 전부 마이너스인 경우도 그런 경우로 이에 대비해서 가장 큰 수를 구하는 로직이 추가되도 O (n)에서 벗어나진 않을거 같습니다. 다른 정보를 위해서는 Programming Perls (번역서: 생각하는 프로그래밍) 을 참고하십시요.





요즘 재미있게 시청하였던 "알아두면 쓸데 없는 신비한 잡학사전: 알쓸신잡" 이라는 나PD가 만들고 유시민작가등이 출연하는 프로그램에서 따와서 "알아두면 쓸데 없는 재밌는 소프트웨어 지식:  알쓸재소" 이야기 하나 합니다. 그냥 가볍게 읽으세요~이런거 논쟁 할 시간에 실질적으로 사람들에게 필요한 서비스를 만드는데 집중 하는게 응용개발자들의 덕목이라고 생각하니까요 ~:-) 

https://dev.to/danlebrero/the-broken-promise-of-static-typing

위의 블로그 글을 간략하게 번역/정리/추가 해본것입니다.
 


 


엉클 밥 마틴(클린코드의 저자) 은 자신의 블로그에 이런 글을 올려 놓았는데요. "타입전쟁" 

로버트 마틴, Robert Martin (Uncle Bob) (@unclebobmartin),은 1970년부터 프로그래머로 일했다. 그는 현재 8th Light inc 라는 회사에서 Master Craftsman 이라는 직함으로 일하고 있으며, 전세계에서 열리는 수많은 컨퍼런스에서 인기를 누리는 발표자이며, 다음과 같은 수많은 책들을 쓰기도 했다: The Clean Coder, Clean Code, Agile Software Development: Principles, Patterns, and Practices, UML for Java Programmers. 그는 수많은 기사들을 쓰고, 논문들을 발표하면서도, 블로그 활동도 활발하게 하고 있다. 그는 C++ Report의 편집장(Editor-in-chief)으로 활동했고, Agile Alliance의 초대 의장으로도 활동했다.

그는 여기서 "테스트 주도 개발이 주요하게 될 것이며, 동적언어가 잘 나갈 것이다. 결국 더 적고 간단하게 말하는 놈이 이긴다."  라고 동적언어에 대한 긍정적인 얘기했었습니다.(역주: 동적타입의 문제는 테스트를 통해서 해소되며 , 이렇게 잘 해소할 수 있는데 굳이 타입이라는 굴레에 얽매일 필요가 없다고 말합니다.. 즉 타입문제를 실수와 연관해서 말하고 있습니다. 강타입,상속등을 통한 설계에 대한 이야기는 아님) 

뭐 이런 주장에 대해서 별로 납득하지 않는 정적타입언어 추종자들도 많을 것이다. 타입이 있기 때문에 불필요한 유닛테스트 비용이 없어지고, 좀 더 안정적이 될 수 있다고 주장하며 , 정적 타입 언어인 헤스켈은 이렇게 말하기도 함 " 일단 당신의 코드가 컴파일되면 바로 사용 될 수 있을 것이다!" 즉 정적타입 언어를 통해서 명확한 코드리딩, 더 안전한 리팩토링, 더 나은 문서화, 더 정확하고 많은 것을 도와줄 수 있는 IDE 제작, 이런 모든것들을 약속하죠. 한마디로 " 버그를 더 적게 만든다" 라고 주장합니다.

네 전 버그를 매우 싫어합니다. 이것들은 나의 소중한 시간들을 갉아먹고 있으며, 프로젝트의 원할한 진행을 위한 에너지를 소모시키죠. 따라서 언어별로 버그가 어떻게 발생되고 있는지 조사하였습니다.

언어별 버그 조사

그래서 각 언어들이 발생시키는 버그를 Github 를 통해 살펴 봤습니다.

녹색은 발전된 정적타입언어들 : 하스켈,스칼라,F#
오렌지는 오래되고 지루한 정적타입언어들: Java, C++, Go
빨강은 동적타입언어들: Javascript, Ruby, Python, Clojure, Erlang 

Round 1.  버그 밀집도 :  모든 저장소

round1

Round 2. 버그 밀집도 :  10 stars 이상의 저장소

round2

Round 3. 버그 밀집도 :  100 stars 이상의 저장소

round3

좀 빈약한 증거라는 것은 부정 할수 없지만,  (역주: 해당 언어로 만드는 프로젝트가 보통 어떤 복잡도를 가지냐에 따라서도 달라질테니..) 어쨋든 동적타입군이 버그정도가 약합니다.

정적vs동적은 큰 이슈가 아니다.

정말 중요한것은 얼마나 단순한가에 따르는거 같습니다. Go 창시자인 롭파이크나 클로저의 창시자 리치하키는 그들의 언어의 코어와 단순화에 대해서 좋은 이야기를 해 주었었는데요. 

단순화는 어플리케이션을 좀 더 이해하기 쉽고, 변경하기 쉽고, 유지하기 쉽게 만들어 주며 그 결과 버그의 숫자에도 영향을 미칠 것 입니다. 그럼 단순화된 언어는 무엇일까요? 여기 리스팅 해보겠습니다.

Go, Erlang and Clojure 의 공통사항이기도 하죠.:

  • No 수동메모리관리
  • No 뮤텍스기반의 동시성 구현
  • No 클래스
  • No 상속
  • No 복잡한 타입 시스템
  • No 멀티파라다임
  • Not 많은 구문들 
  • Not 학구적 

(역주: 개인적으로는 No 클래스 제외하고는 동의 합니다. 우연적 복잡성은 저기에서 기인한다고 생각) 

Tony Hoare 의 말로 글을 끝내죠.

두가지 방식의 소프트웨어 디자인 방식이 있습니다. 한가지는 단순화해서 명백히 결함이 없게 만드는 것이고, 다른 방식은 복잡하게 만들어서 명백한 결함이 없도록 만드는것입니다.

(There are two ways of constructing a software design: 
one way is to make it so simple that there are obviously no deficiencies; 
the other is to make it so complicated that there are no obvious deficiencies.) 

역주:
Tony Hoare (토니호어) : 호어이론, 퀵정렬로 유명합니다. 널포인터를 만든 사람으로도 알려져 있으며 후에 "널포인터는 10억달라짜리 실수" 라고 말하기도 하였습니다. 스위프트, 러스트, 하스켈, 스칼라, 코틀린 등은 널포인터 실수를 방지 할 수 있는  optional type을 가지고 있는 대표적인 언어들입니다. 이러한 언어에서는 널 포인터를 사전에 컴파일러가 차단하므로 널 포인터 오류가 나지 않습니다. 사용자가 의도치 않게 널 포인터를 양산하는것을 강제적으로(?) 막죠. 


P.S 

전 만들려고 하는게 간단하던, 복잡하던 죽어도 클래스는 못버려~~타입인지라 (정적,동적은 상관없음) 
Go,Clojure 를 할 생각이 없지만, 언젠가 한번 버려서 제작은 해봐야지~라는 열린 생각은 가지고 있습니다.  

참고로 : http://philoskim.github.io/doc/why-clojure.html 

여기 링크에는 파이썬와 비교해서 클로저의 장점을 말하고 있는데, 이 글을 동의해서 시도해 봐야지 라고 생각 보다는, 그냥 언젠가 시도는 해 볼 생각은 하고 있습니다. 항상 내가 익숙한것 끌리는 것만 하고 사는것은 재미없는 인생이니까 말이죠. 남들이 재밌다는 영화/만화책은 한번 읽어봐주는 아량(?) 처럼 ㅎㅎ 

한주의 마지막이네요. 항상 건강 유념하시고~ 주말에는 푹 쉬시고 햇볕도 받으며 적절한 운동하시길 바랍니다.이번 글은 남의 글 2개를 읽고 정리 및 가벼운 코멘트 해보았습니다. 경어가 아닌점 양해해 주십시요. (_._) 



비동기와 동기 네트워킹 I/O 에 대한 성능차이 


위의 링크 글을 읽어보면 대략 이렇다.
@ 사람이 증가할때의 초당처리율(QPS), 빨리응답해주는능력(Latency), CPU 사용율은 큰 차이 없다.
@ 메모리 사용율은 큰차이로 비동기가 좋다.

CPU 사용량 차이 - 거의 없다.

메모리 사용량 차이 - 심하다.

응답 빨리 해주는 차이 (낮을수록 좋음) - 별로 없다

얼마나 많은 처리를 할 수 있나 (높을수록 좋음) - 큰 차이 없다. 


따라서 메모리를 적극적으로 사용해도 문제가 없다면 그냥 동기써라~ 훨씬 코드작성하기도 좋고 가독성도 좋다. 그러니깐 관리하기도 좋고 버그도 줄어들고 버그가 생겨도 더 명확하겠뭐 이런 내용이다. 

나도 비동기를 나름 잘 안다고 생각하는데도 불구하고 거의 대부분의 소켓통신 코드는 동기통신으로 짠다. 즉 SELECT나 IOCP 류를 사용하지 않는 다는 말~ 같은 사람이 아래와 같은 글도 썼다.


Node.js 를 사용 할 이유가 없어요.


Node 를 쓸 필요가 없단다. 다른 좋은것들이 많기 때문에~~뭐 주로 성능 측면( 근데 성능비교를 boost.Asio 랑 해놓아서..좀) 에서 그렇게 바라보는거 같다.그리고 비동기코드가 가지고 있는 비 직관성 때문에~~

내 생각도 비슷하지만 사람들이 많이 사용하는 이유로는 프런트엔드랑 동일한 언어로 개발하는것 , 코드길이가 비교적 짧고, 단일 CPU서도 비교적 좋은 성능을 보인다고 볼 수도 있겠다. 덕분에 사용자층이 두텁다. 공개 모듈도 많고~

빨리 응답해주는 차이 (낮을수록 좋다) 

얼마나 많은 처리를 할 수 있나 (높을수록 좋음) 

@ 참고로 위의 boost.Asio 는 C++ 라이브러리이며 ,  비동기도 사용하지 않은 결과이다.쩐다..

저 블로그 저자의 결론은 이렇다

Node.js 웹싸이트에서 머라고 선전하건 간에, 노드는 쉽게 사용하기 어렵고, 확장하기 어렵고 응답성도 낮은 편인데 노드를 왜 써? 그럼 무엇을 쓰냐고?  내가 생각하는것은 아래와 같아~  
GreatC++, Rust
Pretty GoodJava, C#, Scala, Go
MehRuby, Python
GarbageJavascript, PHP


음 C++ 빠(단순 성능빠)인거 같은데.. (자바스크립트,노드를 안좋아하는 것에 있어선 나와 비슷함) 성능만으로 선택하기엔 웹개발 세계는 매우 다양한 요소가 버무려져 있다는 거라.... 다만 분명한것은 대부분의 경우 비동기에 목맬 필요는 없다는 것 정도?? 얻는 것보다 잃는게 훨씬 더 크니깐~~

p.s  참고로 저런 벤치마킹은 그냥 벤치마킹으로 받아드리면 된다. 맹신하지 말것이며 본인 스스로의 환경에서 중요 컨텍스트에 맞게 벤치마킹을 직접 해보는게 정확하다.


*경어가 아닌점 양해해 주십시요. 책 광고 아닙니다 ^^
*초보자분이나 학생들에게 강추합니다. 반드시 자신의 언어에 해당되는 책을 두번,세번 읽으십시요.




나의 취미가 무엇이냐고 묻는다면, 테니스라고 서류상으로는 말은 해놓지만 실질적으로는 책 읽기/구매인거 같다.


북콜렉터!!


개인적으로 소장한 IT 계열 책만 300여권은 되는 듯하고, 10년 넘게 한달에 한번 이상 도서관에서 빌려다가 읽어보는 책도 꽤 많으니 IT 분야에서는 나름 다독가라고 할 수 있겠다.(더민주 최재천의원의 도서정가제에 원망이 많다. 어제 구입한 전문가를 위한 파이썬은 5만원이었다.RxJava를 활용한 리액티브 프로그래밍도 사야하는데 더이상의 용돈은 없다.;;)  물론 다독과 실력과는 상관관계가 많진 않다고 생각하며 , 그냥 취미 정도일 뿐... (개인적으로 PDF 등 모니터를 통해서 보는것을 싫어한다, 전자파가 싫고 그냥 먼가 책만지는게 좋다. 하지만 이런 습관은 빨리 빨리 무엇인가 습득하고 처리해야하는 현대 소프트웨어 연구직군에는 좀 맞지 않는 것도 사실이다.) 


이번 포스트에서는 역사상 가장 유명한 IT 시리즈인 Effective 시리즈에 대해서 알아보며 과연 Effective 로 시작하는 책들이 무엇이 있는지 찾아본 결과를 기록해 두려한다. 앞으로 새로운 Effective 시리즈들이 나올테고 그 때 마다 업데이트할 것이며, 내 북콜렉팅 일기장정도로 보면 될거 같다. 


* 참고로 Effective 시리즈는 세컨북류이다. The C Programming language 처럼 언어 그 자체를 설명한 첫번째 책을 읽고 나서 읽는 참고/팁 류의 두번째로 읽어야할 책이라는 뜻. 사실 무조건 읽어보라고 권하고 싶다. (솔직히 두번째로 읽기는 어렵긴 할거다. 개발 경험도 쌓여야 한다. effective 책을 체득하는 순간 당신은 초고수다.)


먼저 Effective 시리즈의 조상인 스캇마이어의 책들부터 살펴보자.


c++



최초 발행일: 1992년 1월



초판이 1992년에 나왔으니.. 엄청 오래된 책이다. 2판은 그리 크게 차이가 나지 않지만 최종 3판은 2005년에 나왔고 TR1에 대한 내용이 추가되는등 나름 변화가 있었다.




좀 더 추가된 내용이 있는 More effective c++ (1996년) 과 STL (2001년)만 전문적으로 쓰여진 책 그리고 가장 최근 모던한 C++ (C++11,14 : 2014년 발행) 에 대해 쓰여진 책이 있다. 가장 최근책은 왜 에디슨웨슬리에서 출간안됬는지 궁금. 

모두 번역되어 있으며, 번역이 잘 된 책으로도 유명하다. 
개인적으로 C++ 놓은지가 한참이지만 모던C++ 은 샀다. 스캇의 이야기로 C++11,14를 듣고 싶어서..

아마존 바로가기 



c#


저자 : Bill Wagner (빌 와그너)
발행일 : 초판 2004년  / 번역됨

최신 버전 3판(C# 6.0) 은 2016년에 발행되었다.  (3판은 번역 안됨) 

형제 책인 More Effective C# 은 2008년에 발행되었다.(번역안됨)

아마존 바로가기 


Java

저자 : Joshua J. Bloch  (조슈아 블로흐, 자바의 각종 라이브러리들을 설계함)
발행일 : 초반 2001년 ,  2판 2008년  / 번역됨 


자바의 구루가 쓴 책으로 자바 개발자들의 필독서 이다. 자바9가 나오고 비동기에 대한 열풍이 부는 이 시점에 3판을 만들어 줬으면 하는 바램이 있는가? 그렇다면 2017년 가을을 기대하시라~ 3판이 나온다!!. 자바  자바의 첫번째 책으로는 단연코 Thinking in Java 를 꼽겠다. (사실 Effective Java 보다 thinking in Java 5판이 나오길 더 바란다.) 

아마존 바로가기 




저자 :Ted Neward
발행일 : 초반 2004년 / 번역안됨 

아마존 바로가기 


Javascript


저자 : David Herman  (데이비드 허먼)
발행일 : 초반 2012년  / 번역됨 (억지로 읽고 또 다시 느꼈다. 난 자바스크립트가 무섭다) 

아마존 바로가기


Python


저자 : Brett Slatkin  (브렛 슬라킨)
발행일 : 초반 2015년  / 번역됨 (하지만 표지와 제목이 많이 다름 주의.제목: "파이선 코딩의 기술" )  

아마존 바로가기


SQL


저자 : John L. Viescas  (Author), Douglas J. Steele (Author), Ben G. Clothier (Author)
발행일 : 초반 2016년  / 번역안됨, (번역되고 있는 건 아닐까 싶다) 

아마존 바로가기


Objective C

저자 : Matt Galloway
발행일 : 초반 2013년  / 번역안됨 ( 이제 번역되바짜인가? Swift 가 대세이며, 훨씬 훌륭한 언어로 생각한다.)

아마존 바로가기


Effective Debugging

저자 : Diomidis Spinellis

발행일 : 초반 2016년  / 번역안됨 

아마존 바로가기


Perl 

저자 : Joseph N. Hall 

발행일 : 초반 2010년  / 번역안됨 

아마존 바로가기


Ruby

저자 : Peter J. Jones 

발행일 : 초반 2014년  / 번역안됨 (국내에선 Ruby 가 생각보다 찬밥이었나보다)

아마존 바로가기


TCP/IP

저자 : Jon C. Snader

발행일 : 초반 2000년  / 번역됨 (이거 국내 번역되었을때 바로 샀는데, 벌써 이렇게 시간이..yes24에 중고서적이 7만원에 올라와 있다. 골동품이란건가? 개인적으로 좋은책이라 생각하지만 굳이 저런 가격에 살 필요는 없어 보인다.) 

아마존 바로가기


XML  

저자 : Elliotte Rusty Harold

발행일 : 초반 2003년  / 번역안됨 (집에 XML 책이 3권있다. 마이크로 소프트 XNA 책 2권과 함께 가장 쓸모없는 책으로 선정되었다.) 


Concurrency 

  • 저자 : 허브셔터 ( C++ 계의 구루, Exceptional C++ 등 집필) 

    발행일 : 초반 2017년 예정 (대세는 동시성!!)



아래는 Effective 라는 말은 붙었지만 , 형식이 다른 책이다.

Akka , DevOps 




둘다 번역은 안됬고, akka 경우는 learning akka 가 현재 베타리딩이 끝났다. 




다른 언어에서는 아직 출간되지 않은 듯 하며, 
다른 언어(Scala, Go등) 에서도 Effective 시리즈가 나오길 바란다.!





하마블로그 





제 경험이 세상의 모든 경험을 대변하지 않으며,  나는 무조건 인공지능을 하고 싶다 등의 개인의 의지 또한 별개입니다. 하고 싶으면 해야죠. 인공지능, IoT, 빅데이터, 클라우드등 취업시장에서 과장되고 있다고 느껴지는 기술들에 대한 학원 과정들에 대해서 취업자들이 선택하는데 고심을 많이 하고 있는거 같습니다.  okky 게시판에도 매일 꾸준히 올라오는듯하고.그 고민/결정에 작은 도움이 되길 바랍니다.



어떤 분야가 전망이 좋다는 것과, 그 분야에서 채용이 많이 이루어진다는 별개입니다.

만약 전기자동차에 대한 전망이 좋다고 해도, 전기에너지 자체를 연구하는 사람의 수는 그리 늘지 않습니다. 생산직/세일즈맨은 거의 항상 일정 수를 유지하며 ,  회사가 잘되면 그들이 더 늘어나겠지요. 또한 전기자동차를 사용하는 사람들을 위한 SW(웹,앱등)서비스,인프라개발이 늘어나거나 하겠죠.

(논외로 인공지능,로봇이 모든것을 대체한다? 일어나지도 않을일을 미리 걱정하는것은 미련한 짓입니다. 만약 그 때가 되면 대부분의 직업이 사라질 것이며,  투표를 잘하는것 만이 살길입니다.기본소득제도가 강화되야하고 복지제도가 발전해야하니깐..) 


학원에서는 요즘 시류에 따라서 그런 과목을 넣는게 당연합니다만, 
실제 세계에서 채용되는 TO 와는 거의 무관함을 아셔야 합니다.

일반웹,응용개발직 몇백명 뽑을때, 그 쪽(머신러닝) 은 한명 정도 뽑을지 말지 모르는 상황..
그 특수분야에서의 몇개월 학원과정으로 한명이 님이 될 자신 있습니까?  전공대학원생도 많은데 말이죠. 
CEO 가 왜 굳이? (대개의 경우 회사내에서 찾을 가능성도 큼)
또한 현재 빅데이터,머신러닝만으로 생존이 되는 기업(스탓업 포함)이 얼마나 있나요? 거의 없죠. 
앞으로 엄청 많아질까요? 글쎄요..비관적 입니다. 많아져도 TO는 일반 sw 개발자의 몫이 훨씬 많을겁니다.

그냥 공부하기 위해서라면 IoT, 빅데이터, 머신러닝 모두 좋지만..(국비로 저렴하게 누군가에게 질문도 하면서 새로운것을 배운다는것은 즐거운일이며, 인공지능 공부는 누구든지 할 수 있습니다. 센스가 뛰어나다면 전공자들 보다 더 잘 할 수 도 있을 테지요.)  취직이라는 삶(?) 이 걸려있는거라면 확률적으로 비추합니다. 

그냥 웹프론엔드,벡엔드,앱개발 과정등을 공부하세요. 그 과정에서 남는 시간에 유행기술(딥러닝)등을 공부하지 말고 , 학부 컴공전공과목들을 공부하시고, 객체지향설계,데이터베이스,자료구조,TCP/IP 네트워크 같은거 말입니다. 진짜 서비스를 하기위해서 필요한 기술은 여기 수백,수천가지가 있는데, 이런것이 있는지도 모르고 , 또는 쳐다보지도 않고, 저 멀리 있는 허상의 기술들을 쫒아가면 안되겠지요. 

그 기본적인/중추적인 SW 기술들로 모든것을 할 수 있고, 참여할 수 있습니다. 주인공이 아니더라도 말이죠. 꼭 주인공일 필요 없지 않아요? ^^ 



p.s

부록으로 빅데이터에 관심있어 하는 취준생분들을 위한 정리
빅데이터 관련 잡은 크게 아래와 같이 4개로 나누어집니다. ( 이건 제 기준입니다.)

1. 인프라 관리/구축 
2. 관련 소프트웨어 개발  
   2-1. 하둡,카프카같은 툴개발
   2-2. 분석된 데이터를 통한 SI 성 개발
3. 데이터 분석 (통계,머신러닝) 
4. 가시화

어떤 회사는 4개를 나누지 않고, 한사람이 다 하기도 합니다. 
어떤 회사는 각 번호에 전문 인력이 독립되어 있습니다. 
일반 개발자들도 학습을 통해 1~4 모두 할 수 있습니다. 
(회사가 지향하는 분석의 정도 및 인재의 수준에 따라 범위가 달라집니다)  
새로운 신규인력을 채용하지 않고, 사내에서 어느정도 학습 후 담당하기도 합니다. 

* 조그마한 회사에서, 통계(분석)전문가만 따로 채용해서 3번만 맞기는 경우는 별로 없습니다. 
 그러한 상황에서 학원 몇개월 공부한 취준생의 자리는 더더욱 없습니다. 
 다만 일반 SW 개발자들이 공부를 해서 분석 업무를 하게 될 확률은 훨씬 높은거 같습니다.
 이유는 간단합니다. 데이터분석만 가지고 수익이 발생하는 회사가 거의 없기 때문입니다.


Published: November 30th, 2010

https://www.html5rocks.com/en/tutorials/eventsource/basics/번역 


저는 웹 개발 경험이 부족하여 최근에 알게된 기술들이 많습니다. 이것도 꽤 오래된 내용인 듯 한데요. SSE (Server-Sent Events : HTML5 표준안 권고사항) 에 대해서 소개하는 글을 번역해 보았습니다. 간략하게 요약하면 이 SSE 는 어느정도 웹소켓의 역할을 하면서 더 가볍습니다. 주로 서버에서 받는 (푸쉬) 위주의 작업에 유용하게 사용 될 수 있습니다. 웹소켓과 같은 양방향은 아니기 때문에 보낼때는 Ajax 를 활용합니다. 


"Play2 와 Iterratee 로 채팅구현 10분완성"에서Concurrent.broadcast와 iteratee,EventSource를 이용하여 업데이트된 내용을 클라이언트들에 전송하고, Ajax 를 이용하여 메세지를 받는 채팅구현을 소개하고 있습니다. 추후에 저 내용에 Angular2 를 이용해 클라이언트측을 추가한 버전을 이용하여 Play2와 Angular2 를 이용한 Reactive 채팅 구현이라는 글을 게시할 예정입니다. 관심있으시면 참고하세요. 

* 참고로 Play2 에서 의 사용되는 모습은 대략 아래와 같습니다. ( Play2 와 Server-Send Events 이용하여 데이터 헨들링하기)



val
source = scala.io.Source.fromFile(Play.getExistingFile("conf/coosbyid.txt").get)
val jsonStream = lineEnumerator(source) &> lineParser &> validCoordinate &> asJson val eventDataStream = jsonStream &> EventSource() Ok.chunked(eventDataStream).as("text/event-stream")

자 시작합니다~!



소개 


 "Server-Sent Events (SSE) 란 무엇입니까?" 라고 궁금해 하는 사람들에 대해 저는 별로 놀라지 않을 거 같네요. 왜냐면 많은 사람들이 그것에 대해 들어 본 적이 별로 없었을 것이란 걸 잘 알기 때문이죠. 지난 몇 년 동안 스펙은 상당한 변화를 겪었으며 API는 WebSocket API와 같이 더 새롭고 더 섹시한 통신 프로토콜에 의해 뒷전으로 밀려갔습니다. SSE의 개념은 아마 익숙 할 수도 있을 것입니다. 웹 응용프로그램은 서버에 의해 생성된 업데이트 스트림를 "구독" 하고 새 이벤트가 발생할 때마다 클라이언트에 알림을 보냅니다. 그렇지만 이런 Server-Sent Events를 제대로 이해하려면 Ajax 전임자들의 한계를 어느 정도는 이해해야 할 겁니다. 


폴링은 대다수의 AJAX 응용 프로그램에서 사용되는 전통적인 기술입니다. 기본 개념은 응용 프로그램이 서버에서 데이터를 반복적으로 요청하는 것입니다. HTTP 프로토콜에 익숙하다면 데이터를 가져 오는 것이 요청 / 응답 형식을 중심으로 진행된다는 것을 알 텐데요. 클라이언트는 요청을 하고 서버가 데이터로 응답 할 때까지 기다립니다. 갱신되지 않은 의미없는 응답이 리턴될 수 도 있습니다. 즉 단점으로는 더 많은 HTTP 오버 헤드를 만들 것 입니다. 하지만 일정하게 갱신이 되는 서버 데이터의 경우 매우 유용하며 기존 단순한 모델을 유지 할 수 있습니다.

긴 폴링 (Hanging GET / COMET)은 폴링에 약간의 변형입니다. 긴 폴링에서 서버에 사용 가능한 데이터가 없으면 서버는 새 데이터를 사용할 수 있을 때까지 요청을 보류합니다. 따라서이 기술은 종종 "Hang GET"이라고합니다. 정보를 사용할 수있게되면 서버가 응답하고 연결을 닫은 후 프로세스가 반복됩니다. 결과적으로 서버는 새로운 데이터가 사용 가능할 때마다 지속적으로 응답합니다. 단점은 이러한 절차의 구현은 일반적으로 '무한' iframe에 스크립트 태그를 추가하는 것과 같은 술수가 필요하다는 것입니다. 

반면에 Server-Sent 이벤트는 처음부터 효율적으로 설계되었습니다. SSE를 사용하여 통신 할 때 서버는 초기 요청을 하지 않고도 필요할 때마다 데이터를 앱으로 푸시 할 수 있습니다. 즉, 서버에서 클라이언트로 업데이트를 스트리밍 할 수 있습니다. SSE는 서버와 클라이언트 사이에 단일 단방향 채널을 엽니다.

Server-Sent Events와 long-polling의 가장 큰 차이점은 SSE는 브라우저에서 직접 처리되므로 사용자는 메시지를 청취(구독) 해야 한다는 것입니다. (즉 그런 코딩이 추가됨)

Server-Sent Events vs. WebSockets

웹 소켓을 통해 Server-Sent 이벤트를 선택하는 이유는 무엇입니까? 좋은 질문입니다.  ^^

SSE가 그림자에 머물러있는 이유 중 하나는 WebSocket과 같은 API가 양방향 전이중 통신을 수행하기 위한 더 풍부한 프로토콜을 제공하기 때문입니다. 양방향 채널을 보유하면 게임, 메시징 앱 및 양방향으로 거의 실시간으로 업데이트해야하는 경우에 더 매력적입니다. 하지만 일부 시나리오에서는 클라이언트에서 데이터를 전송하지 않아도 되며 서버 작업에서만 업데이트 해주면 됩니다. 몇 가지 예는 친구의 상태 업데이트, 주식 시세 표시기, 뉴스피드 또는 기타 자동화 된 데이터 푸시 메커니즘 (예 : 클라이언트 측 웹 SQL 데이터베이스 또는 IndexedDB 객체 저장소 업데이트)입니다. 이때 서버에게 데이터를 보내야 하는 경우 XMLHttpRequest는 항상 친구입니다. (역주: 채팅 기능을 예로 들면  SSE 로 브로드캐스팅된 내용을 듣고, Ajax 를 통해 서버로 말함) 

SSE는 전통적인 HTTP를 통해 전송됩니다. 즉, 작동하려면 특별한 프로토콜이나 서버 구현이 필요하지 않습니다. 반면 WebSockets는 프로토콜을 처리하기 위해 전이중 연결과 새로운 웹 소켓 서버가 필요합니다. 또한 서버 보낸 이벤트에는 자동 재 연결, 이벤트 ID 및 임의 이벤트를 보내는 기능과 같이 WebSockets은 디자인 측면에서 부족한 다양한 기능이 있습니다.

JavaScript API

이벤트 스트림을 구독하려면 EventSource 객체를 만들고 스트림의 URL을 전달합니다.

if (!!window.EventSource) {
  var source = new EventSource('stream.php');
} else {
  // Result to xhr polling :(
}

참고 : EventSource 생성자에 전달 된 URL이 절대 URL 인 경우 해당 출처 (scheme, domain, port)가 호출 페이지의 출처와 일치해야합니다.그런 다음 메시지 이벤트에 대한 핸들러를 설정하십시오. 선택적으로 open 과 error 를 수신 대기 할 수 있습니다.

source.addEventListener('message', function(e) {
  console.log(e.data);
}, false);

source.addEventListener('open', function(e) {
  // Connection was opened.
}, false);

source.addEventListener('error', function(e) {
  if (e.readyState == EventSource.CLOSED) {
    // Connection was closed.
  }
}, false);

서버에서 업데이트된 내용이 푸시되면 onmessage 함수가 실행되고 e.data 속성에서 새 데이터를 사용할 수 있습니다. 마법 같은 부분은 연결이 닫힐 때마다 브라우저가 ~ 3 초 후 자동으로 소스에 다시 연결된다는 것이며 서버 구현은 이 재 연결 시간 초과를 제어 할 수도 있습니다.

그게 전부에요. 이제 클라이언트는 stream.php에서 이벤트를 처리 할 준비가 되었습니다.~

Event Stream Format

소스에서 이벤트 스트림을 보내는 것은 SSE 형식인 text/event-stream Content-Type을 사용하여 일반 텍스트 응답을 작성하면서 수행되며 기본 형식에서 응답에는 "data :" 행 다음에 메시지가 오고 스트림 뒤에는 두 개의 "\n"문자가 있어야 스트림을 끝낼 수 있습니다.

data: My message\n\n

Multiline Data

메시지가 길면 여러 개의 "data :"행을 사용하여 메시지를 분할 할 수 있습니다. "data :"로 시작하는 두 줄 이상의 연속 된 줄은 하나의 데이터 조각으로 간주되어 하나의 메시지 이벤트 만 발생합니다. 각 행은 단일 "\ n"으로 끝나야합니다 (마지막 행은 2로 끝나야 함). 메시지 처리기에 전달 된 결과는 개행 문자로 연결된 단일 문자열입니다. 예 :

data: first line\n
data: second line\n\n

e.data에 "first line \ second line"을 생성합니다. 그런 다음 e.data.split ( '\ n') .join ( '')을 사용하여 "\ n"문자를 다시 생성 할 수 있습니다.

Send JSON Data

여러 줄을 사용하면 구문을 깨지 않고 JSON을 쉽게 보낼 수 있습니다.

data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n

해당 스트림을 처리 할 수있는 클라이언트 측 코드

source.addEventListener('message', function(e) {
  var data = JSON.parse(e.data);
  console.log(data.id, data.msg);
}, false);

Associating an ID with an Event

"id :"로 시작하는 줄을 포함시켜 스트림 이벤트와 함께 고유 한 ID를 보낼 수 있습니다.

id: 12345\n
data: GOOG\n
data: 556\n\n

ID를 설정하면 브라우저가 마지막 이벤트를 추적하여 서버 연결이 끊어지면 특수한 HTTP 헤더 (Last-Event-ID)가 새 요청으로 설정됩니다. 이렇게 하면 브라우저가 어떤 이벤트를 실행하기에 적합한 지 스스로 결정할 수 있게 됩니다. message 이벤트는 e.lastEventId 속성을 포함합니다.

Controlling the Reconnection-timeout

브라우저는 각 연결이 닫힌 후 대략 3 초 후에 원본에 다시 연결하려고 시도하며 "retry :"로 시작하는 줄과 재 연결을 시도하기 전에 대기 할 시간 (밀리 초)을 포함하여 시간 제한을 변경할 수 있습니다.

다음 예제에서는 10 초 후에 다시 연결을 시도합니다.

retry: 10000\n
data: hello world\n\n

Specifying an event name

단일 이벤트 소스는 이벤트 이름을 포함시켜 여러 유형의 이벤트를 생성 할 수 있습니다. "event :"로 시작하는 행 다음에 이벤트의 고유 한 이름이 오는 경우 이벤트는 해당 이름과 연관됩니다. 클라이언트에서 이벤트 리스너를 설정하여 해당 특정 이벤트를 청취 할 수 있습니다.

예를 들어 다음 서버 출력은 일반적인 'message'이벤트, 'userlogon'및 'update'이벤트의 세 가지 유형의 이벤트를 보냅니다.

data: {"msg": "First message"}\n\n
event: userlogon\n
data: {"username": "John123"}\n\n
event: update\n
data: {"username": "John123", "emotion": "happy"}\n\n

클라이언트의 이벤트 리스너 설정 :

source.addEventListener('message', function(e) {
  var data = JSON.parse(e.data);
  console.log(data.msg);
}, false);

source.addEventListener('userlogon', function(e) {
  var data = JSON.parse(e.data);
  console.log('User login:' + data.username);
}, false);

source.addEventListener('update', function(e) {
  var data = JSON.parse(e.data);
  console.log(data.username + ' is now ' + data.emotion);
}, false);

Server Examples

PHP의 간단한 서버 구현 :

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.

/**
 * Constructs the SSE data format and flushes that data to the client.
 *
 * @param string $id Timestamp/id of this connection.
 * @param string $msg Line of text that should be transmitted.
 */
function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}

$serverTime = time();

sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));

Download the code

Node JS: 의 유사한 구현

var http = require('http');
var sys = require('sys');
var fs = require('fs');

http.createServer(function(req, res) {
  //debugHeaders(req);

  if (req.headers.accept && req.headers.accept == 'text/event-stream') {
    if (req.url == '/events') {
      sendSSE(req, res);
    } else {
      res.writeHead(404);
      res.end();
    }
  } else {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(fs.readFileSync(__dirname + '/sse-node.html'));
    res.end();
  }
}).listen(8000);

function sendSSE(req, res) {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });

  var id = (new Date()).toLocaleTimeString();

  // Sends a SSE every 5 seconds on a single connection.
  setInterval(function() {
    constructSSE(res, id, (new Date()).toLocaleTimeString());
  }, 5000);

  constructSSE(res, id, (new Date()).toLocaleTimeString());
}

function constructSSE(res, id, data) {
  res.write('id: ' + id + '\n');
  res.write("data: " + data + '\n\n');
}

function debugHeaders(req) {
  sys.puts('URL: ' + req.url);
  for (var key in req.headers) {
    sys.puts(key + ': ' + req.headers[key]);
  }
  sys.puts('\n\n');
}

Download the code

sse-node.html:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
</head>
<body>
  <script>
    var source = new EventSource('/events');
    source.onmessage = function(e) {
      document.body.innerHTML += e.data + '<br>';
    };
  </script>
</body>
</html>

Cancel an Event Stream

일반적으로 브라우저는 커넥션이 끊어졌을때 이벤트 소스에 자동으로 다시 연결되지만 클라이언트 또는 서버로 부터 동작을 취소 할 수 있습니다.

클라이언트에서 스트림을 취소하려면 다음을 호출하시구요.

source.close();

서버에서 스트림을 취소하려면 "text/event-stream" Content-Type 이 아닌 200 OK 이외의 HTTP 상태 (예 : 404 찾을 수 없음)를 반환하면 됩니다. 두 가지 방법 모두 브라우저가 연결을 다시 설정하지 못하게 합니다.





SSE의 장점 정리 -  spoqa 기술블로그 (https://spoqa.github.io/2014/01/20/sse.html) 참고 

1.전통적인 HTTP를 통해 통신하므로 다른 프로토콜이 필요가 없습니다.

2.재접속 처리 같은 대부분의 저수준 처리가 자동으로 됩니다.

3.표준 기술답게 IE를 제외한 브라우저 대부분을 지원합니다.

4. HTML과 JavaScript만으로 구현할 수 있으므로 현재 지원되지 않는 브라우저(IE 포함)도 polyfill을 이용해 크로스 브라우징이 가능합니다. (여기서 polyfill이란 브라우저가 지원하지 않는 API를 플러그인이나 JavaScript 등으로 흉내 내 구현한 것을 뜻합니다. polyfill에 대한 자세한 설명은 이 블로그 포스트를 참조하시기 바랍니다.)


SSE 단점 및 Websocket 과의 비교 


- 인터넷 익스플로러 미지원 ( ㅋㅋ )  등 



http://streamdata.io/blog/push-sse-vs-websockets/   참고하시길


이 글은 웹 개발 파라다임의 거대한 변화 - "Reactive" 에 이어지는 코딩 위주의 글입니다.
스칼라 언어와 Play 프레임워크를 통해서 진행되니 관련 지식이 있으면  이해하기 편할 것입니다.
다만 학습을 위한 글로서는 내용이 많이 생략되어 있으며 추상층이 높아서 한번에 이해하기 원래 어려우니  자책 할 필요는 없습니다. 저도 체득하려면 멀었음을 많이 느끼고 있습니다. 완전한 이해를 하려면 여기저기 찾아다니면서 의문점을 해결해야하는 수고가 동반되며 그런면에서 Hello world 라는 제목은 좀 안어울리긴 합니다..@@  그냥 대략 어떤것인지 맛만 본다고 생각하시고 제대로된 학습을 위해서는 나중에 기회가 되면 오프라인 모임등을 통해서 함께 하였으면 하는 마음을 전합니다. 


서론

자 위와 같은 프로그램을 코딩해야한다고 치자. 메인쓰레드가 쭈욱 나가고 있으며 2개의 동작이 분기되어 나중에는 2개 동작의 결과가 합쳐져서 새로운 결과를 표시해야하는 것이다.

먼저 떠오르는 것은 쓰레드 하나를 생성해서 일 시키고.. 또 하나 생성해서 일 시키고... 좋아!!
근데 저 두개의 결과를 메인쓰레드 블럭없이 어떻게합치지??  고민의 시간이다..아주 다양한 방법이 있을 수 있겠다. 
이런데 쏟아야 하는 에너지를 아주 쉬운(편리한) 방법으로 해결해보자. 


먼저 알아 두어야 할 것들 


Future

* Future 자체에 대한 포스트는 요기에 있으니 참고를 => http://hamait.tistory.com/748 

먼저 스칼라에서 비동기 방식으로 Helloworld 를 찍어보자. 스칼라에서의 Future 는 꽤 다양한 방식으로 사용 할 수있는데 먼저 스칼라에서의 Future 모양은 다음과 같다.

trait Future[T]   

퓨쳐값을 나타낸다. T 타입의 실제 우리가 리턴받기 원하는 객체를 포함한다. 

defy apply[T](b: =>T) (implicit e: ExecutionContext) : Future[T] 

퓨처 계산을 나타낸다. 실제 계산을 수행하는 함수를 매개변수로 넣어주고 있다.

: 암시적으로 ExecutionContext 를 맥변수로 넣어준다. 즉 쓰레드풀을 넣어주는것. 

퓨쳐값 과 퓨쳐계산을 잘 구분해서 기억해두자.

예를 보면 이해가 더 잘 될거 같다. ㄱㄱ~

object FuturesDataType extends App {
import scala.concurrent._
import ExecutionContext.Implicits.global

val myFuture: Future[String] = Future {
Thread.sleep(1000*1)
"HelloWorld"
} log(s"status: ${myFuture.isCompleted}")

Thread.sleep(3000)
log(s"status: ${myFuture.value}")
}

그 어디에도 쓰레드를 직접 실행하는 모습을 볼 수 없다. 하지만 위의 코드는 메인쓰레드와 서브쓰레드로 나뉘어져있다. 어디에서 서브쓰레드가 실행되는 것일까? 그렇다. Future {  .. }  안의 코딩이 내부 쓰레드풀에서 실행되어진다. 1초 쉬었다가 "HelloWorld" 문자열을 리턴해주는 행동을 한다. 퓨쳐계산 한다고 한다.

참고로 Future { .. } 는 Future.apply 메소드 호출을 생략해서 쓴 것이다. (이런 문법적 유도리는 Syntatic sugar 라고 불린다)

그럼 myFuture 는 무엇인가? 퓨쳐값이다. 바로 리턴받게 되는데 myFuture 에는 실제 리턴값 즉 "HelloWorld" 는 들어 있지 않다. 대략 1초 후에 real data 가 담길 것이기 때문에 메인쓰레드에서 3초를 쉬었다. 그 후에 real data 를 가져와서 log 로 출력해주고 있다.

순서를 다시 정리해보면 

- "HelloWorld" 를 만드는 일을 Future 계산을 통해서 위임하고 바로 Future [String] 값을 리턴받는다.
- 리턴 받은 Future 에서 계산 완료가 되었는지 계속 확인 한 후에 (즉 계속 폴링) 실제 결과 값을 찍어준다. 
- 이렇게 Future 를 사용하면 사실 비동기를 사용하는 의미가 줄어 든다.

예를들어 스타벅스에서 1분마다 카운터에 가서 계속 내 커피가 나왔는지 물어 보지 말고, 커피가 나왔으면 커피를 내 자리로 바로 배달해주도록 하는 것 (콜백받는)은 어떻게 할까? 

자 그 역할을 하는 콜백함수 Future 에 넣어보자.

def getHello(): Future[String] = Future {
Thread.sleep(1000*1)
"HelloWorld"
}

val myHello: Future[String] = getHello()

myHello foreach {
word => log(s"result: ${word}")
}

myHello 라는 Future 값을 가지고 real data 가 결정되었는지 물어보는 코드가 없다.
다만 실제 데이터가 결정되었을때 바로 이용하는 콜백함수 
foreach { .... }   가 생겼다.
저 함수는 메인쓰레드를 블럭시키지 않고 또 다른 쓰레드에서 실행될 것이다.

마지막으로 함수 합성에 대해서 알아보자.

비동기 함수합성 과 map

콜백은 유용하지만 프로그램이 커지면 이를 사용한 프로그램 흐름에 대한 추론이 어려워 진다. 
또한 콜백을 사용하면 특정 패턴의 비동기 프로그래밍을 사용할 수 없게 된다. 특히 여러 퓨처에 한꺼번에 한 콜백을 등록하는 것은 귀찮은 일이 된다.

그래서 이러한 비동기 작업의 흐름을 "결정론적" 으로 자연스럽게 순서를 부여해서 해결 해주는 방식이 map 을 이용하는 것인데.. 이렇게 하면 첫번째 비동기 작업이 끝난 후에 다음 비동기 작업을 자연스럽게 연계해 줄 수 있게 된다. 

* map 은 기존 퓨처에 있는 값을 사용해서 새 퓨처를 만드는 메소드이다. 
def map[S](f: T => S) (implicit e : ExecutionContext) : Future[S] 

아래 예를 보자.


val buildFile = Future { Source.fromFile("build.sbt").getLines }

val longestBuildLine = buildFile.map(lines => lines.maxBy(_.length))

longestBuildLine onComplete {
case Success(line) => log(s"the longest line is '$line'")
}


위의 예에서는 1. build.sbt 파일에서 각 라인들을 읽어서 2. 그 중에서 가장 긴 라인을 출력하는 2가지의 함수가 합성되었다.이걸 쓰레드를 직접만들어서 했을 때와 비교하면 얼마나 간단해 졌는지 실감 할 수 있을 것이다.

val gitignoreFile = Future { Source.fromFile(".gitignore-SAMPLE").getLines }

val longestGitignoreLine = for (lines <- gitignoreFile) yield lines.maxBy(_.length)

longestBuildLine onComplete {
case Success(line) => log(s"the longest line is '$line'")
}

다음처럼 for comprehension 으로 처리 할 수 도 있다.
스칼라에서는 map 메소드가 있는 객체에 대해서는 for comprehension 을 허용한다.

* 아래 카카오에서 발행한 글을 통해 다시 복습해서 이해해보면 좋을 거 같다.
카카오톡 - 스칼라 Future 를 통한 모나드 이해 


Promise 

스칼라의 Promise 는 (스칼라의 Promise 다. 다른 언어 라이브러리들에서 Future, Promise 등에 대한 정의가 조금씩 다를 수 있다. 요즘 처럼 동시성 이슈가 많은 시절에는 원할한 의사 소통을 위해 구분 지어야 할 것이다)  는 Future 의 일반화라고 볼 수 있다.

위의 Future 에서는 항상 행동이 강결합되있었다.

future.( 행동 )  

하지만 Promise 는 Promise 만 먼저 선언해두고 나중에 해당 Promise 에 어떤 행위를 해서 결과를 받게 한다.

즉 Future 는 아래와 같은 시나리오라면 
다른데서 하는 행위 - 그 행위에 대한 실제 결과 -  미리 받은 Future - 실제 결과를 이용한 행동

Promise 는 
다른데서 하는 행위가 빠졌고 - 실제 결과를 돌려주는 타이밍을 스스로 결정  - 실제 결과를 이용한 행동 
으로 볼 수 있다.

다음 예를 보면 이해가 쉽게 갈 것이다.

import scala.concurrent._
import ExecutionContext.Implicits.global

val p = Promise[String]

p.future foreach {
case text => log(s"Promise p succeeded with '$text'")
}

p success "kept"

- Promise 를 미리 만들어 두었다.
- Promise 가 완료되면 진행할 행동을 foreach 의 케이스 매칭으로 만들어 두었다.
- success 를 호출해서 완료 및 인자를 전달한다.

다른 예로는 

def myFuture[T](body: =>T): Future[T] = {
val p = Promise[T]

global.execute(new Runnable {
def run() = try {
val result = body
p success result
} catch {
case NonFatal(e) =>
p failure e
}
})

p.future
}

val future = myFuture {
"naaa" + "na" * 8 + " Katamari Damacy!"
}

future foreach {
case text => log(text)
}

- 먼저 Promise 객체를 행동과 연결되는게 없이 만든다.
- 어떤 행동을 하게 하고 바로 Future 를 리턴시켜준다.
- 어떤 행동 (registerOnCompleteCallback) 안에서 어떤 행동이 완료되었다면 success 라는 메소드를 실행시켜서 먼저 리턴한 Future 에게 complete 신호를 보낸다. 

마지막으로 아직도 헥깔리실까봐 말하자면 Future 는 자신의 쓰레드를 생성하고 , 행위를 강결합해서 실행했다면  Promise 는 전혀 그러하지 않다. 그냥 다른 쓰레드 (위에서는 ForkjoinPool) 에 스며들어가서 완료시점과 전달값을 세팅만 했을뿐이다.

Observable

동시성 도구인 Observable 을 배우기 전에 선두 학습이 필요한데 옵저버패턴과 생산자-소비자 패턴이다.

Gof 의 옵저버 패턴 

Reactive 프로그래밍이 데이터 흐름을 자연스럽게 이어지게 하자는 것이라고 말했었다. 이벤트를 주고 받는 모습은 기존에 Gof 패턴에서 Observer 패턴이 비슷한 형태를 보여줬고, 예를들어 이 패턴을 설명해보면 어떤 매니져 객체가 있고 , 이 객체에 어떤 이벤트를 받길 원하는 옵저버들이 매니져 객체에 등록한다. 
매니져에서 어떤 이벤트를 감지하면 , 자신에게 등록된 옵저버들에게 이벤트를 notify 해주는게 골자이다.

GUI 프로그래밍에서 특히 당연하게 사용되고 있다. 
어떤 데이터를 다루는 다양한 View 가 있을때, 어떤 하나의 View를 통해 데이터에 변화를 사용자가 주면, 나머지 View에게도 그 변화를 전파하기 위한 설계에 사용되는 것이다. 


POSA2 의 생산자-소비자 패턴 (멀티쓰레드 패턴중 하나) 

멀티쓰레드 디자인패턴의 꽃이라고 한다면 단연코 "생산자-소비자" 패턴이라고 할 수 있다. 멀티쓰레드/서버코드를 작성할때 거의 무조건 "생산자-소비자" 패턴이 사용되기 마련이며, 다른 고차원 패턴들 (예를들면 node.js 의 기반패턴인 react 패턴) 의 기반이 되면서 동시에 멀티쓰레드 코어패턴을 포함하고 있는 , 즉  "허리" 역할을 제대로 하고 있는 패턴이라고 볼수 있기 때문에 아주 중요하다고 볼 수 있다.

이것도 역시 이벤트(데이터) 를 주는 놈이 있고 받는 놈이 있는데 


스칼라 (rxJava) 의 Observable
주거니 받거니 (Polling 이 아니라 Push 를 통해)  하는 기본적인 매커니즘은 같다. 다만 기능이 추가되었고 비동기로 사용가능하도록 확장 되었다. 

강결합된 데이터를 이미 가지고 있는 생산자 (Observable)


object ObservablesItems extends App {
import rx.lang.scala._

val o = Observable.items("Pascal", "Java", "Scala")
o.subscribe(name => log(s"learned the $name language"))
o.subscribe(name => log(s"forgot the $name language"))

Thread.sleep(1000)
}

코드를 보면 Observable 은 어떤 데이터(이벤트) 를 가지고 있다. 생산자 역할을 한다.
소비자는 ?? 그렇다 name => log(s"learned the $name language")  이 함수리터럴이 담당한다.

생산자(Observable) 은 자신이 보낼(Push) 할 이벤트를 처리할 친구들을 subscribe 메소드로 모집하고 있다.


Iteratee & Enumerator 

Play 를 이 Reactive 웹프로그래밍을 하기 위한 최적의 툴로 play 라 손꼽힌다. (개인적으로 스프링은 조잡한거 같다.) 따라서 play 에서 제공하는 도우미들에 대해서 알아두면 아래 진행될 "Hello world" 를 이해하기 편할 것같아 간단히 소개하고자 한다.  아래 레퍼런스를 참고로 짥게 정리하였으니 참고 하길 바란다.

레퍼런스: http://mandubian.com/2012/08/27/understanding-play2-iteratees-for-normal-humans/

개념 

Play 에서 Iteratee / Enumerator / Enumeratee을 간단하게 말하면 :

이름설명
Iteratee [E, A]

 Iteratee [E, A]는 함수형 프로그래밍에서 iteration 컨셉의 일반화. E 입력,A 출력 (소비자역할)

Enumerator [E]컬렉션을 일반화 한 것으로 형태 E를 열거한다. 무한 열거 (Streaming) 할 수도있다. (생산자역할)
Enumeratee [E, A]거의 사용하지 않기 때문에 지금은 생각하지 좋다.

과 같다.

먼저 Iteratee 에 대해서 알아보자. Iteratee 는 보다시피 Iterator 와 먼가 관련이 있어 보이는데 자바에서 Iterator 가 어떻게 활용되는지 살펴보면 

val l = List(1, 234, 455, 987)

var total = 0 // will contain the final total
var it = l.iterator
while( it.hasNext ) {
total += it.next
}

total
=> resXXX : Int = 1677


짧게 정리하면  (1,234,455,987).iterating( .... total += it.next ....)  이렇게 되는데 
앞에 데이터가 있고 그것을 순회하면서 어떤 행동 하게 된다. 

데이터 + 순회 + 행동 으로 이루어져있는데  데이터가 정해져있다. 강결합이다. 

여기서 이것을 느슨하게 만들어 줄 방법에 대해서 생각해보면 iterator 를 일반화 하는것에 미치게 되는데 앞에 데이터가 무엇이 오건간에 순회하면서 행동을 하게 하자는 것이다. 즉 데이터는 주입받자는 것이다. 제어역전!! 

iteratee = 순회(iterating) + 행동(react)
Enumerator  = 데이터 

Enumerator 는 정해진 콜렉션의 데이터가 아니라 비동기적으로 데이터를 생산하는 것으로 또 일반화된다.  
다음 예제를 통해서 냄새를 맡아보자.

Enumerator 예제)

// 고정된 데이터로 부터 Enumerator 가 만들어진다. val stringEnumerator: Enumerator[String] = Enumerate("alpha", "beta", "gamma")

val integerEnumerator: Enumerator[Int] = Enumerate(123, 456, 789)
// 파일로 부터 Enumerator 가 만들어진다.

val fileEnumerator: Enumerator[Array[Byte]] = Enumerator.fromFile("myfile.txt")

// 어떤 콜백함수로부터 생성된 데이터로 부터 Enumerator가 만들어진다.
val dateGenerator: Enumerator[String] = Enumerator.generateM(
play.api.libs.concurrent.Promise.timeout(
Some("current time %s".format((new java.util.Date()))),
500
)
)

Iteratee 예제)

// 데이터들의 합을 만드는 (행동) 을 정의했다.
val iterator = Iteratee.fold(0){ (total, elt) => total + elt }
// 데이터는 요기 Enumerator 를 사용하고 val e = Enumerator(1, 234, 455, 987)

// apply 메소드는 결과는 받지 않는다.
val totalIteratee: Promise[Iteratee[Int, Int]] = enumerator apply iterator

// run 메소드는 결과를 result1 에 담는다. 물론 비동기로 받는다.
val total: Promise[Int] = enumerator run iterator

앞으로 이런 표시들이 나올 텐데  &>|>>|>>> ><>.  각각  throughapplyapplyOn or compose. 의 약자이다. 자세히 알고 싶으면  관련 문서를 찾아서 공부하길 바란다.


"Hello world" in reactive Web 

외부에서 내부로 많은 데이터들이 흘러다니는 환경이 마련되어진 요즘 그런 데이터 흐름을 유연하게 처리 할 아키텍처가 필요해졌다. 그 중 트위터로부터 데이터가 흘러들어오면 자연스럽게 사용자에게 서비스해주는것을 목표로 한다. Play 를 이용해서 웹서비스를 하기 때문에 지식을 미리가지고 있으면 좋겠지만 그렇지 개념을 이해하는데는 무리 없을 것이다. 

트위터로 부터 흘러들어오는 데이터를 웹소켓을 이용해서 바로 사용자에게 전달해주는 방식이다.

결과 부터 보면  "박근혜" 라는 낱말로 매칭을 해서 실시간 트윗들이 흘러 들어오게 하고 있다.

사용자 <----- 웹소켓 ---->   Play Reactive 서버 < ------ Rest API -----> 트위터  
이런 자연스러운 흐름이 생긴 것이다.

실제 Reactive 코드를 살펴보자.

View

<div id="tweets"></div>

<script type="text/javascript">

function appendTweet(text) {
var tweet = document.createElement("p");
var message = document.createTextNode(text);
tweet.appendChild(message);
document.getElementById("tweets").appendChild(tweet);
}

function connect(attempt) {
var connectionAttempt = attempt;
var tweetSocket = new WebSocket("@routes.Application.tweets().webSocketURL()");
tweetSocket.onmessage = function (event) {
console.log(event);
var data = JSON.parse(event.data);
appendTweet(data.text);
};
tweetSocket.onopen = function() {
connectionAttempt = 1;
tweetSocket.send("subscribe");
};
tweetSocket.onclose = function() {
if (connectionAttempt <= 3) {
appendTweet("WARNING: server lost" + connectionAttempt);
setTimeout(function() {
connect(connectionAttempt + 1);
}, 5000);
} else {
alert("server was lost. Please try again later");
}
};
}

connect(1);
</script>

- WebSocket 객체를 생성해서 서버측과 연결하고 있다.
- onmessage 콜백을 통해서 데이터를 서버로 부터 받으면  div 에 append 해당 내용을 해주고 있다. 

Controller

class Application extends Controller {

def index = Action { implicit request =>
Ok(views.html.index("Tweets"))
}

def tweets = WebSocket.acceptWithActor[String, JsValue] { request => out =>
TwitterStreamer.props(out)
}

def replicateFeed = Action { implicit request =>
Ok.feed(TwitterStreamer.subscribeNode)
}

}

WebSockets 를 TwitterStreamer 액터를 이용해서 사용한다. out 은 외부 즉 클라이언트의 연결통로 역할을 하는 액터의 레퍼런스이다. A 액터라고 치자.  TwitterStreamer 액터는 그 A액터에 메세지를 보내는역할.

- [String, JsValue] 는 [In, Out] 인데, 웹소켓은 클라이언트에서 내용(String)을 받고,  JSON 객체(JsValue)로 만들어서 클라이언트에게 전달(Out) 한다는 의미이다.

- request => out => TwitterStreamer.props(out) 은 좀 헥깔릴수 있겠는데 스칼라의 람다식이다.
  request 라는 매개변수를 받고 out=> TwitterStreamer.props(out) 라는 함수를 리턴하는 람다식이다.
  최종적으로  TwitterStreamer.props(out) 라는 액터객체가 만들어 질 것이다.
  여기서 props 는 Akka 에서 Actor 를 만드는 팩토리 메소드 쯤으로 보면 된다.

Play 는 2가지 다른 방식으로 웹소켓을 제공합니다. => 사용법 바로가기

 1. 액터 기반의 Akka Streams 사용 
 2. iteratees 사용

def acceptWithActor[In, Out](f: RequestHeader => HandlerProps)(implicit in: FrameFormatter[In],
out: FrameFormatter[Out], app: Application, outMessageType: ClassTag[Out]): WebSocket[In, Out] = {
tryAcceptWithActor { req =>
Future.successful(Right((actorRef) => f(req)(actorRef)))
}
}

 - acceptWithActor 메소드의 내부는 이렇게 생겼다. 어질어질...-.-;;


Twitter Actor 

class TwitterStreamer(out: ActorRef) extends Actor {
def receive = {
case "subscribe" =>
Logger.info("Received subscription from a client")
TwitterStreamer.subscribe(out)
}

override def postStop() {
Logger.info("Client unsubscribing from stream")
TwitterStreamer.unsubscribe(out)
}
}

object TwitterStreamer {

private var broadcastEnumerator: Option[Enumerator[JsObject]] = None
private var broadcaster: Option[Broadcaster] = None
private val subscribers = new ArrayBuffer[ActorRef]()

def props(out: ActorRef) = Props(new TwitterStreamer(out))

def subscribe(out: ActorRef): Unit = {

if (broadcastEnumerator.isEmpty) {
init()
}

def twitterClient: Iteratee[JsObject, Unit] = Cont {
case in@Input.EOF => Done(None)
case in@Input.El(o) =>
if (subscribers.contains(out)) {
out ! o
twitterClient
} else {
Done(None)
}
case in@Input.Empty =>
twitterClient
}

broadcastEnumerator.foreach { enumerator =>
enumerator run twitterClient
}
subscribers += out
}

def unsubscribe(subscriber: ActorRef): Unit = {
val index = subscribers.indexWhere(_ == subscriber)
if (index > 0) {
subscribers.remove(index)
Logger.info("Unsubscribed client from stream")
}
}

def subscribeNode: Enumerator[JsObject] = {
if (broadcastEnumerator.isEmpty) {
TwitterStreamer.init()
}

broadcastEnumerator.getOrElse {
Enumerator.empty[JsObject]
}
}

def init(): Unit = {

credentials.map { case (consumerKey, requestToken) =>

val (iteratee, enumerator) = Concurrent.joined[Array[Byte]]

val jsonStream: Enumerator[JsObject] = enumerator &>
Encoding.decode() &>
Enumeratee.grouped(JsonIteratees.jsSimpleObject)

val (e, b) = Concurrent.broadcast(jsonStream)

broadcastEnumerator = Some(e)
broadcaster = Some(b)

val maybeMasterNodeUrl = Option(System.getProperty("masterNodeUrl"))
val url = maybeMasterNodeUrl.getOrElse {
"https://stream.twitter.com/1.1/statuses/filter.json"
}

WS
.url(url)
.sign(OAuthCalculator(consumerKey, requestToken))
.withQueryString("track" -> "박근혜")
.get { response =>
Logger.info("Status: " + response.status)
iteratee
}.map { _ =>
Logger.info("Twitter stream closed")
}

} getOrElse {
Logger.error("Twitter credentials are not configured")
}

}

}

The Play WS API 를 이용해서 외부 서비스에 비동기 http 로 접속한다. 

- withQueryString("track" -> "박근혜") 로 연관단어를 통해 트윗을 검색한다.

- 접속후에 수신한 메세지를 Enumerator 를 통해  iteratee 에 넘긴다.

- iteratee 는 브로드 캐스팅하며 연결된 모든 웹소켓 객체에 데이터를 뿌려준다.

- 액터를 사용하여 쓰레드 안전성을 높혔다. 많은 클라이언트들을 대응하려면 이렇게 하면 좋다.
  즉 subscribe (사용자들의 목록)  같은 멤버변수는 쓰레드 안전이다. 

  &> 는  through의 약자이다. 필터링을 한다는 뜻이다.


다음 포스팅에는 Play기반 Reactive Web applications 을 좀 더 분해해서 설명하는 시간을 가질 예정입니다. 스프링의 경우 아래 토비의스프링으로 유명하신 이일민님의 동강을 참고하세요. 

* 스프링으로 하는 Reactive Streams - 이일민 



레퍼런스:

reactive web applications 

+ Recent posts