스토리 홈

인터뷰

피드

뉴스

조회수 1137

S/W 공학과 실전과의 거리감

학교에서 배우는 소프트웨어 공학이 왜? 실제 업무에서 사용이 안되는가?그동안 후배들에게 멘토링을 할 때에 가장 많이 받았던 질문 중의  하나이다. 평소에 답변하던 것들을 글로 옮겨 본다.소프트웨어를 전공하는 많은 후배들은 대학생활 4년 동안 배우는 다양한 이론들과 소프트웨어공학들의 수많은 이론을 배운다. 하지만. 대부분의 선배들은 사회생활의 실제 프로그래머로 취업을 한다고 해도, 프로그래밍을 실제 업무에서 하지만, 실제 관련된 이론과 기술. 수많은 가이드라인과 품질 관련 이슈에 대해서 실제 적용하기 어렵거나, 거의 사용하지 않는다고 선배들에게서 이야기를 듣는다.물론, 이 경향은 많이 바뀐 것은 사실이다. 이제 대부분 공학적인 접근법을 사용한다. 하지만, 그럼에도 불구하고 실제 현장에서는 이 현상이 그다지 바뀌지는 않았다.과연 우리가 학창 시절 배우는 그 많은 이론들은 도대체 왜? 만들어졌는데, 실제 사용이 안 되는 이유는 무엇인가? 학창 시절에는 자바나 C와 같은 프로그래밍 스킬만 높이면 되는 것인가? 도대체, 학생 시절 배우는 그 많은 이론과 공학, 품질 관련 이슈들은 실제 업무에서 그렇게 쓸모없는 것이라고 대부분의 선배들이 이야기하는가?실전과 대한민국의 현실. 그리고. 소프트웨어 프로그래밍에 대해서 학생들에게 삽질의 대가가 한마디 하려 한다. 왜? 우리는 학교에서 배우는 이론을 실제 사용할 기회가 없는 것일까?필자는 소프트웨어 공학을 학창 시절 배운 것이 아니다. 오히려, 실제 소프트웨어 개발 활동을 하면서, 공학적인 것이나 소프트웨어의 시각화를 해야만, 소프트웨어의 품질을 관리할 수 있다는 것을 몸으로 느끼고, 이것이 실제 소프트웨어를 상품이나 서비스의 명목으로 사용자들에게 제공하는 경우에 정말 필요하다는 것을 20년의 실제 개발자 생활을 하면서 그 필요성에 대해서 처절하게 느껴왔다.차라리, 필자가 핵심 서비스와 중요한 개발 내용을 직접 코딩하는 개발자의 역할을 할 때에는 이러한 공학적인 것이나, 작은 규모의 소프트웨어를 개발할 때에는 이러한 필요성을 느끼지 못했었다. 대부분의 작은 규모의 소프트웨어를 개발할 때에는 단기적인 일들이 많았다.사용자의 요구사항에 맞추어서 그 시기에 그때에 맞추어서 소프트웨어를 개발하였고, 해당 소프트웨어를 다시 유지 보수한다던가, 다시 수정 작업을 하지 않는 식의 작업을 하는 경우에는 이러한 공학적인 개념이나 그 배경으로 디자인하고 설계한다는 것에 대해서 매우 귀찮게 생각했었다.과거 첫 경험이었던 코볼이나 클리퍼 시절에는 해당 소프트웨어의 규모가 크지도 않았으며, 데이터의 구조 설계 또한 대부분 파일 중심의 데이터였었고, 화면의 구조 또한 수십 개를 넘지 않는 정도의 규모였다.오히려, 고속의 인덱스를 걸기 위한 테이블 접근법이나, 고속으로 화면에 출력하는 방법. 데이터를 조금 더 빠르게 구성하는 방법들에 집중할 시기에는 굳이 플로우 차트를 왜? 그리는 것이며, 파일 구조에 대해서 디자인해야 하는지에 대해서 의아함을 똑같이 가지고 있었으며, 굳이 설계나 디자인 없이 바로 코딩과 개발을 하던 시절이었다.하지만, 대규모 시스템을 주로 구사하는 웹서비스의 시대에 있어서, 단순한 로그 정보하나를 시리얼라이즈화시키는 것만 봐도 그 사람의 수준을 파악할 수 있고, 텍스트 중심의 구성 설계를 보면 향후 시스템의 성능에 대해서도 예측이 되는 경험을 축적하게 되면, 가장 중요한 것은 역시... 공학적인 접근법이다.필자가 소프트웨어 공학의 첫 번째 개념에 대해서 눈을 뜨고, 그 필요성을 절감하던 첫 번째가 바로, 고객에게 제공되는 소프트웨어가 지속적인 유지보수성을 가지기 시작할 때에 그 필요성을 처음으로 인지하기 시작하였다.처음의 요구사항이 변화되면서 사용자의 업무 흐름이 소프트웨어의 구조와 데이터베이스의 구조를 계속 변화하여 나가고, 이러한 상황을 미리 설계된 자료를 통해서 예측하거나, 소프트웨어 아키텍처적인 관점으로 조금 더 세밀한 환경에 대해서 메모가 되어있고, 그 구성에 대해서 서술해두었다면, 상당히 고속 개발을 하고, 소프트웨어 품질을 향상시키는데 매우 중요한 첫 번째 개발 행위가 되었을 것이라고 느꼈었다.또한, 개발자가 수십, 수백 명 단위로 소프트웨어의 설계가 대단위로 변화하고, 그 개발 품질에 대한 통제와, 적정한 수준의 개발 수준을 형성하게 하는 방법에 대해서 고민할 때에도 똑같이 이러한 소프트웨어 개발의 시각화에 대해서 인지하기 시작한 것이다.당시에는 공학적인 개념 없이 유사한 방법이나 표현방법을 고안하였으나, 관련된 내용을 찾아보고, 전문가들에게 조언을 구해보니, 상당 부분 그 부분에 대해서 전문가들 간의 협의가 있었고, 그 표준화되는 시각화 방법들과 방법론들이 매우 많이  연구되었다는 것을 알게 되었다.필자는 오히려, 이러한 개발과정에 있어서 필요한 것들을 개발하다가, 공학적인 베이스나 방법론들이 어떻게 실제 개발에 사용되어야 효과적인가에 대해서 실전에서 터득하고, 실전에 배치되는 것에 대해서 이해를 넓혔다.또한, 미국에서 개발되어진 개발 방법론이 국내의 실정이나 환경에 적합하지  않은다는 것을 깨닫고, 그러한 부분들을 어떻게 지식을 바꾸어야 하며, 실제 실천해야  하는지에 대해서 아키텍트 포럼이나 모임에서 역설하기 시작하였고, 그 부분을 실제 개발에 접목하려 애써왔다.그리고, 그 경험을 중심으로 소프트웨어 아키텍팅과 관련된 경험을 늘려왔고, 모바일과 웹서비스를 중심으로 하는 기업에서 개발 총괄을 하는 경우에는 그동안 축적한 소프트웨어 개발의 경험을 바탕으로 소프트웨어 형상관리 SCM(Software Configuration Management)을 중심으로 이슈관리, 개발, 테스트, 배포의 단계를 자동화하는 소프트웨어 개발의 비주얼라이제이션을 어떻게 실현할 것인가에 대해서 고민하고, 그 환경을 보다 쉽게 전파할 수 있는 공정과 형태를 미국 중심의 CMMI체계와 국내의 SP의 기준을 배경으로 상당 부분 고민하고 있다.그런데, 가끔 만나는 후배들이나 이제 막 개발자의 생활을 시작하려는 친구들에게서 많이 받은 질문 중의 대표적인 질문이 ‘도대체, 학교에서 배우는 소프트웨어 공학은 언제 사용하나요?’, ‘도대체, 대학 4년 동안 배우는 그 많은 이론들은 언제쯤 사용할 수 있는 것일까요?라는 질문들을  그동안 수십 번, 수백 번 받아왔다.심지어, 소프트웨어 개발 생활을 몇 년정도 한 후배들에게서 마저도 듣게 되니, 이 부분에 대해서 한 번쯤은 글로 남겨 두어야 하겠다고 생각하였다.과거, ‘서울 행복 직업박람회’에서  질문받은 내용은 이러했다.그 당시 필자에게 찾아온 대학생이 질문한 내용은 매우 간단하지만, 매우 어려운 답변일 수 있었다. 그것은, ‘왜 대학교 때 배우는 이론이나 원론과 같은 기본적인 내용들이 실제 사회생활 나가면 필요 없다고 자기의 선배들이 이야기하는 것일까요?. 실제. 취업하면 정말 그런가요?’이 질문은 이번 이야기의 주제이며, 필자가 20년을 넘게 소프트웨어 개발자 생활을 하면서 받아온 질문 중에 가장 빈도수가 높은 질문이라고 하겠다. 필자가 자부해온 삽질의 대가라는 점에서 그 친구는 그 친구는 정말 그 이야기를 잘 해줄 사람을 찾아온 것이라고 생각하면서 다음과 같이 설명했다.결과론적으로 '필요하지만, 필요없는 곳도 있다. 하지만, 가능한 필요한 곳을 찾아봐라.'라는 식의 이야기를 해주었다.자, 그렇다면. 필자가 이런 선문답 식의 답변을 하게 된 내용을 하나씩 풀어서 설명해보자. 도대체, 대한민국의 소프트웨어 개발을 하는 곳에서 왜? '소프트웨어 공학'적인 개념이나 이론들이 사용이 안되고 있는 것일까?물론, 정답은 간단할 수 있다. 국내의 대부분의 소프트웨어 개발회사의 경우에는 소프트웨어 공학쯤은 없어도, 아무런 문제(?) 없이 소프트웨어 개발이 가능한 경우이다.실제, 그런 회사도 그런 개발 조직도 상당히 많다는 것을 필자는 경험으로 알게 되었다. 그렇다면, 그렇게 소프트웨어 공학쯤은 필요 없는 기업이나 개발 조직은 어떤 곳들일까? 그곳들부터 알아보자.개발 총괄 책임자의 대우가 형편없는 회사필자는 개발자의 생활을 시작하는 어린 친구들이 첫 번째 직장을 가지는 곳에 대한 선택에 대해서 조언을 해왔을 때에 가장 먼저 해주는 조언은 이것이다. 면접을 보려는 회사의 개발 총괄 책임자나 리더에 대한 대우와 회사 내에서의 위치를 먼저 살펴보라는 것이다.대부분 대우가 형편없거나, 매일 야근과 반복된 개발 일정의 반복이 계속되는 회사의 경우에는 그 대우가 형편없는 것 이상으로 개발의 공정이나 개발의 방법이 정형화되어있지 못할 가능성이 매우 높다.물론, 소프트웨어 개발이 시각화가 되면, 요구사항의 변동폭이 보이게 되고, 해당 정량적인 지수가 도출되므로, 해당 부분에 대해서 대응이 가능하지만, 개발 총괄 책임자의 지위가 낮거나 대우가 형편없다는 이유는 다음의 두 가지의 경우에 해당이 된다.하나. 공학적인 방법이나 정형화된 방법을 제안하는데, 회사의 최고책임자가 인정하지 않는 경우이다.이 경우에는, 보통은. 제대로 알고 있는 소프트웨어 개발자들은  해당되는 조직을 빠르게 떠나고, 별로 기대할 수 없다는 것에 대해서 자괴감이나 패배감과 같은 분위기가 개발 조직 내에 흐른다는 것을 곧 감지할 수 있을 것이다.둘. 실제 이러한 공학적인 방법 따위의 개발 방법론으로 통제할 수 없는 고객이 '슈퍼갑'인 경우이다.실제, 소프트웨어 개발 활동을 해당 '슈퍼갑'에서 영업적인 능력으로 얻어낸 경우의 회사의 경우에는 아무리, 옳은 이야기, 옳은 방법론으로 대응한다고 해도, 개발 막판에 개발의 방향성 자체를 손 뒤집듯이 바꿔버리는 상황이 빈번한 경우이다.대부분 이런 경우에는 소프트웨어 개발 총괄 책임자가 오히려, 공학적인 것을 알고 있거나, 똑똑한 사람이라면 멘붕에 빠지거나, 자괴감에 빠져서,  대충대충 소프트웨어 개발을 하거나, 자기가 먼저 자리를 뜨는 경우가 대부분이다. ( 버티는 사람은 몰라서 버틸 수 있다고 설명하는 것이 더 바람직하겠다. )물론, 이 경우에도 그런 것을 당연시하면서, 공학적인 개념도 모르는 리더가 고객과 같이 동조하는 경우가 오히려, 업무가 수월해지는 경우가 많은 것 또한 현실이다. 고객과 개발 책임자가 같이 '닭짓'을 하는데, 개발 조직이 온전할 리 없다. 공학 따위는 집어치우고, 프로세스나 정량화된 목표, 자동화된 방법과 같은 소프트웨어 품질은 그냥, '책'에만 나오는 단어이며, 개념일 뿐이다.실제, 똑똑하고 말 잘하고, 올바른 방향으로 이끄는 리더가 이 조직에 리더가 된다고 하더라도. 어쩔 수 없이, 버티지 못하고, 떠나게 되는 것을 흔히 보게 된다.그리고, 이러한 조직에 있는 대부분의 개발자들은 '소프트웨어 공학'따위의 '장난'은 실제 개발이 필요 없다고 역설하고, 이것을 당연하게 여긴다. 보통, 이렇게 만들어지는 소프트웨어의 품질은 보장할 수 없고, 이 보장할 수 없는 소프트웨어를 통해서, '슈퍼갑'에서 꾸준한 유지보수 비용과 일거리가 발생하는 방법은.. 아마도, '4대 강'처럼. 한번 만들어 두면, 끊임없는 유지보수 업무를 발생시키는 식의 문제 정의와 처리방법이라고 할 수 있겠다.당연한 것이지만, 결론적으로 이야기하자면, 이런 개발 조직에서 개발 총괄 책임자의 대우는 형편없고, 일정 조절이나 개발에 대해서 지휘할 수 있는 권리나 인사권 같은 것도 매우 부족한 상황으로 변화한다.그래서, 이런 회사일 수록, 소프트웨어 공학은 그냥, 뜬구름 잡는 이야기가 되는 경우가 일상다반사이다.실제, 소프트웨어 개발을 하지 않는 회사소프트웨어 개발 조직이 있지만, 실제 소프트웨어는 개발하지 않고, 심지어. 소프트웨어 유지보수마저도 관련 업체에 일임하거나 위임하는 경우의 조직이 해당되는 경우이다. 대부분의 슈퍼갑인 회사와, 어설프게 소프트웨어를 개발하는 기업들의 전산실에  해당하는 곳이 이런 환경에 해당된다.이 경우 소프트웨어의 공학적인 배경이나, 개발에 대한 스킬과 협조보다는, 일반 회사의 기획과 경영, 회계와 관리에  해당하는 업무들이 가장 중요하므로, 소프트웨어 개발의 시각화나 공정에 대해서는 그다지 관심이 없는 경우이다. 오히려, 제품을 선택하고, 유지보수 업체를 어떻게 관리하고 운용할 것이냐에 핵심과 초점이 있기 때문에, 소프트웨어 공학적인 배경은 가장 중요한 선택의 포인트가 되지 못한다.오히려, 투입 대비 효과에 대한 경영학적인 관점의 스킬과 개념이 더욱더 중요하다고 하겠다. 필자는 개인적으로 대부분의 대학에서 이러한 관점으로 교육을 하지 않는 것에 대해서 매우  불만족스럽다.분명, 소프트웨어 개발과 소프트웨어를 개발, 유지보수, 운영 및 관리한다는 것은 매우 연관성이 높기 때문에, 이와 관련된 과정이나 소통방법, 그리고. 윤리체계와 운영방법 등에 대해서도 충분하게 소프트웨어 관련학과에서 교육이 필요하다고 생각한다.이러한 회사에 입사하게 되는 개발자의 경우에는 소프트웨어 개발자가 된다기 보다는, 소프트웨어 개발과 운영을 관리하는 회사를 관리하는 업무를 더욱더 많이 배우고 경험하게 되므로, 소프트웨어 개발공학 따위의 뜬구름 잡는 이야기는 경력이 쌓여갈수록 더더욱 필요 없게 된다.사장이 직접 개발하는 소규모 개발회사이러한 경우도 몇 가지의 사례로 나눌 수 있지만, 대부분의 구성 형태는 정말 비슷해지는 점이 매우 특이하다. 그것은, 소프트웨어 개발회사에 있어서 개발 총괄 책임을 '사장님'이 직접 통제를 하는 경우이고, 실제, 중요한 코딩도 '사장님'께서 직접 하는 경우이다.이 경우에는 '소프트웨어 공학'적인 콘셉트보다는, '사장님'의 경험적인 바탕에 의해서 소프트웨어 개발의 시각화가 만들어지고, '사장님'의 지극히 개인적인 경험과 지식의 배 경위에서 '정량적'지수들이 결정되는 경우이다.이 경우에는 '사장님'의 스킬이 높은 파트의 경우에는 매우 느슨할 수도, 매우 강하게 조일 수 있고, 사장님의 경험이 부족하거나 어색한 지식을 가진 파트의 경우에는 매우 불완전하고, 매번 변경된다는 것을 개발 조직 전체가 느낄 수 있다.이러한 조직의 특성은 상당 부분 필요한 소프트웨어 품질을 유지하고 있기는 하지만, 특정 버그나 특정 형태, 특정 상황에 대해서는 포기하는 경우가 많다는 점이다. 또한, 개발 조직의 구성역시 특정한 방향으로 구성되어진 기형적인 개발 조직이 만들어진다는 것이다.물론, 이 방향이 완전히 틀린 것이 아니라는 점 또한 매우 중요한다. 해당 업무나 설루션, 패키지에 적합한 방향에 대해서 '사장님'의 경험에 의해서 구축되었기 때문에, 특정 공학적인 지식을 가지고 있거나, 개발의 경험이 풍부한 사람이 해당 조직에 들어와서 보기에는 매우 어색한 점이나, 매우 이상한 형태를 느끼게 된다.대부분 이러한 소프트웨어 개발 조직은 보통, 수년 이상 설루션이나 서비스를 진행해오고 있고, 특정한 형태로 발전되어 있고, 적당한 개발자들이나 서비스 운영조직과 내재화된 자체들의 경험들이 중첩되어 있어서, 정말 세밀하게 분석하고, 환경을 조절하기에는 정말 어려운 환경으로 진화된 경우가 많다.대부분, 급여와 업무, 직원들의 잦은 이탈과 특정 개발 조직에 대한 '사장님'의 편애가 눈에 뜨일 정도로 보이는 경우가 많다. 그것은, 해당 소프트웨어와 서비스가 그 환경에 가장 적합한 구조를 가지고 있기 때문에 발생하는 경우이기 때문에, 냉정하게 분석해보면, 그 조직의 형태가 매우 적합한 구조인 경우가 많다.그래서, 이러한 조직에 들어가는 경우에는 '이론적'인 소프트웨어 공학은 잠시 뒤로하고, '경험적으로 구축되어진 개발 프로세스'에 익숙해져야만 그 조직과 프로세스를 이해할 수 있게 된다. 이러한 회사의 경우에는 필요한 경험과 지식에 대해서 매우 제한적이기는 하지만, 나름대로의 규칙과 개발 철학, 향후. 발전방향에 대해서 어느 정도 구축하고, 이를 따라서 개발 조직을 운영하고 있다는 점이기 때문에, 어설픈 개발공학적인 개념으로 이러한 환경을 이해한다는 것은 매우 어려울 것이다.초보 개발자들의 경우에는 이러한 개발 조직에서 수년 이상을 지내야만, 이러한 방법을 이해하는 경우가 대부분이다. 그래서, 초기에는 '공학'따위는 없다고 푸념하거나, 필요 없다고 이야기하는 경우이다.소프트웨어 공학은 해당 개발 조직과 개발자들의 수준, 축적된 시각화 방법들을 종합화하여 보이는 활동이기 때문에, 이러한 개발 조직은 이러한 정착된 패턴에 대해서 한 번쯤은 시각화를 위한 종합진단과, 형태에 대해서 정립하고 자신들만의 개발 문화를 선언하는 방법을 택하는 것이 좋다. 그래서, 공학적인 방법에 대해서 고민하고, 품질에 대해서 조금은 더 발전적인 방법으로 진화할 수 있게 하는 방법이 될 것이다.하여간, 잘 모르는 사람들에게는 이러한 개발 조직은 매우 이상하게 보인다. 단, 이 조건에 가장 적합한 회사의 경우는 '적당한 수익을 시장에서 얻고 있으며, 그 시장에 맞추어 개발 조직과 문화가 발전한 회사의 경우'를 의미하는 경우이다.당연한 것이겠지만, 이러한 환경으로 '시장'에서는 버티기 매우 어려울 것이고, 곧 망할 가능성이 높은 경우이다. 물론, 영업적은 능력으로 개발 조직이나 회사가 운영되고 있다면, 자연스럽게, '개발 총괄 책임자의 대우가 형편없는 기업'으로 변화되기 때문이다.특정 개발 조직이 관습화 된 인사권을 행사하는 경우보통은 이러한 회사를 게임회사에서 잘 찾아볼 수 있다. 특정 서버의 기술이나 클라이언트의 개발팀에서 사람을  구인하는 데 있어서, 일반적인 구인의 방법보다는 인맥이나, 특정 방법에 의해서 인력을 수급하는 경우이다.이 경우에 중요한 개발 공정이나 프로세스와 개발경험들은 내부의 팀에서 내부의 팀원들을 통해서만 서로 간에 운영되는 형태이며, 보통은 게임회사나 특정 하드웨어 기술을 가진 업체들에게서 이러한 환경들이 빈번하게 나타난다.한편으로는 이러한 방법이 개발 조직 내에서의 테두리가 제한되기는 하지만, 어느 정도 회사가 성장하거나, 회사의 규모 이상이 되지 않는다면, 그렇게 문제가 되지 않는 경우가 된다. 필자의 경험에 의하면 매출 1조 원을 넘기는 기업이 되는 경우의 하드웨어 업체이거나, 매출 1천억을 넘기는 소프트웨어 기업의 경우에 이러한 개발 조직의 문화가 가장 큰 걸림돌이 되는 경우를 많이 보아왔다.이런 경우에 대부분의 중심 개발 조직이 아닌 조직에서는 자신들이 공정을 변화시키거나 제품의 중요 기능을 다룰 수 없고, 반복적인 유지보수나 무의미한 행위들이 연속되는 경우를 계속 경험하게 되므로, 소프트웨어 공학에 대해서 많은 의아심을 가지게 되는 경우이다.이상의 몇 가지 기업의 형태를 살펴보면서 필자가 알게 된 것은 소프트웨어 개발의 형식은 역시 무형식이며, 그 상황과 형태에 따라서 변화되고 진화한다는 것이다. 또한, 위에서 이야기한 몇 가지의 경우의 공통점은 바로, ‘소프트웨어의 품질’이 그다지 중요하지 않은 기업의 경우에 해당한다고 이야기할 수 있다.위에서 언급한 회사들의 공통점은 ‘소프트웨어의 품질’ 때문에 개발 조직을 변화시키거나, 개발 문화에 대해서 고민할 필요가 없는 회사라는 점이다. 당연한 것이겠지만, 소프트웨어 공학은 ‘뜬구름 잡는 이야기’를 하는 학창 시절 때에나 이야기한다고 이야기를 하는 선배들을 대부분 만날 것이다.대한민국에서 만날 수 있는 대부분의 소프트웨어 개발 활동들은 소프트웨어의 품질이 그다지 중요하지 않은 경우가 참 많다는 것이다.일단, 가동을 시작한 서비스가 죽게 되면 크게 문제가 되는 경우이거나, 해당되는 소프트웨어가 작은 문제로 인해서, 실제 비즈니스와 업무에 크게 문제가 되는 경우가 아니라면, 소프트웨어의 품질에 대해서는 그 중요성이 떨어지게 되는 것이 당연하다.충분한 소프트웨어 가치를 인정받을 수 있는 평가와 방향성에 대해서 충분하게 고민하고 있지 않은, 회사이거나 소프트웨어 개발 조직의 경우에는 당연한 것이겠지만, ‘소프트웨어 공학’은 그다지 중요하지 않다는 것이 결론이라고 하겠다.소프트웨어 품질이 정말 필요한 곳인가?이렇게 답변을 정의할 수 있다.소프트웨어 품질이 중요한 가치를 가지는 곳에서는 충분하게 소프트웨어 공학적인 이론과 배경이 가장 중요한 것이 될 것이다. 필자가 아는 어느 회사의 경우에는 소프트웨어의 기본적인 행위하나 가 실제 큰 비용으로 계산되는 경우가 있었다.단순한 하나의 물류이지만, 어떤 물류를 크레인을 사용하여 한 번 잘못 이동하게 되고, 해당되는 물품이 전혀 엉뚱한 나라에 가있거나, 해당 물품이 적재되고 내려지는 과정이 중첩되면서 만들어지는 비용을 단 한번 행위의 가치로 평가하였을 때에 1번 펑션이 1억 원 정도의 비용으로 계산되는 경우라면, 소프트웨어 개발의 펑션이나 개발 프로세스에 대해서 얼마나 고수준으로 설계하고 평가될 것인가에 대해서 생각해보면 될 것이다.이미, 은행에서 자금이 이체되고, 움직이는 과정에 대해서도 개별적인 가치에 대해서 평가를 할 수 있을 것이다. 과연, 내가 만드는 소프트웨어의 기본가치는 어떻게 되는 것일까? 에 대해서 생각해보면, 우리가 만드는 소프트웨어에 얼마나 고품질이 필요한 것인가에 대해서 설명할 수 있을 것이다. 그렇지만, 필자는 이렇게 이야기하겠다.슬프지만, 대한민국의 IT 중에서 소프트웨어 개발 분야에 있어서, 정말 고품질이나 고성능을 요하는 수준으로 요구하는 곳이 거의 없기 때문에 이러한 문제는 계속 발생할 것이며, 계속 이러한 질문은 만들어질 것이다.대부분의 학생 시절에 우리가 배우는 기본과 이론들은 쉽게 설명해서 죽지 않는 서버와 데몬을 만들고, 가능한 정해진 규칙 하에서는 다운되지 않는 웹서비스를 만들려고 그런 기본과 이론을 배운다.하지만, 대부분의 서비스들은 죽으면, 서버의 데몬 프로세스를 죽였다가, 다시 동작하면 되는 수준의 업무면 충분한 경우가 대부분이다. 더군다나, 외국에서 만들어진 프레임웍이나 만들어진 소프트웨어 위에서 동작되는 소프트웨어를 만드는 환경에서라면, 이러한 공학이나 이론 따위야 그다지 중요한 것이 아니게 될 것이 아니라는 점이다. ( 그 책임은 비싸게 구매한 DBMS나 프레임웍이 해결해야할 책임이라고 떠넘긴다. )결론적으로 마지막 이야기를 한다면, 과연 이러한 소프트웨어 가치를 충분하게 만들어 낼 수 있는 소프트웨어 개발 활동을 내가 하고 있는가에 대해서 고민해보자. 그리고, 그러한 행위를 할 수 있고, 발전 가능성이 있는 곳이야말로, 이러한 고수준의 품질활동이 필요한 곳이 될 것이다.그리고, 이러한 고수준의 소프트웨어 품질활동이 필요한 곳은, 바로. 아직은 단 한 번도 이러한 소프트웨어나 서비스가 만들어지지 않은 곳에서 이러한 활동이 더 많이 필요하다. 그것은 바로, 스타트업이나 이제 서비스를 개시하려는 곳일수록, 적절한 소프트웨어 품질활동이나 시각화가 필요하다고 이야기할 수 있겠다.소프트웨어 활동을  시각화한다는 것은 결론적으로 소프트웨어 개발자가 투입하는 행위에 대한 가치에 대해서 얼마나 고수준으로 끌어올린 것이며, 어느 정도 적절한 품질 수준을 고려할 것인가에 대한 활동을 의미한다.그러므로, 현재 스타트업을 꿈꾸고 있거나, 적적할 소프트웨어의 개발비용을 고민하고 있는 곳이라면, 소프트웨어 공학은 매우 중요한 활동이나 방향성에 대해서 정답에 근접하도록 도움을 줄 것이다. 소프트웨어 고품질의 세계와 소프트웨어 공학의 세계는 소프트웨어 개발자들이 어떤 생각을 하고, 개발에 참여하느냐에 따라서 결정되어진다. 그 선택은 역시, 각자가 하는 것이다.
조회수 851

