스토리 홈

인터뷰

피드

뉴스

조회수 1733

TDD(파이썬) : 테스트 잘하고 계신가요?

Overview반복적인 테스트에 지쳐가고 있던 무렵, TDD방법론을 접하게 되었습니다. TDD(Test Driven Development)는 테스트 주도적인 개발로 소스코드 작업 전에 테스트 코드를 먼저 작성해 소스수정에 대한 부담을 덜고 디버깅 시간을 줄일 수 있습니다. TDD 장점소스코드의 품질이 높다.재설계 및 디버깅 시간이 절감된다.TDD 단점단기적 코드일 경우 생산성이 떨어진다.실제 코드보다 테스트 케이스가 더 커질 수 있다.파이썬에서 TDD가 필요한 이유1) 파이썬에는 정적 타입 검사 기능이 없다. (Python 3.6 에서는 정적 타입 선언 가능)2) 동적언어이기 때문에 TDD를 하기에 적합하다.3) 파이썬은 간결성과 단순함으로 생산성이 높은 반면 런타임 오류가 발생할 수도 있다.4) 파이썬을 신뢰할 수 있는 유일한 방법은 테스트를 하는 것이다.파이썬 테스트 모듈 unittest이번 글에서는 unittest를 사용해 단위 테스트를 해보겠습니다. unittest는 이미 내장되어 있어 따로 설치하지 않아도 되는 표준 라이브러리입니다. 사용방법1) import unittest 2) unittest.TestCase 상속받는 하위 클래스 생성3) TestCase.assert 메소드를 사용하여 테스트 코드를 간략화4) unittest.main() 실행그럼 간단한 예제로 단위 테스트를 해보겠습니다.1.사칙연산 함수를 추가합니다.def add(a, b):     return a + b   def substract(a, b):     return a - b   def division(a, b):     return a / b   def multiply(a, b):     return a * b 2. unittest.TestCase 상속받아 테스트 클래스를 생성합니다. 아래는 각각의 함수 결과값을 비교해 텍스트를 출력하는 코드입니다.import unittest class TddTest(unittest.TestCase): def testAdd(self):         result = lib_calc.add(10, 20)         if result == 30:             print('testAdd OK')      def testSubstract(self):         result = lib_calc.substract(20, 30)          if result > 0:             boolval = True         else:             boolval = False if boolval == False:             print('testSubstract Error')      def testDivision(self):         try:             lib_calc.division(4, 0)         except Exception as e:             print(e)      def testMultiply(self):         result = lib_calc.multiply(10, 9)          if result < 100>             print('testMultiply Error') if __name__ == '__main__':     unittest.main() 3.결과: 해당 조건에 만족해 작성한 텍스트가 출력됩니다.이번에는 unittest에서 지원하는 TestCase.assert 메소드를 사용해 간략하게 소스를 수정해보겠습니다.TestCase.assert 메소드1) assertEqual(A, B, Msg) - A, B가 같은지 테스트2) assertNotEqual(A, B, Msg) - A, B가 다른지 테스트3) assertTrue(A, Msg) - A가 True인지 테스트4) assertFalse(A, Msg) - A가 False인지 테스트5) assertIs(A, B, Msg) - A, B가 동일한 객체인지 테스트6) assertIsNot(A, B, Msg) - A, B가 동일하지 않는 객체인지 테스트7) assertIsNone(A, Msg) - A가 None인지 테스트8) assertIsNotNone(A, Msg) - A가 Not None인지 테스트9) assertRaises(ZeroDivisionError, myCalc.add, 4, 0) - 특정 에러 확인1. TestCase.assert 메소드 사용TestCase.assert 메소드를 사용하여 에러를 발생시켜 보겠습니다.import unittest class TddTest(unittest.TestCase): def testAdd(self):         result = lib_calc.add(10, 20)          # 결과 값이 일치 여부 확인         self.assertEqual(result, 31)      def testSubstract(self):         result = lib_calc.substract(20, 10)          if result > 10:             boolval = True         else:             boolval = False # 결과 값이 True 여부 확인         self.assertTrue(boolval)      def testDivision(self):         # 결과 값이 ZeroDivisionError 예외 발생 여부 확인         self.assertRaises(ZeroDivisionError, lib_calc.division, 4, 1)      def testMultiply(self):         nonechk = True result = lib_calc.multiply(10, 9)          if result > 100:             nonechk = None # 결과 값이 None 여부 확인         self.assertIsNone(nonechk) if __name__ == '__main__':     unittest.main() 2. 결과1) 테스트가 실패해도 다른 테스트에 영향을 미치지 않음2) 실패한 위치와 이유를 알 수 있음다음으로 setUp(), tearDown() 메소드를 사용하여 반복적인 테스트 메소드 실행 전, 실행 후의 동작을 처리해보겠습니다.TestCase 메소드1) setUp() - TestCase클래스의 매 테스트 메소드가 실행 전 동작2) tearDown() - 매 테스트 메소드가 실행 후 동작 1. setUp(), tearDown() 메소드 사용- setUp() 메소드로 전역 변수에 값을 지정- tearDown() 메소드로 “ 결과 값 : ” 텍스트 출력import unittest class TddTest(unittest.TestCase): aa = 0     bb = 0     result = 0 # 매 테스트 메소드 실행 전 동작     def setUp(self):        self.aa = 10        self.bb = 20 def testAdd(self):         self.result = lib_calc.add(self.aa, self.bb)          # 결과 값이 일치 여부 확인         self.assertEqual(self.result, 31)      def testSubstract(self):         self.result = lib_calc.substract(self.aa, self.bb)          if self.result > 10:             boolval = True         else:             boolval = False # 결과 값이 True 여부 확인         self.assertTrue(boolval)      def testDivision(self):         # 결과 값이 ZeroDivisionError 예외 발생 여부 확인         self.assertRaises(ZeroDivisionError, lib_calc.division, 4, 1)      def testMultiply(self):         nonechk = True self.result = lib_calc.multiply(10, 9)          if self.result > 100:             nonechk = None # 결과 값이 None 여부 확인         self.assertIsNone(nonechk)      # 매 테스트 메소드 실행 후 동작     def tearDown(self):         print(' 결과 값 : ' + str(self.result))   if __name__ == '__main__':     unittest.main() 2. 결과- setUp() 메소드로 지정한 값으로 테스트를 수행 - tearDown() 메소드로 각각의 테스트 메소드 마다 “ 결과 값 : ” 텍스트 출력실행 명령어 여러 옵션을 사용하여 실행 결과를 출력해보겠습니다.실행 명령어python -m unittest discover [option]1. -v : 상세 결과 2. -f : 첫 번째 실패 또는 오류시 중단3. -s : 시작할 디렉토리4. -p : 테스트 파일과 일치하는 패턴5. -t : 프로젝트의 최상위 디렉토리1. 상세 결과테스트 메소드명 및 해당 클래스명 출력 2. 첫 번째 실패 또는 오류시 중단첫 번째 테스트에서 오류 발생하여 중단3. 여러 옵션 실행현재경로 디렉토리 안에 tdd_test*.py 패턴에 속하는 모든 파일의 상세 결과Conclusion지금까지 파이썬에서 unittest 모듈을 이용한 테스트 코드를 작성했습니다. 처음에는 귀찮고 번거롭지만 테스트 코드를 먼저 작성하는 습관을 길러보세요. 분명 높은 품질의 소스코드를 만들 수 있을 겁니다!참고Python 테스트 시작하기파이썬 TDD 101글곽정섭 과장 | R&D 개발1팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발자 #개발팀 #인사이트 #경험공유 #파이썬 #Python
조회수 979

HyperCut으로 인물사진 필터를 만들었습니다

얼마전 하이퍼커넥트의 아이디어 제안 채널에서 나온 이야기입니다.mel 은 사업그룹의 터키지역 담당 팀에 있는 친구인데, 꾸준히 좋은 제안과 아이디어를 주는 훌륭한 동료입니다. 이번에는 최근 여러 스마트폰이나 사진 앱들에서 나타나기 시작한 인물사진 모드 를 아자르에서도 지원하는게 좋겠다는 제안이었습니다.스마트폰이 자체 기능으로 인물사진 필터를 제공하는 경우는 보통 듀얼 카메라를 사용해 인물 외의 배경을 흐릿하게 만들어 심도를 표현합니다. 하지만 모두가 듀얼 카메라를 탑재한 스마트폰을 쓰는 것은 아니기 때문에, 이런 인물사진 모드를 소프트웨어적으로 구현하는 앱들도 존재합니다. mel 이 보여준 링크의 인스타그램도 그렇게 구현했네요.인물사진 모드를 소프트웨어적으로 구현하려면, 영상에서 얼굴을 포함한 사람을 배경으로부터 정확히 분리해 내는 기술이 필요합니다. 그리고 사진을 찍을 때에 실시간으로 프리뷰를 보아야 할테니까 이것을 실시간으로 처리할 수 있을 정도의 성능도 필요하구요.하이퍼커넥트에서는 머신러닝, 특히 영상과 이미지를 다루는 분야에 대해 지속적으로 투자와 연구를 해 왔습니다. 영상에서 인물을 분리해내는 문제는 크게 Image Segmentation 의 범주에 속합니다. 좀 더 직접적으로 Portrait segmentation 이라고 부를 수도 있습니다. 이를 잘 하기 위해서 하이퍼커넥트에서는 자체적인 학습 데이터를 만드는 것부터 시작하여 기술 개발을 지속적으로 추진해 왔고, 그 결과 Machine Learning 팀에서 이미 실시간으로 얼굴과 배경을 분리해내는 - HyperCut - 이라는 기술을 확보한 상태입니다. 아직 실제 서비스에 탑재되진 않았지만 이미 하이퍼커넥트의 주요 서비스인 아자르의 개발 버전에서는 HyperCut을 응용한 여러가지 이펙트를 사용할 수가 있습니다. 그리고, 그 중에 인물사진 모드 필터도 이미 있습니다.mel 의 제안이 있던 날 오후 아이디어 제안 채널에 이런 답이 달렸습니다.모델이 되어 주신 분은 하이퍼커넥트의 CTO 인 ken 이네요. 아자르 개발 버전에서 HyperCut 을 응용한 인물사진모드 필터를 사용하고 찍은 사진입니다. 아자르의 저장하기 기능을 사용했더니 UI 없이 오른쪽 아래에 아자르 로고만 남게 되었네요. 아직 실서비스에는 포함되지 않았지만, 최적화와 튜닝 과정을 거쳐 조만간 많은 사용자들이 HyperCut 을 사용한 이펙트를 쓸 수 있게 될 예정입니다.#하이퍼커넥트 #개발 #개발자 #아이디어 #아이디에이션 #구체화 #협업 #팀워크 #팀플레이
조회수 1339

레진 기술 블로그 - 모두를 위한 설계. 레진 웹 접근성 가이드라인.

레진엔터테인먼트는 글로벌(한국, 일본, 미국) 서비스를 운영하고 있기에 다양한 사람들의 재능과 욕구에 관심이 있습니다. 우리는 웹 접근성에 관심을 기울여 조금 특별한 욕구를 가진 사람들의 문제를 해결하려고 합니다. 소수의 특별한 욕구는 모두의 욕구와 연결되어 있다고 생각하기 때문입니다.조금 특별한 욕구를 가진 사람WHO는 세계 인구의 15%에 해당하는 사람들이 장애가 있는 것으로 파악하고 있습니다. 그리고 보건복지부 장애인 실태조사에 따르면 후천적 장애 발생률은 90% 수준입니다. 이런 통계에 따르면 한 개인이 일생을 살면서 장애인이 되거나 일시적으로 장애를 체험하게 될 확률은 무려 13.5%나 됩니다.저는 적록 색약입니다. 약한 수준의 장애로 분류할 수 있죠. 채도가 낮은 상태의 적색과 녹색을 쉽게 구별하지 못합니다. 충전 중 적색이었다가 완충이 되면 초록색으로 변하는 LED가 박혀있는 전자제품은 전부 망했으면 개선하면 좋겠어요. 전 세계 남성의 8%가 색약이고, 여성은 0.5%가 색약입니다. 대부분 적록 색약이고 마크 저커버그도 적록 색약입니다. 만화가 이현세 선생님도 적록 색약이고요. 한편 색약인 사람은 빛의 밝고 어두움을 구별하는 능력이 뛰어난 것으로 밝혀져 있어 저격과 관측에 탁월한 능력을 발휘합니다. 숨어있는 저격수 빨리 찾기 게임을 해 보세요. 위장 사진 1, 위장 사진 2, 위장 사진 3. 색약인 사람이 이길 것입니다.전맹 시각장애인은 마우스 포인터와 초점을 볼 수 없으므로 키보드만을 사용해서 웹을 탐색합니다. 키보드와 음성 낭독에 의존하지만, 키보드 기능을 정말 잘 다루죠. 그래서 키보드 접근성 문제를 해결하면 시각장애인뿐만 아니라 키보드를 능숙하게 사용하는 사람들의 사용성이 높아집니다. 소수의 특별한 요구사항을 해결하는 것이 모두를 위한 설계와 연결되어 있습니다.결국, 누구에게나 특별히 다른 측면이 있고 그것을 고려할 때 "모두를 즐겁게 하라!"라는 우리의 좌우명에 한 걸음 더 가까워질 수 있다고 믿습니다.도저히 풀 수 없을 것 같은 숙제웹 접근성을 소개할 때 많이 듣는 질문이 있습니다.장애인이 우리 서비스를 이용해요?매출에 도움이 돼요?시간과 비용이 많이 필요하지 않아요?이 질문에 대한 제 대답은 다음과 같습니다.이용한다면 기쁠 것 같아요.큰 도움은 안 될 거예요.조금은 그렇죠. 하지만 반환이 있어요.레진코믹스와 같이 이미지 기반의 콘텐츠를 서비스하는데 웹 접근성을 준수하려고 노력한다는 것은 무모한 도전에 가깝습니다. 왜냐하면, 현재로서는 전맹 시각장애인 고려가 없고 논의조차 쉽지 않기 때문입니다.하지만 달에 갈 수 없다고 해서 일찌감치 체념할 필요는 없겠지요. 쉬운 문제부터 하나씩 풀어 나아가길 기대합니다. 로켓에 올라탔으니까 금방 갈 수 있지 않을까요?W3C 표준을 우리 언어로W3C에서는 WCAG 2.1이라는 웹 콘텐츠 접근성 지침을 제시하고 있고요. 국내 표준 KWCAG 2.1 또한 있습니다. 국내 표준은 W3C 표준에서 중요도가 높은 항목을 우리 언어로 정리한 것이기 때문에 결국 어떤 지침을 선택해서 따르더라도 괜찮습니다.하지만 표준 문서는 너무 장황하고 전문 용어가 많아 다양한 분야 전문성을 가진 직원들과 함께 보기에는 한계가 있다고 생각했습니다. W3C 표준을 근간으로 하되 비전문가도 15분 정도면 읽고 이해할 수 있을 만큼 정리된 문서가 필요했고 레진 웹 접근성 가이드라인 사내 표준을 제안하고 공개하게 됐습니다.의미를 전달하고 있는 이미지에 대체 텍스트를 제공한다.전경 콘텐츠와 배경은 4.5:1 이상의 명도 대비를 유지한다.화면을 400%까지 확대할 수 있다.키보드만으로 조작할 수 있다.사용할 수 있는 충분한 시간을 제공한다.발작을 유발하는 콘텐츠를 제공하지 않는다.반복되는 콘텐츠 블록을 건너뛸 수 있다.모든 문서의 제목은 고유하고 식별할 수 있다.링크와 버튼 텍스트는 콘텐츠의 목적을 알 수 있다.섹션에는 의미있는 마크업과 헤딩이 있다.문서의 휴먼 랭귀지 속성을 제공한다.문맥 변경은 예측할 수 있다.폼 콘트롤 요소에 설명을 제공한다.실수를 예방하고 정정하는 것을 돕는다.HTML 문법을 준수한다.WCAG 2.1 지침의 1.1.1 항목 예를 들어 볼게요.All non-text content that is presented to the user has a text alternative that serves the equivalent purpose, except for the situations listed below. 사용자에게 제공되는 모든 텍스트 아닌 콘텐츠는 아래 나열된 상황을 제외하고 같은 목적을 수행하는 대체 텍스트를 제공한다.원문 표현보다 아래와 같이 다듬은 표현이 좋다고 보는 것이죠.의미를 전달하고 있는 이미지에 대체 텍스트를 제공한다.물론 사내 지침은 너무 단순하게 표현했기 때문에 지침마다 ‘부연 설명, 관련 예시, 기대 효과, 관련 표준, 평가 도구’ 텍스트와 링크를 간략하게 제공하고 있습니다. 사실상 W3C 표준에 대한 링크 페이지라고 생각해도 괜찮습니다. 사실이 그런걸요.맺음말레진 웹 접근성 가이드라인은 사내 유관 부서 담당자분들께 공유하고 동의를 얻어 사내 지침으로 결정하고 공개할 수 있게 됐습니다. 긍정적으로 검토해 주신 사우님들 감사합니다.레진 웹 접근성 가이드라인은 W3C 표준을 요약한 버전에 불과하므로 누구라도 복제(Fork), 개선 요청(Pull Requests), 문제 제기(Issues)할 수 있습니다."Design for all, amuse everyone!"
조회수 1031

