스토리 홈

인터뷰

피드

뉴스

조회수 1077

2016, 개발자의 Life.. 꿈...#1

주변 개발자들의 삶이 매우 행복을 추구하는 삶으로 변해가고 있다는 것을 느낀다. 주변의 개발자들의 모습을 몇 가지 정리해보자. 이를 '지속 개발을 위한 개발자 Life 스타일'이라고 정의하겠다.개발자#A10년 넘게 개발하던 패키지를 기반으로 필요 기능을 최소화하여 1인 개발기업에 성공하였고 제주도로 내려가서 지역에 속한 분들과 호흡하는 삶을 추구하면서도 소프트웨어 개발의 핵심을 잃지 않았다. 정말, MVP 기능에 최대한 집중하면서 필요한 시장 영역을 더 확대하지 않고, 소프트웨어를 개발하고 있는 개발자와 해당 소프트웨어를 사용하는 고객과 시장에 대해서 같이 합리적으로 지속할 수 있는 지속할 수 있는 소프트웨어 개발의 삶을 이루었다.그리고, 그러한 Life환경을 주변에 전파하면서 불과 얼마 전 또 한 명의 구 루급 개발자에게 비슷한 삶의 길을 가르쳐준다. 정말 부러운 개발자들...개발자#B복잡한 업무나 더 많은 보수를 위해서 더 좋은 회사를 찾기보다는 삶이 존재하는 근무시간을 위해서 재택근무를 찾고 있다. 비용도 최대한 낮추면서 생활을 위한 회사를 찾아다니고 있다. 아무래도, 외국계 개발회사를 선택할 것 같다.개발자#C오픈소스 진형에서 인정받는 개발자이다. 본인이 원하는 오픈소스 프로젝트를 추진하는 것을 보장받고 외국계 기업의 원격근무를 선택했다. 보수도 나쁘지 않고, 근무시간은 알아서 하는 것이지만, 원격으로 일하는 것이기 때문에 '능력'을 보여주기 위해 더 많은 시간을 소프트웨어 개발에 투자한다. 굳이, 서울 시내에 있을 필요가 없기 때문에 외각으로 집도 옮겼다.개발자#D일부러, 실리콘 벨리의 스타트업을 선택했다. 조만간 상장 예정인데 매우 큰 혜택을 받을 것 같다. 그 역시 지속 개발이 가능한 삶을 추구한다.2016년 올 초의 개발자 트렌드는 '지속 개발을 위한 Life'를 지향하는 개발자들이 늘어났다고 평가해본다.우리 모두 지속개발이 가능한 삶을 지향해 보는 것은 어떨까나...
조회수 1124

iOS Passbook의 signpass 분석

Passbook이란?iOS 6에서 새로 추가된 Passbook은 여러 장의 디지털 티켓, 쿠폰, 매장 카드를 담고 편리하게 이용할 수 있는 애플리케이션입니다. Passbook 안에 들어가는 티켓, 쿠폰, 매장 카드들을 Pass라고 부릅니다. Pass는 기존 앱보다 손쉽게 개발, 배포할 수 있으며, 위치나 시간에 따라 최적의 사용 시간에 맞춰 알림을 주기 때문에 더 높은 사용성을 보장할 수 있습니다. 이번 블로그 글에선 Pass를 만드는 과정에서 이용되는 signpass의 소스코드를 분석해보도록 하겠습니다.signpass?signpass는 애플이 Pass 개발에 이용할 수 있게 제공한 툴킷으로, 형식에 맞춰 개발된 Pass 디렉터리를 인증서와 함께 하나의 파일(*.pkpass)로 묶어주는 프로그램입니다. Xcode Project 형태로 소스코드와 함께 Passbook Materials에 포함되어있습니다. Passbook Materials는 애플 개발자 계정이 있으면 무료로 다운받으실 수 있습니다.왜 분석하는가?signpass가 하는 일은 Pass를 만들어서 배포하는 데 있어 필수적인 과정이지만, 많은 Pass 배포환경이 리눅스 운영체제 위에 각자 고유의 서버 시스템 위에 구축될 것이므로, 해당 커맨드 라인 툴을 그대로 이용하기보단 기반 서비스 플랫폼에 맞춰서 새로 구현해야 할 필요가 있습니다. 다행히 signpass가 하는 일은 간단하며, 애플도 signpass를 주석이 포함된 소스코드 형태로 배포하였기 때문에 어렵지 않게 분석할 수 있습니다. 이 글은 signpass를 분석해야 할 분들에게 더욱 편하게 처리 과정을 이해할 수 있도록 돕고자 합니다.signpass의 처리 과정signpass는 크게 3가지 일을 수행합니다.파일명과 hash를 key/value 형태로 담은 manifest.json을 제작manifest.json을 인증서로 인코딩한 signature 제작기존 파일과 앞의 1, 2 번에서 제작한 파일을 함께 zip 압축signpass에는 이 외에도 validation 기능과 몇 가지 커맨드 라인 처리 기능이 포함되어있지만, pkpass를 만드는 과정만 보고 싶으시다면 PassSigner.m 의 +(void)signPassWithURL:(NSURL *)passURL certSuffix:(NSString*)certSuffix outputURL:(NSURL *)outputURL zip:(BOOL)zip 메서드만 참조하시는 것만으로 충분합니다.manifest.json 제작형식에 맞춰 제작된 *.pass 디렉터리의 파일을 훑으면서 json 형태로 저장합니다. key는 파일이름, value는 파일 내용에 대한 SHA1 해싱이 들어가며 ([fileData SHA1HashString]), 완성된 json 파일은 보통 아래와 같은 내용을 하게 됩니다.{ "[email protected]" : "bd5442b4b08aa4dde333ec9ef0269e7fd93140b3", "icon.png" : "ba47a8021c8d74d2146d7244c8a0566be37df43b", "pass.json" : "1cdbac541c1736420e7fbd7455c98d0735a71a9e", "logo.png" : "780540b3a324bf66aeaee2d352283371356e9502", "[email protected]" : "a718ffd4e611e404dd3eb701454bcaefdabbe311" } signature 제작manifest.json 파일을 애플에서 받은 Pass 인증서를 이용해 인코딩합니다. (CMSEncodeContent()) 인증서를 통해 manifest를 인코딩함으로써, 쿠폰 발급자가 아닌 다른 사용자가 임의로 Pass를 편집하는 것을 방지합니다. 인코딩된 파일은 signature라는 이름의 파일로 Pass의 Top-level 디렉터리에 manifest.json과 함께 저장합니다.CMSEncodeContent는 PKCS #7 기반의 Cryptographic Message Syntax RFC 3852 표준으로, 해당하는 과정을 다른 플랫폼에 포팅할 때 표준 문서와 애플 인증서 형태를 함께 참조하시기 바랍니다.zip 압축위의 두 파일과 나머지 파일들을 모두 포함하여 zip파일로 압축합니다. 단순한 과정이므로 부가적인 설명은 생략합니다. :-)마치며signpass의 구현 과정을 이해하였다면, Pass 배포 서비스를 구축 시 기반 플랫폼 의존도가 낮은 시스템을 구축할 수 있습니다. 또한, 이 분석을 통해, 애플이 Pass를 어떠한 방식으로 안전하게 보호하는지를 이해할 수 있으므로, 여러 회사에서 Pass의 도입 여부를 고민할 때 보안 측면에서 좋은 참고 자료가 될 것으로 기대합니다.#스포카 #개발 #개발자 #iOS #iOS개발 #앱개발 #Signpass #인사이트
조회수 2856

웹 플러그인 개발기 - iframe의 재발견

채널 웹 플러그인을 개발하며 겪은 문제들과 우리 팀의 해결책을 소개합니다. 채널 웹 플러그인은 SDK의 형태로 고객사 웹사이트에 붙어서 고객이 매니저와 대화할 수 있는 인터페이스를 제공합니다. 이 글을 쓰고 있는 당시 약 2300개의 채널이 개설되었고, 하루 약 180만 명의 일반 유저가 웹사이트에 붙은 저희 플러그인을 보고 있습니다.플러그인은 고객사 웹사이트 (이하 호스트 웹사이트라고 함) 의 HTML 도큐멘트에 붙어서 실행됩니다. 이 말은 실행 환경 (자바스크립트, CSS, DOM 환경 등) 을 우리가 컨트롤하지 못한다는 것을 의미합니다. 이것이 일반적인 웹서비스와 플러그인 개발의 가장 큰 차이점이고 사실상 많은 이슈들은 이 차이로부터 기인합니다. 또 이것에 대응하기 위해 프레임워크의 선택부터 개발, 배포에 이르기까지 훨씬 신경 써야할 부분이 많았습니다. 이 글에서는 그 중 호스트 웹사이트와의 실행 환경 공유에 따른 문제들을 자바스크립트와 CSS로 나누어 나열하고 iframe 을 이용하여 해결한 과정에 대해 설명하겠습니다.채널 홈페이지에 웹 플러그인이 붙은 모습1. 자바스크립트와 관련된 이슈1-1. 네임스페이스 공유에 따른 충돌 문제브라우저에서 자바스크립트는 글로벌 네임스페이스를 공유합니다. 이 속성 때문에 플러그인에서 window 를 접근해서 수정한다던가 글로벌로 객체를 정의해서 사용하면 호스트 웹사이트에 영향을 미칠 수 있습니다. 이 문제는 코딩할 때 아래 항목을 주의하는 정도로 큰 비용 없이 방지할 수 있습니다.플러그인의 최상위 네임스페이스를 만든다.(ex. window.CHPlugin)플러그인에서 사용하는 모든 객체는 최상위 네임스페이스 아래에 정의되도록 한다.(ex. window.CHPlugin.outObject)window 객체에 접근할 때는 수정하거나 추가하는 부분이 없도록 주의한다.(ex. [removed] = function(){}와 같은 코드는 사용하면 안 됨. 기존에 [removed] 이벤트가 날아감)사용하는 라이브러리들 중에 window에 바인딩하는 것이 없는지 체크하고 있으면 직접 수정하여 모듈화한다. (ex. lodash는 기본적으로 window 에 _ 객체를 생성함)이건 사실 플러그인이 아니더라도 주의해야하는 거죠..1-2. 에러로 인한 오동작 가능성더 어려운 문제는 바로 예측하기 어려운 오동작의 가능성이 있다는 것입니다. 호스트 웹사이트에서 동작하는 자바스크립트에서 에러가 날 경우 플러그인의 동작에도 영향을 미칠 수 있으며, 반대로 플러그인에서 에러가 발생해서 호스트 웹사이트의 코드 실행을 멈출 수 있다는 것입니다. 양방향으로 영향을 미칠 수 있는 것이죠. 특히 후자의 경우는 우리의 실수로 고객사의 서비스에 피해를 끼칠 수 있으니 쉽게 넘길 문제는 아닙니다.아이디어 1: try/catch를 적절히 처리한다?이를 해결하기 위해 가장 쉽게 생각할 수 있는 방법으로는 호스트 웹사이트 쪽에서 try/catch를 적절하게 처리하도록 가이드를 하는 방법입니다. 예를 들어 플러그인 코드의 바깥 쪽에 try/catch처리를 하고 호스트 웹사이트의 자바스크립트에도 적당하게 처리를 하면 되지만 이 방법은 현실적으로 어려움이 있습니다. 우리의 타겟 고객사들은 일반 쇼핑몰들이고 이들은 대부분 개발자가 없거나 쇼핑몰 빌더를 이용해 만들어진 사이트들이기 때문에 개발력이 없는 경우가 많습니다. 또 설사 개발력이 있다 하더라도 플러그인을 붙이기 위해 가이드할 것이 너무 늘어나는 문제가 있죠.아이디어 2: 자바스크립트 실행 순서를 강제한다?생각해볼 수 있는 또 다른 방법은 호스트 웹사이트의 코드와 플러그인 코드의 실행 순서를 명확히 정해서 한 방향의 영향이라도 차단하는 것입니다. 예를 들어 플러그인 코드가 호스트 웹사이트의 코드보다 항상 먼저 실행되도록 고객사에게 가이드한다면 우리의 코드는 항상 문제 없이 실행될 것이고 호스트 웹사이트에서 에러가 발생하더라도 영향을 받지 않을 것입니다. 하지만 이 방법 역시 마음에 들지 않았는데요 양방향의 영향을 모두 차단하지는 못하기 때문입니다. 그리고 더욱 큰 문제는 플러그인은 한 번 실행되고 끝나는 단순한 스크립트가 아니라 계속해서 실행이 되는 애플리케이션이기 때문에 사실상 소용이 없습니다.2. CSS와 관련된 이슈채널 웹 플러그인은 UI도 포함합니다. 플러그인의 DOM이 호스트 웹사이트에 붙어있기 때문에 플러그인의 스타일을 정의하는 CSS도 호스트 웹사이트에 Inject 되어야합니다. 호스트 웹사이트의 CSS와 플러그인의 CSS가 같은 스코프에 존재하기 때문에 우리가 의도한 스타일이 제대로 표현되지 않을 가능성이 있습니다. 실제로 이 문제는 런칭 초기에 우리를 가장 괴롭혔던 문제입니다. 쉽게 생각해볼 수 있는 방법은 아래와 같습니다.플러그인의 CSS에 네임스페이스를 둔다.(플러그인 CSS가 호스트 웹사이트 CSS에 주는 영향을 차단함)CSS 의 우선순위를 이해하고 플러그인 CSS의 우선순위가 항상 높도록 처리한다. (CSS Specificity 링크 참조)하지만 위처럼 처리하더라도 모든 경우에 대해 해결이 되는 것은 아닙니다. 주된 이유는 우리가 개발을 할 때 모든 CSS 속성을 정의하지 않기 때문입니다. 플러그인에서 정의하지 않은 속성을 호스트 웹사이트에서 사용한다면 호스트 웹사이트의 스타일이 적용될 것입니다. 또 특수한 경우이긴 하지만 만약 호스트 웹사이트에 !important 가 적용되어 있다면 그 속성이 덮어씌워지게 됩니다.!important는 사용하지 맙시다..ㅜ아이디어: 스타일 Normalizing?여기에서 의미하는 Normalizing은 모든 DOM 엘리먼트에 가능한 모든 CSS 속성의 기본값을 정의하는 것을 의미합니다. 크로미움을 기준으로 모든 CSS 속성 목록은 이 곳을 참조하시면 됩니다. 이것을 바탕으로 normalize.css를 만들어 적용했습니다.이 방법을 적용한 이후로는 스타일이 오버라이딩되는 문제는 어느 정도 해결되었습니다. 물론 !important에 대한 대응은 여전히 되지 않지만요. 그런데 예상하지 못한 부작용이 발생했는데 첫번째는 디버깅할 때 크롬 인스펙터가 도저히 사용하지 못할 정도로 느리다는 것입니다. 두번째는 CSS가 inheritance 가 안 되고 기본 엘리먼트 셀렉터의 우선순위가 높아서 직접 코딩해야하는 CSS가 2~3배는 길어지는 불편함입니다. 위 두 이유로 개발 피로도가 상당히 높아져서 머지 않아 다른 방법을 알아보게 되었습니다.3. iframe 도입위에 나열한 문제들을 해결할 수 있는 아이디어로 iframe을 리서치하게 되었습니다. 사실 iframe은 최근 웹서비스에서는 잘 사용하지 않기도 하고, 보통은 사용하지 않는 것을 권장하기도 하죠. 따라서 저희 팀에서도 처음에는 고려사항이 아니었는데요 우리와 유사하게 채팅 인터페이스를 제공하는 인터콤에서 iframe 을 적용한 것으로부터 아이디어를 얻어왔습니다.원래 목적에 맞게 사용하지 않으면 독이 됩니다.iframe은 HTML 도큐멘트 안에서 또 다른 도큐멘트를 임베드합니다. iframe 내에 있는 도큐멘트는 호스트 도큐멘트와 자바스크립트 스코프가 분리되어 있고, CSS가 적용되는 스코프 역시 분리되어 있습니다.이런 속성 때문에 위에 나열한 문제들을 원천 차단할 수 있습니다. 자바스크립트 스코프가 분리되어 있기 때문에 글로벌 네임스페이스에 접근해도 호스트 웹사이트에는 전혀 영향이 없고, 자바스크립트의 에러로 인해 다른 쪽 자바스크립트까지 실행을 멈추는 오동작을 막을 수 있습니다. CSS 역시 Normalizing 을 하지 않더라도 호스트 웹사이트와 플러그인은 완벽히 분리가 됩니다.4. iframe 의 단점iframe을 도입하여 1, 2번에 나열한 문제들은 해결했지만 그에 따른 작은 문제들도 발생했습니다. 첫번째는 iframe도입 시 가장 먼저 고민해야할 부분인데 바로 3rd-party cookie 문제입니다. iframe 안에서 로드되는 도큐멘트는 3rd-party 컨텐츠로 인식합니다. IE에서는 기본 설정이 3rd-party cookie 허용을 하지 않기 때문에 쿠키를 사용해서 인증을 구현한 경우 문제가 될 수 있습니다.두번째는 도큐멘트가 분리됨에 따라 발생하는 코딩상의 여러 불편함들입니다. 여기에서는 범위를 벗어나 더 자세하게는 설명하지 않겠지만 도큐멘트가 분리되니 조금 더 신경써야할 것들이 있었습니다.저희 팀의 경우 쿠키 인증 방식이 아닌 토큰 형태의 인증도 지원을 하고 있었기 때문에 첫번째는 크게 문제되지 않았고 두번째 문제도 얻는 이득에 비하면 불편함을 감수하는 편이 훨씬 좋다는 판단이 들어서 도입을 결정했습니다.마무리플러그인 개발을 시작할 당시에 우리 팀은 웹 SDK 형태의 프로젝트 개발 경험이 없었습니다. 리서치를 해도 플러그인 개발과 관련된 아티클이나 리소스 그리고 보일러플레이트 프로젝트도 많지 않았습니다. 프레임워크, 아키텍쳐를 선택하는 것부터 프로젝트를 구성하는 것부터 개발, 배포 및 운영에 이르기까지 일반적인 웹서비스를 개발할 때와 조금 다른 고민들을 해왔던 것 같습니다. 앞으로 저희가 해 온 고민을 공유하려고 합니다. 저희와 같은 플러그인, SDK 형태의 제품을 개발하고 계신 분들에게 도움이 되었으면 좋겠습니다.#조이코퍼레이션 #개발자 #개발팀 #인사이트 #경험공유 #일지
조회수 818

