스토리 홈

인터뷰

피드

뉴스

조회수 1725

StyleShare Engineering Blog?!

변정훈님 강의 모습생각해보기한 번도 생각해본 적이 없다! 왜 글을 작성해야 하는가?! 왜냐하면, 우리는 글로 먹고사는 사람도 아니고, 수려한 글솜씨도 없기 때문에?! 하지만, 이미 우리 사회는 PR의 시대를 뛰어넘어 미디어의 홍수에서 살아가고 있고, 매우 쉽게 무의식적으로 많은 글을 읽고 있다.하지만, 우리가 글을 작성하기 위해서 얼마나 많은 준비가 되어 있을까? 이 글을 쓰고 있는 필자 역시 글을 써본 경험이 거의 없다. 특히 회사의 이름을 걸고 글을 쓴다는 것은 매우 부담스러운 일이다.그래서 우리는 변정훈[Outsider’s Dev Story]님을 초대하여, 그분이 생각하는 블로그 일상과 엔지니어링 블로그에 대한 생각 공유의 시간을 가져보았다.엔지니어링 블로그회사 블로그 운영을 해보았는가?아쉽게도 변정훈 님도 회사 블로그를 운영해본 적은 없다고 하신다. 그 원인을 다음과 같은 이유로 해석을 하였다.주제가 많지 않다.개발보다 우선순위가 떨어진다.누구나 처음부터 글을 잘 적을 수 있는 건 아니다.그렇다. 이 글을 쓰는 중에도 위와 공감할 수 있는 부분이 적지 않다.우선 글을 많이 써보지 못한 필자로서도 어떤 글을 적어야 할지 난감하게 느껴지고, 업무 중에서도 중요도가 떨어지는 것은 분명하다. 마지막으로 주제 선정부터 매우 어렵게 느껴진다.그런 이유로 글쓰기를 즐겨 하시는 변정훈 님 조차 회사 블로그 운영을 잘 이끌어 본 적이 없다고 하신다.변정훈 님의 블로그는 2007년 부터 총 1,300여개 글이 게시되어 있다고 한다.왜 우리는 블로그를 운영하려 하는가?여러 가지가 있겠지만, 필자가 가장 공감한 부분은 이 부분이었다.팀 내 지식/경험 공유잠재적 입사자에게 기술 스택 및 문화 공유팀 전체의 실력 향상그동안 개발일을 해오면서, 몇 년 동안 풀리지 않는 큰 숙제 중에 하나가 좋은 개발자를 찾는 것이었다. 항상 사람을 찾는 것이 어렵다. 좋은 사람의 기준이 높아서인지 더 좋은 기업이 많아서인지 알 수는 없지만, 일하면서 느낀 가장 어려운 문제 중에 하나이다. 결국, 내부의 인력을 더 좋은 사람으로 만들고, 외부의 좋은 사람과도 교류의 장을 만들 수 있다는 희망을 품을 수 있게 되는 것이다.글 작성의 문턱을 어떻게 낮춰야 할까?좋은 점은 쉽게 공감이 되지만, 언제나 가장 어려운 것은 실천이 아닐까 싶다. 특히 회사에서 업무로 이런 일이 발생된다면, 많은 사람들이 엄청난 부담감을 가질 것이고, 결국 회사 엔지니어링 블로그는 대문만 남은 유명무실한 블로그가 될 가능성이 높다.그래서 변정훈 님은 이렇게 제안하셨다.월 1개 보다 적어도 된다.주제를 계속해서 제안하고 만들어 내야 한다.돌아가면서 작성한다.챙겨주는 사람이 필요하다.글도 리뷰하는게 좋다.부담감은 의도적으로 줄여야 …특히 변정훈님은 부담감을 줄이는 방법에 대해서, 팀 공유를 해주셨는데 잠시 소개하면 다음과 같다.나보다 모르는 사람 — 나 — 나보다 잘하는 사람언제나 어떤 기술에 대하여, 나보다 잘 모르는 사람과 나보다 잘 하는 사람이 있다는 것을 인지하고, 나보다 못하는 사람을 위해서 글을 작성한다는 것이다. 그러다 나보다 잘하는 누군가가 어쩌다 피드백을 준다면 오히려 매우 감사하게 새로운 지식을 터득하게 된다는 것이다. 역시 모든일에는 긍정적인 마인드가 중요하다. 세상 어딘가에는 나의 작은 지식이라도 필요로하는 사람들이 분명히 있을테니, 작은 용기를 가지고 세상에 누군가를 위해서 작성한다면, 세상은 분명 아름다워질 것이다.좋은 글, 좋은 주제란 무엇일까?사실 가장 어려운 이야기일지도 모른다. 변정훈님은 이런 내용을 좋다고 표현하셨는데, 잠시 정리하면 다음과 같다.개발팀의 문화어떻게 일하는가?프로젝트 수행 회고실패기개인적으로 실패기가 가장 적기 어려운 글이라고 생각한다. 하지만, 누군가에게는 가장 소중한 경험이 될지도 모르니, 간접경험의 공유는 특히 소중한 것일 수 있다.만약 회사 블로그의 글이 회사 내/외부의 사람들에게 지식 공유와 전달이 목적이라면, 그리고 좋은 문화를 계속 가지고 유지하기 위해서라면, 실패기가 어쩌면 가장 소중한 경험의 공유가 아닐까 생각해본다.질문과 답변을 통한 소통의 시간마지막으로 이 날 소개 받은 좋은 글의 흐름이란 다음과 같았다.하고자 했던 일 (Context)경험한 문제 사황 정리(격리된 상황)시도해 본 방법(내가 아는 지식)왜 동작이 안되는가? 왜 동작하는가?(가설)문제 상황 재현예제 코드관련 링크개념 설명지금까지 적은 이 글을 위의 원칙대로 다시 한번 살펴본다. 부족한 부분이 있는지, 수정할 부분이 있는지…이제 부터 회사 블로그를 더욱더 적극적으로 운영해보자!!!#스타일쉐어 #개발팀 #조직문화 #블로그 #기업문화 #사내복지
조회수 1284

MySQL에서 RDS(Aurora) 로 이관하기