코인원 크루의 채굴 현장을 포착했다! - ‘코인원 작업증명(PoW)’을 소개합니다

블록체인에서 PoW는 Proof of Work, 즉 작업증명을 말합니다. 블록체인의 암호화된 작업에 대해 참여자가 암호를 풀면, 보상을 제공받는 것이죠.오늘은 코인원의 PoW에 대해서 이야기 해보려고 합니다. '코인원 PoW'의 PoW는 블록체인을 기반으로 금융을 혁신하는 기업답게 블록체인 용어에서 차용했어요. :-)  코인원 크루들이 스스로와 동료들의 회고를 진행하고 피드백을 주고 받는 과정을 통해, 업무 개선과 성과에 대한 보상을 제공받도록 만들어진 일종의 성과관리 시스템이죠. 코인원 피플팀은 코인원 PoW 과정을 다음과 같이 설명하고 있습니다.크루들이 자신들의 업무 성과에 대해 투명하게 풀어놓는 회고를 우선적으로 진행해요. 그 후 함께 업무를 진행한 페어 그룹(pair group)끼리 서로 잘된 업무와 지원이 필요한 부분에 대해 대화를 진행하며, 이를 통해 개선점과 방법을 찾아 업무에 적용합니다.코인원 PoW의 특별한 점은, 대부분의 과정이 정성적으로 진행된다는 것에 있습니다. 물론 숫자로 보이는 결과도 중요하지만, 모든 일은 결과 못지 않게 과정도 중요하다고 판단했기 때문이에요. 그 과정에 충실함을 보인 크루들을 선별해 ‘슈퍼크루'로 지정하고 보상을 제공하는 과정도 여기에 포함됩니다. 이 변화무쌍한, 그리고 결코 쉽지 않은 크립토(crypto) 세계에서 ‘정도'를 걸어가고 있는 ‘코인원스러운' 성과관리 시스템이 아닐까 생각합니다. :-) 우선 코인원 PoW가 어떤 과정으로 진행되는지 한 번 살펴볼까요? 코인원 PoW는 아래와 같은 5가지 단계로 진행됩니다.Self mining셀프마이닝은 지난 6개월 동안 자신이 진행한 업무와 그 과정에 대해 에세이를 작성하는 단계입니다. 에세이를 작성할 때는 자신이 진행한 업무와, 업무를 진행하는 과정에서 본인이 생각하는 잘한 부분 및 아쉬웠던 점들 등에 대해서 작성합니다. ‘잘한 부분'의 경우, 성과가 좋았던 점 외에 성과는 좋지 않았어도 최선을 다한 것에 대해 상세히 작성합니다. Peer mining피어마이닝은 업무적으로 연관된 동료 크루의 셀프마이닝을 토대로 그 동료에 대한 의견을 작성하는 단계입니다. 함께 일했을 때 동료 크루의 좋았던 점, 훌륭한 점, 배워야 할 점 등을 서술하고, 앞으로 더 효율적인 협업을 위해 동료가 개선하면 좋을 것 같은 점도 함께 작성해요. Segwit세그윗 단계에서는 위 두 단계에서 도출된 자기 평가와 동료 평가를 토대로 각 셀의 리더들과 1:1 면담을 진행합니다. 이때 셀 리더들이 꼭 염두해야 하는 것은, 코인원 PoW를 통해 우리가 이끌어내고자 하는 것은 '평소의 관심과 피드백, 그리고 동반 성장'이라는 것이죠. Blue paper세그윗을 통해 최종 작성되는 것이 바로 블루페이퍼에요. 크루들의 면담을 진행한 각 셀의 리더들이 작성하죠. 이 내용은 크루들이 지난 6개월 동안 한 일에 대한 자기 자신과 동료, 그리고 셀 리더의 피드백이 담긴 한 장의 문서죠. Consensus마지막으로 컨센서스 단계에서는 최종 완성된 블루페이퍼를 바탕으로 본부별 슈퍼크루를 선정합니다. 그리고 이렇게 선정된 슈퍼크루에 대해 전체 크루가 동의 가능한지에 대한 적절성 심사가 한 번 더 진행됩니다. 현재 코인원에는 약 120명의 크루들이 일하고 있어요. 사실 모든 크루들의 정성적인 분석 과정을 진행하는 것이 쉬운 일은 아니에요. 코인원 PoW는 셀프마이닝부터 블루페이퍼 작성 및 슈퍼크루 선정까지 약 5주에 걸쳐 진행되는, 길지만 자연스러운 평가와 이를 통한 성장의 과정이라고 생각합니다.이 과정에서 코인원 피플팀 여러분들이 정말 많은 노력과 정성을 기울여주고 계시죠!그렇게 10월의 어느 날, 2018년 상반기의 코인원 PoW가 성공적으로 마무리가 됐습니다. :-)코인원의 모든 크루들이 지난 상반기의 업무에 대해 작업증명을 진행했죠! 그리고 또 다른 6개월 동안 앞으로 다시 힘차게 나아가기 위해 자신의 어떤 좋은 점을 강화하고, 어떤 점은 보완하면 될지를 나 자신, 그리고 동료 크루들과 공유하는 시간이었습니다.또, 이렇게 진행된 2018년 코인원 PoW를 통해 여섯 명의 슈퍼크루가 탄생했어요!2018 코인원 슈퍼크루를 소개합니다 :-)올 한해도 모든 크루가 각자의 분야에서 최선을 다했어요.슈퍼크루는 그 중에서도 특히 다른 크루들에게 좋은 영향을 준 크루들을 선정한 것인데요, 이 분들에게는 코인원의 특별한 추가 보상이 제공될 예정이랍니다. :-)코인원은 앞으로도 크루들이 즐겁게 일하면서 성장할 수 있는 여러가지 재밌고 유익한 도전들을 해보려고 합니다. 코인원이 크립토 세상에서 어떤 즐거운 도전들을 하고 있는지, 앞으로도 코인원 공식 블로그를 통해 지켜봐주세요! :-)#코인원 #블록체인 #기술기업 #암호화폐 #스타트업인사이트 #기업문화 #조직문화 #사내복지 #업무환경 #팀원소개
조회수 1396

레진 기술 블로그 - 자바 기반의 백엔드와의 세션 공유를 위한 레일즈 세션 처리 분석

레일즈 기반의 프론트엔드(브라우저에서 서버 사이드 렌더링 계층까지)와 자바 기반의 백엔드(내부 API와 그 이후 계층)이 세션을 공유하기 위해 먼저 레일즈의 세션 처리 과정을 분석하고, 레일즈 세션 쿠키를 다루기 위한 자바 소스 코드를 공유합니다.여기저기 자랑하고 다녔으니 아시는 분은 아시다시피 레진은 구글앱엔진을 사용하고 있습니다. 지금이야 Java, Python, Node.js, Go 언어와 Flexible Environment 같은 다양한 선택지가 있지만, 레진이 입주할 당시만 해도 Java 7(subset), Python(subset)을 지원하는 Standard Environment라는 선택지 밖에 없었죠.최근 Saemaeul Undong 기술 부채 탕감의 일환으로 자바7, 스프링3.x, JSP(!) 기반의 백엔드에 포함되어 있던 프론트엔드를 레일즈 기반의 프론트엔드 서버(서버 사이드 렌더링을 담당하는 서버는 프론트일까요? 백엔드일까요?)로 분리하고 있습니다.서로 다른 세계의 존재들 - 자바와 레일즈의 세션을 공유해야하는 상황이 문제의 발단입니다.자바와 레일즈의 세션을 공유하는 여러가지 방법이 있겠지만, 가장 단순하고 효과적인 방법은 쿠키(cookie)라고 판단하고, 세션 encrypt/decrypt와 marshal/unmarshal을 동일한 방식으로 맞추기로 했습니다. (백엔드 API를 완전히 stateless하게 새로 만들면 좋겠지만, 코인은 벌어야 소는 키워야죠)이를 위해 레일즈의 세션 처리 과정을 분석하고 정리했습니다.레일즈의 actionpack의 action_dispatch/middleware/cookie.rb를 보면 EncryptedCookieJar 클래스의 초기화 과정은 다음과 같습니다(digest의 경우 따로 지정안하면 SHA1이 사용되는 듯):class EncryptedCookieJar < AbstractCookieJar # :nodoc: include SerializedCookieJars def initialize(parent_jar) super if ActiveSupport::LegacyKeyGenerator === key_generator raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " + "Read the upgrade documentation to learn more about this new config option." end secret = key_generator.generate_key(request.encrypted_cookie_salt || '') sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || '') @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) end private def parse(name, encrypted_message) debugger deserialize name, @encryptor.decrypt_and_verify(encrypted_message) rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage nil end def commit(options) debugger options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value])) raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE end end key_generator는 EncryptedCookieJar에 포함된 SerializedCookieJars 모듈에 정의되어 있습니다:module SerializedCookieJars # ... def key_generator request.key_generator end end 흠… 좀 더 파보죠. request.key_genrator는 다음과 같습니다:class Request # ... def key_generator get_header Cookies::GENERATOR_KEY end #... end 흠… 좀 더 파야할 듯 ㅠㅠ.Cookies::GENERATOR_KEY는 다음과 같습니다:class Cookies #... GENERATOR_KEY = "action_dispatch.key_generator".freeze end action_dispatch.key_generator는 레일즈의 엔진 모듈에 해당하는 railties의 application.rb에 정의되어 있습니다:def key_generator # number of iterations selected based on consultation with the google security # team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220 @caching_key_generator ||= if secrets.secret_key_base unless secrets.secret_key_base.kind_of?(String) raise ArgumentError, "`secret_key_base` for #{Rails.env} environment must be a type of String, change this value in `config/secrets.yml`" end key_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000) ActiveSupport::CachingKeyGenerator.new(key_generator) else ActiveSupport::LegacyKeyGenerator.new(secrets.secret_token) end end # ... def env_config @app_env_config ||= begin validate_secret_key_config! super.merge( # ... "action_dispatch.key_generator" => key_generator, "action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt, "action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt, "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt, "action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer, "action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest ) end end 너무 깊이 판 느낌적느낌(?)이 있지만, 여기까지 왔으니 좀 더 파보겠습니다.핵심 알고리즘은 activesupport의 key_generator.rb, message_encryptor.rb, message_verifier.rb에 정의되어 있습니다.먼저, key_generator.rb의 핵심은 다음과 같습니다:class KeyGenerator def initialize(secret, options = {}) @secret = secret # The default iterations are higher than required for our key derivation uses # on the off chance someone uses this for password storage @iterations = options[:iterations] || 2**16 end # Returns a derived key suitable for use. The default key_size is chosen # to be compatible with the default settings of ActiveSupport::MessageVerifier. # i.e. OpenSSL::Digest::SHA1#block_length def generate_key(salt, key_size=64) OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) end end 계속해서, message_encryptor.rb의 핵심은 다음과 같습니다:def initialize(secret, *signature_key_or_options) options = signature_key_or_options.extract_options! sign_secret = signature_key_or_options.first @secret = secret @sign_secret = sign_secret @cipher = options[:cipher] || 'aes-256-cbc' @verifier = MessageVerifier.new(@sign_secret || @secret, digest: options[:digest] || 'SHA1', serializer: NullSerializer) @serializer = options[:serializer] || Marshal end def _encrypt(value) cipher = new_cipher cipher.encrypt cipher.key = @secret # Rely on OpenSSL for the initialization vector iv = cipher.random_iv encrypted_data = cipher.update(@serializer.dump(value)) encrypted_data << cipher.final "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" end def _decrypt(encrypted_message) cipher = new_cipher encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)} cipher.decrypt cipher.key = @secret cipher.iv = iv decrypted_data = cipher.update(encrypted_data) decrypted_data << cipher.final @serializer.load(decrypted_data) rescue OpenSSLCipherError, TypeError, ArgumentError raise InvalidMessage end def encrypt_and_sign(value) verifier.generate(_encrypt(value)) end def decrypt_and_verify(value) _decrypt(verifier.verify(value)) end (Hopefully)마지막으로, message_verifier.rb의 핵심은 다음과 같습니다:def initialize(secret, options = {}) raise ArgumentError, 'Secret should not be nil.' unless secret @secret = secret @digest = options[:digest] || 'SHA1' @serializer = options[:serializer] || Marshal end def valid_message?(signed_message) return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank? data, digest = signed_message.split("--".freeze) data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) end def verified(signed_message) if valid_message?(signed_message) begin data = signed_message.split("--".freeze)[0] @serializer.load(decode(data)) rescue ArgumentError => argument_error return if argument_error.message =~ %r{invalid base64} raise end end end def generate(value) data = encode(@serializer.dump(value)) "#{data}--#{generate_digest(data)}" end private def encode(data) ::Base64.strict_encode64(data) end def decode(data) ::Base64.strict_decode64(data) end def generate_digest(data) require 'openssl' unless defined?(OpenSSL) OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) end # ... # encode, decode는 base64사용 이제 레일즈가 쿠키 기반의 세션을 어떻게 처리하는지 조금 눈에 들어옵니다. 그러나 우리의 최종 목표는 레일즈의 내부를 공부하는 것이 아니라, 자바에서 동일한 처리를 하는 것입니다. 모듈 의존성 따위는 가볍게 무시하고 무한복붙(?)을 시전해서, 레일즈의 세션 처리 과정을 눈으로 확인할 수 있도록 재구성했습니다:require 'openssl' require 'base64' require 'concurrent/map' class Object def blank? respond_to?(:empty?) ? !!empty? : !self end def present? !blank? end end class Hash # By default, only instances of Hash itself are extractable. # Subclasses of Hash may implement this method and return # true to declare themselves as extractable. If a Hash # is extractable, Array#extract_options! pops it from # the Array when it is the last element of the Array. def extractable_options? instance_of?(Hash) end end class Array def extract_options! if last.is_a?(Hash) && last.extractable_options? pop else {} end end end module SecurityUtils def secure_compare(a, b) return false unless a.bytesize == b.bytesize l = a.unpack "C#{a.bytesize}" res = 0 b.each_byte { |byte| res |= byte ^ l.shift } res == 0 end module_function :secure_compare end class KeyGenerator def initialize(secret, options = {}) @secret = secret # The default iterations are higher than required for our key derivation uses # on the off chance someone uses this for password storage @iterations = options[:iterations] || 2**16 end def generate_key(salt, key_size=64) OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) end end class CachingKeyGenerator def initialize(key_generator) @key_generator = key_generator @cache_keys = Concurrent::Map.new end # Returns a derived key suitable for use. def generate_key(*args) @cache_keys[args.join] ||= @key_generator.generate_key(*args) end end class MessageVerifier class InvalidSignature < StandardError; end def initialize(secret, options = {}) raise ArgumentError, 'Secret should not be nil.' unless secret @secret = secret @digest = options[:digest] || 'SHA1' @serializer = options[:serializer] || Marshal end def valid_message?(signed_message) return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank? data, digest = signed_message.split("--".freeze) data.present? && digest.present? && SecurityUtils.secure_compare(digest, generate_digest(data)) end def verified(signed_message) if valid_message?(signed_message) begin data = signed_message.split("--".freeze)[0] @serializer.load(decode(data)) rescue ArgumentError => argument_error return if argument_error.message =~ %r{invalid base64} raise end end end def verify(signed_message) verified(signed_message) || raise(InvalidSignature) end def generate(value) data = encode(@serializer.dump(value)) "#{data}--#{generate_digest(data)}" end private def encode(data) ::Base64.strict_encode64(data) end def decode(data) ::Base64.strict_decode64(data) end def generate_digest(data) require 'openssl' unless defined?(OpenSSL) OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) end end class MessageEncryptor module NullSerializer #:nodoc: def self.load(value) value end def self.dump(value) value end end class InvalidMessage < StandardError; end OpenSSLCipherError = OpenSSL::Cipher::CipherError def initialize(secret, *signature_key_or_options) options = signature_key_or_options.extract_options! sign_secret = signature_key_or_options.first @secret = secret @sign_secret = sign_secret @cipher = options[:cipher] || 'aes-256-cbc' @verifier = MessageVerifier.new(@sign_secret || @secret, digest: options[:digest] || 'SHA1', serializer: NullSerializer) @serializer = options[:serializer] || Marshal end def encrypt_and_sign(value) verifier.generate(_encrypt(value)) end def decrypt_and_verify(value) _decrypt(verifier.verify(value)) end def _encrypt(value) cipher = new_cipher cipher.encrypt cipher.key = @secret # Rely on OpenSSL for the initialization vector iv = cipher.random_iv encrypted_data = cipher.update(@serializer.dump(value)) encrypted_data << cipher.final "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" end def _decrypt(encrypted_message) cipher = new_cipher encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)} cipher.decrypt cipher.key = @secret cipher.iv = iv decrypted_data = cipher.update(encrypted_data) decrypted_data << cipher.final @serializer.load(decrypted_data) rescue OpenSSLCipherError, TypeError, ArgumentError raise InvalidMessage end def new_cipher OpenSSL::Cipher.new(@cipher) end def verifier @verifier end end #key generate encrypted_cookie_salt = 'encrypted cookie' encrypted_signed_cookie_salt = 'signed encrypted cookie' def key_generator secret_key_base = 'db1c366b854c235f98fc3dd356ad6be8dd388f82ad1ddf14dcad9397ddfdb759b4a9fb33385f695f2cc335041eed0fae74eb669c9fb0c40cafdb118d881215a9' key_generator = KeyGenerator.new(secret_key_base, iterations: 1000) CachingKeyGenerator.new(key_generator) end # encrypt secret = key_generator.generate_key(encrypted_cookie_salt || '') sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt || '') encryptor = MessageEncryptor.new(secret, sign_secret, digest: 'SHA1', serializer: MessageEncryptor::NullSerializer) value = "{\"session_id\":\"6022d05887d2ab9c1bad8a87cf8fb949\",\"_csrf_token\":\"OPv/LxbiA5dUjVsbG4EllSS9cca630WOHQcMtPxSQUE=\"}" encrypted_message = encryptor.encrypt_and_sign(value) #encrypted_message = encryptor._encrypt(value) p '-----------encrypted value-------------' p encrypted_message # decrypt encrypted_message = 'bDhIQncxc2k0Rm9QS0VBT0hWc3M4b2xoSnJDdkZNc1B0bGQ2YUhhRXl6SU1oa2c5cTNENWhmR0ZUWC9zN05mamhEYkFJREJLaDQ3SnM3NVNEbFF3ZVdiaFd5YXdlblM5SmZja0R4TE9JbDNmOVlENHhOVFlnamNVS2g1a05LY0FYV3BmUmRPRWtVNUdxYTJVbG5VVUlRPT0tLXd1akRqOU1lTTVneU9LTWszY0I5bFE9PQ==--b0a57266c00e76e0c7d9d855b25d24b242154070' p '-----------decypted value-------------' puts encryptor.decrypt_and_verify encrypted_message p '---------------------------------------' 이 과정을 자바로 구현한 소스는 생략 깃헙에 올려두었습니다. 이 코드를 이용해서 서블릿 세션과 연동하는 방법은 추후 사측(?)과 협의되는 대로 공유할 예정입니다. 물론, 그 전에 쿠키를 공유할 필요가 없어지면(or 공유할 쿠키가 없어지면) 더 좋겠죠 :D
조회수 2736