순서대로 척척, ORDER BY

ORDER BY 는 원하는 순서대로 자료를 출력하고 싶을 때 사용합니다. 편의를 위해 이전 글의 예제에서 MBR_NM 의 INDEX 인 IX_MBR_BAS_02 를 제거하고 진행하겠습니다. 이번 글에서는 이해-적용-출력-활용의 순서로 살펴볼게요. 지난 글 보기: 단일 TABLE을 SELECT하자!이해: ORDER BY의 오름차순과 내림차순SELECT     MBR_NM FROM test.TB_MBR_BAS ORDER BY     MBR_NM  ; 기본적인 ORDER BY는 위와 같이 사용합니다. 오름차순과 내림차순으로도 정렬할 수 있습니다. 오름차순일 때는 컬럼 뒤에 옵션을 넣지 않거나 ASC를 사용하고, 반대로 내림차순일 때는 DESC를 사용하면 됩니다.[오름차순]ORDER BY      MBR_NM ORDER BY      MBR_NM ASC [내림차순]ORDER BY      MBR_NM DESC 위의 Query(오름차순) 의 실행계획을 보면 아래와 같이 표시됩니다.결과는 다음과 같습니다. (수행시간 3초)내림차순 Query의 실행 계획을 보면 아래와 같이 표시됩니다.결과는 다음과 같습니다. (수행시간 3초)오름차순과 내림차순 정렬 Query를 보면 실행계획은 같고 결과는 다르게 나타납니다.실행계획을 보면 이렇게 표시됩니다.- table : TB_MBR_BAS - type : ALL - Extra : using filesort Extra의 using filesort는 DBMS에서 정렬을 한다는 의미로 퀵소트 알고리즘을 사용합니다. 실행계획의 내용을 풀어보면 “TB_MBR_BAS 을 전부(ALL) 읽은 후 정렬한다(using filesort)” 정도로 보면 됩니다.적용: INDEX와 정렬의 관계이번에는 삭제했던 MBR_NM의 INDEX인 IX_MBR_BAS_02를 다시 생성하고 수행해보겠습니다.CREATE INDEX IX_MBR_BAS_02 ON test.TB_MBR_BAS (MBR_NM); SELECT     MBR_NM FROM test.TB_MBR_BAS ORDER BY     MBR_NM  ; INDEX를 생성하고 실행계획을 보면 아래와 같이 표시됩니다.실행계획을 보면 몇 가지 달라진 게 눈에 띕니다.1. type : ALL -> index 2. key : 없음 -> IX_MBR_BAS_02 3. Extra : using filesort -> Using index 특히 Extra는 using filesort에서 Using index 로 바뀐 것을 알 수 있습니다. using filesort가 정렬을 한다는 것인데, 정렬을 하지 않고 어떻게 정렬해서 보여준다는 것일까요? INDEX를 이해하면 바로 알 수 있습니다. 일반적인 INDEX는 기본이 BTree INDEX 입니다. MySQL의 BTree INDEX는 오름차순 정렬 상태로 저장되어 있습니다. 이미 정렬한 상태로 저장되어 있는 INDEX를 사용하기 때문에 Query를 수행할 때 다시 정렬할 필요가 없죠. 그래서 using filesort가 나타나지 않는 겁니다.출력: Query 실행다음으로 성이 김 씨인 사람들의 이름을 순서대로 출력해보겠습니다. 여기서는 두 가지 Query를 이용해 비교해보겠습니다.예시 1)SELECT     MBR_NM FROM test.TB_MBR_BAS WHERE MBR_NM LIKE '김%' ORDER BY     MBR_NM  ; 예시 2)SELECT     MBR_NM FROM test.TB_MBR_BAS WHERE SUBSTR(MBR_NM,1,1) = '김' ORDER BY     MBR_NM  ; 예시를 보면 WHERE 절이 다릅니다. 예시1은 “MBR_NM이 ‘김’으로 시작하는 것을 오름차순 정렬해 보여주라는 것”이고, 예시2는 “MBR_NM의 첫 번째 글자가 ‘김’인 것을 오름차순 정렬해 보여주라는 것”입니다.이제 두 개의 Query 실행계획을 비교해보겠습니다.예시 1)예시 2)여기서 주의 깊게 봐야 할 컬럼은 type입니다. 다른 컬럼들은 TB_MBR_BAS의 테이블을 조회하면서 IX_MBR_BAS_02 INDEX만을 사용해 보여주겠다는 내용을 갖고 있습니다. IX_MBR_MAS_02 INDEX가 MBR_NM으로 정렬되어 있기 때문에 using filesort가 나타나지 않은 것입니다. 그렇다면 type에 range와 index는 어떤 차이가 있는 것일까요?range : where 조건에 조회하는 범위가 지정된 경우 나타납니다.예시1은 TB_MBR_BAS를 조회하는데 IX_MBR_BAS_02 INDEX의 MBR_NM에서 ‘김’이 시작되는 위치부터 끝나는 위치까지 조회해 보여주라는 의미입니다. IX_MBR_BAS_02 INDEX를 이용해 ‘김’이 시작되는 위치로 바로 접근할 수 있는 것이 핵심입니다.index : index를 처음부터 끝까지 읽는다는 의미입니다.예시2는 TB_MBR_BAS를 조회하는데 IX_MBR_BAS_02 INDEX를 순서대로 읽어서 MBR_NM의 첫 글자가 ‘김’인 것을 보여주라는 의미입니다.두 개의 차이점을 꼽자면, range는 원하는 범위로 바로 접근해 값을 가져올 수 있는 것이고, index는 처음부터 끝까지 읽어서 그 값이 조건에 맞을 경우 가져오라는 것입니다. 따라서 예시1이 휠씬 성능이 뛰어난 Query라고 볼 수 있습니다. 결과는 모두 아래와 같이 출력됩니다.수행시간은 차이를 보였습니다. 예시1은 0.0041초, 예시2는 0.5초였는데요. 예시에서는 건수가 적기 때문에 큰 차이가 없는 것처럼 보이지만, 자료가 10배 또는 100배 많아진다고 생각해보세요. 엄청난 차이겠죠.활용: Query를 만들고 DISTINCT !마지막으로 Query 하나를 만들어보겠습니다. 1) MBR_NM의 중복을 제거하고2) 김 씨이면서3) 이름이 ‘혜’로 시작하는 사람을 먼저 출력하고4) 이외의 사람은 그 다음부터 오름차순으로 출력하려면 어떻게 만들어야 할까요?중복을 제거할 때는 일반적으로 DISTINCT 와 GROUP BY 두 가지를 사용합니다. 이번 글에서는 DISTINCT를 사용하겠습니다. 다음으로는 오름차순 정렬할 때 김 씨를 먼저 출력하는 것인데 조건문을 사용하여 김 씨인 것과 아닌 것을 구별해 우선순위를 주겠습니다. 다른 것은 위의 Query를 이행하면 됩니다. 먼저 DISTINCT를 넣고 수행해 보겠습니다.SELECT     DISTINCT     MBR_NM FROM test.TB_MBR_BAS ORDER BY     MBR_NM  ; 실행계획은 다음과 같습니다.DISTINCT를 수행하면 Extra가 나타나며 group by로 표시됩니다. 여기서는 IX_MBR_BAS_02를 이용하여 gorup by(중복제거)하여 보여준다는 의미입니다. 수행하면 다음과 같은 값이 나옵니다.다음으로는 MBR_NM이 ‘김혜’로 시작하는 것을 먼저 보여주기 위해 ORDER BY 절에 CASE WHEN문을 사용하겠습니다.SELECT     DISTINCT     MBR_NM FROM test.TB_MBR_BAS ORDER BY     CASE         WHEN MBR_NM LIKE '김혜%'    THEN 0         ELSE 1     END     ,MBR_NM  ; 실행계획은 다음과 같습니다.ORDER BY에 조건이 들어가면서 INDEX의 순서대로 정렬한 것을 그대로 보여줄 수 없기 때문에 Extra에 Using temporary, Using filesort가 나타납니다. Using temporary는 가상 테이블을 만들어 사용하는 것인데, 다시 말해 가상 테이블을 만들어 다시 정렬하는 것입니다. 이에 대한 출력값은 다음과 같습니다.‘김혜’로 시작하는 사람이 먼저 나왔군요.글을 마치며지금까지 ORDER BY와 연관된 조건 처리를 알아봤습니다. 데이터를 더욱 체계적으로 나타내고 싶으신가요? ORDER BY를 이용해서 원하는 목적을 달성해보세요.글한석종 부장 | R&D 데이터팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발자 #개발팀 #인사이트 #경험공유
조회수 2838