안녕하세요. 스티비팀 서버 개발자 이학진 입니다. 저희는 최근 서비스에서 사용 중이던 MySQL DB를 RDS로 이관하는 작업을 진행하였습니다. 무엇 때문에 이관을 결정하게 되었는지와 어떻게 이관을 진행하였는지에 대해 글을 써보도록 하겠습니다.배경stibee.com은 작년 11월에 정식 오픈한 새내기 이메일 마케팅 서비스 입니다. 사실 오픈 초기부터 얼마전까지만 해도 AWS EC2의 m4.large 인스턴스 하나로 운영되던 서비스였습니다.(사실 웹+API 서버 1대, 메일발송서버 1대)그리고 이 싱글 인스턴스에 무려 6개의 서버, mysql 1개, kafka 1개, redis 1개가 돌고 있었습니다. 그럼에도 불구하고 cpu사용률은 20%를 넘지 않았습니다.하지만 최근 사용자도 점점 늘어났고, 네이버에서 메일 수신정책을 변경하면서 메일발송서버에 대한 요청이 급증했습니다.스티비에서 네이버로 대량메일을 발송했을 때 해당 메일의 본문 링크를 자동검사하는 것을 발견했는데요, 따라서 네이버로부터 비정상적으로 많은 요청이 들어오고 있었습니다. (어떤 기준으로 이런 검사를 하는 것인지 정확한 정책은 아직 모릅니다. 담당자분 이 글을 보신다면 연락주세요. 친하게 지냈으면 합니다#슬로워크 #스티비 #개발 #서버개발 #개발환경 #MySQL #인사이트
조회수 10494

Next.js 튜토리얼 7편: 데이터 가져오기

* 이 글은 Next.js의 공식 튜토리얼을 번역한 글입니다.** 오역 및 오탈자가 있을 수 있습니다. 발견하시면 제보해주세요!목차1편: 시작하기 2편: 페이지 이동 3편: 공유 컴포넌트4편: 동적 페이지 5편: 라우트 마스킹6편: 서버 사이드 7편: 데이터 가져오기 - 현재 글8편: 컴포넌트 스타일링9편: 배포하기개요꽤 그럴듯한 Next.js 애플리케이션을 만드는 방법과 Next.js 라우팅 API의 모든 장점을 배웠습니다.대부분의 경우 데이터 소스에서  원격으로 데이터를 가져와야 합니다. Next.js는 페이지에 데이터를 가져오기 위한 표준 API를 제공합니다. getInitialProps라 불리는 비동기 함수를 사용하여 구현할 것입니다.주어진 페이지에 원격 데이터 소스를 통해 데이터를 가져오고 원하는 페이지에 props을 통해 전달할 수 있습니다. 서버와 클라이언트 둘 다 동작하도록 getInitialProps를 작성할 수 있습니다. 그래서 Next.js는 클라이언트와 서버에서 모두 사용할 수 있습니다. 이번 편에서는 getInitialProps를 사용하여 공개된 TVmaze API에서 가져온 데이터로 배트맨 TV 쇼에 대한 정보를 보여주는 애플리케이션을 구현할 예정입니다.설치이번 장에서는 간단한 Next.js 애플리케이션이 필요합니다. 다음의 샘플 애플리케이션을 다운받아주세요:아래의 명령어로 실행시킬 수 있습니다:이제 http://localhost:3000로 이동하여 애플리케이션에 접근할 수 있습니다.배트맨 쇼 데이터 가져오기데모 애플리케이션 내의 home 페이지에 블로그 포스트 목록이 있습니다. 배트맨 TV 쇼 목록을 표시할 것입니다.쇼의 데이터들을 하드코딩하는 대신에 원격 서버에서 그 정보를 가져옵시다.여기서는 TV 쇼를 가져오기 위해 TVMaze API를 사용합니다.TV 쇼 정보를 검색하는 API 입니다.먼저 isomorphic-unfetch를 설치해야 합니다. 데이터를 가져올 때 사용할 라이브러리입니다. 브라우저 fetch API 구현을 간단히 할 수 있도록 만들어진 것이지만 클라이언트와 서버 환경에서 모두 동작합니다.npm install --save isomorphic-unfetchpages/index.js를 다음과 같이 변경해주세요:위의 페이지에 있는 모든 내용은 아래에 표시된 Index.getInitialProps를 제외하고는 익숙할 것입니다:애플리케이션의 어떤 페이지에든 추가할 수 있는 정적 비동기 함수입니다. 이것을 사용하여 데이터를 가져오고 가져온 데이터를 props를 통해 페이지로 보낼 수 있습니다.보다시피 배트맨 TV 쇼 데이터를 가져오고 'shows' props를 통해 페이지로 전달합니다.위에서 보았던 getInitialProps 함수에서 가져온 데이터 숫자를 콘솔에 출력합니다.이제 브라우저 콘솔과 서버 콘솔을 살펴봅시다. 그리고 페이지를 새로고침 해주세요.페이지를 새로고침 한 후 출력되는 메시지는 어디에서 보였나요?- 서버 콘솔- 브라우저 콘솔- 둘 다- 어떤 콘솔에도 출력되지 않았다서버에서만 출력됩니다이 경우 메시지는 서버에서만 출력됩니다.이는 서버에서 페이지가 랜더링되기 때문입니다.이미 데이터를 가지고 있어 클라이언트에서 다시 정보를 가져올 필요가 없습니다.post 페이지 구현하기TV 쇼에 대한 자세한 정보를 보여주는 "/post" 페이지를 구현해봅시다.먼저 server.js를 열고 /p/:id 라우트를 다음과 같이 바꿔주세요.위처럼 바꾼 코드를 적용하기 위해 애플리케이션을 재실행시켜주세요.이전에는 title 쿼리 파라미터를 페이지에 매핑했습니다. 이제 id로 이름을 바꿔야합니다.다음과 같은 내용으로 pages/post.js를 변경해주세요.페이지의 getInitialProps을 살펴봅시다:여기에서 함수의 첫 번째 파라미터는 context 객체입니다. 정보를 가져올 때 사용할 수 있는 쿼리 필드를 가지고 있습니다.예제에서 쿼리 파라미터로부터 보여지는 ID를 선택하고 TVMaze API로부터 데이터를 가져옵니다.이 getInitialProps 함수에서 표시할 제목을 출력하는 console.log를 추가했습니다. 이제 어디에서 출력되는지 볼 수 있습니다.서버와 클라이언트의 콘솔를 둘 다 열어주세요.그 다음 홈페이지 http://localhost:3000로 이동하여 배트맨 쇼 제목을 클릭하세요.위에서 애기했던 console.log 메시지가 보여지는 장소는 어디인가요?- 서버 콘솔- 브라우저 콘솔- 콘솔 둘 다- 아무 콘솔에서도 출력되지 않는다클라이언트 사이드에서 데이터 가져오기브라우저 콘솔에서 메시지를 볼 수 있습니다.클라이언트 사이드를 통해 포스트 페이지에 이동했기 때문입니다. 그런 다음 클라이언트 사이드로부터 데이터를 가져오는 것은 가장 좋은 방법입니다.예를 들어 http://localhost:3000/p/975에 직접 이동한다면 클라이언트가 아닌 서버에서 메시지가 출력되는 것을 볼 수 있습니다.마무리데이터를 가져오고 서버 사이드에서 렌더링하도록 만드는 Next.js의 가장 중요한 기능 중 하나를 배웠습니다.대부분의 유스 케이스에서 충분히 사용할 수 있는 getInitialProps의 기본을 배웠습니다. 더 많은 것을 배우고 싶다면 Next.js의 문서 중 data fetching 문서를 참고할 수 있습니다.#트레바리 #개발자 #안드로이드 #앱개발 #Next.js #백엔드 #인사이트 #경험공유
조회수 1602

RxJava2 함수 파헤치기!

Overview지난 글 Rxjava를 이용한 안드로이드 개발에서는 RxJava의 Android 연결 방법과 기본적인 사용법을 다뤘습니다. 이번 글에서는 RxJava의 강력하고 다양한 함수들을 살펴보고자 합니다. Android에서 복잡하게 구현되는 내용들을 단 몇 개의 함수로 처리할 수 있는 RxJava를 꼭 사용해보길 권합니다.1. just2. fromArray/fromlterable3. range/rangLong4. interval5. timer6. map7. flatMap8. concatMap9. toList10. toMap11. toMultiMap12. filter13. distinct14. take15. skip16. throttleFirst17. throttleLast18. throttleWithTimeout참고: 공통적으로 사용하는 구독(수신) 클래스는 아래와 같습니다.static class CustomSubscriber<T> extends DisposableSubscriber<T> { @Override public void onNext(T t) { System.out.println(Thread.currentThread().getName() + " onNext( " + t + " )"); } @Override public void onError(Throwable t) { System.out.println(Thread.currentThread().getName() + " onError( " + t + ")"); } @Override public void onComplete() { System.out.println(Thread.currentThread().getName() + " onComplete()"); } } 1. just파라미터를 통해 받은 데이터로 Flowable을 생성하는 연산자입니다. 최대 10까지 전달할 수 있고, 모든 데이터가 수신되면 onComplete() 수신됩니다. 기본적인 Flowable 생성자 함수로 볼 수 있으며 단순 작업에서 많이 사용합니다.public static void just() { //파라미터 값을 순차적으로 송신하는 Flowable 생성 Flowable<String> flowable = Flowable.just("A", "B", "C", "D", "E", "F"); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( A ) main onNext( B ) main onNext( C ) main onNext( D ) main onNext( E ) main onNext( F ) main onComplete() 2. fromArray/fromIterablefromArray, fromIterable 함수는 파리미터로 배열 또는 Iterable(리스트 등)에 담긴 데이터를 순서대로 Flowable을 생성하는 연산자입니다. 모든 데이터를 순차적으로 송신 후 완료됩니다. 반복적인 데이터 변환 작업 같은 경우 for 문 대신 대체할 수 있습니다. 결과를 보면 main Thread 에서 작업 결과가 나오지만, flatMap 을 사용한다면 별도의 Thread로 main Thread의 부하를 막을 수 있습니다.1. fromArray public static void fromArray() { //fromArray 배열로 파라미터를 전달 받는다. Flowable<String> flowable = Flowable.fromArray("A", "B", "C", "D", "E"); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( A ) main onNext( B ) main onNext( C ) main onNext( D ) main onNext( E ) main onComplete() 2. fromIterable public static void fromIterable() { List<String> list = Arrays.asList("A", "B", "C", "D", "E"); //fromIterable 리스트로 파라미터를 전달받는다. Flowable<String> flowable = Flowable.fromIterable(list); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( A ) main onNext( B ) main onNext( C ) main onNext( D ) main onNext( E ) main onComplete() 파라미터와 함수는 다르지만 동일하게 처리된다. 3. range/rangLongrange 함수는 지정한 숫자부터 지정한 개수만큼 증가하는 Integer 값 데이터를 송신하는 Flowable를 생성합니다. rangLong 함수는 range와 동일하며 데이터 타입은 Long을 사용합니다. 두 함수 데이터 송신을 마치면 onComplete를 송신합니다.1. range public static void range() { //range(int start, int count) //start : 시작 값 //end : 발생하는 횟수 Flowable<Integer> flowable = Flowable.range(10, 5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( 10 ) main onNext( 11 ) main onNext( 12 ) main onNext( 13 ) main onNext( 14 ) main onComplete() 2. rangLong public static void rangeLong() { //range(int start, int count) //start : 시작 값 //end : 발생하는 횟수 Flowable<Long> flowable = Flowable.rangeLong(10, 5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( 10 ) main onNext( 11 ) main onNext( 12 ) main onNext( 13 ) main onNext( 14 ) main onComplete() 4. interval지정한 간격마다 0부터 시작해 Long 타입 숫자의 데이터를 송신하는 Flowable을 생성합니다. 데이터는 0, 1, 2, 4 순차적으로 증가된 데이터를 송신합니다. Android 에서는 반복적인 작업인 TimerTask를 대신해서 interval로 간단하게 처리할 수 있습니다. UI 변경이 필요한 부분에서는 interval scheduler를 AndroidSchedulers.mainThread() 를 변경해 적용할 수 있습니다.public static void interval() { //(long time, TimeUnit unit, Scheduler scheduler) //time : 발생 간격 시간 //unit : 간격 시간 단위 //scheduler : 발생 scheduler를 변경하여 사용할 수 있습니다. // ex)AndroidSchedulers.mainThread() // - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 // 1초 간격으로 데이터 요청을 송신하다. Flowable<Long> flowable = Flowable .interval(1000L, TimeUnit.MILLISECONDS).take(10); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onNext( 5 ) RxComputationThreadPool-1 onNext( 6 ) RxComputationThreadPool-1 onNext( 7 ) RxComputationThreadPool-1 onNext( 8 ) RxComputationThreadPool-1 onNext( 9 ) 5. timertimer 함수는 호출된 시간부터 일정한 시간 동안 대기하고 Long 타입 0을 송신 및 종료하는 flowable을 생성합니다. interval이 조건까지 반복적으로 송신한다면, timer는 한번만 송신하고 종료됩니다.public static void timer() { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy.MM.dd hh:mm ss"); System.out.println("현재시간 : " + simpleDateFormat.format(System.currentTimeMillis())); //(long time, TimeUnit unit, Scheduler scheduler) //time : 발생 간격 시간 //unit : 간격 시간 단위 //scheduler : 발생 scheduler를 변경하여 사용할 수 있습니다. // ex)AndroidSchedulers.mainThread() Flowable<Long> flowable = Flowable.timer(1000L, TimeUnit.MILLISECONDS); //구독을 시작한다. flowable.subscribe(value -> { System.out.println(" timer : " + simpleDateFormat.format(System.currentTimeMillis())); }, throwable -> { System.out.println(throwable); }, () -> { System.out.println(" complete"); }); } 결과 현재시간 : 2019.04.29 09:09 56 timer : 2019.04.29 09:09 57 complete 6. mapFlowable 에서 송신하는 데이터를 변환하고, 변환된 데이터를 송신하는 연산자입니다. 하나의 데이터만 송신할 수 있으며, 반드시 데이터를 송신해야 합니다. 혹여 송신되는 데이터가 null 을 포함하면 map 대신 아래의 flatMap 을사용하는 것이 좋습니다.public static void map() { Flowable<String> flowable = Flowable.just("A", "B", "C", "D", "E") //map(Function mapper) //mapper : 받은 데이터를 가공하는 함수형 인터페이스 //알파벳 값을 소문자로 변경하여 return 한다 .map(value -> value.toLowerCase()); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( a ) main onNext( b ) main onNext( c ) main onNext( d ) main onNext( e ) main onComplete() 7. flatMapflatMap은 map과 동일한 함수이지만, map과는 달리 여러 데이터가 담긴 Flowable을 반환할 수 있습니다. 또한 빈 Flowable를 리턴해 특정 데이터를 건너뛰거나 에러 Flowable를 송신할 수 있습니다.파라미터 mapper에서 새로운 Flowable의 데이터 전달이 아닌 다른 타임라인 Flowable로 작업하면 들어온 데이터 순서대로 출력을 지원하지 않습니다. 타임라인 Flowable(timer, delay, interval 등)에서는 가급적 사용을 피하거나, 순서에 지장이 없을 때 사용하는 것이 좋습니다.public static void flatMap() { Flowable<String> flowable = Flowable.range(10, 2) //flatMap(Function mapper, BiFunction combiner) //mapper : 받은 데이터로 새로운 Flowable를 생성하는 함수형 인터페이스 //combiner : mapper가 새로 생성한 Flowable 과 원본 데이터를 조합해 새로운 송신 데이트를 생성하는 함수형 인터페이스 //첫 번째 데이터를 받으면 새로운 Flowable를 생성한다. //take(3) : 3개까지만 발생한다. .flatMap(value -> Flowable.interval(100L, TimeUnit.MILLISECONDS).take(3), (value, newData) -> "value " + value + " newData " + newData); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( value 10 newData 0 ) RxComputationThreadPool-2 onNext( value 11 newData 0 ) RxComputationThreadPool-1 onNext( value 10 newData 1 ) RxComputationThreadPool-2 onNext( value 11 newData 1 ) RxComputationThreadPool-1 onNext( value 10 newData 2 ) RxComputationThreadPool-2 onNext( value 11 newData 2 ) RxComputationThreadPool-2 onComplete() 결과를 보면 각기 생성된 Flowable이 비동기식으로 송신 되기때문에 서로 다른 스레드에서 실행돼 데이터를 받는 순서대로 송신하지 않는다는 점을 주목하자 8. concatMap받은 데이터를 Flowable로 변환하고 변환된 Flowable을 하나씩 순서대로 실행해서 수신자에서 송신합니다. 다시 말해 여러 데이터를 계속 받더라도 첫 번째 데이터로 생성한 Flowable 의 처리가 끝나야 다음 데이터로 생성한 Flowable을 실행하는 것입니다.생성된 Flowable의 스레드에서 실행되더라도 데이터를 받은 순서대로 처리하는 것을 보장하지만, 처리 성능에 영향을 줄 수 있습니다.public static void concatMap() { Flowable<String> flowable = Flowable.range(10, 5) //map(Function mapper) //mapper : 받은 데이터를 가공하는 함수형 인터페이스 .concatMap(value -> Flowable.interval(100L, TimeUnit.MILLISECONDS).take(2) .map(data -> ("value : " + value + " data : " + data))); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( value : 10 data : 0 ) RxComputationThreadPool-1 onNext( value : 10 data : 1 ) RxComputationThreadPool-2 onNext( value : 11 data : 0 ) RxComputationThreadPool-2 onNext( value : 11 data : 1 ) RxComputationThreadPool-3 onNext( value : 12 data : 0 ) RxComputationThreadPool-3 onNext( value : 12 data : 1 ) RxComputationThreadPool-4 onNext( value : 13 data : 0 ) RxComputationThreadPool-4 onNext( value : 13 data : 1 ) RxComputationThreadPool-5 onNext( value : 14 data : 0 ) RxComputationThreadPool-5 onNext( value : 14 data : 1 ) RxComputationThreadPool-5 onComplete() 결과를 보면 생성된 Flowable 스레드와 데이터 순서대로 출력이 보장된다 것을 알 수 있다. 9. toListtoList는 송신할 데이터를 모두 리스트에 담아 전달합니다. 한꺼번에 데이터를 List로 가공해서 받기에 좋습니다. 하지만 많은 양의 데이터를 처리할 경우 버퍼가 생길 수 있고, 쌓은 데이터 때문에 메모리가 부족해질 수도 있습니다. 또한 수신되는 데이터는 하나이므로 Flowable이 아닌 Single 반환값을 사용합니다.public static void toList() { Single<List<String>> single = Flowable.just("A", "B", "C", "D", "E", "F") .toList(); // 구독을 시작한다. single.subscribe(new SingleObserver<List<String>>() { @Override public void onSubscribe(Disposable d) { System.out.println(Thread.currentThread().getName() + " onNext()"); } @Override public void onSuccess(List<String> strings) { //최종 완료된 리스트를 순서대로 출력한다. for (String text : strings) { System.out.println(Thread.currentThread().getName() + " onSuccess( " + text + " )"); } } @Override public void onError(Throwable e) { System.out.println(Thread.currentThread().getName() + " onError() " + e); } }); } 결과 main onNext() main onSuccess( A ) main onSuccess( B ) main onSuccess( C ) main onSuccess( D ) main onSuccess( E ) main onSuccess( F ) 10. toMaptoMap은 송신할 데이터를 모두 키와 값의 쌍으로 Map에 담아 전달합니다. 나머지는 toList의 특징과 같습니다. 송신되는 데이터 타입은 Map에 담아서 송신하는데 동일한 key에서 value는 마지막 데이터가 덮어 씁니다. 요청되는 값보다 결과 값이 적을 수도 있습니다. List 값을 손쉽게 key, value로 분리할 수 있는 함수이기도 합니다.public static void toMap() { Single<Map<Long, String>> single = Flowable.just("1A", "2B", "3C", "1D", "2E") //toMap(Fuction keySelector, Function valueSelector, Callable mapSupplier) //keySelector : 받은 데이터로 Map에서 사용할 키를 생성하는 함수형 인터페이스 //valueSelector : 받은 데이터로 Map 넣을 값을 생성하는 함수형 인터페이스 .toMap(value -> Long.valueOf(value.substring(0, 1)), data -> data.substring(1)); //구독을 시작한다. single.subscribe(new SingleObserver<Map<Long, String>>() { @Override public void onSubscribe(Disposable d) { System.out.println(Thread.currentThread().getName() + " onNext()"); } @Override public void onSuccess(Map<Long, String> longStringMap) { //최종 완료된 map을 순서대로 출력한다. for (long id : longStringMap.keySet()) { System.out.println(Thread.currentThread().getName() + " onSuccess( id : " + id + ", value " + longStringMap.get(id) + " )"); } } @Override public void onError(Throwable e) { System.out.println(Thread.currentThread().getName() + " onError() " + e); } }); } 결과 main onNext() main onSuccess( id : 1, value D ) main onSuccess( id : 2, value E ) main onSuccess( id : 3, value C ) 11. toMultiMap키와 컬렉션 값으로 이루어진 Map을 데이터로 변환하여 송신하는 함수입니다. 나머지 특징은 toList, toMap과 같습니다. toMap에서 중복되는 value를 관리하는 건 없었지만, value를 collection으로 관리하여 전달되는 데이터를 모두 수신할 수 있습니다.public static void toMultiMap() { Single<Map<String, Collection<Long>>> single = Flowable.interval(100L, TimeUnit.MILLISECONDS) .take(5) //toMultimap(Function keySelector, Function valueSelector) .toMultimap(value -> { //value가 홀수인지 짝수 인지 판단해서 key값을 리턴한다. if (value % 2 == 0) { return "짝수"; } else { return "홀수"; } }); //구독을 시작한다. single.subscribe(new SingleObserver<Map<String, Collection<Long>>>() { @Override public void onSubscribe(Disposable d) { System.out.println(Thread.currentThread().getName() + " onNext( " + d + " )"); } @Override public void onSuccess(Map<String, Collection<Long>> stringCollectionMap) { for (String key : stringCollectionMap.keySet()) { StringBuffer stringBuffer = new StringBuffer(); for (long value : stringCollectionMap.get(key)) { stringBuffer.append(" " + value); } System.out.println(Thread.currentThread().getName() + " onSuccess( id : " + key + ", value " + stringBuffer.toString() + ")"); } } @Override public void onError(Throwable e) { System.out.println(Thread.currentThread().getName() + " onError() " + e); } }); } 결과 main onNext() RxComputationThreadPool-1 onSuccess( id : 짝수, value 0 2 4 ) RxComputationThreadPool-1 onSuccess( id : 홀수, value 1 3 ) 12. filterfilter는 받은 데이터가 조건에 맞는지 판단해 결과가 true인 값만 송신합니다. 위의 just, fromArray, interval이 반복적인 케이스였다면, filter는 if문처럼 조건문의 역할을 할 수 있습니다. 반복문 함수와 조건문 함수를 같이 사용해 몇 줄 안에 for, if와 똑같이 구현할 수 있죠.public static void filter() { Flowable<Long> flowable = Flowable.interval(300L, TimeUnit.MILLISECONDS) //짝수만 통과한다. 3개만큼 .filter(value -> value % 2 == 0).take(3); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 13. distinct이미 처리된 데이터를 다시 볼 필요가 없을 때 사용하는 함수입니다. 송신하려는 데이터가 이미 송신된 데이터와 같다면 해당 데이터는 무시합니다. 이 함수는 내부에서 HashSet으로 데이터가 같은지 확인합니다.public static void distinct() { Flowable<String> flowable = Flowable.just("A", "a", "B", "b", "A", "a", "B", "b") //distinct(Function keySelector) //keySelector : 받은 데이터와 비교할 데이터를 확인하는 함수 //모두 소문자로 변환하여 알파벳 기준으로 데이터를 판단한다. .distinct(value -> value.toLowerCase()); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( A ) main onNext( B ) main onComplete() 14. take1.taketake 함수로 지정된 횟수만큼 받은 데이터를 송신합니다. 지정된 횟수에 도달하면 완료를 송신해 처리 종료합니다.2.takeUntil지정된 조건까지 데이터를 송신하는 연산자입니다. 조건이 되면 완료를 송신해 종료합니다.3.takeWhile지정된 조건이 해당할 때만 데이터를 송신하는 연산자입니다.4.takeLast데이터의 끝에서부터 지정한 조건까지 데이터를 송신하는 연산자입니다.take 함수는 한 화면에 출력되거나 칠요한 데이터만큼 리스트에서 값을 하나씩 수신할 때 사용합니다. 예를 들어 화면에 데이터가 6개가 필요하면 take를 이용해 원하는 만큼의 데이터를 가져올 수 있습니다.Flowable.take(6) 또한 이후에 나올 skip 함수를 같이 사용하면 두 번째 화면에서 필요한 데이터를 6개 가져올 수 있습니다.Flowable.skip(6).take(12) 1. take public static void take() { // 100 밀리세컨드만큼 반복하며 총 5개를 출력후 종료한다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .take(5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 2. takeUntil public static void takeUntil() { // 100 밀리세컨드만큼 반복하며 값이 5가 될때까지 송신한다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .takeUntil(value -> value == 5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onNext( 5 ) RxComputationThreadPool-1 onComplete() 3. takeWhile public static void takeWhile() { // 100 밀리세컨드만큼 반복하며 값이 5가 아닐경우까지 송신한다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .takeWhile(value -> value != 5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 4. takeLast public static void takeLast() { //100밀리 세컨트만큼 반복하며 5개의 출력중 뒤에 2개만 송신한다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .take(5) .takeLast(2); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 15. skip1.skip함수로 지정된 횟수만큼 받은 데이터 송신을 제외합니다. 지정된 횟수가 초과되면 나머지 데이터를 송신합니다.2.skipUntil지정된 조건까지 데이터 송신을 제외하는 연산자입니다. 조건이 되면 나머지 데이터를 송신합니다.3.skipWhile지정된 조건이 해당될 때만 데이터 송신을 제외하는 함수입니다.4.skipLast데이터의 끝에서부터 지정한 조건까지 데이터 송신을 제외하는 함수입니다.take와 반대의 기능을 갖고 있습니다. 보통 페이저나 리스트에서 paging을 처리할 때는 take와 skip을 혼용합니다.1. skip public static void skip() { //100 밀리세컨드만큼 반복하며 5번 발행하고, 처음 2개를 제외합니다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .take(5) .skip(2); //구독을 시잔한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 2. skipUntil public static void skipUntil() { //300밀리 세컨드만큼 반복하며 5개를 발행하고, 1000 밀리세컨드 제외 후 송신합니다. Flowable<Long> flowable = Flowable.interval(300L, TimeUnit.MILLISECONDS) .skipUntil(Flowable.timer(1000L, TimeUnit.MILLISECONDS)) .take(5); //구독을 시잔한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-2 onNext( 3 ) RxComputationThreadPool-2 onNext( 4 ) RxComputationThreadPool-2 onNext( 5 ) RxComputationThreadPool-2 onNext( 6 ) RxComputationThreadPool-2 onNext( 7 ) RxComputationThreadPool-2 onComplete() 3. skipWhile public static void skipWhile() { //300밀리세컨드만큼 반복하며 5개를 발행하고, 데이터 3이 올때까지 데이터를 제외힙니다. Flowable<Long> flowable = Flowable.interval(300L, TimeUnit.MILLISECONDS) .skipWhile(value -> value != 3) .take(5); //구독을 시잔한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onNext( 5 ) RxComputationThreadPool-1 onNext( 6 ) RxComputationThreadPool-1 onNext( 7 ) RxComputationThreadPool-1 onComplete() 4. skipLast public static void skipLast() { //1000 밀리세컨드만큼 반복하며 5개를 발행하고 마지막 2개는 제외합니다 Flowable<Long> flowable = Flowable.interval(1000L, TimeUnit.MILLISECONDS) .take(5) .skipLast(2); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onComplete() 16. throttleFirst데이터를 송신하고 지정된 시간 동안 들어오는 요청을 무시합니다. 이 함수는 View의 Event 처리에서 많이 사용됩니다. 중복되는 처리를 막기 위해 최초 실행 후 일정 시간 동안 View의 클릭 이벤트나 API 이벤트를 막을 수 있기 때문에 비동기 처리와 화면에 직접적인 피드백이 발생했을 때 throttleFirst를 자주 사용하고 있습니다. //데이터 요청이 30 밀리초마다 5번 발생합니다. //데이터 요청 발생시 100 밀리세컨트 동안 들어오는 데이터 요청을 무시합니다. // — 0 — 1 — 2 — 3 — 4 interval 30 밀리초 마다 // — — -*- — throttleFirst 100 밀리초 무시 Flowable<Long> flowable = Flowable.interval(30L, TimeUnit.MILLISECONDS) .take(5).throttleFirst(100L, TimeUnit.MILLISECONDS); flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 17. throttleLastthrottleLast 함수는 데이터를 송신하고 지정된 시간 동안 들어오는 마지막 요청을 송신합니다. 이 함수도 throttleFirst처럼 반복적인 선택 이벤트 처리에 유용하게 사용할 수 있습니다. 간단하게 장바구니 카운트 변경을 요청할 때 마지막 변경 이벤트 데이터만 처리하면 되므로 값이 선택되고 일정 시간이 지났을 때 API를 요청해 리소스 낭비를 줄일 수 있습니다.public static void throttleLast() { //데이터 요청이 1 초 마다 6번 발생합니다. //데이터 요청 발생시 2 초 동안 들어오는 마지막 요청을 송신하다. // - 0 - 1 - 2 - 3 - 4 interval 1 초 마다 // - - -* - throttleLast 2 초의 마지막 값 송신 Flowable<Long> flowable = Flowable.interval(1, TimeUnit.SECONDS) .take(5) .throttleLast(2, TimeUnit.SECONDS); flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 18. throttleWithTimeoutthrottleWithTimeout 함수는 데이터를 송신하고 지정된 시간 동안 다음 데이터를 받지 못하면 현재 데이터를 송신합니다. 완료 시엔 마지막 데이터를 송신하고 종료됩니다.public static void throttleWithTimeout() { Flowable<String> flowable = Flowable.<String>create(emitter -> { emitter.onNext("A"); Thread.sleep(1000L); // 1000 밀리세컨드 슬립 // 500 밀리세컨드 동안 데이터 다음 데이터 요청이 없으므로 A 송신 emitter.onNext("B"); Thread.sleep(300L); // 300 밀리세컨드 슬립 emitter.onNext("C"); Thread.sleep(300L); // 300 밀리세컨드 슬립 emitter.onNext("D"); Thread.sleep(1000L); // 1000 밀리세컨드 슬립 // 500 밀리세컨드 동안 데이터 다음 데이터 요청이 없으므로 D 송신 emitter.onNext("E"); Thread.sleep(100L); // 100 밀리세컨드 슬립 emitter.onComplete(); //완료 요청 시 마지막 데이터 송신 후 종료 }, BackpressureStrategy.BUFFER) .throttleWithTimeout(500L, TimeUnit.MILLISECONDS); flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( A ) RxComputationThreadPool-1 onNext( D ) main onNext( E ) main onComplete() ConclusionRxJava에서 많이 사용되고, 또 알고 있으면 좋은 함수들을 살펴봤습니다. 브랜디에서도 이 함수들을 응용해 그동안 다양한 기능을 구현했고, 복잡한 함수도 사용하고 있습니다. 지금까지는 Flowable로 송신과 수신이 1 : 1 로 진행되었지만, 다양한 수신자를 사용해 하나의 Flowable로도 다른 화면에서 여러 수신자를 등록하여 반복적인 작업을 할 수 있습니다. 덕분에 같은 작업을 코드 중복 없이 간단하게 구현할 수 있죠.다음 글에서는 2개 이상의 Flowable을 결합해 사용하는 방법과 Android View에서 RxJava를 응용하는 방법, 구독을 관리하는 방법 등 Android에서 유용하게 쓰는 방법들을 알아보겠습니다.글고재성 팀장 | R&D 개발MA팀[email protected]브랜디, 오직 예쁜 옷만
조회수 1912

입사 후 4개월, 나는 그동안 무엇을 했을까

8월 18일에 입사하여 글을 쓰는 오늘까지 4개월이란 시간이 흘렀다. 트레바리는 4개월을 한 시즌으로 묶어 운영하는 멤버십 서비스이기 때문에 트레바리에서 4개월을 일했다는 건 한 시즌에 필요한 모든 시기를 거쳤다는 의미이다. 4개월을 함께 해야지만 비로소 트레바리를 한 번 했다고 말할 수 있게 된다. 나는 이제서야 트레바리에서 한 번 일했다.트레바리에서의 한 번을 보내며 나는 그동안 무엇을 했는지 정리해보려고 한다. 할 말이 많다 보니 이번 글에서는 기능적으로 무엇을 했는지만 이야기할 예정이다. 어떻게 일했는지, 잘했던 점은 무엇이었는지, 아쉬웠던 점은 얼마나 많았는지에 대한 건 아쉽지만 다음 글에 담기로 했다. 4개월 동안 내가 진행한 일 중 큰 단위의 작업 위주로 살펴보고자 한다.4개월 동안 내가 한 일은 크게 두 가지다. 첫 번째는 기존의 웹 서비스를 개선하는 일. 두 번째는 노가다로 했던 일들에 IT를 끼얹는 일. 두 가지 일에 대한 요구 사항들은 모두 추상적인 문장으로 주어졌고, 나의 역할은 그 추상적인 요구들을 정리하여 실질적인 기능으로 정의하고 구현하는 것이었다. 그동안 어떤 요구 사항들이 있었고 그에 대해 어떤 결과물을 내었는지 정리해보았다.1. 독후감을 활성화되게 만들어주세요.입사 후 최우선으로 개선이 요구됐던 부분은 독후감이었다. 독후감은 트레바리 서비스가 독특하다는 평가를 듣는 이유 중 하나이다. 아무리 돈을 내고 온 멤버일지라도 우리가 내어준 400자의 독후감이라는 숙제를 해오지 않으면 독서 모임에 참가하지 못한다. 우리 크루들은 독후감을 통해 자신의 생각을 한번 정리하고 참가한 독서 모임이 아무런 준비 없이 맞닥뜨리는 독서 모임보다 더 풍성해짐을 안다. 그렇기에 멤버들이 트레바리 홈페이지에서 더 열심히 독후감을 쓰고, 더 많이 다른 사람들의 독후감을 읽고, 더 다양하게 대화할 수 있도록 만들어야 했다.디자인 개선[이전 디자인]일을 시작하자마자 가장 먼저 갈아치우기 시작한 건 디자인이었다. 페이지에 보이는 정보들의 가독성이 나빴다. 독후감 정보와 관련 없는 이미지 배경을 가지고 있었고, 모바일에서는 본문을 포함하여 모든 요소들의 배열이 일정하지 않았다. 가장 문제였던 점은 좋아요 기능이 있음에도 불구하고 유저들이 좋아요 버튼이 있는지를 몰라 활성화가 되지 않는 것이었다.(대표님 얼굴과 우왕이라는 글자가 떡하니 자리 잡은 곳이 좋아요 버튼이다.) 답댓글 없이 한 줄로만 나열된 댓글도 불편했다.[변경한 디자인]전반적으로 컨텐츠가 더 잘 보일 수 있도록 변경했다. 불필요한 배경 이미지를 빼고 책 정보를 추가했다. 좋아요 버튼도 보다 쉽게 인지할 수 있도록 보편적인 모양의 하트로 바꾸었다. 한 줄로만 나왔던 댓글에는 대화하는 듯한 느낌의 UI로 변경하고 답댓글 기능을 추가했다. 특히 모바일에서 더 편하게 쓸 수 있도록 각 요소들을 일정하게 배열했고, 이미지로는 보이지 않겠지만 독후감을 읽고 목록으로 다시 갈 때마다 다른 모임 정보가 뜨는 이상한 시나리오도 개선했다.넛지 만들기더 나은 디자인만으로는 부족했다. 멤버들이 실제로 더 많이 좋아요를 누르고 댓글을 달고 싶게 만들어주는 넛지가 필요했다. 좋아요 수에 따라 재밌는 워딩이 나오고, 댓글 입력 창의 워딩이 항상 다르고 등의 디테일한 요소들을 살렸다. 페이스북 공유하기 기능도 추가했으며 우리 모임에 놀러 오는 멤버들을 보여주는 UI도 추가하는 등의 작업을 진행했다. 하지만 결정적인 한 방이 필요했고 그 한 방은 이달의 독후감 기능이었다.이달의 독후감 선정 기능홈페이지 밖의 운영에서 돌아가던 이달의 독후감이라는 시스템이 있었다. 매 모임마다 가장 좋았던 독후감을 선정하는 것이었는데 잘 알지 못하는 멤버들이 많아 좋아요 수 자체가 적었고, 선정된 독후감을 찾아보기 어려워 활성화가 되지 못했었다. 그래서 이달의 독후감 시스템을 홈페이지에 어워드 형태로 옮겨오면 동기부여와 동시에 별도의 안내 없이도 이달의 독후감 시스템을 학습시킬 수 있겠다고 생각했다.그래서 결과는?결과는 데이터로 나타났다. 디자인과 기능 개선 후 독후감 한 개 당 평균 좋아요/댓글 수가 대략 150% 증가했다. 크루들이 매번 이달의 독후감을 선정하고 하이라이트 구문을 뽑는 오퍼레이션도 줄일 수 있었다. 변경 후 독후감 쓰는 것이 더 즐거워졌다는 멤버 피드백도 종종 들을 수 있었다.2. 멤버십 신청 페이지를 개선해주세요.멤버십 신청 페이지는 트레바리 멤버가 아닌 유저들이 가장 많이 보게 되는 페이지다. 트레바리가 어떤 곳인지 어필하고 결제까지 진행하게 하는 가장 중요한 역할을 하고 있다. 흔히들 말하는 판매 페이지로 트레바리에서 가장 중요한 서비스인 독서 모임을 파는 곳이다. 그 중요성에 비해 디자인과 기능이 모두 엄청나게 부실했고 개선해야 했다.디자인 개선[이전 디자인]대체 트레바리가 어떤 곳인지 알 수 있는 부분이 하나도 없었다. 독서 모임에 대한 설명마저 줄글이 전부였다. 내가 트레바리 독서 모임에 가면 어떤 분위기를 즐길 수 있고 만나는 사람들은 어떨지 상상하기 어려웠다. 모바일에서는 특히 불편했고 필수적인 정보들만 보이는 곳에 불과했다.[변경한 디자인]각 독서 모임에 대한 소개가 풍성하지만 편하도록 변경했다. 이전과 다르게 사진을 많이 활용하여 트레바리 독서 모임이 어떤 분위기인지 보여주고 싶었다. 설명 글도 더 잘 읽힐 수 있도록 배치를 중점적으로 신경 썼고 포인트 컬러를 틈틈이 사용했다. 같은 모임이지만 다양한 시간과 장소가 있는 독서 모임인 경우에는 한 페이지에서 한 번에 볼 수 있도록 구성했다. 모바일 접속자가 압도적으로 많은 만큼 모바일 UI에 많은 시간을 들였다.결제 기능 추가멤버십 신청 페이지에서 가장 큰 문제는 결제였다. 실시간 결제가 아닌 계좌 이체로만 가능하게 되어있다 보니 엄청나게 불편했다. 유저들도 수동으로 이체를 해야 했고, 담당하는 대표님도 24시간 잠도 못자며 휴대폰을 붙잡고 있다가 계좌 이체 알림이 올때마다 등록 처리를 해주어야 했다.(매 시즌 대표님 혼자 몇천 명의 계좌 이체를 확인하고 등록해주셨다. 그래서 멤버십 신청 기간 때에는 제대로 자보신 적이 없다고...)그런데 트레바리는 작은 회사에다 무형의 서비스를 팔고 있다 보니 PG사를 통한 결제를 붙이는 게 어려웠다. PG를 제외한 편하게 결제할 방법을 찾다가 토스 결제를 찾아보게 되었다. 찾자마자 바로 미팅을 진행했고 토스 측에서도 트레바리의 가치를 잘 봐주셔서 미팅부터 결제 연동까지 빠르게 진행하여 구현했다.사랑해요 토스그래서 결과는?막상 개선하여 배포하니 예상보다 저조한 유저 반응이 나타났다. 물론 지난 시즌보다는 훨씬 더 많은 유저분들이 등록하시기는 하였으나 기대했던 목표치에는 못 미쳤다. 디자인이 좋아지고 이용하기 편해지면 당연히 등록 효율이 몇 배로 높아질 거라고 생각했으나 생각처럼 되지 않았다. 신청 기간 내내 저조한 이유에 대한 가설을 세우고, 변경하고, 데이터 보기를 반복했다. 그 과정에서 몇몇 유저분들과 인터뷰를 진행했고 막판에 등록에 영향을 미치는 것은 의외로 홈페이지 사용성이 아닌 다른 곳에 있음을 발견했다. 아쉽게도 늦게 원인을 찾아 더 많은 것을 해보기 전에 신청 기간이 끝나버렸지만, 다음 시즌에는 이번 시즌보다 뾰족하고 탁월하게 개선할 수 있겠다고 생각했다. 영훈님과 함께 공부도 시작해보기로 했다.결제 부분에서는 자세한 데이터를 공개할 수는 없지만 많은 유저들이 토스를 통해 결제를 진행했다. 원래도 트레바리는 N빵 할 일이 많아 토스 송금을 이용하는 유저들이 많았지만 이번 결제 연동을 덕분에 새로 쓰게 된 분도 많아진 것 같았다. 핀테크에 대한 막연한 불안감 때문에 쓰지 않았다는 유저들도 있었지만 막상 써보니 엄청나게 편해서 놀랐다는 피드백도 많이 받았다. 아마도 트레바리에서는 앞으로 계속 토스 송금/결제를 활발하게 사용할 것 같다.3. 트레바리가 어떤 곳인지 보여줍시다.위에서 말한 등록에 영향을 미치는 것은 서비스에 대한 설득이었다. 그동안 트레바리는 지인의 소개로 오는 유저들이 많았고, 기사를 보고 오는 유저들이 많았고, 소문을 듣고 오는 유저들이 많았다. 그래서 따로 트레바리가 어떤 곳인지 잘 설명할 필요성이 적었던 것 같다. 이제는 유저들이 점점 많아지면서 트레바리가 어떤 곳인지 적극적으로 보여줄 필요가 있었고 방치되어 있던 랜딩페이지를 끄집어냈다.[이전 랜딩페이지]이곳만 봐서는 트레바리가 어떤 곳인지 알 수 없었다. 트레바리가 얼마나 매력적인지 어필이 되지 않았고 어떤 활동을 할 수 있는지도 알기 어려웠다.[변경한 랜딩페이지]트레바리가 지향하는 가치들을 더 많이 설명했다. 중간중간 트레바리 사용설명서 영상을 볼 수 있는 곳을 추가했고 실제 멤버들의 후기도 담았다. 트레바리는 독서 모임을 제공하는 서비스이므로 대표적인 독서 모임이 무엇이 있는지도 보여주고 싶었다. 각종 미디어에서 이야기하는 트레바리와 멤버들만을 위한 혜택도 정리해두었다.그래서 결과는?급하게 만드느라 트레바리의 매력을 아직 반의반도 못담았다고 생각한다. 랜딩페이지만 봐도 트레바리가 어떤 곳이고 트레바리를 통해 당신이 얼마나 더 멋있어질 수 있는지를 보여주고 싶다. 랜딩페이지는 꾸준히 만지고 다듬어야 할 과제로 남겨두었다.4. 손에 잡히는 무언가를 주기(일명 손잡무)한 시즌을 끝낸 멤버들에게 각자가 한 시즌 동안 무엇을 해왔는지 쥐여주고 싶었다. 그래서 시즌 말 약 1700명의 멤버들 모두에게 개개인이 이뤄온 활동 데이터를 이미지로 재밌게 엮어 나눠주었다. 기발한 워딩과 이미지는 이 방면에 재능이 있는 세희님과 지현님이 함께해주셨다.[1705 시즌 손잡무][1709 시즌 손잡무]1700명 모두에게 개인화된 이미지를 노가다로 만들어주는 것은 불가능했다. 자동화를 하기 위해서 SQL 쿼리를 통해 필요한 로우 데이터를 추출하고, 스케치라는 디자인 툴을 활용해 이미지 생성을 자동화했다. 덕분에 모든 멤버들의 이미지를 한땀한땀 만드는 노가다를 피하면서도 개인화된 컨텐츠를 제작할 수 있었다. 이 방법은 이다윗님의 코드로 100명 이상의 네임택 한 번에 디자인하기 글을 보고 영감을 받아 가능했다.그래서 결과는?성공적이었다. 인스타그램에 #트레바리를 검색하면 손잡무를 나눠준 시점에 많은 멤버분들이 공유해주신걸 볼 수 있다.(개근상 받으신 분들이 제일 많이 공유해주셨다.) 이미지를 공유해주시면서 4개월 동안 얼마나 즐거웠고 많이 배웠는지에 대한 후기도 소상히 적혀있는 경우도 많아 더욱 뿌듯한 결과물이었다.5. 그 외 각종 버그/개선 요구 사항 해결도 해주세요.각종 UI 및 사용성 개선여러 페이지들의 UI를 개선하고 기능을 개선하여 배포하였다. 자잘한 기능 추가부터 페이지 통째로 갈아엎기까지 손을 댈 수 있는 리소스만큼 건들여보고 개선했다. 이 과정에서는 우선순위를 정하는 일이 중요했는데 우선순위에 대한 이야기는 후에 다시 해볼 예정이다.각종 버그/요구 사항 해결 + 그에 따른 CS내가 만든 것도 많았지만(…) 그거말고도 도대체 개발자가 없을땐 홈페이지가 어떻게 굴러갔지 싶을 정도로 버그가 많았다. 버그도 많고 요구되는 개선 사항도 많았다. 줄어들고는 있지만 아직까지도 버그 및 요구 사항에 응대하는 시간이 하루에 한 시간씩은 꼬박꼬박 들고 있다. 더 많이 줄여나가는 것을 목표로 하고 있다.자동화독서 모임을 이끄는 크루들이 노가다를 하느라 고생하는 시간이 많다. 위에서 이야기 했던 계좌 이체 확인이 가장 큰 사례이다. 그 외에도 개설되는 클럽 데이터 입력을 어드민에서 며칠동안 노가다로 진행해야하는 등의 낭비가 많았다. 이런 부분에서 IT를 끼얹어 공수를 덜 들이고 빠르게 끝낼 수 있도록 엑셀 import 등의 기능을 구현했다.트레바리의 한 번을 끝마치며 나는 그동안 무엇을 했는지 정리해보았다. 쓰다보니 만족스러운 것보다 아쉬운 것들이 눈에 더 많이 들어온다. 무엇이 아쉬웠나 하면 할말이 너무나도 많아 다른 글에 써보기로 하고 이번 글은 기능적인 이야기로만 마무리했다.돌이켜 생각해보면 트레바리에서 쓰이는 기술 스택인 루비도 레일스도, 서버 인프라도 하나 모르는 나를 믿고 이 모든걸 배우고 익힐때까지 기다려준 크루들이 새삼 대단하다고 느낀다. 그 과정에서 실수로 인한 버그도 엄청 많았고 그 버그 때문에 불필요하게 운영 코스트가 늘어났을 때도 있었지만 나무란 적 한 번 없이 격려와 함께 기다려주고 믿어주었다. 그래서 더 열심히 달릴 수 있었던 것 같다.아쉬움과 감사함 때문에라도 다음 4개월에는 일을 더 '잘'하는 사람이 되어야겠다고 다짐했다. 앞으로도 계속 성공하고 실패하고, 배우고 성장한 일들을 꾸준히 기록해나가며 일을 더 잘하는 사람이 되고 싶다. 다음 4개월은 지난 4개월보다 보다 더 실질적이고 큰 변화들을 만들 수 있는 사람이 되어야 겠다. 걱정반 기대반이지만 설레는 마음으로 새로운 시즌을 맞이하며 글을 끝맺으려 한다.어떻게 하면 더 잘할지 고뇌하는 모습의 크루들#트레바리 #기업문화 #조직문화 #CTO #스타트업CTO #CTO의일상 #인사이트
조회수 2827

리디북스 웹뷰어의 이어보기를 개발하며

최근 리디북스에서는 판타지 연재물을 웹에서 바로 볼 수 있는 기능을 새롭게 선보였습니다.기존에는 별도의 앱을 설치하고 다운로드하는 과정을 거쳐야 했기에 연재물을 보는 사용성이 좋지 않았습니다만, 브라우저에서 바로 볼 수 있는 “웹뷰어” 기능을 제공함으로써 사용성을 높일 수 있었습니다.그리고 여기에 사용성을 더하기 위해 추가된 것이 이어보기 기능입니다. 짧아도 100화 이상, 길게는 1000화가 넘는 연재물에서 다음 화로의 매끄러운 연결은 매우 중요합니다. 혹은 잠시 읽기를 중단했다가 다시 돌아왔을 때, 어디까지 보고 있었는지를 빠르게 알려준다면 호흡을 이어서 작품에 더욱 몰입할 수 있을 것입니다.이어보기가 구현된 모습리디북스에 로그인되어 있다면, 이곳에서 확인하실 수 있습니다.이번 글은 이어보기 기능에 대한 개발 후기입니다. 요구 사항에 따라 여러 저장소 솔루션을 비교해 보았으며 최종적으로 Couchbase를 선택한 이유와 간단한 벤치마크 결과, 그리고 겪었던 문제를 공유합니다.요구 사항기획된 내용을 요약하니 아래와 같습니다.연재물의 가장 마지막에 읽은 화를 알 수 있다.보았던 모든 연재물에서 가장 마지막에 읽은 연재물을 알 수 있다.사용자가 본 모든 연재물 목록을 확인할 수 있다.이를 개발자 용어로 다시 풀어보면 아래와 같습니다.연재물을 읽을 때마다 연재물 ID와 화(episode) 정보를 기록한다.보았던 연재물을 최신순으로 정렬하여 가져온다.선택된 연재물의 마지막으로 읽은 화를 가져온다.목록에서 특정 연재물을 삭제한다.이어보기는 가장 마지막에 읽은 연재물을 기억하기 위해 작품을 열 때마다 해당 정보를 기록해야 합니다. 그런데 수십 화를 연달아서 보는 연재물의 특성상 내가 어디까지 읽었는지를 조회하는 것(read)보다 내가 읽은 연재물을 기록하는 것(write)이 더 많을 것으로 판단했습니다. 즉, 읽기보다 쓰기가 더 많을 것으로 예상했습니다.NoSQL을 쓰자대부분의 연산이 쓰기(write)와 관련된 이상, 어떤 저장공간을 사용할 것인지가 주된 관심사였습니다.특히 RDBMS와 NoSQL 사이에서 어떤 것을 사용할지 많은 고민과 테스트를 했고, 결국 아래와 같은 이유로 NoSQL을 사용하는 것이 적합하다고 판단했습니다.현재 사용 중인 MariaDB를 그대로 사용한다면 마스터에 부담을 줄 수 있다.별도로 MariaDB를 구성하더라도 운영 및 쓰기 분산하기가 여전히 어렵다.반면 NoSQL은 RDBMS 대비 확장(Scale out)이 간편하므로 운영에 대한 부담이 적다.단순 Key-Value 보관 용도면 충분하다.이어보기 데이터는 독립적인 성격을 가지고 있어서 다른 사용자 데이터와 JOIN을 할 필요가 없다.이어보기 데이터는 크리티컬한 트랜잭션이 필요하지 않다.MongoDB vs. Couchbase데이터를 영속적으로 유지해야 한다는 요구 사항을 충족하기 위해, Redis 등의 메모리만 사용하는 NoSQL은 제외했습니다. 물론 디스크에 기록할 수 있지만, 성능이 급감하기 때문에 실용적이지 못 합니다. 또한, 메모리 사이즈에 기반을 두기 때문에 Scale up 비용이 크고, 서비스 확장시 Scale out 빈도가 높습니다.그래서 MongoDB와 Couchbase를 비교 대상으로 했습니다. 둘 다 도큐먼트 기반의 NoSQL이고 확장이 용이합니다. 과거에는 MongoDB가 Write lock 사용에 있어서 문제점이 있었지만, 최근 버전에서는 문제가 되지 않습니다.[1] 둘 다 기업용 서비스 및 충분한 부가 기능들을 제공하므로 선택하기 어려웠지만, 최종적으로 아래와 같은 이유로 Couchbase(CE)를 선택했습니다.1. 이미 사내에서 다른 서비스에 사용되고 있습니다.가장 중요한 요인이었습니다. 더 좋은 솔루션이 있더라도 어디까지나 서버 스택을 늘리는 것 이상의 효용이 있는지를 따져보아야 합니다. 이미 사용하고 있는 솔루션이 있다면, 검증이 되었을 뿐만 아니라 개발 및 운영 경험도 활용할 수 있습니다.2. 이어보기는 복잡한 쿼리(Query)가 필요 없습니다.이어보기에서 사용할 쿼리는 간단하기 때문에 Couchbase의 뷰(View)만으로 충분했습니다.Couchbase, 실제 성능은 어떨까?테스트를 하기 전 우리가 어떤 식으로 사용할 것인지 정리해야 합니다. 애플리케이션 액세스 패턴이나 동시성 문제, 데이터 구조화 등을 파악하고 그에 맞는 테스트를 진행해야 합니다. 이번 이어보기는 쓰기 연산이 보다 많기 때문에 이로 인한 뷰의 인덱싱(Indexing)에 초점을 맞추고 테스트를 진행했습니다.성능을 위협하는 요소들View IndexingCouchbase는 MapReduce를 이용하여 뷰를 제공합니다. MapReduce는 일반적으로 리소스를 많이 소모하는 동작입니다. 그래서 Couchbase는 버킷의 새로 갱신된 데이터만 인덱싱하는 Incremental MapReduce라는 기법을 적용해서 리소스 소모를 줄였다고 합니다.[2] 하지만 해당 작업으로 인한 부하는 여전히 발생합니다.Auto CompactionCouchbase는 데이터와 인덱스를 디스크에 데이터를 저장할 때 파일에 추가하기(Append) 모드로만 쓰기를 수행합니다.[3] 그리고 오래되고 불필요한 데이터들은 추후 한꺼번에 정리하는데, 이는 디스크 쓰기 성능을 최대화하기 위함입니다.그런데 이렇게 추가만 하게 되면 오래된 정보들은 파일의 앞에 쌓이게 됩니다. 그리고 사용하지 않게 된 데이터도 남아있습니다. 이를 주기적으로 정리해서 최적화하는 작업을 Auto Compaction이라고 합니다. 뷰의 인덱스는 디스크에 존재하기 때문에 디스크 작업이 있으면 인덱싱에 영향을 미치게 됩니다.성능 테스트Couchbase는 기본적으로 5,000ms마다 Index를 업데이트합니다.[2] 그리고 데이터를 비동기적으로 응답합니다. 비동기는 응답속도를 빠르게 하지만, 데이터 불일치가 발생할 수 있습니다. 데이터 불일치가 신경 쓰이고 이 시간이 길다고 생각되면, stale 옵션을 지정해서 뷰의 인덱스를 업데이트할 수 있습니다.이어보기는 뷰가 간단하기 때문에 응답시간에 큰 문제가 없을 것으로 예상하고 stale 옵션을 꺼두었습니다. 이 옵션은 뷰를 조회했을 때 버킷의 변경사항에 따라 뷰를 인덱싱하고 데이터를 응답합니다. 하지만 예상한 것과 같이 실제로도 응답시간이 짧은지 확인할 필요가 있습니다. 그래서 다음과 같이 테스트를 진행했습니다.테스트 환경은 아래와 같이 2-tier로 준비하고 요청을 늘려가면서 RPS를 측정했습니다.서버 구성OS: Ubuntu 14.04Application: Couchbase Server (CE) 3.1.3클라이언트 구성클라이언트 1개에서 50개의 세션으로 요청10만 사용자 가정책은 1만개의 책중 랜덤으로 선택됨요청의 70%는 책 읽기(Bucket Write)요청의 30%는 연재물의 마지막에 읽은 책 가져오기(View Read)그래프 분석성능 테스트 주요 지표RPS : Response Per SecondSP : Saturation PointBuckle zone : 시스템 과부하로 인해 내부 자원이 서로 경쟁상태나 적체 상태가 심해지기 때문에 최대 처리량보다 더 떨어지는 경우가 발생함성능테스트 결과그래프를 보면 요청이 늘어남에 따라 RPS가 선형으로 증가하지만, SP인 8,000 RPS에 도달하고 나서 Buckle zone에서 7,000 RPS로 수렴하고 있습니다. 물론 1개의 클라이언트에서 세션을 생성해서 테스트를 진행했기 때문에 서버의 성능 부족이 아닌 클라이언트의 병목 현상이 원인일 수 있습니다. 또한 JMeter나 다른 부하 테스트 툴을 사용하지 않고 간략하게 만든 테스트 툴을 사용하였기 때문에 수치가 부정확할 수 있습니다. 그러나 어디에서 병목이 있었든 현재 이 이상의 성능이 필요하지 않기 때문에 테스트 결과에 만족할 수 있었습니다.이어보기 배포 후모바일 브라우저 캐시 문제이어보기 기능을 배포하자마자 당일 저녁 이슈 하나를 접수했습니다. 아이패드와 PC를 번갈아 이용할 경우 이어보기 데이터가 맞지 않다는 것이었습니다.데이터를 쌓을 때 모든 이력을 기록하지는 않았지만, 다행히도 Couchbase에 이용기기와 시간은 기록하였기 때문에 이를 바탕으로 디버깅을 할 수 있었습니다. (서비스 초기라 할지라도 최대한 많은 이력을 남기는 것이 중요함을 다시 느꼈습니다)원인은 아이패드의 멀티태스킹으로 인한 캐시 소멸이었습니다. 아이패드 브라우저의 캐시가 소멸되면서 마지막으로 열어두었던 페이지가 강제적으로 리로딩되었고, 이때 의도치 않게 마지막 위치 정보가 덮어씌워진 것입니다.이 문제는 기술적으로 해결이 쉽지 않아 결국 기획을 수정하게 되었습니다. 사용자가 해당 책을 읽었다고 판단하는 기준이 “페이지를 열어본 즉시”였다면, 이를 “페이지를 열고 수 초 이상을 유지”하는 것으로 기준을 변경하였습니다. 물론 근본적인 해결책은 아니었지만, 실제 사용에는 지장이 없는 합리적인 해결책이라고 생각합니다.Key 구조의 변경 및 동시성 문제Couchbase는 높은 성능을 위해 메타데이터(Key + @)를 모두 메모리에 적재하는 특징이 있어서, Document 하나가 평균 350Byte를 차지하고 있었습니다. 따라서 현재 상태로 1000만개의 데이터를 저장할 경우 최소 3.5G의 메모리를, 2개의 사본(Replica)를 유지할 경우 약 10.5G의 메모리를 사용하게 될 것으로 예상되었고 이는 큰 부담으로 다가왔습니다.처음에는 단순히 “사용자ID_연재물ID” 형태의 Key를 사용하였지만, 보다 빠르게 증가할 것으로 예상되는 것은 사용자보다 연재물 이었으므로 아래와 같이 Key값을 변경하여 메모리 사용량을 크게 줄였습니다.// U_id : S_id 조합을 사용하면 Key가 엄청 많아진다. // 그래서 사용자당 Key를 100개로 제한하도록 한다. Count = 100 Key = '사용자ID' + ('연재물ID' % Count) 그런데 이렇게 Key 구조를 변경하였더니, 간단한 업데이트 동작임에도 불구하고 정상적으로 수행되지 않는 경우가 빈번하게 발생하였습니다. 이유는 낙관적 동시성(Optimistic concurrency) 모델의 특징 때문이었는데, Couchbase는 명시적인 잠금 이외에도 “Check and Set(CAS)”이라는 기능을 제공하고 있었습니다.공식 문서의 예제를 참고하여 아래와 같이 로직을 수정한 뒤로는 다행히도 동시성 문제가 아직까지 발생하지 않고 있습니다.boolean updateUsingCas(key, value) {  for (tryCount = 0; tryCount < MAX>    orgValue, cas = getValueAndCas(key)           // Update the original value.     // newValue = ... if setValueWithCas(key, newValue, cas)      return SUCCESS sleep(0.1) // 부하를 줄이기 위해  }  return FAIL } 맺으며동작하는 서비스에 새로운 기능을 추가한다는 것은 어려운 일입니다. 특히 새로운 데이터 스토리지를 필요로 하는 일이라면 더더욱 어렵다고 생각합니다. 그리고 그럴 때일수록 설계에 많은 시간을 들여야 한다는 것을 느꼈습니다. 설계 초기에는 RDBMS의 샤딩까지 고려하였지만, 요구 사항을 구체화할수록 단순 Key-Value로도 같은 문제를 해결할 수 있음을 깨달았기 때문입니다.또한, 서비스 개발에 있어서 어려운 문제를 마주했을 때 기술적으로만 접근할 것이 아니라 고객이 정말 원하는 것이 무엇인지를 고민하여 기획적으로 해결하는 능력도 중요하다는 것을 실감하였습니다.마지막으로 Couchbase는 현재로서도 꽤 좋고 앞으로도 많은 발전이 기대되는 NoSQL입니다. 도입을 고민하시던 분들께 조금이라도 도움이 되었기를 바랍니다.참고자료[1] MongoDB - Concurrency[2] Couchbase - Views Operations[3] Couchbase - File write#리디북스 #개발 #개발자 #서버개발 #서비스개발 #고객중심 #기능개발 #Couchbase #인사이트 #개발후기
조회수 2183

비전공자를 위한개발자 되기 5 스텝

안녕하세요. 언제 어디서나 함께하는 코딩 교실 엘리스입니다 :)아이디어만 좋다면 뭐든 실현해볼 수 있는 시대! 지금은 '프로그래밍'이라는 강력한 무기를 통해 원하는 세계를 실현할 수 있는 잠재적 가능성이 폭발적인 때입니다. 그리고 그 기회는 비단 '개발자'라는 특정 직업에 국한하지 않더라도 각계 분야에 펼쳐져 있는데요. 이미 마케터, 기획자, 디자이너, 콘텐츠 창작자, 금융업계 종사자, 지리학자, 연구원 등 다양한 분야의 많은 사람들이 프로그래밍을 통해 각자의 영역과 세계 곳곳을 새로운 곳으로 만들고 있습니다.높은 급여와 삶의 질을 보장하고 나의 꿈을 펼칠 수 있는 탁월한 수단인 프로그래밍.프로그래밍을 업으로 삼고 있는 사람들의 시작은 어땠을까요?이 글에서는 소프트웨어 엔지니어가 되고자 이제 막 마음먹은 분들을 위해 프로그래머가 되기 위한 다섯 가지 짚고 넘어가면 좋을 팁들을 알려드릴게요.STEP 1. 개발 친화적인 환경 찾아가기서당개 삼 년이면 풍월을 읊는다컴퓨터 공학 전공자와 비전공자가 가지게 되는 가장 큰 차이는 무엇일까요? 개발에 대한 이론 지식? 개발 능력?물론 모든 게 상대적인 것이겠지만 일반적으로 한 가지 큰 차이가 있다면 바로 '환경'이라고 할 수 있을 듯합니다. 내 주변에 개발과 관련된 자원이 얼마나 풍부한가 하는 점입니다.전공자가 개발을 시작하고자 마음을 먹으면 주위에서 좋은 리소스를 쉽게 찾을 수 있습니다. 한편 비전공자는 개발 공부를 시작하려고 할 때 레퍼런스로 삼을만한 좋은 예가 없으니 망망대해에 홀로 떠있는 기분이 들 수밖에 없겠죠! 그렇다고 해서 반드시 컴퓨터 공학 전공에서부터 다시 시작하거나 고액의 학원에 다닐 필요는 없습니다. 먼저 개발과 관련된 인적, 물적 자원이 풍부한 곳으로 적극적으로 다가가보세요. 작은 환경의 변화가 큰 변화의 시작점이 될 수 있습니다.엘리스가 추천하는 방법!온라인 커뮤니티 활동하기 : 코딩과 관련된 페이스북 그룹에 가입하여 많은 정보를 접하고 질문도 하면서 활동해보세요. 나와 비슷한 상황인 사람을 만나 서로 도움을 주고받을 수도 있고, 내 롤모델이 될만한 훌륭한 개발자를 만나 공부의 동력이 될지도요!개발 동아리, 스터디 등에 참여하기★ 엘리스 코딩 클래스 활용하기 : PC로도, 모바일 앱으로도 언제 어디서든 프로그래밍을 위한 환경에 접속하세요! 엘리스에 로그인하는 것만으로 공부하기 위한 모든 리소스를 얻을 수 있을 뿐만 아니라 과목별 채팅방을 통해서 함께 공부하고 있는 수강생들, 과목 튜터와의 활발한 대화에 참여할 수 있습니다. STEP 2. 강력한 동기와 조력자 만들기하늘은 스스로 돕는 자를 돕는다컴퓨터 공학 전공자라고 하면 모두 다 개발을 잘할까요? 적어도 아주 조금은 더 잘할까요? 대답은 NO!아무리 많은 이론을 배웠다고 해도 직접 개발을 하지 않는다면 아무런 소용이 없겠지요. 이해도가 다르기 때문에 배움의 속도는 조금 다를 수도 있겠지만 이런 차이보다는 개인의 학습 의지와 동기가 얼마나 분명하냐가 더 중요합니다.막연하게 '개발자'라는 너무 먼 목표만 보고 달리는 것보다는 보다 가까이에 있고 달성하기 쉬운 분명한 목표를 단계별로 설정해보세요. 그리고 '즐거움'을 느낄 수 있는 수단을 찾아 목표 달성을 위한 집중력을 높이세요. 동시에 내가 어려움에 처하거나 헤매고 있을 때 도와줄 조력자가 있다면 금상첨화!Photo by Mimi Thian on Unsplash엘리스가 추천하는 방법!동기 부여를 위한 작은 목표 설정 : 지식 습득 및 학습과 관련된 목표로 그룹 스터디 참여, 부족한 부분의 프로그래밍 강의 완강, 책 한 권 떼기 등이 있을 수 있고, 더 적극적인 형태의 개발 경험을 위해 공모전, 경진 대회 등 기간과 보상이 정해져 있는 대외 활동 참가 및 수상도 좋은 목표가 될 수 있을 거예요.★ 엘리스 코딩 튜터 활용하기 : 엘리스에는 학습을 도와주는 튜터가 있습니다. 엘리스 튜터는 답을 알려주는 사람이 아니라 답을 찾는 법을 알려주는 길잡이입니다. 공부하다가 막힐 때, 길을 잃은 것 같을 때 엘리스 튜터를 멘토로 삼아 보세요! 구독 및 트랙 이용 시 담당 튜터가 배정되어 개인 채팅방을 통해 1:1 튜터링을 받을 수 있고, 클래스 수강 시 단체 채팅방을 통해 언제든 질문할 수 있습니다.STEP 3. 원하는 개발 분야 탐색해보기  콩 심은 데 콩 나고 팥 심은 데 팥 난다개발에는 아주 숱~한 다양한 분야가 있습니다. 그리고 그 분야에 따라 특성도, 익혀야 하는 언어와 기술도 천차만별인데요. 아래 몇 개의 개발 분야와 사용 언어 및 기술에 대해서 적었으니 참고해보세요. 그리고 이보다 더 다양한 개발의 세계를 탐색해보면서 흥미가 가는 분야가 있다면 구체적으로 검색하고 공부를 시작할 계획을 세워보세요.Photo by Victoriano Izquierdo on Unsplash잘 모르겠다 or 코알못이다파이썬은 분야를 막론하고 많은 분야에서 사용되며 익히기에 쉬워 처음 코딩을 시작하는 입문자에게 가장 적합한 언어 중 하나입니다. 개발 언어부터 접해보고 싶다면 파이썬 언어 학습에서 시작해보세요!웹 개발 '콩 심은 데 콩 나고~'라는 속담을 인용했지만, 사실 다양한 개발 영역의 많은 지식들이 서로 겹치는 부분도 있고, 어느 한 분야를 잘할 수 있을 때 다른 분야로 전향하거나 옮겨가는 일은 보다 수월할 수 있습니다. 개발의 시작을 보다 쉽게 하고 싶다면 웹 개발부터 접근해보세요. 공부할 수 있는 자원이 풍부하고 추후 다른 개발 분야로의 전향도 가능하기 때문이에요.프론트엔드프론트엔드 개발은 주로 웹 환경에서 사용자와 맞닿는 가시적인 부분을 개발하는 영역입니다. 사용자가 코드를 작성하지 않고도 컴퓨터에게 명령을 내리는 등의 의사소통을 그래픽적으로 쉽게 할 수 있도록 가시적으로 만들어주는 것이 바로 프론트엔드 개발자의 역할이라고 할 수 있는데요. 예를 들어 엘리스에 로그인하고 싶을 때 '로그인 버튼을 클릭'하여 쉽게 로그인할 수 있는 인터페이스도 프론트엔드에 해당합니다. * 익혀야 하는 기본기 : HTML, CSS, JavaScript* 좀 더 나아가서 : JavaScript의 프레임 워크인 React.js 또는 Vue.js 또는 Angular.js 백엔드/서버백엔드 개발은 웹 환경에서 보통 사용자에게는 보이지 않는 서버(컴퓨터) 단의 개발을 의미하며, 사용자가 웹 상에서 활동함으로 인해 쌓이는 데이터가 모이는 DB(Data Base)를 다루는 영역을 개발합니다.* 익혀야 하는 기본기데이터베이스에 대한 지식 : MariaDB, PostgreSQL, MongoDB 등. 서버 쪽의 언어- 금융, 제약 등 전통적인 대기업 : Java의 프레임 워크인 Spring을 많이 사용- 과거 많이 쓰이던 기술 : Php(학습 속도와 개발 속도가 빠르며 무료!)를 많이 사용- 요즘 떠오르는 기술 : Python 기반 프레임 워크인 Django 또는 Flask. JavaScript의 프레임 워크인 Node.js* 좀 더 나아가서 : 클라우드 컴퓨팅 기술 Amazon AWS 또는 Azure에 대한 지식데이터 사이언스 - 데이터 분석가21세기에 가장 각광받는 직업 중 하나로 떠오른 '데이터 사이언티스트'에 대해서 모두 다 한 번쯤은 들어보셨을 거예요. 데이터 사이언스 분야에도 아주 복잡하고 다양한 영역들이 존재하는데요. 통상 데이터 사이언스라고 하면 수학 및 통계에 대한 지식, 컴퓨터 공학에 대한 지식, 인공지능 및 머신러닝과 관련된 기술을 사용하게 됩니다. 너무 많아 보이나요? 아래에는 데이터 사이언스의 많은 영역 중에서도 '데이터 분석가'로서 꼭 알아야 하는 내용을 적었습니다.* 익혀야 하는 기본기수학적 지식 : 통계, 선형대수학분석을 위한 언어 : Python, R* 좀 더 나아가서 : 머신러닝 기술임베디드 개발계산기, 에어컨, 자동차 등의 기계가 일정 기능을 컴퓨터처럼 수행할 수 있도록 기계 내부의 하드웨어 시스템을 구축하는 것이 임베디드 개발입니다. 사물 인터넷(IoT, Internet of Things)이나 하드웨어 부품과 관련된 분야에 관심이 간다면 임베디드 개발에 대해서 좀 더 알아보세요!* 익혀야 하는 기본기임베디드 개발 언어- 가장 많이 사용하는 언어 : C언어 - 국내 전통적인 대기업 : Java- 수요가 많은 언어 : Python (임베디드 분야에서도 빠지지 않고 자주 사용하는 언어! 국내 채용 사이트에서 임베디드 관련 개발 스택으로 많이 요구.)* 좀 더 나아가서 : 무선 통신 기술에 대한 지식*(공통) 개발자라면 익히고 있어야 할 기본기 : Git을 사용한 버전 관리 방법엘리스가 추천하는 실습 기반 과목HTML/CSS | JavaScript | 모바일 웹 코딩Git과 Git 버전 관리 (6월 오픈 예정)Python 기초 I | Python 기초 IIC 언어 | C++Java 기초 및 심화인공지능/머신러닝 기초 | 프로그래밍 수학데이터 분석 | Numpy, Pandas | 크롤링 | Kaggle 문제R 기초 |  R 패키지 | R 데이터 분석STEP 4. 실습, 프로젝트 기반으로 공부하고 개발 경험 쌓기구슬이 서 말이어도 꿰어야 보배다책을 사고 인강을 결제해도 직접 만들어보면서 익히지 않으면 절대 내 것이 될 수 없는 것이 또 개발!처음 언어를 익히는 단계에서부터 실습 기반으로 직접 코딩하고 그 결과를 확인해보면서 학습하는 것이 중요해요! 필요한 공부를 실습 단위로 쪼개어 직접 구현해보면서 익히고, 좀 더 나아가서는 프로젝트 단위로 구현하면서 실전 기술을 습득해보세요. 또한 실무에서는 혼자 개발하는 것이 아니라 뭐든 '협업'해야 하기 때문에 혼자 하는 프로젝트 외에도 여러 사람들과 함께하는 그룹 프로젝트의 경험이 큰 도움이 될 거예요. 자기소개서, 포트폴리오, 면접 시에도 어떤 프로젝트에서 내가 맡은 부분은 어느 부분이었고 어떻게 주도적으로 이끌었는지가 관건이 될 수 있습니다.엘리스가 추천하는 방법!★ 온라인 코딩 실습으로 기본기 다지기 : 엘리스는 별도의 코딩 환경 세팅 없이 온라인에서 바로 코딩 문제를 풀고 내가 짠 코드의 결과를 확인할 수 있어서 실습 기반으로 학습하기에 탁월한 플랫폼입니다. :) KAIST, SKT, 삼성 SDS 등에서도 활용하는 검증된 플랫폼에서 코딩 실습으로 기본기를 다지세요!프로젝트 단위로 혼자서 만들어보기 : 프로그래밍 언어의 기본에 익숙해졌다면, 직접 A to Z를 구현하는 작은 프로젝트를 통해 실제 필요한 기술이 뭔지 파악해가며 실전 기술을 익혀보세요. 그룹 프로젝트에 참여해서 협업 경험을 통해 익히기 : 취업을 위해서 중요한 것 중 하나인 '협업'능력! 그룹 프로젝트에 참여하여 비단 개발 실력뿐만 아니라 실무에 필요한 다양한 역량 또한 길러보세요.STEP 5. 포트폴리오, 시험 준비하고 개발 직군에 지원하기시작이 반, 그 이상이다!아시겠지만 개발자가 되면 끝인 그런 일은 없겠죠. (어떤 직무에서도 마찬가지일 거예요.) 끊임없는 공부, 새로운 기술 연마, 리팩토링, 문서화, 코딩 공부 코딩 공부!그러니 완벽에서 시작해야 한다는 생각은 버리고 지금까지 최선을 다해온 결과물을 가지고서 개발 직군에 지원하세요. 실제 개발자로 일하게 되면 그 속에서 배우고 성장할 수 있는 자원이 훨씬 더 많아집니다!'자리가 사람을 만든다'라는 말이 괜한 말이 아니니, 더 큰 성장과 가능성이 있는 곳으로 가기 위한 준비와 지원을 주저 없이 해보시길 바라요!Photo by Green Chameleon on Unsplash엘리스가 추천하는 방법!나를 잘 보여줄 포트폴리오 만들기 : (사용한 언어 / 프레임 워크 / 앞의 것을 적용하여 프로젝트에서 내가 한 역할) 별로 정리해두고 내가 커밋한 코드와 함께 보여주기.   블로그 쓰기 : 거창한 것이 아니어도 좋으니 공부하면서 느꼈던 것, 새로 알게 된 지식들, 프로젝트하면서 고민했던 것들을 블로그로 정리해보세요. 내가 구현한 것들을 이미지를 통해서 가시적으로 보여줄 수 있다면 금상첨화!★ 엘리스에서 알고리즘 시험 준비하기 : 이미 많은 수강생 분들이 엘리스 알고리즘 과목을 통해서 코드를 발전시키고 알고리즘 시험 및 취업에 성공하고 있습니다. :) 대기업 입사를 준비하시는 분이라면 엘리스 알고리즘 과목들을 꼭 수강해보세요.이다음의 6번째 스텝은 무엇이 될까요? 아마도 1~5 스텝을 계속 반복해나가면서 익숙해지고, 다른 역할로 각각의 스텝에 참여하게 되는 일이 아닐까요.엘리스는 누구나 프로그래밍을 통해 원하는 일을 할 수 있도록 좋은 강의 콘텐츠와 서비스, 플랫폼으로 여러분의 다섯 스텝에 함께하고자 합니다. :) 막막한 초심자 분들에게 앞으로의 방향성을 그려보는 데에 조금이라도 도움이 되길 바라며 글을 발행합니다.그럼 엘리스에서 만나요! >> 엘리스 아카데미 바로가기* 이밖에 조언, 첨언, 질문 등을 댓글로 남겨주시면 이 글의 독자분들에게 큰 도움이 됩니다.
조회수 2612