PHP CI 환경에서 완전한 Vue 사용하기

편집자 주Vue 또는 VUE로 혼용하나 공식 사이트의 표기에 맞춰 아래와 같이 통일함-Vue-Vuex-Vue-Router목차1.Controller2.VIEW3.webpack Vue 소스 진입점4.webpack 설정5.Package.json6.Vue-Router7.Vuex8.공통 처리 mixin9.요약10.마치며시작하며드디어 브랜디 관리자 서비스에 Vue를 도입하고자 떠났던 여정의 마지막 장입니다. 브랜디 관리자 서비스는 PHP Codeigniter와 jQuery로 구성되어 있습니다. 사실 잘 운영되고 있는 서비스에 리스크가 큰 신기술을 도입하는 것은 도박에 가깝습니다. 몇 시간만 운영이 정지되어도 회사에 엄청난 피해를 안겨줄 수도 있으니까요. 하지만 여러 번의 검증과 실험으로 도박에서 이길 확률을 100%에 가깝게 끌어올린다면 한번 도전해볼 만하지 않을까요?이전 글인 PHP Codeigniter 환경에서 VUE 사용해보기에서 기본적인 webpack + Vue + Codeigniter 환경 구축 방법을 알아봤는데요. 하지만 단순히 webpack과 Vue만 적용했다고 해서 “우리 시스템은 UI 프레임워크로 Vue를 사용하고 있습니다.”라고 말할 순 없습니다. 아주 중요한 숙제가 남았죠.Vue에는 활용도를 대폭 끌어올려주는 Vue-Router와 Vuex Store1)가 있는데 그중 Vue-Router를 이번 글에서 자세히 다루려고 합니다.2) Vue-Router는 Vue.js의 공식 라우터입니다. 공식 홈페이지의 소개는 아래와 같습니다.중첩된 라우트/뷰 매핑모듈화된, 컴포넌트 기반의 라우터 설정라우터 파라미터, 쿼리, 와일드카드Vue.js의 트랜지션 시스템을 이용한 트랜지션 효과세밀한 내비게이션 컨트롤active CSS 클래스를 자동으로 추가해주는 링크HTML5 히스토리 모드 또는 해시 모드(IE9에서 자동으로 폴백)사용자 정의 가능한 스크롤 동작한마디로 정리하면 입력된 URL에 반응해 부분에 해당 URL의 view를 보여주는 기능인 것입니다. 다시 말해 URL이 변경될 때 한 페이지에서 화면 전체를 갈아끼우거나, 화면의 일부분(부분)을 치환해주는 역할을 한다는 것이죠. 더 나아가 해당 화면이 로드되기 전후로 전처리, 후처리 기능까지 가능합니다.착안점Vue와 Vue-Rotuer를 알게 되었을 땐 PHP 기반 프로젝트에 Vue-Router를 적용할 수 없으니 처음부터 새로 만들어야 한다고 생각했습니다. 로그인 인증 문제, 메뉴의 권한 관리 등 모든 것이 Vue 아래에 있어야 한다고 생각했기 때문입니다.어느 날 관리자 서비스에 TDD를 구현해보려고 Python Flask + webpack + Vue 프로젝트를 구성하고 있었습니다. 그러던 중 우연히 Flask + Vue-Router에서 404 페이지를 처리하려면 Flask Fallback 페이지를 Vue-Router 메인 페이지(가 있는 페이지)로 보내고, Vue-Router에서 진짜 매핑된 URL이 없으면 404 처리를 하는 식으로 구성한다는 글을 읽고 문득 호기심이 생겼습니다.‘관리자 서비스에서도 컨트롤러로 여러 URL을 한 가지 페이지로 보낸다면?’PHP를 거쳐 페이지로 이동한 것이므로 권한 관리와 메뉴 트리까지는 PHP에서 처리되면서 URL이 변할 것이고, 실제로 화면을 보여주는 Contents 영역만 를 사용한다면 어떻게 될지 궁금해졌습니다. 바로 하던 일을 멈추고 관리자 소스에 Vue-Router를 활용한 테스트 소스코드를 작성해봤습니다.예상했던 대로 PHP의 로그인 인증 처리를 거치면서 실제로 보이는 부분에는 부분만 정상적으로 치환되었습니다. 이 간단한 실험을 바탕으로 통계 시스템의 일부를 구현하는 데에 Vue-Router와 Vuex Store, 공통 처리 Mixin을 추가해 제작했습니다.1.Controller4개의 페이지를 가진 통계 시스템의 Codeigniter 컨트롤러 모습입니다. 기존의 서비스 URL들이 존재하기 때문에 Fallback을 통으로 Vue-Router로 보낼 순 없었고, 라우터를 사용할 페이지들을 하나의 페이지로 보냈습니다.1-1) /application/controllers/[컨트롤러 경로]... 생략 /* [라우터 view]에서 태그를 포함하고 있습니다. */ public function salesAnalysisProduct() { $this->load->view('[라우터 view]'); } public function salesAnalysisSeller() { $this->load->view('[라우터 view]'); } public function trendAnalysisProduct() { $this->load->view('[라우터 view]'); } public function trendAnalysisSeller() { $this->load->view('[라우터 view]'); } ... 생략 2.VIEWCodeigniter 환경에 반영하는 것이므로 CI에서 인식시킬 PHP view와 webpack에서 빌드 결과를 자동으로 바인딩할 html 파일로 구성됩니다.2-1)/application/views/[Vue용 view 경로]/index.php// [index.php] Vue 를 매핑할 php파일 컨트롤러의 view로딩용 [라우터 view]입니다. ... header, menu 생략 ... //바인딩될 부분 //자동매핑 html 인클루드 <?php include('index.html'); ?> ... footer 생략 ... 2-2)/application/views/[Vue용 view 경로]/index.html webpack의 빌드 결과로 자동으로 생성되는 파일입니다. [removed][removed] 위는 webpack의 HtmlWebpackPlugin에 의해 자동으로 바인딩된 모습입니다. 빌드되기 전 index.html은 다음 항목에 있습니다.3.webpack Vue 소스 진입점관리자에서는 프로젝트 폴더 안에 webpack과 Vue 용 서브 폴더를 두고 webpack.config.js에서 output 옵션을 통해 빌드 결과를 삽입하는 구조입니다. webpack 루트 폴더는 application 폴더와 같은 레벨에 위치하며, 폴더 구조나 파일 위치는 어디에 둬도 상관없습니다. webpack.config.js에서 entry 속성으로 잡아주시면 됩니다.3-1)/[webpack루트]/index.html// HtmlWebpackPlugin으로 스크립트를 삽입하기 위한 빈 템플릿 파일 3-2)/[webpack루트]/index.js/** * 진입용 index.js */ import Vue from 'vue' import axios from 'axios' import router from './router' import App from './App.vue' Vue.prototype.$http = axios new Vue({ el : '#app', router, components : { App }, template : '' }); 3-3)/[webpack루트]/App.vue [removed] import mixin from './common/common-mixin.js' import store from './vuex/store' export default { store, name : 'App' } [removed] Vuex와 통신 모듈 axios, Vue-Router 등을 루트 Vue 객체에 추가해줍니다. 브랜디 관리자의 webpack은 babel을 사용하고 있기 때문에 위의 store처럼 축약해서 작성하면 빌드된 파일에는 store: store와 같이 입력됩니다.Vue-Router는 태그에 자동으로 매핑되며, 위와 같은 구조로 상위 컴포넌트에서 할당되어 있어야 합니다. Vuex와 Vue-Router 설정은 글 아래에서 다루겠습니다.4.Webpack 설정이번에 Vue-Router와 Vuex를 도입하면서 webpack의 설정도 실제 서비스용과 개발용으로 분리했습니다. 폴더는 편의상 추가하였으며, package.json에서 자신이 설정한 경로로 설정하면 됩니다.Webpack 설정 파일은 Webpack의 시작과 끝이라고도 할 수 있습니다. Webpack 설정 파일에서 빌드할 소스의 경로와 빌드 결과 파일의 명명 규칙을 정하고, html 파일에 스크립트파를 자동으로 주입시키거나, Babel 플러그인을 통해 최신 스크립트 작성법을 브라우저를 신경쓰지 않고 사용할 수도 있습니다.그중에서도 중요한 옵션이 있는데 바로 Code Splitting에 관련된 옵션입니다.관리자 초기 Vue 모델에는 Vue-Router가 없었기 때문에 js 번들 파일의 크기가 그렇게 크지 않았습니다. 하지만 Vue-Router를 사용해 싱글 페이지 어플리케이션이 되거나 화면의 UI가 복잡해 컴포넌트 수가 많아지면 번들 js 파일의 크기가 매우 커집니다. 즉, 캐시를 사용하지 않는 익스플로러라면 소스에서 한 글자만 바뀌더라도 모든 페이지에서 거대한 번들 js를 새로 로딩하게 되고, 상당한 서버 자원을 소모합니다.Code Splitting 적용 전위의 이미지는 Code Splitting을 적용하기 전의 번들 js 정보입니다. 실제로 완성된 Vue 프로젝트의 번들 js는 더욱 큽니다. 정말 단순한 페이지 하나를 띄우는데 매번 뚱뚱한 js를 로딩해야 하는 것은 서비스 제공자와 서비스 사용자를 모두 괴롭게 할 것입니다.Code Splitting 적용 후하지만 위처럼 작은 조각으로 나눠 필요한 시점마다 필요한 번들 js만 로드하면 매우 빠른 페이지를 제작할 수 있습니다. 따라서 Code Split 기능은 매우 중요한 이슈입니다.물론 개발을 진행하다 보면 역시 어느 것 하나 쉽게 넘어가지지 않습니다. 관리자의 웹팩은 4.x 버전대를 사용하고 있습니다. 예전에 TF에서는 Webpakc 3.x 버전대를 사용하였는데 당시에는 CommonChunkPlugin 설정을 통해 Code Splitting을 사용할 수 있었습니다. 그대로 관리자에 적용하려 했는데..Removed라고 쓰여 있습니다. 찾아보니 CommonChunkPlugin이 옵티마이즈 옵션 하위의 splitChunk 속성으로 들어가면서 설정 방법이 바뀌었더군요. 머리를 싸매고 설정을 잡습니다.4-1) /[webpack루트]/build/webpakc.config.js : 공통 설정파일'use strict' const HtmlWebpackPlugin = require('html-webpack-plugin'); const { VueLoaderPlugin } = require('vue-loader'); const path = require('path'); module.exports = { entry: { //string, object, array 가능 - 기본은 ./src app: path.join('[스크립트 파일 경로]', 'index.js') //진입점 스크립트 파일입니다. }, output: { path: '[빌드된 js 목적지 경로]', publicPath: '[이미지등의 웹상 리소스 경로]', filename: './[name].[chunkhash].js', // 엔트리 파일명명규칙 chunkFilename: '[id]_[chunkhash].js', // chunk파일 명명 규칙 // --mode development에서는 [id]에도 chunkName들어갑니다. }, //vue와 js, css 로드 규칙을 설정합니다. module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', include: [ /[Vue 소스 경로]/ ] }, { test: /\.js$/, use: { loader: 'babel-loader?cacheDirectory', }, include: [ /[Vue 소스 경로]/ ] }, { test: /\.css$/, oneOf: [ { use: [ 'vue-style-loader', 'css-loader' ] } ] } ] }, resolve: { alias: { '@': '[Vue소스 경로]', // 편의상 소스단축경로를 설정합니다. }, //파일 확장자 자동인식 임포트시 해당 확장자는 생략가능합니다. extensions: ['.js', '.vue', '.json'], }, plugins: [ // Vue 파일 로더 new VueLoaderPlugin(), // html 자동 바인딩 // 아래의 플러그인으로 인해 index.html에 해시네임으로 빌드된 index.js가 자동으로 매핑됩니다. new HtmlWebpackPlugin({ // index.php에서 include할 파일이 생성될 경로와 파일명 입니다. filename: path.join('[View경로]', 'index.html'), // 자동으로 매핑할 진입점파일을 지정합니다. template: path.join('[Vue소스 경로]', 'index.html'), inject: true, minify: { removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true } }), ], optimization: { //웹팩 4.x 버전에서 옵티마이즈 속성으로 추가된 CodeSplitting 기능입니다. splitChunks: { //initial - static파일만 분리, async - 동적로딩파일만 분리, all - 모두 분리 chunks: 'async', minSize: 30000, minChunks: 1, maxAsyncRequests: 5, //병렬 요청 chunk수 maxInitialRequests: 3, //초기 페이지로드 병렬 요청 chunk수 automaticNameDelimiter: '_', //vendor, default등 prefix 구분문자 (default : '~') name: true, //development모드일때 파일에 청크이름 표시여부 cacheGroups: { default: { minChunks: 2, //2개 이상의 chunk priority: -20, reuseExistingChunk: true //minChunks이상에서 사용할경우 공통사용 }, //axios, vue 같은 공통 모듈은 vendor로 관리합니다. vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 } } } } }; 4-2) /[webpack루트]/build/webpack.dev.config.js 개발용 설정 파일 (네이밍은 자유)'use strict' const merge = require('webpack-merge') const webpack = require('webpack') const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin const baseWebpackConfig = require('./webpack.config') const config = require('../config').dev //개발용설정 const devWebpackConfig = merge(baseWebpackConfig, { //mode는 chunk[id], 디버깅 코드 등에 영향을 줍니다. webpack 3.x 버전에서는 Env 속성을 통해 관리했다고 합니다. mode: 'development', plugins: [ new BundleAnalyzerPlugin(), //번들 무게 분석기 제대로 스플릿 되었는지 확인할 때 사용합니다. new webpack.DefinePlugin({ env : config.env }), ], watch: true, //코드의 변화를 감지해 자동으로 재빌드해주는 옵션입니다. cache: true, //캐시 사용을 활성화하면 변경사항이 있는 코드만 재빌드합니다. optimization: { //uglify 플러그인 코드 압축 여부를 설정합니다 압축 시 용량을 매우 줄일 수 있으나 빌드 속도가 크게 저하되므로 개발 시에는 꺼줍니다. minimize: false, } }) module.exports = new Promise((resolve, reject) => { resolve(devWebpackConfig); }) 4-3) /[webpack루트]/build/webpack.prod.config.js 서비스용 설정파일 (네이밍은 자유)'use strict' const merge = require('webpack-merge') // 설정파일 결합에 사용합니다. const webpack = require('webpack') const baseWebpackConfig = require('./webpack.config') //베이스 설정파일 const config = require('../config').prod //서비스용 설정 const prodWebpackConfig = merge(baseWebpackConfig, { mode: 'production', //chunk[id], 디버깅 코드등 영향 있음 plugins: [ new webpack.DefinePlugin({ env : config.env }), ], //개발용과 반대로 용량은 줄이고 필요 없는 기능은 꺼줍니다. watch: false, cache: false, optimization: { minimize: true, } }) module.exports = prodWebpackConfig 5.package.json웹팩 설정 파일이 분리되면서 package.json의 런 스크립트도 변경했습니다.... "scripts": { "build": "webpack --config build/webpack.prod.config.js --progress", "build-dev": "webpack --config build/webpack.dev.config.js --progress" }, ... 6.Vue-RouterVue-Router는 위에서 설명한 대로 Vue의 컴포넌트와 밀접하게 결합된 라우터입니다. 그런데 여기서 webpack의 Code Split을 사용하려면 컴포넌트 import 방법이 매우 중요한데요.import './testComponent' 위처럼 import 구문을 사용해 컴포넌트를 불러오면 코드가 쪼개지지 않고 한 덩어리로 빌드되므로 아래와 같은 형태로 사용해야 합니다.const testComponent = () => import('./testComponent') webpack 공식 문서에도 나와있듯이 위처럼 ES2015 Loader spec에 있는 import()를 사용하여 컴포넌트를 생성해야 번들 js가 제대로 분리되며, Dynamic Import가 가능해집니다.Vue-Router를 쓰는 순간 싱글 페이지 어플리케이션이 되기 때문에 이곳에서 설정을 잘못 잡아주는 순간 육중한 컴포넌트 한 덩어리가 튀어나오면서 Code Splitting은 물거품이 되어버립니다. 조심합시다!또한 import 함수 안쪽엔 아래와 같은 주석을 달아야 청크 이름이 적용됩니다.const testComponent = () => import( /* webpackChunkName: '[청크이름]'*/ './testComponent') 라우터 경로 속성인 path 와 Codeigniter의 컨트롤러 경로를 맞춰주는 것이 포인트입니다!6-1) /[webpack루트]/router/index.js - 경로와 파일명은 자유입니다!import Vue from 'vue' import Router from 'vue-router' // 주석의 webpackChunkName = 코드 스플릿 chunk Name으로 사용됩니다. // 꼭 컴포넌트와 청크 이름을 같게 설정할 필요는 없습니다. const SalesAnalysisProduct = () => import(/* webpackChunkName: "salesAnalysisProduct" */ '[컴포넌트 파일 경로]') const SalesAnalysisSeller = () => import(/* webpackChunkName: "salesAnalysisSeller" */ '[컴포넌트 파일 경로]') const TrendAnalysisProduct = () => import(/* webpackChunkName: "trendAnalysisProduct" */ '[컴포넌트 파일 경로]') const TrendAnalysisSeller = () => import(/* webpackChunkName: "trendAnalysisSeller" */ '[컴포넌트 파일 경로]') Vue.use(Router) const router = new Router({ mode: 'history', routes: [ /* 통계 */ { path: '[CI컨트롤러 url]/salesAnalysisProduct', name: 'salesAnalysisProduct', component: SalesAnalysisProduct }, { path: '[CI컨트롤러 url]/salesAnalysisSeller', name: 'salesAnalysisSeller', component: SalesAnalysisSeller }, { path: '[CI컨트롤러 url]/trendAnalysisProduct', name: 'trendAnalysisProduct', component: TrendAnalysisProduct }, { path: '[CI컨트롤러 url]/trendAnalysisSeller', name: 'trendAnalysisSeller', component: TrendAnalysisSeller }, ] }) // 아래의 함수로 전처리 후처리도 가능합니다! router.beforeEach((to, from, next) => { // ... }) router.afterEach((to, from) => { // ... }) export default router 7.Vuex앞서 Vue와 Vuex, 컴포넌트간 통신과 상태 관리에서 소개했던 상태 관리와 통신을 위한 Vuex도 추가합니다. Vuex는 하나의 Store만 쓸 경우 상태 변수의 과포화로 인해 유지 보수가 어려워질 수 있으므로 namespace: true 옵션을 통해 도메인별로 관리합니다.7-1) /[webpack루트]/vuex/store.js - Vuex 진입파일import Vue from 'vue' import Vuex from 'vuex' // 각 도메인별 store들이 들어있는 modules 를 임포트해줍니다. import * as modules from './modules/index' Vue.use(Vuex) export default new Vuex.Store({ state : { }, getter: { }, mutations : { }, actions : { }, modules : modules.default }) 7-2) /[webpack루트]/vuex/modules/index.js - 도메인별 Store 자동 바인딩 스크립트const files = require.context('.', false, /\.js$/) const modules = {} //자신(index.js)를 제외한 파일들을 파일이름을 Key로 modules에 담습니다. files.keys().forEach((key) => { if (key === './index.js') return modules[key.toLowerCase().replace(/(\.\/|\.js)/g, '')] = files(key).default }) export default modules 7-3) /[webpack루트]/vuex/modules/statistics.js(통계 store 파일) - 예시입니다.export default { namespaced : true, //해당 속성을 통해 파일명을 namespace로 사용합니다. state: { /* 상태값 및 데이터 */ ... }, getters: { }, mutations: { /* state 변경처리 */ ... }, actions: { /* 통신처리 */ ... } } namespace: true로 되어있으므로 파일명인 statistics를 namespace로 사용하게 됩니다. 따라서 store 각 항목에 대한 접근은 다음과 같이 이뤄지며 computed 속성에 state: this.$store.state.statistics 처럼 정의해두면 편리합니다.dispatch는 this.$store.dispatch(‘statistics/[action 이름]’)commit은 this.$store.commit(‘statistics/[mutation 이름]’)state 변수 접근은 this.$store.state.statistics.[state 이름]8.공통 처리 mixinapi 통신에 사용되는 통신 라이브러리와 그 라이브러리의 복잡한 설정 코드, 단순한 Toast 출력 함수, 로딩 이펙트를 보여주는 함수 등 모든 항목들이 매 페이지마다 있으면, 통일되지 못한 UI, 페이지마다 일관되지 못한 설정 등으로 휴먼 에러가 발생할 확률이 높아집니다. 유지 보수 측면에서도 비용이 높아집니다. 이러한 단순 반복 코드들은 한번만 정의하고 재사용하는 것이 바람직합니다. 나중에 수정할 때도 용이하죠.공통사항을 묶어 Vue 전역 믹스인으로 Vue 루트 객체에 추가합시다. 단, global 옵션인 만큼 조심해서 써야 합니다. 시스템에 영향을 줄 것 같으면 하위 컴포넌트 mixins 속성에 넣어 해당 스코프에서만 사용하는 것이 바람직합니다.8-1) /[webpack루트]/common/common-mixin.js (파일이름, 경로는 자유입니다!)import Vue from 'vue' import Vue from 'axios' import Cookies from 'js-cookie' const TIMEOUT = '[타임아웃 시간(ms)]' /* mixin의 기본 형태는 Vue 컴포넌트의 형태와 동일합니다. 주로 전역 통신과 상태 관리는 vuex store에서, 전역 data 속성과 전역 함수는 mixin에서 관리합니다. */ Vue.mixin({ /* 전역 사용 data속성 선언 */ data: () => { return { ... //이곳에 선언하는 data 속성은 전역에서 this로 접근 가능합니다. } }, created: function() { // 공용 axios 객체 생성 this.axios = axios.create({ timeout: TIMEOUT, withCredentials: true, //공통해더는 여기에 headers : { } }); //axios 의 success와 error를 mixin method에서 처리 하도록 등록 this.axios.interceptors.response.use(this.onSuccess, this.onError) }, /* 전역 사용 함수 선언 */ methods: { /* axios의 response handling 함수*/ onSuccess : response => { }, onError : function (error) { }, /*GET, POST 등의 통신 함수, Toast(alert) 표출함수, 에러핸들링함수 등 선언*/ /*... 내용이 너무 길어서 생략 ...*/ } }); 9.요약지금까지의 내용은 파일 경로를 토대로 요약하면 다음과 같습니다. 참고로 아래의 폴더 구조는 절대적인것은 아닙니다. 모든 폴더 구조는 자율이며, 폴더 구조에 맞게 webpack.config.js에서 조정해주면 됩니다.[프로젝트 루트] └ [웹팩 루트] └ package.json └ [Vue 소스 루트] └ [common] └ [router] └ index.js // 라우터 설정파일 - CI 컨트롤러와 url 맞춰줘야함 └ [vuex] └ index.js // 도메인별 store module export 스크립트 └ [modules] └ 도메인별 store.js └ [컴포넌트 폴더] //예시에서는 ststistics └ App.vue //진입점 vue파일 Vuex와 전역 mixin 세팅 └ index.html //index.js가 주입될 껍데기 └ index.js //진입점 js Vue-Router와 App.vue 세팅 └ [build] // 빌드파일경로 └ webpack.config.js //베이스 설정파일 └ webpack.dev.config.js //개발용 설정파일 └ webpack.prod.config.js //서비스용 설정파일 └ [application] //Codeigniter 루트 └ [controllers] └ [컨트롤러 경로] // 예시의 통계부분 └ [views] └ [웹팩빌드 결과 폴더] └ [index.php] // CI 에서 로드하는 view (index.html include) └ [index.html] // js 번들이 자동 주입된 빌드결과 파일 └ [include] └ [scripts] └ [빌드결과 js 경로] //public path 속성 경로 └ 빌드 결과 js chunk들 마치며관리자 서비스에서 완전한 Vue를 사용하기 위해 꽤 험난한 과정을 거쳤습니다. 지금도 잘 돌아가는 서비스에 리스크를 감수하면서도 새로운 것을 도입하려는 이유를 찾아야 했고, 한동안은 레거시와 Vue로 된 소스를 2중으로 개발해야 했습니다.게다가 이 글을 작성하기 시작했을 땐 Code Splitting 설정 방법이 바뀌어 적용하지도 못한 상황이었기 때문에 사실 Code Splitting 내용이 없었습니다. 그런데 글을 작성하면서 splitChunk옵션을 성공해버렸어요! 덕분에 이 글도 모두 수정해야 했죠. Vue의 도입을 고려하는 개발자분들에게 도움이 되길 바라는 마음으로 글을 마칩니다.참고1)Vuex Store는 Vue와 Vuex, 컴포넌트간 통신과 상태 관리에 자세히 정리해두었다.2) 브랜디 관리자 서비스는 jQuery로 작성되어 있다. 따라서 jQuery를 베재할 수만은 없는 상황이었다. 이에 따라 기존 jQuery 컴포넌트들에 대한 해결책은 천보성 팀장님이 작성한 JQuery 프로젝트에 VUE를 점진적으로 도입하기를 참고했다. props와 emit 기능을 이용해 jQuery로 제작한 컴포넌트를 깔끔하게 Wrapping 하는 방법에 대해 자세히 기술되어 있으며, 이를 활용하면 레거시 UI 플러그인을 마치 네이티브 Vue 플러그인처럼 사용할 수 있다.글강원우 과장 | R&D 개발2팀[email protected]브랜디, 오직 예쁜 옷만
조회수 2416