Retrofit2 로 전환

Android 와 NetworkAndroid 에서 Network 라이브러리들은 다양하지만 근 1년 사이에 주로 사용되는 라이브러리들이 점차적으로 적어지고 있습니다.오늘은 그 중에서 Retrofit 에 대해 이야기 하고자 합니다.토스랩의 Android Network Library1. Spring-Android, Retrofit 그리고 Retrofit2토스랩은 총 3개의 네트워크 라이브러리를 사용하였습니다. 초창기에는 AndroidAnnotations 에 연동되어 있는 Spring-Android 를 사용하였습니다. 하지만 다중 쓰레드 환경에서 동일한 Request 객체를 사용하면서 저사양 단말에서 문제로 두각되기 시작하였습니다.그래서 Retrofit 으로 2015년 중순쯤 전환을 하였습니다. 그러다 2016년 초 Retrofit2 가 정식 배포가 되면서 자연스럽게 Retrofit2 로의 전환이 대두되기 시작하였습니다. 전환의 이유는 내부의 네트워크 모듈에 대한 Refactoring 이었는데 그와 동시에 Retrofit2 로의 전환도 함께 진행되었습니다.2. 이슈들What the CALL기존의 Retrofit 은 200~399 에러에 대해서는 정상적인 Body 를 반환하고 400 이상의 경우에는 Typed Exception 형태로 로직을 진행하였습니다. 하지만 이정도로는 Response Status 나 Header 정보를 알기에는 추가적인 로직이 필요로 하였습니다. 물론 Success 케이스에도 마찬가지이긴 하였습니다.이는 Retrofit 의 기본적인 목적에 부합되지 않는다는 문제가 있었습니다. Retrofit 의 가장 기본적인 목적은 Okhttp 의 상위 구현체로써 쉽게 Request 와 Response 를 구현한다는 것입니다. 손쉬운 구현이 필요한 정보를 제외시킨다는 것은 별개의 문제이기 때문입니다.그래서 Retrofit2 에서는 Call 객체를 통해서 Request 와 Response 에 적용된 Header, StatusCode, Body 등을 직접 접근 할 수 있도록 인터페이스를 추가하였습니다.이 객체는 불변성을 가지고 있기 때문에 Getter 만이 존재하며 Request 에 필요한 정보는 다른 부분에서 적용되어야 함을 명시하셔야 합니다.Call 객체의 적용Call 객체를 적용하는 과정에서 2가지의 이슈가 있었습니다.Interface 의 모든 Return Value 를 Call 로 전환할 것Request Error 를 직접 핸들링 하도록 수정해야 함이 2가지 때문에 여러가지가 연쇄적으로 수정되어야 했습니다.먼저 수정과정을 설명하기 앞서 Jandi 앱의 Network 통신 전제조건에 대해서 설명해드리도록 하겠습니다.Jandi 앱은 모든 Network 통신은 Current Thread 에서 한다는 것을 전제로 합니다. 이는 MainThread 에서의 통신이 아니라 호출자의 Thread 를 따라간다는 것을 전제로 하고 있습니다. 또한 이를 위해 Reponse 반환, Error Handling, 세션 자동 갱신을 위해 Generic 으로 선언된 Facade 용도의 Wrapper Class 를 별도로 두고 있습니다.따라서 수정해야할 1,2 번을 위해 아래와 같은 수정을 하였습니다.Facade Class 내에서 성공여부를 직접 파악한다.성공시 Return Value 를 직접 반환할 수 있도록 한다.실패시 Status, Response 정보를 이용하여 throw Exception 을 한다. (세션 정보를 갱신 로직은 당연히 포함되어 있습니다.)그래서 아래와 같은 코드 형태가 되었습니다.Response response = apiExecutor.execute(); if (response.isSuccessful()) { RESULT object = response.body(); retryCnt = 0; return object; } else { // 400 이상 오류에 대해 처리 return handleException(apiExecutor, response, null); } Network 통신 과정에서의 Exception 이 나는 경우는 2가지 입니다.기기의 Network 자체가 끊겨 있거나 비정상인 경우Response 의 Parsing 과정에서 오류가 발생한 경우Annotation 의 변화Annotation 의 가장 큰 변화는 DELETE 였습니다. 기존의 Retrofit 에서는 DELETE 요청은 GET 방식으로 가능하였습니다. 즉 POST 처럼 Body 를 설정할 수 없게 되어 있었습니다. 따라서 DELETE 를 쓰기 위해서는 별도의 Custom HTTP Annotation 을 설정 할 적용하여야 했습니다.Retrofit2 에서는 이런 경우에 대비하기 위해 @HTTP 를 개방하였습니다. @HTTP(path = "{url}", method = "DELETE", hasBody = true) 와 같이 사용해야만 Custom HTTP Method 를 적용하실 수 있습니다.Jackson2-Converter 대응Jackson2-Converter 의 이슈는 최근에서야 알게 되었습니다. Jandi 앱은 그동안 Jackson 1.x 를 사용하였고 최근에서야 Jackson2 로 전환을 하였습니다.그 과정에서 Retrofit2 의 converter-jackson 라이브러리를 사용하려 하였으나 중대한 문제가 있었습니다.Retrofit2 에서 Reqeust Body 의 Serialize 는 메소드의 참조변수로 선언된 클래스만 지원하며 상속한 자녀클래스를 넣어도 부모 클래스의 결과만을 리턴 하는것이었습니다. (gson 과 여타 converter 에 대해는 해당 이슈에 대해 파악해보지 않았습니다).이를테면 아래와 같은 경우입니다.interface Api { @PUT("/profile") Call modifyProfile(@Body Profile profile); } public class Profile {} public class NameProfile extends Profile{ String name; } public class PhoneProfile extends Profile{ String phone; } // using case api.modifyProfile(new NameProfile("Steve")); 허나 아래와 같은 상황이 펼쳐집니다.// expect {"name":"Steve"} // actual {} 해당 문제는 Converter-Jackson 의 이슈이기 때문에 위와 같은 상황이 예상된다면 별도의 Converter.Factory 를 선언하여 사용하시기 바랍니다.OkHttpClient 생성 이슈Okhttp 에 여러가지 기능이 추가되었습니다. 그중 잔디가 사용 중인 목록입니다.okhttp-logging-interceptorauthenticatorCutome SSL이런 이유 때문에 OkHttpClient 를 직접 생성하여 사용 하고 있습니다.처음에는 OkHttpClient 를 모든 API 호출시 새로 생성하도록 하였습니다. 헌데 TestCode 가 200회가 넘어가면 File IO 를 너무 많이 사용했다는 오류가 계속적으로 발생하였습니다.이 오류가 단순히 File IO 가 많아서 라는 메세지 때문에 처음에는 Database 에 대한 오류인 줄 알고 Memory Cache 작업과 테스트코드 개선작업을 하였으나 정상 동작이 되지 않았습니다. (테스트 코드 1회에 평균 2번의 API 통신과 2회의 DB 처리를 합니다.)그 와중에 기존의 테스트 코드는 정상 동작하는 것을 보고 Retrofit2 작업을 진행한 branch 만의 문제임을 깨달았습니다.현재는 OkhttpClient.Builder 를 통해 생성한 1개의 OkhttpClient 만을 재사용하도록 변경하였습니다.Network Retry 시 동작 변경Retrofit2 는 Call 객체를 이용하여 동일한 정보로 재요청을 할 수 있도록 지원하고 있습니다. 하지만 이에 대한 제약이 하나 있습니다. 이미 Network IO 가 끝난 경우 Retrofit2 는 Call 객체를 복사하여 재사용할 것을 가이드 하고 있습니다. 그래서 재요청시 다음과 같이 코드를 작성하셔야 합니다.Call call = action0.call(); if (!call.isExecuted()) { return call.execute(); } else { return call.clone().execute(); } OkHttp3 의존성Okhttp 를 사용하는 타 라이브러리가 있다면 Okhttp3 의존성을 가지고 있기 때문에 이에 유념하셔야 합니다.3. 정리Retrofit1 -> Retrofit2 로 변경하는 과정에서 다양한 이슈를 발견하였습니다.Return Value 수정Exception 처리 강화Annotation 수정Request-Response Converter 수정OkhttpClient 재사용 정의재요청 처리에 대한 validation 추가OkHttp3 의존성Retrofit2 로 변경에 있어서 가장 큰 핵심은 Call 이라는 객체라고 할 수 있다는 것입니다.이 객체는 Request 에 대한 동작 제어(cancel, retry 등), Request-Response 의 독립성 보장, 그에 따라 각각의 정보에 대한 접근 등을 보장하게 됩니다.Retrofit2 는 그외에도 Okhttp3 와 다양한 플러그인 지원하고 있습니다. 요청-응답에 필요한 Body 의 변환툴 (Converter-xxx), EndPoint 에서 접근하는 Call 객체에 대한 다양한 툴 (CallAdapter-xxx)현재 Retrofit1 에서 잘 동작하고 있고 의도대로 흐름제어를 하고 있다면 Retrofit2 로 옮겨갈 이유는 없습니다. 하지만 변경을 하고자 한다면 이러한 영향도가 있을 것임을 공유해드렸습니다.참고하면 좋은 Slidehttps://speakerdeck.com/jakewharton/simple-http-with-retrofit-2-droidcon-nyc-2015Jake Wharton’ Retrofit2Presentation 영상#토스랩 #잔디 #JANDI #개발 #개발자 #인사이트 #경험공유
조회수 2947

iOS 아키텍처 패턴(MVC, MVVM, VIPER)

Overview“글 한 번 써보실래요?” 입사하고 일주일이 지나 기술 블로그에 글을 써 보라는 제안을 받았습니다. 여러 고민 끝에, 아이폰 앱(이하 ‘iOS’) 주니어 개발자로서 프로젝트 경험과, 공부한 내용을 바탕으로 글을 쓰기로 했습니다. 적절한 짤이라고 생각하는 중iOS 개발자 사전 준비iOS 개발자의 길에 들어섰다면 이미 앱 개발과 개발 언어에 대해서는 알고 있을 겁니다. 개발 프로그램 Xcode와 프로그램을 지원하는 macOS 환경, 개발 언어 Swift 또는 Objective-C, iOS 앱 프로그래밍 등 iOS 앱을 개발하기 위해 필요한 내용까지도요. 우선 ‘iOS 주니어 개발자라면 꼭 알고 있어야 할 것’들을 아래 목록과 같이 정리했습니다. 글을 읽기 전, 목록 중에서 공부가 더 필요한 것이 있다면 꼭! 검색해보세요. Xcode, macOSApple Developer ProgramSwift or Objective-CCocoa TouchUIKitAuto Layout…iOS Architecture Patterns(아키텍처 패턴)“Viper 패턴 들어보셨어요?” Viper는 단순히 ‘독사’를 의미하는 줄 알았는데, MVC 패턴와 같이 디자인 패턴의 한 종류라는 건 입사하고 나서 알게 됐습니다. MVC와 SingleTon(싱글톤) 패턴은 익숙했지만 Viper 패턴은 생소했습니다. Viper 패턴을 3일 안에 분석하겠다는 저의 부끄러운 과거를 반성합니다... ㅜㅜ검색해보니 다양한 디자인 패턴을 찾을 수 있었습니다. iOS 개발자는 앱 프로젝트를 시작하기 전 또는 이미 진행되고 있는 프로젝트에서 개발을 시작한다면 우선 어떤 패턴으로 설계되어 있는지 파악해야 합니다. 그러므로 오늘은 iOS 개발에 주로 사용되는 패턴인 MVC, MVVM, VIPER를 간단하게 살펴보겠습니다.MVCMVC 패턴Model(모델), View(뷰), Controller(컨트롤러). Model에서는 애플리케이션에서 사용할 데이터들을 관리하고, View는 유저 인터페이스를 표현 및 관리합니다. Controller는 View와 Model의 다리 역할을 해 View의 입력을 Model이 반영하고, Model의 변화를 View에 갱신하는 역할을 합니다. 하지만, 애플의 MVC 패턴은 기존 MVC 패턴과 다릅니다. View와 Controller가 강하게 연결되어 있어 View Controller가 거의 모든 일을 합니다.1) 애플 MVC 패턴MVVMMVVM 패턴Model(모델), View(뷰), ViewModel(뷰모델). Controller를 빼고 ViewModel을 추가한 패턴입니다. 여기서 View Controller가 View가 되고, ViewModel이 중간 역할을 합니다. View와 ViewModel 사이에 Binding(바인딩-연결고리)가 있습니다. ViewModel은 Model에 변화를 주고, ViewModel을 업데이트하는데 이 바인딩으로 인해 View도 업데이트됩니다. ViewModel은 View에 대해 아무것도 모르기 때문에 테스트가 쉽고 바인딩으로 인해 코드 양이 많이 줄어듭니다.import Foundation // ViewModel var gameScore: Int? var gameScoreLabel: UILabel func updateGameScoreLabel() {   var text = ""   if let gameScore = gameScore, gameScore == 100 {       text = "Excellent!!"   } else if let gameScore = gameScore, gameScore >= 90 && gameScore < 100>       text = "Great Job!"   } else if let gameScore = gameScore, gameScore < 90>       text = "Not Bad~"   }   gameScoreLabel.text = text } // View Controller gameScoreLabel.text = viewModel.updateGameScoreLabel간단한 예를 들면, 게임 점수에 따라서 textView에 보여줄 내용을 담당하는 함수 등, View에서 변화가 일어나는 함수들이 View Controller에 정의되어 사용하는 경우가 많을 겁니다. 이런 함수들이 점점 많아지면 View Controller가 Massive, 많은 코드를 담게 됩니다. 그래서 이런 함수들을 ViewModel에 옮기고, 값들을 미리 세팅한 다음에 view controller에서 viewModel을 선언하고 viewModel의 함수를 불러오는 식으로 사용하면 됩니다. 매우 간단한 예제이기 때문에 대략 viewModel과 view controller에서 어떻게 사용하는지만 보시면 될 것 같습니다. 이 패턴은 주로 Reactive programming(ReactiveCocoa, RxSwift 등)을 할 때 많이 사용하는 패턴이어서 다음에 설명하겠습니다.VIPERVIPER 패턴View(뷰), Interactor(인터렉터), Presenter(프리젠터), Entities(엔티티), Router(라우터). MV(X) 패턴과 다른 패턴으로 MVC 패턴을 대체하기 위해 만들어진 패턴입니다. 먼저 Entity는 그저 모델 객체입니다. 단순하게 어떤 모델의 속성들만 있는, Dumb Model이라고 부를 수 있습니다. 이 모델 객체를 조작하는 것이 바로 Interactor입니다. 어떤 행동(behavior or use case)에 따라서 모델 객체를 조작하는 로직이 담겨 있습니다. 작업이 완료되어도 View에 아무런 영향 없이 오로지 데이터 작업만 합니다.Presenter는 데이터를 Interactor에서 가져오고, 언제 View에 보여줄지 결정합니다. View에 보여주기 전 내용을 준비하는 로직을 담당한다고 생각하면 됩니다. View는 Presenter에서 어떻게 보여줘야 할지 요청대로 디스플레이하고, 사용자의 입력을 받으면 다시 Presenter로 넘깁니다. Presenter는 View/ViewController, Interactor, Router와 상호작용합니다. Interactor로부터 조작된 데이터를 가져오고, 디스플레이하기 위해 데이터들을 준비한 다음 View/ViewController에 보냅니다.Router 또는 Wireframe은 화면 전환(navigation information)을 담당합니다. Presenter가 “언제” 화면을 전환해야 하는지 안다면, Router는 화면 전환을 “어떻게” 하는지 알고 있습니다. Router는 화면 전환 애니메이션을 구현하고, View Controller를 생성하여 Presenter와 연결합니다.항목내용ViewPresenter의 요청대로 디스플레이하고, 사용자 입력을 Presenter로 보내는 작업을 합니다.InteractorUse case에 따라서 Entity 모델 객체를 조작하는 로직을 담고 있습니다.PresenterInteractor로부터 데이터를 가져오고, View로 보내기 위해 데이터를 준비하여 “언제” View에 보여줄지를 결정합니다.Entity모델 객체. Dumb Model.Router(Wireframe)화면 전환(navigation information)을 담당하며, Presenter가 “언제” 화면 전환해야하는지를 안다면, Wireframe은 화면 전환을 “어떻게” 하는지를 알고 있습니다.하...지금까지 설명한 내용들은 막상 프로젝트 만들어 소스를 작성하려고 하면 막막해집니다. 역할이 잘 분할되어 있기에 앱의 기능을 하나 정하여 interactor, entity, presenter, view, router 만들고, 또 앱의 기능에 따라서 다시 interactor, entity,…. 고민을 많이 해야 해서 다시 MVC 패턴으로 돌아가고 싶은 마음이 생깁니다.크게 보면 Add Module와 List Module, 그리고 공통적인 모델(데이터)을 잘 분리한 앱 구조Conclusion도대체 우리는 왜 다양한 앱 디자인 패턴을 알아야 할까요? 그 이유는 바로 앱의 특성에 따라 적합한 설계를 가지고 작업해야 하기 때문입니다. 간단한 앱 프로젝트는 쉽게 개발하고 적용할 수 있는 MVC 패턴이 더 적합합니다. 반대로 MVVM 패턴이나 VIPER 패턴을 적용하면 점점 커지는 앱 프로젝트에 잘 대응할 수 있습니다. 또는 어떤 디자인 패턴이 적용된 앱 프로젝트에 참여하면, 그 디자인 패턴에 대해 알아야 앱 구조를 이해하고 기능을 추가하거나 수정할 수 있고, 작업하는 시간을 줄일 수 있을 겁니다.가장 좋은 패턴은 사람마다 차이가 있습니다. 패턴마다 장단점도 있습니다. 다만 어떤 패턴이든지 간에 구조화되고 정리된 코드는 쉽고, 직관적입니다. 이 글 하나만으로 앱 패턴을 완벽하게 마스터할 수는 없어도 패턴의 종류와 특징을 알게 되었다면 본전입니다. 다음 편도 기대해주세요! :-) 도움말 1) View Controller에서는 Controller가 View의 life cycle(라이프 사이클)에 관여하기 때문에 View와 Controller를 분리하기 어렵습니다. 개발자들 사이에서는 Massive View Controllers라고도 불립니다. 앱을 테스트할 때, Model은 따로 분리되어 테스트를 할 수 있어도 View와 Controller는 강하게 연결되어 있기 때문에 각각 테스트하기 어렵습니다. 참고문헌 iOS Architecture Patterns: Demystifying MVC, MVP, MVVM and VIPER글김주희 사원 | R&D 개발1팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유 #iOS
조회수 1641