웹기반 컨텐츠 저작 도구 셀프(XELF) v1.0 GS인증 획득

웹기반 컨텐츠 저작 도구 셀프(XELF) v1.0 (Web-based Contents Authoring Tool XELF v1.0)이 한국정보통신기술협회(TTA) 소프트웨어 시험인증연구소로부터 GS인증 1등급을 획득하였습니다.  셀프(XELF)는 별도의 프로그램 설치 없이도 접속만으로 웹브라우저 상에서 다양한 용도의 콘텐츠를 저작할 수 있는 디자인 플랫폼입니다. 디자인 전문가가 아니어도 누구나 손쉽게 프리젠테이션, 웹브로셔, 유저 인터페이스, 문서 등 비즈니스 및 교육환경에 필요한 다양한 콘텐츠를 디자인할 수 있습니다. 또, 이렇게 제작된 콘텐츠는 클릭만으로 SNS에 공유하거나 이메일로 전달하는 등 간편하게 활용할 수 있는 장점을 가지고 있습니다.   GS인증은 엄격한 시험을 통해 품질이 우수한 소프트웨어를 인증해주는 국가공인 소프트웨어 품질인증제도로 공공기관에서 우선 구매 대상으로 지정되기도 합니다. ISO 국제표준을 기준으로 SW의 기능성, 신뢰성, 효율성, 사용성, 유지보수성, 이식성, 성능 등을 평가하고 검증을 거쳐 부여되었습니다. ㈜그로비스인포텍은 이번 GS인증을 계기로 디자인 플랫폼으로서의 기술성과에 자신감을 가지고 향후 계획된 베타서비스 준비에 최선을 다하고 있습니다. 더 나은 사용성과 기술적 안정성을 목표로 다양한 환경에 적용하고 테스트를 진행하고 있습니다. 곧이어 더 향상된 성능과 기능으로 찾아뵙길 기대하겠습니다. 감사합니다.
조회수 4054