영화 ‘앤트맨’을 통해 알아 본 안드로이드 나인패치(Android 9 Patch)

시작영화 ‘캡틴 아메리카: 시빌워’를 본 사람이라면 작은 ‘앤트맨’의 존재감이 누구보다 컸다고 말할 수 있습니다. 앤트맨은 가장 중요한 전투 신에서 자유자재로 신체 크기를 바꾸며 맹활약을 한 히어로인데요.안드로이드에도 이런 앤트맨처럼 크기를 자유자재로 바꾸되, 해상도를 그대로 보존하여 앱을 구현하는데 큰 도움을 주는 이미지 저장방식이 있습니다. 바로 나인패치입니다. 포스팅을 통해 나인패치를 이해해보고자 합니다.나인패치 이해하기#사이즈는 바뀌지만 내용은 그대로영화 속 앤트맨은 핌입자를 통해 분자보다 더 작은 양자 사이즈만큼 작아졌다가, 비행기보다 더 큰 사이즈로 변하는 히어로인데요. 핌 입자를 사용시 질량에는 변화가 없어 작아진 크기에서도 정상 어른의 펀치와 같은 위력을 줍니다.나인패치 역시 앤트맨과 같은 특징을 가지고 있는데요. 우리가 사용하고 있는 핸드폰의 해상도는 제각각 입니다. 하지만 이미지를 그 해상도에 전부 맞춰서 제작하기에는 무리가 있죠. 그렇기 때문에 디바이스에 표현되는 아이콘이나 버튼 등에 확대 되는 영역을 지정해줍니다. 그러면 큰 해상도에 이미지를 적용 하여도 픽셀이 깨지지 않고 확대된 이미지를 사용 할 수 있습니다.좀 더 정확하게 설명하자면, 이미지를 9분할 하여 확대되는 영역과 아닌 영역을 구분하여 저장하는 방식이며 이미지 확장자는. 9.png가 됩니다. 아래의 그림에서 살펴보면 빨간색 화살표 영역은 늘어나고 흰색 영역은 늘어나지 않게 됩니다.나인패치 이미지가 어떤 구조를 가지고 있는 어떻게 동작하는지에 대해 추가적으로 설명해보겠습니다.우선, 나인패치는Stretchable area와 Padding box 두가지의 영역으로 나뉩니다.Stretchable area는 늘어나는 영역은 이미지를 늘려주는 구간을 설정해주는 나인패치 영역입니다. 그래서 가로, 세로 어떤 크기로 늘어나도 형태가 깨져 보이지 않습니다.Padding box는 이미지 위에 어떠한 내용물을 어느 위치에 표시할지 정의 하는 영역입니다.버튼 크기가 변경되어도 정보 표시 영역을 나인패치로 잡아 좌우,상하 여백은 그대로 두고 이미지 확대/축소에 따른 텍스트가 정리되어 보여집니다.나인패치는 1px 검정색 선의 길이와 여백을 이용해서 늘려주고 싶은 이미지 영역과 표현하고 싶은 텍스트의 영역을 지정할 수 있는 것입니다.조금은 복잡해 보이지만, 나인패치로 지정하는 과정이 필요한 이유는 모바일은 한정된 용량을 가지고 있기 때문에 용량을 줄여서 하나의 이미지로 다양하게 사용할 수 있도록 하기 위해서 입니다.나인패치 만들어보기#만드는 방법나인패치를 만드는 방법에는 여러가지가 있습니다.   1. 포토샵으로 만들고, 확장자를 name.9.png으로 저장   2. 안드로이드 sdk 도구를 다운로드하여 만든다.       https://developer.android.com/studio/?hl=ko   3. Android Asset Studio 활용       http://romannurik.github.io/AndroidAssetStudio/nine-patches.html그중에서 툴 설치도 필요 없고 쉽게 만들 수 있는 3번의 방법을 활용하여 간략하게 나마 만들어보겠습니다.#우리는 그저 감사하게 사용할 뿐세상은 넓고 금손이 많은 것 같아요. 빠르게 만들수 있는 방법을 선택하겠습니다. 위 3번의 주소를 타고 사이트에 접속하면 아래와 같은 화면이 보여집니다.나인패치를 만들 수 있는 웹 툴인데, 저 사이트에는 나인패치 뿐만 아니라 안드로이드 디자인을 위한 다양한 툴을 제공하니 한번 참고해보시면 좋을 것 같습니다. 언제 이런걸 만들 생각을 하셨는지 한번 더 자괴감과 감사함을 느끼며 샘플 버튼 이미지를 불러옵니다.왼쪽 패널을 보면 이미지의 리소스 해상도를 지정하는 부분과 Drawable 이름을 편집할 수 있는 기능이 있습니다. 이름을 변경하게 되면 zip파일로 다운 받았을 때 변경된 이름으로 다운로드 됩니다.자 그럼 불러온 이미지가 가운데 화면에 보여집니다. Stretch Regions는 늘어나게 되는 부분을 설정하는 것입니다. 화면에 보이는 얇은 검은 선으로 Stretch Regions을 지정하면 됩니다.위와 같이 설정하게 되면 해상도에 따라 붉은색 부분이 늘어나게 됩니다.Contetns Padding은 안에 들어가는 텍스트가 들어가는 여백을 설정해줍니다.오른쪽 패널에서 Preview로 텍스트가 들어가는 것을 확인하면서 설정 할 수 있습니다.With content를 체크해주셔야 텍스트가 보여집니다.완성되면 Assets 탭에서 zip파일을 다운로드 받아주세요.다운로드가 완료되면 drawable name.9.zip으로 다운로드 되고 zip파일을 압축해제 하면 해상도 별로 나인패치 파일이 생성됩니다.부족하지만 나인패치에 대해 알아가는 시간이 되었기를 바라며, 이번 글은 여기서 마무리 하겠습니다.#에이치나인 #디자이너 #개발자 #협업툴 #크래커나인 #솔루션기업
조회수 1832

