유니티 자격요건에 본인이 왜 충족되었는지 설명

  • 문서 엔지니어링과 API 문서화

    유니티 자격요건에 본인이 왜 충족되었는지 설명

    전정은2019-06-28

    LINE에서 문서 솔루션 및 기술 문서 작성 기법을 고민하는 도큐먼트 엔지니어입니다.

    이 글은 마이크로소프트웨어 396호에 기고된 글입니다. 들어가기 테크니컬 라이터(technical writer)라는 말을 들으면 대부분 '라이터'라는 단어만 보고 '글 쓰는 사람'이라 생각하기 십상입니다. 물론 틀린 것은 아니지만, 실상 키보드를 두드리며 글 쓰는 일이 테크니컬 라이터 업무의 대부분을 차지하지는 않습니다. 하루에 얼마 동안 글을 쓰는지 측정해 본 적은 없으나, 테크니컬 라이터 톰 존슨(Tom Johnson)이 말하기로는 일하는 시간의 약 10%라고 합니다. 그렇다면 그 밖의 시간에는 뭘 할까요? 역시 톰 존슨에 따르면 개발자 인터뷰, 다른 사람이 쓴 문서 리뷰, 앱 스크린 캐스팅이 필요할 때 아이폰 탈옥, 미디어위키에서 링크된 이미지에 캡션 넣는 방법 찾기 등등이라고 합니다. 그중에서도 제가 말하고자 하는 일은 비록 '아이폰 탈옥' 같은 건 아니지만 결과적으로는 글쓰기와는 전혀 관계 없어 보이는 엔지니어링에 관한 것입니다. 기술 문서 작업을 해보면 한 번 이상은 이런 요청을 받게 됩니다. '기술 정보를 담은 텍스트 파일 200개를 워드 파일 하나로 만들어 주세요.''사내 위키에 쓴 게시물을 공식 개발자 사이트에 올려주세요.' 이런 요청을 받았을 때 취할 수 있는 방법은 단 하나, '복붙(복사해서 붙여넣기)'입니다. 물론 고결한 지성을 발휘해 텅 빈 백지에 새로운 글 한 편을 써내야만 하는 테크니컬 라이터에게 있어, 아무런 지성을 동원하지 않아도 되는 이런 작업은 재충전 기회기도 하니 하루 정도 투자할 가치는 있습니다. 그런데, 일주일 후 또 이런 요청이 들어옵니다. '텍스트 파일을 살펴봤더니, 구버전에만 적용되는 내용이 있었어요. 200개 파일에서 군데군데 수정했으니, 그 부분만 지난번에 만든 워드 파일에 업데이트해주세요.' 그들은 '그 부분만 업데이트'라고 말하지만, 부분 업데이트는 자칫하면 빠뜨리는 곳이 생기거나 다른 부분을 망가뜨릴 수 있어서 결국엔 전체를 다시 한번 손봐야 하는 일입니다. 저와 제 동료는 이를 '인형 눈알 꿰기'라고 불렀습니다. 애초에 워드 파일에 업데이트해주면 좋으련만, 그렇게는 안 됩니다. 왜 안 되는지는 블로그 분량의 한계 때문에 구구절절 말할 수 없으나, 보통 그렇고 그런 이유로 인형 눈알 꿰기 작업이 반복적으로 발생합니다. 몇 번쯤 인형 눈알 꿰기를 하던 열정에 찬 신입 테크니컬 라이터는 자연스럽게, 워드 파일을 만든 Microsoft를 원망하게 됩니다. 그리고 분노와 원망의 단계를 넘어서고 난 뒤 비로소 문서 엔지니어로 거듭납니다. 테크니컬 라이팅과 문서 엔지니어링이 떼려야 뗄 수 없는 관계인 이유입니다. 저는 본래부터 도구를 만들고 쓰는 것을 좋아해서, Microsoft를 원망하는 기간이 조금 짧았습니다. 덕분에 좀 더 빨리 문서 엔지니어의 길로 들어섰고, 이렇게 그에 관한 글을 쓰고 있습니다. 생각해보면 테크니컬 라이터가 된 후, 처음 맡은 프로젝트에서 백여 개 엑셀 파일을 워드 파일 하나로 만드는 작업을 하게 된 것이 그 길의 시작이었습니다. 당시 프로젝트 매니저 역시 반복되는 엑셀-워드 간 복붙이 비효율적이라고 느껴 해결책을 찾던 중이었습니다. 저는 그 일을 맡아 오피스 오픈 XML(Office Open XML)을 이용해 엑셀을 워드로 변환하는 도구를 만들었습니다. 처음에는 엑셀을 워드로 바로 변환했지만, 데이터 버전 관리와 양방향 변환이 필요해져 엑셀 데이터를 표준 XML 형태로 변환, 저장한 다음 워드 파일로 출력하게 바꿨습니다. 배운 게 도둑질이라고 그 후 저는 문서 생성 자동화, 다양한 포맷 지원을 위한 문서 표준화에 관심을 가지게 됐습니다. 문서 엔지니어링에 속하는 작업은 다양합니다. 문서화 프로세스를 구축하고 프로젝트 산출물을 관리하는 것도 문서 엔지니어링이지만, 이 글에서는 제 관심 분야인 문서 변환과 자동화에 관해서만 써보려고 합니다. API 문서화 테크니컬 라이팅 업무 가운데 API 문서화는 문서 엔지니어링이 가장 많이 적용되는 분야입니다. 이 글에서 다른 기술문서가 아닌 API 관련 문서를 다루는 것도 그런 이유입니다. API 문서, 주로 API 레퍼런스라고 부르는 이 기술 문서는 프로그래밍 역사와 함께 존재해왔습니다. 뜻 맞는 친구 몇 명이 모여 소프트웨어를 만들던 시절에 반드시 테크니컬 라이터가 함께 했다곤 할 수 없을 테니, 대부분은 프로그래머가 직접 API 레퍼런스를 썼을 것입니다. 처음에는 텍스트 파일로 썼겠지만, 소스코드와 동기화하기도 어렵고 문서를 별도로 작성해야 하므로 부담스러웠겠지요. 그 덕분에 소스코드에서 API 레퍼런스 작성하는 방법이 생겨난 것으로 추측할 수 있습니다. API 레퍼런스는 흔히 개발 문서라고 하는 개발자 가이드와는 약간 다릅니다. 개발자 가이드는 내용 흐름과 연관 관계가 있지만, API 레퍼런스는 API 간 순서나 연결이 거의 없습니다. 극단적으로 말하면, 한 API 설명이 곧 하나의 문서인 셈입니다. <그림 1>과 <그림 2>에서 보는 것처럼 개발자 가이드는 설명글이 길게 이어지지만, API 레퍼런스는 유사한 것끼리 분류는 했으나 각자 역할만 설명하고 있습니다. <그림 1> LINE 개발자 가이드 예(LINE SDK for iOS) <그림 2> LINE API 레퍼런스 예(LineSDKJSONWebToken) API 레퍼런스는 각 API 설명 형태도 무척 비슷합니다. 라이브러리 API나 프레임워크 API는 함수(메서드)명, 설명, 파라미터, 반환 값이 반복되고, REST API는 엔드포인트(endpoint), 설명, 파라미터, 응답 설명이 반복됩니다. 이처럼 서로 연결되지 않으며 반복되는 구조를 가진 문서는 도구를 이용해 작업하기 좋습니다. 그래서 개발자 가이드를 자동으로 만들어주는 도구는 없어도, API 레퍼런스를 만들어주는 도구는 예전에도 있었고 지금도 계속 발전하고 있습니다. 1995년, 썬 마이크로시스템즈(Sun Microsystems)가 자바(Java)와 함께 소스코드 주석을 이용해 API 문서를 만드는 자바독(Javadoc)을 선보였고, 20년이 지난 지금까지도 널리 쓰이고 있습니다. 자바독의 성공에 고무된 덕분일까요. 다른 프로그래밍 언어도 소스코드를 이용한 자체 문서화 도구를 속속 내놓았습니다. 소스코드 기반은 아니지만, REST API를 위한 'OpenAPI Specification(이하 OAS)'도 문서화 도구를 제공합니다. API 레퍼런스는 꼭 필요할까 이 분야에서 수년간 일한 경험에 기반하여 기술 문서 중에서 개발자가 가장 많이 보는 것은 API 레퍼런스라고 어렴풋이 느끼고는 있었지만, 주관적인 느낌일 뿐 뒷받침해 줄 데이터가 없었습니다. 그런데 고맙게도, SIGDOC에서 실험을 통해 정량적인 데이터를 발표했습니다. 모집단이 작긴 하지만 이런 유의 조사가 많지 않으니 개발자가 새로운 API를 마주했을 때 어떤 행동을 하는지 참고할 만합니다. SIGDOC가 발표한 데이터를 참고해 만든 <그림 3>은 개발자가 처음 접하는 API를 사용해 문제를 해결할 때 어떤 부분에 얼마나 시간을 할애하는지 보여줍니다. <그림 3> 문제 해결 시간 동안 개발자 시선이 머문 곳(How Developers Use API Documentation: An Observation Study, SIGDOC) 가장 많은 부분을 차지한 'Editor & Client'는 문서 외 작업 시간입니다. 이를 제외하면, 개발자가 기술 문서를 볼 때 가장 많은 시간을 할애한 부분이 API 레퍼런스임을 확인할 수 있습니다. 따라서 인력 및 시간 부족으로 모든 문서를 작성하기 어려운 프로젝트라도 최소한 API 레퍼런스는 작성하는 것이 좋습니다. 그래야 누군가 사용해줄 테니까요. 운 좋게도 API 레퍼런스는 개발자가 작성하기에 가장 부담 없는 문서입니다. 구구절절 이야기를 나열할 필요도 없고, 딱 정해진 내용만 쓰면 되기 때문입니다. API 레퍼런스에는 무엇을 써야 할까 API 문서화 요청을 받으면, 저는 보통 다음 순서로 작업합니다. API 유형 및 프로그래밍 언어, 출력 포맷을 확인한다. 그에 맞는 도구를 선정한다. API 하나를 골라 설명 샘플을 작성한다. 어디에 뭘 작성해야 하는지를 설명하는 가이드 템플릿을 작성한다. 개발자에게 API 설명 작성을 요청한 후, 그 내용을 검토한다. 전체 프로세스를 검수한 다음, 문서 생성을 자동화한다. 5, 6번 단계까지 진행할 수 있다면 더할 나위 없는 문서가 될 수 있습니다. 하지만 프로젝트 종료까지 테크니컬 라이팅을 전담해주지 못한다면 4번 단계까지만 작업해도 꽤 만족스러운 결과를 얻을 수 있습니다. 1번 작업이 필요한 이유는 API 유형과 프로그래밍 언어, 출력물 포맷에 따라 사용할 도구와 가이드 템플릿이 달라지기 때문입니다. 예를 들어, Android용 라이브러리 API 레퍼런스를 HTML으로 만들려면 자바독을 사용하고, 스프링(Spring)으로 작성한 REST API 레퍼런스를 웹서버로 공개하려면 스웨거(Swagger)를 사용하고, 파이썬(Python)으로 작성한 라이브러리의 API 레퍼런스를 전자책으로 배포하려면 스핑스(Sphinx)를 사용하는 식입니다. 아래 코드는 소스코드 주석 기반 API 레퍼런스 생성 도구인 자바독의 예시입니다. /** * 내가 만든 정말 멋진 클래스 * @author jeongeun.jeon * @version 0.1 */ public class MyFantasticClass { /** * 주어진 값의 2배를 계산한다. * * 정수가 아닌 값의 두 배를 얻으려면 see also에 있는 항목을 참고하라. * * @param base 두 배 하고자 하는 값. 0부터 100까지만 입력할 수 있다. * @return 주어진 값의 두 배 * @see getDouble(float) */ public int getDouble (int base) { …(중략) } } 보통은 IDE(integrated development environment)가 메서드 프로토타입에 따라 자동으로 필요한 태그(@param, @return 등)를 입력해줍니다. 이 태그를 보면 작성해야 할 내용이 무엇인지 알 수 있습니다. 이처럼 정해진 형식에 맞춰 내용을 작성하게 하는 것은 API 레퍼런스 도구의 기본 기능이자 쓰는 사람이 포기하지 않도록 붙잡아주는 끈입니다. 테크니컬 라이터도 백지를 펼쳐놓고 글을 쓰라고 하면 아득해지는데, 하물며 개발자에게 텅 빈 텍스트 파일을 주면서 쓰라고 하면 어떻게 될까요? 물론 잘 쓰는 사람도 있지만, 보통은 첫 문장을 시작할 때까지 시간이 한참 걸릴 것입니다. 정해진 형식에 따라 내용을 채우게 하는 방법은 객관식 문제와 유사해서, 설명글을 작성하는 속도가 훨씬 빨라집니다. 이 방법이 써야 할 항목을 정해주고 뭘 써야 할지 알려주긴 하지만, 각 항목에 꼭 필요한 내용을 쓰게 하려면 이것만으로는 부족합니다. 4번 단계처럼, 이 부분에는 어떤 내용을 쓰고 어떤 내용을 쓰지 말아야 하는지 설명해주는 가이드 템플릿을 함께 제공하는 것이 좋습니다. 가이드 템플릿은 프로그래밍 언어 또는 API 유형에 따라 다릅니다. 아래 코드는 자바독 가이드 템플릿입니다. 파라미터 설명 가이드 등은 유사한 구조를 가진 다른 API에도 적용할 수 있습니다. /** * 첫 문장에는 동사를 사용해 메서드의 역할을 설명하십시오. '무엇을 반환한다'는 지양하십시오.<p/> * 줄바꿈한 다음 아래와 같은 정보가 있으면 기술하십시오. * - 이 메서드를 호출하기 전후에 해야 하는 작업이 있다면 기술하십시오. * - 특정한 상황에서 이 메서드를 사용하지 말아야 한다면 이유를 설명하십시오. * *참고* 영어로 쓴다면 첫 글자는 대문자로 쓰십시오. * * @param base 어떤 의미의 파라미터인지 쓰십시오. * boolean 값이라면 언제 true이고 언제 false 인지 쓰십시오. * 숫자라면 범위가 있는지 쓰십시오. enum이라면 그 enum 항목을 링크하십시오. * 허락되지 않은 값을 전달했을 때 무슨 일이 발생하는지 쓰십시오. * @return 무엇을 반환하는지 명사로 쓰십시오. * boolean 값이라면 언제 true이고 언제 false 인지 쓰십시오. * 숫자라면 범위가 있는지 쓰십시오. enum이라면 그 enum 항목을 링크하십시오. * 문제가 발생했을 때 일반 상황과 다른 의미의 값을 반환한다면 기술하십시오. * (예: -1을 반환하는 경우) * @see 유사한 기능을 하는 메서드가 있다면 기술하십시오. * 어떤 스펙을 구현한 것이라면 그 스펙의 이름 혹은 링크를 기술하십시오. */ public int getDouble (int base) { …(중략) } } 가이드 템플릿에도 나와 있지만, API 설명에서 가장 자주 빠뜨리는 내용은 바로 파라미터나 반환 값의 범위입니다. 반환 값이 나열형이라면 어떤 값을 쓸 수 있는지 나열해야 하고, 반환 값이 숫자라면 그 범위나 값의 의미를 함께 설명해야 합니다. 이를 빠뜨리면 API 레퍼런스를 보고 개발하는 사람이 시행착오를 겪기 때문입니다. 따라서, 이런 주의사항을 쓴 가이드 템플릿을 제공하면 훨씬 빠르고 정확하게 설명문을 작성할 수 있습니다. 이렇게 작성된 내용으로 <그림 4>와 같은 출력물을 만들면 API 레퍼런스 작성이 끝납니다. <그림 4> 자바독 출력물 예(LINE SDK v5.0.0 for Android) 가이드 템플릿이 없더라도 도구를 이용해서 이런 상황을 막을 수 있을까요? 소스코드 주석 기반 도구에서 좀 더 발전한 것이 API 명세서 기반 도구입니다. 이 중 OAS가 그런 기능을 제공합니다. OAS의 주목적은 문서화가 아니라 설계 및 테스트이므로, API를 정의할 때 API가 사용할 객체를 명확히 입력하도록 권장합니다. 예를 들어, 객체의 필드가 enum이면 enum 스키마를 정의한 후 그 위치로 레퍼런스하고, 객체의 필드가 숫자면 최댓값과 최솟값을 입력해 범위를 지정합니다. 비록 필수는 아니지만 다른 도구에 비해 범위 지정을 빠뜨리는 횟수를 줄일 수 있습니다. API 레퍼런스는 어떻게 게시할까 API 레퍼런스 생성 도구 대부분은 HTML 출력물을 만드는 기능이 있습니다. 오픈소스 프로젝트, 특히 전담 테크니컬 라이터가 없는 프로젝트는 이런 도구를 이용해 만든 HTML 파일을 깃허브 페이지(GitHub Pages) 등에 업로드하면 충분합니다. 하지만 공식 개발자 지원 웹사이트를 가진 회사라면 상황이 조금 다릅니다. CMS(content management system) 같은 웹 콘텐츠 관리 도구로 공식 웹사이트를 만들거나 README.io, Apiary 같은 개발 문서 서비스를 사용하는 곳도 있습니다. 물론 자체 제작한 웹사이트를 사용하는 곳도 있습니다. 어느 쪽이든 <그림4>에서 본 출력물을 그대로 게시하기에는 어려움이 따릅니다. 기술적인 어려움도 있지만 정책적인 어려움도 있습니다. 구글만 해도, Android API 레퍼런스를 자바독 기반 독릿(doclet)인 Doclava를 사용해 서비스했으나, 자체 도구로 바꾼 지 오래입니다. 소스코드 주석에 기술한 API 설명을 보면 자체 도구도 자바독 기반일 것으로 짐작됩니다. <그림 5> Android API 레퍼런스 화면 Microsoft는 어떨까요? Microsoft는 IT 기업 가운데 기술 문서를 오픈소스화한 대표적인 곳입니다. 깃허브에 있는 애저(Azure) 기술 문서를 잘 들여다보면, REST API를 자체 정의한 YAML 형식1으로 기술한 것을 볼 수 있습니다. 애저 공식 웹사이트는 이 데이터 정보를 API 레퍼런스로 만들어 제공합니다. <그림 6> 애저 API 레퍼런스 화면 애저 문서 저장소에서 찾아본 YAML 데이터는 아래 코드와 같습니다. - uid: azure-arm-sql.JobVersions.get name: 'get(string, string, string, string, number, Object)' children: [] type: method langs: - typeScript summary: Gets a job version. syntax: content: >- function get(resourceGroupName: string, serverName: string, jobAgentName: string, jobName: string, jobVersion: number, options?: Object) parameters: - id: resourceGroupName type: - string description: > The name of the resource group that contains the resource. You can obtain this value from the Azure Resource Manager API or the portal. ...(중략) 오픈소스 프로젝트인 Enact 역시 개발 문서를 깃허브에 공개했으므로, 어떤 도구를 사용했는지 추측해볼 수 있습니다. Enact의 자바스크립트 API 레퍼런스는 JSDoc 형식으로 소스코드에 기술하고, 자체 엔지니어링을 거쳐 웹사이트에 게시하는 방식입니다. <그림 7> Enact API 레퍼런스 화면 Android와 Enact는 소스코드 주석을 사용하고, 애저는 YAML 기술 형식을 사용했습니다. 방식이 다른 이유는 사용하는 프로그래밍 언어와 API 유형에 따라 널리 사용되는 도구를 활용했기 때문일 것입니다. 그렇다 해도 이 도구를 회사 공식 웹사이트에 통합 적용하기 어려웠다면 과연 사용했을까요? 자바독과 JSDoc은 출력물 커스터마이즈를 지원하며, 애저는 OAS와 유사한 자체 API 명세서를 사용했으므로, 기존 웹사이트에 어울리도록 출력물을 바꿀 수 있습니다. 즉, 세 예시 모두 공식 웹사이트에 잘 녹아드는 출력물을 만들 수 있는 도구 혹은 방법을 선택한 것입니다. 데이터 기반으로 말해야 하는 공학도로서 다소 어울리지 않는 말이지만, 솔직히 말해 외부에 공개하는 문서는 겉모습도 중요합니다. 겉보기에 깔끔하고 아름다운 문서는 글을 읽기 전부터 독자의 관심과 신뢰를 얻을 수 있기 때문입니다. 물론 내용도 제대로 돼 있어야 하지만, 애초에 겉모습이 기업 이미지에 맞지 않거나 아마추어 같으면 내용을 읽기도 전에 신뢰도가 하락할 것입니다. 이런 이유로 저는 외부 공개 문서를 작성할 때 자바독이나 독시젠(Doxygen), 스웨거 등이 제공하는 기본 출력물 사용을 권장하지 않습니다. 기본 출력물은 회사 브랜드 아이덴티티를 담기 어렵고, 그간 회사가 쌓아 온 신뢰감을 문서에까지 전달해주지 못하기 때문입니다. 개인적으로는, 몸담은 회사가 문서 엔지니어링에 대한 기술이나 관심이 없는 것으로 오해를 받을까 우려되기도 합니다. API 레퍼런스는 이를 사용하는 개발자가 가장 많이 보는 기술 문서이며, 문서 엔지니어링을 어떻게 하느냐에 따라 작성 시간 및 출력물 모양이 크게 달라집니다. 이런 이유로 전문 테크니컬 라이팅 팀을 보유한 회사라면 자체 API 문서화 솔루션을 가져야 한다고 생각합니다. 완전히 새로운 도구를 만들어야 한다는 것이 아니라, 이미 있는 도구를 사용하더라도 트렌드 변화에 맞춰 정확하고 전문적인 결과물을 내놓는 프로세스를 가지고 있어야 한다는 뜻입니다. 구글도 그렇고 Microsoft도 그렇습니다. LINE 역시 다르지 않습니다. 좋은 API 레퍼런스 솔루션 그럼 좋은 API 레퍼런스 솔루션이란 어떤 것일까요? 앞서 언급한 예시에서 두 가지 조건을 찾아볼 수 있습니다. 출력 형식이 바뀌어도 쉽게 적응할 수 있어야 한다. 작성하는 사람이 뭘 써야 하는지 쉽게 알 수 있어야 한다. Android 개발자 사이트는 여러 차례 리뉴얼됐으나, API 레퍼런스는 겉모양만 바뀌었을 뿐 소스코드에 자바독으로 설명을 작성하는 방식은 바뀌지 않았습니다. 소스코드에 작성한 내용을 웹페이지로 변환할 때 구글이 사용한 도구가 웹사이트의 변화에 쉽게 적응할 수 있다는 뜻입니다. Android 예에서 보듯 API 레퍼런스가 공개될 웹사이트의 형태는 언제든지 바뀔 수 있습니다. 종종 웹사이트가 아닌 PDF 같은 파일이 최종 배포 형태가 되기도 합니다. '손목시계로도 인터넷이 되는 시대에 PDF라니'하고 어리둥절하는 사람도 있겠지만, 현장에서는 아직도 PDF가 많이 쓰입니다. 불특정 다수가 아닌 특정 그룹에만 배포하거나, 공식 웹사이트 공개 전에 배포하는 문서가 그 예입니다. 물론 나중에는 그 문서를 웹사이트에서 배포하게 될 가능성이 높습니다. 처음에는 문서를 PDF로 배포하다가 몇 달 뒤 웹사이트에 공개하는 문서화 프로젝트를 저도 몇 번 경험했습니다. 따라서 새로 시작하는 프로젝트에서 매니저가 API 레퍼런스는 PDF로 배포하겠다고 선언하더라도, 테크니컬 라이터는 웹 배포까지 염두에 두는 것이 좋습니다. 변화하는 출력물에 빠르게 대응하기 위해서는 데이터(설명)와 뷰(출력 형태)를 분리하는 것이 기본입니다. 데이터는 인라인 포매팅 외 출력물에 관한 어떤 정보도 포함하지 않아야 하며, 뷰는 데이터에 담긴 정보를 표출하되 형태를 쉽게 변경할 수 있어야 합니다. 이렇게 데이터를 분리해낸 후 어디에 무엇을 써야 하는지 명시적으로 나타내는 구조로 기술하면, 좋은 API 문서화 솔루션의 두 번째 조건을 달성할 수 있습니다. 자바독, JSDoc, OAS와 유사한 YAML 형식 모두 데이터와 뷰를 분리하고 명시적인 구조로 데이터를 작성하게 했습니다. 앞에서 본 예시처럼, 셋 모두 텍스트 기반으로 API 설명을 작성한 다음, 그 데이터에 원하는 뷰를 적용해 출력물을 생성합니다. 앞서 언급한 도구가 지원하지 않는 것, 예를 들어 C++로 작성한 API나 gRPC API는 레퍼런스 문서를 어떻게 만들어야 할까요? 솔직히 말하면, 둘 다 서드파티 API 레퍼런스 작성 도구가 있으니 큰 문제가 되지는 않습니다. 하지만 좀 더 나아가서, 하나의 프로젝트에서 다양한 프로그래밍 언어나 다양한 API 유형을 제공한다고 생각해봅시다. 프로그래밍 언어별로 각각 다른 도구를 사용한다면 프로젝트 전체 API 레퍼런스는 제각각 다른 형태가 될 수밖에 없습니다. 이것까지 고려한다면, 좋은 API 문서화 솔루션 조건을 하나 더 추가해야 합니다. 다양한 프로그래밍 언어와 API 유형을 통합할 수 있어야 한다. API 레퍼런스 솔루션 유스케이스 좋은 API 문서화 솔루션을 위한 세 가지 조건을 달성하기 위해 <그림 8>과 같은 API 문서화 솔루션을 구상해보았습니다. <그림 8> 좋은 API 레퍼런스 솔루션 조건을 달성하기 위한 API 문서화 작업 흐름 <그림 8>에서 보는 것처럼 소스코드 주석을 이용한 문서화 도구가 있다면 재활용하고, 그런 도구가 없으면 텍스트 기반으로 자체 기술 포맷을 만듭니다. 구조화 데이터용으로 쓰는 XML, JSON, YAML 등의 형식을 사용하는 게 좋겠습니다. 소스코드와 별도 포맷으로 작성한 데이터는 출력 형식을 정의한 템플릿을 적용해 최종 출력물로 변환됩니다. 사용하는 템플릿 언어는 각자 편리한 것을 선택합시다. 저는 이 솔루션을 ‘API 레퍼런스 템플릿’이라고 부르기로 했습니다. 제가 진행하는 프로젝트는 모두 API 레퍼런스 템플릿을 적용했습니다. 안타깝게도 아직 공개하지 않은 프로젝트이기에, 이 글에서는 가상의 데이터를 이용해 API 레퍼런스 템플릿이 무엇인지 소개하려 합니다. 가상 프로젝트는 C++ 기반의 라이브러리 API와 REST API를 제공합니다. 테크니컬 라이터 손에 들어왔을 때 라이브러리 API 설명은 소스코드 주석에, REST API 설명은 사내 협업 도구를 이용한 웹 게시물에 기술돼 있었습니다. 아래 코드는 가상 프로젝트의 C++ 라이브러리 API 설명입니다. namespace mine { /** * 내가 만든 완벽한 C++ 클래스. * 생성 후에는 반드시 `CallMe()`를 호출해야 합니다. */ class MyClass { public: /** * 용건 있을 때 전화해달라는 함수. */ void CallMe(void); /** * 내 질문에 대답하게 하는 함수. * @question 내가 한 질문. null이면 반드시 false를 반환합니다. * @return 질문에 대한 응답. 응답이 '그렇다'면 true, '아니다'면 false를 반환합니다. */ bool AnswerMe(char *question); …(중략) }; } <그림 9> 가상 프로젝트의 REST API 설명 제가 할 일은 두 종류 API 레퍼런스를 통합해 같은 형식으로 웹사이트에 게시하는 것입니다. 우선 현 상태를 분석해서 어떤 도구를 사용할지 생각해봅시다. 라이브러리 API는 소스코드에 설명을 작성했으니 데이터를 뷰에서 분리한 상태입니다. REST API는 사내 게시판에 썼으니 데이터와 뷰가 혼재돼 있습니다. 따라서, 먼저 라이브러리 API 레퍼런스 데이터를 출력할 뷰 템플릿을 만들고, 그 다음 REST API 문서에서 데이터를 추출한 다음 또 뷰 템플릿을 만들고, 마지막으로 두 종류 API의 뷰를 문서 하나로 통합해야 합니다. <표 1> API 레퍼런스 템플릿을 적용할 가상 프로젝트 상태 항목 라이브러리 API REST API 원본 소스코드 주석 사내 게시판 데이터-뷰 분리 분리 미분리 적용 가능한 기존 도구 독시젠 사용 가능 없음 <표 1>에서 보는 것처럼, 언어 독립적 문서화 도구인 독시젠(doxygen)을 사용해서 C++ 소스코드에 작성한 주석으로 API 레퍼런스를 만들 수 있습니다. 독시젠으로 HTML 파일을 생성할 수도 있지만, 커스터마이징이 까다로운 데다 우리 목적이 개별 HTML이 아닌 통합 HTML을 제공하는 것이므로, 직접 HTML을 생성하는 대신 통합하기 좋은 포맷으로 만들 것입니다. 사내 게시물로 작성한 REST API 설명은 변경이 잦을 땐 업데이트가 무척 불편하므로, 과감하게 이 방식을 포기하고 수동으로 데이터를 추출해 YAML로 재작성할 것입니다. 반드시 사내 게시물로 공유해야 한다면, 이 YAML 파일을 공유할 수 있습니다. 그 후 각각 데이터를 하나의 문서로 만들려면 언급한 것처럼 HTML이 아닌 통합이 쉬운 포맷을 사용해야 합니다. 여기서는 그 중간 출력물 포맷으로 마크다운(markdown)을 선택했습니다. C++ 라이브러리 API 레퍼런스 작업 우선 소스코드에 있는 라이브러리 API 주석을 마크다운으로 변환합시다. 독시젠은 마크다운 출력을 지원하지 않지만, 자체 정의한 XML 형식으로 API 명세서를 만들 수 있습니다. 이 명세서에는 소스코드가 빠진 순수 API 설명만 담깁니다. <그림 10> 독시젠 실행 화면 <그림 10>처럼 독시젠의 옵션에서 XML을 출력하도록 설정하고 빌드합니다. 이렇게 생성한 XML 데이터를 다양한 출력물로 만들어주는 도구가 Moxygen입니다. 이를 이용해 마크다운으로 변환합니다. 아래 코드는 C++ 라이브러리 API 주석 데이터를 마크다운으로 변환한 결과(중간 출력물)입니다. # class `mine::MyClass` {#classmine_1_1MyClass} 내가 만든 완벽한 C++ 클래스. 생성 후에는 반드시 [`CallMe()`](api-CallMe.md#classmine_1_1MyClass_1a20213a9d29c24324d6ea32a0010d3e9f)를 호출해야 합니다. ## Summary Members | Descriptions --------------------------------|--------------------------------------------- `public void `[`CallMe`](#classMyClass_1adc72431f56d6803585f60cef6e467991)`(void)` | 용건 있을 때 전화해달라는 함수. `public bool `[`AnswerMe`](#classMyClass_1a98aecda3ae776dd1f99aad5f9990ff62)`(char * question)` | 내 질문에 대답하게 하는 함수. ...(중략) Moxygen도 뷰 템플릿을 커스터마이징할 수 있으므로, 필요에 따라 출력물을 수정할 수 있습니다. 하지만 가상 프로젝트에서는 기본 템플릿을 사용하고, 다음에 처리할 REST API 레퍼런스를 이와 비슷한 형태가 되도록 작성할 것입니다. REST API 레퍼런스 작업 REST API 설명은 아래 코드처럼 YAML 포맷에 재작성합니다. 앞 예시에서 본 애저 기술 문서의 방법과 유사합니다. summary: 사용자 정보 조회 path: /user/{user_id} method: GET description: |- 주어진 아이디(user_id)의 사용자 정보를 조회합니다. 요청이 성공적으로 서버에 전달되면 200 OK를 반환하며, 요청 처리 중 발생한 오류는 응답 객체의 `error` 필드에 나타납니다. parameters: - name: user_id in: path description: |- 조회할 사용자 ID. 사용자 ID는 [사용자 목록](/apis/user-list)에서 확인할 수 있습니다. required: true response: header: description: |- HTTP Result Code가 200 OK일 때 반환하는 정보입니다. [공용 응답 필드](/apis/response-common-fields)를 참고하십시오. body_segments: id: description: |- 파라미터로 수신한 `user_id` required: true type: string name: description: |- 사용자의 이름 required: true type: string image: description: |- 사용자의 사진이 저장된 URL required: false type: object ...(중략) 척 봐도 OAS와 상당히 유사합니다. 다만, OAS는 모든 API를 한꺼번에 기술하고 응답 코드에 따라 좀 더 다양한 응답 설명이 있는데 반해, 여기서는 한 명세서에 한 API만 기술하고 응답도 ‘200 OK’로 한정했습니다. 물론, 각 프로젝트 특성에 따라 형식을 변경해도 상관없습니다. 이렇게 텍스트로 API 설명을 기술하면 소스코드와 함께 깃(Git) 저장소에서 버전을 관리할 수 있다는 장점이 있습니다. 또, API를 추가하거나 수정할 때도 이 형식에 맞춰 작성하게 되므로, 사내 게시물로 작성할 때보다 고정적인 데이터 구조를 유지할 수 있습니다. 즉, 누군가 정해진 형식에서 벗어나 임의로 테이블이나 그림을 입력할 수 없다는 말입니다. 이제 이 YAML 데이터를 마크다운으로 변환하는 작업이 필요합니다. 마크다운은 뷰를 포함한 문서이므로 레이아웃을 결정해야 합니다. 앞서 말한 대로 C++ 라이브러리 API 레퍼런스의 중간 출력물과 유사하게 만들 것입니다. 템플릿 언어는 핸들바(Handlebars)를 사용합니다. 아래 코드는 REST API의 YAML 데이터를 마크다운으로 변환할 템플릿입니다. ...(중략) ## Summary Members | Descriptions ---------------------------|--------------------------------------------- {{#each paths}}{{toUpperCase method}} {{path}} | {{summary}} {{/each}} {{#each paths}} ## {{#if summary}}{{summary}}{{/if}} `{{toUpperCase method}} {{path}}` {{#if operationId}}{{addId operationId}}{{/if}} {{description}} {{! parameters }} ### Request parameters {{#if parameters}} Parameter | Type | Description :--------------:|-------------|------------------------------------------ {{#each parameters}} `{{name}}`<br/>({{in}}) | {{schema.type}} {{#if schema.items}} of {{schema.items.type}}{{/if}} | {{description}} {{#ifCond required "true"}}O{{/ifCond}} {{/each}} {{else}}None{{/if}} ...(중략) YAML 파일을 읽고 이 템플릿 파일을 적용하면, 아래 코드와 같이 C++ 라이브러리 API 레퍼런스와 유사한 중간 출력물을 얻을 수 있습니다. ## Summary Members | Descriptions ---------------------------|--------------------------------------------- GET /user/{user_id} | 사용자 정보 조회 ## 사용자 정보 조회 `GET /user/{user_id}` 주어진 아이디(user_id)의 사용자 정보를 조회합니다. 요청이 성공적으로 서버에 전달되면 200 OK를 반환하며, 요청 처리 중 발생한 오류는 응답 객체의 `error` 필드에 나타납니다. ### Request parameters Parameter | Type | Description :----------------:|-------------|--------------------------------------------- `user_id`<br/>(path) | | 조회할 사용자 ID. 사용자 ID는 [사용자 목록](/apis/user-list)에서 확인할 수 있습니다. ...(중략) 자체 API 템플릿을 구현하는 방법은 주제에서 벗어나기 때문에 상세히 설명하지 않고 깃허브 저장소의 설명과 코드로 대신합니다. 레퍼런스 통합 드디어 포맷은 같고 모양은 유사한 뷰로 두 종류의 API 레퍼런스를 출력해냈습니다. 이제 이들을 공식 웹사이트에 집어넣거나 지킬(Jekyll) 같은 정적 사이트 생성 도구를 이용해 자체 웹사이트를 서비스하면 됩니다. <그림 11> 화면은 포맷 변환 도구인 팬독(Pandoc)과 그 HTML 템플릿 중 하나를 이용해 통합 API 레퍼런스 웹페이지를 만들어본 것입니다. <그림 11> 통합 API 레퍼런스 웹페이지 중간 출력물로 마크다운을 사용하면, HTML뿐 아니라 좀 더 다양한 파일 포맷으로 변환할 수 있습니다. <그림 12>는 그중 하나인 PDF 파일입니다. <그림 12> 통합 API 레퍼런스 PDF <그림 12>처럼 유려한 PDF를 만들려면 레이텍(LaTeX)을 사용해야 합니다. PDF 출력 방법에 관해서는 여기서 다루지 않으므로, 관심이 있으면 레이텍을 살펴보시기 바랍니다. 서로 다른 API 레퍼런스를 통합해야 할 필요가 없더라도, 다양한 파일 포맷을 지원하기 위해 마크다운을 중간 출력물로 사용하는 것도 좋은 방법입니다. 앞서 말한 것처럼 PDF 배포 후 웹사이트 게시로 진행되는 문서에 이 방법을 사용하면 웹사이트가 언제 공개돼도 즉시 대응할 수 있습니다. 끝맺기 API 레퍼런스를 작성할 때 중요한 요소를 소개하고, 이를 만족할 수 있는 API 레퍼런스 솔루션을 살펴보았습니다. 일견 복잡해 보일 수 있는 방법이지만, 이 모든 것을 스크립트를 통해 단번에 수행할 수 있습니다. 이렇게 프로세스화 된 문서 작업에서는 젠킨스(Jenkins) 같은 지속적 통합 도구를 이용하면 데이터(소스코드 혹은 YAML 파일) 변경 시 자동으로 최종 출력물을 만들 수 있으므로, 초기에 한 번 설정해 주면 손이 갈 일이 많지 않습니다. 독시젠 같이 널리 쓰이는 API 레퍼런스 생성 도구도 그 속을 뜯어보면 소스코드 주석에서 데이터를 추출하고 사용자가 변경할 수 있는 뷰에 데이터를 입혀 출력물을 커스터마이징 할 수 있게 해 놨습니다. 모든 작업이 가려져 있기에 복잡하게 보이지 않을 뿐, 결과적으로 이 글에서 소개한 방법과 크게 다르지 않습니다. 그래도 여전히 복잡해 보인다면, 프로젝트 특성에 따라 중간 출력물 생성 단계나 YAML API 명세서 작성 단계를 생략해도 좋습니다. 이 글의 목적은 쓸데없이 복잡한 프로세스를 적용하라고 권유하기 위함이 아닙니다. API 레퍼런스를 좀 더 쉽게 작성하고 관리하려면 이런 종류의 솔루션을 적용하면 좋겠다는 것이 말하고자 하는 핵심입니다. 이 글의 독자들은 앞으로 API 레퍼런스 생성 도구를 선정할 때 다음 두 가지를 꼭 고려하기 바랍니다. 데이터와 뷰를 분리했는가? 소스코드와 함께 버전 관리할 수 있는가? 첫 번째에 관해서는 본론에서 길게 설명했으니 더 말하지 않아도 될 것 같습니다. 두 번째는 이 글에 자세히 언급하지는 못했지만, 텍스트 기반 API 기술 방식을 적용하면 쉽게 달성할 수 있습니다. 이 글에서 소개한 API 레퍼런스 템플릿 중 YAML로 기술한 API 명세서는 두 가지 고려사항을 만족하는 도구 중 하나입니다. 소스코드 주석기반 문서화 도구가 없는 프로그래밍 언어를 사용하거나, 소스코드가 아직 없거나, 소스코드 수정을 극도로 꺼리는 프로젝트에 알맞은 방법입니다. 좀 더 전문화된 통합 API 레퍼런스 생성 도구가 발표되기 전까지, 앞서 언급한 문제로 인해 API 레퍼런스 작성에 애먹고 있는 사람들이 있다면 쓸만한 해결책이 될 것으로 믿습니다. 참고자료 idratherbewriting.com/2012/03/02/technical-communication-metrics-what-should-you-track sigdoc.acm.org/cdq/how-developers-use-api-documentation-an-observation-study YAML은 XML, C, 파이썬, 펄, RFC2822에서 정의된 이메일 양식에서 개념을 얻어 만든, '사람이 쉽게 읽을 수 있는' 데이터 직렬화 양식입니다.

  • AI RUSH 2019를 개최합니다

    유니티 자격요건에 본인이 왜 충족되었는지 설명

    박민우2019-06-26

    Developer Relations 팀에서 에반젤리스트로서 일하며 LINE의 멋진 기술을 사내외에 알리고 있습니다.

    'AI RUSH 2019' 소개 안녕하세요. LINE Developer Relations 팀 박민우입니다. LINE과 NAVER가 협력하여 AI 기술에 관심이 있는 분을 대상으로 한 개발자 행사, 'AI RUSH 2019'를 개최합니다(1등 우승 상금 미화 10,000 달러!). 오늘 공식 사이트를 열고 참가 등록 접수를 시작했습니다.  참고. 'AI RUSH 2019'는 전 세계에서 참가자를 모집하기 때문에 공식 사이트를 영어로 운영하고 있으며 질문과 답변도 영어로 진행하고 있습니다. 이번 행사는 단순한 해커톤 행사를 넘어 LINE과 NAVER가 AI 기술 리더로서 국내 AI 개발자와 글로벌 톱 엔지니어 간의 교류의 자리를 만든다는 데 의미가 있습니다. 'AI RUSH 2019'에는 개인으로도 참가할 수 있고 최대 3명으로 구성된 팀으로도 참가할 수 있습니다. 참가자들은 AI 기술과 관련된 과제를 해결하면서 최적의 결과를 내기 위해 서로 경쟁하게 됩니다. 예선 기간은 7월 29일부터 8월 8일까지입니다. 예선은 온라인으로 진행되며 최대 100팀까지 참가 신청을 받을 예정입니다(참가 신청한 팀이 100팀을 초과하면 주최 측 추첨으로 예선에 참가할 팀을 결정합니다). 예선에서 높은 점수를 획득한 상위 30개 팀이 본선에 진출하는데요. 본선에 진출한 참가자는 8월 14일부터 26일까지 온라인 본선을 치루고, 이후 8월 27일부터 29일까지 춘천에 있는 'NAVER Connect One'에 모여서 해커톤 형식으로 진행되는 오프라인 본선을 치루게 됩니다. 'AI RUSH 2019' 출제 과제 예선과 본선에 출제되는 과제의 주제는 대략 다음과 같습니다. 구체적인 과제의 내용과 사용할 데이터 세트는 예선과 본선이 시작되면 각 참가자들에게만 공개할 예정입니다. 예선 : Image classification 본선 : Click-through rate (CTR) prediction 참가자들은 예선과 본선에서 NAVER가 제공하는 기계학습 플랫폼인 'NSML(NAVER Smart Machine Learning)'을 이용하게 됩니다. 또한 'AI RUSH 2019'의 예선과 본선 기간 동안 각 팀의 순위는 참가자 전원이 실시간으로 확인할 수 있도록 공개할 예정입니다.  'AI RUSH 2019' 일정 참가 신청 기간 6월 25일(화) ~ 7월 22일(월) 15:00 (KST) 온라인 예선 기간 7월 29일(월) ~ 8월 8일(목) 예선 결과 발표 8월 8일(목) 본선 온라인 개발 기간 8월 14일(수) ~ 8월 26일 월) 본선 오프라인 개발 기간 8월 27일(화) ~ 8월 29일(목) 맺으며 이번 행사는 LINE이 처음으로 주최하는 AI 관련 글로벌 개발 행사인데요. 전 세계의 AI 관련 개발자와 연구자 커뮤니티를 활성화하고 AI 관련 기술 발전에 기여하는 게 이번 행사의 목표입니다. 많은 개발자가 'AI RUSH 2019'에 참가해 과제 해결 과정을 즐기면서 서로 교류할 수 있기를 기대합니다. 많은 관심과 지원 부탁드립니다(공식 홈페이지: https://ai-rush.com/).

  • 게임 보안 운영 관점에서 바라본 게임 치트 방지 모니터링

    유니티 자격요건에 본인이 왜 충족되었는지 설명

    이명재2019-06-19

    LINE에서 보안 업무를 맡고 있습니다. 

    안녕하세요. LINE Game Security 팀에서 LINE GAME의 보안 운영을 담당하고 있는 이명재입니다. LINE GAME이 탄생한 지 벌써 6년이 지났습니다. 지난 6년 간의 경험을 토대로 '게임 보안 운영 관점에서 바라본 게임 치트(cheat) 방지 모니터링'에 대하여 여러분께 소개하려고 합니다. 게임 치트란, 악의적인 유저(이하, 어뷰저(abuser)로 지칭)가 앱을 조작하거나 데이터를 조작하는 등 비정상적인 방법으로 게임이 본인에게 유리하게 진행되도록 만드는 행위라고 정의할 수 있습니다.  LINE에서는 LINE GAME 사용자 여러분이 안심하고 게임을 즐길 수 있도록 게임 앱을 릴리스한 뒤 다양한 방법으로 게임 내 어뷰저를 분석하고 이에 대응하고 있습니다. 최근에는 모바일 환경에서 작동하는 게임 앱이 인기를 누리고 있고, LINE이 릴리스하는 게임도 대부분 iOS나 Android 기반의 모바일 게임 앱입니다. 따라서 이번 글은 모바일 게임 앱을 주 대상으로 삼고 있습니다. 또한, 치트 방지를 위한 앱 자체의 대비보다는, 릴리스 후 모니터링 관점에서의 대책에 초점을 맞춘 이야기로 생각하고 읽어주시면 좋겠습니다 LINE GAME의 특징 게임 치트에 대해서 말씀드리기 전에, 먼저 LINE GAME의 특징을 소개하겠습니다. 첫 번째 특징은 LINE 로그인 연동입니다. 게임에서 LINE 로그인을 하면 LINE 친구와 랭킹을 겨루거나, 게임 진행에 필요한 하트를 서로 보내는 등 게임을 좀 더 다양한 방식으로 즐길 수 있습니다. 또한 휴대폰을 교체하거나 통신사를 변경한 후에도 기존 LINE 계정에 연결하여 게임을 이어서 진행할 수 있습니다. 두 번째 특징은 게임 내 통화가 2종류 존재한다는 점입니다. 과금해서 구입하는 통화를 1차 통화라고 하고, 1차 통화로 교환할 수 있는 통화를 2차 통화라고 합니다. 예를 들어 최근 릴리스한 LINE CHEF에선 다이아몬드가 1차 통화고, 코인이 2차 통화라고 생각하시면 됩니다. 1차 통화와 2차 통화는 게임 내에서 확률형 아이템이나 기타 다른 아이템을 구입하는데 사용할 수 있습니다. 세 번째 특징은 게임 내에서 통화 및 아이템을 다른 사용자에게 주거나 거래할 수 없다는 점입니다. 본인이 획득한 게임 내 통화와 아이템은 본인만 사용할 수 있습니다. 게임 치트의 종류 게임에서의 치트가 최근에 등장한 것은 아닙니다. 오래 전부터 PC 게임이나 콘솔 게임에서도 게임 치트는 존재해 왔고, 인터넷과 휴대폰이 널리 보급되어 온라인과 모바일 상에서 게임을 즐기는 지금까지 게임과 치트는 공존하고 있습니다. 지난 6년 간 게임 보안 운영 관점에서 바라본 치트의 종류는 아래와 같이 크게 3가지로 나눌 수 있습니다. 메모리 조작 메모리 상에서 값을 바꾸는 치트입니다. 예를 들어 메모리에서 게임 내 통화 및 아이템의 수를 검색해서 변경하는 방법입니다. 최근에는 메모리 검색과 변경을 손쉽게 할 수 있도록 도와주는 치트 도구가 인터넷에 많이 공개되어 있어서 누구나 입수해 시도할 수 있습니다. 이 글에서 치트 도구의 이름이나 사용 방법에 대해선 언급하지 않겠습니다. 도구 사용법 또한 인터넷에서 쉽게 찾을 수 있기 때문에 전문지식이 없어도 메모리 치트에 도전하는 어뷰저가 많을 겁니다. 아래 그림은 메모리에서 Coin의 수량을 변경한 메모리 치트의 예시입니다. 메모리 치트 방지 대책으로는 게임 내 통화나 아이템 수 등의 중요 변수를 암호화하는 방법이 있습니다. 패킷 조작 게임에서 통신할 때 사용하는 패킷을 조작하는 치트입니다. 예를 들어 게임 플레이 결과의 패킷을 분석, 조작하여 게임 내 통화나 아이템의 수를 변경합니다. 많은 네이티브(native)게임1은 클라이언트 쪽에서 게임을 플레이하고 그 결과를 서버로 전송하는 방식입니다. 이때 전송되는 플레이 결과의 패킷을 분석하고 조작하려면 경우에 따라서 전문 지식이 필요할 수도 있는데요. 그럼에도 빈번히 발견되는 치트 방식입니다. 아래 그림은 플레이 결과 패킷을 조작하여 실제 결과와는 다른 결과를 서버로 전송한 예시입니다.  패킷 조작의 대책으로는, 게임 서버 측에서 게임 플레이 결과(파라미터)가 타당한지 체크하는 것입니다. 또한 플레이 1번으로 얻을 수 있는 통화나 아이템의 수 등을 제한하면 혹시 치트가 발생하더라도 그로 인한 피해를 최소화할 수 있는 효과를 얻을 수 있습니다. 바이너리 파일 조작 게임 클라이언트 측의 바이너리 파일을 조작하는 치트입니다. 예를 들어, Android에선 .apk파일을, iOS에선 .ipa파일을 조작합니다. 바이너리 파일의 내부에는 게임 연산에 사용되는 변수와 로직이 많이 존재합니다. 이런 변수나 로직을 조작하여 게임 내 통화 및 아이템의 수를 증가시키거나, 아군을 강하게 만들기도 하고, 적군을 약하게 만들기도 합니다. 바이너리 파일은 각 모바일 환경에 맞게 실행 파일의 형태로 변환되어 있기 때문에 아무나 쉽게 조작할 수 없습니다. 따라서 주로 전문지식을 갖고 있는 사람이 일명 리버스 엔지니어링 방법으로 진행하는 경우가 많습니다.2 아래 그림은 바이너리 파일을 조작하여 exp 값을 변경한 예시입니다. 바이너리 파일 조작은 방지하는 게 굉장히 어렵습니다. 단말기에 설치한 게임 앱은 사용자가 추출하는 게 가능하여 사용자가 마음만 먹으면 아무때나 클라이언트 모듈을 추출하여 손댈 수 있기 때문입니다.  바이너리 파일 조작을 방지하기 위한 대책으로는 리버스 엔지니어링 분석을 어렵게 만드는 난독화와 같은 방법이 있습니다. 하지만 이런 대책이 완벽하다고 생각하지는 않습니다. 전문 지식을 갖춘 사람이 시간과 노력을 들이면 언젠가는 클라이언트 모듈을 파헤칠 수 있다고 생각하기 때문입니다. 물론 그럼에도 난독화를 적용하면 조작에 대한 내성을 키울 수 있어서 적용하는 걸 추천합니다. 다만, 난독화는 난독화 방법이나 난독화 수행에 사용한 제품에 따라서 파일의 크기나 앱 기동 시간이 늘어나는 경우도 있어서 고려할 점이 많은데요. 이 글에선 난독화 적용 방법에 대한 자세한 설명은 생략하겠습니다. 이상 3가지 종류의 치트 방식에 대해서 설명했는데요. 대부분 클라이언트 측을 해킹하는 방식입니다. 클라이언트 측 해킹 방지는 매일 치트 방식의 동향을 관찰하며 클라이언트 모듈을 개선해 나가는 일련의 노력이라고 할 수 있습니다. 경우에 따라서는 비슷한 수정이 계속 되풀이되는 경우도 있습니다만 그러한 노력이 절대 헛되진 않다고 생각합니다. 치트를 방지하기 위한 모니터링 앞서 설명드린 것처럼 LINE에서는 클라이언트 측 해킹을 방지하기 위해 꾸준히 노력하고 있는데요. 이렇게 클라이어트 측 해킹을 방지하는 것만큼이나 어뷰저를 모니터링할 수 있는 체계를 만드는 것도 치트로 인한 피해를 방지하는데 중요하다고 LINE에선 생각하고 있습니다. 모바일 환경의 네이티브 게임에선 게임에서 칼을 휘두르거나 피하는 등의 움직임을 사용자가 정교하게 조작할 수 있도록 관련 기능을 클라이언트 모듈에 넣는 경우가 많은데요. 앞서 말씀드렸듯 이 클라이언트 모듈은 언제라도 사용자가 파헤쳐서 살펴볼 수 있는 부분이기 때문에 어뷰저가 가장 먼저 치트 대상으로 삼는 부분이고 언젠가는 파헤쳐질 수도 있는 부분입니다. 따라서 클라이언트 측 해킹 방지 대책의 다음 단계로 치트 행위를 측정할 수 있는 지표를 모니터링하여 발견된 어뷰저에게 사후 대응3하는 관점이 등장하였습니다. LINE에서는 치트 방지 모니터링의 기본 관점을 크게 3가지로 나누고 있는데요. 첫 번째는 '게임 클라이언트 측에서 악의적인 행위가 없었는지' 확인하는 것이고, 두 번째는 '게임 서버 측에 치트 행위의 결과로 발생한 부정한 데이터가 없는지' 확인하는 것입니다(이런 모니터링이 가능하려면 클라이언트 측에 전용 모듈을 탑재해야 하는데 자세한 설명은 이 글에서는 생략합니다). 마지막으로 세 번째는 '환불 처리를 악용하는 행위와 같은 기타 다른 악의적인 행위가 없었는지' 확인하는 것입니다. 게임 클라이언트 측의 악의적인 행위 모니터링 게임 클라이언트 측의 악의적인 행위를 모니터링할 때는 다음과 같은 지표를 사용하고 있습니다. 실제 단말기 이외의 환경(rooting, jailbreak, emulator)에서 게임 앱을 설치, 실행한 사용자가 얼마나 존재하는가 치트 도구를 설치, 실행한 사용자가 얼마나 존재하는가 정상과는 다른 해시값을 갖는 바이너리 파일을 설치, 실행한 사용자는 얼마나 존재하는가 하나의 단말기에서 복수의 계정으로 로그인한 기록이 있는가 예를 들어 rooting이나 jailbreak 환경에서 치트 도구를 설치하고 실행한 사용자는 메모리 조작 종류의 치트를 시험해 봤을 가능성이 있습니다. 따라서 모니터링 중 이 지표값이 갑자기 증가한다면 메모리를 조작하는 어뷰저가 존재한다고 추측할 수 있습니다. 또한 emulator 환경에서 앱 설치를 반복하는 사용자는 이른바 리세마라4라고 부르는 치트 방법을 시험해 봤을 가능성이 있습니다. 모니터링 중 이 지표값이 갑자기 증가한다면 계정 매매와 같은 행위가 발생했을 가능성이 있다고 추측할 수 있습니다(반드시 그런 것은 아닙니다). 하나의 단말기에서 복수 계정의 로그인이 발생했다면 치트를 대행해주는 사람이 존재한다고 추측할 수 있습니다. 아래는 누군가 rooting, jailbreak, emulator 환경을 사용하거나 치트 도구를 사용했는지 탐지한 데이터를 표시하는 모니터링 화면입니다. 이런 지표를 모니터링하여 어뷰저의 존재 여부를 추측할 수 있습니다. 또한 정상과는 다른 해시값을 갖는 바이너리 파일을 설치하고 실행한 사용자는 바이너리 파일을 조작하는 치트를 시험해 봤을 가능성이 있기 때문에 모니터링 중 갑자기 이 지표값이 증가한다면 관련 어뷰저가 존재한다고 추측할 수 있습니다. 아래는 정상 바이너리 파일과 다른 해시값을 갖는 파일의 일부를 모니터링한 화면입니다. 이런 경우엔 조작된 바이너리 파일이 많은 사용자에게 배포되어 사용되었을 가능성도 있습니다. 현재 조작 바이너리 파일을 입수하면 누가 언제 사용했는지 모니터링할 수 있도록 조치하고 있습니다. 아래는 정상 바이너리 파일과 다른 해시값을 갖는 Assembly-Csharp.dll 파일 탐지 수(왼쪽)와 해시값 별 개수(오른쪽)를 그래프로 나타낸 모니터링 화면입니다. LINE에서는 위와 같은 지표를 매일 모니터링하며 게임 클라이언트 측에서 악의적인 행위가 얼마나 존재하는지 파악하고, 이를 통해 어뷰저가 게임에 영향을 미치지 못하도록 리스크를 관리하고 있습니다. 아래는 모니터링 도구의 대시보드 화면입니다. 게임 서버 측 비정상 데이터 모니터링 게임 서버 측에 비정상적인 데이터가 발생했는지 모니터링할 때는 다음과 같은 지표를 사용하고 있습니다. 한 번의 플레이로 게임 내 통화를 기대 이상으로 많이 획득한 사용자가 얼마나 존재하는가 한 번의 플레이로 기대 이상으로 높은 점수를 기록한 사용자가 얼마나 존재하는가 굉장히 짧은 시간 내에 게임을 클리어한 사용자가 얼마나 존재하는가 게임 클리어(WIN) 로그는 있지만 그에 상응하는 플레이 상세 로그가 없는 사용자가 얼마나 존재하는가 예를 들어 한 번의 플레이에서 게임 내 통화를 비정상적으로 많이 획득하거나 비정상적으로 높은 점수를 기록한 사용자는 메모리 조작이나 패킷 조작 치트를 시험해 봤을 가능성이 있습니다. 따라서 모니터링 중 이 지표값이 갑자기 증가한다면 메모리 조작이나 패킷 조작 치트를 사용한 어뷰저가 존재한다고 추측할 수 있습니다. 아래는 획득한 통화가 마이너스 값인 사용자들을 추려낸 결과인데요. 메모리 조작 치트를 이용해 게임 내 통화를 저장하는 변수를 조작했는데, 변수 타입의 범위를 넘어서는 숫자로 잘못 조작하여 값이 마이너스가 되어버린 사례입니다. 만약 게임 내 통화를 저장하는 변수의 타입이 signed int라면 -2,147,483,648부터 2,147,483,647까지의 값을 저장할 수 있는데요. 이때 2,147,483,647를 넘는 값이 변수에 들어오면 integer overflow가 발생하여 마이너스 숫자로 값이 바뀌게 됩니다. 또한 게임의 클리어 시간이 기대 이상으로 짧거나, 짧은 시간동안 연속으로 플레이가 기록되는 사용자는 바이너리 파일 조작이나 패킷 조작 치트를 시험해 봤을 가능성이 있습니다. 모니터링 중 이 지표값이 갑자기 증가한다면 바이너리 파일 조작이나 패킷 조작 치트를 사용하는 어뷰저가 존재한다고 추측할 수 있습니다. 예를 들어 적 보스(boss)의 체력(HP)을 0으로 바꾸는 바이너리 파일 조작 치트를 사용하면 게임 클리어 시간이 비정상적으로 짧아집니다. 또한 게임을 플레이하는 과정 없이 결과 패킷만 보낼 수 있는 치트를 사용해서 결과 패킷을 연속으로 보내면 짧은 시간 동안 연속적인 플레이가 기록됩니다. 아래는 원래 플레이하는데 적어도 30초 이상 소요되는 게임에서 굉장히 짧은 시간인 5초(클라이언트 측에서 그래픽 묘사하는 시간을 포함하기 때문에 실제로 플레이 시간은 5초보다 짧음)의 플레이가 기록된 로그입니다. 바이너리 파일 조작 치트로 게임을 실제 플레이 없이 바로 클리어한 사례라고 할 수 있습니다. 플레이 시간이 굉장히 짧은 사용자를 모니터링한 결과 추가로, 정규로 앱을 설치하지 않아도 패킷만으로 플레이가 가능한 non-client 봇이나 웹사이트가 어딘가에 출현할 가능성도 있습니다. non-client 봇과 웹사이트에서는 게임에 필요한 패킷만을 보내기 때문에 정규 앱과는 달리 기록되는 로그가 적은 경우가 많습니다. 예를 들어 클리어(WIN) 로그는 있는데 그에 상응하는 플레이 상세 로그가 없는 경우를 생각할 수 있습니다. LINE에서는 위와 같은 지표를 매일 모니터링하면서 게임 서버 측에 부정한 데이터가 발생했는지 관측하여 어뷰저가 게임에 영향을 미치지 못하도록 관리하고 있습니다. 환불 처리를 악용하는 사용자 모니터링 게임 치트가 예상치 못한 곳에서 발생하는 경우도 있습니다. 대표적으로 Google IAP(In App Purchase)의 환불 정책을 악용하는 사례입니다. Android 앱에는 Google IAP를 이용한 결제 기능이 있습니다. 그리고 Google에서는 자사의 환불 정책에 근거하여 IAP로 구입한 일부 아이템에 대해선 환불 처리를 해주는데요. 이런 Google의 IAP 환불 정책을 악용하는 행위는 다음과 같은 과정으로 진행됩니다. 1. 어뷰저가 Google의 IAP로 게임 내 통화인 1차 통화를 구입2. 어뷰저가 1차 통화를 게임 내에서 사용(확률형 아이템을 구입하거나 게임 내 2차 통화로 교환)3. 어뷰저가 Google에 환불 요청4. Google은 자사의 환불 정책에 근거하여 환불 처리 만약 어뷰저가 위 과정을 거쳐 환불을 받았는데 게임 운영 측에서 이러한 사실을 파악하지 못한다면, 어뷰저는 자신이 구입한 1차 통화로 게임 내 아이템을 구입한 후 환불까지 받았기에 결과적으로 과금없이 아이템을 획득하게 됩니다. Google에서 제공하는 Voided Purchases API(사용자가 취소한 구입 내역과 관련된 앱 내 주문 리스트 제공)를 이용하면 이런 행위를 모니터링할 수 있습니다. LINE에서는 환불 정책을 악용한 사례가 발생했는지 모니터링하여 어뷰저가 게임에 영향을 미치지 못하도록 관리하고 있습니다.  마지막으로 이상으로 게임 치트 방지 대책 관점에서의 모니터링에 대해 몇 가지 예시와 함께 소개드렸습니다. 클라이언트 측에서 해킹을 방어하기 위한 노력을 '사전 대응'이라고 한다면, 치트 행위를 가늠할 수 있는 지표를 모니터링하고 발견된 어뷰저를 조치(치트로 획득한 통화와 아이템 회수, 플레이 정지 등)하는 것은 '사후 대응'이라고 할 수 있습니다. 사전 대응의 성격이 강한 클라이언트 측 해킹 방지는 매일 치트 수법의 동향을 관찰하고 이를 바탕으로 클라이언트 측을 개선해 나가는 일련의 노력이라고 할 수 있고, 확실하게 성과를 거두고 있습니다. 또한 사후 대응의 성격이 강한 '어뷰저 모니터링 및 조치'도 게임 치트를 방지하기 위해 빼놓을 수 없는 치트 대책이라고 할 수 있습니다. 예를 들어 누군가 악의적으로 바이너리 파일을 조작하여 아래와 같이 비정상적인 특징을 가진 조작된 파일(.ipa)을 인터넷에 배포한다면, 이런 파일을 악용하는 사용자가 급속히 많아질 가능성이 있습니다. 이런 사태는 당연히 게임에 악영향을 미칠 것입니다. 과금하지 않고도 게임을 유리하게 진행할 수 있음 Jailbreak 환경과 같은 특별한 환경이 아니더라도 동작 웹사이트에 접속해서 설치하면, .ipa 파일 설치에 필요한 사전 지식이나 직접 설치해야 하는 번거로움 없이 이용 가능 하지만 클라이언트 측 치트 행위를 측정할 수 있는 모니터링 체계가 미리 준비되어 있다면, 이런 조작된 파일이 인터넷을 통해 유통되더라도 게임에 영향을 미치지 못하도록 적절하게 대응할 수 있습니다. 이처럼 적절한 모니터링 체계는 게임 보안 운영 측면에서 꼭 필요합니다. LINE에선 모니터링을 포함한 여러 치트 방지 대책을 통해 악의적인 행위가 게임에 영향을 미치지 못하도록 철저히 관리하고 있습니다. 모바일 게임 중 브라우저와 같은 다른 앱을 경유하지 않고 직접 실행 가능한 형태의 게임을 의미합니다. 예를 들어 Unity로 개발된 앱은 Assembly-CSharp.dll 파일을 리버스 엔지니어링으로 분석하는 경우가 많이 있습니다. 이 글에선 치트에 대한 상세한 설명은 생략하겠습니다. 궁금하신 분은 아래 자료를 참고해 주시기 바랍니다. https://engineering.linecorp.com/ja/blog/unity-from-a-security-engineer-point-of-view/(일본어) https://www.slideshare.net/linecorp/cedec2017-line(일본어) 모니터링 결과 어뷰저라고 판단된 경우, 행해지는 조치를 의미합니다. 치트로 획득한 통화 및 아이템 회수, 게임 플레이 정지 등의 조치가 이뤄집니다. 리셋마라톤(reset marathon)을 생략해서 리세마라라고 부릅니다. 게임 앱의 설치와 삭제(uninstall)를 계속 반복하여 자신이 원하는 아이템을 입수하는 방법으로 알려져 있습니다.

  • 프론트엔드 개발자의 LINE 입사 후기

    유니티 자격요건에 본인이 왜 충족되었는지 설명

    송헌용2019-06-12

    자바스크립트로 웹앱을 개발하고 있습니다.

    안녕하세요! LINE의 UIT(User Interface Technology) 팀에서 프론트엔드 개발을 하는 송헌용입니다. 뒤돌아보니 LINE에 입사한 지 벌써 8개월이나 지났더라고요. 그래서 지난 8개월 동안 제가 LINE에서 어떻게 지냈는지 되돌아보는 시간을 가져보았습니다. 입사 후기 겸 회고록이라고 할까요. 저는 개발자로 일한 지 올해로 5년 차가 되었고, 그동안 주로 프론트엔드 개발을 담당했습니다. 예전에는 PC용 웹 애플리케이션을 많이 개발했는데요. LINE에서 일하면서 모바일 개발에 더욱 집중하게 되었습니다. 예전에도 나름대로 최적화에 신경쓴다고 생각하며 개발했지만, 이제와 돌이켜보니 부족한 부분이 있었던 것 같습니다. 한국은 인터넷 속도가 빨라서 네트워크 병목 현상으로 렌더링이 느려지는 일은 흔치 않기 때문에 자연스럽게 그런 측면에서의 최적화를 간과하며 개발했던 것 같습니다. 반면, LINE에서 제공하는 서비스는 사용자가 대부분 해외에 거주합니다. 따라서 개발하면서 여러 국가를 대상으로 다국어 설정, 국가별 화면 분기 등을 고려하는 것은 물론, 국가별 네트워크 속도, 모바일 기기에 표시할 콘텐츠의 양, 에러 핸들링 등 이전보다 훨씬 더 많은 측면을 고민하게 되었습니다. 이런 고민을 바탕으로 한국처럼 인터넷 속도가 빠르지 않은 다른 나라의 사용자들도 최대한 빠르고 편하게 LINE의 서비스를 이용할 수 있도록 최적화하고 있습니다. LINE SQUARE의 초기 로딩 속도 개선 먼저 가장 최근에 있었던 일화를 소개하는 걸로 시작해보겠습니다. 최근에 LINE SQUARE 서비스의 초기 로딩 속도를 높인 일이 있습니다. 당시 인도네시아에서 LINE SQUARE의 로딩 속도가 느리다는 의견이 나왔는데요. 문제를 해결하기 위해 테스트 기기들을 가지고 서버 개발자와 함께 현지로 출장을 갔습니다. 테스트 기기를 여러 개 가져가다 보니 공항에서 검사하는 사람이 저희에게 이상한 눈초리를 보냈던 건 웃지 못할 해프닝입니다. 인도네시아에 맞게 현지화된 코니 기기별로 네트워크 성능을 체크하고 초기 단계부터 시작해서 개선할 수 있을 것 같은 지점을 하나씩 분석한 결과, 개선 가능한 지점을 두 군데나 찾아서 개선할 수 있었는데요. 첫 번째로 적용한 개선 방법은 HTML 파일을 일정 시간 CDN(Contents Delivery Network)으로 캐시(cache)하도록 설정한 것입니다. 기존에는 초기 로딩할 때 화면에 빈 페이지가 오래 보이는 경향이 있었는데요. 이런 현상을 브라우저 개발자 도구로 분석해 보니 CSS와 자바스크립트 파일 요청과 응답이 네트워크의 한 지점에서 오래 걸리는 바람에 병목 현상이 발생하고 있었습니다. 이 문제는 HTML 파일을 캐시하여 해결할 수 있었는데요. 서버 개발자의 도움을 받아 함께 처리했습니다. 두 번째 개선 방법은 온로드(on load) 이벤트가 발생하기 전까지 렌더링을 위한 스크립트 동작 외의 나머지 불필요한 작업을 삭제한 것입니다.  출장 기간이 길지 않았기 때문에 현지에서는 자료 조사만 진행했고, 한국으로 돌아온 뒤 두 가지 개선 방법을 적용한 코드를 배포했습니다. 그 결과 인도네시아는 물론 다른 나라에서도 초기 로딩 성능이 기존 대비 최소 14%에서 최대 243%까지 향상되었습니다. 그 당시 한 땀 한 땀 테스트했던 결과 중 하나.jpg 해외 출장 및 근무에 결격사유 없는 자 처음 입사 지원할 때, 채용 공고의 자격 요건에서 '해외 출장 및 근무에 결격사유 없는 자'라는 문구를 보았습니다. 처음엔 다른 많은 회사처럼 그저 신체가 건강한지를 체크하기 위해 이런 문구를 자격요건에 넣은 거라고 생각했습니다. 관례상 쓰여 있는 문구 정도로 이해한 건데요. 입사 후 3개월 만에 해외로 출장을 간 뒤, 지금까지 약 8개월간 벌써 3번이나 해외 출장을 다녀왔습니다. LINE에선 '해외 출장 및 근무에 결격사유 없는 자'란 조건이 정말 꼭 필요한 자격요건이었습니다. 문제의 그 문구.png 더 넓은 영역에 손대다 이전에 다녔던 회사는 개발하는 회사와 운영하는 회사가 서로 달라서 커뮤니케이션이나 일정 조율 등 개발 외적인 부분에서 불편하고 애매한 부분이 있었습니다. 하지만 LINE은 모든 유관부서가 제가 일하는 근처에 있다보니 보다 편하고 빠르게 일할 수 있게 되었습니다. 특히 일정 조율이나 이슈 대응 등 유동적으로 처리해야 하는 일이 발생했을 때 그런 장점이 배가되었습니다. 그리고 그에 따라 개발자로서 참여할 수 있는 회의가 많아졌습니다. 기존에는 별다른 회의 참여없이 기획 파워포인트 자료, 개발 명세서 등 최종 결과물만 전달받고 개발을 진행했던 적이 많았습니다. 혹은 회의에 참여하더라도 마지막 최종 회의에만 참여했던 적이 많은데요. LINE에서는 기획 회의, 개발자 API 회의 등 여러 회의에 참여하게 되었고 자연스레 제 의견을 개진할 수 있는 기회도 많아졌습니다. 특히 해외 오피스에 있는 분들과 화상 회의를 많이 하는데요. 이런 경험 하나하나가 모두 신기합니다.또한 프론트엔드 개발 외에 좀 더 넓은 영역을 경험해 볼 수 있었습니다. 예를 들어 기존에는 설정이 완료된 빌드, 배포 서버를 이용하는 경우가 많아서 저는 개발에만 신경쓰고 나머지 부분엔 크게 관심을 두지 않았습니다. 하지만 LINE에서는 빌드, 배포 서버 설정을 직접 해볼 수 있었고, Node.js 기반의 웹 서버를 직접 운영하기도 했습니다. 그동안 프론트엔드 개발만 해오다가 백엔드 개발을 병행하니 훨씬 재밌기도 하고 더 넓은 영역을 배우는 것 같아서 개발자로서 스스로 성장하는 걸 느끼고 있습니다. 공유 문화속에서 성장하다 이러한 개발 활동은 저뿐만 아니라 같은 조직 내에 있는 모든 분들이 함께 해오고 있는 활동입니다. 바쁜 업무 탓에 자칫 조직 분위기가 조용히 흘러갈 수 있는데요. LINE에는 같은 조직에서 일하고 있는 동료로부터 간접적으로 지식을 습득할 수 있는 주간 회의나 월간 회의 등은 물론, 그 외에도 편하게 개발 이슈를 공유할 수 있는 자리가 많이 마련되어 있습니다. 이런 자리를 통한 공유 문화가 저를 지속적으로 성장하게 만들어 준 가장 큰 밑거름이라고 생각합니다. LINE엔 개발자의 성장에 도움이 되는 요소가 많아서 정말 입사하기 잘했다는 생각이 듭니다. 한 가지 아쉬운 점은 LINE에 프론트엔드 개발 조직, UIT 팀이 있다는 사실이 아직 외부에 많이 알려지지 않았다는 점인데요. 이 글을 통해 더욱 많은 분들이 LINE의 UIT 팀에 대해 알게 되어 지금보다 더 많은 동료가 생겼으면 좋겠습니다. LINE의 UIT 팀이나 회사 생활과 관련하여 궁금한 점이 있다면 Facebook을 통해 언제든 질문해 주세요. 질문은 언제나 환영합니다. 또한 LINE에선 현재 프론트엔드 개발자를 모집하고 있습니다. 관심 있으신 분은 채용공고를 참고하시기 바랍니다.

  • 오픈소스 LINE SDK for Unity를 향한 도전: 과제와 선택지

    유니티 자격요건에 본인이 왜 충족되었는지 설명

    Wei Wang2019-06-12

    Wei Wang 님은 현재 iOS에 집중하고 있는 전문 개발자입니다. 뛰어난 Swift, Objective-C, 네트워크 프로그래밍 실력을 갖추고 있으며, 이 실력을 바탕으로 널리 사용되는 애플리케이션과 프레임워크를 많이 만들었습니다.

    LINE SDK 개발팀의 Wei Wang입니다. 저희는 작년 LINE DEVELOPER DAY 2018에서 새로운 LINE SDK for iOS와 LINE SDK for Android를 오픈소스로 배포했습니다. 이 SDK는 LINE 로그인과 몇 가지 API를 앱에 통합하는 기능을 제공하는데요. 이를 통해 각 앱에 따라 매력적인 사용자 경험을 만들 수 있습니다. 저희는 배포를 마친 후 게임 개발자에 대한 지원도 필요하다는 점을 깨달았습니다. 게임은 App Store와 Google Play 전체 앱 중 50% 이상을 차지하며 압도적인 수익을 창출하고 있습니다. 그래서 저희는 또 하나의 중요한 플랫폼인 Unity용 LINE SDK를 개발하기로 결정했습니다. LINE SDK for Unity는 게임 개발자가 혁신적인 차기 게임 타이틀을 개발하면서 LINE SDK를 좀 더 쉽게 사용할 수 있도록 설계했습니다. 이번 글에서는 기존 SDK for Unity를 랩핑(wrapping)하기 위해 저희가 시도했던 방법을 소개하고, iOS와 Android에 LINE SDK를 통합하는 방법을 이야기하려 합니다. LINE SDK for iOS와 LINE SDK for Android처럼 LINE SDK for Unity도 오픈소스입니다. 코드는 GitHub에서 확인할 수 있고, Unity 게임에서 LINE SDK를 사용하는 방법은 셋업 가이드를 참조해 주시기 바랍니다. 개요 저희는 Unity SDK를 제공하면서 기존 iOS와 Android용 네이티브(native) SDK를 랩핑하여 사용자가 쓰기 편한 C# 인터페이스를 제공하기로 결정했습니다. SDK 전체를 다시 구현하지 않고 기존 기능을 랩핑하면 다음과 같은 이점이 있습니다. 유지관리 비용이 절감됩니다. 기존 네이티브 SDK 코드를 재사용하면 유지관리 비용을 줄일 수 있습니다. LINE SDK는 오픈소스로 공개되어 보급률이 높고 평판도 아주 좋습니다. 네이티브 SDK를 사용하면 프로젝트의 품질이 확보될 뿐만 아니라, 네이티브 SDK의 새로운 기능이나 수정사항이 Unity SDK에 동기화됩니다.  네이티브 기능을 활용할 수 있습니다. LINE SDK의 로그인 기능을 사용하려면 LINE 앱이 설치되지 않은 경우에 사용하는 웹 뷰(WebView) 로그인이나 다른 앱과의 데이터 전송 처리 등 시스템 플랫폼의 다양한 기능이 필요합니다. 네이티브 SDK를 사용하면 이러한 문제를 네이티브에서 적절하게 대처할 수 있습니다. 익숙한 조작 방법을 제공합니다. 모델과 API 정의를 C# 레벨로 제공하기 때문에 Unity 사용자는 iOS 플랫폼과 Android 플랫폼 간 차이에 상관 없이 C# 규칙대로 LINE SDK를 사용할 수 있습니다. LINE SDK for Unity의 기본 구조는 다음과 같습니다. 구현 방법 LINE SDK for Unity 구현 방법에 대해 설명하겠습니다(전체 소스코드는 GitHub에 공개되어 있습니다). Unity용 네이티브 플러그인 Unity에서는 광범위한 네이티브 플러그인이 지원됩니다. 네이티브 LINE SDK와 통신하도록 하려면 Unity C# 부분과 네이티브 부분(iOS에선 Swift, Android에선 Java)을 연결하는 다리를 만들어야 합니다. 비동기(asynchronous) 작업 처리 iOS는 DllImport를 사용해서 공개된 인터페이스를 Objective-C++에서 Unity C#으로 임포트합니다. Android는 AndroidJavaObject를 사용해서 LINE SDK 네이티브에서 메서드를 호출합니다. // iOS interface [DllImport("__Internal")] private static extern int foo(); public static int Foo() { return foo(); } // Android interface public static int Foo() { var androidObject = new AndroidJavaObject("com.linecorp.linesdk.SomeClass"); return androidObject.Call<int>("foo"); } 값 반환 여부에 상관없이 모두 동기(synchronous) 방식으로 쉽게 API를 호출할 수 있는데요. LINE SDK에서는 로그인이나 그 밖의 네트워크 관련 조작을 위해 몇 가지 비동기 API도 제공하고 있습니다. 예를 들어, iOS에는 로그인 API에 다음과 같은 서명(signature)이 있습니다. // Login method signature in LINE SDK Swift. public func login( permissions: Set<LoginPermission> = [.profile], options: LoginManagerOptions = [], completionHandler completion: @escaping (Result<LoginResult, LineSDKError>) -> Void) -> LoginProcess? 로그인 결과를 처리하려면 일종의 비동기 콜백(callback)이 필요하며, 네이티브 플랫폼과 Unity 게임 간에 데이터를 전달해야 합니다. 복잡한 방법 iOS의 경우 C#과 C++ 간의 콜백은 보통 Marshal 클래스를 이용해서 이루어집니다. 이 클래스는 관리되지 않는(unmanaged) 메모리를 할당하고, 관리되지 않는 메모리 블록을 복사하고, 관리되는 형식을 관리되지 않는 형식으로 변환하는 메서드 집합을 제공합니다. 예를 들어, C# delegate를 관리되지 않는 함수 포인터로 변환하면 C# 메서드를 네이티브로 전달할 수 있습니다. 또한 함수 포인터의 파라미터도 관리되지 않는 형식의 포인터로 표현할 수 있습니다. // Defines a Marshal function pointer to receive callback method from the native side. delegate void Callback(IntPtr foo); [DllImport(DllLib)] private static extern void method([MarshalAs(UnmanagedType.FunctionPtr)] Callback callback); public static void Method() { method(CBMethod); } [MonoPInvokeCallback(typeof(Callback))] private static void CBMethod(IntPtr foo) { // Convert Int pointer foo to a managed string string fooString = Marshal.PtrToStringAuto(foo); Debug.Log("Parameter is " + fooString); } 네이티브 C++(보통 Objective-C++로 작성)에서는 extern method를 다음과 같이 정의합니다. // Defines and invokes callback in the native side. typedef void (*CallbackT)(const char *foo); extern "C" void method(CallbackT callback); void method(CallbackT callback) { // Do something asynchronously. Then call `callback`. //... callback("foo sent"); } Unity에서 Method()를 호출하면, 네이티브에서 비동기 작업이 종료된 후 파라미터 'foo sent'와 함께 CBMethod()가 호출됩니다. Android에서는 비동기 작업의 결과를 얻기 위해 리스너(listener) 인터페이스가 있는 옵저버 패턴을 사용하곤 합니다. 예를 들어, LINE SDK for Android에는 LoginListener가 있습니다. // Listener interface for observing login result in the LINE SDK for Android. public interface LoginListener { void onLoginSuccess(@NonNull LineLoginResult result); void onLoginFailure(@Nullable LineLoginResult result); } Unity에서 Java 인터페이스를 표현하려면 AndroidJavaProxy를 사용하는 게 가장 좋습니다. AndroidJavaProxy를 상속한 클래스를 구현한 후 리스너 파라미터를 사용해서 적절한 Android API에 전달합니다. // Login proxy in Unity C#, which behaves as a native listener. class LoginCallback : AndroidJavaProxy { public LoginCallback() : base("com.linecorp.linesdk.$LoginListener") {} void onLoginSuccess(AndroidJavaObject result) { Debug.Log("Login successfully."); } void onLoginFailure(AndroidJavaObject result) { Debug.Log("Login failed."); } } androidObject.Call<int>("login", new LoginCallback()); Marshal과 AndroidJavaProxy 클래스는 유용합니다. 네이티브 파트와 Unity 파트를 연결하는 다리 역할로도 활용할 수 있습니다. 다만, LINE SDK의 규모와 여러 플랫폼을 지원해야 한다는 점을 고려하면 이 두 가지 방법은 사실 이상적인 방법은 아닙니다. 다음과 같은 몇 가지 단점이 있기 때문입니다. 관리되지 않는 메모리와 형식이 지정되지 않은 객체를 사용합니다. Marshal 클래스는 관리되지 않는 포인터에서 작동하며, AndroidJavaObject 또는 AndroidJavaProxy 클래스는 형식이 지정되지 않은 객체에서 작동합니다. 둘 다 C#의 규칙을 따르지 않습니다. SDK 확장이 어렵습니다. 기능을 추가하려면 네이티브와 Unity 모두에 형식 정의를 추가해야 해서, SDK를 변경하면 작업량이 두 배가 됩니다. iOS와 Android가 각각 방식이 달라서 멘탈 모델이 복잡해집니다. 쉬운 방법 저희는 Marshal과 AndroidJavaProxy 클래스를 활용하는 대신 훨씬 더 간단한 모델을 사용해서 비동기 콜백을 처리합니다. 그 모델은 바로 네이티브 쪽의 간단한 메서드인 UnitySendMessage 메서드입니다. 이 메서드는 토큰을 식별자로 사용하고, 데이터는 JSON 직렬화 파라미터를 사용해서 전달합니다.  Unity 엔진 런타임은 iOS와 Android 양쪽에 UnitySendMessage 메서드를 공개해서 메시지를 임의의 게임 객체(GameObject)로 전송합니다. 게임 객체에 메시지와 동일한 이름의 메서드가 있다면, 그 메서드를 호출하여 메시지에 응답할 수 있습니다. 이런 방식으로 네이티브 쪽에서 Unity의 메서드를 호출할 수 있습니다. // Send a message from native to Unity. UnitySendMessage("GameObjectName", "MethodName", "Message to send"); 위 메서드에는 다음과 같은 3개의 파라미터가 있습니다. 대상 GameObject의 이름 해당 객체를 호출하기 위한 스크립트 메서드 호출된 메서드에 전달할 메시지 문자열 Unity에서 이 메시지에 응답하려면, GameObjectName이란 이름의 게임 객체에 스크립트를 추가해야 하는데요. 이 스크립트는 단일 string 파라미터를 가지는 MethodName이란 이름의 메서드를 포함해야 합니다. // This script should be added as a component on "GameObjectName". class MyClass: MonoBehaviour { void MethodName(string parameter) { //... Debug.Log(parameter); // "Message to send" } } 식별자를 사용해서 네이티브 API를 호출하고, 동일한 식별자를 UnitySendMessage 메서드를 통해 반환하면 비동기 작업을 호출한 주체를 식별할 수 있습니다. iOS의 경우 기본 개념은 아래와 같습니다. // Passing the `identifier` to track asynchronous operation caller. // Unity - interface for the SDK users. public static void Method() { // Generates an identifier internally. var identifier = Guid.NewGuid().ToString(); Foo(identifier); } // Unity - wrapper [DllImport("__Internal")] private static extern void foo(string identifier); public static int Foo(string identifier) { foo(identifier); } // iOS extern "C" void foo(const char *identifier); void foo(const char *identifier) { // Do something asynchronously. Then call `UnitySendMessage` with `identifier`. //... UnitySendMessage("NativeListener", "CallbackMethod", identifier); } // In Unity, there is a game object with the name of `NativeListener`. void CallbackMethod(string identifier) { // We know who initializes the operation by checking `identifier`. // Do something with the callback from native side. } 전달받은 identifier를 확인하면 특정 콜백을 어떻게 처리할지 결정할 수 있습니다. 이렇게 해서 Unity와 네이티브가 비동기 방식으로 통신할 수 있게 됩니다. JSON과 Serializable로 데이터 전달 UnitySendMessage 메서드를 사용하면 오직 하나의 문자열 값만 주고 받을 수 있습니다. 위의 예에서는 identifier 값을 반환했습니다. 하지만 LINE SDK에서는 일부 값을 네이티브 형식으로 Unity로 전달하는 것도 필요합니다. 예를 들어, 토큰 값 문자열, 만료기간, 토큰 범위(scope) 등을 포함한 액세스 토큰이 있습니다. 따라서 네이티브와 Unity에서 모두 해석할 수 있는 포맷으로 데이터를 직렬화하는 방법이 필요합니다. 이렇게 서로 다른 언어 간에 데이터를 교환해야 할 때는 자연스럽게 JSON을 선택하게 됩니다. JSON은 이해하기 쉽고 네이티브와 Unity 양쪽에서 완벽하게 지원됩니다. LINE SDK for Unity에서는 콜백 페이로드(payload)를 다음과 같은 형식으로 정의합니다. // Shared structure for token between native side and Unity. { "identifier": "abcdefg...", // The received GUID from Unity. "value": { // A nested object represents native object. "token": "...", ... } } 그냥 identifier를 반환하는 것이 아니라, 우리에게 중요한 실제 데이터를 표현하는 value와 조합하여 JSON 문자열로 직렬화한 뒤 전송합니다. // Serialize a native object to JSON, then send it as a message parameter to Unity. void foo(const char *identifier) { // Do something asynchronously. // Then call `UnitySendMessage` with `identifier` and the actual data. [loginManager loginWithCompletionHandler:^(LineSDKLoginResult * result, NSError *error) { NSDictionary *dic = @{ @"identifier": convertToNSString(identifier), @"value": [result toDictionary] }; NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:kNilOptions error:nil]; const char* payload = convertToCString([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); UnitySendMessage("NativeListener", "CallbackMethod", payload); }]; } Unity에선 JsonUtility를 사용해서 JSON 문자열을 역직렬화한 후 특정 형식의 C#에서 관리하는(managed) 객체로 다시 변환합니다. 이런 방법으로 아래 그림과 같이 임의의 데이터를 네이티브와 Unity 게임 간에 주고 받을 수 있습니다. iOS에서 LINE SDK 통합(Swift) 저희는 2018년에 LINE SDK for iOS 버전 5를 Objective-C와 Swift로 릴리스했고 앞으로 이 SDK를 지속적으로 발전시켜 나갈 예정입니다. Swift로 작업하는 것은 좋지만, Swift는 아직 모듈의 안정성이 확보되지 않았기 때문에(Swift 5에서도 동일) Swift SDK를 바이너리 형식으로 제공하는 것은 불가능합니다.  네이티브 Swift SDK를 Unity 프로젝트에 통합하려면 소스코드의 의존성을 해결하고 관리할 수 있는 방법이 필요합니다. 또한 Unity는 iOS 프로젝트를 주로 Objective-C++로 내보내고, 네이티브의 인터페이스는 C로 작성되어야 하기 때문에 C와 C++, Objective-C, Swift 간의 상호운용성도 과제입니다. 이런 몇 가지 과제에 대해 아래에서 설명하겠습니다. 네이티브 SDK 설치 Unity에서는 '바이너리 라이브러리 내보내기(export)'를 잘 지원하고 있습니다. 바이너리 파일은 Assets/Plugins/iOS 폴더 아래에 위치합니다. 하지만, Swift SDK를 선택했기 때문에 개발자의 기기에 설치된 Swift 툴체인(toolchain)을 사용해서 소스부터 빌드해야 합니다. 네이티브 SDK와 유사하게 의존성 관리 도구로 CocoaPods나 Carthage를 선택할 수 있습니다. 모든 통합 작업은 포스트 프로세스 스크립트가 수행합니다. PostProcessBuildAttribute 속성은 Unity의 내보내기가 종료되는 시점에 훅(hook)을 제공합니다. 훅을 이용하면 의존성을 설정하는 스크립트를 실행하고 최종 프로젝트를 구성할 수 있습니다. LINE Swift SDK는 오픈소스 프로젝트입니다. 따라서 CocoaPods나 Carthage를 통해 SDK를 설치하는 건 LINE SDK를 일반적인 iOS 프로젝트에 추가하는 것과 별 차이가 없습니다. 다음과 같이 iOS 프로젝트에 SDK를 추가할 수 있습니다. Podfile 또는 Cartfile을 내보내기한 프로젝트의 루트에 배치합니다. 터미널에서 pod install 또는 carthage update를 실행하면, 네이티브 SDK의 소스코드가 GitHub에서 다운로드되고 필요하면 컴파일됩니다. 프레임워크 연결, 빌드 설정 변경 등 추가로 필요한 설정을 합니다. 흥미로운 점은 거의 모든 설정 작업에서 Unity의 Xcode API를 사용할 수 있다는 점입니다. 모든 API는 Unity의 UnityEditor.iOS.Xcode 네임스페이스(namespace) 아래 존재하며, 이를 통해 내보내기한 Xcode 프로젝트를 Unity 에디터에서 C# 코드로 조작할 수 있습니다. 예를 들어, Xcode 프로젝트의 빌드 설정을 변경하려면 PBXProject 객체를 생성한 후 그 객체에 SetBuildProperty를 호출하면 됩니다. // Set a build setting value in exported Xcode project. var path = PBXProject.GetPBXProjectPath(projectRoot); var project = new PBXProject(); project.ReadFromFile(path); project.SetBuildProperty(target, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES"); Unity Xcode API를 사용하면 네이티브 SDK를 통합하기 위해 필요한 대부분의 설정 작업(링크 단계에 프레임워크 추가, 콜백 URL 스킴 설정 등)을 자동화할 수 있습니다. 상호운용성 iOS의 LINE SDK for Unity를 사용할 땐 다양한 언어로 작업해야 합니다. 네이티브 SDK의 로직은 순수한 Swift로 구현되어 있는데요. 호환되지 않는 몇 가지 언어 기능(Swift의 enum과 struct 등)이 포함되어 Objective-C에서 직접 사용할 수 없습니다. Swift SDK를 Objective-C에서 사용하기 위해 래퍼(wrapper)를 추가로 준비했습니다. 이 래퍼도 Swift로 작성됐지만 Objective-C와 호환되는 언어 기능만 사용되었습니다. Unity로 작업할 때, 내보내기한 프로젝트가 실제론 Objective-C++ 프로젝트이기 때문에 이 래퍼와 통신해야 합니다. 즉, 다음과 같은 처리가 필요합니다. 네이티브의 Swift SDK를 C++ 프로젝트에 가져오기 위한 clang 모듈을 활성화한다. Swift 라이브러리를 번들에 삽입한다. 런타임에 정확한 검색 경로를 설정한다. 이 처리를 앞서 말한 Unity Xcode API로 수행할 수 있습니다. // Enable clang module to use Swift SDK in Objective-C++. project.SetBuildProperty(target, "CLANG_ENABLE_MODULES", "YES"); // Setup Swift environment. // These two are only necessary when using Carthage. CocoaPods will help us to setup them. project.SetBuildProperty(target, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES"); project.SetBuildProperty(target, "LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/Frameworks"); Unity의 Xcode API를 사용해서 저희는 프로젝트 구성 시간을 대폭 단축시킬 수 있었습니다. 여러분도 이 API를 사용해서 프로젝트를 한층 더 깊이 커스터마이징해 보시길 추천합니다. 시스템 이벤트 수신 사용자가 자신의 기기에 이미 LINE 앱을 설치한 경우, LINE SDK는 그 LINE 앱을 인증 바로가기로 열 수 있습니다. 사용자명과 패스워드는 필요 없습니다. 이는 Unity 게임과 LINE 앱 간에 통신이 이루어지고 있다는 것을 의미합니다. 대부분의 경우 URL 스킴(scheme)을 사용해서 사용자의 인증 정보를 게임에 전달하는데요. LINE SDK for Unity에서는 그 이벤트를 얻을 방법이 필요합니다. iOS 앱을 개발 중이라면 방법은 간단합니다. Objective-C 또는 Swift 소스코드에 몇 줄을 추가해서 커밋하면 됩니다. 하지만, Unity에서 작업하는 경우에는 Xcode 프로젝트를 Unity에서 내보낼 때마다 프로젝트가 덮어쓰기됩니다. 다행히도 Unity에서는 앱 열기 이벤트 알림에 등록하는 방법을 제공합니다. // Code exported from Unity. It defines a listener interface for anyone to use. @protocol AppDelegateListener<LifeCycleListener> //... // notification will be posted from // - (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation // notification user data is the NSDictionary containing all the params - (void)onOpenURL:(NSNotification*)notification; @end AppDelegateListener에 준거한 클래스를 생성한 후 UnityRegisterAppDelegateListener를 호출해서 알림 옵저버(observer)로 추가하면, 앞으로 발생하는 해당 이벤트를 얻을 수 있습니다. // Register the app delegate listener to receive app life cycle events. @interface LineSDKAppDelegateListener()<AppDelegateListener> //... @end @implementation LineSDKAppDelegateListener - (instancetype)init { self = [super init]; if (self) { // ... UnityRegisterAppDelegateListener(self); } return self; } - (void)onOpenURL:(NSNotification *)notification { // Current app is opened (be navigated from LINE app). } @end LINE의 GitHub 저장소에서 LineSDKAppDelegateListener 코드 전체를 확인할 수 있습니다. Android에서 LINE SDK 통합 Android SDK 통합은 간단합니다. 저희는 Unity에서 기능을 쉽게 트리거(trigger)할 수 있도록 네이티브 LINE SDK API를 랩핑하는 unity-wrapper android 프로젝트를 제공하고 있습니다. 플러그인 바이너리는 미리 빌드되어 Assets/Plugins/Android 폴더에 저장되어 있습니다. unity-wrapper android 프로젝트에서 수정이 필요하면 언제든지 수정 후 바이너리를 다시 빌드할 수 있습니다. 커스텀 Gradle 템플릿을 사용해서 의존성 관리 Android 플랫폼용 Unity 프로젝트에서는 Gradle 빌드 시스템을 사용합니다. unity-wrapper 프로젝트와 LINE SDK 라이브러리에 필요한 모든 의존성을 포함시키려면 Unity 프로젝트에서 커스텀 Gradle 템플릿을 구성하는 것이 중요합니다. Unity Player Settings의 Android settings 탭에 있는 Publishing Settings에서 Custom Gradle Template가 활성화되어 있는 걸 확인합니다. mainTemplate.gradle이라는 이름의 파일이 Assets/Plugins/Android 폴더에 생성됩니다. 템플릿 파일에 아래 내용을 추가해야 합니다. buildscript 섹션 buildscript { ext.kotlin_version = '1.3.11' ... dependencies { ... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } dependencies 섹션 dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.linecorp:linesdk:5.0.1' implementation 'com.google.code.gson:gson:2.8.5' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" ... } 통합에 관한 자세한 사항은 'LINE SDK for Unity 가이드'를 참고하시기 바랍니다. 마치며 저희는 여러분이 제작할 훌륭한 게임 차기작이나 기존 게임에 LINE을 쉽게 추가할 수 있도록 LINE SDK for Unity를 만들었습니다. 더 통합하기 쉽고 더 사용하기 쉽게 만들기 위해 노력했습니다. LINE 로그인이나 그 밖의 LINE API를 Unity 게임에 통합하는 것에 관심 있으신 분들은 LINE SDK for Unity 관련 문서를 확인해 주시기 바랍니다. LINE SDK for iOS나 LINE SDK for Android와 동일하게 LINE SDK for Unity도 오픈소스 프로젝트입니다. 문제점을 발견하거나 제안하고 싶은 사항이 있을 땐 부담 없이 저장소에서 이슈를 열어 코멘트를 입력해 주세요.

  • LINE 원정대: 사우디아라비아로 현장 테스트를 다녀왔습니다

    박종혁2019-06-10

    LINE에서 Yuki Live SDK 개발을 담당하고 있습니다

    저는 지난 4월 초에 사우디아라비아로 현장 테스트를 다녀왔습니다. LINE은 작년에 중동에서 LIVE 개인방송 애플리케이션을 론칭하여 서비스하고 있습니다. 이번 현장 테스트의 목적은 현재 서비스하고 있는 LINE LIVE MENA(Middle East and North Africa)를 더욱 발전시키기 위해 서비스 영상의 품질과 경쟁사 앱과 비교한 장점과 개선점을 파악하고, 현재 사용하고 있는 RTMP(Real Time Messaging Protocol)가 아닌 다른 전송 프로토콜을 사용한다면 어떤 결과가 나타나는지 알아보는 것이었습니다.  LINE 원정대: 원정대 결성 아랍어를 못하는 세 명의 개발자가 사우디에 간다? 이 말만 들으면 어딘가 모르게 불안한 느낌을 지울 수가 없는데요. 다행히 현장 테스트에는 개발자뿐 아니라 LINE의 중동 전문가들도 함께 참여했습니다. 중동 출장을 여러 번 다녀온 중동 사업팀 두 분, 한국에서 10년 넘게 살아 한국어 패치가 완벽하게 적용된 사우디아라비아인 한 분, 그리고 LINE 개발자 셋. 이렇게 여섯 명이 이번 LINE 원정대로 결성됐습니다(선배 LINE 원정대의 이야기가 궁금하신 분은 여기를 참고하세요). 떠나기 전 사전 회의를 통해 팀별로 현지에서 어떤 일을 진행할지와 각 팀의 상황에 맞게 출장 일정과 이동 경로를 정했습니다. 또한 회의에서는 현장 테스트를 여러 도시에서 진행하기 보다는 한 곳에 집중해서 진행하자는 결론이 나왔고, 그에 따라 사우디아라비아의 수도인 리야드(Riyadh)에서만 현장 테스트를 진행하게 됐습니다. 여정의 시작 출발 전 긴 비행시간을 걱정하셨던 분들이 많았는데요. LINE에서는 출장 제도를 잘 활용하면 누구나 비즈니스 클래스를 타고 출장갈 수 있습니다! 덕분에 10시간이 넘는 비행시간에도 불구하고 하늘 위 호텔에 몸을 맡긴 채 편안히 비행을 마치고 리야드에 도착할 수 있었습니다. 리야드에 도착했을 때 저희를 반겨준 건 'Welcome LINERs!'라고 적혀진 플래카드를 들고 환영하는 인파!... 였다고 말씀드리고 싶지만, 실제로 마중 나온 건 푹푹 찌는 더위였습니다. 저희가 4월 초에 출장 갔는데요. 4월은 여름에 비하면 준수한 편이라고는 했지만 벌써 기온이 30도까지 올라가 있었습니다. 날씨부터 이번 출장이 호락호락하진 않을 거라고 말하고 있는 것 같았습니다. 날씨뿐 아니라 생소한 문화에 적응하는데도 시간이 필요했는데요. 날마다 정해져있는 기도 시간 때문에 갑자기 카페 영업이 일시적으로 중단되거나, single(남성)과 family(남성과 여성 동반) 공간이 분리된 장소를 잘못 들어가기를 몇 번씩 경험하면서 저희가 완전히 다른 문화권의 나라에 출장 왔다는 사실을 실감할 수 있었습니다. 'It’s Mine... My Precious...' 'It’s Mine... My Precious...'는 영화 '반지의 제왕: 반지 원정대'에 등장하는 골룸이 자신이 소중하게 생각하던 반지를 표현한 대사입니다. 아마 영화를 보신 분들이라면 집에서 몰래 한 번쯤 이 대사를 따라해 보셨을 것 같은데요. 골룸에게 절대 반지가 소중했던 것 만큼이나 저희에게 현지 유심(usim)칩이 소중했습니다. 사우디아라비에서 외국인은 일 인당 한 개의 유심칩밖에는 구입하지 못합니다. 사우디아라비아에는 세 개의 주요 통신사(STC, Mobily, Zain)가 있는데요. 같은 통신사끼리 교차로 테스트해 보기 위해서는 적어도 한 통신사당 두 개의 유심칩이 필요합니다. 다행히 저희 멤버가 여섯 명이라 유심칩을 한 통신사당 두 개씩 살 수는 있었지만, 그것만으론 테스트를 원활하게 진행할 수 없었습니다. 유심칩이 없으면 테스트를 할 수 없다 보니 그럴 땐 그저 누군가의 테스트가 끝나기를 기다려야 했고, 제가 유심칩을 받아 테스트를 진행하면 또 다른 누군가는 또 기다리고. 이런 과정이 무한 반복되었습니다. 역시 돌이켜 생각해봐도 이번 출장에서 가장 소중했던 것은 바로 유심칩이었습니다. 'My precious…' LINE 원정대: 두 개의 PoP PoP는 'Point of Presence'의 준말로, 네트워크 상호간 또는 개별 네트워크에 대한 접속점 또는 접근점을 의미합니다. 지금부터는 사우디에서 진행된 테스트 내용을 살펴보겠습니다. 테스트 장소는 현지인들이 많이 찾는 백화점, 현지 와이파이 상태를 경험할 수 있는 카페, 호텔 주변, 이동 시 차 안과 같은 곳이었는데요. 쉽게 말해, 발이 닿는 모든 곳이 저희 테스트 장소였습니다. 특별히 현지인 집에도 방문할 기회가 생겨 실제 서비스 이용자 환경과 유사한 곳에서도 테스트해 볼 수 있었습니다. 현지 카페에서 테스트를 진행하는 모습 현지 전통 식당에서 테스트를 진행하는 모습 현지 가정집에 방문해 테스트를 진행하는 모습 테스트 진행 현장에서 다음 두 가지 테스트를 진행했습니다. 서비스 중인 PoP 상태를 체크하기 위한 RTMP 송출 및 ping 테스트 WebRTC 적용 검토 테스트 첫 번째 테스트에서는 현재 LINE LIVE에 사용하고 있는 PoP의 상태를 확인하고자 했는데요. LIVE 서비스에서는 모든 사용자의 송출 영상과 음성 데이터(비디오와 오디오 전송률, 이용 가능한 대역폭(bandwidth), RTT 등)를 서버에 기록해서 분석하고 있습니다. 이번 테스트에서도 그와 동일하게 기록된 데이터를 시각화하여 분석했고, ping 테스트로 RTT값을 비교해 보면서 PoP 망의 상태를 확인했습니다. 통신사 'B'의 PoP ping 테스트 결과 다음 테스트 항목은 WebRTC 적용 검토 테스트였는데요. 현재 LINE LIVE 스트리밍은 방송하는 사람(BJ)이 RTMP와 TCP를 사용하여 송출하면 이를 시청자가 서버 연계(relay)와 트랜스코딩(transcoding)을 통해 RTMP 혹은 HLS(HTTP Live Streaming)로 수신하는 방식을 사용합니다. 실시간 스트리밍 서비스는 저희가 보통 'latency'라고 표현하는, 송출자와 시청자 사이에서 발생하는 지연의 정도와 시청자의 기기에 첫 번째 프레임이 표시되는 시간인 'first frame delay'를 최소화하는 게 중요한데요. HLS는 무조건 서버의 트랜스코딩과 CDN(content delivery network)에 TS파일 형태로 저장하는 과정을 거쳐야 하기 때문에 사실상 송출자와 시청자 간에 지연이 발생할 수 밖에 없습니다. 이런 형태의 지연은 실시간 스포츠 중계를 즐겨 보시는 분이라면 한번쯤 간접적으로 경험해 보셨을 겁니다. TV를 시청하는 사람들은 환호하고 있는데 인터넷 스트리밍으로 시청하는 나는 아직 화면에서 보지 못했고, 뭔가 일이 일어난 것 같기는 한데 뭔지 모르겠고, 왠지 모르게 스포일러를 당한 것 같은 느낌. 다 이와 관련있는 경험입니다. HLS가 아닌 RTMP로 연계된 영상을 수신하더라도 프로토콜 자체의 오버헤드 때문에 실시간으로 표시하는 데는 어려움이 있습니다. 이렇게 실시간으로 표시하기 어려울 때 저희는 보통 'latency가 크다'라고 표현합니다. 이러한 이유로 RTMP가 아닌 WebRTC가 저희 서비스에 더 적합한지 확인하고자 했습니다. 검증하기 위해서 기본적으로 P2P(peer-to-peer) 연결에 문제가 없는지, 전체적인 송출 품질은 어떤지, latency와 first frame delay가 어느 정도 인지를 중점적으로 살펴봤는데요. 송출과 수신 품질을 확인하기 위해서 WebRTC Stats Report를 이용해 필요한 데이터(bitrate, RTT, packetReceived, packetsLost, bytesReceived 등)를 추출하고 서버에 저장, 분석했습니다. WebRTC를 도입할 땐 앞서 말씀드린 latency나 first frame delay가 중요하기 때문에 이 두 가지 지표는 별도로 테스트를 진행했는데요. WebRTC를 사용하면 RTMP로 송출하고 RTMP로 시청했을 때보다 latency는 전체적으로 더 작았고 first frame delay도 비슷하거나 더 낮은 수준이라서 몇 가지 튜닝을 한다면 서비스에 적용시킬 수 있겠다는 긍정적인 결과를 얻었습니다. LINE 원정대: 귀환 사실 7일 간의 테스트 기간(실제 테스트 기간은 약 4~5일 정도)만으로 네트워크 환경이 정확하게 어떤 상태라고 결론 내리기는 쉽지 않았습니다. 네트워크 환경은 시간대마다 속도 차가 있을 수도 있고, 사람이 많이 몰리는 곳인지 아닌지에 따라 다를 수도 있으며, 지역과 위치에 따라서도 충분히 차이가 발생할 수 있습니다. 따라서 이번 테스트 결과를 생각할 때 매시간 연속적으로 테스트를 하지 않았고, 모든 지역을 커버할 수 없었다는 한계를 참작할 필요가 있을 것 같은데요. 이를 고려하더라도 누군가 저를 포함한 개발자 모두에게 현지 네트워크 환경의 상태를 물어본다면, 하나같이 서비스하기에 '좋다'라고 대답할 겁니다. 네트워크 환경이 좋아서인지 현지 사람들은 대부분 휴대폰으로 비디오 영상을 시청하고 있었고, 심지어 운전하면서도 계속 휴대폰 영상을 보는가 하면 실시간 영상통화까지 즐기는 모습도 보였습니다. 덕분에 저희는 차를 탈 때마다 안전벨트를 착용하고도 마음을 졸여야 했습니다. 영화 '반지의 제왕'의 주인공인 프로도는 절대 반지를 없애기 위해 여정을 떠나는데요. 영화를 보면 그 여정에서 반지를 없앤다는 목적과는 별도로 또 다른 삶의 소중한 가치를 발견하고 새로운 깨달음을 얻는 것을 볼 수 있습니다. 그와 마찬가지로 이번 출장의 목적은 현지 네트워크 환경 조사였지만, 돌이켜보면 이번 여정을 통해 얻은 것은 그 이상이었습니다. 제가 개발한 서비스를 실제 환경에서 사용할 땐 어떤 느낌인지 체감할 수 있어서 개발자와 실제 사용자 간의 온도 차이를 줄일 수 있었고, 서비스의 어떤 점이 좋고 어떤 점을 개선해야 할지 깨달을 수 있었습니다. 또한 함께 출장간 동료들과 끊어질 듯, 끊어질 듯 하면서 계속 이어지는 토론을 벌이고 대화를 나눴는데요. 이를 통해 아마 사무실에만 계속 앉아 있었다면 결코 얻지 못했을 인사이트를 얻을 수 있었습니다.  끝으로 원정대에 합류하여 함께 원정 다녀온 모든 분께 감사드리며, 이번 테스트를 발판으로 LINE LIVE 서비스가 더 크게 성장하기를 희망합니다. 

    유니티 자격요건에 본인이 왜 충족되었는지 설명
  • LINE Search UI 개선기

    유니티 자격요건에 본인이 왜 충족되었는지 설명

    이상원2019-05-29

    LINE에서 프론트엔드 업무를 담당하고 있습니다

    사용자에게 유려한 UI(User Interface)와 좋은 UX(User eXperience, 사용자 경험)를 제공하는 일은 까다롭습니다. 간단한 동작을 만들 때도 많은 것들을 고민하고 만들지만 항상 좋은 결과물이 나오진 않습니다. 사용자를 생각하며 여러가지 시도를 멈추지 않는 것이 프론트엔드 개발자의 숙명이 아닐까 생각합니다. 안녕하세요. 저는 LINE UIT 조직에서 프론트엔드 업무를 담당하고 있는 이상원입니다. 이번 글에선 제가 LINE Search 프로젝트를 담당할 때 새로 추가된 UI를 조금 더 개선하기 위해 노력했던 내용을 공유하려고 합니다. 배경 설명 제가 LINE Search 프로젝트를 담당할 당시, LINE Search에서 이미지 뷰어를 실행할 때 화면 상단에 위치한 검색 바(bar)를 가리는 기능이 추가되었습니다. 개발 당시 여러 일정과 상황이 맞물려서 검색 바가 사라질 때 애니메이션 효과를 추가할 수 없었는데요. 결론적으로 프론트엔드에서 웹 뷰(web view) 영역만 제어하여 사용자가 최대한 좋은 UI를 경험할 수 있도록 만들어야 하는 상황이었습니다. 구현 당시 일정에 쫓기는 바람에 이 글에서 고려했던 모든 사항을 고려하지 못하고 단순하게 클라이언트 팀에서 제공한 API를 호출해 검색 바를 숨길 수 있는 기능을 추가하는 것으로 작업을 완료했습니다. 작업 완료 후 QA(quality assurance)가 시작되었고, QA 팀에서 해당 기능의 동작과 관련된 이슈를 등록했습니다. 아래 동영상은 QA에서 이슈 등록과 함께 제공한 동영상입니다. 위 동영상을 살펴보면서 발견한 첫 번째 문제점은 이미지 뷰어가 실행되기 전에 검색 바가 사라지면서, 웹 뷰 영역이 늘어나며 콘텐츠가 위로 올라가는 현상이 보인다는 점이었습니다. 처음엔 단순하게 '콘텐츠가 위로 밀려 올라가는 모습이 보이지 않으면 되겠지'라는 생각으로 로직 순서를 수정해서 구현해 봤습니다(아래 샘플은 이해를 돕기 위해 실제로 구현된 상태보다 느리게 재생한 상태입니다). 순서 샘플 1. 콘텐츠 영역을 가립니다. 2. 검색 바를 없앱니다. 3. 웹 뷰 영역 크기 변경이 완료되길 기다립니다. 4. 이미지 뷰어를 실행합니다. 첫 번째 시도에서 얻은 결과물을 함께 프로젝트를 진행하고 있던 팀원에게 공유한 뒤 여러 의견을 종합한 결과, 애니메이션을 추가하기로 결정했습니다. QA 시작 전에는 다른 기능들을 구현하느라 애니메이션을 추가할 여건이 되지 않았지만, 기획, 디자인, QA 팀과 협의해 보니 이번엔 추가할 수 있겠다고 판단했습니다. 애니메이션 효과를 추가하는 과정은 일반적인 개발 순서와 크게 다르지 않습니다. 먼저 애니메이션을 설계하고 코드를 작성한 뒤, 코드 리뷰와 QA 과정을 거쳤습니다.  애니메이션 설계 및 코드 작성 간단하게 CSS로 동작하는 애니메이션을 추가해서 몇 가지 샘플을 제작해 봤습니다. 샘플 1 샘플 2 제작한 샘플 중 팀원들과 함께 결정했던 건 첫 번째 샘플이었습니다. 하지만 좀 더 많은 사람들의 피드백을 받아보니 두 번째 샘플이 좋다는 의견이 더 많아 두 번째 샘플의 애니메이션 형태로 구현했습니다. 애니메이션은 아래와 같이 Vue.js 프레임워크의 transition 기능을 사용해 간단하게 구현할 수 있었는데요. 이 기능을 사용하면 CSS 애니메이션이 시작되고 종료되는 시점을 쉽게 제어할 수 있다는 장점이 있습니다. <transition name="layer" @after-enter="afterEnter"> <v-image-viewer /> </transition> <script type="text/javascript"> export default { methods: { afterEnter () { // call api : close search bar } } } </script> <style> .layer-enter-active, .layer-leave-active { transition: all ease 0.2s; } .layer-enter, .layer-leave-to { opacity: 0; } .layer-leave-to { transition: all linear 0.1s; } </style> 위 코드에서 이미지 뷰어를 실행하고 종료하는데 걸리는 시간을 각각 0.2초와 0.1초로 설정한 것도 피드백을 반영한 부분입니다. 단 0.1초 차이일 뿐이지만, 이미지 뷰어가 종료될 때 조금 더 빠른 반응을 보여주는 게 좋다는 의견이 많았습니다. 코드 리뷰와 QA QA 기간에 추가 기능을 개발하는 것은 금지되어 있었지만, 애니메이션을 추가하기로 결정한 시점엔 이미 QA가 진행되고 있었습니다. 또한 팀원들과 함께 여러 사람의 피드백을 받아 반영하는 와중에 QA 담당자도 현재 상황에서 조금 더 개선해 줄 수 있겠냐는 요청을 보내왔습니다. 아래 동영상을 보면 가장 먼저 화면 하단에 이미 렌더링된 콘텐츠가 이미지 뷰어 앞으로 올라온 것처럼 보인다는 점이 눈에 띕니다. 두 번째 문제는 이미지 뷰어가 종료될 때, 적용해 놓은 애니메이션이 정상적으로 동작하지 않는다는 점이었습니다. 완벽함을 도모하기 위해 아래와 같이 슬로우 모션으로 촬영해 보았더니 육안으로는 파악하기 힘들었던 몇 가지 문제점을 더 발견할 수 있었습니다. 발견한 문제점 이미지 뷰어를 종료할 때 애니메이션이 정상 동작하지 않는다 이 문제는 이미지 뷰어에 슬라이드로 적용된 DOM 객체가 많을 경우 애니메이션 동작에 영향을 미치게 되는 문제였습니다. 이미지 뷰어가 닫히는 시점에서 이미지 뷰어의 슬라이드 DOM 객체를 모두 삭제한 뒤 애니메이션이 동작하도록 수정하여 간단하게 문제를 해결할 수 있었습니다. 추후 사용자가 보고 있는 영역 근처의 DOM만 이미지 슬라이더에 그리도록 개선하는 계기가 되었습니다. 이미지 뷰어를 실행할 때 화면 하단에 콘텐츠 영역이 잠시 보인 뒤 사라진다 이 문제를 해결하기 위해서 z-index를 설정하여 뒤에 그려진 콘텐츠가 왜 이미지 뷰어 앞에 보이는지 원인을 파악했습니다. 원인 파악을 위해 아래 샘플 코드 조건에 맞는 단순한 샘플 코드를 작성해서 테스트해 보았습니다. 샘플 코드의 조건 버튼을 눌러 검색 바를 가리거나 다시 나타나게 조작할 수 있어야 합니다. 첫 번째 DOM 객체는 z-index 값이 없어야 합니다. 두 번째 DOM 객체는 position : fixed 값과 z-index 값을 갖고 첫 번째 DOM 객체 안에 존재해야 합니다. <html style="margin:0;padding:0;"> <head> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,viewport-fit=cover,user-scalable=no,target-densitydpi=medium-dpi"> </head> <body style="margin:0;padding:0;"> <div style="width:100%;height:150%;display:block;background-color:red;"> <div style="width:100%;height:100%;display:block;z-index:1;position:fixed;background-color:yellow;zoom:1;"> <button id="openSearchBar"> open Search bar </button> <button id="closeSearchBar"> close Search bar </button> </div> </div> </body> <script> window.document.querySelector('#openSearchBar').addEventListener('click', function () { // call open search bar }) window.document.querySelector('#closeSearchBar').addEventListener('click', function () { // call close search bar }) </script> </html> 위 샘플 코드로 확인해 본 결과 검색 바를 가리도록 조작했을 때 배경 색상(background-color)을 빨간색(red)으로 넣은 DOM이 보이는 동일한 현상을 발견할 수 있었습니다. Chrome 개발자 도구에서 layers에 그려진 DOM 객체 상황을 확인해 보니 좀 더 쉽게 이유를 파악할 수 있었습니다. Layers 파악된 문제 검색 바가 사라지며 웹 뷰의 크기를 변경합니다. window → resize 이벤트가 발생합니다. 이때 이미 렌더링된 첫 번째 DOM 객체는 repaint 이벤트 이전에도 하단이 노출됩니다. 두 번째 DOM 객체에 적용된 height: 100%가 두 번째 DOM 객체의 크기를 변경하여 repaint를 실행합니다. 문제의 원인은 파악했지만 해결은 생각처럼 쉽지 않았습니다. height 값을 120%, 혹은 calc(100% + 150px)로 변경해도 position이 fixed로 지정된 상황에서는 브라우저가 화면을 넘어간 부분을 렌더링하지 않았습니다. 이 문제는 샘플 코드에서 몇 가지 테스트를 해 본 뒤 min-height 값을 브라우저 크기보다 조금 더 크게 설정하여 해결할 수 있었습니다. 화면 하단에 콘텐츠 대신 흰 부분이 보인다 이 이슈는 Android에서만 발생하는 이슈였는데요. 검색 바가 가려지면서 웹 뷰의 사이즈가 변경될 때 repaint되는 순서가 문제였습니다. 1. 검색 바 숨김 요청 2. 웹 뷰 영역의 크기 변경 3. 렌더링 트리의 루트 노드(<html>) 크기 변경 후 repaint 발생 4. 변경된 루트 노드(<html>)의 크기에 맞춰 DOM 객체 repaint 발생 이미지 뷰어의 배경이 검은색이라서 너무 눈에 띄었는데요. HTML에서 동일한 색상을 배경에 적용하는 방식으로 문제를 회피한 상태입니다(참고로 orientationchange 이벤트가 발생할 때는 해당 현상이 나타나지 않았습니다).  개선 전후 비교 첫 구현 결과물 마지막 구현 결과물 첫 구현 당시 예상치 못한 많은 문제점을 발견했고, 해결하고 나서 이미지 뷰어를 실행, 종료하는 과정에서 어색한 부분이 눈에 띄게 줄어든 것을 확인할 수 있습니다. 보통 애니메이션을 적용하면 실행할 때 더 많은 시간이 걸리게 되지만, 이미지가 뒤틀리는 현상 같은 게 보이지 않아 오히려 더 빨리 실행되는 것처럼 보이기도 합니다. 사용자들이 이미지 뷰어를 사용하면서 조금도 불편함을 느끼지 않길 바라 봅니다. 마치며 이쪽 분야를 잘 모르는 사람들의 관점에선 사용자의 UI와 UX를 개선하는 일이 별것 아닌 일에 큰 공수를 들이는 것처럼 보일 수 있습니다. 하지만 이렇게 심혈을 기울여 제작된 결과를 통해 사용자는 작은 경험들을 쌓아 나가고, 그렇게 쌓인 경험들이 결국 앱에 대해 사용자가 갖는 전체적인 이미지를 결정하게 됩니다. 제가 현재 몸담은 LINE UIT 조직엔 이렇게 작은 부분도 놓치지 않고 꼼꼼하게 확인하며 만들어 나가는 사람들이 함께 있습니다. 덕분에 사용자를 배려하는 코드가 무엇인지 고민하는 개발자로 조금씩 성장하고 있습니다. 이 글을 통해 함께 고민하고 노력해 주시는 팀원분들께 감사 인사를 전해 봅니다. 

  • PM2를 활용한 Node.js 무중단 서비스하기

    유니티 자격요건에 본인이 왜 충족되었는지 설명

    조성일2019-05-28

    라인에서 자바스크립트 개발을 하고 있습니다.

    이 글은 마이크로소프트웨어 393호에 기고된 글입니다. 자바스크립트는 가장 널리 사용되는 클라이언트 측 프로그래밍 언어이자 프론트엔드 웹 개발 언어 중 하나입니다. 그리고 Node.js는 Chrome의 V8 자바스크립트 엔진으로 빌드된 자바스크립트 런타임(runtime)으로 'Event Driven', 'Non-Blocking I/O' 모델을 사용해 가볍고 성능이 뛰어나 높은 평가를 받고 있습니다. 실제로 여러 글로벌 기업에선 웹 애플리케이션을 개발할 때 Node.js를 많이 선택하고 있습니다. LINE 역시 웹 테크 파트에서 진행하는 여러 프로젝트에서 SSR(Server Side Rendering)과 BFF(Backend for Frontend)등의 역할을 Node.js로 개발해 서비스하고 있습니다. 이번 글에선 이와 같이 최근에 많은 관심을 받고 있는 Node.js를 활용해서 실제 서비스를 무중단으로 운영해 본 경험을 공유하고자 합니다. Node.js의 프로세스 매니저 PM2 먼저 알아야 할 사실은 Node.js는 기본적으로 싱글 스레드(thread)라는 점입니다. Node.js 애플리케이션은 단일 CPU 코어에서 실행되기 때문에 CPU의 멀티코어 시스템은 사용할 수 없습니다. 만약 보유하고 있는 서버의 사양이 8코어이며 하이퍼스레딩을 지원한다면 최대 16개 코어를 사용 할 수 있는데요. 모든 코어를 사용해 최대 성능을 내지 못하고 오직 한 개의 코어만 사용해야 한다면 주어진 자원을 제대로 활용하지 못하는 꼴이 됩니다. Node.js는 이런 문제를 해결하기 위해 클러스터(Cluster) 모듈을 통해 단일 프로세스를 멀티 프로세스(Worker)로 늘릴 수 있는 방법을 제공합니다. 그렇다면 우리는 클러스터 모듈을 사용해서 마스터 프로세스에서 CPU 코어 수만큼 워커 프로세스를 생성해서 모든 코어를 사용하게끔 개발하면 됩니다. 애플리케이션을 실행하면 처음에는 마스터 프로세스만 생성되는데요. 이때 CPU 개수만큼 워커 프로세스를 생성하고 마스터 프로세스와 워커 프로세스가 각각 수행해야 할 일들을 정리해서 구현하면 됩니다. 예를 들어 워커 프로세스가 생성됐을 때 온라인 이벤트가 마스터 프로세스로 전달되면 어떻게 처리할지, 워커 프로세스가 메모리 제한선에 도달하거나 예상치 못한 오류로 종료되면서 종료(exit) 이벤트를 전달할 땐 어떻게 처리할지, 그리고 애플리케이션의 변경을 반영하기 위해 재시작해야 할 때 어떤 식으로 재시작을 처리할 지 등등 고민할 게 많습니다. 이런 것들은 직접 개발하기에 번거로운 작업입니다. 따라서 이런 문제를 간편하게 해결할 수 있는 무언가가 있으면 좋겠다고 생각할 수 있는데요. 다행히 이런 고민이 녹아있는 PM2라는 Node.js의 프로세스 매니저가 존재합니다. 이 PM2를 간단히 살펴보고, 서비스에 PM2를 적용해 어떻게 Node.js 애플리케이션을 무중단으로 운영할 수 있는 지에 대해서 알아보겠습니다. PM2 설치 방법 PM2는 아래와 같이 Node.js의 패키지 매니저(Package Manager)인 NPM으로 쉽게 설치할 수 있습니다. //코드1. PM2 설치 $ npm install -g pm2@latest PM2 기본 사용 방법 먼저 아래 '코드2'를 완성된 애플리케이션이라고 가정해보겠습니다. //코드2. 예시 애플리케이션 app.js //app.js const express = require('express') const app = express() const port = 3000 app.get('/', function (req, res) { res.send('Hello World!') }) app.listen(port, function () { console.log(`application is listening on port ${port}...`) }) 위 '코드2'의 애플리케이션은 아래 '코드3'의 명령어로 데몬화(daemonize)하고 모니터링할 수 있습니다. //코드3. 명령어 $ pm2 start app.js [PM2] Spawning PM2 daemon with pm2_home=/Users/gentlejo/.pm2 [PM2] PM2 Successfully daemonized [PM2] Starting /Users/gentlejo/Projects/maso/app.js in fork_mode (1 instance) [PM2] Done. 위 '코드3'처럼 아무런 옵션없이 애플리케이션을 실행하면 PM2는 기본 모드인 포크(fork)모드로 애플리케이션을 실행합니다. 앞서 언급한 것처럼 모든 CPU를 사용하기 위해서는 애플리케이션을 클러스터 모드로 실행해야 합니다. 아래 '코드4'와 같이 간단하게 설정파일을 만들어보겠습니다. //코드4. 설정파일 ecosystem.config.js //ecosystem.config.js module.exports = { apps: [{ name: 'app', script: './app.js', instances: 0, exec_mode: ‘cluster’ }] } 완성된 설정파일을 활용해 애플리케이션을 클러스터 모드로 실행할 수 있습니다. exec_mode 항목값을 'cluster'로 설정하면 애플리케이션을 클러스터 모드로 실행하겠다는 의미이고, instance 항목값을 '0'으로 설정하면 CPU 코어 수 만큼 프로세스를 생성하겠다는 뜻입니다. //코드5. 설정파일 실행 $ pm2 start ecosystem.config.js [PM2][WARN] Applications app not running, starting... [PM2] App [app] launched (4 instances) 위 '코드5'와 같이 실행하면 pm2 start app.js로 실행할 때와 다르게 클러스터 모드로 실행되고 CPU 코어 수 만큼 프로세스가 생성되며 최대 코어 수 만큼 요청을 처리할 수 있게 됩니다. 이를 통해 Node.js가 싱글 스레드라서 주어진 자원을 최대한 활용하지 못하고 하나의 CPU만 사용하는 문제를 해결할 수 있습니다. 만약 프로세스 개수를 늘리거나(scale up) 줄여야(scale down) 한다면 pm2 scale 명령어를 사용해서 실시간으로 프로세스 수를 증가시키거나 감소시킬 수 있습니다. 예를 들어, 현재 프로세스 4개가 실행 중인데 4개를 더 실행시켜 총 프로세스 8개를 실행하고 싶다면 아래 '코드6'과 같이 명령을 실행하면 됩니다. //코드6. 프로세스 늘리기 $ pm2 scale app +4 [PM2] Scaling up application [PM2] Scaling up application [PM2] Scaling up application [PM2] Scaling up application 혹은 실행 중인 8개의 프로세스가 너무 많다고 판단돼 다시 4개로 줄이고 싶다면 아래 '코드7'과 같이 명령을 실행하면 됩니다. //코드7. 프로세스 줄이기 $ pm2 scale app 4 [PM2] Applying action deleteProcessId on app [0](ids: 0) [PM2] [app](0) ✓ [PM2] Applying action deleteProcessId on app [1](ids: 1) [PM2] [app](1) ✓ [PM2] Applying action deleteProcessId on app [2](ids: 2) [PM2] [app](2) ✓ [PM2] Applying action deleteProcessId on app [3](ids: 3) [PM2] [app](3) ✓ app.js의 내용을 수정한 뒤 수정한 사항을 프로세스에 반영하고 싶다면 프로세스를 재시작해야 합니다. 아래 '코드8'과 같이 pm2 reload 명령어를 사용하면 실행 중인 프로세스를 재시작할 수 있습니다. //코드8. 프로세스 재시작 $ pm2 reload app [PM2] Applying action reloadProcessId on app [app](ids: 4,5,6,7) [PM2] [app](5) ✓ [PM2] [app](4) ✓ [PM2] [app](6) ✓ [PM2] [app](7) ✓ 현재 프로세스 상태를 간략하게 표시해주는 pm2 list 명령어를 실행하면 재시작이 잘 되었는지 확인할 수 있습니다. 아래 '코드9'를 보면 재시작 전과 다르게 restart 필드의 숫자가 '0'에서 '1'로 변경된 것을 확인할 수 있습니다. //코드9. 프로세스 상태 간략 표시 $ pm2 list 추가적인 명령어 사용 방법이나 로그를 보는 방법, 그리고 설정 파일에 설정할 수 있는 추가 옵션은 PM2 공식 사이트에서 자세하게 확인할 수 있습니다. 서비스 운영하기 이제 PM2를 활용해서 Node.js 애플리케이션을 운영할 수 있는 몇 가지 기본 지식을 갖췄습니다. Node.js로 개발한 애플리케이션을 서비스 서버로 배포하고 PM2로 애플리케이션을 실행하면 우리가 만든 애플리케이션을 사용자에게 서비스할 수 있습니다. 서비스는 오픈 이후에도 여러 상황 변화에 따라 지속적으로 변경해야 합니다. 만약 애플리케이션에 새로운 기능을 추가했거나 발견된 버그를 수정했다면, 이를 실제 서비스에 반영하기 위해선 다시 배포해야 합니다. 또한 배포가 완료된 후에는 애플리케이션의 변경을 반영하기 위해서 기존 프로세스를 재시작해야 합니다. 이때 reload 명령어를 활용하면 프로세스를 재시작할 수 있는데요. 이때 무중단 서비스를 유지하려면 몇 가지 주의해야 할 사항이 있습니다. 애플리케이션이 '코드2'의 예제 app.js 수준이라면 기본적으로 reload 명령어만 수행해도 PM2가 별다른 문제없이 알아서 프로세스를 재시작할 테고, 따라서 서비스에 영향을 주지 않고 무중단 서비스를 운영할 수 있습니다. 하지만 우리가 만드는 실제 서비스 애플리케이션들은 그렇게 가벼운 애플리케이션이 아닐 겁니다. 그저 reload 명령어만 신뢰하고 수행한다면, 배포과정에서 사용자에게 종종 에러 메시지를 보여주게 될 수도 있습니다(그렇게 당신은 사용자를 한 명 잃게 될 수도 있겠죠). LINE 타임라인 웹(Timeline Web) 프로젝트에서도 이와 같은 문제가 발생했습니다. LINE 타임라인 웹은 SPA(Single Page Application) 구조의 CSR(Client Side Rendering) 방식으로 개발된 서비스였습니다. 여기에 Node.js를 도입해 리액트(React) SSR(Server Side Rendering)을 적용하고, 백엔드와의 통신을 Node.js에서 하도록 전환하고 있었습니다. 대부분의 개발이 완료되고 RC(Release Candidate) 환경에서 한창 QA를 진행하던 중이었습니다. 그런데 QA를 통해 발견된 버그를 수정하고 이를 확인하려고 하면, 배포 직후에만 가끔씩 브라우저에 'Service Unavailable', 'ERR_CONNECTION_REFUSED' 같은 에러 메세지가 출력됐습니다. 로컬(Local)이나 베타(Beta) 환경에서는 문제가 없었습니다. 릴리스를 위한 준비 과정에서 문제가 발생한 것입니다. 아마도 대부분의 개발자들이 한 번씩 겪어봤을 문제라고 생각합니다. 이 현상은 나중에 서비스를 릴리스하고 나서 새로운 버전이나 핫픽스 등을 배포할 때 재현될 것이라고 판단했고, 원인을 찾아서 꼭 해결해야만 했습니다. 왜 이런 문제가 발생하는 걸까요? 이 문제를 해결하기 위해서 PM2가 여러 개의 프로세스를 재시작하는 방식과 과정을 알아보고, 그 과정에서 서비스 중단이 발생할 수 있는 문제를 발견하고 어떻게 대처할 수 있는지 알아보겠습니다. 우리의 목적은 '서비스 무중단을 어떻게 실현할 것인가!'입니다. 프로세스 재시작 과정 그림1. 프로세스 재시작 과정 프로세스 10개가 실행되고 있다고 가정해보겠습니다. 이런 상태에서 pm2 reload를 실행하면 PM2는 기존 '0'번 프로세스를 '_old_0' 프로세스로 옮겨두고 새로운 0번 프로세스를 만듭니다. 새로운 0번 프로세스는 요청을 처리할 준비가 되면 마스터 프로세스에게 'ready' 이벤트를 보내고, 마스터 프로세스는 더 이상 필요없어진 _old_0 프로세스(기존 0번 프로세스)에게 'SIGINT' 시그널을 보내고 프로세스가 종료되기를 기다립니다. 만약 SIGINT 시그널을 보내고 난 후 일정 시간(1600ms)이 지났는데도 종료되지 않는다면, 'SIGKILL' 시그널을 보내 프로세스를 강제로 종료합니다. 0번 프로세스의 재시작은 이런 과정을 거쳐 완료되는데요. 이 과정을 총 프로세스 개수만큼 반복하면 모든 프로세스의 재시작이 완료됩니다.  재시작 과정에서 서비스 중단이 발생하는 경우 새로 만들어진 프로세스가 실제로는 아직 요청을 받을 준비가 되지 않았는데 ready 이벤트를 보내는 경우 아래 '그림2'와 같이 과정(2)(Spawn new app)에서 프로세스를 생성한 뒤 과정(3)에서 앱 구동이 완료되기도 전에 마스터 프로세스에게 ready 이벤트를 보낸다면, 마스터 프로세스는 새로운 프로세스가 요청받을 준비가 완료됐다고 판단해 버립니다. 이에 기존 프로세스는 더 이상 필요없다고 판단하고 SIGINT 시그널을 보내 프로세스에게 곧 종료될 것을 알립니다. 여기서 만약 SIGINT 시그널을 보내고 일정시간(1600ms)이 지났는데도 프로세스가 살아있다면 이번엔 SIGKILL 시그널을 보내 프로세스를 강제 종료합니다. 만약 'App'을 실행했을 때, 매우 짧은 시간에 초기화 작업이 진행되고 요청을 받을 수 있는 준비가 완료됐다면 크게 문제되지 않을 수 있습니다. 하지만 (2)번 과정에서 새로운 프로세스를 생성하고 요청받을 준비를 하는데까지 일정시간(1600ms) 이상 걸리게 된다면 기존 프로세스는 이미 종료된 상태에서 새로운 프로세스는 사용자 요청이 유입돼도 처리할 수 없는 상황이 되어 버립니다. 즉 서비스 중단이 발생하게 되는 것입니다. 그림2. ready 이벤트 전송 시점부터 서비스 중단 가능성 발생 이 문제를 해결하기 위해서는 프로세스가 수행될 때 바로 ready 이벤트를 보내지 말고, 애플리케이션 로직에서 요청 받을 준비가 완료된 시점에 ready 이벤트를 보내도록 처리해야 합니다. 그리고 마스터 프로세스가 ready 이벤트를 언제까지 기다리게 할 것인지도 설정 파일에 명시해야 합니다. 아래 '코드10'은 '코드2'의 app.js와 '코드4'의 ecosystem.config.js에 관련 코드를 추가한 코드입니다. ecosystem.config.js에서 wait_ready 옵션을 'true'로 설정하면 마스터 프로세스에게 ready 이벤트를 기다리라는 의미입니다. listen_timeout 옵션은 ready 이벤트를 기다릴 시간값(ms)을 의미합니다. app.js에는 app.listen이 완료되면 실행되는 콜백(Callback) 함수에서 마스터 프로세스로 ready 이벤트를 보내도록 합니다. //코드10. ready 이벤트 설정 변경 //ecosystem.config.js module.exports = { apps: [{ name: 'app', script: './app.js', instances: 0, exec_mode: ‘cluster’, wait_ready: true, listen_timeout: 50000 }] } //app.js const express = require('express') const app = express() const port = 3000 app.get('/', function (req, res) { res.send('Hello World!') }) app.listen(port, function () { process.send(‘ready’) console.log(`application is listening on port ${port}...`) }) 아래 '그림3'은 ready 이벤트 설정 변경 후의 모습입니다. 그림3. ready 이벤트 설정 변경 후 클라이언트 요청을 처리하는 도중에 프로세스가 죽어버리는 경우 reload 명령어를 실행할 때, 기존 0번 프로세스인 _old_0 프로세스는 프로세스가 종료되기 전까진 계속해서 사용자 요청을 받습니다. 그런데 만약 SIGINT 시그널이 전달된 상태에서 사용자 요청을 받았고, 그 요청을 처리하는 데 5000ms가 걸린다고 가정해보겠습니다. 앞서 말씀드렸듯, SIGINT 시그널을 받은 뒤 1600ms 이후에도 종료하지 않는다면 SIGKILL 시그널을 받고 강제 종료됩니다. 따라서 5000ms가 걸리는 사용자 요청을 처리하는 도중 SIGKILL 시그널을 받고 사용자에게 응답을 보내주지 못한 채 종료될 테고, 프로세스가 강제 종료되었기 때문에 클라이언트와의 연결은 끊어지게 됩니다. 이런 경우에도 서비스가 중단됩니다. 그림4. 클라이언트 요청 처리 도중 프로세스 중단 이 문제를 해결하기 위해서는 SIGINT 시그널을 리스닝(listening)하다가 해당 시그널이 전달되면 app.close명령어로 프로세스가 새로운 요청을 받는 것을 거절하고 기존 연결은 유지하게 처리합니다. 그리고 사용자 요청을 처리하기에 충분한 시간을 kill_timeout에 설정하고, 기존에 유지되고 있던 연결이 종료되면 프로세스가 종료되도록 처리합니다. 아래 '코드11'처럼 ecosystem.config.js에서 kill_timeout 옵션을 '5000'으로 설정하면 SIGINT 시그널을 보낸 후 프로세스가 종료되지 않았을 때 SIGKILL 시그널을 보내기까지의 대기 시간을 디폴트 값 1600ms에서 5000ms로 변경할 수 있습니다. app.js에 새로 추가된 코드는 해당 프로세스에 SIGINT 시그널이 전달되면, 새로운 요청을 더 이상 받지 않고 연결되어 있는 요청이 완료된 후 해당 프로세스를 강제로 종료하도록 처리하는 코드입니다. //코드11. 클라이언트 요청 처리 설정 //ecosystem.config.js module.exports = { apps: [{ name: 'app', script: './app.js', instances: 0, exec_mode: ‘cluster’, wait_ready: true, listen_timeout: 50000, kill_timeout: 5000 }] } //app.js const express = require('express') const app = express() const port = 3000 app.get('/', function (req, res) { res.send('Hello World!') }) app.listen(port, function () { process.send(‘ready’) console.log(`application is listening on port ${port}...`) }) process.on(‘SIGINT’, function () { app.close(function () { console.log(‘server closed’) process.exit(0) }) }) app.close를 이용해 새로운 요청을 거절하고 이미 연결되어 있는 건 유지할 때, 만약 아래 '그림5'와 같이 'HTTP 1.1 Keep-Alive'를 사용하고 있었다면, 요청이 처리된 후에도 기존 연결이 계속 유지되기 때문에 앞의 방법만으로는 해결되지 않습니다. 그림5. HTTP 1.1 Keep-Alive를 사용하는 경우 이때는 아래 '코드12'와 같이 SIGINT 시그널을 받았을 때 특정 전역 플래그값에 따라 응답 헤더에 'Connection: close'를 설정해 클라이언트 요청을 종료하는 방법을 활용, 타임아웃으로 서비스가 중단되는 문제를 해결할 수 있습니다. //코드12. 특정 전역 플래그값에 따른 응답 헤더 설정 //app.js const express = require('express') const app = express() const port = 3000 let isDisableKeepAlive = false app.use(function(req, res, next) { if (isDisableKeepAlive) { res.set(‘Connection’, ‘close’) } next() }) app.get('/', function(req, res) { res.send('Hello World!') }) app.listen(port, function() { process.send(‘ready’) console.log(`application is listening on port ${port}...`) }) process.on(‘SIGINT’, function () { isDisableKeepAlive = true app.close(function () { console.log(‘server closed’) process.exit(0) }) }) 마치며 지금까지 Node.js의 프로세스 매니저인 PM2를 서비스에 적용해 Node.js 애플리케이션을 무중단으로 운영하고자 할 때 고려해야 할 사항에 대해서 알아보았습니다(물론 여기에 언급한 내용이 서비스를 무중단으로 운영하는데 필요한 전부는 아니고 이외에도 고려할 사항이 많습니다). 이 글이 PM2를 사용하여 Node.js 서비스를 운영하면서 마주칠 수 있는 이슈를 미리 파악하고 미연에 방지하는데 조금이나마 도움이 됐으면 합니다.

  • 2019 LINE API Experts에 선정된 멤버 소개

    LINE API Expert는 LINE API에 대한 해박한 지식을 갖추고 개발자 커뮤니티에서 영향력을 행사하는 유능한 개발자를 발굴하는 프로그램입니다. 선정된 개발자에겐 'LINE API Expert'라는 타이틀이 주어지는데요. LINE은 LINE API Expert에게 활동 지원을 비롯한 여러 혜택을 제공하고 있습니다.   LINE API Expert를 선정할 땐 개발자 커뮤니티에서의 영향력, 쓰기와 말하기(발표 능력)를 포함한 커뮤니케이션 능력, LINE과 관련된 소프트웨어 개발 능력, 향후 LINE과 기술 파트너가 될 수 있는 가능성 등 많은 요소를 고려하고 있습니다. 그럼 많은 후보들 중에서 이번에 새로 선정된 분들을 소개하겠습니다. 새로운 멤버를 소개합니다 일본과 태국에서 선정된 9명의 새로운 멤버를 소개합니다. 새로운 멤버의 활발한 참여로 LINE 개발자 커뮤니티가 더욱 활성화되길 기대합니다.  Japan Takeyuki Imajo Takeyuki 님은 Clova CEK(Clova Extension Kit)가 릴리스되자 바로 CEK 교육에 참석했는데요. 뭐든지 빨리 배우는 능력에 성실함까지 갖춘, 실무 강사이자 많은 개발 프로젝트에 참여한 개발자입니다. 자신의 목표가 자신과 세상을 신나게 만드는 것이라고 하네요. Takeyuki 님은 종종 오프라인 회의나 SNS에서 만난 다른 개발자들에게 Clova 스마트 스피커에 대해 극찬을 했다는데요. 또한 'Ah! Nomite! Kanji-kun!'이라는 봇도 개발했습니다. 이 봇은 파티를 열고 싶을 때 레스토랑이나 바를 쉽게 섭외할 수 있는 서비스를 제공하는 봇입니다. LIFF(LINE Front-end Framework)는 LINE 그룹과 친구들을 목록에 표시할 때 알파벳 순서대로 나열하도록 구현되어 있는데요. 봇 명칭은 이 점을 고려하여 봇이 친구 목록의 상단에 위치할 수 있도록 지었다고 합니다. 봇을 만들면서 LINE 앱의 사용자 흐름과 UX를 고려했다는 점을 알 수 있습니다. Takeyuki 님은 올해 4월 초, KAYAC에 엔지니어로 입사했다고 하는데요. 지금껏 쌓아온 경험을 잘 활용하여 앞으로도 꾸준히 새롭고 창의적인 결과를 만들어 내기를 바랍니다. Sumihiro Kagawa Sumihiro 님은 Piecemeal Technology에서 프로젝트 매니저 역할을 하는 동시에 스타트업 기업인 COMPASS에서 CTO를 맡고 있습니다. Piecemeal Technology에서는 IT 컨설턴트로서 지방 정부의 시스템 인프라 개발 지원과 같은 업무를 수행하고 있고, COMPASS에서는 LINE 봇을 이용해 'CHOICE!'라는 온라인 커리어 상담 애플리케이션을 개발하고 있습니다.  작년에 개최된 LINE BOOT AWARDS 2018에서 Clova와 Messaging API, LIFF를 잘 섞어 'Family Chore Log' 서비스를 개발해 가족 부문 상을 수상했습니다.  사용자의 니즈에 맞춘 좋은 제품을 개발하기 위해 헌신하며, 늘 놀라운 결과물로 LINE 봇 개발자들에게 영감을 주고, 꾸준히 LINE 개발자 커뮤니티에 정보를 공유해 주시는 Sumihiro 님. 앞으로도 멋진 행보 기대하겠습니다. Takuya Kanatani Takuya 님은 '개발하고 테스트하라'라는 모토를 가진 프로토타입 컨셉 개발자입니다. 현재 Kobe Digital Labo 그룹의 리더로서 신기술의 활용을 촉진하면서 또한 이니셔티브의 일원으로 시장에 잘 알려지지 않은 기술을 사용해 보고 사내 외에 후기를 공개하고 있습니다. 그는 2017년부터 VUI(Voice User Interface) 기술을 사업에 접목시키는데 집중해 왔는데요. 일본에서 구입 가능한 모든 스마트 스피커의 기술을 테스트해 보는 것은 물론 효고, 오사카, 그리고 그의 고향인 미에 현에서 VUI를 홍보하기 위한 IT 세미나도 주최했습니다. 현재 재직 중인 회사에서 음성 UI 부서의 장을 맡고 있는데요. 2018년 6월엔 VUI 커뮤니티인 'VUI Kobe'를 만들어 그곳의 대표직을 겸하고 있습니다. 작년에 열린 LINE BOOT AWARDS 2018에서는 'MY BODY: Easy Healthcare'로 RIZAP 상을 수상했는데요. Takuya 님을 LINE DEVELOPER DAY 2018에 초빙해 'The Future of VUI and the Benefits of the LINE Platform(일본어)'라는 프로그램을 진행할 수 있었던 건 저희에게 보람찬 일이었습니다. Takuya 님은 VUI 분야에 큰 기여를 한 개발자입니다. 이 분야에서 경험이 풍부한 리더로서 앞으로 그가 선보일 역량과 정교한 미학을 기대합니다. Kenichiro Nakamura Kenichiro 님은 LINE 개발자 커뮤니티에서 일인(independent) 개발자의 선두주자 역할을 맡고 있는데요. 커뮤니티를 위해 블로그를 포스팅하고 스터디 세션을 운영하고 있습니다. 또한 C# SDK와 샘플 프로그램을 개발하고 LINE 클라이언트 시뮬레이터, Clova 시뮬레이터와 같은 다양한 도구를 개발하는 것은 물론 GitHub이나 Qiita 같은 사이트를 통해 정보도 공유합니다. 그의 개발 프로젝트에는 Clova와 LINE Pay API와 같은 LINE 서비스들이 포함되어 있는데요. Kenichiro님이 개발한 'My Training' 와 'Professor Laf'는 LINE BOOT AWARDS의 결선에 진출하기도 했습니다. Kenichiro 님이 이렇게 왕성하게 활동하는 건 그가 개발자 커뮤니티에 남다른 애정을 갖고 있기 때문입니다. 앞으로도 꾸준히 LINE 개발자 커뮤니티에 새로운 기술 도구를 공유하고 업데이트하며 여러 행사도 진행해 주시길 바랍니다. Yui Nishimura 현재 고등학교 마지막 학년인 Yui 님은 Mitou Junior Super Creator로서 많은 대회에 참가하느라 바쁘게 지내고 있습니다.  그는 LINE BOOT AWARDS Grand Prix 수상자이자 SXSW Edu Student Startup Competition 결선 진출자이며, 제4회 Makers University U-18에도 참가했습니다. LINE BOOT AWARDS Grand Prix를 수상한 Yui 님의 봇 'Toubans!'는 미국 텍사스 주 오스틴 시에서 열린 SXSW Edu Student Startup Competition에 일본인으로는 처음으로 결선에 진출하였다고 합니다. 여기(영어)에서 Yui 님이 SXSW에서 선보인 발표를 확인할 수 있습니다. 향후 세계 무대에서 Yui 님이 선보일 행보가 매우 기대됩니다. Hironori Takauma Hironori 님은 IT 컨설팅 회사 i-enter Corporation의 연구개발 팀에서 cURL-ing 지원 도구를 연구하고 있습니다. 그는 'Introduction to Smart Speaker App Development for the Top 3 Smart Speakers: Amazon Echo, Google Home, and LINE Clova'라는 책의 저자인데요. 책에는 스마트 스피커 앱 개발자들이 참고할 만한 아주 다양하고 유용한 정보들이 담겨져 있습니다. 현재 Hironori 님은 주로 간사이에서 실무 강사로 강의하고 있는데요. 강의에서 스마트 스피커를 통해 구현될 수 있는, 넓은 가능성을 가진 서비스에 대한 정보를 공유하고 있습니다. 스마트 스피커와 LINE 봇, Clova 스킬 개발에 대한 정보를 널리 알리는 그의 열정이 앞으로도 꾸준히 지속되기를 바랍니다. Taiki Tanaka Miso Tanaka라고 불리기도 하는 Taiki 님은 Qiita 외 여러 기술 정보 공유 사이트에 Clova Skills에 대해 자주 포스팅하고 있습니다. 또한 오프라인 행사 주최를 포함해 다양한 방면에서 활동하며 개발자 커뮤니티에 기여하고 계십니다. 100명이 넘는 참가자가 Taiki 님이 주최한 'Play Around and Master Your Smart Speaker' 커뮤니티 행사에 참여했는데요. Taiki 님은 이 행사에서 수많은 개발자를 대상으로 일본어 VUI를 어떻게 사용하고 개발하는 지에 대해 강의했습니다. 또한 LINE BOOT AWARDS에서는 심장박동 센서를 이용해 개발한 'Heart Beat Music Recommender'로 obniz 상을 수상했습니다. 앞으로도 Taiki 님이 Clova와 LINE Things를 위한 개발에 힘쓰며 다른 개발자를 위해 지속적으로 포스팅하고 행사를 주최 해주시기를 바랍니다. Takashi Kawamoto Takashi 님은 현재 Classmethod에서 AWS(Amazon Web Services) 머신러닝과 AI 서비스를 이용한 개발 업무 컨설팅을 하며 자사 블로그에 글을 포스팅하고 있습니다(Classmethod는 AWS의 프리미엄 컨설팅 파트너이며 기술 블로그로 유명한 Developers.IO를 운영하고 있는 회사입니다). 2018년에는 Clova Skill 중 6번째로 인기가 많은 'Animal Noises Quiz(일본어)'를 개발했으며, LINE BOOT AWARDS에서 강사로 강의도 했습니다. 요즘엔 남는 시간에 VUI 앱(Clova 포함)을 개발하고 있다는데요. LINE의 Clova Skills와 클라우드 역량이 사용자의 니즈를 충족시킬 수 있도록 돕는 그의 작품을 더욱 많이 볼 수 있길 바랍니다. Thailand Aeknarin Sirisub Aeknarin 님은 까셋사 대학교(Kasetsart University)에서 컴퓨터 과학을 전공했습니다. Aeknarin 님은 6년 동안 PHP와 라라벨(Laravel) 프레임워크를 사용하며 백엔드 개발자로 근무하면서 데이터베이스의 데이터 구조 설계와 같은 업무를 수행했습니다. 2014년부터 LINE 메신저 플랫폼과 관련된 내용을 학습하고 시스템을 개발하기 시작했습니다. 또한 태국 FWD 생명보험의 일대일 채팅 모니터링 시스템도 개발했습니다. 현재 그는 태국 신생기업의 공동 창업자이자 CEO입니다. 그는 사진 촬영과 여행이 취미라는데요. 동시에 기업에 적용할만한 새로운 플랫폼 개발 기술을 탐구하며 자기 계발하기 위해 IT 책을 읽는 것도 취미라고 합니다. 여가 시간에는 종종 LINE Developer Thailand의 LINE Message API 이슈에 대응하기도 합니다.  마치며 여러 유능한 참가자 중 몇 명을 추려 LINE API Expert로 선정하는 일은 결코 쉬운 일이 아니었습니다. 이번에 선정된 9명의 LINE API Expert를 진심으로 환영합니다. 이번 LINE API Expert는 작년에 열린 대규모 해커톤 행사인 LINE BOOT AWARDS에서 상품을 기획하거나 관리, 출시한 이력, 혹은 강의하거나 수상한 이력 등이 있는 참가자 위주로 선정하게 되었습니다.  앞으로 LINE API Expert의 활발한 활동이 LINE 개발자 커뮤니티에 긍정적인 영향을 끼치기를 바랍니다. 

    유니티 자격요건에 본인이 왜 충족되었는지 설명
      ;
  • LINE 프론트엔드 개발자, 어떻게 일할까?

    유니티 자격요건에 본인이 왜 충족되었는지 설명

    홍연의2019-05-20

    Developer Relations팀에서 LINE의 개발자와 개발자 문화를 세상에 알리는 Developer Advocate로 일하고 있습니다.

    안녕하세요. Developer Relations팀의 홍연의입니다. 이번 글에서는 지난 4월 16일, 두 번째 LINE Developers Meetup을 개최한 이야기를 전해드리려고 합니다. 이번 밋업은 'LINE 프론트엔드 개발자, 어떻게 일하는가?'라는 주제로 진행되었는데요. 참가 정원 120명이 순식간에 마감될 만큼 뜨거운 관심을 보여주셔서 굉장히 뿌듯했습니다.  이번 밋업의 주제를 정하기 위해서, LINE의 프론트엔드 팀과 이야기를 나누었는데요. 아직 한국에서는 많은 분들이 LINE이 메신저 앱만 만드는 회사라고 알고 계시기 때문에, 메신저 앱뿐만 아니라 다양한 분야를 서비스하고 있고 그중 특히 웹 기술로 어떤 일들을 하고 있는지 알리고 싶다는 데에 의견이 모아졌습니다. 참가자분들을 위한 샌드위치를 준비하고, 자리를 세팅하다 보니 시간이 금방 흘렀습니다. 예쁜 LINE DEV 스티커도 참가자분들에게 인기가 많았습니다. 이번 밋업에서 어떤 내용들이 공유되었는지 궁금해하시는 분들이 많았는데요. 각 세션 내용을 간략히 정리하여 공유해 드리겠습니다. LINE의 웹 기반 서비스와 기술 첫 번째 발표는 허석 님께서 진행해 주셨습니다. 네이티브 애플리케이션(native application)을 주로 서비스하는 LINE에서 웹 기술 기반으로 일하는 개발자들이 어떤 서비스를 개발하고 있는지 소개하고, LINE에서 어떤 웹 기술들을 사용하고 있는지 소개하는 시간이었습니다. 다음 표는 LINE의 웹 기반 서비스들입니다. LINE 메신저 앱에서 사용할 수 있는 서비스들은 국내에서도 확인할 수 있고, 일본, 대만, 베트남 등 해외를 타깃으로 제공되는 서비스들도 많습니다. 분야 서비스 Web Service · LINE Timeline Web Hybrid Web App · LINE Search· LINE More Tab· LINE Chat App Messenger · LINE Chrome Messenger Library & Platform & SDK · LINE Social Plugin Blockchain · Bitbox· LINK· dApp - 4Cast, Wizball 다음 그림은 LINE에서 사용 중인 웹 개발 기술 스택인데요. 가장 중요하게 생각하는 것은 자바스크립트 영역이라고 합니다. 네트워크나 백엔드 기술도 주요하게 다루고 있는데, SSR(Server Side Rendering)을 위해 Node.js와 Express를 사용해서 WAS(Web Application Server)를 제공하고 노드 인스턴스를 관리하기 위해 PM2를 사용하고 있습니다.  그리고 LINE의 대부분의 웹 서비스나 웹 애플리케이션들은 뷰나 리액트를 이용해 Single Page Application으로 개발되고 있고, 필요에 따라 Node.js를 이용하여 SSR 같은 기술도 사용하고 있습니다. 허석 님은 이 모든 기술들을 전부 잘해야만 LINE 프론트엔드 개발자로 지원할 수 있는 것은 아니며, 빠르게 변화하는 기술을 시기에 맞게 적절하게 사용할 수 있고 자바스크립트 기초에 대한 이해와 열정이 있다면 충분히 함께 일할 수 있을 것이라고 이야기하며 세션을 마무리했습니다.   LINE 프론트엔드 개발자는 어떻게 일 하는가? 두 번째 세션은 임정인 님께서 LINE 프론트엔드 개발팀이 어떻게 일하는지 소개해 주셨습니다. 프로젝트는 어떻게 진행되는지, 코드 리뷰 등의 개발 문화, 해외 오피스 개발자들과의 글로벌 프로젝트 협업 등에 대해 이야기했습니다. LINE 내에서 프론트엔드 개발을 전문적으로 수행하는 조직이고, 같은 조직이지만 서로 다른 프로젝트에 참여하면서 주체적으로 움직이는 매트릭스 조직이라는 말씀이 인상적이었습니다. 또한 팀에서 필수로 진행한다는 코드 리뷰에 대해서도 이야기해 주셨는데요. 방식은 Git 리뷰, 미팅을 통한 리뷰, 페어 프로그래밍으로 이루어진다고 합니다. 정인 님께서 소속된 팀 멤버들의 개발 환경, 선호하는 IDE와 사용 중인 키보드(!!)까지 공개해 주셨는데, 이러한 일상적인 이야기들을 듣는 재미도 쏠쏠했습니다.   LINE 프론트엔드 개발자를 찾습니다! 세 번째 세션으로 최영규 님께서 LINE 프론트엔드 개발팀이 채용 문제를 출제하며 고민했던 과정들을 공유하고, 프론트엔드 조직에 지원하는 분들을 위한 소소한 팁들을 전해주셨는데요.  팀원들이 스스로 출제한 문제의 난이도나 풀이 시간을 파악하기 위해 실시한 Dogfooding에 21명의 부서원들이 5시간 동안 참여해 주셨다고 합니다. 큰 리소스가 필요한 상황이었지만 좋은 동료를 찾겠다는 마음으로 함께 해주셨다고 하네요. 그리고 서류를 작성하실 때는 채용 공고 내용에 주목해서 '무엇'을 만들었는지보다 '어떤 기술'을 사용해서 만들었는지를 위주로 작성해 주시면 좋다고 합니다. 인사팀 소속이 아닌 실제 LINE에서 일하고 있는 프론트엔드 개발자분이 직접 전해주는 이야기여서 LINE 채용에 관심 있는 참가자분들에게 많은 도움이 되었을 것 같습니다. 마치며 모든 행사가 마무리되고 sli.do를 통해 세션 진행 중 참가자분들이 남겨주신 질문들에 대해 답변하는 시간을 가졌습니다. 이후 행사 진행을 맡은 박민우 님이 "여러분, 한국에서도 외국과 같은 네트워킹이 가능할 거에요!"라고 말했고, 곧 참가자와 LINE 개발자가 동그랗게 둘러앉은 형태의 네트워킹이 시작됐는데요. LINE에 대한 궁금증, 신입 프론트엔드 개발자로서의 고충, 최신 프론트엔드 기술에 대한 이야기 등이 이어졌습니다. 이번 밋업을 통해 LINE의 프론트엔드 개발자들이 어떤 일을 하고, 웹과 관련된 어떤 서비스들을 운영하고 있는지 많은 분들께 알려질 수 있는 기회가 되었기를 바랍니다.  LINE에서 모집 중인 프론트엔드 개발자 채용 공고를 공유하며 글을 마치겠습니다. 다음 밋업도 많은 기대 부탁드립니다! [LINE PLUS] Global Service Web FrontEnd 개발