Next.js 튜토리얼 8편: 컴포넌트 스타일링

* 이 글은 Next.js의 공식 튜토리얼을 번역한 글입니다.** 오역 및 오탈자가 있을 수 있습니다. 발견하시면 제보해주세요!목차1편: 시작하기 2편: 페이지 이동 3편: 공유 컴포넌트4편: 동적 페이지 5편: 라우트 마스킹6편: 서버 사이드 7편: 데이터 가져오기 8편: 컴포넌트 스타일링 - 현재 글9편: 배포하기개요지금까지 컴포넌트를 스타일링 하는 것을 미뤄왔습니다. 그러나 이제는 몇 가지 스타일을 적용해볼만 합니다.React 애플리케이션에는 컴포넌트를 스타일링 할 수 있는 여러가지 기술들이 있습니다. 크게 두 가지 방법으로 분류할 수 있습니다:1. 전통적인 CSS 파일 기반의 스타일링 (SASS, PostCSS 등)2. CSS in Js 스타일링 결과적으로 전통적인 CSS 파일 기반의 스타일링(특히 SSR)은 실용적인 문제가 많아 Next.js에서 스타일을 지정할 때는 이 방법을 사용하지 않는 것이 좋습니다. 대신 CSS in JS 방법을 추천합니다. 이 방법은 CSS 파일들을 불러오는 것보다 개별적인 컴포넌트 스타일링 할 때 사용 할 수 있습니다.Next.js는 styled-jsx라는 CSS in JS 프레임워크를 미리 설치해두었습니다. 컴포넌트에 이미 익숙한 CSS를 작성할 수 있습니다. 이 CSS는 해당 컴포넌트에만 적용되며 심지어 하위 컴포넌트에도 적용되지 않습니다.이는 CSS가 범위가 있음을 뜻합니다.styled-jsx를 어떻게 사용할 수 있는지 살펴봅시다.설치이번 장에서는 간단한 Next.js 애플리케이션이 필요합니다. 다음의 샘플 애플리케이션을 다운받아주세요:아래의 명령어로 실행시킬 수 있습니다:이제 http://localhost:3000로 이동하여 애플리케이션에 접근할 수 있습니다.home 페이지 스타일링하기home 페이지(pages/index.js)에 스타일을 추가해봅시다.간단히 pages/index.js를 다음과 같이 변경해주세요:   <style jsx> 엘리먼트를 살펴봅시다. 이것은 CSS를 작성하는 곳입니다.코드를 바꾼 후 블로그 home 페이지는 다음과 같이 보일 것입니다:위의 코드에서 스타일 태그 안에 직접 스타일을 작성하지 않고 템플릿 문자열 안에 작성하였습니다.템플릿 문자열({``}) 없이 직접 CSS를 작성해봅시다:어떤 일이 일어날까요?- 아무 일도 일어나지 않는다.- 새로운 스타일이 적용된다.- "문법 에러: 기대되지 않는 토큰"이라는 에러가 발생한다.- "허용되지 않는 스타일 제공자"라는 에러가 발생한다.스타일은 템플릿 문자열 안에 위치해야 합니다styled-jsx는 babel 플러그인을 통해 동작합니다. babel 플러그인은 빌드 과정에서 모든 CSS를 분해하고 적용합니다. (스타일이 추가 시간 없이 적용됩니다)styled-jsx 내에 제약 조건을 제공합니다. 나중에 styled-jsx 안에 동적 변수를 사용할 수 있습니다. 이것이 스타일을 템플릿 문자열 ({``}) 안에 작성해야하는 이유입니다.스타일과 중첩된 컴포넌트home 페이지에 작은 변화를 만들어봅시다. 다음과 같이 링크 컴포넌트를 분리시켰습니다:    import Layout from '../components/MyLayout.js'   pages/index.js 안의 내용을 위와 같이 수정해봅시다.무슨 일이 일어나나요?- 아무런 일도 일어나지 않는다.- 링크가 아닌 h1만 스타일이 적용된다.- 페이지에 에러가 발생한다.- 콘솔에 에러가 발생한다.중첩된 컴포넌트에는 적용되지 않습니다위의 코드를 실행하면 다음과 같이 보입니다:보다시피 CSS는 하위 컴포넌트 내부의 엘리멘트에는 적용되지 않습니다.styled-jsx의 특징은 더 큰 애플리케이션에서 스타일들을 관리할 때 도움이 됩니다.이 경우에는 하위 컴포넌트에 직접 스타일을 적용해야 합니다. 지금 상황에서는 링크 컴포넌트에 직접 스타일을 적용해야 합니다:다른 방법로는 global selectors을 사용할 수 있습니다.전역 스타일때때로 하위 컴포넌트 안의 스타일을 바꿔야 합니다. 일례로 React에서 마크다운을 사용하는 경우가 있습니다. post 페이지(pages/post.js)에서 볼 수 있습니다.post 페이지는 전역 스타일이 유용하게 쓰일 수 있는 곳입니다. styled-jsx를 사용하여 몇 가지 전역 스타일을 추가해봅시다. pages/post.js에 다음과 같은 내용을 적용해주세요.다음 내용을 적용하기 전에 npm install --save react-markdown 명령어를 통해 react-markdown 컴포넌트를 설치해주세요. 무슨 일이 일어나나요?- 아무런 일도 일어나지 않는다.- 마크다운 컨텐츠에 스타일이 적용된다.- 페이지에 에러가 발생한다.- 콘솔에 에러가 발생한다.전역 스타일이 동작합니다전역적으로 스타일이 적용되므로 잘 동작합니다.이 기능은 매우 유용할 수 있지만 항상 전역 prop 없이 스타일을 작성하길 추천합니다.여전히 일반적인 스타일 태그보다 좋은 방법입니다. styled-jsx를 사용하면 필요한 모든 접두사와 CSS 유효성 검사가 babel 플러그인 내부에서 수행되어 추가적인 런타임 오버헤드가 없습니다.다음엔 무엇을 해야할까요이 편에서는 styled-jsx의 표면만 다루었습니다. 더 많은 것들을 할 수 있습니다. styled-jsx Github 저장소에서 더 많은 내용을 참고하세요.Next.js에서 꽤나 괜찮은 다른 스타일링 방법들이 있습니다. 이 부분도 같이 참고해주세요.#트레바리 #개발자 #안드로이드 #앱개발 #Next.js #백엔드 #인사이트 #경험공유
조회수 3920

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

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