[Buzzvil Career] 좋은 데이터 애널리스트는 어떤 사람일까?

모바일 잠금화면 미디어 플랫폼 사업자 버즈빌은 어떠한 인재를 찾는지 지원자에게 잘 알리려고 노력합니다. 그럼 지원자도 버즈빌이 자신에게 맞는 기업인지 알 수 있을 테니까요. Buzzvil Career에서는 각 직무에 대해 더욱 심도 있는 정보를 제공합니다. 현재 채용에 관련한 자세한 내용은 여기에서도 확인 가능합니다. 이 게시물은 데이터 애널리스트 Elia와의 인터뷰를 담고 있습니다. 그는 좋은 데이터 애널리스트는 어떤 사람인지에 대해 이야기합니다. 데이터를 좋아하고 데이터에 기반한 기업 성장에 기여하고 싶다면 이 글에 주목해주세요.업무에 대해 설명해주세요.안녕하세요. 버즈빌의 데이터 애널리스트 Elia입니다. 팀에서 일한 지 어느덧 4년이라는 세월이 흘렀네요. 데이터 분석을 위한 툴을 세팅하고 많은 양의 가공된 데이터를 공유하고 있습니다. 데이터로 무엇을 하고 어떻게 활용할지 고민하는 것이 제 일입니다. 또 저는 올바른 결정을 내리고 효율적으로 업무를 수행할 수 있도록 A/B 테스팅과 다양한 연구를 수행하고 있습니다. 마지막으로 SQL 세션을 열어서 사람들이 데이터에 유연하게 접근하고 잘 사용할 수 있도록 합니다.왜 버즈빌을 선택 했나요?가까운 지인이 이 회사를 추천해줬습니다. 분위기가 친근했고 한국에도 이런 곳이 있다는 게 놀라웠습니다. 그리고 버즈빌은 석촌 호수 바로 앞에 있어서 전망이 훌륭한데 특히 봄이 되면 벚꽃을 볼 수 있습니다. 그리고 무엇보다 사무실은 저희 집과 가깝습니다. 그러니 제가 거절할 이유가 없었죠.버즈빌은 어떤 곳인가요?버즈빌은 데이터 애널리스트로서 성장할 수 있는 곳입니다. 팀 규모가 그렇게 크지 않아서 유연합니다. 많은 사람과 이야기를 할 수 있고 사람들을 어떻게 도울 수 있을지, 어떤 일을 이루어야 하는지 스스로 고민할 수 있기 때문에 컨설턴트 같은 존재입니다. 그만큼 특정 역할에 고정되지 않습니다. 데이터 분석은 새로운 분야입니다. 그래서 회사가 그것을 어떻게 생각하는지에 따라 담당자가 할 수 있는 일이 달라집니다. 다행히 버즈빌리언은 새로운 아이디어와 제안에 개방적인 태도를 보입니다. 버즈빌처럼 새로운 분야를 배우는 걸 좋아하는 집단도 없을 겁니다. 그래서 담당자가 얼마나 능동적인지에 따라 할 수 있는 일이 많아집니다. 정말 독특한 문화를 가졌죠.팀 분위기는 어떤가요?여기서는 다양한 프로젝트에 대해 데이터를 조사하고 돌파구를 찾아야 합니다. 초집중해야 하며 테스트를 수행하고 데이터를 분석하는 방법을 발견해야만 합니다. 올바른 의사 결정을 내리는 것은 쉬운 일이 아니기 때문에 데이터 애널리스트가 필요하죠. 이 역할이 왜 필요한지 사람들이 알 수 있도록 자신을 잘 표지셔닝을 해야 합니다. 그래야 새로운 프로젝트에 참여할 수 있는 기회가 더 많아지고 기업 성장에 더욱 직접적으로 기여할 수 있죠.좋은 데이터 애널리스트는 어떤 사람일까요?# 커뮤니케이션 데이터 애널리스트는 효과적으로 딱 필요한 말만 잘 전달하는 커뮤니케이터가 되어야 합니다. 같은 말을 반복하거나 요점에서 자꾸 벗어나면 안 되죠. 버즈빌에서 데이터 분석가로 일하려면 다양한 팀과 일하기 때문에 소통을 효과적으로 잘해야만 합니다. 그래야 데이터 연구 결과가 정확할 수 있기 때문이죠. 이는 더 나은 비즈니스 의사 결정으로 이어지죠.#적극성 데이터 애널리스트는 능동적일수록 더 성장할 것입니다. 당신의 역량이 향상될 것이고 되고 직장에서 다양한 사람들과 상호작용하는 요령을 익힐 수 있습니다. 버즈빌은 데이터 애널리스트가 다양한 연구를 수행하기 좋은 인프라를 갖추고 있는데 이것은 데이터 분석이 새로운 분야라는 점에서 매우 플러스입니다. 따라서 버즈빌은 새로운 기회를 얼마든지 제공할 수 있기 때문에 탐험을 즐기는 사람을 찾고 있습니다.*버즈빌은 현재 채용 중입니다. (전문연구 요원 포함) 자세한 내용은 아래 버튼을 눌러주세요!모바일 잠금화면 미디어 플랫폼 사업자 버즈빌은 어떠한 인재를 찾는지 지원자에게 잘 알리려고 노력합니다. 그럼 지원자도 버즈빌이 자신에게 맞는 기업인지 알 수 있을 테니까요. Buzzvil Career에서는 각 직무에 대해 더욱 심도 있는 정보를 제공합니다. 현재 채용에 관련한 자세한 내용은 여기에서도 확인 가능합니다. 이 게시물은 데이터 애널리스트 Elia와의 인터뷰를 담고 있습니다. 그는 좋은 데이터 애널리스트는 어떤 사람인지에 대해 이야기합니다. 데이터를 좋아하고 데이터에 기반한 기업 성장에 기여하고 싶다면 이 글에 주목해주세요.업무에 대해 설명해주세요.안녕하세요. 버즈빌의 데이터 애널리스트 Elia입니다. 팀에서 일한 지 어느덧 4년이라는 세월이 흘렀네요. 데이터 분석을 위한 툴을 세팅하고 많은 양의 가공된 데이터를 공유하고 있습니다. 데이터로 무엇을 하고 어떻게 활용할지 고민하는 것이 제 일입니다. 또 저는 올바른 결정을 내리고 효율적으로 업무를 수행할 수 있도록 A/B 테스팅과 다양한 연구를 수행하고 있습니다. 마지막으로 SQL 세션을 열어서 사람들이 데이터에 유연하게 접근하고 잘 사용할 수 있도록 합니다.왜 버즈빌을 선택 했나요?가까운 지인이 이 회사를 추천해줬습니다. 분위기가 친근했고 한국에도 이런 곳이 있다는 게 놀라웠습니다. 그리고 버즈빌은 석촌 호수 바로 앞에 있어서 전망이 훌륭한데 특히 봄이 되면 벚꽃을 볼 수 있습니다. 그리고 무엇보다 사무실은 저희 집과 가깝습니다. 그러니 제가 거절할 이유가 없었죠.버즈빌은 어떤 곳인가요?버즈빌은 데이터 애널리스트로서 성장할 수 있는 곳입니다. 팀 규모가 그렇게 크지 않아서 유연합니다. 많은 사람과 이야기를 할 수 있고 사람들을 어떻게 도울 수 있을지, 어떤 일을 이루어야 하는지 스스로 고민할 수 있기 때문에 컨설턴트 같은 존재입니다. 그만큼 특정 역할에 고정되지 않습니다. 데이터 분석은 새로운 분야입니다. 그래서 회사가 그것을 어떻게 생각하는지에 따라 담당자가 할 수 있는 일이 달라집니다. 다행히 버즈빌리언은 새로운 아이디어와 제안에 개방적인 태도를 보입니다. 버즈빌처럼 새로운 분야를 배우는 걸 좋아하는 집단도 없을 겁니다. 그래서 담당자가 얼마나 능동적인지에 따라 할 수 있는 일이 많아집니다. 정말 독특한 문화를 가졌죠.팀 분위기는 어떤가요?여기서는 다양한 프로젝트에 대해 데이터를 조사하고 돌파구를 찾아야 합니다. 초집중해야 하며 테스트를 수행하고 데이터를 분석하는 방법을 발견해야만 합니다. 올바른 의사 결정을 내리는 것은 쉬운 일이 아니기 때문에 데이터 애널리스트가 필요하죠. 이 역할이 왜 필요한지 사람들이 알 수 있도록 자신을 잘 표지셔닝을 해야 합니다. 그래야 새로운 프로젝트에 참여할 수 있는 기회가 더 많아지고 기업 성장에 더욱 직접적으로 기여할 수 있죠.좋은 데이터 애널리스트는 어떤 사람일까요?# 커뮤니케이션 데이터 애널리스트는 효과적으로 딱 필요한 말만 잘 전달하는 커뮤니케이터가 되어야 합니다. 같은 말을 반복하거나 요점에서 자꾸 벗어나면 안 되죠. 버즈빌에서 데이터 분석가로 일하려면 다양한 팀과 일하기 때문에 소통을 효과적으로 잘해야만 합니다. 그래야 데이터 연구 결과가 정확할 수 있기 때문이죠. 이는 더 나은 비즈니스 의사 결정으로 이어지죠.#적극성 데이터 애널리스트는 능동적일수록 더 성장할 것입니다. 당신의 역량이 향상될 것이고 되고 직장에서 다양한 사람들과 상호작용하는 요령을 익힐 수 있습니다. 버즈빌은 데이터 애널리스트가 다양한 연구를 수행하기 좋은 인프라를 갖추고 있는데 이것은 데이터 분석이 새로운 분야라는 점에서 매우 플러스입니다. 따라서 버즈빌은 새로운 기회를 얼마든지 제공할 수 있기 때문에 탐험을 즐기는 사람을 찾고 있습니다.*버즈빌은 현재 채용 중입니다. (전문연구 요원 포함) 자세한 내용은 아래 버튼을 눌러주세요!버즈빌과 함께하고 싶은 분은 지금 바로 지원 해주세요! (전문연구요원 포함)
조회수 4039

풀스택 개발자, 그것은 환상..