[Tech Blog] PhantomJS를 Headless Chrome(Puppeteer)로 전환하며

버즈빌에서는 모바일 잠금화면에 내보내기 위한 광고 및 컨텐츠 이미지를 생성하기 위한 PhantomJS 렌더링 서버를 다수 운영하고 있습니다. 일반적으로 PhantomJS는 웹페이지 캡쳐에 많이 쓰이지만, 기본적으로 headless하게 웹페이지를 렌더링하고 캡쳐할 수 있다는 특성 때문에 동적인 이미지 생성에도 많이 활용됩니다. 버즈빌의 렌더링 서버는 200개 이상의 컨텐츠 프로바이더로부터 실시간으로 잠금화면 컨텐츠 이미지를 생성하고 있어 분당 수백 건의 이미지를 안정적으로 생성하는 것이 가능해야 합니다.  렌더링 서버의 스케일링 이슈를 해결하기 위해 버즈빌에서는 여러 대의 렌더링 서버를 둬서 횡적으로 확장을 함과 동시에, 개별 서버 내에서도 리소스 사용률을 높이기 위해 Ghost Town이라는 라이브러리를 작성해 PhantomJS 프로세스 풀을 구성하여 사용하고 있었습니다(Scaling PhantomJS With Ghost Town ) 한편, 시간이 지나면서 잠금화면에서 렌더링하는 이미지 템플릿의 종류가 다양해지고, emoji 및 여러 특수문자를 표현하기 위해 렌더링 서버에 여러 폰트(대표적으로 Noto Sans CJK)를 설치해야 하는 요구사항이 추가됐는데, PhantomJS에서 폰트 렌더링이 일관적이지 않은 문제가 발생했습니다. 동일한 템플릿이지만 폰트가 비일관적으로 렌더링되고 있는 모습 이 문제의 정확한 원인은 결국 찾지 못했지만 PhantomJS의 이슈였거나 시스템 상에 폰트가 시간이 지나면서 추가 설치됨에 따라 font cache가 서버마다 일관되지 않은 상태가 되었기 때문인 것으로 짐작하고 있습니다. 다른 워크로드와 마찬가지로 렌더링 서버도 최초에는 packer를 이용해 일관되게 이미지를 빌드하고 업데이트하려고 했지만, 자주 기능이 추가되거나 배포되는 서비스가 아니기에 서버를 오래 띄워놓고 수동으로 유지보수를 한 케이스들이 누적되어 더 이상 packer를 이용해 시스템이나 폰트를 최신 상태로 유지하는 것이 어려운 상태였습니다. 모든 눈꽃송이가 자세히 보면 조금씩 다르게 생겼다는 것에서 비롯된 snowflake, 즉 배포된 서버들이 시간이 지남에 따라 조금씩 다른 상태가 된 것입니다. 평소에는 문제가 없어 보이지만, 추가적인 확장성이 필요해 scale out을 하거나 새로운 템플릿을 개발해 배포를 하면 문제가 발생하는 상황이었습니다. 사실 더 큰 문제는 PhantomJS 프로젝트가 더 이상 관리되지 않는다는 점이었습니다. 2017년 Google Chrome 59버전부터 Headless Chrome이 내장되기 시작하였고, 곧바로 Node API인 puppeteer가 릴리즈 되어, 현시점에서 가장 많이 쓰이는 렌더링 엔진을 손쉽게 headless로 사용할 수 있는 환경이 되었습니다. 때문에 PhantomJS 관리자가 사실상의 중단을 선언하였고, 2018년에는 최초 개발자에 의해 프로젝트가 아카이브 되었습니다. 프로젝트가 업데이트되지 않는 것은 템플릿에 최신 CSS 스펙을 사용하지 못한다는 것을 의미하고, 버그 수정도 되지 않기에 어플리케이션의 유지보수가 굉장히 어려워짐을 의미합니다. 현재까지의 문제점을 정리하면 아래와 같습니다.  자주 배포되지 않는 서비스 특성으로 인한 서버들이 snowflake화 되는 현상(특히 폰트) PhantomJS의 개발 중단으로 인해 버그 픽스 및 최신 CSS 속성 사용이 어렵게 되고, 향후 유지보수나 새로운 템플릿 개발이 어려워짐  해결방안은 명확했습니다. 첫번째 문제를 해결하기 위해서는 어플리케이션과 폰트가 설치된 시스템을 통째로 컨테이너로 만들고, CI/CD 파이프라인을 통해 지속적으로 빌드하여 snowflake화 되지 않도록 하면 됩니다. 사실 최초에 packer를 이용해 AMI 이미지를 생성하도록 구성이 되어있었기에, 매 배포마다 AMI를 새로 생성하고 지속적으로 렌더링 서버를 배포하는 환경이기만 했으면 snowflake를 방지할 수 있었을 것입니다. 하지만 자주 기능이 추가되거나 배포되는 서비스가 아닌데다, AMI를 빌드하는 과정이 CI/CD에 통합돼 있지 않고 어플리케이션만 지속적으로 배포하는 환경이었기에 편의상 서버를 종료하지 않고 장기간 관리를 해 오게 되었고, packer로 새로운 AMI 이미지를 빌드하는 것이 어려워 졌습니다. 때문에 AMI 빌드를 통한 배포 대신, 이미 운영 중인 kubernetes 클러스터에 도커 컨테이너를 빌드해 immutable한 형상으로 배포하기로 결정하였습니다. 두번째 문제의 간단한 해결책은 PhantomJS를 puppeteer로 변경하는 것입니다. 이 부분은 생각보다 간단했습니다. 의도했는지는 알 수 없으나 puppeteer의 api는 PhantomJS와 꽤나 비슷합니다. drop-in replacement까진 아니지만, PhantomJS api 호출하는 부분만 살짝 바꿔주는 정도로 교체가 가능하였습니다. 물론 교체만 하였다고 해서 기존에 개발된 템플릿이 의도된 대로 출력되는 것을 보장하지는 않기에, 렌더링 서버가 렌더링하는 수많은 템플릿들을 PhantomJS와 puppeteer로 각각 출력하여 일일히 비교하는 작업이 필요했습니다. 어떤 템플릿이 어떤 인자를 필요로하며 의도된 출력 결과가 무엇인지에 대한 정의가 남아있지 않았기에 템플릿마다 샘플 케이스들을 생성하는 작업이 필요했습니다. 아직까지는 수동으로 결과를 비교해야하는 문제점이 있지만 적어도 직접 확인할 수 있는 것은 큰 도움이 되었습니다. 향후에는 자동화된 테스트 케이스를 구성하여 기능 개발이 좀 더 용이하도록 보완할 계획입니다. 결과는 만족스러웠습니다. 많은 경우 기존과 출력 결과가 달랐지만, 최신의 크롬 웹킷이 사용되면서 오히려 템플릿을 개발할 때 의도했던대로 CSS를 더 정확하게 렌더링하게 된 것이었습니다.  FROM node:10-slim RUN apt-get update && \ apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \ libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \ libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \ libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \ ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget unzip && \ wget https://github.com/Yelp/dumb-init/releases/download/v1.2.1/dumb-init_1.2.1_amd64.deb && \ dpkg -i dumb-init_*.deb && rm -f dumb-init_*.deb && \ apt-get clean && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* RUN yarn global add [email protected] && yarn cache clean ENV NODE_PATH="/usr/local/share/.config/yarn/global/node_modules:${NODE_PATH}" RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser # Set language to UTF8 ENV LANG="C.UTF-8" RUN wget -P ~/fonttmp \ https://noto-website-2.storage.googleapis.com/pkgs/NotoSans-unhinted.zip \ https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip \ https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKkr-hinted.zip \ https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKtc-hinted.zip \ https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKsc-hinted.zip \ https://noto-website-2.storage.googleapis.com/pkgs/NotoColorEmoji-unhinted.zip \ && cd ~/fonttmp \ && unzip -o '*.zip' \ && mv *.*tf /usr/share/fonts \ && cd ~/ \ && rm -rf ~/fonttmp WORKDIR /app # Add user so we don't need --no-sandbox. RUN mkdir /screenshots && \ mkdir -p /home/pptruser/Downloads && \ mkdir -p /app/node_modules && \ chown -R pptruser:pptruser /home/pptruser && \ chown -R pptruser:pptruser /usr/local/share/.config/yarn/global/node_modules && \ chown -R pptruser:pptruser /screenshots && \ chown -R pptruser:pptruser /usr/share/fonts && \ chown -R pptruser:pptruser /app # Run everything after as non-privileged user. USER pptruser RUN fc-cache -f -v COPY --chown=pptruser:pptruser package*.json /app/ RUN npm install && \ npm cache clean --force COPY --chown=pptruser:pptruser . /app/ ENTRYPOINT ["dumb-init", "--"] CMD ["npm", "start"]  puppeteer를 사용하면서 약간의 권한 문제가 있어서 결과적으로 위와 같은 Dockerfile을 작성하게 되었는데, puppeteer 도커 이미지 작성에 관한 최신 정보는 여기서 확인할 수 있습니다. 컨테이너 오케스트레이션(K8s)을 사용하면 process 기반의 스케일링은 컨테이너를 여러대 띄워 로드밸런싱을 손쉽게 할 수 있지만, 개별 컨테이너의 throughput을 향상시키기 위해 기존에 Ghost town을 작성해 PhantomJS 프로세스 풀을 만든 것처럼 크롬 프로세스 풀을 구성하기로 하였습니다. 프로세스 풀 구성에는 generic-pool 라이브러리를 사용하였으며 아래처럼 구성하였습니다.  const puppeteer = require("puppeteer"); const genericPool = require("generic-pool"); const puppeteerArgs = ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"]; const createPuppeteerPool = ({ max = 5, min = 2, maxUses = 50, initialUseCountRand = 5, testOnBorrow = true, validator = () => Promise.resolve(true), idleTimeoutMillis = 30000, ...otherConfig } = {}) => { const factory = { create: async () => { const browser = await puppeteer.launch({ headless: true, args: puppeteerArgs }); browser.useCount = parseInt(Math.random() * initialUseCountRand); return browser; }, destroy: (browser) => { browser.close(); }, validate: (browser) => { return validator(browser) .then(valid => Promise.resolve(valid && (maxUses <= 0 || browser.useCount < maxUses xss=removed xss=removed xss=removed> genericAcquire().then(browser => { browser.useCount += 1; return browser; }); pool.use = (fn) => { let resource; return pool.acquire() .then(r => { resource = r; return resource; }) .then(fn) .then((result) => { pool.release(resource); return result; }, (err) => { pool.release(resource); throw err; }); }; return pool; }; module.exports = createPuppeteerPool;  Caveats PhantomJS에서 puppeteer로 전환함에 있어서 몇가지 주의해야 할 점이 있었는데요. 첫째는 기존에 사용하던 템플릿의 html에 이미지 소스를 file:// url 프로토콜을 이용해 로드하는 경우가 있었는데, PhantomJS에서는 정상적으로 로드가 되지만 Headless Chrome에서는 보안 정책으로 인해 로컬 파일을 로드할 수 없었습니다(관련 이슈). 때문에 로컬 이미지가 필요한 템플릿은 Express 서버에서 static file serving을 하도록 하고 http:// 프로토콜로 변경하였습니다. 다음으로 발생한 문제는 PhantomJS을 이용한 기존 구현에서는 jade template을 compile한 후 page 객체의 setContent 메소드를 이용해 html을 로드하였는데, puppeteer에서는 page#setContent API 호출 시 외부 이미지가 로드될 때까지 기다리지 않는다는 점입니다. puppeteer 에 올라온 관련 이슈에서는 `=setContent`= 대신 아래와 같이 html content를 data URI로 표현하고 page#goto의 인자로 넘기면서 waitUntil 옵션을 주는 방식을 해결방법으로 권하고 있습니다.  await page.goto(`data:text/html,${html}`, { waitUntil: 'networkidle0' });  이 때 주의해야 할 점은 waitUntil의 옵션으로 networkidle0이나 networkidle2 등을 사용하면 외부 이미지가 충분히 로드될 때 까지 기다리는 것은 맞지만, 500ms 이내에 추가적인 네트워크 커넥션이 발생하지 않을 때까지 기다리는 옵션이기 때문에 외부 이미지가 로드되더라도 추가적으로 500ms를 기다리게 됩니다. 때문에 SPA 웹페이지를 캡쳐하는 경우가 아니라 정적인 html을 로드하는 경우라면 `load` 이벤트로 지정하면 됩니다. 이외에도 향후에 프로젝트의 유지관리나 운영 중인 서비스의 모니터링을 위해 Metrics API 엔드포인트를 만들어 prometheus에서 메트릭을 수집할 수 있도록 하고 grafana 대시보드를 구성하였습니다. 이 대시보드는 어떤 템플릿이 실제로 사용되고 있는지, 템플릿 렌더링에 시간이 얼마나 소요되는지 등을 모니터링할 수 있도록 구성하여 사용되지 않고 있는 템플릿을 판단하거나 서비스 지표를 모니터링 하는 데 이용하고 있습니다. grafana와 prometheus를 이용해 구현한 렌더링 서버 모니터링 대시보드. 마치며 최근에 들어서는 PhantomJS를 사용하던 많은 곳에서 puppeteer로의 전환을 해오고 있어 본 포스팅에서 다루고 있는 내용이 크게 새로운 내용은 아닐 수 있습니다. 하지만 버즈빌에서는 렌더링 서버가 과거에 이미 PhantomJS를 사용하는 것을 전제로 상당한 최적화가 진행되어 왔고, 꽤나 높은 동시 처리량이 요구되는 상황에서 puppeteer로 교체를 해버리기에는 여러 불확실한 요소들이 존재하는 상황이었습니다. 버즈빌의 핵심 비즈니스 중 하나인 잠금화면에 사용되는 이미지를 렌더링하는 서비스가 레거시(개발이 중단된 PhantomJS)에 의존하는 코드베이스 때문에 변경이 어려워지는 것은 향후 꽤나 큰 기술부채로 작용할 것이라 판단하였습니다. 이번 마이그레이션을 진행하면서는 이 부분을 염두에 두고 컨테이너를 사용해 CI/CD 파이프라인을 구축해 지속적으로 컨테이너 기반의 이미지를 생성하도록 변경하였고, 그 결과는 꽤나 만족스러웠습니다. 마이그레이션 이후 그간 밀려 있던 신규 템플릿 개발이나 신규 컨텐츠 프로바이더를 추가하는 과정이 수월해졌기 때문입니다. 빠르게 변화하는 비즈니스 요구사항에 대응하다보면 기술부채는 필연적으로 쌓일 수밖에 없습니다. 개발자에게는 당연히 눈에 보이는 모든 기술부채들을 청산하고 싶은 욕구가 있지만 늘 빚 갚는데 시간을 쓰고 있을 수만은 없는 노릇입니다. 리소스에는 한계가 있으니까요. 어떤 기술부채를 지금 당장 해결해야하는지 의사결정을 하는데 있어 고민이 된다면 일단 “측정”을 해보는 것을 권장합니다. 수치화된 지표가 있다면 당장 의사결정권자나 팀을 설득하는 데 사용할 수도 있지만, 서비스의 핵심 지표들을 하나 둘씩 모니터링 해나가다 보면 서비스에 대한 가시성이 높아지고 미래에 정말로 병목이 되는 지점을 찾아내기 쉬워질 것입니다. 참고 자료  https://docs.browserless.io/blog/2018/06/04/puppeteer-best-practices.html https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md Icons made by Freepik from Flaticon is licensed by Creative Commons BY 3.0    *버즈빌에서 개발자를 채용 중입니다. (전문연구요원 포함)작가소개 Liam Hwang, Software Engineer 버즈빌에서 DevOps를 담당하고 있습니다. Cloud Native 인프라를 구현하기 위해 여러 노력을 기울이고 있으며 새로운 기술들을 공부하는 것을 좋아합니다.
조회수 1057