Eclipse Memory Analyzer 소개

안드로이드 개발을 하다보면 종종 OutOfMemory(OOM)에러를 만나게 됩니다. 이전에 올렸던 포스팅에서도 이 문제로 고생을 했는데요, 메모리 누수 관련 문제는 로직 에러와는 달라서 찾기가 매우 난감한 경우가 많습니다. 이러한 메모리 누수 관련 문제를 해결하기 위한 검사 기능을 제공하는 무료 툴이 있습니다. 바로 Eclipse MAT(Memory Analyzer)(MAT)입니다.Eclipse MATMAT은 사용자로 하여금 힙 메모리의 상황을 파악하게 해주어 메모리 누수 현상과 필요없는 메모리 할당을 감지할 수 있도록 도와줍니다. 또한 자동으로 보고서를 작성하여 어떤 객체들이 메모리 누수를 일으키는지에 대한 추측을 해주는 기능을 제공합니다. MAT은 Eclipse 플러그인이기 때문에 사용하려면 Eclipse가 깔려 있어야 합니다. MAT을 설치하려면 MAT 다운로드 페이지에서 자신의 Eclipse버전에 맞는 파일을 받으시면 됩니다.How to use MATMAT을 설치하였다면 Eclipse화면에서 MAT관련 탭이 뜹니다. 탭을 클릭 하고File -> Open Heap Dump 를 누르면 힙 상황이 기록 된 hprof파일을 읽어올 수 있습니다.탭이 뜨지 않는다면Window -> Open Perspective -> Other에서 Memory Analysis 를 누르면 탭이 뜨는 것을 볼 수 있습니다.hprof 파일을 읽어오면 분석을 시작하고 결과를 Overview 화면에 보여줍니다.파이 차트의 각 부분에 마우스를 갖다 대면 옆의 Inspector 화면에 해당 객체의 정보를 보여주는 것을 볼 수 있습니다.InspectorInspector 창에서는 선택된 객체의 내용을 볼 수 있습니다. 해당 객체의 클래스명과 패키지 명 그리고 해당 객체가 가지고 있었던 변수의 내용을 살펴볼 수 있습니다.유용한 기능들MAT에서 가장 중요하게 살펴볼 기능이라고 한다면 Leak suspoect report와 Dominator tree라고 볼 수 있습니다. Leak suspect와 Dominator tree 둘 다 가장 메모리를 많이 차지하고 있는 객체에 대한 정보를 제공합니다.Leak suspectLeak suspect는 가장 큰 용량을 차지하고 있는 객체들을 좀 더 세분된 파이 도표로 보여줍니다. Problem suspect 1을 보면 현재 이 스레드 객체의 크기가 전체 힙 메모리의 크기 중 19.73%를 점유하고 있다는 것을 알 수 있습니다. 전체의 20% 가까이 차지하고 있다는 것은 이 객체를 OOM의 범인(?)이라고 생각할 근거가 됩니다. 해당 객체에 대한 더 자세한 정보를 얻고 싶다면 Details을 클릭하면 됩니다.Dominator treeDominator tree를 띄우면 현재 덤프 된 매모리 스냅 샷 중 가장 큰 용량을 차지하고 있는 객체 순으로 정렬하여 보여줍니다. Leak suspect와 비슷해 보이지만 더 구체적인 정보를 제공한다는 점이 다릅니다. 따라서 Leak suspect로 현 상황에 대한 힌트를 얻은 후 Dominator tree에서 디테일하게 살펴보는 것이 시간을 절약하는 방법입니다.상위에 있는 몇몇 객체들이 가장 의심 되는 객체들이라고 볼 수 있겠습니다. 왼쪽의 화살표를 클릭함으로써 그 객체가 참조하고 있는 다른 객체들에 대한 정보들을 볼 수 있습니다. 각 객체를 클릭하면 옆에 Inspect창의 내용이 달라지는 것을 볼 수 있습니다.실제 이 스냅 샷은 이전 포스팅의 문제를 해결하려고 떠놓은 스냅 샷인데요, 이 결과를 보고 많은 메모리가 네트워크를 통해 받아오는 스트림을 처리하고 문자열로 가공하는데에 낭비되고 있다는 생각이 들어 다른 방법으로 우회하는 방법을 썼고 결과적으로 문제를 해결 할 수 있었습니다.Android에서 MAT사용법먼저 안드로이드 기기에서 힙 덤프를 수행하여 hprof파일을 생성해야 합니다. hprof파일을 생성하기 위해서 간단하게 취할 수 있는 2가지 방법이 있습니다.1. DDMS를 이용한 추출Eclipse의 DDMS를 이용하여 힙 덤프를 추출할 수 있습니다. 아 방법을 쓰려면 앱의 메니페스트 파일에 WRITE_EXTERNAL_STORAGE 권한을 부여해야 하며, sdcard에 쓸 수 있는 권한이 있어야 합니다. 이 방법을 통해 sdcard경로에 앱 패키지명의 hprof파일이 생성됩니다.2. Heap dump method안드로이드 API에서 제공하는 메서드 중에 hprof파일을 생성하는 메서드인 dumpHprofData가 있습니다. 이 메서드는 Debug 클래스의 메서드인 것을 알 수 있는데, 이 Debug 클래스에는 앱의 상태를 점검할 수 있는 여러 유용한 메서드가 있으므로 나중에 필요하면 사용할 수 있도록 익혀두면 좋습니다.Android hporf 파일 변환앞서 설명한 방법을 적용하여 hprof파일을 추출하였어도 안드로이드에서 추출한 hprof파일은 MAT에서 받아들이는 일반적인 hprof포맷과 다르기 때문에 먼저 변환하는 과정이 필요합니다. 이러한 기능을 제공하는 것이 기본 SDK에 포함된 hprof-conv유틸입니다. 이 유틸은 SDK폴더 내의 tools폴더 안에 있는데 사용하려면 콘솔에서$ hprof-conv <안드로이드용 hprof 파일> <변환할 hprof 파일> 를 치시면 됩니다. 이제 변환된 파일을 MAT에서 열면 분석을 하실 수 있습니다.More tipEclipse Memory Analyser (MAT) - TutorialMemory Analyzer BlogJava Performance blog상기의 사이트들은 MAT과 Java의 메모리 처리에 관련된 내용을 포스팅한 사이트들입니다. 한 번 들러보면 좋은 정보를 얻을 수 있을것입니다.#스포카 #꿀팁 #개발 #개발자 #스킬스택 #스택소개 #인사이트
조회수 1409

AWS Rekognition + PHP를 이용한 이미지 분석 예제 (1/2)

OverviewAWS Rekognition은 딥 러닝 기반의 이미지, 동영상 분석 서비스입니다. Rekognition API를 사용하면 서비스에서 객체, 사람, 텍스트, 장면 및 동작을 식별하고 부적절한 콘텐츠를 탐지할 수 있습니다. Rekognition은 딥 러닝 기술을 기반으로 하고 있기 때문에 지금 이 순간에도 새로운 데이터를 통해 끊임없이 학습하고 있고, AWS에서도 새로운 레이블과 얼굴 인식 기능을 추가하고 관리합니다. 이번에는 AWS S3 Bucket에 업로드한 이미지로 이미지 분석 결과를 볼 수 있는 예제 사이트를 통해, Rekognition과 친해지는 시간을 갖도록 하겠습니다. 저는 예제 사이트를 개발하기 위해 PHP 프레임워크인 CodeIgniter 3, MAMP, Bootstrap을 사용했습니다.1. AWS Rekognition SDK 설치하기1-1) AWS Rekognition 사이트에 접속해 Download SDKs 를 클릭합니다.1-2) AWS 에서 제공하는 다양한 언어의 SDK를 확인할 수 있습니다. 저는 PHP를 사용할 것이므로 PHP 의 Install을 클릭하겠습니다.1-3) AWS SDK 를 설치할 수 있는 방법은 여러가지가 있습니다. 이 중에서 저는 Composer를 이용해 설치했습니다.curl -sS https://getcomposer.org/installer | php php -d memory_limit=-1 composer.phar require aws/aws-sdk-php 1-4) 짠! 짧은 명령어 2줄로 SDK 설치가 완료되었습니다 :)2. AWS S3 Bucket 에 업로드된 이미지를 분석하기2-1) 여기에 임의로 만든 예제 사이트가 있습니다. [이미지 선택] 과 [S3에 이미지 업로드하기] 를 통해 이미지 파일을 등록하면, 백단(Back-end) 에서는 해당 파일을 특정 S3 Bucket 에 업로드 한 후 Rekognition 에게 이미지 분석을 요청하도록 짜여있습니다. 관련 코드는 아래와 같습니다.{     "Image": {         "S3Object": {             "Bucket": "bucket",             "Name": "input.jpg"         }     },     "MaxLabels": 10,     "MinConfidence": 80 } 위의 코드 블록은 AWS Rekognition 개발자 안내서에 나와있는 예제 포맷이고, 아래의 코드는 예제 포맷을 PHP 에서 요청할 수 있는 방식으로 코딩한 것입니다.detectLabels 메소드 를 이용해 분석할 이미지가 저장되어 있는 S3 Bucket 과 이미지의 Name 을 전달해줍니다. 1) MaxLabels : 응답 받을 최대 Label 수 2) MinConfidence : Label 에 대한 최소 신뢰성 여기서 Label 이란 ‘이미지에서 발견되는 객체, 장면 또는 개념’ 이라고 생각하면 됩니다. 예를 들어 해변에 있는 사람들을 촬영한 사진에는 ‘사람’, ‘물’, ‘모래’ (객체) 및 ‘해변’ (장면) 그리고 ‘야외’ (개념) 등이 Label 이 될 수 있습니다. 자, 우주 사진을 한 번 분석해볼까요? array(3) {     ["Labels"]=>     array(8) {       [0]=>       array(2) {         ["Name"]=>         string(9) "Astronomy"         ["Confidence"]=>         float(96.8987350464)       }       [1]=>       array(2) {         ["Name"]=>         string(5) "Earth"         ["Confidence"]=>         float(96.8987350464)       }       [2]=>       array(2) {         ["Name"]=>         string(5) "Globe"         ["Confidence"]=>         float(96.8987350464)       }       [3]=>       array(2) {         ["Name"]=>         string(11) "Outer Space"         ["Confidence"]=>         float(96.8987350464)       } ...     } Rekognition이 업로드한 우주 사진을 분석하여 정확히 연관된 Label들만 반환한 것을 확인할 수 있습니다. 이 Label을 가지고 이미지 태그를 간단하게 구현했습니다.참 쉽죠 ?Conclusion이번 시간에는 AWS Rekognition 을 이용하여 기본적인 이미지 분석을 해보는 시간을 가져봤습니다. 다음 시간에는 ‘얼굴 감지 및 분석’ 기능을 응용하여 Collection 을 생성해보고, 얼굴 검색을 해보는 시간을 갖겠습니다. 참고놀라운 무료 이미지 · Pixabay핀터레스트 스타일 레이아웃 만들기 (masonry) - 생활코딩이미지에서 레이블 감지 - Amazon Rekognition글김우경 대리 | R&D 개발1팀[email protected]#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 1857

프론트엔드 개발자라면!

Angular의 A마크를 알아본 프론트엔드 개발자님!이 글은 새로운 플랫폼을 개발하고 있는 타운컴퍼니 개발팀으로 당신을 모셔보려는 글이에요.이들이 당신과 함께 일하고 싶은 동료입니다..타운컴퍼니팀은 알고 있습니다.잘 만들어진 편리한 앱과 고객의 이탈률이 얼마나 밀접한 관계가 있는가를요.1%의 스타트업에는 1% 개발자가 필요하며 그들이 1%의 플랫폼을 만든다는 것을요.자율적이며 열정이 넘치는 팀으로 즐겁게 높은 수준의 개발을 할 수 있는 환경이죠!당신에게 즐거운 회사, 좋은 동료가 되어 줄 수 있습니다.급여는 협의 후 결정이니 원하는걸 말해봐요!좋은 동료를 얻을 수 있다면 그정도가 어렵겠어요 ; )우리는 현재 플랫폼(townus.co.kr)이 많이 부족하다는걸 알고 있어요.그래서 완전 멋있게 새롭게 만들고 있는 중입니다 :)일단 우리는 협업툴 JIRA와 Confluence, Slack을 사용하고 있어요.우리팀은 Agile 칸반을 바탕으로 테스트 주도 개발, 코드 리뷰, 페어프로그래밍으로 프로젝트를 진행하고 있죠.도메인이 잘 분석된 명세서가 Confluence에 정리되어 있고 사용자를 위한 깊은 고민이 녹아있는 디자인이 Zeplin에 올라가고 있어요.- Back-End는 Django(DRF) 기반으로 개발되고 있고, AWS, Vagrant, Docker같은 기술을 사용해요.- Front-End는 Angular 5를 사용해서 개발하고 있고, Less, RxJS, Webpack 등의 기술을 사용하고 있어요.Angular 상용 프로젝트 개발 경험이 있다면 격하게 환영하며 모십니다. 리엑티브 프로그래밍, Ionic 경험이 있다면 더 좋구요!엥 이거 완전 나 아니냐!? 라는 생각이 들었다구요? 그렇다면 얼른 지원해야지 뭐해요!무엇보다 개발을 즐기고 오픈소스활동을 좋아하는 사람이라면,종종 맛있는것도 먹으면서 많은 대화를 나눌수 있지 않을까요? :>우리 개발자는 맥주제조도 할 줄 알거든요 (겁나 맛이 좋더라구요)물론 당신에 대해 알 수 있는 Github 주소와 이력서를 보내준다면 우리가 연락하기 더 쉬울거에요!아! 참고로 보충역 산업기능요원 편입도 가능하니 문의가 필요하다면 언제든 환영이에요!타운컴퍼니팀에게 연락하고 싶다면 02–561–0950 잊지말아요,[email protected]로 메일 보내준다면 언제든 답변줄게요 :D#타운컴퍼니 #개발자 #채용 #팀빌딩 #조직문화
조회수 2724