풀스택 개발자라는 용어가 가끔 등장한다. 죄송하지만, 한국에서는 이 용어가 정말 잘못 이해된 상태로 사용되고 있다. 처음에 만들어진 의미와 뜻이 한국에 들어오면서 변한 것을 보는 것이 이번만도 아니다.언제나처럼, 이 '단어'가 의미하는 뜻은 '귤이 회수를 건너면서 언제나 탱자가 되는' 한국적인 환경에서는 매우 이상하게 와전된 의미로 사용되고 있다. 특히나 비개발자들인 경영진들이 그러하고, 개발자들도 가끔 잘못된 의미로 사용한다.와전된 의미의 '풀스택 개발자(Full Stack Developer)'는 프런트엔드와 서버 엔드를 넘나드는 모든 것을 다 아는 전지전능한 개발자인 것처럼 쓰인다. 죄송하지만, 풀스택 개발자의 의미는 프런트-엔드부터 서버-엔드까지 모든 것을 다룰 줄 아는 개발자를 의미하는 것이 아니다.이 '용어'가 쓰이는 분야를 조금은 국한시켜야 할 필요가 있다.그것은 '웹'환경의 프론트 영역으로 국한시키는 것이 매우 현명할 것이다. 다음의 링크를 참조하기를 권한다.http://www.sitepoint.com/full-stack-developer/위의 사이트에 있는 이미지와 단어를 차용한다. 아래의 그림을 살펴보라.[이미지출처 : http://www.sitepoint.com/full-stack-developer/ ]OS부터 Database, WebServer, Server Side Code, Browser, Client Side Code를 아우르는 능력을 가진 사람을 Full-Stack Developer라고 부를 수 있다.좀 더 쉽게 이야기하자면, 'Web'환경은 서버사이드 코드와 클라이언트 사이드 코드를 모두 이해하고 작성되어야 한다. 브라우저( 특히나 변덕스러운 호환성 문제들.. )의 스크립트 환경이 효과적으로 가동되기 위해서는 웹서버의 API를 적절하게 디자인하고 구현된 상태에서 동작되어야 하며, 대부분의 코드들은 직접 Database에 영향을 주는 경우가 많다. 더군다나, 소프트웨어 개발을 하려면 형상관리부터 배포 처리를 위한 기술도 할 줄 알아야 한다.맞다. 'Web'개발 환경에서는 Full-Stack Developer가 되지 않으면 제대로 된 개발이 어렵다. 그래서, '웹'에서는 풀스택 개발자를 지향해야 하고, 매우 당연하게 해당 스킬들을 익숙하게 다루어야 한다.풀스택 개발자는 Web의 개발환경에서는 어쩔 수 없이 매우 당연한 기술적인 한계이고 해야 할 업무를 위해서는 필연적인 형태 인 것이다.이렇게 '웹 환경에서의 풀스택 개발자'는 한국에도 많이 존재한다. 상당수의 PHP개발자 분들이 그러한 '풀스택 개발자'인 경우가 많다.그렇지만, 이 풀스택 개발자의 용어는 '개발'이나 '소프트웨어'를 잘 모르는 경영자의 머릿속으로 잘못 들어가서 마치, iOS나 Android APP도 개발하고 Rest API 디자인이나 구현도 하면서, AWS의 분산 환경에 대한 이해나 개발도 모두 가능한 '전지전능한 개발자'와 같은 의미로 잘못 사용되기도 한다.( 더군다나, 디자인능력이 극도로 필요한 자바스크립트나 능동형 웹-UI를 만들어 내는 능력은 전혀 다른 능력이다 )원래 의미의 '풀스택 개발자'는 '혼자서 웹서비스 하나를 만들 수 있는 개발자'라는 좁은 의미로는 맞다. 하지만, 이를 과도하게 해석하거나 아전인수격으로 해석하는 것은 매우 곤란하다. 그것은 바로 한국적인 특수한 환경 때문에 그러하다.슬프지만, 한국적인 의미의 풀스택 개발자가 존재하기는 하고 있다.프로그래머가 기획도 하면서, 서버 구입부터 설치까지 다진 행하고, DB도 일부 다룰 줄 알면서, 웹이나 클라이언트 프로그래밍의 일부도 할 줄 아는 매우 한국적인 풀스택 개발자가 존재하기는 한다. ( 근데, 그런 개발자들을 풀스택 개발자라고 표현하지 않는다. 거의 기업의 잡부(?)처럼 부려지는 경우다. )노가다 - dokata, 土方 -'막일'을 하는 노가다를 하는 잡부가 한국형 풀스택 개발자라고 표현하겠다.하지만, 그런 테크트리로 형성된 한국형 풀스택 개발자들의 실력은 매우 볼품이 없는 경우가 대부분이다. 필자가 공공 SI현장에서 만난 수많은 한국형 풀스택 개발자들이 그러했다.그들은 컴파일러가 만들어내는 에러 메시지에 대한 이해는 없지만, 10년 넘게 업무를 배운 경험과 대충 Linux나 Windows Server의 기본적인 경험과 온통 스파게티 식으로 구성되어진 소스로 만들어진 더 이상 시장이 커지지 않는 한계가 다다른 시장에서 소프트웨어 개발을 하고 있다.태생적으로 '잡부'가 될 수밖에 없는 작업현장에서 진정한 의미의 풀스택 개발자는 거의 형성되기 어렵다. 이런 한국형 풀스택 개발자들은 실제 하나하나의 스킬들을 확인하거나 체크해본다면 거의 대부분 매우 부족하거나, 특정 기능에만 적합한 일반적으로 쓸모없는 기술들이 대부분일 가능성이 크다고 단언하겠다.이런 경향은 게임업계도 비슷하다. 한국형 풀스택 게임 개발자는 게임 기획부터 스프라이트의 2D부터, 포토샵이나 일러스트레이트도 다룰 줄 알며, 3D Max로 3D도 만들고, Auto-Cad로 도면 데이터도 다루고, DirectX에 Unity도 다루며, 서버나 iOS의 앱까지 만들 줄 안다고 하지만, 정작 그 어느 하나도 제대로 못 다루는 경우가 태반이다.물론, 전부 다루는 사람이 없는 것은 아니다. 있기는 있지만... 그분들 굉장히 유명하거나 특정 기술하나 가 대가의 수준이기 때문에 자신이 가진 다른 기술들을 포함해서 자신을 '풀스택 개발자'라고 포장하지 않는다.하지만, 한국에서 유독 '개발자 구인 광고'를 보면 '풀스택 개발자'를 찾는 곳이 많은 이유는 무엇 때문일까?그것은, 무지한 경영진이나 무지한 비즈니스 모델, 무지한 리소스 활용이 난무하는 헬게이트의 주인들이나 그런 단어들을 주로 사용한다고 보면 된다.100% 단언컨대 한 사람의 개발자가 완벽한 풀스택 개발자라고 하더라도, 요구사항이 발생하고 유지보수업무가 존재하는 업무를 하드웨어적인 서버 관리부터 서버 API, 앱 프로그래밍, 웹 프로그래밍을 하기 위한 스킬은 알 수 있다고 하더라도 그 복잡하고 어지러운 업무량은 모두 다룰 수 없다.만일 그런 것이 가능하다고 이야기하는 경영진이 있거나 무지한 영업맨이 있다면 정신 차리라고 조언해주자. 심지어 그렇게 만들 수 있는 서비스는 존재하지 않고, 존재한다고 하더라도.. 어마어마한 '기술적 부채'가 존재하며, 대부분의 가장 비싼 개발자의 리소스를 그 기술적 부채를 해결하기 위해서 사용되고 있을 것이라고.물론, 그렇게 동작하는 허접하고 쓰레기 같은 코드라고 하더라도, 특정 조건과 특정 환경에서는 서비스가 가능한 경우가 한국에는 많이 존재한다. 경영진이나 영업, 기획은 고객들을 설득하고 고객들이 해당 제품과 서비스를 사용하기 위해서 일부를 희생할 것이다. 그리고, 분명 다른 영역에서 누수가 발생하거나 희생되고 있는 것을 잊지 말자.특히나 경쟁이 없는 제품이거나 더 이상 리소스를 투입하기 어려운 소프트웨어나 서비스의 경우에는 이런 형태로도 동작은 할 것이다. 하루에 한두 번 서버의 Oracle 커넥션을 모두 종료하는 유지보수 행위를 하는 전산실의 업무가 그러한 경우 때문에 벌어진다.중견기업이거나 제조업체, 병원의 전산실에 '야간 당직'업무가 있고, 시스템 모니터링에 민감하다면 대부분 '기술적 부채'를 안고 허접하게 만들어진 것뿐이라고 판단하면 된다.말 그대로, 헬조선의 헬게이트, 헬(!)한 업무환경으로 소프트웨어 개발자로서 비전이 없는 영역이라고 생각하면 된다. 하지만, 그럼에도 불구하고... 스타트업 경영진이나 대기업, 중소기업 경영진들은 '풀스택 개발자'의 환상에 대해서 이야기한다.'모든 것을 다 하는 개발자'가 있으면, 복잡한 커뮤니케이션 비용도 안 들고, 인건비도 적게 들것이라는 착각을 한다. 다만, 이 부분만큼은 명쾌하게 이야기하겠다. '그런 회사 가지 말라'는 것이다.'풀스택 개발자'를 구인하고 있는 회사는 개발자의 무덤이라는 것이다. 대부분 그러하다. 그 이유를 다음과 같이 정리하겠다. 그들이 '풀스택 개발자'를 뽑고 싶은 이유는 간단하다. '돈'이 없어서다. 그리고, 다음의 이유들이 있는 경우이다.하나. 경영진이 요구사항 정의도 제대로 못하므로 개발자와 의사소통에 자신이 없다. 그래서, 풀스택 개발자를 구하려고 한다. 한 명 하고만 이야기하면 될 것이라고 착각한다.둘. 개발자의 인력이 몇 명이 투입되는지에 대해서 평가나 정의가 불가능하므로, 풀스택 개발자를 구하려 한다.셋. 개발자가 두 명, 세명이 있다면 팀 리더도 있어야 하고, 관리자도 있어야 하므로 그 비용을 줄이기 위해서 풀스택 개발자가 필요하다. 한마디로, 돈이 없다.넷. 현대의 웹서비스들을 가동하기 위해서는 최소한의 비용과 인건비가 투여된다. 이 비용을 투자할 정도로 비즈니스 모델에 가치가 없기 때문에 여러 명의 개발자를 고용할 수 없기 때문에 풀스택 개발자를 구하려 한다.다섯. 풀스택 개발자라면 막연하게 다 해줄 것 같은 환상을 가진 경영진이 있는 경우이다. 슬프지만, 전설의 개발자인 '제프 딘'을 고용한다고 하더라도, 삽질을 할 것이다.물론, 스타트업에 초기에 합류하면서 CTO의 역할을 부여받았다면 조금은 입장이 달라진다. 정당한 지분을 받고, 미래의 가치에 대해서 나눌 수 있다면, 해당 롤을 가진 사람은 알아서 '풀스택 개발자'가 될 가능성이 크다. 그러므로, 매우 당연하지만 CTO는 풀스택 개발자에 근접되면 좋기는 할 것 같다. 하지만, 현실적으로는 그렇게 세팅하지 못하는 경우가 대부분이다.그리고, 냉정하게 초기 개발이나 Lab수준, 시리즈 A를 투자받기 전의 '소프트웨어'나 '서비스'는 대부분 비즈니스 모델을 증명하는 수준에서 끝내는 것이 바람직하다. 굳이, 환상의 개발자나 풀스택 개발자가 아니라도 비즈니스 모델을 검토하고 증명하는 모델을 구현하는 것은 충분하게 가능한 경우가 대부분이다.사용자가 수백만 명도 아니고, 구현된 기능들도 수백 가지가 아니며, 아직은 스파게티 식으로 구성하더라도 무방하기 때문이다. 해당 기술적 부채는 서비스의 증명 후에 해당 코드는 버려지고, 다시 개발팀을 제대로 세팅하여 구현하면 되기 때문이다. 더군다나, 대부분의 스타트업은 고속 개발을 해야 하기 때문에 '풀스택 개발'이 가능한 '웹'만으로는 모든 것을 커버하기 어려울 것이다.좌우지간, 간단하게 이야기해서 '풀스택 개발자'타령하는 구인광고를 보게 된다면, 그 회사나 팀은 무언가 잘못 생각하고 있거나, '돈'이 없는 조직이라고 생각하면 된다. 거기에, '기술'이나 '개발'에 대해서는 아무것도 모르는 사람이 사장이 존재하는 곳이라고 생각하면 된다.헬게이트에 입성하고픈 개발자라면 '풀스택 개발자'를 구인하는 곳으로 가면 된다. 엄청난 '일'의 쓰나미를 경험하고, 인성이 피폐해지는 것을 경험할 것이다.필자는 국내 최고의 개발자들을 여럿 알고 있다. 하지만, 그분들은 자신들을 '풀스택 개발자'라고 이야기하지 않는다. 그 용어가 의미하는 것 자체가 '날림'이라는 것을 너무도 잘 알고 있기 때문이다. 물론, 10년 20년을 소프트웨어 개발을 하다 보면 얻어지는 경험과 지식들이 있다.궁극적으로는 풀스택 개발자가 이야기하는 비슷한 테크트리를 대부분 알고는 있게 된다. 하지만, 경력 20년 되고 하나의 도메인에 익숙하며, 특정 분야의 대가인 분들을 스타트업에서 고용한다는 것은 거의 불가능에 가깝다. 간혹, 그런 분들이 직접 스타트업을 하는 것이라면 모를까 말이다.이제 이야기를 마무리하겠다.'웹 개발'을 하려면 '풀스택 개발'을 지향하는 것은 맞다. 하지만, 그것 자체가 완벽한 풀스택 개발을 의미하는 것이 아니라는 것을 생각하기 바란다. 그리고, 경영진이나 비개발자들에게도 다시 한번 이야기한다. '풀스택 개발자'를 구인하겠다는 환상을 버리기 바란다.그런 사람 없고, 있다고 하더라도... '풀스택 개발자'를 구인하겠다는 발상으로는 절대 초빙하거나 모실 수 없다는 것을... 깨몽 하기 바란다.물론, '풀스택 개발자'처럼 이것 저것 다하는 정성스럽고, 일에 애정 넘치는 개발자들을 제대로 대우해주시기를... 기술로써의 풀스택 개발자가 아니라, 그 기업이 원하는 일을 풀스택 개발자처럼 일할 뿐이다. 그들에 대한 애정 넘치는 말한마디... 경영진들에게 부탁드린다.갑자기, '풀스택 개발자'에 대한 환상에 대해서 정리하고 싶어서 한 번에 글을 써 내려갔다. ~.~
조회수 1570

VCNC가 Hadoop대신 Spark를 선택한 이유