블록체인 진짜 하나도 모르는 디자이너의 독학일기(2)

1편에 이어 2편을 작성하기까지 참으로 많은 시간이 걸렸답니다. 물론 내용이 어려워서 이해하는 데 시간이 걸린 것도 있고... 어려운 만큼 귀차니즘이 강해져서 미루고 미룬 이유도 있지요.1편에선 블록체인이 왜 발생했는가! 에 대해서 말했어용. 혹시라도 못 보신 분들은 링크를 타고 슝 한 번 더 보고 와주시면 좋을 것 같습니다.https://brunch.co.kr/@roysday/199짧게 줄이자면, 결국 신뢰의 문제 때문이예요. 내가 널 뭘 믿고??? 라는 명제죠. 단순히 너와 나의 사이뿐만 아니라 정부나 기업 등이 해커나 서버폭발 등으로 탈탈 털리는 일을 보면서 우린 두려워진 거예요. 은행을 믿을 수 있어?? 보험사를 믿을 수 있어?? 국민연금 겁나 떼가는데 나중에 받을 수는 있는거야?? 등등...그래서 우린 누구도 깰 수 없고 변하지 않고 삭제도 되지 않는 강력한 '장부'를 만들고 싶었던 거예요. 그래서 생각해낸 가장 좋은 방법이 바로 다수에게 뿌리는 거였죠. 하지만 우린 이런 궁금증이 생겨요. 다수라구??...누가 참여하는데?? 내 컴퓨터엔 블록체인 같은 게 없는데??사실 이 부분을 이해하기가 진짜 어려웠어요. 아니 페이스북에 투표참여나 주식시장같이 '내가 이걸 산다! 투표한다! 동의한다! 클릭~!' 이런 식의 동작이 없잖아요. 그런데 어떻게 내가 동의를 했는지 안했는지 내 장부에 뭐가 언제 어떻게 기록된다는 거야??....는 궁금증이 생기는 거죠.그래서 오늘은 이 과정을 쉽게 정리해보려고 해용 :) 혹시 틀린 부분이 있다면 꼭!! 댓글로 남겨주세요!!1. 컴퓨터에게 말을 걸어보자.지금 컴퓨터를 켜고 이렇게 외쳐보세요. "윙가르디움 레비오싸."네, 아무일도 일어나지 않았어요. 혹시 무슨 일이 일어나셨다면 소름이네요. 컴퓨터는 마법주문이나 우리의 감정이나 목소리나 표정을 인식하지 못해요.(물론 요즘엔 이걸 가능하게 만들고 있어요. 놀라워요. 하지마 마법주문은 좀 시간이 걸릴 것 같아요.) 일본은 일본어를 쓰고 중국은 중국어를 쓰고 스페인은 스페인어를 써요. 컴퓨터는 2진법을 써요. 얘네들은 0 아니면 1이라는 원시적인 언어를 쓰고 있어요. 물론 인간도 아주아주 오래전엔 2진법으로 언어를 말했어요. 쿼스랜드는 원시인들은 'a(아)'와 'o(오)' 만을 사용해서 숫자를 표현했다고 해요. 아, 오, 아오아, 오아오아..등으로 말이죠. 컴퓨터는 이처럼 0와 1로 이루어진 신호들을 통해 소통해요. 그러니 우리가 컴퓨터에게 말을 걸고싶다면 2진법으로 0과 1을 마구마구 적어줘야 해요.2. 컴퓨터의 언어를 만들었졍.근데 0과 1로만 말을 걸다보니 도대체 눈이 아프고 헷갈려서 너무 어려운 거예요. 그래서 규칙을 만들었어요.A = 100 0001B = 100 0010C = 100 0011D = 100 0100...이런식으로 알파벳이나 기호, 한글 등등을 컴퓨터가 이해할 수 있는 신호와 대응시켰어요. 그래서 나온 게 컴퓨터 언어죠. 오늘 날 코딩이라고 불리는 그것들은 결국 컴퓨터의 말로 이렇게해라 저렇게 해라 명령을 내리는 거예요. 컴퓨터는 그 명령에 의해 이런저런 일들을 처리해요. 이걸 누르면 = 저 페이지로 넘어가게 해.이곳을 채우면 = 다음 칸을 적을 수 있게 해.여길 클릭하면 = 파란색으로 바뀌게 만들어줘.등등 뭔갈 하면 = 결과가 등장하는 거죠. 신기하죠? 네 저도 신기해요. 이렇게 명령어를 입력하면 결과가 짜짠.3. 규칙을 만들 수 있게 되었엉.컴퓨터는 논리에 의해서 움직여요. 뭔가를 누르면 - 계산하고 - 0이면 안하고, 1이면 해요. 사실 되게 단순하게도 '한다/안한다' 로 명확하게 움직여요. 이렇게 명확하기 때문에 사람의 목숨을 담보로 하는 수많은 것들을 만드는 거예요. 비행기도 그렇고, 인공위성, 놀이기구, 자동차 등등... 컴퓨터가 기분따라 오늘은 왠지 일하기 싫어서 땡깡이나 부려버리면 그냥 다 죽는 거잖아요. (물론 가끔 파랗게 질려서 멍청댕청해질 때가 있긴 하지만...)결정장애가 없는 특성 때문에 컴퓨터는 한 번 규칙을 정해주면 그렇게 계속 움직여요. 이런 점에서 보면 인간과 컴퓨터의 가장 큰 특징 중 하나가 '갈등' 이 아닐까 싶어요. 결정장애가 있으신 분들은 엄청 인간적인 매력을 지니신 거예요. 블록체인은 '규칙'이예요. 변하지 않고 계속 그대로 움직이는 규칙이죠.규칙을 컴퓨터에게 명령하는 거예요. 이렇게 하면 이렇게 처리해!~ 알았지? 하고 명령하는 거죠. 이 코드(=명령어)를 누가 짜요? 그렇죠 그걸 블록체인 회사에 있는 개발자님들이 만드는 거예요. 그러니 어떤 블록체인 코드가 만들어지면 처음엔 그 회사 컴퓨터에만 있을 거예요. 4. 사람들을 모아보쟈.명령어를 만들긴 만들었는데, 여튼 이제 돈을 벌어야 하잖아요. 회사니까. 많은 사람들이 우리가 만든 블록체인을 이용해줬으면 좋겠어요. 그래서 사람들을 모아야겠단 생각을 했어요. 사람들에게 막 알리기 시작했어요.블록체인은 다수의 사람들이 이용해야 의미가 있어요. 꼴랑 2명만 쓰고있으면 그 중 한명의 컴터만 털어버려도 장부를 조작할 수 있잖아요. 하지만 수백, 수천만명이 블록체인에 참여하고 있다면 얘기가 달라지죠. 그 많은 사람들의 컴터를 한꺼번에 해킹할 순 없으니까요. 그래서 사람이 많으면 많을수록 블록체인은 튼튼해져요.5. 블록을 만들면 보상을 줄께!가장 단순하고 간단한 방법은 누군가가 블록을 만들도록 하는 거예요. 블록체인은 블록이 우르르르 붙어있다는 소린데, 그 블록이란 건 사실 눈에 보이는 택배박스가 아니라 손으로 적는 기록과 같아요. 롤링페이퍼 아시죠? 딱 그런 느낌인거예요. 돌아가면서 나의 기록을 블록으로 만들어서 열차놀이를 하는거죠. 그리고 블록을 만들면 그에 대한 보상으로 무언갈 주는 거예요! 대부분 그 보상이 바로 암호화폐와 같은 것들이예요. 우린 이걸 '채굴한다.' 라고들 하죠. 열심히 노동했으니 보상을 주는 거예요.6. 블록을 어떻게 만들어? 채굴!그럼 어떻게 블록을 만들까용. 음 생각해봐요. 누구나 그냥 노트북만 있어도 블록을 만들 수 있다면 물론 순식간에 블록들이 엄청나게 만들어져서 온세상 온누리에 우리 블록체인이 아름답게 꽃피긴 하겠지만....'보상'을 줘야하는 걸 생각해보면 소름이 돋을 거에요. 더군다나 화폐의 가치가 있는 것을 만드는 데 아무나 10초만에 만들 수 있다고 하면 이건 복사기에 지폐를 위조해서 그냥 마구 쓸 수 있는 것과 비슷해요. 그래서 블록을 만드는 과정은 어려워야 해요. 개발자들은 그래서 사람들이 엄청 고민을 해야만 풀 수 있는 문제를 명령어로 만들었어요. 그리고 그걸 풀면 블록이 완성되고 보상을 받는 거예요. 물론 종이와 펜으로 푸는 건 아니예요. 인터넷에 떠돌아다니는 '이거 풀면 아이큐150 이상임' 이런 문제와 비슷하긴 하지만....이건 사람이 직접 푸는게 아니라 컴퓨터가 푸는 거에요. 예전에 막 그래픽카드가 없어서 난리가 났다..PC방에서 그래픽카드만 훔쳐갔다더라..이런 뉴스가 한참 떴었잖아요. 맞아요. 마치 영화에나 나올법한 슈퍼컴퓨터같이 엄청나게 엄청난 컴퓨터들을 잔뜩 가져다놓고 계산을 시키는 거예요. 사람은 그냥 엔터만 누르고 가만히 있으면 돼요. 고생은 컴퓨터가 하니까요. 컴퓨터는 미친듯이 계산을 해요. 모터가 탈 정도로 고생을 하죠. 그리고 마침내 문제가 풀리면 짜잔!!! 블록이 완성되었어요!! 물론 블록이 완성이 되었는 지 어쩐지는 눈으로 보지 못해요. 하지만 문제가 풀면 블록이 생기도록 명령어를 짜놓았으니 생겼을 거예요. 컴터는 명확하니까요.(항상 이걸 전제로 해요.) 그리고 약속된 보상이 생겨요. 나에게 암호화폐가 뾱! 생겼어요. 빗썸이나 코인원같은 거래소에서 현금으로 바꿀 수 있도 있어요. 7. 쉬운 방법도 있어요.이렇게 수십대의 컴퓨터와 첨단 장비들이 있어야만 블록을 만들 수 있는 건 아니예요. 일반인들도 블록을 만들 수 있어요. 다만 쉬운 만큼 보상이 굉장히 작겠죠. 단순한 예로 '스팀잇'을 들 수 있어요. 스팀잇은 겉보기엔 브런치같이 그냥 주절주절 글이나 쓰는 플랫폼처럼 보이지만...사실 그건 훼이크예요. 스팀잇에 글을 쓰는 것 자체가 사실 블록을 만드는 것과 같아요. 그래서 그 보상으로 스팀을 주는 거예요. 그래서 정확히 얘기하면 '글을 쓰니 돈을 주더라!!' 가 아니라..'블록을 만드니 보상을 준다!' 가 맞는 거예요. 블록을 만드는 방식이 '콘텐츠' 일 뿐이죠.이처럼 블록을 만드는 방식은 결국 개발사가 정하기 나름이예요. 여행사진을 500장 올릴 때마다 블록을 생성하자! 라고 규칙을 만들면 그렇게 만들어져요. 그리고 보상을 받는거구요. 기부를 하면 블록이 만들어지게 하자! 라고 할 수도 있고하루에 1km씩 뛰어다니면 블록이 만들어지게 하자! 라고 할 수도 있어요.심지어 성인사이트에서 결제를 하면 블록이 만들어지게 할 수도 있어요. 실제로도 있더라구요.규칙은 만들면 되니까요. 그래서 다양한 프로젝트들이 만들어지고 블록체인 회사들이 각자 자신만의 방법으로 사람들을 모으고 있죠. 8. 하지만 사람들은 그 사실을 잘 몰라요.스팀잇에 접속해보신 분이 계신가요?? 사실 그곳은 능력자들 천지라서 다들 블록체인을 어느정도 알고 있는 사람들이 많지만.. 또 많은 사람들은 그런거에 상관없이 그냥 돈 준다니까 가입해서 글을 쓰고 있기도 해요. 사람들은 이게 블록인지 뭔지도 몰라요. 그냥 보상준다니까 열심히 뭘 쓰고 있는거에요.내가 블록을 만드는 걸 눈으로 볼 수도 없고 손에 잡히지도 않아요. 이 모든 건 그냥 컴퓨터가 처리하고 인터넷상에 떠돌아다니는 전기신호로만 존재할 뿐이예요. 우리는 겉으로 드러난 것들만을 보죠. 그래서 수많은 블록체인 회사들이 예쁘고 쉽고 접근하기 좋은 웹페이지를 만들거나 플랫폼을 만들어서 이런저런 활동을 하게 만드는 거예요. 사실 블록체인이 정말 널리고 널려서 이제 공인인증서 등등이 필요없어지게 될 지도 몰라요. 지금도 공인인증서는 폐지수순을 밟고 있고 은행의 인증절차도 간편해지고 있잖아요. 중요한 건 우린 그냥 '우왕 편하다~~' 라는 것만 인지할 뿐 이게 왜 편해졌는지는 관심이 없어요.맞아요. 우린 알게모르게 블록을 만들고 있을 수도 있어요. 당신의 컴퓨터에서 말이죠. 이미 당신은 블록체인에 참여한 거예요. 당신도 장부에 뭔가를 기록했고, 그 블록체인에 참여한 철수란 사람이 그 후에 또 뭔가를 적으면 당신의 컴퓨터에서도 그걸 인식할 수 있어요. 그래서 당신은 철수를 모르지만 당신의 컴퓨터는 철수를 알고 있어요.  이 때문에 P2P거래도 별 인증절차없이 이루어질 수 있는 거예요. 당신의 컴퓨터는 철수를 믿고있거든요. 정리해보면 블록체인은 규칙이예요. 코드로 이루어진 일종의 어떤 규칙이죠. 이걸 블록체인회사에서 만든다음자기들이 어느정도 지분을 가져가요. 자기들이 만들었으니 좀 가지고 있어야 할 거 아니예요. 주로 암호화폐의 형태겠죠.그리고 또 어느 정도는 채굴자들을 모아서 채굴을 시켜요. 대부분은 장비가 충만하신 전문채굴자님들이겠죠. 이 분들은 적극적으로 블록을 만들어내고 많은 보상을 가져가요. 이 때의 보상도 대부분 암호화폐겠죠.나머지는 쪼끄마한 우리들이에요. 우린 그게 뭔진 잘 모르지만 그냥 재밌으니까 막 활동을 해요. 그러면서 블록들을 만들어내요. 우리도 블록체인을 튼튼하게 만드는 역할을 해주었으니 일종의 작은 보상들을 받아요. 이것도 암호화폐겠죠.이렇게 블록체인에 참여하는 컴퓨터수가 많아지면서 블록체인은 더 튼튼해지고 견고해져요. 그리고 겁나 빠르고 편해서 많은사람들이 쓰게 된다면....그게 추후엔 어떤 핵심플랫폼이 될 수도 있겠죠?...다들 그걸 꿈꾸고 열심히 블록체인 코드를 만들고 있는 거예요.여기서 궁금한 게 생겼어요. 그럼... 이런 블록체인 회사들은 돈을 어떻게 버는 걸까요???.... 생각해보면 개발비용이나...홍보나 인건비나..얘네들도 돈이 필요할 텐데 당장 가상화폐는 돈이 안되요. 이제 갓 태어난 화폐는 가치가 거의 없을 거예요. 그러니 마구 가상화폐를 만들어서 팔아도 그건 의미가 없어요. 이분들의 수익은 도대체 어디에서 나는 건지 그게 궁금해졌어요.그래서 3편에선 블록체인 회사들은 뭐 먹고 사는건지 알아보도록 하겠어요 :)어휴 힘들어..이제 저도 규칙에 의해서 자야겠어요.새벽2시가 되면 = 잠을 자라.(규칙)
조회수 2265