React + Decorator + HOC = Fantastic!!

React + Decorator + HOC = Fantastic!!지난 포스팅에서는 ES7의 Decorator 문법을 이용해 선언된 클래스와 그 프로퍼티들을 디자인 시간에 변경하는 법을 알아보았습니다. 그렇다면 리액트 컴포넌트와 Decorator가 만나면 어떤 시너지가 발생할까요?만약 ES7의 Decorator에 대해 모르신다면 지난 포스팅을 읽고 오시는 걸 권장합니다. 이 포스팅은 독자들이 Decorator에 대해 이미 알고 있다고 가정하고 작성됐습니다.Higher Order Component리액트 공식 문서를 보면 Higher Order Component(이하 HOC)를 다음과 같이 설명하고 있습니다.리액트 컴포넌트 로직을 재활용할 수 있는 고급 기법리액트에서 공식적으로 제공하는 API가 아니라 단순히 아키텍쳐이 설명으로는 HOC가 어떤 역할을 하는지 이해하기는 역부족이기 때문에 간단한 예제를 통해 HOC를 어떻게 작성하는지 알아보겠습니다.function withSay(WrappedComponent) {     return class extends React.Component {     say() {       return 'hello'     } render() {       return (                   {...this.props}           say={this.say} />       )     }   } } withSay 함수는 WrappedComponent를 인자로 받아 원하는 속성들을 결합해 새로운 컴포넌트를 반환합니다. 이렇게 만들어진 withSay 함수는 아래와 같이 사용 가능합니다.@withSay class withOutSay extends React.Component {     render() {     return (               {this.props.say()}           )   } } withOutSay 컴포넌트는 say 메소드를 가지고 있지 않습니다. 하지만 withSay 함수를 사용하니 say 메소드를 사용할 수 있게 됐습니다. 이처럼 컴포넌트를 인자로 받아 입맛에 맞게 바꾼 뒤 새로운 컴포넌트로 반환하는 기법을 HOC라고 부릅니다.그렇다면 HOC는 리액트에서 어떻게 사용을 해야 효율적일까요?Cross Cutting Concerns개발을 하다 보면 다음과 같은 상황에 직면하는 경우가 종종 있습니다.개발 전반에 걸쳐 반복해서 등장하는 로직그럼에도 불구하고 모듈화가 쉽지 않은 로직예를 들어 방명록 작성, 게시글 작성, 게시글 스크랩을 하는 컴포넌트들에서 유저 인증과 에러 처리의 과정이 필요하다고 했을 때 어떻게 코드를 디자인해야 할까요? 컴포넌트와 직접적으로 연관이 없는 기능들이 컴포넌트와의 결합이 너무 강해 쉽게 모듈화를 시키지 못합니다.그림 1. Cross Cutting Concerns의 예시이렇듯 코드 디자인적인 측면에서 공통적으로 발생하지만 쉽게 분리를 시키지 못하는 문제를 Cross Cutting Concerns라고 합니다. 이 문제를 끌어안고 가면 프로젝트의 코드는 쉽게 스파게티가 되고 나중에는 유지 보수를 하기 힘들어집니다.하지만 우리게에는 HOC와 Decorator가 있고 이를 이용해 이 문제를 쉽게 해결할 수 있습니다.유저 인증 문제를 HOC로 해결아래는 인증이 안된 유저에게 다른 페이지를 보여주는 코드입니다.class TeamChat extends React.Component {     constructor() {     super()     this.state = {       unAuthenticated: false     }   } componentWillMount() {     if (!this.props.user) {       this.setState({ unAuthenticated: true })     }   } render() {     if (this.state.unAuthenticated) {       return     }     return I'm TeamChat   } } 유저 인증을 전통적인 if-else 구문으로 구현했습니다. 당장 이 컴포넌트를 본다면 문제가 없어 보입니다. 어떻게 보면 정답처럼 보이기도 합니다. 하지만 유저 인증이 필요한 컴포넌트가 많아지면 상황이 달라집니다.100개의 컴포넌트에서 위와 같은 방식으로 유저 인증을 하고 있는데 유저 인증을 하는 로직이 변경된 상황을 생각해 봅시다. 100개의 컴포넌트 모두 유저 인증 코드를 바꿔야 하는 상황에 직면하게 됩니다. 전부 다 바꾸는 것도 일이지만 실수로 몇 개의 컴포넌트를 수정하지 않을 확률이 농후합니다. 당장에는 간단하지만 잠재적 위험을 안고 있는 위 코드는 아래와 같이 수정되어야 합니다.function mustToAuthenticated(WrappedComponent) {     return class extends React.Component {     constructor() {       super()       this.state = {         unAuthenticated: false       }      } componentWillMount() {       if (!this.props.user) {         this.setState({ unAuthenticated: true })       }     } render() {       if (this.state.unAuthenticated) {         return       }       return     }    } } HOC를 이용해 확장이 용이한 유저 인증 로직이 탄생했습니다!! 이렇게 만들어진 HOC는 아래와 같이 적용이 가능합니다.@mustToAuthenticated class TeamChat extends React.Component {     render() {     return I'm TeamChat   } } @mustToAuthenticated class UserChat extends React.Component {     render() {     return I'm UserChat   } } 기존의 코드와 비교했을 때 코드가 훨씬 간단해진 것을 확인할 수 있습니다. 비단 코드만 간단해진 것뿐만 아니라 아래와 같은 추가 효과를 기대할 수 있습니다.유저 인증 로직이 컴포넌트와 분리가 되어 자신이 맡은 역할에만 집중할 수 있습니다.유저 인증 로직이 바뀌어도 코드를 수정해야 할 곳은 하나의 컴포넌트뿐입니다.예시로 작성한 HOC는 최소한의 코드로만 작성된 예시입니다. 실제 제품에서 사용되기 위해서는 몇 가지 고려해야 할 사항이 있는데 이는 리액트 공식 문서를 참고해주세요.i18n 컴포넌트를 HOC로 작성채널 서비스는 한국어, 영어, 일본어를 지원하기 때문에 번역 기능이 필요했습니다. 초기에는 번역 서비스를 아래와 같이 구현했습니다.@connect(state => ({   locale: getLocale(state) }) class Channel extends React.Component {     render() {     const local = this.props.locale     const translate = TranslateService.get(locale)     return (               {translate.title}         {translate.description}           )   } } 처음에는 위와 같은 방식으로 번역 서비스를 구현하는 것이 괜찮았습니다. 하지만 번역을 제공해야 하는 컴포넌트가 많아지면 많아질수록 중복되는 코드가 많아지는 것을 보고 아래과 같이 HOC를 이용해 코드의 중복을 제거했습니다.function withTranslate(WrappedComponent) { @connect(state => ({     locale: getLocale(state)   }))   class DecoratedComponent extends React.Component {     render() {       const locale = this.props.locale       const translate = TranslateService.get(locale) return (                   {...this.props}           translate={translate} />       )    }   } } 이렇게 작성된 HOC는 아래와 같이 사용이 가능합니다.@withTranslate class Channel extends React.Component {     render() {     const translate = this.props.translate     return (               {translate.title}         {translate.description}           )   } } HOC의 작성 방법은 예시로 작성한 두 개의 HOC에서 크게 벗어나지 않습니다. 이를 응용해 자신의 프로젝트에 맞는 코드를 작성해보세요.중첩 가능한 HOCHOC는 여러 개를 중첩해서 사용할 수 있습니다.. 예를 들어 유저 인증과 i18n 서비스를 동시에 제공하고 싶을 때 두 HOC를 중첩해서 사용하면 됩니다.@mustToAuthenticated @withTranslate class Channel extends React.Component {     render() {     return (               {`Hello!! ${this.props.user.name}`         {translate.title}         {translate.description}           )   } } 마무리이상으로 리액트에서 HOC를 사용할 수 있는 상황과 작성 방법을 알아보았습니다. 본 포스팅에서 다루지는 않았지만 만능처럼 소개한 HOC에도 몇 가지 단점은 존재합니다.Component Unit Test를 할 때 문제가 있을 수 있습니다.HOC를 몇 개 중첩하면 디버깅이 힘들 수 있습니다.WrappedComponent에 직접적으로 ref를 달 수 없어 우회 방법을 사용해야 합니다.비동기 작업과 같이 사용하다 보면 예상치 못한 결과를 만날 수 있습니다.하지만 이러한 단점에도 불구하고 상속을 제공하지 않은 리액트에서 HOC는 많은 문제를 효율적으로 해결해주는 단비와 같은 존재입니다. 유명한 리액트 라이브러리들(react-redux, redux-form 등)은 이미 예전부터 HOC를 사용해 사용자들에게 편의를 제공해 왔습니다. 이러한 라이브러리들과 자신의 프로젝트가 직면하고 있는 문제에 맞는 HOC를 작성해 같이 사용한다면 우아하고 아름다운 설계에 한층 더 다가간 프로젝트를 발견할 수 있습니다.마지막으로 한 문장을 남기고 본 포스팅을 마치도록 하겠습니다.React + Decorator + HOC = Fantastic!!본 포스팅은 2017 리액트 서울에서 발표한 내용입니다. 발표 자료와 발표 영상을 확인해보세요.#조이코퍼레이션 #개발자 #개발팀 #인사이트 #경험공유 #일지
조회수 2543

JANDI 검색엔진 도입기

이번 포스트에서는 JANDI가 검색엔진을 도입하게 된 배경과 어떤 작업을 했는지 공유하려고 합니다검색엔진 도입 배경JANDI는 사용자가 입력한 메시지를 검색하고 사용자가 올린 파일의 파일명/파일 타입을 검색하는 메시지/파일 검색 기능을 제공하고 있습니다. 데이터 저장소로 MongoDB를 사용하고 있는데 검색되는 필드에 인덱스를 걸고 정규 표현식을 이용하여 DB Like 검색(“DB는 검색을 좋아한다”아니에요;;)을 하고 있습니다.초기에는 데이터가 아담했는데, 서비스가 커감에 따라 사용자 증가하면서 생성되는 데이터도 많아졌습니다. 올 초에 데이터가 많아지면서 검색이 DB에 부하를 주고, JANDI 서비스에도 영향을 주게 되었습니다. 그래서 JANDI 서비스용 MongoDB와 검색 전용 MongoDB를 분리했는데 이는 임시방편이었고 언젠가는 꼭 검색엔진을 도입하자며 마무리를 지었습니다.시간은 흘러 흘러 4월이 되었습니다. 당시 메시지 증가량을 봤을 때 올해 안에 검색엔진을 사용하지 않으면 서비스에 문제가 될 거라고 판단이 되어 도입을 진행하게 되었습니다.검색엔진 도입의 목표는 다음과 같았습니다.현재 DB Like 검색과 비슷한 검색 품질이어도 좋다. (일정때문에)검색엔진 도입을 통해 검색이 JANDI 서비스에 영향을 주지 않도록 한다.색인을 위해서 주기적으로 JANDI의 MongoDB 데이터를 가져 와야 했지만, 이 작업이 JANDI 서비스에 큰 부하를 주지 않을 거라고 생각했습니다.검색엔진 후보로는 Solr, ElasticSearch, CloudSearch, ElasticSearch Service 가 있었는데 Solr를 선택했습니다.왜냐하면제가 경험한 검색엔진이 Solr 였습니다. 더군다나 2010년 초에 접했던 Solr 비해 많이 발전한 것 같아 개발자로서의 열정과 도전 욕구가 샘솟았습니다. SolrCloud pdf, WhyNoWarAWS에서 제공하는 검색 서비스는 많은 부분을 관리해준다는 면에서 솔깃했지만, Custom Analyzer는 적용할 수 없어서 선택하지 않았습니다.ElasticSearch에 크게 흔들렸지만 경험이없다 보니 공부하면서 프로젝트를 진행한다는 부담감이 커서 다음을 기약했습니다.작업 내용1. MongoImporter, Sharding. MongoImporter 수정현재 JANDI는 MongoDB를 데이터 저장소로 사용하고 있습니다. MongoDB의 데이터를 색인하기 위해 데이터를 검색엔진으로 가져와야 하는데 Solr에서는 DataImportHandler 기능을 제공하고 있습니다. 기본 DataImportHandler로 RDB 데이터는 가져올 수 있지만 이 외 MongoDB나 Cassandra 같은 NoSQL의 데이터를 가져오기 위해서는 따로 구현이 필요합니다. 구글신에게 물어봐서 SolrMongoImporter 프로젝트를 찾았는데 문제가 있었습니다. mongo-java-driver 버전이 낮아서(2.11.1) 현재 JANDI에서 서비스 되고 있는 MongoDB(3.0.x)의 데이터를 가져올 수 없었습니다.url: Reference compatibility MongoDB Java2.11.1에서 3.2.2로 버전을 올리고 변경된 api를 적용하는 작업, 빌드 툴을 ant에서 maven으로 변경하는 작업을 하였습니다. 마음의 여유가 된다면 P/R을 할 계획입니다.여담으로 DataImportHandler 작업과 함께 검색 schema 정하는 작업을 했는데 sub-document 형식이 필요하게 되었습니다. Solr 5.3부터 nested object를 지원한다는 article을 보았는데, nested object 지원 얘기를 보니 Solr도 text search 뿐 아니라 log analysis 기능에 관심을 가지는건 아닐까 조심스레 생각해봤습니다. (역시나… 이미 banana, silk 같은 프로젝트가 있습니다. Large Scale Log Analytics with Solr 에 관련된 이야기를 합니다.). Sharding. 그리고 Document Routing대량의 데이터를 처리하기 위해 한 개 이상의 node로 구성된 데이터 베이스에 문서를 나누어 저장하는 것을 sharding이라고 합니다. SolrCloud는 shard 생성/삭제/분리할 수 있는 API가 있고, 문서를 어떻게 나눌지 정할 수 있습니다. 어떻게 나눌지는 shard 생성 시 router.name queryString에 개발한 router 이름을 적어주면 됩니다. 그렇지않으면 Solr에서 murmur Hash 기반으로 문서를 나누는 compositeId router를 사용합니다. JANDI의 검색 기능은 Team 단위로 이루어지기 때문에 TeamId를 기준으로 문서를 나누기로 하고, compositeId Router를 사용했습니다. 실제 서비스의 문서 데이터를 색인 돌려서 각 node에 저장되는 문서 개수나 메모리/디스크 사용량을 확인했는데 다행히도 큰 차이가 나지 않았습니다.하나의 문서는 TeamId와 MessageId를 조합한 “TeamId + ! + MessageId” 값을 특정 field에 저장하고 해당 필드를 uniqueKey 지정했습니다. 간단한 수정으로 문서 분배가 되는점이 좋았고, 더 좋았던건 검색시 _route_ 를 이용해서 실제 문서가 존재하는 node에서만 검색을 한다는 점이 었습니다. 4년 전 제가 마지막으로 Solr를 사용했을 때는 사용자가 직접 shards queryString에 검색할 node를 넣어주어야 했습니다..../select?q=\*:\*&shards=localhost:8983/solr/core1,localhost:8984/solr/core1SolrCloud RoutingSolrCloud Routing2Multilevel CompositeId2. analyzer, queryParser. analyzerSolr에 기본으로 있는 text_cjk analyzer를 사용하였습니다. <!-- normalize width before bigram, as e.g. half-width dakuten combine --> <!-- for any non-CJK --> text_cjk는 영어/숫자는 공백/특수기호 단위로 분리해주고 cjk는 bigram으로 분리해주는 analyzer 입니다. analyzer는 이슈 없이 완성될 거라 생각했지만 오산이었습니다. 텍스트가 들어오면 token을 만들어주는 StandardTokenizerFactory 에서 cjk와 영어/숫자가 붙어있을 때는 분리하지 못해 원하는 결과가 나오지 않았습니다. 또한 특수기호중에 ‘.’(dot), ‘_‘(underscore)가 있을 때에도 분리하지 못했습니다.nametextInputTopic검색개선_AB1021_AB제시CD.pdfStandardTokenizerFactoryTopic검색개선_AB1021_AB제시CD.pdfCJKWidthFilterFactoryTopic검색개선_AB1021_AB제시CD.pdfLowerCaseFilterFactorytopic검색개선_ab1021_ab제시cd.pdfCJKBigramFilterFactorytopic검색개선_ab1021_ab제시cd.pdf원하는 결과topic 검색개선 ab 1021 ab 제시 cd pdf그래서 색인/검색 전에 붙어있는 cjk와 영어/숫자사이에 공백을 넣어주고 ‘.’와 ‘_‘를 공백으로 치환해주는 작업을 하였습니다. 색인은 Transform에서 처리하고 검색은 다음에 알아볼 QParserPlugin에서 처리했습니다.nametextInputTopic검색개선_AB1021_AB제시CD.pdfTransform 단계Topic 검색개선 AB 1021 AB 제시 CD pdfStandardTokenizerFactoryTopic 검색개선 AB 1021 AB 제시 CD pdfCJKWidthFilterFactoryTopic 검색개선 AB 1021 AB 제시 CD pdfLowerCaseFilterFactorytopic 검색개선 ab 1021 ab 제시 cd pdfCJKBigramFilterFactorytopic 검색개선 ab 1021 ab 제시 cd pdf※ 추가 : 검색 결과를 보여줄때 어떤 키워드가 매칭되었는지 Highlight 해야했는데, 색인하기 전에 원본을 수정을 해서 Solr에서 제공하는 Highlight를 사용하지 못하게 됐습니다. 눈 앞의 문제만 바라보고 해결하기 급급했던 저를 다시금 반성하게 되었습니다.. queryParser앞에서도 언급하였지만, 색인뿐만 아니라 검색할 때도 검색어가 입력되면 검색하기 전에 붙어있는 cjk와 영어/숫자를 분리하고 ‘.’, ‘_‘를 공백으로 치환해주는 작업이 필요합니다. Solr에서 기본으로 사용하는 LuceneQueryParserPlugin 을 수정하였습니다.@Override public Query parse() throws SyntaxError { // 수정한 코드 String qstr = splitType(getString()); if (qstr == null || qstr.length() == 0) return null; String defaultField = getParam(CommonParams.DF); if (defaultField == null) { defaultField = getReq().getSchema().getDefaultSearchFieldName(); } lparser = new SolrQueryParser(this, defaultField); lparser.setDefaultOperator (QueryParsing.getQueryParserDefaultOperator(getReq().getSchema(), getParam(QueryParsing.OP))); return lparser.parse(qstr); } QParserPlugin3. DataImportHandler manageMongoImporter에서도 얘기했지만 Solr에서는 DB 데이터를 가져오는 DataImportHandler 기능을 제공 하고 있습니다. DataImportHandler Commands를 보면 총 5개의 명령을 제공하고 있는데, 그중 색인을 실행하는 명령은 full-import와 delta-import입니다. full-import 명령은 DB의 모든 데이터를 색인 하는 것을 말합니다. 색인 시작할 때의 시간을 conf/dataimport.properties에 저장하고 이때 저장한 시간은 delta-import 할때 사용됩니다. 전체 색인한다고 말합니다. delta-import 명령은 특정 시간 이후로 생성/삭제된 데이터를 색인 하는 것을 말합니다. 특정 시간이란 full-import 시작한 시간, delta-import가 최근 종료한 시간을 말합니다. full-import와는 다르게 delta-import가 종료된 시간을 conf/dataimport.properties에 저장합니다. 증분 색인 혹은 동적 색인이라고 하는데 여기서는 증분 색인이라고 얘기하겠습니다. 두 명령을 이용하여 JANDI의 메시지/파일을 색인 하기 위한 삽질 경험을 적었습니다.. 첫 번째 삽질full-import는 현재 active인 데이터를 가져올 수 있도록 query attribute에 mongo query를 작성하고, delta-import 는 특정 시간 이후에 생성된 데이터를 가져올 수 있도록 deltaQuery attribute에 mongo query를 작성합니다. 또한 deltaQuery로 가져온 id의 문서를 가져올 수 있도록 deltaImportQuery attribute에 mongo query를 작성하고, 특정 시간 이후에 삭제된 데이터를 가져올 수 있도록 deletedPkQuery 에도 mongo query를 작성합니다.<!-- data-config.xml --> <?xml version="1.0" encoding="UTF-8" ?> 정상적으로 동작은 했지만, 색인 속도가 실제 서비스에 적용하기 힘들 정도였습니다. 실행되는 mongo query를 확인했는데 다음과 같이 동작하였습니다.특정 시간 이후에 생성된 데이터를 색인하기 위해 약 (새로 생성된 문서개수 + 1) 번의 mongo query가 실행되었습니다. (batch size와 문서 갯수에 따라 늘어날 수도 있습니다.) 메신저 서비스 특성상 각각의 문서 크기는 작지만 증가량이 빠르므로 위 방식으로는 운영 할 수 없었습니다. 그래서 delta-import using full-import 를 참고해서 두 번째 삽질을 시작 하였습니다.. 두 번째 삽질full-imoprt 명령을 실행할 때 clean=false queryString을 추가하고 data-config.xml query attribute를 수정하는 방법으로 증분 색인 하도록 수정했습니다. 특정 시간 이후 생성된 문서를 가져오는 attribute인 deltaQuery와 deltaImportQuery 는 필요가 없어 지웠습니다.<!-- data-config.xml --> <?xml version="1.0" encoding="UTF-8" ?> <!-- if query="" then it imports everything --> 전체 색인은 /dataimport?command=full-import&clean=true 로 실행하고, 증분 색인은 /dataimport?command=full-import&clean=false(생성된 문서)와 …/dataimport?command=delta-import&commit=true(삭제된 문서)로 실행하도록 했습니다.정상적인 것 같았지만, 문제가 있었습니다.full-import, delta-import 명령을 실행하면 conf/dataimport.properties 파일에 전체 색인이 실행한 시작 시각 혹은 증분 색인이 최근 종료한 시간이 “last_index_time” key로 저장됩니다. 첫 번째 삽질에서 증분 색인시 delta-import 명령 한 번으로 생성된 문서와 삭제된 문서를 처리했지만, full-import와 delta-import 두개의 명령으로 증분 색인이 동작하면서 생성된 문서를 처리할 때도 last_index_time이 갱신되고 삭제된 문서를 처리할 때도 last_index_time이 갱신되었습니다.예를 들면증분색인 동작이 1분마다 삭제된 문서를 처리하고, 5분마다 생성된 문서를 처리 한다고 가정해보겠습니다. 3시 13분 14초에 delta-import가 완료되어 last_index_time에 저장되고, 다음 delta-import가 실행되기 전 3시 13분 50초에 full-import가 완료되어 last_index_time이 갱신되었다면, 3시 13분 14초부터 3시 13분 50초 사이에 삭제된 문서는 처리를 못 하는 경우가 발생합니다.Solr에서 dataimport.properties에 기록하는 부분을 수정하는 방법과 전체/증분 색인을 동작시키는 Solr 외부에서 특정 색인 시간을 관리하는 방법이 있었는데 Solr를 수정하는 건 생각보다 큰 작업이라 판단되어 외부에서 관리하는 방법으로 세 번째 삽질을 시작하였습니다.. 세 번째 삽질전체/증분 색인을 주기적으로 동작 시키는 곳에서 full-import&clean=false(생성된 문서) 처리할 때 필요한 마지막으로 색인 된 문서 id와 delta-import(삭제된 문서) 처리할 때 필요한 마지막으로 색인 된 시간을 관리하도록 개발하였습니다. 증분 색인 시 full-import&clean=false를 실행하기 전에 현재 색인 된 마지막 id 조회 후 해당 id보다 큰 데이터를 처리하도록 하였고, delta-import를 마지막으로 마친 시간을 따로 저장하다가 delta-import 실행 시 해당 시간을 전달하는 방법으로 수정하였습니다.<!-- data-config.xml --> <?xml version="1.0" encoding="UTF-8" ?> 마치며튜닝의 끝은 순정이라는 말이 있는데 IT 기술은 예외인 것 같습니다. 현재는 Solr의 기본 기능만으로 구성했지만, 고객에게 더 나은 서비스를 제공할 수 있는 시작점으로 생각하고, JANDI 서비스에 맞게 끊임없이 발전해나가겠습니다.감사합니다.참고Getting Started with SolrApache Solr 5.5.0 Reference Guide PDFApache Solr 6.1 - Analyzers, Tokenizers and FiltersRebalance API for SolrCloud issueYonik Blog#토스랩 #잔디 #JANDI #개발자 #개발팀 #개발후기 #인사이트
조회수 1951

Docker, NodeJS, Nginx! 너로 정했다!

편집자 주아래와 같이 용어를 표기하기로 저자와 협의함Docker, NodeJS, NginxOverview안녕하세요. 칼 같은 들여쓰기에 희열을 느끼는 브랜디 개발자 강원우입니다! 서버를 운영해본 개발자라면 Fatal 에러, 아웃오브메모리 에러, 또는 전날 흡수한 알코올로 인해 손을 떨다가 한 번쯤 서버를 요단강 너머로 보내봤을 겁니다. 만약 테스트 서버였다면 잠시 마음을 가다듬으면 되지만, 현재 상용 서비스 중인 서버라면 얘기는 달라집니다.님아, 그 강을 건너지 마오!이런 간담이 서늘해지는 경험은 저 하나로 족합니다. 그래서 고군분투했던 지난 날을 되돌아보면서 빠르고 안정적이며, 죽어도 죽지 않는 좀비 같은 서버 구축 방법을 쓰려고 합니다.준비물서비스를 운영할 때 가장 중요하게 여겨야 하는 건 역시 안정성입니다. 이번 글에서는 오래 전부터 개발 세계의 뜨거운 감자였던 Docker와, 단일 스레드와 이벤트 루프로 태생적으로 심플하고 민첩한 NodeJS, 마지막으로 고성능을 목표로 개발된 Nginx를 활용하겠습니다.1. DockerDocker는 컨테이너 기반의 오픈소스 가상화 플랫폼입니다. 대표적으로 LXC(Linux Container)가 있습니다. 화물 컨테이너처럼 어떠한 일련의 기능을 완전히 격리된 소프트웨어 환경에서 작동하게 만드는 기술을 말합니다.OS 가상화와 별반 다를 게 없는 것 같지만 소프트웨어적으로 작동한다는 차이가 있습니다. 다시 말해, 현재 OS의 자원을 그대로 사용하기 때문에 하이퍼 바이저가 가상환경을 위해 가상의 커널을 만드는 오버헤드가 거의 없다는 것이죠.이미지와 속도도 차이를 보입니다. 완벽하게 구성한 세팅을 그대로 이미지화할 수 있고, 해당 이미지는 Docker 위에서 완벽히 동일하게 동작하는 걸 보장합니다. 해당 이미지로 컨테이너를 제작할 땐 1~2초면 새로운 컨테이너가 생겨날 정도로 엄청나게 빠른 속도도 자랑합니다. 1)또한 Docker는 자주 사용되는 다양한 이미지를 퍼블릭 레포지토리에 공유해 사용할 수 있기도 합니다. 양파도 아닌데 특징이 계속 나오죠? 다음 글에서 Docker의 특징을 더 자세히 다루겠습니다.Docker는 리눅스만 지원했었지만, 요즘은 Docker for Windows와 Docker for Mac으로 거의 모든 OS에서 사용할 수 있습니다. 2) Docker 설치 링크는 윈도우와 맥으로 나뉘어져 있습니다. 리눅스는 아래를 참고하세요.curl -fsSL https://get.docker.com/ | sudo sh 2. NodeJSNodeJS는 구글이 구글 크롬에 사용하려고 제작한 V8 오픈소스 자바스크립트 엔진을 기반으로 제작된 자바스크립트 런타임입니다. NodeJS에는 몇 가지 특징이 있습니다.단일 스레드입니다.비동기 방식입니다.이벤트 루프를 사용합니다NPM이라는 끝내주는 동반자가 있습니다.비유하자면 예전엔 낡은 곡괭이로 큰 돌을 캐내려고 수십 명의 인부가 달라 붙었는데, 지금은 육중한 포크래인으로 거대한 돌을 쑥! 뽑아버리는 것과 비슷합니다. 굉장히 효율적이죠. NodeJS는 단일 스레드의 장점을 극대화하려고 이벤트 루프를 통해 모든 처리를 비동기로 수행합니다. 서버 사이드의 묵직한 CPU들이 빠르게 일을 처리하고 이벤트 루프에 등록된 일을 감지해 다음 작업을 빠르게 수행하는 방식입니다.마지막으로 NPM(Node Package Manager)은 NodeJS에서 사용할 수 있는 다양한 모듈을 관리해주는 프로그램입니다. 도커와 상당히 유사합니다. NodeJS에서는 무언가 기능을 만들기 전에 NPM을 먼저 뒤져보라는 말이 있을 정도로 풍부한 모듈 생태계가 구성되어 있습니다. 이는 로깅이나 날짜 계산 등 생각보다 까다로운 것들을 가져다 사용할 수 있게 도와주기 때문에 개발이 빨라집니다. NodeJS 설치링크는 여기를 클릭하세요. 이 글의 예제에서는 NodeJS의 현재시점 LTS인 codename Carbon버젼을 사용합니다!8.x 버젼이 Active LTS 상태입니다.LTS은 Long Term Support의 약자로 가장 오랜기간 지원하는 버전입니다.우선 서비스 구성을 위해 간단한 NodeJS 어플리케이션을 작성해보겠습니다.첫째, packge.json를 작성합시다.{   "name": "nodejs_tutorial_server",   "version": "0.0.0",  "private": true,   "scripts": {     "start": "node nodejs_tutorial_server.js"   },   "description": "NodeJS Tutorial Server",   "author": {     "name": "WonwooKang"   },   "dependencies": {     "express": "^4.16.3",     "uuid": "^3.2.1"   } } nodejs_tutorial_server.js 파일을 메인으로 실행합니다. HTTP Request를 처리하려면 express를 사용해야 하며, 서버를 구분하려면 uuid모듈이 필요합니다.둘째, package.json의 의존 파일들을 설치합시다.npm install npm install 전npm install 후셋째, 간단한 웹 어플리케이션을 작성합시다.var express = require('express'); var app = express(); const port = 3000;  var server = app.listen(port, function () {     console.log("Express server has started on port : "+port);  });  app.get('/', function (req, res) {     res.send('Hello?');  }); 넷째, package.json의 script start 구문을 실행하여 서버를 로드합시다.npm start 3000번 포트로 서버가 시작되었습니다!접속해볼까요?잘 접속됩니다.그런데 수정할 때마다 서버를 매번 다시 띄우면 귀찮을 겁니다. 이럴 땐 nodemon 모듈을 사용합시다. nodemon은 Nodejs의 파일이 수정되는 걸 감지해 자동으로 리로드해주는 편리한 도구입니다.nodemon설치npm install nodemon -g package.json script 변경"scripts": {     "start": "nodemon nodejs_tutorial_server.js"   }, nodemon 실행확인을 위해 약갼의 수정//nodejs_tutorial_server.js 수정 app.get('/', function(req, res) {     res.send('Hello Nodemon');  }); nodemon을 통해 어플리케이션이 실행된 모습파일수정 후 저장했을 때 자동 감지한 모습서버 잘 떴습니다!성공적으로 단 하나의 GET 요청을 처리할 수 있는 심플한 NodeJS 기반 웹 어플리케이션을 완성했습니다. 이제 웹 어플리케이션을 Docker Container위에서 구동해봅시다!3. Docker로 NodeJS Express 서버 구동하기이제 Docker Container위에서 NodeJS서버를 구동할 건데요. 그러려면 우선 Dockerfile을 작성해야 합니다. 물론 Docker의 이미지를 당겨 받고, 컨테이너를 생성하고, 또 컨테이너를 실행해서 Attach하고, 필요한 파일들을 밀어넣는 등 귀찮은 방법도 있습니다. 하지만 개발자에게 이것은 힘든 작업이므로 Dockerfile을 적극 활용합시다. (Dockerfile의 D는 대문자여야 합니다! 꼭이요)Node 도커 이미지에 어플리케이션 파일을 추가해 실행하는 Dockerfile 작성하기FROM node:carbon MAINTAINER Wonwoo Kang [email protected] #app 폴더 만들기 - NodeJS 어플리케이션 폴더 RUN mkdir -p /app #winston 등을 사용할떄엔 log 폴더도 생성 #어플리케이션 폴더를 Workdir로 지정 - 서버가동용 WORKDIR /app #서버 파일 복사 ADD [어플리케이션파일 위치] [컨테이너내부의 어플리케이션 파일위치] #저는 Dockerfile과 서버파일이 같은위치에 있어서 ./입니다 ADD ./ /app #패키지파일들 받기 RUN npm install #배포버젼으로 설정 - 이 설정으로 환경을 나눌 수 있습니다. ENV NODE_ENV=production #서버실행 CMD node nodejs_tutorial_server.js Dockerfile 내용은 node:carbon에서 :carbon이 NodeJS의 이미지 버전 Tag 입니다.Dockerfile을 통해 docker image 빌드하기docker build –tag 레포지토리명: 태그 Dockerfile 경로docker build --tag node_server:0.0.1 [Dockerfile이 위치하는 경로] 호오... 게이지가 마구마구 차오르는군요?build가 완료된 화면입니다. Dockerfile의 내용 순서가 각 Step별로 진행된 것을 알 수 있습니다.빌드 결과 생성된 이미지 확인하기docker images 빌드 명령어에서 입력했던 버전 태그까지 잘 입력된 것을 알 수 있습니다.NodeJS Carbon 이미지를 기반으로 한 node_server 이미지를 제작했습니다. 사이즈는 둘이 합쳐 1Gb가 넘을 것 같지만 실제로는 변경된 부분만 저장됩니다. 그러므로 node_server 이미지의 크기는 6~10Mb 정도입니다.생성된 이미지로 컨테이너 만들기컨테이너 생성 명령어는 아래와 같습니다.docker create --name [서버명] -p [외부 포트:컨테이너 내부포트] [이미지명:버전태그] 주의할 점이 있습니다. 포트번호 바인딩 중 왼쪽은 우리가 접속할 실제 포트이고, 오른쪽은 컨테이너 내부의 NodeJS서버 할당 포트가 된다는 것입니다. 공유기의 포트포워딩 설정과 같습니다.docker create --name NODE_SERVER_0 -p 3000:3000 node_server:0.0.1 알 수 없는 코드가 생성되었습니다. 응?컨테이너 확인하기생성한 컨테이너를 확인해볼까요?docker ps 어.. 없잖아?옵션을 추가합니다.docker ps -a 나타났다!docker ps 명령어는 현재 실행 중(STATUS:Up)인 컨테이너의 목록을 보여줍니다. -a 옵션은 실행하지 않는 모든 컨테이너를 보여줍니다. 위의 이미지에서 node_server:0.0.1이미지로부터 NODE_SERVER_0 이라는 이름으로 2분 전에 생성되었다는 걸 알 수 있습니다. 3)컨테이너 실행하기docker start NODE_SERVER_0 다시 확인하기docker ps 19초 전에 Up상태가 되었다는 걸 알 수 있다.외부 3000번 포트 -> 내부 3000번 포트로 연결되었습니다. 서버도 실행되었고요! 이제 접속해볼까요?내용도 안 바꾸고 새로고침도 빨라서 뜬 건지 잘 모르겠군요. 내용을 수정해서 다시 확인하겠습니다.//nodejs_tutorial_server.js 수정 app.get('/', function (req, res) {     res.send('Hello I\'m In Docker Container Now!');  }); 파일 변경해서 다시 확인하기//버전 태그도 0.0.2로 업해주고 docker build --tag node_server:0.0.2 [Dockerfile위치] 잘 생성되었습니다.//이미지가 잘 생성되었는지 확인하고 docker images 0.0.2가 나타났습니다.//기존 컨테이너를 삭제합니다. -f 옵션은 실행중인 컨테이너도 강제로 삭제하겠다는 뜻입니다.  docker rm -f NODE_SERVER_0 // 잘지워졌나 확인하고  docker ps -a 잘 지워집니다.//0.0.2 버젼 이미지로 컨테이너를 다시 생성합니다.  docker create --name NODE_SERVER_0 -p 3000:3000 node_server:0.0.2   //서버를 실행합니다. docker start NODE_SERVER_0 잘 실행됩니다.이제 다시 접속해봅시다.안녕! 나 지금 Docker 안에 있어!이제 Docker로 여러 개의 서버를 띄우겠습니다. NodeJS는 싱글 스레드이기 때문에 하나의 CPU를 여럿이 나눠 갖는 건 비효율적입니다. 따라서 CPU 숫자에 맞춰서 서버를 띄워보겠습니다.제 맥북엔 CPU가 4개뿐입니다.CPU수에 맞춰 추가로 생성하기추가로 컨테이너를 생성하고, 서버를 실행합니다. 서버 목록도 확인해야겠죠.서버 생성서버 실행서버 목록 확인포트번호는 같은 포트를 쓸 수 없기 때문에 3001, 3002, 3003으로 매핑합니다. 브라우저로 접속해서 확인해보겠습니다.각 포트별 접속 화면미리 만들어둔 이미지 덕분에 서버 3대를 띄우는 데에 5분도 안 걸렸습니다. 하지만 Docker 서버를 여러 개 띄워도 결국 사람의 손이 닿아야 합니다. 따라서 이번에는 NodeJS의 Cluster를 활용해 적은 수의 Docker Container를 이용하면서도 다수의 CPU를 사용하겠습니다. 또 죽은 워커를 다시 살려 서버가 다운되는 것을 막아 안정적인 서비스도 구축해보겠습니다.4. 멀티코어대응 NodeJS Cluster 구성2컨테이너용 NodeJS Cluster서버 어플리케이션 작성하기var cluster = require('cluster'); var os = require('os'); var uuid = require('uuid'); const port = 3000; //키생성 - 서버 확인용 var instance_id = uuid.v4();  /**  * 워커 생성  */ var cpuCount = os.cpus().length; //CPU 수 var workerCount = cpuCount/2; //2개의 컨테이너에 돌릴 예정 CPU수 / 2  //마스터일 경우 if (cluster.isMaster) {     console.log('서버 ID : '+instance_id);     console.log('서버 CPU 수 : ' + cpuCount);     console.log('생성할 워커 수 : ' + workerCount);     console.log(workerCount + '개의 워커가 생성됩니다\n');        //CPU 수 만큼 워커 생성     for (var i = 0; i < workerCount>         console.log("워커 생성 [" + (i + 1) + "/" + workerCount + "]");         var worker = cluster.fork();     }        //워커가 online상태가 되었을때     cluster.on('online', function(worker) {         console.log('워커 온라인 - 워커 ID : [' + worker.process.pid + ']');     });        //워커가 죽었을 경우 다시 살림     cluster.on('exit', function(worker) {         console.log('워커 사망 - 사망한 워커 ID : [' + worker.process.pid + ']');         console.log('다른 워커를 생성합니다.');                 var worker = cluster.fork();     });  //워커일 경우 } else if(cluster.isWorker) {     var express = require('express');     var app = express();     var worker_id = cluster.worker.id;         var server = app.listen(port, function () {         console.log("Express 서버가 " + server.address().port + "번 포트에서 Listen중입니다.");     });        app.get('/', function (req, res) {         res.send('안녕하세요 저는 워커 ['+ cluster.worker.id+'] 입니다.');     });  } CPU 숫자를 받아 CPU 수(4)를 컨테이너 수(2) 로 나눠 워커를 생성하는 NodeJS 클러스터 구성입니다. 이렇게만 해도 운영에는 무리가 없지만 컨테이너 2개의 구분이 안 되서 확인할 수가 없습니다.그러므로 마스터와 워커의 통신을 이용해 마스터의 uuid를 얻겠습니다. (워커와 마스터 간의 데이터 이동은 통신 말고는 메모리DB 등의 데이터 저장소밖에 없습니다)마스터의 아이디를 알아오는 로직이 추가된 어플리케이션 작성var cluster = require('cluster'); var os = require('os'); var uuid = require('uuid'); const port = 3000; //키생성 - 서버 확인용 var instance_id = uuid.v4();  /**  * 워커 생성  */ var cpuCount = os.cpus().length; //CPU 수 var workerCount = cpuCount/2; //2개의 컨테이너에 돌릴 예정 CPU수 / 2  //마스터일 경우 if (cluster.isMaster) {     console.log('서버 ID : '+instance_id);     console.log('서버 CPU 수 : ' + cpuCount);     console.log('생성할 워커 수 : ' + workerCount);     console.log(workerCount + '개의 워커가 생성됩니다\n');         //워커 메시지 리스너     var workerMsgListener = function(msg){                    var worker_id = msg.worker_id;             //마스터 아이디 요청             if (msg.cmd === 'MASTER_ID') {                 cluster.workers[worker_id].send({cmd:'MASTER_ID',master_id: instance_id});            }      }        //CPU 수 만큼 워커 생성     for (var i = 0; i < workerCount>         console.log("워커 생성 [" + (i + 1) + "/" + workerCount + "]");         var worker = cluster.fork();                //워커의 요청메시지 리스너         worker.on('message', workerMsgListener);     }        //워커가 online상태가 되었을때     cluster.on('online', function(worker) {         console.log('워커 온라인 - 워커 ID : [' + worker.process.pid + ']');     });        //워커가 죽었을 경우 다시 살림     cluster.on('exit', function(worker) {         console.log('워커 사망 - 사망한 워커 ID : [' + worker.process.pid + ']');         console.log('다른 워커를 생성합니다.');                 var worker = cluster.fork();         //워커의 요청메시지 리스너         worker.on('message', workerMsgListener);     });  //워커일 경우 } else if(cluster.isWorker) {     var express = require('express');     var app = express();     var worker_id = cluster.worker.id;     var master_id;        var server = app.listen(port, function () {        console.log("Express 서버가 " + server.address().port + "번 포트에서 Listen중입니다.");     });        //마스터에게 master_id 요청     process.send({worker_id: worker_id, cmd:'MASTER_ID'});     process.on('message', function (msg){         if (msg.cmd === 'MASTER_ID') {             master_id = msg.master_id;         }     });        app.get('/', function (req, res) {         res.send('안녕하세요 저는 ['+master_id+']서버의 워커 ['+ cluster.worker.id+'] 입니다.');    });  } Docker Container에 올리기 전 로컬 테스트를 먼저 진행합니다. 서버 구동!두 개의 워커가 실행되었습니다.똑같은 localhost:3000번 접속이지만 워커의 번호가 다릅니다.이제 워커로 CPU 수만큼 워커를 생성할 수 있게 되었습니다. 이제 워커가 어떻게 안정적으로 서비스되는지 테스트하겠습니다. 워커 킬링 테스트하기워커 킬러 로직 작성//워커 킬링 테스트     app.get("/workerKiller", function (req, res) {         cluster.worker.kill();         res.send('워커킬러 호출됨');     }); 실험에 앞서 똑같은 상황 재연 마스터 아이디를 유심히 봐주세요. 워커 킬러를 실행하겠습니다.워커 킬러 호출아래는 호출된 결과입니다. 하나의 워커가 죽자마자 곧장 다른 워커가 태어나(?) 3000번을 Listen하기 시작했습니다. 워커 킬러가 호출된 화면이제 워커 킬러를 여러 번 호출해보겠습니다. CMD+R을 꾸욱 눌러 연속으로 킬링해봤는데 아래 화면처럼 바로 살아납니다.접속해서 현재 워커를 확인합니다.위의 화면처럼 마스터의 UUID가 그대로인데 워커만 교체되었습니다. 준비는 끝났습니다. 이제 Docker를 이용해 2명의 워커를 가진 2개의 NodeJS서버를 실행하고, 4개의 귀여운 CPU를 불살라봅시다! 5. Docker로 NodeJS Cluster 서버 실행하기docker build --tag node_server:0.0.3 /Users/kww/eclipse-workspace/nodejs-for-article docker create --name NODE_SERVER_0 -p 3000:3000 node_server:0.0.3 docker create --name NODE_SERVER_1 -p 3001:3000 node_server:0.0.3 docker start NODE_SERVER_0 docker start NODE_SERVER_1 cluster가 적용된 2개의 컨테이너 start0.0.3번 이미지로 생성된 2개의 컨테이너 서버가 무사히 로드되었습니다. 이제 접속해서 확인해볼까요?cluster가 적용된 2컨테이너 4서버 구동화면WOW! 2개의 URL, 2개의 UUID, 각 2명의 워커까지. 완벽한 2.2.2입니다. 마치 홍진호를 보는 듯한 서버 현황입니다. 이제 워커 킬러로 습격해보겠습니다.워커 킬러 습격 후위의 이미지를 보면 3000번 포트서버에서 13명, 3001번 포트서버에서 22명의 워커가 사망했습니다. UUID를 통해 2개의 서버에서 일정량의 워커가 매우 안정적으로 서버를 지키고 있는 걸 알 수 있었습니다.지금까지 2개의 컨테이너로 4개의 서버를 구성해보았습니다. CPU 숫자와 나눠지는 수에 따라 컨테이너의 수, NodeJS 클러스터 서버의 수를 유동적으로 조정할 수 있습니다. 전에 운영하던 API서버는 16코어 서버였고, 로드벨런서 및 기타 작업용 1코어의 여분을 남기고 15코어 / 3 으로 5개의 워커를 가진 3개의 NodeJS서버를 도커 컨테이너로 운영했었습니다.여기서 문제점이 생깁니다. 우리는 어떤 서비스를 할 때 하나의 도메인을 쓰는데 포트번호가 2개죠? 어떻게 해야 할까요. 여기서 바로 한참을 기다렸던 불곰국의 Nginx가 등장합니다.6. Nginx로 로드밸런싱 하기Nginx은 “더 적은 자원으로 더 빠르게”를 지향합니다. 러시아의 이고르 시쇼브(Игорь Сысоев)는 Apache에서 10,000개의 접속을 동시에 다루기 힘든 걸 해결하려고 Nginx를 개발합니다.Nginx는 NodeJS와 유사하게 싱글 스레드 방식에 이벤트 드리븐 구조 사용하는 오픈소스 HTTP서버로 최근 아파치의 점유율을 상당히 뺏고 있는 서버입니다. 다운로드 링크를 아래에 써두었습니다.Nginx 설치WindowNginx 다운로드Macbrew install nginx Linuxapt-get install nginx or yum install nginx Nginx 설치 성공Nginx 기본 접속 화면서버 조작방법서버 시작 : nginx 서버 중지 : nginx -s stop 서버 재시작 : nginx -r reload (맥에선 이건 안되는듯?) 기본 설정은 8080포트로 되어있습니다. 원하는 포트르 로드벨런싱 설정을 해보겠습니다. Nginx 로드밸런싱 설정아래는 Nginx의 로드밸런싱입니다.#http블럭 내부에 추가     #NodeJS 서버 로드밸런싱     upstream nodejs_server {         #least_conn;         #ip_hash;         server localhost:3000 weight=10 max_fails=3 fail_timeout=10s;         server localhost:3001 weight=10 max_fails=3 fail_timeout=10s;     }        #3333번 포트 NodeJS 서버로 연결     server{         listen               3333;         server_name  localhost;                location / {             proxy_pass http://nodejs_server;         }     } 로드밸런싱이 잘 적용되었는지 확인해보겠습니다. 로드밸런싱 적용 이후모든 브라우저에서 3333번으로 접속했는데 서로 다른 2개의 서버가 번갈아 접속되고, 워커가 가끔 바뀌는 걸 확인할 수 있습니다. 이번엔 로드밸런서로 워커 킬러를 호출하겠습니다.로드밸런싱 포트인 3333번 포트로 여러 번 호출결과 확인Nginx 로드밸런서가 확실하게 작동하는 걸 확인할 수 있었습니다. 위의 이미지에서 서버가 자꾸 바뀌는 모습을 볼 수 있는데, 이는 세션이 유지되지 않기 때문입니다. 실제 서비스에서는 세션의 유지를 위해 ip_hash 옵션이 꼭 필요합니다.ip_hash : 동일한 IP의 접속은 같은 서버로 접속하도록 하는 옵션입니다.  least_conn : 가장 접속이 적은 서버로 접속을 유도하는 옵션으로 ip_hash와 같이쓰입니다. Conclusion자, 고생하셨습니다. 여기까지 Docker와 NodeJS, Nginx를 이용해 관리하기 쉽고, 일부러 죽여도 죽지 않는 안정적인 서비스 환경을 구축해봤습니다. 한 가지 주의할 점이 있습니다. NodeJS의 Cluster는 죽은 워커를 바로 살리는데 싱글스레드여서 그런지 그 속도가 정말 어마어마합니다. 따라서 NodeJS Cluster를 사용할 땐 여러 핸들링에 신중하세요. 모든 promise에 반드시 catch를 달아 핸들링하고, 오류가 날 것 같은 로직엔 반드시 try - catch를 달아 핸들링을 해야 합니다. 그렇지 않으면 다시 살아나는 워커에 의해 서버의 자원이 고갈될 수 있습니다.예전에 16코어 서버를 운영할 땐 서버 자원에 비해 사용자가 적어서..(눈물) 5워커 2개의 서버만 구동하고 여유를 두었습니다. 그리고 서버 패치가 있을 때 3번째 서버를 대기시켰습니다. 앱에서 업데이트가 완료되는 시점에 Docker Container를 바꿔치기 하는 방식으로 Non-Stop서비스를 운영했죠. 혹시 코어가 빵빵한 여유 서버가 있는데 재빠르고 좀비 같은 서비스를 구성해야 한다면 위와 같은 환경 구축을 강력히 추천합니다. 지금까지 긴 글을 읽어주셔서 감사합니다.ps. 글 쓰다 보니 해가 떴네요. 하하.참고1) 가상 머신은 작은 이미지라도 기가바이트 단위의 사이즈와 Load되기까지 상당한 시간이 소요된다.2) 그러나 Windows의 경우, Hiper-v위에 리눅스를 띄워 도커를 구동한다. Mac에서도 가상 머신 위에서 구동된다. 따라서 성능적인 강점은 리눅스에만 적용된다.3) 도커에서는 NAME 속성을 지어주지 않으면 알아서 이름을 지어주는데 romantic한 단어가 많다.글강원우 과장 | R&D 개발2팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발팀 #개발자 #개발환경 #업무환경 #인사이트 #경험공유
조회수 1861

Genius? Jininus!

나는 인생을 살면서 많은 "천재"들을 만났다. 스타트업에 있다보면 더더욱 "영재""천재"로 불리는 수 많은 사람들을 보게 된다. 그들은 학문적으로 놀라운 성과와 스펙을 보유하고 있었다. 아마 당신이 한 회사를 운영하는 사람이거나 인사 담당자라면 분명 혹할 것이다. 하지만 정작 나는 같이 일하고 싶었던 사람이 단 한 명도 없었다. 주변에서는 천재들과 같이 일하면 성공할 것이라고 생각하지만, 사업적 결과물과 두뇌는 별개의 문제라고 나는 생각한다. 대단한 능력을 가지고도 빛 없이 사라진 사람들을 얼마나 많이 보았는가. 물론 나도 대단한 사람과 일하고 싶다. 그러나 그 기준을 "영특함"에 국한시키고 싶지는 않다. 사업적으로 혹은 사회적으로 더 나은 미래를 후손에 물려주기 위해서는 그 이상의 "무언가"가 필요하다. 지금부터 나에게 그 "무언가"를 가르쳐 준 "진짜 천재"에 대한 이야기를 하고자 한다. 그에 대한 이야기를 하기 전에 나에 대한 이야기를 가볍게 하고자 한다. 5년 전만 해도 나는 비전과 목표가 없었다. 어려서 부터 돈 욕심만 많았다. 대학교를 다니면서도 돈을 벌 수 있는 방법이면 수단과 방법을 가리지 않았다. 한 일화로 당시에 학원 강사 아르바이트를 하고 있었는데 도매시장에서 트렌디한 문구류를 사와 수업을 가르쳤던 중/고등학생에게 팔았다. 시간과 행동에 제약이 있는 학생들은 수업 시간에 벌어지는 소소한 쇼핑에 돈을 지불했다. 그러나 끝이 좋지 않았다. 학생의 부모님에게 알려져 결국 학원에서 해고 조치 되었다. 지금의 내가 돌이켜보면 엄청나게 창피한 일이다. 학생들에게 단순한 편리와 재미를 줄 순 있었지만, 돈 말고는 남는게 없었다.20대의 대부분은 가치 없는 돈벌이의 연속이었다. 혹자는 말한다. 우선 돈 벌고 가치 있는 곳에 쓰면 된다고. 그러나 이런 식의 무의미한 접근은 내가 가야할 길이 아니라고 느꼈다. 인생에서 가치 있는 일을 찾아야 했다. 그때 발견했다. 혁신, 도전, 열정이 정말 실천되고 있는 세계가 있다는 것을. 스타트업이라는 단어조차 생소했던 시기였다. 심지어 IT라는 분야를 그 전까지 제대로 공부해 본 적도 없었다. 스타트업의 "ㅅ"도 모르던 내가 이 세계에 적응할 수 있는 방법은 뛰어난 사람들과 함께 시작하는 것 뿐 이었다. 온갖 미사여구로 괜찮은 연봉과 복지를 내세우는 기업도 꽤 있었다. 그러나 나에게 가장 중요한 건 "내가 성장할 수 있는지"와 “구성원”이였다. 꽤나 당연한 조건으로 기업을 찾았음에도 불구하고 찾을 수가 없었다. 그러다가 첫 스타트업으로 선택한 게 라우드소싱 이라는 작은 팀이었다. (찾게 된 과정에 대해서는 다른 글을 통해 소개하겠다) 안정적인 연봉도 없고, 확실한 미래도 없었지만 내가 이 팀과 같이 해야겠다 결정한 건 "권진" 이라는 단 한 사람 때문이었다. 모든 기업이 그렇지만 누구나 회사에 합류하면 3개월간의 수습기간을 거친다. 스타트업이라고 예외는 아니다. 오히려 더 냉정하게 자신을 되돌아 보는 시간을 가져야 한다. 나는 내 스스로를 입증하고 싶었다. “제가 3달 안에 이 회사가 성장할 수 있는 계약들을 가져오겠습니다. 그 정도 능력도 발휘 못한다면 제 발로 나가겠습니다” 3달 동안 권진은 일에 대해서 전혀 간섭하지 않았다 . 팀워크에 있어서 가장 중요한 부분은 신뢰라고 생각한다. 하지만 신뢰라는 부분이 친하다고 해서 혹은 비전과 목표가 같다고 해서 생기는 것이 아니다. 각자의 위치에서 최고의 성과를 목표로 내고, 한계를 뛰어넘어 성장하는 모습을 보여줄 때 강력한 신뢰가 생긴다. 서로가 같이 일하고 싶은 마음을 만들어 주는 것.이게 팀워크의 핵심이다. 나는 나대로 권진은 권진대로 각자가 맡은 일들을 완벽하게 수행했고, 우리는 그 일들을 하나의 사업으로 만들어 갔다. 그는 나에게 따로 주저리 주저리 피드백을 하지 않았다. 하지만 행동으로 결과물의 중요성을 보여주었고, 나는 3달동안 7건의 B2B 계약을 성사시켰다.애초에 같이 할 사람을 정할 때는 모든 부분을 면밀히 살피고 고민해야 하지만, 내가 같이 하기로 결정 했다면 상대가 최고의 결과물을 낼 수 있도록 믿어주는 것. 내가 배운 첫번째 교훈이었다.실력을 보여주었다고 환상적인 Fit일까? 누구든 본인이 만들어 내는 결과물을 혼자만의 능력이라고 오판하기 쉽다. 내가 영업처를 설득하고, 계약서를 체결해 왔기 때문에 내가 없었으면 이 계약도 없었을 것이다. 감각적이고 환상적인 디자인을 뽑아냈는데 이건 순전히 나의 재능에 의한 것이다. 팀원들이 이런 생각들을 하기 시작한다면 그 팀은 단시간 내에 모래성처럼 무너질 것이다. 권진은 개인이 만들어 내는 결과물도 팀원들이 각자의 분야에서 해 온 노력들의 최종산출물이라고 생각한다.영업처를 설득할 수 있었던 건, 우리 팀이 환상적인 서비스를 만들어 주었기 때문이다.나의 디자인은 기획팀과 마케팅팀의 노력을 하나로 담은 것 뿐이다.톱니바퀴처럼 팀원들이 맞물려 돌아가며 서로의 존재에 대해 감사함을 느낄 때 놀라운 일이 벌어진다. 내가 배운 두번째 교훈이다.권진이 지켜온 2가지 요건이 계속 좋은 사람을 팀으로 영입할 수 있었던 강력한 요소였다고 생각한다. 나의 실력을 우리 팀에 입증하는 것. 나의 결과물은 우리 팀 노력의 산물 이라는 것.권진과 함께 일하며 느낀 그의 주요한 능력은 개발도 디자인도 아니었다. (물론 이 2가지도 잘한다)팀 내의 균형을 맞추고 팀원들이 끊임없이 성장하게 도와주는데 있다. 개성 넘치는 팀원들을 하나의 비전으로 묶어서 성장할 수 있게 하는 사람을 나는 살면서 권진 이외에는 아직 본 적이 없다. 장담컨데, 만약 현재 더팀스 대표가 권진이 아니라 다른 사람으로 바뀐다면 팀원들은 전부 팀을 나갈 것이다. (연봉이 대폭 인상된다 할지라도)그래서 나는 이걸 Jin in Us 라고 명칭했다. 권진이라는 확실한 구심점 안에 개성넘치는 팀원들이 한 몸처럼 목표로 향해가는. 나는 앞으로 대표라는 역할을 할 생각이 없다. 권진 이라는 사람보다 대표의 역할을 충실히 수행할 자신이 없어졌기 때문이다.리더십이라는 분야가 있다면 그는 천재가 아닐까?내가 우리 팀에 합류시키고 싶은 사람이 있을 때면 하는 단골멘트로 이 글의 마무리를 짓는다.“우리 팀의 권진을 만나보세요. 분명히 함께 하고 싶을 겁니다”#더팀스 #THETEAMS #천재디자이너 #풀스택개발자 #CEO #리더십 #경험공유 #팀원자랑 #팀원소개 #회사의자랑
조회수 3942

PHP Codeigniter 환경에서 VUE 사용해보기

Overview이번에는 PHP Codeigniter 기반의 서비스에 VUE를 적용시키려고 고민했던 것들을 나누려고 합니다. VUE JS는 가상 DOM을 활용하여 실시간으로 반응 컴포넌트를 제작할 수 있는 프레임워크입니다. 또한, VUE-ROUTER 및 VUEX라는 컴페니언 라이브러리를 통해 url 라우팅 및 전역상태를 관리하기에도 탁월하죠. VUE와 다른 프레임워크와의 비교 부분은 여기를 참고해주세요. 브랜디의 관리자 서비스는 PHP Codeigniter 프레임워크로 제작되었습니다. 하지만 관리자 서비스의 규모가 점점 커지고 기능이 다양해지면서 “자주 사용하는 기능을 묶어 컴포넌트화하자!”라는 숙제가 남아 있었죠. 요즘 잠깐의 여유가 생겨 이때다 싶었습니다. 관리자 서비스에 VUE를 도입하기 위한 시도를 시작했는데요. 얼마 지나지 않아 문제점에 봉착했습니다. 바로 IE9.0…. 개발자의 숙적 IE가 또 한 번 발목을 잡았습니다. 임포트가 되지 않아….VUE를 좀 더 편리하게 사용하려면 JS의 모듈화가 필요했지만, ES2015에서는 import 혹은 require 구문을 지원하지 않아 불편하고, arrow 함수 또한 사용할 수 없습니다. 게다가 VUE의 JAX 탬플릿 구문을 사용할 수도 없었죠!! 뭔가 배보다 배꼽이 더 커질 것 같은 조짐이 보였습니다.결국 Webpack의 도움 없이 VUE를 적용하려던 시도는 여러 가지 난관을 만났고, Codeigniter 프로젝트 내부에서 Webpack을 사용하는 방법을 연구하기 시작했습니다. Webpack은 모듈 번들러입니다. Webpack의 메인 페이지를 방문하면 아래 네 개의 슬로건이 빙글빙글 돕니다.Bundle your scriptsBundle your imagesBundle your stylesBundle your assets아래의 이미지는 Webpack이 무엇을 하는 녀석인지 잘 설명해줍니다.Webpack은 실제로 번들러라고 광고하는것 처럼 Only Webpack 빌드만으로는 소스 파일들을 모아줍니다. 만약 webpack-dev-server로 실행하면 websocket을 통해 소스가 변경됐을 때 실시간으로 화면을 갱신해주는 개발 툴 제공 정도의 역할 밖에 없습니다. (…충분히 훌륭하잖아?)대부분의 기능은 엄청난 확장성을 가진 webpack의 설정으로 모듈로서 작동할 수 있죠. 예를 들면 Babel은 우리의 발목을 잡았던 IE를 위해 ES6로 작성된 js 문법을 IE에서 사용할 수 있는 ES5문법으로 너무나 쉽게 트랜스컴파일할 수 있습니다.하지만… 관리자 서비스는 위에서 언급했듯이 Codeigniter 기반입니다. 따라서 완벽히 VUE와 API서버를 분리하려면 로그인, 메뉴구성, 헤더, 푸터 등 PHP 기반으로 제작된 모든 기능들과 인증 등 기존 방식을 전부 새로 만들어야만 VUE를 온전히 사용할 수 있습니다.문제점들을 모두 해결하고 넘어가기엔 여유가 부족하기 때문에 조금씩 적용하자고 생각했습니다. 덕분에 webpack-dev-server의 실시간 소스 반영 기능을 포기해야만 했죠.(눈물) 우리의 서버는 node기반이 아닌 apache-php 기반이었기 때문입니다.자, 그럼 Codeigniter 프로잭트 하위에 웹팩을 포함시켜 Hello World까지 가는 짧은(?)여정을 시작해봅시다.Hello world로 가는 여정Node, npm 설치맥에서도 유사한 명령어로 제작할 수 있도록 CMD 위주로 진행하겠습니다. 먼저, 여기를 클릭해 Node를 설치합시다. 8.11.3 LTS버전으로 진행했습니다.맥에서는 Homebrew를 통해 간편하게~brew install node 설치 확인npm 잘 설치되었네요.web pack 폴더 생성 및 이동mkdir webpack cd webpack nom init으로 초기화npm init webpack, vue, babel 설치npm install -D webpack webpack-cli webpack-dev-server npm install -D vue-loader vue-template-compiler npm install -D babel-core babel-loader babel-preset-es2015 여기서 VUE는 설치하지 않습니다! 왜냐하면 VUE.js는 로딩만 하면 되고 필요하지 않습니다! (읭?) VUE는 Codeigniter view에서도 사용해야 하기 때문에 해당 view에서 import 해줍니다. 따라서 VUE 컴포넌트가 들어가는 시점에는 이미 전역에 vue.js 가 있습니다. 따라서 굳이 각 모듈마다 VUE를 import 했다가 webpack 설정에서 다시 vue.js를 제외할 필요는 없습니다.VUE와 template 태그를 로딩할 수 있는 로더도 설치하고, 트랜스컴파일을 위한 바벨, IE9를 지원하기 위한 es2015프리셋도 함께 설치합니다.webpack 빌드명령어 package.json의 script부분에 추가"scripts": { "build": "webpack --mode production", "build-dev": "webpack --mode development",   } 이제 VUE를 빌드할 명령어를 작성합니다. 위처럼 두 가지 명령어를 제작해두면, 추후 env를 통해 webpack.config.js를 분기시켜 원하는 환경으로 빌드할 수 있습니다. 또한 production 모드로 빌드할 땐 자동으로 옵티마이저 - uglify 내장 플러그인이 적용되어 익숙한 min.js형태로 빌드되며 development를 빌드할 땐 사람이 알아볼 수 있는 형태로 빌드되고, debugger 코드 또한 살아있습니다.weboack.config.js 작성const { VueLoaderPlugin } = require('vue-loader'); module.exports = {   entry: {     HelloWorld: './src/main.js'   },    module: {     rules: [       {         test: /\.vue$/,         loader: 'vue-loader',       },       {         test: /\.js$/,         loader: 'babel-loader',       }     ]   },    resolve: {     alias: {       'vue$':'vue/dist/vue.esm.js'     }   },    plugins: [     new VueLoaderPlugin()   ]  } webpack.config.js 가 없다면 생성한 후 위와 같이 작성합니다..babelrc 작성{     "presets": ["es2015"] } 테스트용 파일 작성1)main.js 작성import HelloWorld from './HelloWorld.vue' Vue.component('hello-world', HelloWorld); 2)HelloWorld.vue 작성 [removed] export default {   name: 'app',   data: () => {     return {       word1: 'Hello',       word2: 'World'     }   }  } [removed] 테스트 빌드npm run build-dev 빌드를 할 땐 기본적으로 ‘/dist/’ 하위에 소스코드가 떨어집니다. 자, 여기까지 진행하셨다면 폴더 구조는 다음과 같을 것입니다.지금까지 진행한 파일 모습입니다.뷰 컴포넌트가 잘 제작되고 등록되는지 확인하려면 기본 빌드 폴더인 dist 폴더에 Test.html을 작성해 브라우저로 열어봅시다.확인용 html 파일 작성<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>VUE Test</title>     <!-- VUE 플러그인 -->     [removed][removed] </head> <body>                     [removed][removed]     [removed]         new Vue({             el: '#vue'         })     [removed] </body> </html> 잘 나옵니다.정상적으로 VUE가 적용된 것을 확인합니다.코드이그나이터 설치이제 코드이그나이터 프로젝트 내부에서 VUE 컴포넌트를 출력해보기 위해 코드이그나이터 프로젝트를 생성합시다. 먼저 Codeigniter와 XAMPP를 다운로드 받습니다.Codeigniter 받으러 가기XAMPP 받으러 가기프로젝트 폴더 하위에 Codeigniter 프로젝트용 폴더를 생성합니다.mkdir codeigniter-with-vue-webpack cd codeigniter-with-vue-webpack 다운받은 Codeigniter를 해당 폴더에 압축 해제하면 Codeigniter 설치가 끝납니다.XAMPP 설치 및 DocumentRoot 변경XAMPP를 설치하고 DocumentRoot를 테스트 프로젝트 폴더로 설정한 뒤 아파치를 실행합니다.Codeigniter 프로젝트가 생성되었고, 서버 실행이 완료되었습니다. webpack 폴더를 Codeigniter 프로젝트 하위로 이동node-modules는 너무 크기 때문에 기본 파일만 복사하고, npm install로 설치합니다.Codeigniter에서 VUE를 사용하기 위한 webpack dist설정기존의 프로젝트에서 스크립트를 모아두는 폴더 하위로 빌드 결과 파일을 보내기 위하여 webpack 빌드 시 dist 폴더가 아닌 /application/scripts/vue/hello_world 하위로 빌드 결과 파일이 생성되도록 설정합니다.// 기존 module.exports = {   entry: {     HelloWorld: './src/main.js'   },    //... 생략 } // 변경후 module.exports = {   entry: {     '../../application/scripts/vue/hello_world/HelloWorld.js': './src/main.js'   },    //... 생략 } Codeigniter의 load->view 기능을 활용하여 파일 작성1)header.php// application/views/common/header.php <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>VUE Test</title>     <!-- VUE 플러그인 -->     [removed][removed] </head> 2)실제 view// application/views/vue/hello_world/vueTestPage.php <?php $this->load->view( 'common/header' ); ?> <body>                 [removed] [removed]     [removed]         new Vue({             el: '#vue'         })     [removed] </body> <?php $this->load->view( 'common/footer' ); ?> 3)footer.php// application/views/common/footer.php </html> 실제 프로젝트 구성과 유사하게 header, body, footer로 나누어 파일을 작성해봅니다. 실제로는 더 복잡하지만 이 정도만 나누겠습니다.Codeigniter 테스트용 컨트롤러 작성// application/controllers/Vue.php <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');   class Vue extends CI_Controller {      public function index()     {         $this->load->view('vue/vueTestPage');     }  } 정말 심플(?)한 테스트용 파일 작성이 모두 끝났습니다! 이제 잘 작동하는지 확인해볼까요?코드이그나이터에서 helloworld 출력짜잔이번엔 문제의 IE에서 확인해봅시다.IE9.0 환경에서 확인IE에서도 무사히 출력되는군요. 이제 코드이그나이터 환경의 프로젝트에서도 IE까지 지원하며 무사히 VUE를 사용할 수 있게 되었습니다! (시간이 없어서 가상머신에 IE9가 설치된 윈도우7까지 테스트하진 못했습니다!) 모든 작업이 완료한 후, 파일 폴더 구조는 아래와 같습니다.붉은 네모 부분이 실제로 제작하거나 수정한 파일들입니다.Conclusion여기까지가 Codeigniter 프래임워크 환경에서 webpack + vue를 사용하기 위한 웹팩의 설정 과정 및 테스트 결과였습니다. php 서버를 사용해야 하기 때문에 webpack-dev-server의 핫리로드 기능을 사용하지 못하는 건 매우 안타까운 일입니다. 하지만 짧은 시간에 신기술을 도입하면서도 수많은 리스크를 회피할 수 있다는 건 나쁘지 않은 선택이라 생각합니다.위의 웹팩설정을 조금만 활용한다면 다른 프레임워크 프로젝트에서도 무리없이 VUE를 사용할 수 있을 겁니다! 비슷한 고민을 하셨던 개발자님들… 집에 가기 전 말고 오전에 Webpack을 설치해보세요. 안 그러면 저처럼 집에 못갈 수도 있으니까요!참고.gitignore 작성, index.php 제거 등은 내용에 포함하지 않았으며, 아래의 링크로 자세히 알 수 있음.Codeigniter index.php 없애기글강원우 과장 | R&D 개발2팀[email protected]브랜디, 오직 예쁜 옷만 #브랜디 #개발자 #개발팀 #인사이트 #경험공유 #PHP
조회수 965