요즘은 데이터 분석이 스타트업, 대기업 가릴 것 없이 유행입니다. VCNC도 비트윈 출시 때부터 지금까지 데이터 분석을 해오고 있고, 데이터 기반의 의사결정을 내리고 있습니다.데이터 분석을 하는데 처음부터 복잡한 기술이 필요한 것은 아닙니다. Flurry, Google Analytics 등의 훌륭한 무료 툴들이 있습니다. 하지만 이러한 범용 툴에서 제공하는 것 이상의 특수하고 자세한 분석을 하고 싶을 때 직접 많은 데이터를 다루는 빅데이터 분석을 하게 됩니다. VCNC에서도 비트윈의 복잡한 회원 가입 프로세스나, 채팅, 모멘츠 등 다양한 기능에 대해 심층적인 분석을 위해 직접 데이터를 분석하고 있습니다.빅데이터 분석 기술큰 데이터를 다룰 때 가장 많이 쓰는 기술은 Hadoop MapReduce와 연관 기술인 Hive입니다. 구글의 논문으로부터 영감을 받아 이를 구현한 오픈소스 프로젝트인 Hadoop은 클러스터 컴퓨팅 프레임웍으로 비싼 슈퍼컴퓨터를 사지 않아도, 컴퓨터를 여러 대 연결하면 대수에 따라서 데이터 처리 성능이 스케일되는 기술입니다. 세상에 나온지 10년이 넘었지만 아직도 잘 쓰이고 있으며 데이터가 많아지고 컴퓨터가 저렴해지면서 점점 더 많이 쓰이고 있습니다. VCNC도 작년까지는 데이터 분석을 하는데 MapReduce를 많이 사용했습니다.주스를 만드는 과정에 빗대어 MapReduce를 설명한 그림. 함수형 프로그래밍의 기본 개념인 Map, Reduce라는 프레임을 활용하여 여러 가지 문제를 병렬적으로 처리할 수 있다. MapReduce slideshare 참조MapReduce는 슈퍼컴퓨터 없이도 저렴한 서버를 여러 대 연결하여 빅데이터 분석을 가능하게 해 준 혁신적인 기술이지만 10년이 지나니 여러 가지 단점들이 보이게 되었습니다. 우선 과도하게 복잡한 코드를 짜야합니다. 아래는 간단한 Word Count 예제를 MapReduce로 구현한 것인데 매우 어렵고 복잡합니다.MapReduce로 단어 갯수를 카운트하는 간단한 예제 (Java). 많은 코드를 작성해야 한다.이의 대안으로 SQL을 MapReduce로 변환해주는 Hive 프로젝트가 있어 많은 사람이 잘 사용하고 있지만, 쿼리를 최적화하기가 어렵고 속도가 더 느려지는 경우가 많다는 어려움이 있습니다.MapReduce의 대안으로 최근 아주 뜨거운 기술이 있는데 바로 Apache Spark입니다. Spark는 Hadoop MapReduce와 비슷한 목적을 해결하기 위한 클러스터 컴퓨팅 프레임웍으로, 메모리를 활용한 아주 빠른 데이터 처리가 특징입니다. 또한, 함수형 프로그래밍이 가능한 언어인 Scala를 사용하여 코드가 매우 간단하며, interactive shell을 사용할 수 있습니다.Spark으로 단어 개수를 카운트하는 간단한 예제 (Scala). MapReduce에 비해 훨씬 간단하다.Spark과 MapReduce의 성능 비교. I/O intensive 한 작업은 성능이 극적으로 향상되며, CPU intensive 한 작업의 경우에도 효율이 더 높다. (자료: RDD 논문)Apache Spark는 미국이나 중국에서는 현재 Hadoop을 대체할만한 기술로 급부상하고 있으며, 국내에도 최신 기술에 발 빠른 사람들은 이미 사용하고 있거나, 관심을 갖고 있습니다. 성능이 좋고 사용하기 쉬울 뿐 아니라, 범용으로 사용할 수 있는 프레임웍이기에 앞으로 더 여러 분야에서 많이 사용하게 될 것입니다. 아직 Spark를 접해보지 못하신 분들은 한번 시간을 내어 살펴보시길 추천합니다.기존의 데이터 분석 시스템 아키텍처기존의 데이터 분석 시스템 아키텍처기존의 시스템은 비용을 줄이기 위해 머신들을 사무실 구석에 놓고 직접 관리했으며, AWS S3 Tokyo Region에 있는 로그를 다운받아 따로 저장한 뒤, MapReduce로 계산을 하고 dashboard를 위한 사이트를 따로 제작하여 운영하고 있었습니다.이러한 시스템은 빅데이터 분석을 할 수 있다는 것 외에는 불편한 점이 많았습니다. 자주 고장 나는 하드웨어를 수리하느라 바빴고, 충분히 많은 머신을 확보할 여유가 없었기 때문에 분석 시간도 아주 오래 걸렸습니다. 그리고 분석부터 시각화까지 과정이 복잡하였기 때문에 간단한 것이라도 구현하려면 시간과 노력이 많이 들었습니다.Spark과 Zeppelin을 만나다이때 저희의 관심을 끈 것이 바로 Apache Spark입니다. MapReduce에 비해 성능과 인터페이스가 월등히 좋은 데다가 0.x 버전과는 달리 1.0 버전에서 많은 문제가 해결되면서 안정적으로 운영할 수 있어 비트윈 데이터 분석팀에서는 Spark 도입을 결정했습니다.Apache Zeppelin은 국내에서 주도하고 있는 오픈소스 프로젝트로써, Spark를 훨씬 더 편하고 강력하게 사용할 수 있게 해주는 도구입니다. 주요한 역할은 노트북 툴, 즉 shell에서 사용할 코드를 기록하고 재실행할 수 있도록 관리해주는 역할과 코드나 쿼리의 실행 결과를 차트나 표 등으로 시각화해서 보여주는 역할입니다. VCNC에서는 Zeppelin의 초기 버전부터 관심을 가지고 살펴보다가, Apache Spark를 엔진으로 사용하도록 바뀐 이후에 활용성이 대폭 좋아졌다고 판단하여 데이터 분석에 Zeppelin을 도입하여 사용하고 있고, 개발에도 참여하고 있습니다.또한, 위에서 언급한 하드웨어 관리에 드는 노력을 줄이기 위해서 전적으로 클라우드를 사용하기로 함에 따라서1 아래와 같은 새로운 구조를 가지게 되었습니다.새로운 데이터 분석 시스템 아키텍처새로운 데이터 분석 시스템 아키텍처새로운 데이터 분석 시스템은 아키텍처라고 하기에 다소 부끄러울 정도로 간단합니다. 애초에 전체 시스템 구성을 간단하게 만드는 것에 중점을 두었기 때문입니다. 대략적인 구성과 활용법은 아래와 같습니다.모든 서버는 AWS 클라우드를 이용수 대의 Zeppelin 서버, 수 대의 Spark 서버운영Spark 서버는 메모리가 중요하므로 EC2 R3 instance 사용로그는 별도로 저장하지 않고 서비스 서버에서 S3로 업로드하는 로그를 곧바로 가져와서 분석함중간 결과 저장도 별도의 데이터베이스를 두지 않고 S3에 파일로 저장Zeppelin의 scheduler 기능을 이용하여 daily batch 작업 수행별도의 dashboard용 Zeppelin을 통해 중간 결과를 시각화하며 팀에 결과 공유이렇게 간단한 구조이긴 하지만 Apache Spark와 Apache Zeppelin을 활용한 이 시스템의 능력은 기존 시스템보다 더 강력하고, 더 다양한 일을 더 빠르게 해낼 수 있습니다.기존현재일일 배치 분석코드 작성 및 관리가 어려움Zeppelin의 Schedule 기능을 통해 수행 Interactive shell로 쉽게 데이터를 탐험 오류가 생긴 경우에 shell을 통해 손쉽게 원인 발견 및 수정 가능Ad-hoc(즉석) 분석복잡하고 많은 코드를 짜야 함분석 작업에 수 일 소요Interactive shell 환경에서 즉시 분석 수행 가능Dashboard별도의 사이트를 제작하여 운영 관리가 어렵고 오류 대응 힘듦Zeppelin report mode 사용해서 제작 코드가 바로 시각화되므로 제작 및 관리 수월성능일일 배치 분석에 약 8시간 소요메모리를 활용하여 동일 작업에 약 1시간 소요이렇게 시스템을 재구성하는 작업이 간단치는 않았습니다. 이전 시스템을 계속 부분적으로 운영하면서 점진적으로 재구성 작업을 하였는데 대부분 시스템을 옮기는데 약 1개월 정도가 걸렸습니다. 그리고 기존 시스템을 완전히 대체하는 작업은 약 6개월 후에 종료되었는데, 이는 분석 성능이 크게 중요하지 않은 부분들에 대해서는 시간을 두고 여유 있게 작업했기 때문이었습니다.Spark와 Spark SQL을 활용하여 원하는 데이터를 즉석에서 뽑아내고 공유하는 예제Zeppelin을 활용하여 인기 스티커를 조회하는 dashboard 만드는 예제결론비트윈 데이터 분석팀은 수개월에 걸쳐 데이터 분석 시스템을 전부 재구성하였습니다. 중점을 둔 부분은빠르고 효율적이며 범용성이 있는 Apache Spark, Apache Zeppelin을 활용하는 것최대한 시스템을 간단하게 구성하여 관리 포인트를 줄이는 것두 가지였고, 그 결과는 매우 성공적이었습니다.우선 데이터 분석가 입장에서도 관리해야 할 포인트가 적어져 부담이 덜하고, 이에 따라 Ad-hoc분석을 수행할 수 있는 시간도 늘어나 여러 가지 데이터 분석 결과를 필요로 하는 다른 팀들의 만족도가 높아졌습니다. 새로운 기술을 사용해 본 경험을 글로 써서 공유하고, 오픈소스 커뮤니티에 기여할 수 있는 시간과 기회도 생겼기 때문에 개발자로서 보람을 느끼고 있습니다.물론 새롭게 구성한 시스템이 장점만 있는 것은 아닙니다. 새로운 기술들로 시스템을 구성하다 보니 세세한 기능들이 아쉬울 때도 있고, 안정성도 더 좋아져야 한다고 느낍니다. 대부분 오픈소스 프로젝트이므로, 이러한 부분은 적극적으로 기여하여 개선하여 나갈 계획입니다.비트윈 팀에서는 더 좋은 개발환경, 분석환경을 위해 노력하고 있으며 이는 더 좋은 서비스를 만들기 위한 중요한 기반이 된다고 생각합니다. 저희는 항상 좋은 개발자를 모시고 있다는 광고와 함께 글을 마칩니다.연관 자료: AWS 한국 유저 그룹 - Spark + S3 + R3 을 이용한 데이터 분석 시스템 만들기↩저희는 언제나 타다 및 비트윈 서비스를 함께 만들며 기술적인 문제를 함께 풀어나갈 능력있는 개발자를 모시고 있습니다. 언제든 부담없이 [email protected]로 이메일을 주시기 바랍니다!
조회수 970

[Tech Blog] Software architecture: The important stuff

마틴 파울러는 Software architecture 를 “무엇이건 간에 중요한 것들(The important stuff whatever it is)” 이라고 정의합니다. 조금은 재미있는 정의지만, 그 정의를 도출하기 위해 제시한 다른 정의를 들어보면 고개를 끄덕이게 합니다.  Software architecture 는 전문 개발자들이 같은 생각을 가지고 이해하는 시스템 디자인입니다. Software architecture 는 이른 시기에 정해져야 하는 디자인 결정들입니다. 혹은 여러분이 “아, 처음부터 좀 더 잘 생각하고 할 껄”이라고 후회하는 바로 그 결정들입니다. Software architecture 는 또한 바꾸기 어려운 결정들의 집합입니다.  결국 무엇을 중요하게 생각할 것인가, 그것이 Software Architecture 라는 의미입니다. Why is it important? 왜 중요한지 설득하지 못한다면 사실 중요하지 않은 것일지도 모르죠. 그래서 왜 Software Architecture 이 중요한지 짚어보고자 합니다. 쿠팡은 Microservice architecture 로 전환하는 여정을 글로 남겼는데요. 블로그 글의 제목을 “행복을 찾기 위한 우리의 여정” 이라고 지었습니다. (좋은 글이니 읽어보시길!) 다시 말해서, Software Architecture는 개발가자 더 좋은 제품을 만들 수 있는 길이기 때문에 중요하다고 말합니다. 그러나 좋은 Software Architecture를 만드는 일은 쉽지 않습니다. 블로그 글을 인용 해보겠습니다: “여기 저렴한 제품과 비싼 제품이 있습니다. 비싼 제품은 software architecture 가 잘 고려되어 있고, 저렴한 제품은 시스템 디자인에 대한 고민 없이 구현되어 있습니다. 하지만 두 제품은 겉으로 보기에 차이가 없습니다. 소비자가 보기에 똑같이 보이고, 똑같은 기능이 있으며, 성능 또한 같습니다. 어떤 제품을 사야할까요?” 소비자는 제품을 만든 개발자의 행복을 위해 더 비싼 제품을 선택하지는 않습니다. 개발자 역시 동료들에게 “내가 행복하려면 시간과 돈이 좀 더 들더라도 좋은 software architecture 를 구성해야 해.” 라고 주장하기엔 설득력이 부족하죠. Software architecture 가 왜 중요한지 모두가 공감하려면 경제적인 입장에서 그 중요성을 설득해야 합니다. “내부 품질을 좀 포기하더라도 이번 릴리즈에 더 많은 기능들이 들어가야 해.” 라는 의견에 “안돼 우리(개발자)는 더 전문적으로 구성해야 해.”라는 의견으로 대응하면 항상 질 수 밖에 없습니다. 장인 정신과 경제 논리 사이의 싸움에서는 경제 논리가 항상 이겨왔거든요.   Cumulative functionality over Time Software architecture 를 고려하지 않으면서 제품을 개발하면 초기에는 기능 추가 속도가 빠를 수 있지만, 시간이 흐름에 따라 제품의 기능 증가 속도는 점차 느려집니다. 이미 구현된 기능들과 코드가 새로운 기능을 추가하는데 걸림돌이 되기 때문입니다. 한편, 좋은 설계를 지속적으로 건강하게 유지하고, 주기적으로 리팩토링을 하고, 코드를 깨끗하게 유지한다면 시간이 흘러도 기능 추가가 느려지지 않을 수 있습니다. 오히려 기능을 추가하기 위해 수정해야 할 곳들이 명확하고 모듈화 또한 잘 되어있기 때문에 시간이 갈 수록 기능 추가가 더욱 빠르게 진행될 수 있습니다. 새로운 개발자가 참여하는 시점에도 시스템을 더욱 빠르게 이해하고, 더 빠르고 안전하게 기능을 추가할 수 있게 됩니다. 결국 장기적으로 더 많은 기능을 생산하고 빠르게 고객에게 전달하기 위해서 개발팀은 좋은 디자인과 설계에 대해 깊게 고민해야 합니다. What is the best software architecture? 옳은 software architecture 는 없습니다. 상황에 따라 해답은 다를 수 있습니다. Microservice architecture 가 좋다고 해서 모든 것에 대한 답이 microservice architecture 인 것은 아니고, 마찬가지로 어떤 시스템이 monolithic architecture 로 구현되어 있다고 해서 뒤쳐져 있는 것도 아닙니다. 모든 선택에는 Tradeoff 가 있기 마련이니까요. 유선 통신 시스템을 구성한다고 생각해 볼까요? 우리 나라처럼 인터넷이 잘 구성된 상황에서 Skype 로 할 수 있는 통화는 무료이고, 품질도 좋고, 영상 통화까지 됩니다. “Skype 만세! 인터넷을 통한 통신이 항상 옳습니다!” 라고 외치려던 시점에 정전이 되었습니다. 방금 외친 외침은 멀리 가봐야 옆집 정도 닿겠죠. 한편 기존 유선 전화 시스템은 느리고 화상 통화도 안되지만, 전화선 자체에 전원이 공급되고 있기 때문에 정전 시에도 통화가 가능합니다. 전쟁 상황이나 기타 재난 등에도 반드시 통신이 가능해야 하는 곳은 유선 전화 시스템이 꼭 필요할 것 같습니다. 은행 시스템도 적절한 예시가 될 수 있습니다. 비밀번호 입력, 전화 인증, OTP 확인하는 등 은행 업무는 왜이리도 복잡할까요? 그냥 비밀번호 기억해주고 로그인 유지해주면 참 편할텐데 말이죠. 안전하기 위해서겠죠. 여러분의 자산은 소중하니까요. 사용성(Usability)과 안전성(Security)은 종종 둘 사이를 조절해야 하는 Tradeoff 입니다. 만들려는 제품과 시스템, 환경, 시기와 조건 등에 따라서 적절한 architecture 는 달라집니다. 좋은 architecture 를 선택할때 개발자는 선택한 것의 대척점에 있는 무언가를 포기 해야합니다. 그렇기에 software architecture 는 기술적인 범주 안에서만 고려되면 안되고, 구현하고자 하는 비지니스를 매우 잘 이해하고 고려해서 적용해야 합니다. What are you going to do? 이미 구성된 software architecture 를 변경하는 것은 굉장히 어렵습니다. 이미 구성되어 있는 것들을 상세하게 알고 있어야 하고, 비지니스의 요구 사항을 수용해야 하며, 이미 존재하는 기능이 변경 도중 문제 없이 동작해야 합니다. 또한 기존 시스템에 기여한 개발자들과 변경 사항에 대한 공감대를 이뤄야 하며, 겉으로 보기에 당장 변화가 없는 것에 대한 비용에 대해 많은 사람들을 설득해야 합니다. 최근 Buzzvil 에서는 Architecture Task Force 팀을 구성하였습니다. 이를 통해 전체적인 설계를 정비하고 모든 개발팀이 구조적으로 같은 이해를 할 수 있도록 분석, 조사, 계획 수립, 실행에 옮길 예정입니다. 지속적인 공유를 통해 전사적인 공감대를 유지하고 체계적인 문서화와 가이드라인을 통해 모든 팀원이 함께 실행하며 성장할 수 있는 기반을 준비하게 될 것입니다. 궁극적으로 전사 프로젝트와 모든 팀이 더욱 빨리 움직일 수 있는 software architecture 를 구성하고, 이를 통해 더 많은 기능을 더 빠르게 전달할 수 있게 할 것입니다. 아직 해야할 일들이 많이 남아있지만 제대로 계획하고 빠르게 움직인다면 충분히 좋은 결과를 만들 수 있을 것 같습니다. 당장은 눈에 보이는 변화가 없을지라도, 좋은 디자인에 대한 고민과 실행이 우리가 궁극적으로 바라는 비전과 목표에 한 걸음 더 빠르게 다가가는 올바른 길이라고 믿습니다.   *버즈빌에서 개발자를 채용 중입니다. (전문연구요원 포함)작가소개 Whale, Chief Architect “Keep calm and dream on.”
조회수 1394

Code without Limits