당신이 고민해야 할 성능 분석 요소

IT 서비스는 더욱 복잡해지고 어플리케이션과 인프라의 경계도 클라우드 환경과 함께 허물어지고 있습니다. 많은 기업들이 가상화를 넘어 컨테이너로 가고 있으며 서버리스도 더이상 낮설지 않습니다. 인프라의 변화와 함께 아키텍처의 변화도 다양하게 만들어져 가고 있습니다. 복잡성이 아무리 높아져도 우리는 서비스의 성능을 보장해야 합니다. 서비스의 성능을 보장하기 위해 우리가 체크해야 할 중요 요소들을 알아보려고 합니다. 1. 인프라스트럭처와 클라우드서비스의 성능은 코드 밖에서도 만들어집니다. 그중에서도 인프라스트럭처는 매우 중요한 요소입니다. 국내에서 인프라스트럭쳐 분야는 클라우드로 전환하는 과도기적인 상황에 있습니다. SMB 시장에서 클라우드는 익숙한 환경이지만 국내 엔터프라이즈 기업의 클라우드 도입 비율은 20%가 되지 않습니다. 특히 클라우드를 도입하려는 엔터프라이즈 기업들은 데이터 센터, 퍼블릭 클라우드, 프라이빗 클라우드를 모두 사용하는 상황으로 넘어가면서 클라우드에 대한 모니터링 체계를 구성하는데 많은 어려움을 겪고 있습니다. 특히 기존의 자원 사용량을 설계하고 운영하던 방식에서 스케일의 변화를 통해 서비스의 성능을 실시간으로 조절하는 클라우드 서비스 운영 방법은 조직의 구조 변화를 동반하기 때문에 더욱 어려운 작업이기도 합니다. 이렇듯 클라우드의 전환은 최근 웹 서비스의 성능에 많은 영향을 미치고 있으며 데이터독이나 뉴렐릭 그리고 와탭 같은 성능 분석 서비스들은 클라우드 기반의 인프라 모니터링 기능들을 강화하고 있습니다. 2. 데이터베이스어플리케이션 성능 이슈의 80% 이상이 데이터베이스 레이어에서 발생합니다. 대부분의 엔터프라이즈 기업들은 자사의 어플리케이션을 성능 분석을 위해 DBA 포지션을 마련하거나 필요에 의해 컨설팅을 받고 있지만 아쉽게도 스타트업은 DBA포지션을 마련하는 경우가 거의 없습니다. 웹 서비스의 규모가 커지기 시작하면 데이터베이스로 인한 지연 장애가 매우 심각해 지기 시작합니다. 레거시로 인한 이슈까지 추가되면 서비스의 성능은 지속적으로 낮아지게 되므로 데이터베이스는 꾸준히 관리해야 하는 요소입니다.데이터베이스의 비중이 높다보니 어플리케이션 분석 서비스 중에서도 데이터베이스만 집중적으로 분석하는 도구들이 있습니다. 국내에서는 엑셈과 티맥스에서 데이터베이스 분석 솔루션을 제공하고 있습니다.  3. 오픈 소스와 써드파티 소프트웨어최근 두가지 형태의 트렌드가 서비스 성능에 영향을 주고 있습니다. 하나는 오픈 소스이고 다른 하나는 써드 파티 소프트웨어 입니다. 안정화 된 오픈 소스를 사용하더라도 설정 이슈 또는 사용 환경 이슈로 성능에 영향을 주는 상황이 많이 발생합니다. 위젯, 광고플랫폼, 플러그인등의 써드파티 또한 웹 서비스의 성능에 영향을 주는 요소입니다. 최근 써드 파티의 사용은 점점 늘어나는 추세로 인해 장애 발생에 대한 위험도는 더욱 높아가고 있습니다. 특히 써드 파티는 시간이 흐르면서 성능에 조금씩 부하를 누적시키기도 하므로 충분히 주의를 기울여야 합니다. 이런 환경에서도 서비스의 성능을 유지하기 위한 방법으로 통계 기반의 메소드 분석 기법 모니터링의 중요한 요소가 되어 가고 있습니다. 와탭의 Java 모니터링이 메소드 분석 서비스를 제공하고 있습니다. 4. 모바일구글 이 운영하는 더블클릭(https://www.doubleclickbygoogle.com/articles/mobile-speed-matters/)에 따르면 북미에서 3G에서의 모바일 페이지 로딩까지 소요되는 시간은 평균 19초입니다. 한국은 이미 4G를 넘어가고 있기도 하고 모바일 기기의 성능도 매우 높아서 북미와 상황이 다르지만 모바일 기반의 웹 서비스 성능을 분석할 수 있는 방안의 필요성은 높아져 가고 있습니다. 이와 함께 다양한 환경을 지원하는 end-to-end 모니터링의 중요성이 점점 대두되고 있는 상황입니다.  5. 컨테이너최근 인프라스트럭처의 새로운 흐름은 컨테이너 입니다. 한국은 리눅스 기반의 서비스 구축 시스템이 잘 발달한 덕분에 클라우드 도입이 다른 나라보다 늦은 편입니다. 하지만 최근 국내에 컨테이너 기반의 인프라스트럭처 도입 기업들이 많아지고 있습니다. 우리나라는 가상화를 건너뛰고 컨테이너부터 활성화 될수도 있을 거라 생각됩니다. 컨테이너 환경은 가상화보다 더 많은 인프라를 더 유동적으로 사용하게 되므로 기존의 규모를 뛰어 넘는 관리 체계를 만들어 나가야 합니다. 데이터독과 뉴렐릭 같은 SaaS 기반의 모니터링 서비스들은 이미 컨테이너의 대한 지원을 하고 있으며 와탭 또한 단순 지원을 넘어 컨테이너 전용 서비스를 준비중에 있습니다. 6. 마이크로 서비스많은 기업들이 클라우드와 함께 Micro Service Arichtecture를 도입하고 있기 때문에 독립적인 어플리케이션을 기반으로 하는 서비스 구조는 계속 발전해 나갈 것입니다. 마이크로 서비스와 클라우드의 조합은 커져가는 서비스의 규모를 독립적인 작은 단위로 나눌 수 있어서 매력적이긴 하지만 과거와 다른 운영 조직과 프로세스를 만들어야 하는 숙제를 만들었습니다. 예를 들면 기존에는 하나의 임계치를 사용하여 서비스의 위험도를 관리했다면 이젠 독립적으로 동작하는 서비스들의 임계치를 각각 어떻게 설정하고 관리할 것인지 고민해야 합니다. 독립된 마이크로 서비스의 성능 이슈가 전체 서비스 성능 이슈로 확대되지 않더라도 작게 발생하는 이슈들을 관리하지 못한다면 지속적으로 발전해야 하는 서비스의 미래도 흔들리게 될 것입니다. 7. 서버사이드 코드정상적인 상황이라면 서버사이드 코드에서 발생되는 지연시간은 찰나에 가깝지만 장애 상황에서의 지연은 서버사이드에서 발생하는 경우가 많습니다. 특히 방어가 되어 있지 않은 코드들은 물리적 요소의 작은 변화에 대처하지 못하고 웹 서비스 전체에 영향을 미치게 됩니다. 스타트업의 경우 개발팀이 운영을 함께 맡고 있는 경우가 많기 때문에 서버사이드의 코드를 직접 분석하곤 합니다. 하지만 서비스의 성능이 느려지는 상황 자체를 파악하지 못하는 경우가 많습니다. 서버 사이드에서 평균 응답시간을 체크하는 경우 10초 평균 응답시간이 0.5초를 넘는 경우는 거의 없습니다. 하지만 0.5초의 평균 응답시간을 같는 서비스라 할지라도 하루 동안 10초이상 걸린 고객의 숫자는 규모에 따라 1,000명이 넘을 수도 있습니다. 서비스에 규모가 있다면 꼭 APM을 사용해야 합니다.8. 네트워크 지연네트워크의 지연으로 인한 고객 불만은 예상외로 많이 발생합니다. 인프라스트럭처 이슈로 볼 수도 있겠지만 서비스를 운영한다면 항상 체크하고 있어야 하는 요소입니다. 해당 이슈를 확인 하려면 웹서비스 모니터링을 사용하시면 됩니다. 웹서비스 모니터링을 통해 네트웍상태를 포함한 서비스의 응답시간을 체크해 볼수 있습니다. 와탭의 경우 내부적으로 웹서비스 모니터링을 개발하여 사용하고 있지만 아직 서비스 하고 있지는 않습니다.  9. 자원 사용률자원 사용률은 최근 새로 떠오르는 이슈입니다. 이전에는 인프라스트럭쳐가 고정값이였기 때문에 자원 사용률이 모자라는 경우 서비스 성능을 포기하고 초과되는 고객의 요청을 앞단에서 버리거나 대기시키는 기법들을 사용해왔습니다. 클라우드 환경에서는 자원 사용량의 임계치가 넘어가면 자동으로 스케일을 조정하는 환경이 마련되면서 성능을 유지하는 것이 가능합니다.  클라우드 환경에서 과부하 상태에 접근하면 자동으로 인프라의 규모가 확장되고 과부하 상태는 정상으로 돌아갑니다. 이렇게 환경이 바뀌면서 자원 사용률의 중요 이슈가 성능에서 비용으로 전환되고 있습니다. 부하에 따른 스케일링 정책을 어떻게 정하는지에 따라서 성능과 비용 모두가 영향을 받기 때문에 Auto Scale에 대한 모니터닝이 관심을 받고 있습니다.  마무리웹 서비스의 성능에 영향을 주는 요소는 정말 많습니다. 와탭랩스 IT 기업의 어플리케이션을 모니터링 하기 때문에 기업의 IT 어플리케이션 성능 문제에 대해 항상 고민하고 있습니다. 해당 내용은 매달 또는 분기별로 트렌드를 반영하여 업데이트하고 할 생각입니다. 많은 분들에게 도움이 되었으면 좋겠습니다. #와탭랩스 #개발자 #개발팀 #인사이트 #경험공유 #일지
조회수 1419

반복적인 모니터링 프로세스 구축

IT 서비스에 장애가 발생 할 경우 모니터링 프로세스는 장애를 찾는 것으로 끝나지 않습니다. 장애를 발견하는 것은 모니터링 프로세스의 시작 점이며 최종적으로 모니터링을 통해 장애의 근본 원인을 찾아낼 수 있어야 합니다. 그리고 찾아낸 원인들은 예측과 추론에서 확인까지 이르는 하나의 프로세스로 정착되어 다시금 모니터링 과정에 포함되어져야 합니다. 이렇게 서비스를 운영하는 과정에서 근본적인 장애를 찾기 위해 모니터링을 어떻게 이해해야 하는지 알아보겠습니다. 우리가 모니터링 해야 하는 지표어플리케이션 지표(WORK METRICS)- 처리량 지표(THROUGHPUT)- 성공 지표(SUCCESS)- 에러 지표(ERROR)- 성능 지표(PERFORMANCE)시스템 지표(RESOURCE METRICS)- 가동률(UTILIZATION)- 포화상태(SATURATION)- 에러 지표(ERROR)- 이용률(AVAILABILITY)이벤트(EVENTS)- 코드 변경(CODE CHANGES)- 경고 알림(ALERTS)- 규모 변경(SCALING EVENT)- 기타(ETC)IT 서비스를 운영하는 과정에서 발생하는 문제의 근본원인을 추적하기 위한 모니터링 데이터는 크게 3가지로 나눌 수 있습니다. 어플리케이션 지표(Work metrics)서비스의 흐름(트렌젝션)을 측정하여 시스템의 최상위 레벨의 이슈를 보여줍니다. 시스템 지표(Resource metrics)이용률, 상태, 에러 또는 시스템 의존적인 리소스의 이용률을 수량화합니다.이벤트(Events)코드변경, 내부 경고, 확장 이벤트와 같이 드물게 발생하는 불연속적이 이슈를 보여줍니다.일반적으로 IT 모니터링의 핵심 이슈는 어플리케이션 지표를 통해 확인할 수 있습니다. 하지만 다른 지표들 또한 어플리케이션의 지표에서 나타난 문제의 원인을 찾기 위한 중요한 요소이기 때문에 같이 모니터링 해야 합니다. 시스템 지표를 통한 모니터링인프라스트럭쳐는 대부분 시스템의 자원으로 구성됩니다. 최상위 수준에서 유용한 작업을 하는 각각의 시스템들은 다른 시스템들과 연동하기도 하는데요. 예를 들어, 여러분의 아파치 서버가 MySQL 데이터베이스를 자원으로 사용하여 요청을 처리하는 작업을 지원할 수 있습니다. 연관된 작업을 따라 들어가보면 MySQL은 제한된 커넥션 풀을 관리하기 위한 리소스를 가지고 있고 MySQL이 실행되는 서버의 물리적인 리소스 레벨에서는 CPU, Memory, Disk 같은 지표를 보게 됩니다.어플리케이션이 서비스를 제공하는 데 있어서 각각의 리소스가 그 작업을 지원한다면 우리는 장애가 발생한 경우에, 필요한 원인을 얻는 좋은 방법을 시스템을 통해서도 찾아볼수 있습니다. 이런 프로세스를 만들어 간다면 시스템에서 발생한 경고를 통해 장애의 원인을 체계적인 조사하는데 도움이 될 것입니다. 1. 최상위 어플리케이션 지표에서 시작하기첫번째 해야 하는 질문은 "발생한 장애를 설명할 수 있는가?" 이다. 처음부터 문제를 명확하게 정의하지 못하면 이슈를 분석하기 위해 파고들어가야 하는 시스템 패스를 잃어버릴 확률이 높다.다음으로 문제가 있을 것으로 보여지는 최상위 시스템의 작업 지표를 검사해라. 이 지표들은 종종 문제의 원인을 알아내거나 또는 적어도 추적해야 하는 방향을 알려 줄 것이다. 예를 들어 성공적으로 진행된 작업의 성공율이 한계치 이하로 떨어졌다면 에러 지표를 찾아보고 반환된 에러의 형러의 타입을 살펴봄으로써 문제의 방향을 찾아나갈 것이다. 반면에, 대기시간이 길고 외부 시스템에 의해서 요청된 작업처리량이 매우 높다면 시스템 과부하로 인한 문제일 확률이 높다. 다만 와탭의 어플리케이션 분석 서비스를 사용한다면 약간 방법을 달리해도 된다. 와탭의 성능 분포도(어플리케이션 히트맵)와탭의 어플리케이션 성능 분포도를 통해 문제가 발생한 트랜잭션을 드래그하여 선택하게 되면 실제 어플리케이션에서 발생하는 스탭들을 추적하여 문제 해결에 바로 도달할 수도 있다. 하지만 더 복잡한 형태의 장애라면 시스템의 리소스 정보를 찾아봐야 합니다.  2. 리소스 찾아보기최상위 work metrics를 조사하여 문제의 원인을 알수 없다면, 다음으로 시스템이 사용하는 리소스(물리적인 요소 뿐만 아니라 시스템의 리소스 역할을 하는 소프트웨어 또는 외부 서비스)들을 조사합니다. 해당 리소스가 높다면 리소스를 사용하는 하위 Application 지표를 찾아보는 방식으로 찾아나갑니다. 와탭의 데시보드(CPU, MEMORY)3. 변경 내용 찾아보기다음으로 지표에 연관된 경고와 다른 이벤트들을 살펴봅니다. 문제가 발생하기 직전 코드가 릴리즈 되었거나, 내부 경고가 발생하고나 다른 이벤트가 등록되었다면 문제와 연관된 부분을 찾아봐야 합니다. 4. 수정하기 (잊지 말기)문제의 원인을 찾았다면 문제의 원인이 되는 상태를 수정해보고 증상이 사라지는 것을 확인합니다. 증상이 더이상 나오지 않는다면 향후 유사한 문제를 피하기 위해 시스템을 어떻게 변경할지 고민해야 합니다.  서비스가 중단된 상황이 오면 1분이 중요합니다. 문제를 찾는 속도를 높이기 위해 눈앞에서 벌어진 상황에 대한 높은 집중력을 유지하면서 대쉬보드를 상황에 맞춰 재 조정합니다. 최상위 어플리케이션 데쉬보드와 각각의 서브시스템들을 위한 대시보드를 하나씩 설정합니다. 시스템 대시보드는 시스템 지표의 하위 시스템의 키 메트릭스와 함께 어플리케이션 메트릭을 확인 할 수 있어야 합니다. 이벤트 데이터도 이용가능한 상황이라면 연관 분석 차트에서 관련된 이벤트가 올라가 있어야 합니다. 와탭의 알림 서비스정리하기   서비스에 장애는 무조건 발생하지만 우리는 모니터링을 통해 빠르게 해결 할 수 있습니다. 이를 위해 표준화된 모니터링 프로세스를 만들고 대시보드로 연관관계를 만들어 놓는다면 문제를 빠르게 추적 조사할 수 있습니다. 가능하면 모든 지표는 어플리케이션 지표에서 부터 찾을 수 있도록 대시보드를 구성합니다.인프라스트럭처를 통해서도 문제를 분석할 수 있습니다. 시스템에 대해 대시보드를 설정하고 주요 지표들을 올려놓아야 합니다. 문제의 원인을 조사하는 것은 증세가 나타나는 최상위 시스템에서 부터 시작합니다. 문제가 되는 리소스가 발견되면 문제를 발견하고 수정할 때가지 리소스에서 발견되는 패턴을 조사하고 적용시키는작업을 반복해야 합니다. #와탭랩스 #개발자 #개발팀 #인사이트 #경험공유 #일지
조회수 3375

Next.js 튜토리얼 2편: 페이지 이동

* 이 글은 Next.js의 공식 튜토리얼을 번역한 글입니다.** 오역 및 오탈자가 있을 수 있습니다. 발견하시면 제보해주세요!목차1편: 시작하기2편: 페이지 이동  - 현재 글3편: 공유 컴포넌트4편: 동적 페이지5편: 라우트 마스킹6편: 서버 사이드7편: 데이터 가져오기8편: 컴포넌트 스타일링9편: 배포하기개요이제 간단한 Next.js 애플리케이션을 만들고 동작시키는 법을 알았습니다. 이 간단한 애플리케이션은 하나의 페이지를 가지고 있지만 원하는 만큼 페이지를 추가할 수 있습니다. 예를 들어 pages/about.js에 다음 내용을 추가하여 "About" 페이지를 만들 수 있습니다:그러면 http://localhost:3000/about를 통해 About 페이지에 접근할 수 있습니다.이제 이 페이지들을 연결시켜야 합니다. 이를 위해 HTML의 "a" 태그를 사용할 수 있습니다. 그러나 a 태그를 사용하면 클라이언트 사이드를 통해 이동하지 않습니다. 원하지 않게도 서버 사이드를 통해 페이지가 이동합니다.클라이언트 사이드 이동을 지원하기 위해 next/link를 통해 export된Next.js의 Link API를 사용해야 합니다.설치이번 장에서는 간단한 Next.js 애플리케이션이 필요합니다. 이전 편을 수행하거나 다음의 샘플 애플리케이션을 다운받아주세요:아래의 명령어로 실행시킬 수 있습니다:이제 http://localhost:3000로 이동하여 애플리케이션에 접근할 수 있습니다.Link 사용하기두 개의 페이지를 연결하기 위해 next/link를 사용할 예정입니다.pages/index.js에 다음과 같은 코드를 추가해주세요.next/link를 Link로 import하여 다음과 같이 사용하였습니다:http://localhost:3000에 방문해주세요.그런 다음 "About Page" 링크를 클릭하면 "About" 페이지로 이동합니다.이것은 클라이언트 사이드 이동입니다. 이 동작은 서버 요청없이 브라우저 안에서 수행됩니다.브라우저의 네트워크 상태 검사 툴에서 확인할 수 있습니다.자 지금 간단한 과제가 있습니다:- http://localhost:3000에 방문하세요.- 그런 다음 "About Page"를 클릭하세요- 브라우저의 뒤로가기 버튼을 클릭하세요.뒤로가기 버튼을 클릭했을 때 어떤 일이 일어나는지 가장 잘 설명한 것은 무엇인가요?- 뒤로가기 버튼이 동작하지 않았다.- 뒤로가기 버튼이 브라우저 콘솔에 에러를 발생시켰다.- 클라이언트 사이드를 통해 인덱스(home) 페이지로 이동했다.- "뒤로가기 버튼을 지원하기 위해 'next/back'를 import하세요"라는 알럿창이 띄워졌다클라이언트 사이드 히스토리 지원뒤로가기 버튼을 클릭하면 클라이언트를 통해 인덱스 페이지로 이동합니다. next/link는 모든  location.history를 처리합니다.클라이언트 사이드 라우팅에 대한 코드를 단 한 줄도 작성할 필요가 없습니다.간단하게 페이지들을 연결하세요. 그래도 잘 동작합니다!Link 스타일링하기대부분의 경우 링크에 스타일을 지정하고자 합니다. 스타일을 지정하는 방법입니다:위와 같은 코드를 추가하면 스타일이 올바르게 적용된 것을 볼 수 있습니다.위의 코드 대신 아래의 코드처럼 작성하는면 어떨까요?위의 코드처럼 변경했을 때 어떤 일이 일어났나요?- 원하던 스타일이 올바르게 적용되었다.- 링크에 어떤 스타일도 적용되지 않았다.- 전체 페이지가 다시 로딩된 후에 스타일이 적용되었다.- 스타일이 적용되었지만 콘솔에 에러가 나타났다.Link는 래퍼 컴포넌트입니다사실 next/link에 있는 스타일 prop는 아무런 효과가 없습니다. 왜냐하면 next/link는 단지 "href"와 다른 라우팅 관련 props만 받아들이는 래퍼 컴포넌트이기 때문입니다. 스타일을 적용해야 한다면 하위에 있는 컴포넌트에 지정해야 합니다.Button이 있는 Link링크의 앵커 대신에 "button"을 사용해봅시다. 다음과 같이 코드를 수정해야 합니다:인덱스 페이지의 버튼을 클릭하면 어떤 일이 일어날까요?- 아무 일도 일어나지 않는다- "링크 안에 버튼이 올 수 없습니다"라는 에러가 발생한다- 페이지가 다시 로딩된다- about 페이지로 이동한다Link는 어떤 것과도 동작합니다버튼과 같이 커스텀 React 컴포넌트나 div 등을 Link 안에 배치할 수 있습니다.Link 안에 있는 컴포넌트들의 유일한 요구 사항은 onClick prop를 받을 수 있어야 한다는 것입니다.Link는 간단하지만 강력합니다이번 편에서는 next/link의 기본적인 사용법을 살펴보았습니다. Link를 사용하기 위해  몇 가지 재밌는 방법들이 있습니다. 다음 편들에서 배울 예정입니다.그동안 Next.js Routing documentation를 살펴보세요. 유용합니다.#트레바리 #개발자 #안드로이드 #앱개발 #Next.js #백엔드 #인사이트 #경험공유
조회수 1119

Vue, 어디까지 설치해봤니?

Overview새로운 사용환경 구축에 도전하는 건 개발자의 운명과도 같습니다. 오늘은 여러 장점을 가지고 있는 Vue (프론트엔드 자바스크립트 프레임워크)를 도전해보겠습니다. Vue는 다른 프레임워크에 비해 가볍고, 개발하기에 편합니다. 그럼 우선 Vue를 설치합시다! Vue 설치CDNhttps://unpkg.com/vue 주소를 script 태그에 직접 추가 Vue.js 파일다운개발용, 배포용 버전을 다운 받아 script 태그에 추가개발용 버전은 개발에 도움이 되는 모든 경고를 출력하기 때문에 개발 중에만 사용하고, 실제 서비스에서는 배포용 버전으로 사용해야 한다. NPM 설치규모가 큰 프로젝트 경우 컴포넌트별 독립적으로 관리할 수 있는 싱글 파일 컴포넌트 방식 추천 Vue를 설치하는 방법은 여러 가지가 있습니다. 각자 특성에 맞게 편리한 방법으로 설치해주세요. 이번 글에서는 싱글 파일 컴포넌트 방식을 사용할 것이므로 NPM vue-cli 를 설치해 프로젝트를 구성하겠습니다. # vue-cli 전역 설치, 권한에러시 sudo 추가 $ npm install vue-cli -global vue-clivue-cli를 사용하면 뷰 애플리케이션을 개발하기 위한 초기 프로젝트 구조를 쉽게 구성할 수 있습니다. 다만, 싱글 파일 컴포넌트 체계를 사용하려면 .vue 파일을 웹 브라우저가 인식할 수 있는 형태의 파일로 변환해 주는 웹팩(Webpack)이나 브라우저리파이(Browserify)와 같은 도구가 필요합니다. vue-cli 설치 명령어 vue init webpack : 고급 웹팩 기능을 활용한 프로젝트 구성 방식. 테스팅,문법 검사 등을 지원vue init webpack-simple : 웹팩 최소 기능을 활용한 프로젝트 구성 방식. 빠른 화면 프로토타이핑용vue init browserify : 고급 브라우저리파이 기능을 활용한 프로젝트 구성 방식. 테스팅,문법 검사 등을 지원vue init browserify-simple : 브라우저리파이 최소 기능을 활용한 프로젝트 구성 방식. 빠른 화면 프로토타이핑용vue init simple : 최소 뷰 기능만 들어간 HTML 파일 1개 생성vue init pwa : 웹팩 기반의 프로그레시브 웹 앱(PWA, Progressive Web App) 기능을 지원하는 뷰 프로젝트여러 설치 명령어 중에 특성에 맞는 초기 프로젝트를 생성하세요. 1) vue init webpack 실행# 해당 프로젝트 폴더에서 실행 $ vue init webpack   # 현재 디렉토리에서 프로젝트 생성 여부 ? Generate project in current directory? (Y/n) # 프로젝트 이름 ? Project name (vue_ex) # 프로젝트 설명 ? Project description (A Vue.js project) # 프로젝트 작성자 ? Author (곽정섭 ) # 빌드 방식 ? Vue build (Use arrow keys) # vue-router를 설치 여부 ? Install vue-router? (Y/n) # 코드를 보완하기 위해 ESLint를 사용 여부 ? Use ESLint to lint your code? (Y/n) # ESLint 사전 설정 선택 ? Pick an ESLint preset (Use arrow keys) # 단위 테스트 섧정 ? Set up unit tests (Y/n) # 테스트 러너 선택 ? Pick a test runner (Use arrow keys) # Nightwatch로 e2e 테스트를 설정 여부 ? Setup e2e tests with Nightwatch? (Y/n) # 프로젝트가 생성 된 후에`npm install`을 실행해야합니까? ? Should we run `npm install` for you after the project has been created? (recommended) (Use arrow keys) 2) 고급 웹팩 기능을 활용한 프로젝트 구성 방식으로 설치3) 설치완료4) package.json 파일에 설정된 라이브러리 설치$ npm install 5) 개발모드 실행# 해당 프로젝트 폴더에서 실행(소스수정시 자동 새로고침) $ npm run dev 6) http://localhost:8080/ 브라우저 실행7) Yeah, You got it!!!!추가 도구: Vue Devtools(크롬 확장 플러그인)Vue Devtools(크롬 확장 플러그인)은 Vue를 사용할 때, 브라우저에서 사용자 친화적으로 검사하고 디버그할 수 있습니다.크롬 개발자 도구에 Vue 탭이 추가됨ConclusionVue를 설치하는 여러 방법 중 고급 웹팩 기능을 활용한 프로젝트 구성을 알아봤습니다. 다음 글에서는 Vue 인스턴스 및 디렉티브(지시문) 사용법을 다뤄보겠습니다.참고설치방법 — Vue.js 글곽정섭 과장 | R&D 개발1팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유 #Vue

기업문화 엿볼 때, 더팀스

로그인

/