Good Developer 3 | 나쁜 개발자의 11가지 습관

세상에 나쁜 개발자는 없다. 나쁜 개발 습관만 있을 뿐나쁜 개발자란 누구를 지칭하는 것일까? 코드가 별로인 개발자? 커뮤니케이션이 안되는 개발자? 나쁜 개발자로 지칭될 수 있는 사람들은 굉장히 많다. 하지만, 세상에는 나쁜 개발자는 없다고 생각한다. 단지, 나쁜 개발 습관만 존재할 뿐. 즉, 누구든지 나쁜 습관을 버리고 좋은 습관을 갖는다면 언제든지 좋은 개발자가 될 수 있다는 것이다. 좋은 개발자, 나쁜 개발자. 이것은 칭호가 아니라 속성일 뿐이다. 언제든지 바뀔 수 있는 속성 말이다.이것이 속성인 이유는 누구든지 좋은 개발자와 나쁜 개발자의 속성들을 가지고 있기 때문이다. 단지 그 속성의 비율의 차이가 그 사람이 어떤 개발자인지 결정할 뿐이다. 흔히, 좋은 개발자라고 불리는 사람도 나쁜 개발 습관이 있을 수 있다. 또, 나쁜 개발자라고 욕을 먹는 사람도 좋은 개발 습관이 있을 수 있다.우리는 이 글에서 나쁜 개발 습관(혹은 속성)들을 알아보고 왜 그것이 나쁜지 그리고 그것을 어떻게 피하는지에 대해 이야기할 것이다. 좋은 습관이 아니라 나쁜 습관들을 이야기하는 이유가 있다. 좋은 습관은 습득하기 어렵다. 하지만, 나쁜 습관을 버리는 것은 더더욱 어렵다. 나쁜 습관을 피하는 것이 때로는 좋은 개발자가 되기 위한 요건일 수도 있다. 아래의 습관들을 보면서 자신을 진단해 보자.(아래의 습관들 중 습관인 것들도 있고 단순히 사고방식이나 경향인 것들이 있다. 여기서 습관은 사고방식이나 행동의 양식 등 총체적인 행동 방식 등을 의미한다.)습관 1: 코드 리뷰가 없다.지난번에 같이 해보니까 험악만 말만 나오고, 분위기만 안 좋아졌다. 후배들에게 코드 지적받는 것도 자존심 상하고... 그리고 대부분 시니어들이 지적하고 주니어들은 고개만 끄덕이는 자리 아닌가? 코드 리뷰 할 시간에 코드 한 줄이라도 더 짜서 프로젝트 마감일이나 지키는 게 낫지. 솔직히, 프로라면 자기 코드는 자기가 책임져야 하는 거 아닌가?습관 2: 문서화를 하지 않는다.아니 개발할 시간도 부족한데 무슨 문서화인가. 개발자가 개발하는 사람이지 문서 만드는 사람인가? 인수인계받을 사람 오면 직접 알려주면서 일주일이면 끝날 텐데 말이다. 그리고 이때까지 만든 문서들 만들고 나서 본적이나 있나? 그냥 보여주기식 파일이지 뭐.습관 3: 커뮤니케이션 향상에 관심이 없다.지금도 말 잘하고 대화 잘 통하는데 더 향상시킬게 있나? 그리고 개발자의 핵심은 커뮤니케이션이 아니라 코딩인데 말이야. 컴퓨터랑만 잘 소통하면 되지. 어차피 다른 부서에 있는 사람들은 개발 기술에 대해서 잘 알지도 못하고... 커뮤니케이션 스킬은 그런 사람들이 향상시켜야 한다고 생각한다.습관 4: 업무 공유가 되지 않는다. 자신의 일에 대해 알고 있는 사람이 없다. 데드라인 잘 지키고, 주어진 일을 잘 해내면 된다고 생각한다. 보고를 하기 전까지 굳이 보고하지 않고, 동료나 후배들과 업무 공유를 잘 하지 않는다. 어차피 내가 하는 일에 별로 관심도 없는데 공유해봤자 무슨 소용인가?습관 5: 코드의 복붙(복사 후 붙여넣기)가 '일상화'되어 있다.직접 만드는 것보다 이미 만들어진 코드들을 찾아서 Ctrl +c,v하는게 더 빠르고 생산성 있다고 생각한다. 동료 개발자랑 공통 모듈을 만들어 사용할 수 있겠지만 그렇게 하기에는 너무 많은 리소스가 낭비된다고 생각한다. 잘 돌아가기만 하면 되지 않나?습관 6: 자신의 부족한 점을 드러내지 않는다.부족한 점에 대해 동료들과 터놓고 얘기하지 않는다. 괜히 부끄럽고 껄끄럽기도 하고 자신의 부족한 점이 드러나는 것이 두렵다. 동료들이 조언을 해주려고 해도 방어적으로 나오거나 피한다. 동료의 진솔한 피드백이 없으니 한 번 단점을 만들면 끝까지 내 것으로 가져간다.습관 7: 새로운 기술을 익히는데 시간을 투자하지 않는다.세상은 정말 빠르게 변하고 있다. 그리고 그 변화의 중심은 기술이고 기술 중에서도 IT 기술이 정점에 있다고 봐도 무방하다. 새로운 기술은 새로운 기술자들이 익히는 것이라 생각한다. 지금 하고 있는 일만으로도 벅차다. 그리고 지금 쓰는 기술이 시대의 주류인데 쉽게 바뀔까?습관 8: 자신의 개발 환경에서 벗어나지 않는다.개발자 모임이나 개발 커뮤니티에 시간을 쓰는 것은 낭비라고 생각한다. 개발에 대해 새로운 시도를 하지 않는다. 새로운 프레임워크나 협업 툴들이 나와도 기존의 환경을 고집한다. 왜냐하면 지금 개발 환경이 너무 편하고 익숙하니까.습관 9: 자신이 맡은 개발과 관련된 비즈니스를 이해하지 않는다.개발자는 개발에만 신경 쓰면 된다고 생각한다. 지금 개발하고 있는 서비스의 비즈니스적 관점은 생각해 본 적 없다. 어차피 기획자나 마케터, 프로덕트 매니저가 신경 써야 할 일이라고 생각한다. 개발만으로도 바쁜데 그것까지 신경 쓰면 정말 골치 아파진다.습관 10: 개발에 대한 지신만의 장기적인 목표가 없다.어떤 개발자가 되어야 하는지에 대한 목표가 없다. 주어진 프로젝트 외에 자신이 하고 싶은 프로젝트를 하면서 개발을 발전시키지 않는다. 그냥 개발의 메인 스트림을 따라만 간다. 커리어나 다른 생활에 대한 걱정은 종종 하지만, 개발 자체에 대한 고민은 하지 않는다.습관 11: 자신의 나쁜 개발 습관에 관심이 없다.(습관은 아니지만....)내가 나쁜 개발자라고...? 내가 하고 있는 것들이 나쁜 습관들이라고??? 글쎄..... 그냥저냥 잘 하고 있는 거 같은데.... 라고 생각하는 당신! 아무리 좋은 개발자라도 나쁜 습관은 존재하기 마련이다. 좋은 개발자는 좋은 습관들을 가지고 있는 개발자기도 하지만, 나쁜 습관들이 많지 않은 개발자이기도 하다.나쁜 환경은 나쁜 개발자를 만든다.당신이 만약 스스로를 나쁜 개발자라고 생각한다면 아마 '나쁜' 환경에서 개발을 했을 가능성이 크다. 혹은 선배가 나쁜 개발자여서 그 습관을 그대로 보고 배웠다든지, 아니면 좋은 개발자에 대한 고민 없이 흘러가듯 개발을 배웠을 것이다. 예를 들어, 코드 리뷰를 하지 않았던 것은 회사에서 코드 리뷰를 안 했을 가능성이 크다. 혹은 문서화를 안 하는 경우, 그 회사에서 그것에 대해 크게 신경 쓰기 있지 않을 가능성이 크다.Bad developers are not born, but created.위에서도 언급했듯이 나쁜 개발자는 없다. 나쁜 습관들이 있을 뿐. 당신이 지금 위의 습관에서 많은 부분들이 해당된다 하더라도, 그 습관들을 바꾸면 된다. 다른 개발자들에게 있는 좋은 습관들을 보고 배우면서 자신에게 해당되는 나쁜 습관들을 하나씩 바꿔나가는 것이다.환경이 바뀐다고 자신이 바뀌지는 않겠지만, 나쁜 환경이 나쁜 개발자를 만드는 것처럼, 좋은 환경은 좋은 개발자를 만든다. 좋은 환경을 찾아가라! 직장이 그걸 주지 못한다면 다른 곳에서라도 찾아라. 좋은 개발자는 나쁜 습관들을 하나씩 바꿔나갈 때 될 수 있을 것이다. 다음 포스팅에서는 좋은 개발자가 되기 위해 필요한 정보들을 압축적으로 모아 포스팅할 것이다.
조회수 1430

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

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분이 중요합니다. 문제를 찾는 속도를 높이기 위해 눈앞에서 벌어진 상황에 대한 높은 집중력을 유지하면서 대쉬보드를 상황에 맞춰 재 조정합니다. 최상위 어플리케이션 데쉬보드와 각각의 서브시스템들을 위한 대시보드를 하나씩 설정합니다. 시스템 대시보드는 시스템 지표의 하위 시스템의 키 메트릭스와 함께 어플리케이션 메트릭을 확인 할 수 있어야 합니다. 이벤트 데이터도 이용가능한 상황이라면 연관 분석 차트에서 관련된 이벤트가 올라가 있어야 합니다. 와탭의 알림 서비스정리하기   서비스에 장애는 무조건 발생하지만 우리는 모니터링을 통해 빠르게 해결 할 수 있습니다. 이를 위해 표준화된 모니터링 프로세스를 만들고 대시보드로 연관관계를 만들어 놓는다면 문제를 빠르게 추적 조사할 수 있습니다. 가능하면 모든 지표는 어플리케이션 지표에서 부터 찾을 수 있도록 대시보드를 구성합니다.인프라스트럭처를 통해서도 문제를 분석할 수 있습니다. 시스템에 대해 대시보드를 설정하고 주요 지표들을 올려놓아야 합니다. 문제의 원인을 조사하는 것은 증세가 나타나는 최상위 시스템에서 부터 시작합니다. 문제가 되는 리소스가 발견되면 문제를 발견하고 수정할 때가지 리소스에서 발견되는 패턴을 조사하고 적용시키는작업을 반복해야 합니다. #와탭랩스 #개발자 #개발팀 #인사이트 #경험공유 #일지

기업문화 엿볼 때, 더팀스

로그인

/