WWDC18 Review (1): Bring the Func! 보기 Introduction지난 글 Bring the Func! 에서 WWDC를 소개했습니다. Keynote와 Platforms State of the Union에서 인상 깊었던 경험도 소개했고요. WWDC 첫째 날은 애플에서 큰 이벤트를 진행했고, 둘째 날부터 마지막날까지는 세션과 랩스, 스페셜 이벤트를 진행했습니다. 이번엔 지난 글에서 미처 쓰지 못했던 것을 소개하겠습니다.SessionWWDC 하면 가장 먼저 떠오르는 건 대개 Keynote입니다. 하지만 다른 세션이나 랩스부터 생각나는 애플 개발자도 있을 겁니다. 저도 처음엔 Keynote만 기대했지만, 행사에 참여하면서 세션과 랩스의 매력(?)에 빠졌습니다.Apple Developer 웹사이트에서 수많은 기술 관련 영상을 볼 수 있다.애플 관련 애플리케이션 개발자는 문제에 부딪히면 Apple Developer 웹사이트에서 도움을 얻는데요. 특히 Development Videos 사이트에 들어가면 그해 발표한 WWDC 세션부터 시작해서 그 동안의 세션들을 모두 볼 수 있습니다. Topics에서는 주제별로 카테고리를 만들어, 해당 주제에 관한 동영상들을 모아서 볼 수 있고, Library에서는 찾고자 하는 내용에 대한 키워드를 검색해서 찾을 수 있습니다.Development Videos - Apple Developer 첫 화면Topics 에서는 주제별 동영상들을 볼 수 있다.Library 에서는 검색하는 키워드에 해당하는 동영상들을 볼 수 있다.WWDC 행사장은 Hall 1 ~ Hall 3, 그리고 Executive Ballroom까지 4개의 방으로 구성되어 있었습니다. 이곳에서 각각의 세션을 들을 수 있었는데요. 시간대별로 3~4개의 세션을 동시에 진행합니다. 듣고 싶은 세션은 해당하는 방에 들어가서 들으면 됩니다. 만약 같은 시간에 듣고 싶은 세션이 두 개 이상이라면 하나만 현장에서 듣고, 다른 세션은 developer 웹사이트 또는 WWDC 앱에서 업로드되길 기다려야겠죠. 물론 24시간이 지나면 세션 영상이 WWDC앱에 업로드됩니다. WWDC 앱에서 제공하는 행사장 지도세션이 진행되는 곳의 내부수많은 개발자의 똑똑한 머리와 지미집세션이 시작되자 개발자들은 무릎 위에 올려 놓은 맥북을 열심히 쳤습니다. 하나라도 놓치기 싫어서 열심히 타자를 치는 개발자들의 모습이 멋있었습니다. 마치 대학 영어 강의를 듣는 기분이었죠.아쉬운 점이 있다면, 에어컨을 너무 강하게 틀어 세션 행사장이 매우 추웠다는 겁니다. 며칠을 견디다 마지막 날엔 결국 행사장 밖에서 라이브로 시청했습니다. 그리고 세션을 진행하는 동안 빠르게 코딩을 하다 보니, 소스 코드를 다 작성하기도 전에 다음 장면으로 넘어가는 부분이 많았습니다. 실시간으로 같이 작업할 예제 소스 코드를 제공하거나 조금 더 효율적으로 세션을 들을 수 있게 해줬으면 좋겠다는 생각이 들었습니다.행사장에서 제공하는 아침 식사와 함께 맥북 프로에서 라이브로 세션 시청What’s new in ARTKit 2지금부터는 인상 깊었던 세션 세 가지를 소개하겠습니다. 첫 번째는 What’s new in ARTKit 2였습니다. 이 세션이 가장 인상 깊었던 이유는 애플이 AR에 중점을 두고 있다는 생각이 들었기 때문입니다. 실제로 Keynote 발표 중에 장난감용 블럭을 만드는 회사 관계자 두명이 AR을 활용한 앱을 실행해 노는 모습을 보여주기도 했습니다.Keynote 발표 중 한 장면. 크레이그 페더리기가 AR 파트에서 Shared experiences에 대해 발표하고 있다.가장 재미있었던 건 현실 공간을 저장해 다른 유저들과 공유할 수 있는 기능이었습니다. ARWorldMap Object를 이용해 사용자가 기기를 움직이면서 현실 공간의 모습을 저장합니다. 나중에 앱을 다시 실행하면 저장했던 현실 공간 맵이 그대로 유지되고, 이전의 모습도 나타나죠. 예를 들어, 노란 테이블 위에 가상의 물건을 올려 놓았다면, 나중에 테이블을 향해 기기를 움직였을 때, 그 자리에 놓여있던 가상의 물건이 다시 나타납니다. 또한, 저장한 맵을 근처의 다른 유저의 기기로 전송할 수 있습니다. 이렇게 하면 서로 다른 기기에서 같은 맵을 보면서, 같은 경험을 할 수 있게 됩니다. 개념을 확장하면 하나의 AR앱으로 다중 유저들이 게임을 함께 즐기거나 멀리 떨어져 있어도 같은 교육을 받을 수 있죠.SwiftShot AR게임을 즐기려고 기다리는 개발자들WWDC18 Keynote에서 잠깐 소개되었던 SwiftShot AR 게임이 이런 특징을 잘 나타난 앱입니다. 실제로 행사장 1층 안쪽에 이 게임을 즐길 수 있는 공간이 따로 마련되어 있었습니다. 개발자들이 직접 게임을 즐길 수 있었고, 마지막 날엔 개인전과 팀전을 진행해 1등에게 선물(AR뱃지)을 주었습니다. 옆에서 구경했는데 재밌었습니다. 아이패드가 있다면 여기를 클릭해 샘플 코드를 다운 받을 수 있습니다. 빌드해서 재미있는 AR 게임을 친구들과 함께 즐겨보세요. A Tour of UICollectionView브랜디 앱은 90% 이상 UICollectionView를 이용해 앱 화면을 만들었습니다. 많은 UICollectionViewCell을 다시 사용할 수 있고, 커스텀 레이아웃도 만들 수 있기 때문입니다. 이전에 포스팅한 ‘테이블이냐, 컬렉션이냐, 그것이 문제로다!’에서 UICollectionView를 공부했지만 더 배우고 싶어서 A Tour of UICollectionView를 들었습니다.이 세션은 UICollectionView에 대해 좀 더 깊은 내용을 다뤘습니다. UICollectionView와 UITableView의 가장 큰 차이점인 레이아웃에 초점을 두었는데요. 단순히 UICollectionView에서 선형 레이아웃 말고 그리드 형식의 레이아웃을 만들 수 있다는 것, 커스텀 레이아웃을 만들 때 고려할 것, 구현에 대한 가이드라인까지 제시했습니다. 애플에서 제공하는 레이아웃 중 하나는 UICollectionViewFlowLayout입니다. UICollectionViewFlowLayout은 line-based 레이아웃 시스템입니다. 일직선 상에서 최대한 많은 아이템들을 채운 후, 다음 행 또는 열로 넘어가 아이템을 채우는 형식으로 컨텐츠들을 배치합니다. 가장 흔한 레이아웃 모습이 바로 그리드 레이아웃입니다.그리드 레이아웃, 또는 UICollectionViewFlowLayout으로 구현할 수 있는 레이아웃Line-based 레이아웃이 아닌 다른 모습의 레이아웃이라면 어떤게 있을까요? 세션에서 예를 든 레이아웃이 바로 모자이크 레이아웃이였습니다. 브랜디 앱, 또는 다른 앱에서 볼 수 있는 모자이크 레이아웃은 일직선상에서 일렬로 정렬하지 않고, 그리드 레이아웃과 조금 다른 모습입니다. 아래의 스크린샷을 보면 어떤 레이아웃인지 감이 잡힐 겁니다.브랜디 앱, 인스타그램 앱, 세션 예제 앱의 모자이크 레이아웃모자이크 레이아웃은 line-based 레이아웃이 아니기 때문에 일반적인 UICollectionViewFlowLayout을 사용하지 않고, UICollectionViewLayout을 상속하여 커스텀합니다. 총 4개의 기본 메소드와 추가적으로 고려해야하는 메소드 하나를 이용하여 커스텀 UICollectionViewLayout을 만들 수 있습니다. 모든 컨텐츠를 담는 뷰의 크기, 레이아웃의 속성 2개, 그리고 레이아웃을 준비하는 기본 메소드들을 구현하고, 레이아웃이 변경해야하는 상황(기기를 가로로 눕히거나 레이아웃의 위치가 변경될 때 등)을 고려하여 메소드를 구현하면 됩니다.open var collectionViewContentSize: CGSize { get } func layoutAttributesForElements(in rect: CGRect) → [UICollectionViewLayoutAttributes]? func layoutAttributesForItem(at indexPath: IndexPath) → UICollectionViewLayoutAttributes? func prepare() func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) → Bool 세션 강연자가 직접 소스를 작성하면서 메소드 구현과 퍼포먼스를 위한 팁을 설명했습니다. 이 세션을 통해서 UICollectionView의 핵심인 레이아웃에 대해 더 깊이 배울 수 있었죠. 레이아웃 말고도 멋진 애니메이션 효과 구현 방법을 설명해주었는데요, 여기를 클릭해 직접 동영상을 보는 걸 추천합니다! 영상을 보고 나면 분명 멋진 UICollectionView를 구현할 수 있을 겁니다.Build Faster in XcodeBuild Faster in Xcode 는 가장 인기 있었던 세션 중 하나였습니다. 한국 개발자들 사이에서도 추천할 세션 중 하나로 꼽혔죠. 물론 혁신적으로 빌드 타임을 줄일 수는 없지만, Xcode의 기능과 빌드 타임이 어떻게 연결되는지 알 수 있었습니다. 프로젝트 세팅과 가독성 있는 코드 작성, 이 두 가지가 빌드 타임과 관련되어 있었습니다. Xcode는 프로젝트를 구성(configure)할 때, 빌드할 targets(iOS App, Framework, Unit Tests 등)와 targets 사이의 종속 관계(dependency)를 따릅니다. Dependency에 따라서 target을 빌드하는 순서도 정해지는데, 순서대로 빌드하지 않고 최소한의 연결을 유지하면서 병렬적으로 빌드하게 됩니다.빌드 시간을 아름답게 줄일 수 있다.이것은 Xcode 10에서 Scheme Editor에서 설정할 수 있습니다. 프로젝트의 Target → Edit Scheme → Build → Build Options에서 Parallelize Build를 체크하면 됩니다.Xcode 10의 Parallelize Build또한 Xcode 10에는 빌드 타임을 계산하는 기능도 있습니다. 빌드할 때 어떤 부분에서 얼마나 걸렸는지 요약해서 보여주는 기능도 있습니다. Product → Perform Action → Build With Timing Summary를 선택하면 빌드 후 요약해서 Xcode에 나타납니다.Build With Timing Summary를 선택하여 빌드하면위 스크린샷처럼 요약해서 보여준다.Xcode 프로그램을 이용해서 빌드 타임을 관리하는 방법도 있고, Swift으로 작성한 소스 코드를 가독성 높은 코드로 바꾸는 방법도 알려줍니다. 또한 Bridging Header로 Objective-C와 Swift를 동시에 개발할 때 도움이 되는 방법도 설명해줍니다. 빌드 타임에 대해 관심을 가질 수 있는 계기가 될 겁니다. 한 번씩 영상을 보길 추천합니다!Labs세션을 듣고 궁금한 점이 생겼다면 Labs(랩스)에서 질문할 수 있습니다. 각 세부 분야별 애플 기술자들이 시간대별로 모여서 개발자의 질문을 받거나 문제점을 해결할 수 있도록 도움을 줍니다.Technology Labstechnology Labs 간판Labs 입구에 있는 부스별 주제짙은 남색 Engineer 티셔츠를 입은 애플 기술자들이 질문을 받고 있다.가장 인기가 많았던 랩스는 Auto Layout and Interface Builder, UIKit and Collection View, Building Your App with Xcode 10 등등이었습니다. 사람이 많아서 줄 서서 기다릴 정도였습니다. 내년에는 랩스 시간이 조금 더 길게 진행됐으면 좋겠다는 생각이 들었습니다.WWDC 기간 중에 랩스에서 시간 보낸 적이 있었습니다. iOS 프로그래밍을 시작한 지 1년도 되지 않아 궁금했던 것들과 새로운 Xcode 10에 대해서 질문했습니다. 아래는 질문했던 내용을 문답형식으로 작성했습니다.애플 기술자와의 문답문: iOS 프로그래밍을 개발한지 얼마 안 된 신입 개발자입니다. 어떻게 하면 프로그래밍 실력을 높일 수 있나요? 답: 앱 하나를 처음부터 끝까지 개발해보면 실력을 늘릴 수 있다. 또한, 애플에서 만든 스위프트 책 보는 걸 추천한다.문: WWDC 기간 동안에 테스팅(testing)에 관심을 가지게 되었습니다. 앞으로 상용하는 앱을 테스트하면서 개발하고 싶은데, 테스트는 어떻게 시작하면 좋을까요?답: 이것에 대한 세션 동영상 을 보는 걸 추천한다. 테스트는 중요한 것이기 때문에 이 동영상을 보면서 테스트에 대해 배우고 난 뒤, 직접 앱을 테스트해보길 권장한다.문: 새로운 Xcode 10에서 앱을 빌드해봤는데 에러가 났습니다. 이런 에러가 나타난 이유는 무엇인가요?답: Xcode 10에 있는 컴파일러 문제다. 소스를 수정하면 앱이 빌드될 것이다. 컴파일러에 대해서 Xcode 팀에게 전달하겠다. (Range 관련된 컴파일러 문제였습니다.)문: 빌드 시간을 줄일 수 있는 방법은 무엇인가요?답: 컴파일하는 소스 코드를 줄이거나 프레임워크를 만들어서 빌드할 때 마다 계속 빌드하지 않도록 하면 시간을 줄일 수 있다. 이와 관련된 세션을 들으면 조금 더 자세한 내용을 확인할 수 있다.Consultation Labs애플 기술자와 일대일 면담식으로 진행하는 랩스도 있었습니다. 예전에는 선착순으로 진행되었는데 올해는 신청을 받고 당첨된 개발자에게만 기회를 주었습니다. 당첨되면 30분 동안 신청한 분야(디자인, 앱 스토어, 마케팅 등)의 전문가와 질의응답을 할 수 있습니다. 가장 인기가 많았던 User Interface Design 랩스를 신청하고 당첨이 되었습니다. 디자인 전문가들과 시간을 보낼 수 있었는데요. 애플 디자이너들이 생각하는 최선의 디자인 가이드라인을 배울 수 있었고, 함께 앱을 관찰하면서 개선되었으면 하는 디자인 요소 등의 팁을 얻었습니다. 아쉽게도 촬영 및 녹음은 불가능했습니다. 시간도 짧게 느껴져서 아쉬웠습니다.Special EventsWWDC 기간 동안에는 세션과 랩스 위주로 진행되지만 중간에 가끔 스페셜 이벤트들도 진행합니다. 점심 시간에 유명 인사들을 초청해서 하는 짧은 강연, 아침 일찍부터 모여서 같이 달리면서 즐길 수 있는 이벤트(WWDC Run with Nike Run Club), 맥주와 함께 음악을 즐기는 이벤트 등 개발 외적인 이벤트들을 많이 진행했습니다. 저는 그 중에서 Bash 이벤트를 소개하고 싶군요.BashBash는 목요일에 진행한 뒤풀이 파티였습니다. WWDC 행사장 근처에 공원을 빌려서 맛있는 음식과 주류를 무료로 제공하고, 초청 가수의 공연도 볼 수 있었습니다. 초청 가수가 공연하기 전에 소개할 때 크레이그 페더리기가 무대에 나왔습니다. 개발로 지친 몸과 머리를 식히고 다른 개발자들과 어울려 놀 수 있는 공간이였습니다. 뒤풀이 파티가 끝나갈 때쯤 진짜로 WWDC가 끝나간다는 느낌이 들어서 괜히 아쉽기도 했었습니다.무대와, 맥주와, bash 입장권한국인 개발자들과 함께 즐긴 뒤풀이 파티초청 가수를 소개하러 무대에 올라온 크레이그 페더러기아름다운 노을!마치며이번 글에서는 WWDC의 세션, 랩스, 스페셜 이벤트를 설명했습니다. WWDC가 한 달 전에 끝났지만 지금 다시 생각하면 두근두근 설레고 또 가고 싶어집니다. 내년 WWDC에 또 갈 수 있을까요? 지금까지 애플 개발자들의 축제였던 WWDC의 Review를 마치겠습니다. 긴 글을 읽어주셔서 감사합니다!글김주희 사원 | R&D 개발1팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유 #이벤트참여 #이벤트후기 #미국

기업문화 엿볼 때, 더팀스

로그인

/