Top Five Games Made in Seoul

 Based in a country seeing huge growth in its video game sector, Korean game studios have been releasing big hits in the past few years. As seen in our previous post, the Top Ten Things to do in Seoul for Gamers, Koreans of all ages have been diving headfirst into gaming culture. Mostly focused on digital gaming like mobile and PC, the gaming industry has been growing at an annual rate of 4.3% (Statista). Although the most popular games by far are MMORPGs, we tried to diversify the list for all types of players to enjoy.  MapleStory  MapleStory - Source: maplestory.nexon.net  MapleStory has been around for years and only continues to be a huge favorite in Korea and around the world. An extremely social game, it sees players work to improve their own character’s skills while chatting, looting and even getting married to others. The 2D element gives the game an almost retro vibe, even though it has been updated many times, including the release of a sequel. Create your avatar, choose your class and find out how to fulfill your quest by defeating the Black Mage.   Ragnarok Online  Ragnark Online - Source: mmoculture.com  You guessed it, another tried and true MMORPG. Based on a comic of the same name by Lee Myung-jin, this 3D game features a constantly changing timeline that players must interact and adapt with within the specific world. The key part of the game is choosing the “job” of your character. With that choice come make-or-break strengths and weaknesses that can determine the gameplay for you. Starting at 13 but growing to 50 classes, the choice is daunting but crucial as your job can change as well. Whether you want to try out the newest sequel, the mobile version, or even watch the animated TV series, Ragnarok Online is definitely one to check out.   Blade & Soul  Blade and Soul - Source: www.bladeandsoul.com  Developed by one of the most notable studios in Korea, NCSOFT, this fantasy martial arts game was only released in Western countries 2 years ago, but had been out in Korea and Japan since 2012. A super renowned character customization system gives the game an update from the more traditional fighting style games. There are four playable races that reference the four Chinese Symbols of Azure Dragon, Vermillion Bird, Black Tortoise and White Tiger. Definitely a must for fans of combat-driven games.   ANIPANG  ANIPENG - Source: anipang-for-kakao.en.softonic.com  Finally a change of pace! ANIPANG is a mobile puzzle game, and also the first Korean game to reach 20 million downloads. Filled to the brim with squishy animal faces, this match-3 style game can be enjoyed alone or by competing with friends. Whether it’s killing time waiting for the bus or just wanting to beat that one tricky level, ANIPANG can be played anywhere at any time.   Lineage  Linage - Source: mmogames.com  Rounding out this list is Lineage, a video game series released in the 90s and still receiving sequels and spin-offs to this day. Taking you back to medieval times, this game is one of the most successful MMORPGs to date. The realistic siege warfare and constant lore updates makes it a fun and addictive way to pass the time. The mobile release of the game broke records and had fans eager to play, so don’t miss out. 

기업문화 엿볼 때, 더팀스

로그인

/