블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2017. 4. 11. 20:40 Big Data

RethinkDB는 리얼타임 웹을 위한 NoSQL 저장소이다. 




설치하기


가이드에 따라 OS에 맞게 설치한다. (맥기준)

$ brew update && brew install rethinkdb

또는 

다운로드 (.dmg)


docker로 실행할 경우

$ docker run -d -P --name rethink1 rethinkdb


저장소를 설치했으면 다음으로 NodeJS에서 사용할 클라이언트 드라이버를 설치한다. 




실행하기


설치확인

$ which rethinkdb

/usr/local/bin/rethinkdb


실행하기

$ rethinkdb --bind all

Running rethinkdb 2.3.5 (CLANG 7.3.0 (clang-703.0.31))...

Running on Darwin 16.5.0 x86_64

Loading data from directory /Users/dowonyun/opensource/rethinkdb_data

Listening for intracluster connections on port 29015

Listening for client driver connections on port 28015

Listening for administrative HTTP connections on port 8080


브라우져기반 Admin(http://localhost:8080)을 통해 실시간 변경사항 조회, 테이블 생성, 서버관리, 데이터 조회등을 할 수 있다.




NodeJS환경에서 RethinkDB 접속하기


npm으로 노드환경을 만들고 rethink driver를 설치한다. 

$ npm init -y

$ npm i rethinkdb --save


신규 파일을 만들고 Connect를 하고, 기본 데이터베이스인 test안에 신규 테이블을 생성해 보자. 

// connect.js

r = require('rethinkdb');


let connection = null;

r.connect({ host: 'localhost', port: 28015 }, function (err, conn) {

    if (err) throw err;

    connection = conn;

    

    r.db('test').tableCreate('authors').run(connection, function (err, result) {

        if (err) throw err;

        console.log(JSON.stringify(result, null, 2));

    })

})


실행을 해보면 테이블이 생성되고 Admin화면에서도 확인해 볼 수 있다. rethinkDB 또한 Shard, Replica를 제공함을 알 수 있다. 

$ node connect.js

{

  "config_changes": [

    {

      "new_val": {

        "db": "test",

        "durability": "hard",

        "id": "05f4b262-9e01-477e-a74c-3d4ccb14cf84",

        "indexes": [],

        "name": "authors",

        "primary_key": "id",

        "shards": [

          {

            "nonvoting_replicas": [],

            "primary_replica": "DowonYunui_MacBook_Pro_local_bcb",

            "replicas": [

              "DowonYunui_MacBook_Pro_local_bcb"

            ]

          }

        ],

        "write_acks": "majority"

      },

      "old_val": null

    }

  ],

  "tables_created": 1

}


다음으로 rethinkDB는 자체 query 엔진을 가지고 있고, rethinkDB를 위한 쿼리 언어를 ReQL이라 한다. ReQL의 3가지 특성은 다음과 같다. 

  - ReQL embeds into your programming language: 펑션으로 만들어짐

  - All ReQL queries are chainable: Dot ( . ) 오퍼레이터를 통해 체이닝 메소드 호출이다. 

  - All queries execute on the server: run을 호출해야 비로서 쿼리를 수행한다.


예제는 3가지의 특성을 보여주고 있다. query를 json 또는 SQL query statement으로 짜는게 아니라. 메소드 체이닝을 통해 쿼리를 만들고, 맨 나중에 Connection 객체를 파라미터로 넘겨주면서 run() 메소드를 실행한다. 

r.table('users').pluck('last_name').distinct().count().run(conn)




일반 SQL 문과 자바스크립트 기반의 ReQL 차이점


구문의 차이저을 보자. row가 document이고, column이 field라는 차이점만 존재한다. 


 Insert


Select



다른 예제는 가이드 문서를 참조하자.




Horizon을 이용한 클라이언트 & 서버 구축하기


Horizon은 rehtinkDB를 사용해 realtime서비스 개발을 돕는 Node.js기반의 서버 프레임워크이면서 클라이언트에서 서버단의 접속을 RxJS 기반의 라이브러리를 통해 가능하다. 마치 Meteor 플랫폼과 유사한 리얼타임 아키텍트를 제공한다. Horizon의 CLI를 글로벌로 설치한다.

$ npm install -g horizon


호라이즌 실행은 hz 이다. 애플리케이션 스켈레톤을 hz을 이용해 만든다.

$ hz init realtime_app

Created new project directory realtime_app

Created realtime_app/src directory

Created realtime_app/dist directory

Created realtime_app/dist/index.html example

Created realtime_app/.hz directory

Created realtime_app/.gitignore

Created realtime_app/.hz/config.toml

Created realtime_app/.hz/schema.toml

Created realtime_app/.hz/secrets.toml


hz 서버 구동하고 http://localhost:55584/ 로 접속하면 http://localhost:8080 접속화면과 동일한 Admin화면을 볼 수 있다.

$ hz serve --dev

App available at http://127.0.0.1:8181

RethinkDB

   ├── Admin interface: http://localhost:55584

   └── Drivers can connect to port 55583

Starting Horizon...

🌄 Horizon ready for connections


http://127.0.0.1:8181/을 호출하면 샘플 웹 화면을 볼 수 있다. 보다 자세한 사항은 홈페이지를 참조하고, 차후에 다시 다루어 보기로 하자.




<참조>

  - rethinkDB 10분 가이드

  - ReQL 가이드

  - Pluralsight 가이드

posted by 윤영식
2016. 4. 13. 13:31 Electron & Ionic

Ionic 프레임워크는 하이브리드 웹앱을 만들기 위한 프레임워크이다. 웹앱이기에 웹을 위한 프레임워크로 Angular를 사용하고 있다. Ionic v1에서는 Angular v1Ionic v2에서는 Angular v2를 사용하고 있다. 







Ionic은 하이브리드 웹앱 프레임워크외에 다양한 서비스를 제공하고 있다. Ionic 플랫폼 상에서 화면을 디자인하고 배포하고 테스트할 수 있는 서비스들을 제공한다. Ionic.io 는 Ionic 프레임워크를 이용해 만든 앱을 배포 관리하는 곳이다. Ionic Creator는 화면을 디자인하는 서비스이며, Ionic View는 앱을 앱스토어에 배포하거나 로컬에 USB로 설치하지 않고 Ionic View앱을 설치하면 Ionic 프레임워크로 만든 모든 앱들을 iOS 또는 Android 상에서 바로 볼 수 있는 서비스이다. 


위의 그림 Ionic는 Cordova를 기반으로 하고 CLI(Command Line Interface)를 제공하며 CLI는 두가지를 통합하고 있다. 첫째는 Cordova의 plugin에 대한 install/uninstall을 위한 명령이고 두번째는 Gulp를 이용해서 웹파일(Sass, Html, Scripts)을 위한 명령 Task에 대해 Gulp의 환경파일인 gulpfile.js에 정의하고 있다. Vinyl은 Gulp에서 사용되는 모듈로 다양한 OS에 상관없이 File Stream을 지원하기 위한것으로 개념은 링크를 참조한다. Ionic은 iOS와 Android Native UI에 가깝게 Angular 기반으로 컴포넌트를 제공하고 있다. 즉, Angular의 Directive(지시자, 컴포넌트)를 이용해 화면을 만드는 방식이다. 또한 Cordova의 기능을 Angular의 서비스로 사용하기 위해 ngCordova도 제공하고 있다. 


Ionic을 사용하기 위해서는 다음과 같은 사전지식이 필요하다. 

  - Node.js 그리고 NPM 사용법

  - 단순 테스트 목적이 아니라면 반드시 Angular 프레임워크 사용경험이 필요하다  

  - XCode 또는 Android Studio에서 간혹 Cordova의 Plugin을 수정할 때도 있다. 

  - 기본 XCode, Android Studio 사용법은 알아두는게 좋다. 직접 툴에서 빌드할 경우가 많다. 


현재 Ionic v2는 Angular v2를 기반으로 하고 아직 beta 버전이다 (2016.4.12).  2016년 상반기에 Angular v2 정식버전이 나오면 비슷한 시기에 정식버전이 나오리라 기대해 본다. Angular v2가 정식버전하에 하반기부터 본격적으로 쓰일 것으로 보이기때문에 Ionic도 v2를 사용하고 준비하면 좋을 것같다. Ionic v2를 사용하기 위해서는 따라서 다음과 같은 기초 지식도 필요하다. 

  - ES2015 JavaScript 스팩 상의 문법 추가 사항을 숙지해야 한다. OOP 스타일의 Syntax로 바뀌었다고 보면 된다.  

  - Angular v2 가이드문서 (반드시 TypeScript 기반으로 참조)를 최소 한번쯤은 보도록 한다. 

  - TypeScript 기반 Cordova 서비스를 통해 Native 접근이 필요할 것이다.  


알아야 할 것은 많지만 일단 설치부터 실행까지 보도록 한다. Ionic v2 CLI 명령어Cordova 공식 CLI 명령어도 한번 훑어보는게 좋다. Cordova는 현재 6.* 버전이 최신이다.





1. 설치하기 


먼저 Node.js를 설치한다. NPM(Node Package Manager)를 통해 Cordova와 ionic 프레임워크를 설치하고 Gulp 수행의 기반을 제공한다. 

  - typescript 컴파일러 설치

  - typings 는 typescript의 definition 파일을 관리하는 메니저이다. typings 폴더에서 관리된다. 

  - cordova는 iOS와 Android Native 접근을 위한 Gateway library라고 보면 된다. 

> sudo npm install -g   typings  typescript  cordova ionic@beta



다음으로 ionic에서 제공하는 샘플 파일을 자동 다운로드 받아 설치한다. ionic v2 에 typescript 버전으로 설치하는 명령어이다. 자동으로 typings폴더에 typescript definition 파일 및 package.json에 정의된 모듈도 node_modules 밑으로 자동 설치된다.

프로젝트생성) ionic start <projectName> <templateType> --v2 --ts

예) ionic start myFirst blank --v2 --ts


blank 템플릿 타입을 주면 https://github.com/driftyco/ionic2-starter-blank/archive/typescript.zip 에서 typescript 버전을 자동 다운로드해 설치한다.

templateType에는 tutorial, tabs, sidemenu, blank가 있다. 설치를 하면 다음과 같은 안내글이 나온다. 


Make sure to cd into your new app directory:

  cd ionic2-tutorial-github


To run your app in the browser (great for initial development):

  ionic serve


To run on iOS:

  ionic run ios


To run on Android:

  ionic run android


To test your app on a device easily, try Ionic View:

  http://view.ionic.io


New! Add push notifications, update your app remotely, and package iOS and Android apps with the Ionic Platform!

  https://apps.ionic.io/signup


New to Ionic? Get started here: http://ionicframework.com/docs/v2/getting-started


설명대로 ionic2-tutorial-github 폴더로 이동후 ioinic serve 명령을 수행하면 Gulp의 serve 태스커가 수행된다. 수행 결과로 ionic $ 명령콘솔이 활성화 된다.

> ionic serve 

WARN: ionic.config.js has been deprecated, you can remove it.

Running live reload server: http://localhost:35729

Watching: www/**/*, !www/lib/**/*

√ Running dev server:  http://localhost:8100

Ionic server commands, enter:

  restart or r to restart the client app from the root

  goto or g and a url to have the app navigate to the given url

  consolelogs or c to enable/disable console log output

  serverlogs or s to enable/disable server log output

  quit or q to shutdown the server and exit


ionic $





2. ionic 폴더 구조


ionic의 폴더구조 및 환경설정 파일은 다음과 같다. 


 app

 개발자가 작성하는 모든 애플리케이션 코드가 위치한다. 

 hooks (cordova)

 Cordova 빌드과정의 일부로서 동작될 수 있는 스크립트를 포함하고 있다. 앱을 패키지 할때 필요하다면 언제든 커스터마이징 할 수 있다.  

 node_modules

 npm을 통해 설치된 모듈들이 있다.

 platforms (cordova)

 ionic platform 으로 ios, android를 설치하면 하위 폴더로 생기고, XCode 또는 Android Studio에서 import할 수 있다.

 plugins (cordova)

 ionic platform 선택시 Cordova의 플러그인이 설치되는 폴더이다. 

 resources 

 앱을 위한 icon과 splash image를 해상도가 틀린 모바일 기기별로 놓는 곳이다.  

 typings

 Typescript로 쓰여지지 않는 JS 라이브러리의 타입정의를 한 type definition 파일이 있다.  

 www (cordova)

 index.html를 포함한다. 이곳은 빌드될 때 사용되는 것으로 애플리케이션 코드가 위치하는 곳이 아님을 주의하자.  "ionic build" 를 하면 "cordova build"가 수행되어 www 해당 디렉토리에 app의 코드가 위치하고 다시 platforms/ios 또는 android의 www 폴더에 copy된다. 따라서 최종 사용되는 파일은 platforms/ios (또는 android) /www/* 에 위치한다. 

 config.xml (cordova)

 앱 패키지를 만들때 사용하기 위해 Cordova의 환경설정이 존재한다. 

 ionic.config.js

 not used로 앞으로 없어질 것이다. ionic.config.json 파일은 버전 정보만 전달

 package.json

 npm 으로 설치되는 모든 모듈에 대한 설정 

tsconfig.json / typings.json

 TypeScript 환경 설정 / type definition file 환경 설정


크게 "환경파일", "Cordova", "애플리케이션" 부분으로 나뉠 수 있다. 최초 템플릿이 생성된 이후 개발자는 "애플리케이션"폴더인 "app"를 사용하면 된다. blank타입으로 만들었을 때 platforms 폴더에는 ios 플랫폼이 기본 설치된다. 







3. TypeScript 기반 개발


최근(2016.4.8) Beta버전에 CLI 를 통해 TypeScript 기반의 Angular v2 페이지와 서비스를 만들 수 있는 명령어를 공개했다. 파일이름은 kebob-case로 my-page와 같이 되고, ES6/TypeScript기반의 class는 PascalCase로 MyData로 처럼 이름을 준다. 

> ionic generate ( 또는 축약해서 g ) <page 또는 provider> <Name>


예)

> ionic g page myPage

create app/pages/my-page/my-page.html (.js, .css)


> ionic g provider MyData

create app/providers/my-data/my-data.js 


생성된 my-page.ts 소스는 다음과 같다. @Page는 TypeScript에서 제공하는 Decorator를 이용해 Class Decorator를 Ionic을 위해 만든 것이다. 

  - TypeScript의 Decorator에 대해 자세히 알고 싶다면 링크를 참조한다. 

import {Page, NavController} from 'ionic-angular';


/*

  Generated class for the MyPagePage page.


  See http://ionicframework.com/docs/v2/components/#navigation for more info on

  Ionic pages and navigation.

*/

@Page({

  templateUrl: 'build/pages/my-page/my-page.html',

})

export class MyPagePage {

  constructor(public nav: NavController) {

    this.nav = nav;

  }

}


.ts가 .js코드로 컴파일된 내역을 보고 싶다면 app 폴더로 이동해서 TypeScript 컴파일 명령어인 "tsc"를 수행한다. @Page는 __decorate({ ... }) 안에서 "ionic-angular"의 decorators/page.js 의 Class Decorator가 수행되는 것이다. ionic_angular_1.Page( <config object> ); 결국, @Page는 구현한 decorator 펑션을 호출해 주는 역할을 수행할 뿐이다. TypeScript Decorator 공식문서를 통해 개념을 이해하자. @Page는 Angular v2에서 제공하는 데코레이터가 아니라 Ionic에서 제공하는 것이다. 내부 소스를 보면 @Page 클래스는 ion-page selector를 사용하고 결국 Angular v2 core에 있는 @Component를 호출하고 있을 뿐이다. 즉, 굳이 @Page를 쓰지 않아되 되는 Decorating이다. 

// app/pages/my-page/my-page.js TypeScript 컴파일된 소스


"use strict";

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {

    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;

    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);

    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;

    return c > 3 && r && Object.defineProperty(target, key, r), r;

};

var __metadata = (this && this.__metadata) || function (k, v) {

    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);

};

var ionic_angular_1 = require('ionic-angular');

/*

  Generated class for the MyPagePage page.


  See http://ionicframework.com/docs/v2/components/#navigation for more info on

  Ionic pages and navigation.

*/

// 자동으로 postfix로 Page가 붙는다. 

var MyPagePage = (function () {

    function MyPagePage(nav) {

        this.nav = nav;

    }

    MyPagePage = __decorate([

        ionic_angular_1.Page({

            templateUrl: 'build/pages/my-page/my-page.html',

        }), 

        __metadata('design:paramtypes', [ionic_angular_1.NavController])

    ], MyPagePage);

    return MyPagePage;

}());

exports.MyPagePage = MyPagePage;



// node_modules/ionic-angular/decorators/page.js 데코레이터 구현 소스


function Page(config) {

    return function (cls) {

        // @Page 의 기본 selector

        config.selector = 'ion-page';  

        config.directives = config.directives ? config.directives.concat(directives_1.IONIC_DIRECTIVES) : directives_1.IONIC_DIRECTIVES;

        config.host = config.host || {};

        config.host['[hidden]'] = '_hidden';

        config.host['[class.tab-subpage]'] = '_tabSubPage';

        var annotations = _reflect.getMetadata('annotations', cls) || [];

        // 기본 설정후 Angular v2 core의 @Component를 호출한다. 

        annotations.push(new core_1.Component(config)); 

        _reflect.defineMetadata('annotations', annotations, cls);

        return cls;

    };

}

exports.Page = Page;


provider로 만든 것은 ES2015의 Promise, RxJS, @Injectable이 포함되어 있다. Angular v2에서는 모든 Inject되는 Service Class에는 Class Decorator로 @Injectable를 설정토록 권고한다. 

// app/providers/my-data/my-data.ts 소스 


import {Injectable} from 'angular2/core';

import {Http} from 'angular2/http';

import 'rxjs/add/operator/map';


/*

  Generated class for the MyData provider.


  See https://angular.io/docs/ts/latest/guide/dependency-injection.html

  for more info on providers and Angular 2 DI.

*/

@Injectable()

export class MyData {

  data: any = null;


  constructor(public http: Http) {}


  load() {

    if (this.data) {

      // already loaded data

      return Promise.resolve(this.data);

    }


    // don't have the data yet

    return new Promise(resolve => {

      // We're using Angular Http provider to request the data,

      // then on the response it'll map the JSON data to a parsed JS object.

      // Next we process the data and resolve the promise with the new data.

      this.http.get('path/to/data.json')

        .map(res => res.json())

        .subscribe(data => {

          // we've got back the raw data, now generate the core schedule data

          // and save the data for later reference

          this.data = data;

          resolve(this.data);

        });

    });

  }

}





5. ionic 페이지 만들기 


일단, ionic generate를 통해 생성된 page, provider (서비스)는 참조만 한다. 다음과 같이 서비스를 파일을 만든다. Angular2 http 모듈이 RXJS를 사용하고 있고 return 오브젝트가 Promise가 아니라 Observable이다. RXJS는 Reactive Programming의 자바스크립트 구현체로 링크를 참조한다.

import { Injectable } from 'angular2/core';

import { Http, Headers } from 'angular2/http';


@Injectable()

export class GitHubService {

    constructor(private http: Http) {}


    getRepos(username) {

        let repos = this.http.get(`https://api.github.com/users/${username}/repos`);

        return repos;

    }

}


다음으로 /app/pages/home/home.ts를 수정한다. @Component 대신에 @Page를 사용하고 providers로 GitHubService를 정의한다. 

import {Page} from 'ionic-angular';

import {GitHubService} from '../../providers/github.service';


@Page({

  templateUrl: 'build/pages/home/home.html',

  providers: [GitHubService]

})

export class HomePage {

  public foundRepositories;

  public username;

  constructor(private github: GitHubService) {}

  getRepos() {

    this.github.getRepos(this.username)

      .subscribe(

          data => {

              this.foundRepositories = data.json();

          },

          err => console.error(err),

          () => console.log('getRepos completed')

      );

  }

}


home.html도 다음과 같이 재정의한다. 

<ion-navbar *navbar>

<ion-title>

Home

</ion-title>

</ion-navbar>


<ion-content class="home">

<ion-list inset>

<ion-item>

<ion-label>Username</ion-label>

<ion-input [(ngModel)]="username" type="text"></ion-input>

</ion-item>

</ion-list>

<div padding>

<button block (click)="getRepos()">Search</button>

</div>

<ion-card *ngFor="#repo of foundRepositories">

<ion-card-header>

{{ repo.name }}

</ion-card-header>

<ion-card-content>

{{ repo.git_url }}

</ion-card-content>

</ion-card>

</ion-content>


ionic CLI 명령을 통해 테스트를 해보자. serve 명령을 이용하면 로컬 서버를 띄워서 브라우져상에서 테스트를 해볼 수 있다. 옵션으로 --lab을 주면 ios, android 둘 다 테스트가 가능하다. 

> ionic serve --lab 



ionic의 ion-* 시작하는 Angular component를 사용하면 mobile 플랫폼에 따라 Look and feel이 자동으로 맞춰진다. 이를 원하지 않을 때는 최소한의 ion-* 태그만을 사용하면 된다. ion-navbar 와 ion-content 태그만을 사용하고 나머지는 서비스에 맞게 HTML을 작성해도 무방하다.





6. ionic Navigation 추가하기 


네비게이션을 위해 메인 컴포넌트인 app.ts를 살펴보면, ionic에서 제공하는 @App 데코레이터를 통해서 HomePage 를 최상위 페이지로 설정하고 있다.

import 'es6-shim';

import {App, Platform} from 'ionic-angular';

import {StatusBar} from 'ionic-native';

import {HomePage} from './pages/home/home';


@App({

  template: '<ion-nav [root]="rootPage"></ion-nav>',

  config: {} // http://ionicframework.com/docs/v2/api/config/Config/

})

export class MyApp {

  rootPage: any = HomePage;


  constructor(platform: Platform) {

    platform.ready().then(() => {

      // Cordova 준비

      StatusBar.styleDefault();

    });

  }

}


GitHub 저장소 목록에서 상세페이지 이동을 위한 detail 페이지를 만들고, 일단 home.html 과 .ts 파일을 수정한다.

// home.html 에서 click 이벤트를 추가한다. 

<ion-card *ngFor="#repo of foundRepositories" (click)="goDetail(repo)">


// home.ts 에 Navigation을 위한 controller와 추가 메소드를 정의한다. 

import {Page, NavController} from 'ionic-angular';

import {GitHubService} from '../../providers/github.service';

import {DetailPage} from '../detail/detail'


@Page({

  templateUrl: 'build/pages/home/home.html',

  providers: [GitHubService]

})

export class HomePage {

  public foundRepositories;

  public username;

  constructor(private github: GitHubService, private nav: NavController) {}

  getRepos() {

    this.github.getRepos(this.username)

      .subscribe(

          data => {

              this.foundRepositories = data.json();

          },

          err => console.error(err),

          () => console.log('getRepos completed')

      );

  }

  goDetail(repo) {

      // 페이지를 Stack에 추가하는 것이다. 

      this.nav.push(DetailPage, { repo: repo });

  }

}


home.html 상단의 ion-navbar 태그는 pagenavigation하면 <Back 버튼이 자동으로 생긴다. 즉, <Back 버튼을 클릭하는 순간 이동한 페이지를 pop 하는 것이 자동으로 이루어 지기때문에 명시적으로 해줄 필요가 없다. 다음으로 저장소의 README 파일 내역을 HTML 포멧으로 가져오는 메소드를 GitHubService에 추가한다. 

// github.service.ts


getDetail(repo) {

        let headers = new Headers();

        headers.append('Accept', 'application/vnd.github.VERSION.html');

        return this.http.get(`${repo.url}/readme`, {headers: headers});

}


상세내역을 detail.ts를 통해 상세정보를 가져오고 다시 detail.html에 뿌려준다. NavParams를 통해 전달받은 repo 객체의 url로 README 값을 받아온다. detail.html에서는 아래 소스처럼 {{ readme }} 표현식을 사용하면 HTML 태그가 text로 뿌려질 뿐이다. 따라서 innerHTML 속성을 이용한다. 

// detail.ts 


import {Page, NavController, NavParams} from 'ionic-angular';

import {GitHubService} from '../../providers/github.service';


@Page({

  templateUrl: 'build/pages/detail/detail.html',

  providers: [GitHubService]

})

export class DetailPage {

  public readme = '';

  public repo;

  constructor(private nav: NavController, private github: GitHubService, private navParams: NavParams) {

      this.repo = navParams.get('repo');

      this.github.getDetail(this.repo)

        .subscribe(

            data => this.readme = data.text(),

            err => {

                if(err.status == 404) {

                    this.readme = 'This repo does not have a README file';

                } else {

                    console.error(err);

                }

            },

            () => console.log('getDetail completed')

        );

  }

}



// detail.html 

<ion-navbar *navbar>

  <ion-title>{{ repo.name }}</ion-title>

</ion-navbar>


<ion-content padding class="detail">

  <div padding>{{ readme }}</div>

</ion-content>



detail.html을 text가 아니 DOM으로 넣기 위해 innerHTML속성으로 변경하고 최종 테스트 한다. 파일을 변경하면 ionic은 파일을 watch하고 있다가 자동으로 refresh 해준다. (ionic serve 경우)

<ion-content padding class="detail">

  <div padding [innerHTML]="readme"></div>

  <!-- <div padding>{{ readme }}</div> -->

</ion-content>


ios와 Android 플랫폼에 맞는 "< Back" 버튼이 생성된다. github 사용자 이름을 입력하고 목록이 나오면 목록중의 카드하나를 클릭하면 다음과 같이 Navigation되는 것을 볼 수 있다. ionic은 Tabs 형태 또는 Menus형태의 UI 컨테이너를 통해 Navigation을 Mobile UX에 맞게 변경할 수 있다. 





7. ionic 배포와 테스트 


ionic serve 명령으로 로컬 웹서버를 띄워 로컬 웹 브라우져를 통해 기능을 테스트 했다면 emulate 또는 기기에 App을 배포해서 테스트 한다. 브라우져에서 보는 것과 실 기기에서 테스트하는 것은 하늘과 땅 차이다. 희소식은 XCode 7 부터는 Developer Account(유료)없이도 USB를 통해 iPhone에 App을 배포 테스트할 수 있다. 

// 로컬에서 xcode emulate을 띄워준다. 

> ionic emulate ios


// 연결된 기기에서 수행할 수 있다

> ionic run ios 


MacBook 상에서 ionic run을 위해서는 "npm install -g ios-deploy" 사전에 설치가 필요하다. 



또는 ionic viewer를 모바일 기기에 설치하고 프로젝트를 ionic.io 서비스에 자신의 계정에 upload한 후 작동 여부를 테스트 할 수 있다. 하지만 작동중 디버깅에 대한 부분은 보다 많은 지면을 필요로 하기에 다음에 언급한다. upload시에 로그인 안되어 있으면 등록한 id/pwd를 물어본다. 

> ionic upload 

WARN: No 'upload:before' gulp task found!

If your app requires a build step, you may want to ensure it runs before upload.


Uploading app....

Saved app_id, writing to ionic.io.bundle.min.js...

Successfully uploaded (f3ded32c)


Share your beautiful app with someone:


$ ionic share EMAIL

Saved api_key, writing to ionic.io.bundle.min.js...


최근에는 Xamarin이나 React Native, NativeScript 같은 Native Code로 전환해 주는 프레임워크가 인기를 끌고 있다. 모바일만을 고려하지 않는다면 ionic은 웹표준 기술을 통해 빠른 기능개발과 Angular2기반의 프론트앤드의 서비스와 컴포넌트(만일 Web을 Angular2로 개발한다면)를 공유할 수 있다. 즉, Native Mobile UX/UI 특성과 빌드 환경의 편의성은 Ionic 프레임워크를 사용하면서 웹개발도 도모할 수 있는 Hybrid WebApp + Web 개발이 가능하다 판단된다. 


향후 angular2-seedNativescriptElectron을 접목한 angular2-seed-advanced가 나왔듯이, angular2-seed에 Ionic2를 접목한 seed를 만들 예정이다. 





참조


  - ionic v2 공식 문서

  - Vinyl 개념 이해하기 & Stream Handbook

  - Cordova 공식 홈페이지 문서

  - TypeScript Decorator 만들기

  - 블로깅 참조 

  - 다양한 모바일 기기 테스트 방법들

  - Ionic2 Conference App 소스

  - Ionic.io 의 다양한 서비스들: Push, Deploy, Analytics, User

  - Crosswalk



자료


  - Ionic Advantures

  - Ionic Collection

  - Ionic Resources

  - AppCamp

  - play.ionic.io: playground


     


posted by 윤영식
2016. 2. 19. 12:10 Angular/Concept

모듈단위의 파일을 만들어 모듈간에 모듈을 로딩해서 사용하는 방법을 제시하는 것이 Module Loader의 역할이고, 만들어진 모듈을 어떻게 묶어 사용할지 제시하는 것이 Module Bundler의 역할이다. 이번 글에서는 Module Bundler에 대해 알아보자. 



            





파일 번들링의 일반적인 방식 


index.html 에 <script> 태그를 이용해 첫번째는 개발자가 직접 넣는 방법, 두번째는 Grunt 또는 Gulp의 프론트앤드 툴의 도움을 받아 자동으로 넣는 방법이 있다. 하지만 응답성능에 민감한 애플리케이션에는 초기 다량의 모듈 파일 전송이라는 네트워크 성능이슈를 야기한다. 따라서 운영시에는 좀 더 컴팩하게 파일을 묶을 필요가 있다. 

  - 파일을 합치고(Concatenate)

  - 압축(Minify)하는 과정을 거친다. 


통합하고 압축하는 역할에 대한 플러그인이 Grunt/Gulp에 모두 존재한다. 하지만 CommonJS 또는 AMD나 ES2015 네이티브 로더를 사용할 때는 브라우져에 진화적인 코드로 변환해야 한다. 이때 사용하는 것이 Browserify, Webpack, JSPM 와 같은 모듈 번들러이다. 




Browserify


NodeJS에는 다양한 패키지가 존재하고 NPM(Node Package Manager)를 통해 설치한다. 모든 패키지가 CommonJS에 맞춰 모듈 패턴으로 구현을 하고 있다. 만약 이를 브라우져에서 사용하고 싶을 경우에는 Browserify 의 도움을 받으면 된다.

  - CommonJS를 번들링할 때 사용한다. 

  - AST (Abstract syntaxt tree) 에 따라 require한 모듈에서 require하고 해당 모듈이 require하고 있는 모든 하위의 모듈의 의존 파일을 자동으로 묶어준다. 

  - browserify 명령에서 entry 파일만 지정하면 된다. 의존

// app.js

var math  = require('math');

...


// console

browserify app.js -o bundle.js 




Webpack


AMD 패턴의 모듈을 번들링 할 때는 RequreJS에서 제공하는 r.js를 사용할 수 있다. 하지만 애플리케이션에서 NodeJS의 모듈도 사용하고 AMD 패턴 모듈도 사용한다면 어떻게 될까?  

  - CommonJS, AMD, ES2015 방식에 대한 번들링이 가능하다 

  - Bunlde Chunk라는 단위 묶으로 나누어서 번들링이 가능하다 


* Naver D2 Webpack 상세설명 참조




RollupJS


웹팩과 유사한 차세대 모듈 번들러로는 RollupJS가 있다. 특히 모듈안에 있는 내용중 사용하지 않는 것은 제거하는 Tree-Shaking 기술이 존재한다. 

  - maths.js에 square와 cube 펑션을 export 한다

  - cube만 사용한다. 

// maths.js 

export function square(x) {

 return x*x;


export function cube(x) {

  return x*x*x;

}


// test.js 

import { cube } from './maths.js';

console.log( cube(5) ); 


Tree Saking을 한후 square를 제거한다. 나무를 흔들면 필요없는 것은 떨어지고 필요한 것만 남는 것과 같다. (예제)

(function () {

'use strict';


// This function gets included

function cube ( x ) {

// rewrite this as `square( x ) * x`

// and see what happens!

return x * x * x;

}


console.log( cube( 5 ) ); // 125

}());




 

JSPM 


SystemJS는 모듈을 로딩하는 일관된 API를(System.config, System.import) 제공하고 패키지를 받아오고 로딩하는 역할로 JSPM을 사용할 수 있다.

  - SystemJS를 위한 패키지 메니져이다. 

  - ES6 Module Loader로 불린다

  - npm, bower, GitHub으로 부터 로딩한다. 

  - 브라우져에서 NodeJS 패키지가 browserify와 똑같이 작동한다

  - 개발시에는 개별 파일로 관리하다가 프러덕션에서는 번들링을 한다. 

  - 사용자 가이드


Browserify ==> Webpack ===> JSPM 순서로 정리 해보자. 




Angular2에서의 JSPM/SystemJS
  - JSPM을 패키지 메니져로 사용
  - SystemJS를 모듈 로더로 사용
  - TypeScript를 ES2015 자바스크립트 슈퍼셋으로 사용
  - Angular 2 를 통해 웹, 모바일, 네이티브 개발 플랫폼으로 사용


Module Loader는 CommonJS, AMD, UMD, ES2015 스팩 방식이 있고, Module Bundler는 Browserify, Webapck에서 JSPM(+SystemJS) 방식으로 수렴되고 있다. Angular2를 하게되면 Module Loader로서의 SystemJS와 Module Bundler이면서 패키지 메니져 역할을 수행하는 JSPM을 알아둘 필요가 있다.  

  - Angular 2에서 Bundler에 따른 사용 모듈 목록



<참조>

  - 모듈 번들러 설명 

  - 모듈 번들러 비교 slideshare

  - Choose ES6 Modules

  - Grunt에서 파일들을 통합/압축하는 방법

  - Browserify 와 Webpack 비교 

  - ES2015 스팩  

  - ES2015의 import/export 이야기

  


posted by 윤영식
2014. 6. 28. 19:07 Big Data/ElasticSearch

ElasticSearch(이하 ES) 엔진에 수집된 데이터에 대하여 Kibana 도움 없이 직접 Data Visualization 하는 기술 스택을 알아보고, 실데이터를 통한 화면 1본을 만들어 본다. 




ES 시각화를 위한 다양한 방식의 시도들


사전조사-1) 직접 클라이언트 모듈 제작하여 Data Visualization 수행 

  - elasticsearch.js 또는 elatic.js 사용하지 않고 REST 호출 통해 데이터 시각화 수행 

    (Protovis 는 D3.js 기반 Chart를 사용함)

  - FullScale.co에서는 dangle 이라는 시각화 차트를 소개

    (D3.js 기반의 AngularJS Directives 차트)

  - D3.js 기반의 전문적인 차트를 사용하는 방법을 익힐 수 있다. 하지만 제대로 갖춰진 ES 클라이언트 모듈의 필요성 대두


사전조사-2) elasticsearch.js 사용하여 Data Visualization 수행

  - elasticsearch.js와 D3.js, jquery, require.js를 통해서 샘플 데이터를 시각화 수행

    (AngularJS는 사용하지 않고, 전문적인 차트 모듈사용하지 않음)

  - AngularJS 기반으로 Protovis 또는 Dangle 차트를 사용하여 표현해 본다.


사전조사-3) elastic.js 사용하여 Data Visualization 수행

  - elastic.js 홈페이지에서 API를 숙지한다.

  - DSL API를 살펴보자. DSL을 이해하기 위해서는 ES의 Search API에 대한 이해가 필요하다.

  - Query를 작성하고 Filtering 하면 group by having과 유사한 facets (지금은 aggregation 을 사용)하여 검색을 한다.

    Query -> Filter -> Aggregation에 대해 알면 DSL구성을 이해할 수 있다.

  - 자신의 Twitter 데이터를 가지고 elastic.js와 angular.js를 사용하여 트윗 내용을 표현하는 방법 (GitHub 소스)




ES Data Visualization을 위한 나만의 Tech Stack 만들기 


  - ES 클라이언트 모듈 : elastic.js 의 DSL(Domain Specific Language)를 숙지한다. 

    elastic.js는 ElasticSearch의 공식 클라이언트 모듈인 elasticsearch.js 의 DSL 화 모듈로 namespace는 ejs 이다.  

  - 시각화를 위한 D3.js 의 개념 이해 (D3.js 배우기)

  - Kibana에서 사용하고 있는 Frontend MV* Framework인 AngularJS (AngularJS 배우기)

  - AngularJS 생태계 도구인 yeoman 을 통해 개발 시작하기 (generator-fullstack,  Yeoman 사용방법)

  - 물론 Node.js는 기본이다. 


그래서 다음과 같이 정리해 본다. 


  - AngularJS 기반 Frontend 개발

    1) Node.js 기초

    2) Yeoman + generator-angular 기반 

  - D3.js 기반의 Chart 이며 AngularJS 바로 적용가능토록 Directives 화 되어 있는 차트 중 선택사용

    1) Protovis

    2) Dangle

    3) Angular nvd3 charts (추천)

    4) Angular-Charts

    5) Angular Google Charts 

  - elasticsearch.js를 DSL로 만든 elastic.js 사용


그래서 다시 그림으로 정리해본 기술 스택



ES Data Visualization 개발을 위한 구성 stack 그림






== 이제 만들어 봅시다!!! ==


 

환경설정

  - node.js 및 yeoman 설치 : npm install -g generator-angular-fullstack 설치 (generator-angular는 오류발생)

  - twitter bootstrap RWD 메뉴화면 구성 (기본 화면 구성)

  - angular-ui의 angular-bootstrap 설치 :  bower install angular-bootstrap --save

  - elasticsearch.js 설치 : bower install elasticsearch --save

  - elastic.js 설치 : bower install elastic.js --save

  - angular-nvd3-directives chart 설치 : bower install angularjs-nvd3-directives --save



angular layout 구성 

  - 애플리케이션 생성 : GitHub Repository를 만들어서 clone 한 디렉토리에서 수행하였다

$ git clone https://github.com/elasticsearch-kr/es-data-visualization-hackerton 

$ cd es-data-visualization-hackerton 

$ yo angular-fullstack esvisualization

  - main.html 과 scripts/controllers/main.js 를 주로 수정함 

// main.html 안에 angular-nvd3.directives html 태그 및 속성 설정 

    <div class="row">

      <div class="col-xs-12 col-md-12">

        <div class="panel panel-default">

          <div class="panel-heading">

            <button type="button" ng-click="getImpression()" class="btn btn-default">Impression Histogram</button>

          </div>


          <div class="panel-body">


            <!-- angular-nvd3-directives : multi-bar chart -->

            <div class="col-xs-12 col-md-12">

              <nvd3-multi-bar-chart

                  data="impressionData"

                  id="dataId"

                  xAxisTickFormat="xAxisTickFormatFunction()"

                  width="550"

                  height="350"

                  showXAxis="true"

                  showYAxis="true">

                    <svg></svg>

              </nvd3-multi-bar-chart>

            </div>


          </div>

        </div>

      </div>

    </div>



// main.js 안에서 elasticsearch.js를 직접 호출하여 사용함 

angular.module('esvisualizationApp')

  .controller('MainCtrl', function ($scope, $http) {


    // x축의 값을 date으로 변환하여 찍어준다 

    $scope.xAxisTickFormatFunction = function(){

      return function(d){

        return d3.time.format('%x')(new Date(d));

      }

    }


    // 화면에서 클릭을 하면 impression index 값을 ES에서 호출하여 

    // ES aggregation json 결과값을 파싱하여 차트 데이터에 맵핑한다

    $scope.getImpression = function() {


      // ES 접속을 위한 클라이언트를 생성한다 

      var client = new elasticsearch.Client({

                                              host: '54.178.125.74:9200',

                                              sniffOnStart: true,

                                              sniffInterval: 60000,

                                            });


// search 조회를 수행한다. POST 방식으로 body에 실 search query를 넣어준다 

      client.search({

          index: 'impression',

          size: 5,

          body: {

            "filter": {

                "and": [

                    {

                      "range": {

                        "time": {

                            "from": "2013-7-1", 

                            "to": "2014-6-30"

                        }

                      }

                    }

                ]

            },

            "aggs": {

              "events": {

                "terms": {

                  "field": "event"   

                },

                "aggs" : {   

                  "time_histogram" : {

                      "date_histogram" : {

                          "field" : "time",

                          "interval" : "1d",   

                          "format" : "yyyy-MM-dd" 

                      }

                  }

                }

              }

            }


            // End query.

          }

      }).then(function (resp) {

         var impressions = resp.aggregations.events.buckets[0].time_histogram.buckets;

         console.log(impressions);


         var fixData = [];

         angular.forEach(impressions, function(impression, idx) {

          fixData[idx] = [impression.key, impression.doc_count];

         });

   

   // 결과에 대해서 promise 패턴으로 받아서 angular-nvd3-directives의 데이터 구조를 만들어 준다

   // {key, values}가 하나의 series가 되고 배열을 가지면 다중 series가 된다. 

         $scope.impressionData = [

            {

              "key": "Series 1",

              "values": fixData

            }

          ];


  // apply 적용을 해주어야 차트에 데이터가 바로 반영된다.

        $scope.$apply();

      });

    }


  });




결과 화면 및 해커톤 소감

  - "Impression Histogram" 버튼을 클릭하면 차트에 표현을 한다.

  - elastic.js의 DSL을 이용하면 다양한 파라미터에 대한 핸들링을 쉽게 할 수 있을 것같다. 

  - AngularJS 생태계와 elasticsearch.js(elastic.js)를 이용하면 Kibana의 도움 없이 자신이 원하는 화면을 쉽게 만들 수 있다. 

  - 관건은 역시 ES에 어떤 데이터를 어떤 형태로 넣느냐가 가장 먼저 고민을 해야하고, 이후 분석 query를 어떻게 짤 것인가 고민이 필요!



* 헤커톤 소스 위치 : https://github.com/elasticsearch-kr/es-data-visualization-hackerton



<참조> 

  - elasticsearch.js 공식 클라이언트 모듈을 DSL로 만든 elastic.js 

  - elastic.js를 사용한 ES Data Visualization을 AngularJS기반으로 개발

  - Protovis 차트를 이용한 facet 데이터 표현

  - ElasticSearch Data Visualization 방법

  - D3.js 와 Angular.js 기반으로 Data Visualization

  - ElasticSearch의 FacetAggregation 수행 : 향후 Facet은 없어지고 Aggregation으로 대체될 것이다.

  - AngularJS Directives Chart 비교

posted by 윤영식
2014. 4. 25. 00:15 NodeJS/Concept

Node.js를 제대로 배워보고 싶다면 이렇게 시작해보자


1. Node.js 사용 영역

  - fast, real-time 어플리케이션 개발

  - scalable 확장가능성이 높은 어플리케이션 개발

  - data-driven modern web 어플리케이션 개발

  * 클라우드 환경 이해



2. 잘 못 된 코스 

  - 온라인 및 동영상 강좌들부터 보지 말기

  - 서점에서 서평들 읽고 책 고르지 말기 



3. 제대로 된 코스

  - 봐야할 리소스 

    + 초보 : The Node Beginner Book 번역본

    + Felix's NodeJS Guide 번역본

   + Node.js 소개 자료 -여름으로 가는 문

   + Mastering NodeJS (비젼미디어)


  - JavaScript 를 배운다 : JavaScript Graden 번역본


  - 설치 : Professional Node.js 1장 보고 개발환경 셋업

  - 이해 : Beginner Book 보고서 간단히 프로그램들 돌려보기 

  - 모듈 : CommonJS에 대하여 이해하기

  - Professional Node.js 3장부터 쭉 읽자. 그러면 서버단 개발에 대해서 이해할 수 있다

  - 우리 목적은 modern web application개발이니 front-end의 backbone.js 를 배운다 



4. Node.js & Backbone.js를 익혔다면 

  - http://dailyjs.com/web-app.html 여기서 다양한 어플리케이션의 모험을 해보시라

  - 더 나아가서는 템플릿을 위한 Handlebars.js 와 MongoDB를 익힌다


  * 2013.2.20 일에 작성한 블로그라서 backbone.js가 나왔다. 이제는 angular.js로 옮겨가는 분위기

    3주를 목표로 잡고 시작하시라~~~ Good Luck!



<참조>

  - 원문 : http://javascriptissexy.com/learn-node-js-completely-and-with-confidence/

posted by 윤영식
2013. 11. 25. 09:36 AngularJS/Start MEAN Stack

MEAN Stack을 가지고 서비스를 만드는데 사용을 하려면 어떤 순서로 익혀야 할까? 각각의 기술을 따로 배우는 것은 어차피 전문서적들이 있기 때문에 여기서 다시 상세히 설명하는 것은 의미가 없을 것 같고, 서비스를 기획, 개발, 런칭까지의 과정을 알아보자. 모든 과정의 내용은 웹 서비스쪽에 공개되어 공유될 것이다 




Sprint-1) Full Stack 개발자 되기

  - MEAN 개념과 개요

    + AngularJS

    + NodeJS + ExpressJS

    + MongoDB + Redis(Advanced)

  - MEAN 이외에 알아야 할 것들

    + JavaScript 

    + Bootstrap (or Zurb Foundation

    + NodeJS Modules

    + Mongoose



Sprint-1) 개발 준비 단계

  - 로컬 개발 환경 만들기 

    + Vagrant + Linux 환경 구성하기

    + 개발환경에 Node.js, MongoDB 설치하기 

    + Trello 에서 Scrum 개발하기 

  - MEAN Stack 개발도구 개발환경에 설치하고 익히기 

    + Yeoman : 클라이언트 라이브러리관리 및 배포 Grunt, Bower, Yo 설치하기 

    + NPM : 서버 라이브러리 관리

  - Git & GitHub 저장소 만들기 

    + Git Branch 전략 

    + GitHub 사용방법

  - 클라우드 환경에 배포하여 테스트 하기

    + Cloud Development(Nitrous.io) 를 통한 협업개발 및 테스트하기

    + Heroku같은 PaaS에 배포하여 테스트하기 

 


Sprint-1) MEANK Stack 개발 환경 구성하기

  - SRS 작성하기 

    + Software Request Requirement란 무엇인가?

    + Trello 에 소프트웨어 요구사항 정의서 작성하기 

    + Balsamiq 도구 사용하여 Wireframe & Mockup 그리기 화면 그리기

  - AngularJS Scaffolding 코드 사용하기 

    + angular-generator 알아보기 

    + angular 기본골격 만들기

    + NodeJS로 테스트 하기 

  - MEAN Stack 테스트 전략 

    + AngularJS에서의 테스트 방법 : Karma Framework

    + NodeJS에서의 테스트 방법 : Mocha 그외 



Sprint-2) 서비스 프로토타입 개발하기

  - 가장 중요한 화면 설계하기

    + 클라이언트 화면 Mockup 구체화 하기 

    + NodeJs에서 처리 할 RESTful API 설계하기 

    + MongoDB 에 저장 할 Document Schema 설계하기

  - MongoDB Store 개발 및 테스트

    + Dynamic Schema, Embedded vs Reference 에 대한 이해 

    + Mongoose 기반으로 Schema 만들기

    + Test Framework 이용한 Model 단위 테스트하기 

  - Node.js 기반 RESTful API 개발 및 테스트

    + RESTful API 코드 뼈대 만들기 in NodeJS

    + Test Framework 이용한 서비스 단위 테스트하기

    + RESTful Client 테스트 도구 또는 curl 로 호출 테스트하기

  - Angular.js 기반 화면 개발 및 테스트

    + AngularJS와 Bootstrap 이용하여 화면 구성하기

    + AngularJS 애플리케이션 구조 개발 및 RESTful API 서비스 개발 

    + Test Framework 이용한 화면 단위 테스트하기 

  - Heroku 같은 PaaS에 올려서 통합 테스트 하기 



Sprint-3) 서비스 개발 반복하기

  - 반복하기 

    + sprint-01에서 반복하였던 과정을 지속하기 

  - 메인 및 로그인 화면 만들기 

    + Bootstrap의 다양한 도구 활용

    + OAuth 기능 로그인 하기  

  - 채팅기능 만들기 

    + 채팅 Mockup 만들기

    + NodeJS Socket.io API 구현

    + MongoDB 채팅 Schema 설계

    + AngularJS Socket.io 멀티 케스팅 구현 

    + Bootstrap 통한 화면 구현 및 AngularJS 연동

 - Heroku 같은 PaaS에 올려서 프로토타입 테스트 하기 



Sprint-3) 서비스 런칭하기

  - 서비스 일일 빌드시스템 구성하기 

    + Travis (또는 Jenkins)기반 자동 배포 기능 만들기 

    + Travis 에서 빌드된 것을 클라우드시스템(PaaS) 자동 배포하기 

  - 런칭 페이지 만들기 

    + GitHub Pages를 이용한 런칭 만드는 방법

    + Bootstrap을 이용하여 런칭 페이지만들어 GitHub에 반영하기 

  - 모니터링 하기

    + 클라이언트 AngularJS Log 모니터링 하기
    + NodeJS Log 모니터링 하기 

    + MEAN Stack 운영 및 장애 모니터링 전략 



Next Project) MEAN Stack Advanced

  - 채팅기능 고도화하기 

    + SNS 서비스 아키텍쳐들 알아보기  

    + 대용량 데이터를 처리를 위한 아키텍쳐 설계하기

  - NodeJS + Redis + MongoDB 연동하기 

    + Redis 알아보기 

    + NodeJS-Redis 연동하기 

  - 정제된 데이터 RDBMS와 연동하기

    + NodeJS - MySQL 연동하기 

    + MongoDB의 MapReduce를 이용하여 데이터 마이닝하기 

    + NodeJS에서 MongoDB 마이닝 데이터 MySQL에 넣기 

  - SNS 서비스 운영 고도화

    + NodeJS와 Nginx 연동하기 

    + NodeJS를 Proxy 서버로 만들어 Clustering 하기

    + MongoDB Sharding 구조 만들기 

    + Redis Master/Slave구조 만들기 

  - 유지보수 전략

    + 테스트 환경과 프로덕션 환경의 재구성

    + NodeJS Scale-out 확장 전략

    + MongoDB Sharding 및 Data Backup 전략 

    + Redis Scale-out 전략 

  - 빅데이터의 활용

    + 개인화를 위한 데이터 활용 방안

    + MongoDB Integration Framework을 이용한 MapReducing


전체 서비스를 만들고 런칭하기까지 참 많은 것들을 익히고 개발해야 하지만, 누군가에게 의미있는 가치를 줄 수 있는 SNS(Social Network Service)를 할 수 있다는 것에 만족을 느낀다. 


posted by 윤영식
2013. 11. 20. 10:58 AngularJS/Start MEAN Stack

갑작이 떠오른 아이디어를 모바일 서비스로 빠르게 만들고 싶다. 클라이언트/서버/스토어 이런 것을 해줄 사람은 없다. 나 홀로 만들어 보고 프로토타입핑해서 스타팅해보고 싶을 때 MEAN Stack을 사용하자 




1. 스프링같은 "WebApp Framework" 에 대한 고민

  클라이언트단의 Android 또는 iOS 네이티브 코드는 익히는데 시간은 없고, 다양한 스마트 기기에 대응을 했으면 한다. 그리고 JavaScript를 어느 정도 할 수 있다. 이때 생각할 수 있는 대안이 "웹앱 사이트" 또는 "모바일 웹앱"을 만드는 것이다. 그러나 예전 jQuery 코드를 생각해 보자. 조금만 복잡해 지면 쉽게 스파게티가 되고, 테스트 코드는 엄두도 못내며 결국 유지보수의 골칫덩어리로 남게 된다. 

그렇다면 자바에서 Spring Framework과 같이 DI가 되면서 MVC처럼 모듈화 개발을 가능하게 해주는 클라이언트단 프레임워크는 없을까? 처음엔 Backbone.js를 살펴 보았고, 예로 Trello와 같은 서비스에서 사용을 하고 있다. 하지만 코드의 길이가 길어지고 불필요한 중복코드의 남발이 야기되어 좀 더 간결하면서 예전 Flex Framework과 같은 것은 없을지 고민해 보았다. HTML을 확장해서 사용해도 자동 해석을 하여 브라우져가 이해할 수 있는 HTML로 바꾸어 주면 되는 그런 프레임워크 말이다. 역시 구글 형님들도 똑같은 고민을 하고 프로젝트에 적용하고 놀라운 효과를 본 새로운 프레임워크를 공식 런칭하게 되니 이름하여 AngularJS





2. 새로운 "I/O Machine" 에 대한 고민

  서비스에서 요하는 기능중 가장 중요한 부분은 Push 기능이 아닐까 싶다. 실시간으로 변화하는 정보를 수많은 유저에게 바로 Push 서비스해줄 수 있는 서버가 필요하다. 하지만 Push를 위하여 별도의 Port를 사용하긴 싫고 웹앱이므로 요청하였던 HTTP Port를 사용하고 싶다면 어떤 것을 선택해야 할까?

서버단이 Java라면 vert.x도 고려해 볼 만하다. 하지만 아직 레퍼런스와 문서가 부족하다. 하지만 여기에 우리에게 구세주와 같은 세로운 I/O 머신이 나왔으니 V8엔진을 기반으로 돌아가는 Node.js이다. Node.js를 사용하는 가장 큰 목적중 하나가 Socket.io와 같은 이벤트 기반의 Push 모듈들이 아닐까 생각이든다. Node.js는 모듈기반으로 필요한 것들을 추가하여 사용할 수 있다. 이를 위하여 NPM(Node Package Manager)이 존재하고 방대한 모듈과 레퍼런스들이 존재한다. 또한 Chrome에 적용된 V8 자바스크립트 해석기를 달고 있으므로 모든 코딩은 JavaScript 만으로 이루어진다





3. 스키마에 자유로운 "Store"에 대한 고민 

  서비스를 빠르게 만들고 싶은데 개발을 진행하다 보면 자주 테이블이 변경되고 이에 맞추어 서버코드들도 수정을 해주거나 새롭게 테이블을 생성해 주는 번거로운 작업들을 거친다. 따라서 스키마가 자유로운 데이터베이스를 원한다. 그리고 서비스이므로 사용자의 데이터를 많이 쌓아 두고 싶다. 이를 통해 향후 마케팅 용으로 사용하려 한다. 이왕이면 클라이언트/서버가 모두 자바스크립트이므로 데이터베이스 핸들링도 자바스크립트 이면 좋겠다. 이를 위해 새롭게 나온 NoSQL 이 있으니 이름하여 MongoDB. JSON형태로 데이터를 저장하고 정규화 없이 도큐먼트 형태로 저장을 할 수 있어서 스키마의 제약이 없다. 또한 테라급의 데이터 정도는 MongoDB를 Sharding구조로 만들어 MapReduce를 이용하여 처리할 수 있다. 그리고 Node.js 처럼 V8 해석기를 탑재하여 데이터 핸들링을 JavaScript로 할 수 있다. 마치 Oracle의 SqlPlus에서 Sql을 수행하듯 MongoDB가 정의한 형식의 쿼리를 수행할 수 있다. 





4. Node.js를 기업용 WAS(Web Application Server)로 만들기  

  초창기 Java를 떠올려 보자. 처음엔 Applet에 열광하여 업무를 애플릿으로 만든적이 있다. 그때 서버단은 Apache + PHP정도 였을 것이다. 그리고 SUN에서 J2EE 스팩을 발표하면서 Tomcat, JBoss, WebSphere, WebLogic 같은 WAS가 나왔으며, 이 또한 C계열의 TP-Monitor처럼 Java기반 I/O Machine 이 나오게 된 것이다. Java진영의 WAS는 나름의 공통된 스팩을 기반으로 웹을 이끌어 갈 수 있는 새로운 I/O 머신으로 떠올랐고, 기업에서 요구하는 Transaction 처리에 대한 스팩도 제안하여 많이 사용하게 되었다. 최근에는 Tomcat같은 Servlet 엔진만 갖춘 WAS에 Spring Framework를 사용하면 충분히 기업용 Transaction을 처리하는 업무를 개발할 수 있는 환경까지 오게 되었다. 그러나 Java의 WAS가 web1.0의 중심 머신이었다면 Node.js기반의 WAS가 web2.0의 중심 머신으로 되기 위한 조건은 무엇일까?

  결국 Node.js위에 올라가는 Java의 J2EE 스팩과 같은 견고한 스팩이 필요하다. 현재까지는 아직 그러한 스팩이 나오진 않으나 Java의 Servlet 스팩에 비견할 만한 프레임워크가 나왔으니 그것이 Express.js 이다. 이제 web2.0의 I/O 머신은 Node.js가 될 것이고, 여기에 웹에 대한 표준 스팩구현체는-사실 Node.js를 위한 공통된 스팩은 없다- 아니지만 가장 많이 사용하는 Express.js를 통하여 Node.js가 Tomcat과 같은 기능을 보유하게 되었다.




Angular.js -> Node.js + Express.js -> MongoDB 를 사용하게 되면 

  - JavaScript로 모두 개발을 한다. 클라이언트/서버 분리하여 개발할 필요가 없다. 그냥 FullStack JavaScript개발을 하면 된다

  - 클라이언트 개발이 MV* 개발이 되어 서버 개발에 익숙한 개발자가 쉽게 접근할 수 있다 

  - 서버에서 Push 기능을 쉽게 적용할 수 있으므로 다양한 서비스의 응용이 가능하다 

  - 자바의 서블릿 엔진 구현체인 Tomcat과 같이 충실한 RESTful I/O 머신을 쉽게 구현할 수 있다

  - 서버의 멀티 쓰레드 문제에서 해방될 수 있다. Asynch의 세상에서 놀자 

  - 데이터 스키마를 미리 정의하지 않고 에자일하게 개발을 진행할 수 있다

  - 큰 규모의 데이터를 저장하고 처리 할 수 있다  



그러나 우리에게 남은 과제는 이것들을 어떻게 배우고 사용하는지 잘 모른다는 것이다. 각자를 심도 있게 배우는 것은 별도의 가이드북을 따로 보길 바란다. 대신 여기서는 실전 서비스를 어떻게 하면 빠르게 만들 수 있을지 노하우를 적어보려한다. 낮에는 회사에서 업무를 보면서 향후 스타트업을 준비하는 개발자 또는 사람들에게 좀 더 이로운 가치를 전달해 주고 싶은 개발자가 3개월안에 개발할 수 있는 서비스를 이 책을 통해 런칭할 수 있는 것이 가능해 질 수 있길 바란다. 그래서 책을 쓰면서 동시에 작은 서비스를 만들어 보려 한다. 책은 가이드이고 그물일 뿐이다. 실제 돌아가는 서비스가 물고기이다. 즉, 실제 서비스를 낚지 못하면 이 책의 가치도 없다고 생각한다. 실제로 이것을 증명해 보이고 싶다. 



Getting Start MEAN

          


posted by 윤영식
2013. 10. 28. 17:57 My Services/PlayHub

PlayHub는 Git Submodule을 사용하여 Main 밑으로 Frontend/Backend로 서브모듈단위로 나눌 것이다. 또한 아키텍쳐에 메세지 버스를 사용하므로 여러가지 모듈 분리가 필요할 것으로 예상된다 




아키텍쳐 

  - 구성 

    + 모바일 웹앱 : SPA(Single Page Application)으로 구성한다. AngularJS + BootFlat 사용예정 

    + 조회&푸쉬 서버 : Node.js를 이용하여 OAuth 로그인을 처리한다. Socket.io를 이용하여 Push를 한다 

    + 메세지 버스 : Redis를 사용하여 "조회&푸쉬 서버"와 "이슈정보 서버" 사이를 느슨하게 연결한다. PubSub 방식을 사용한다 

    + 이슈정보 서버 : Open API를 사용하여 Google Doc, DropBox, Trello, GitHub의 정보를 주기적으로 수집한다 

    + 스토어 서버 :  MongoDB 또는 MySql을 사용한다 

  - 장단점

    + 장점 : Backend의 Scale-out이 가능하고 다양한 언어를 통한 구현 및 Node.js를 통한 손쉬운 Push 구현이 가능하다 

    + 단점 : 관리 포인트가 늘어난다. 



Git 서브모듈 만들기 

  - git submodule <command> 를 사용한다 

  - 생성 순서 

    + GitHub에 Main 저장소를 만든다 

    + GitHub에 Submodule이 될 저장소를 만든다 

    + git clone <Main저장소 주소> playhub & cd playhub

    + git submodule add <Submodule 저장소 주소> <이름지정>

    + GitHub으로 git push 수행하기 

$ git clone https://github.com/ysyun/myplayhub.git

Cloning into 'myplayhub'...

remote: Counting objects: 3, done.

remote: Total 3 (delta 0), reused 0 (delta 0)

Unpacking objects: 100% (3/3), done.


$ cd myplayhub/

$ git submodule add https://github.com/ysyun/myplayhub-spa-webapp.git webapp

Cloning into 'webapp'...

remote: Counting objects: 3, done.

remote: Compressing objects: 100% (2/2), done.

remote: Total 3 (delta 0), reused 0 (delta 0)

Unpacking objects: 100% (3/3), done.

warning: LF will be replaced by CRLF in .gitmodules.

The file will have its original line endings in your working directory.


$ git status

# On branch master

# Changes to be committed:

#   (use "git reset HEAD <file>..." to unstage)

#

# new file:   .gitmodules

# new file:   webapp

#

$ git branch

* master


$ git commit -a -m "add submodule webapp"

[master 2fdaa1b] add submodule webapp

warning: LF will be replaced by CRLF in .gitmodules.

The file will have its original line endings in your working directory.

 2 files changed, 4 insertions(+)

 create mode 100644 .gitmodules

 create mode 160000 webapp


$ cat .gitmodules

[submodule "webapp"]

path = webapp

url = https://github.com/ysyun/myplayhub-spa-webapp.git


$ git push

Counting objects: 4, done.

Delta compression using up to 4 threads.

Compressing objects: 100% (3/3), done.

Writing objects: 100% (3/3), 404 bytes | 0 bytes/s, done.

Total 3 (delta 0), reused 0 (delta 0)

To https://github.com/ysyun/myplayhub.git

   0871412..2fdaa1b  master -> master



  - 전체 소스 받아오기 

    + git clone 으로 Main 저장소 받아오기 

    + git submodule init 명령으로 submodule 등록

    + git submodule update로 서브모듈에 대한 clone 작업 수행 

$ git clone https://github.com/ysyun/myplayhub.git

Cloning into 'myplayhub'...

remote: Counting objects: 6, done.

remote: Compressing objects: 100% (4/4), done.

remote: Total 6 (delta 0), reused 3 (delta 0)

Unpacking objects: 100% (6/6), done.


$ cd myplayhub/

$ git status

# On branch master

nothing to commit, working directory clean


$ git submodule init

Submodule 'webapp' (https://github.com/ysyun/myplayhub-spa-webapp.git) registered for path 'webapp'


$ git submodule update

Cloning into 'webapp'...

remote: Counting objects: 3, done.

remote: Compressing objects: 100% (2/2), done.

remote: Total 3 (delta 0), reused 0 (delta 0)

Unpacking objects: 100% (3/3), done.

Submodule path 'webapp': checked out '1bfbcac347a7d6ce4620900b8dad991a638f2763'


$ cd webapp/


~/myplayhub/webapp on  (detached from 1bfbcac)

$ ls

README.md

$ git status

# HEAD detached at 1bfbcac

nothing to commit, working directory clean


// 서브모듈이 현재 존재하지 않는 브랜치가 되었다

~/myplayhub/webapp on  (detached from 1bfbcac)

$ git branch

* (detached from 1bfbcac)

  master


// 해결을 위하여 branch swith를 해야하는데 "git checkout master"를 하면 된다 

$ git checkout master

Switched to branch 'master'


// 해결이 되었다 

~/myplayhub/webapp on  master

$ git branch

* master


// 이후 업데이트 할 내역이 있으면 

$ git pull 


  - 서브모듈 삭제

    + git submodule rm <submodule 디렉토리> 명령 없어졌음 

    + git rm <submodule 디렉토리> 명령으로 제거

    + .gitmodules 의 submodule관련 설정 3줄 제거 

$ git rm webapp

rm 'webapp'


$ vi .gitmodules

webapp submodule 삭제 


$ git status

# On branch master

# Changes to be committed:

#   (use "git reset HEAD <file>..." to unstage)

#

# deleted:    webapp

#

# Changes not staged for commit:

#   (use "git add <file>..." to update what will be committed)

#   (use "git checkout -- <file>..." to discard changes in working directory)

#

# modified:   .gitmodules


$ git commit -a -m "delete webapp submodule"

[master 8305c3c] delete webapp submodule

 2 files changed, 4 deletions(-)

 delete mode 160000 webapp


$ git status

# On branch master

# Your branch is ahead of 'origin/master' by 1 commit.

#   (use "git push" to publish your local commits)

#

nothing to commit, working directory clean


$ git push




Git 브랜치 전략

  - 브랜치 전략에 대하여 숙지한다

  - master 브랜치에 어느 정도 환경설정이 끝났다면 협업을 위하여 develop 브랜치를 만든다. 

    + master와 develop은 절대 삭제되지 않고 계속 유지 되는 브랜치로 둔다 

  - 기능을 구현하기 시작하면 develop브랜치에서 feature 브랜치를 만든다 

    + Story 단위로 만들어서 관리한다 

    + master에서 따오지 않고 develop에서 브랜치하고, remote origin에 push하지 않는다 

    + 필요없을시 삭제한다 

  - develop 브랜치에서 어느 정도 되었다 싶으면 release 브랜치를 만든다

    + release 번호를 달고 수정이 필요한 부분을 보완하고 tag를 단다 

    + master로 merge를 한다 

  - master에 문제가 발견되었을 때 hotfix 브랜치를 만든다 

    + hotfix는 develop과 master 양쪽에 반영을 한다 


   Master + Develop 브랜치 : 영원한 브랜치 

   Feature + Release + Hotfix 브랜치 : 보조 브랜치 (삭제가능)



<참조>

  - Git 서브모듈 명령어 설명

  - Git Submodule 관리 팁

  - GitHub SSH 만들기 

posted by 윤영식
2013. 10. 5. 20:28 Protocols/OAuth

OAuth 트위터에서 Consumer/Acces Key를 생성한 후, Node.js 상에서 EveryAuth를 이용하여 인증방법에 대해서 알아보자 



Twitter 키 생성하기 

  - https://dev.twitter.com/apps/new  에서 생성함

  - 이름, 설명, url -예, http://sv.mobiconsof.co.kr -, 약관체크, CAPCHA등록하고 submit을 하면 키생성 화면이 나온다 

  - 추가로 "access token"을 생성할 수도 있다

     단 여기서 중요한 것은 "Callback URL"을 입력하는 것이다. (sv.mobiconsoft.co.kr은 /etc/hosts파일에 설정한 테스트 도메인)

  - 만들어 놓은 자신의 키들은 https://dev.twitter.com/apps 에서 애플리케이션 별로 확인을 할 수 있다 



Twitter 키 사용하기 

  - Node.js에서 everyauth 모듈을 통하여 사용하는 예를 보자

  - Key 생성시 url로 "http://sv.mobiconsoft.co.kr" 를 입력하였다면 - 각자의 public 또는 test url을 입력하면 된다 - hosts 파일에 등록하고 dig 와 ping 명령으로 확인을 한다 

$ sudo vi /etc/hosts

127.0.0.1    sv.mobiconsoft.co.kr


// ping 성공

$ ping sv.mobiconsoft.co.kr

PING sv.mobiconsoft.co.kr (127.0.0.1): 56 data bytes

64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.044 ms

64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.051 ms

 

  - Express.js, Angular.js 와 Bootstrap을 이용한 코드를 사용할 것이다. 

$ git clone https://github.com/ganarajpr/express-angular.git && cd express-angular

$ 소스 수정하기

  - app.js 안에서 트위터에 대한 consumerKey와 consumerSecret을 입력한다 

everyauth

    .twitter

    .consumerKey('2yQ.....img')

    .consumerSecret('TbhKnP.....fQ1gyvUO4')

    .findOrCreateUser( function (sess, accessToken, accessSecret, twitUser) {

        console.log('twitter User data is', twitUser);

        return usersByTwitId[twitUser.id] || (usersByTwitId[twitUser.id] = addUser('twitter', twitUser));

    })

    .redirectPath('/');

  - Node.js 를 수행한다 

$ sudo node app.js

  - 브라우져에서 호출하기 : 우측 "Login"에서 "Sign in with Twitter" 버튼을 클릭한다 

 - 트위터 승인 화면이 나온다 : "애플리케이션 승인" 버튼을 클릭하면 다시 원래 화면으로 돌아온다 

 - Node.js 콘솔에 뿌려진 사용자 정보를 보자 : 해당 정보를 저장하여 애플리케이션에서 사용을 하면 된다 

$ sudo node app.js

starting step - getRequestToken

...finished step

starting step - storeRequestToken

...finished step

starting step - redirectToProviderAuth

...finished step

starting step - extractTokenAndVerifier

...finished step

starting step - getSession

...finished step

starting step - rememberTokenSecret

...finished step

starting step - getAccessToken

...finished step

starting step - fetchOAuthUser

...finished step

starting step - assignOAuthUserToSession

...finished step

starting step - findOrCreateUser

twitter User data is { id: 48553254,

  id_str: '48553254',

  name: 'Software 행복공동체 키우기',

  screen_name: 'nulpulum',

  location: 'Seoul Korea',

  description: '자건거타기 실용주의프로그래머 에자일정신 글로벌소프트웨어만들기 행복공통체키우기 지식나눔운동',

  url: 'http://t.co/wunTkx6q',

  entities: { url: { urls: [Object] }, description: { urls: [] } },

  protected: false,

  followers_count: 98,

  friends_count: 533,

  listed_count: 0,

  created_at: 'Fri Jun 19 00:44:39 +0000 2009',

  favourites_count: 12,

  utc_offset: 32400,

  time_zone: 'Seoul',

  geo_enabled: true,

  verified: false,

  statuses_count: 506,

  lang: 'ko',

  status:

   { created_at: 'Fri Oct 04 12:15:34 +0000 2013',

     id: 386102292232937500,

     id_str: '386102292232937472',

     text: 'I signed up for Human-Computer Interaction from @ucsandiego on @Coursera! https://t.co/FdQc2h654C #hci',

     source: '<a href="http://twitter.com/tweetbutton" rel="nofollow">Tweet Button</a>',

     truncated: false,

     in_reply_to_status_id: null,

     in_reply_to_status_id_str: null,

     in_reply_to_user_id: null,

     in_reply_to_user_id_str: null,

     in_reply_to_screen_name: null,

     geo: null,

     coordinates: null,

     place: null,

     contributors: null,

     retweet_count: 0,

     favorite_count: 0,

     entities:

      { hashtags: [Object],

        symbols: [],

        urls: [Object],

        user_mentions: [Object] },

     favorited: false,

     retweeted: false,

     possibly_sensitive: false,

     lang: 'en' },

  contributors_enabled: false,

  is_translator: false,

  profile_background_color: '9AE4E8',

  profile_background_image_url: 'http://abs.twimg.com/images/themes/theme16/bg.gif',

  profile_background_image_url_https: 'https://abs.twimg.com/images/themes/theme16/bg.gif',

  profile_background_tile: false,

  profile_image_url: 'http://a0.twimg.com/profile_images/2956822008/b20249749c11917f4ce9e29263ba1b92_normal.jpeg',

  profile_image_url_https: 'https://si0.twimg.com/profile_images/2956822008/b20249749c11917f4ce9e29263ba1b92_normal.jpeg',

  profile_banner_url: 'https://pbs.twimg.com/profile_banners/48553254/1355144673',

  profile_link_color: '0084B4',

  profile_sidebar_border_color: 'BDDCAD',

  profile_sidebar_fill_color: 'DDFFCC',

  profile_text_color: '333333',

  profile_use_background_image: true,

  default_profile: false,

  default_profile_image: false,

  following: false,

  follow_request_sent: false,

  notifications: false }

...finished step

starting step - compileAuth

...finished step

starting step - addToSession

...finished step

starting step - sendResponse

...finished step



<참조> 

  - How to get twitter key

  - Express에서 EveryAuth 사용하기 

  - Facebook에서 EveryAuth 적용하기

'Protocols > OAuth' 카테고리의 다른 글

[OAuth] 인증과 권한 개념잡기  (0) 2013.10.06
posted by 윤영식
2013. 9. 25. 09:16 My Services/Smart Visualization

SPA 방식의 개발을 위해서는 Client에 MV* Framework을 선택해야 한다. 초창기는 Backbone.js(이하, 백본)를 많이 사용하였고, 구현체로 Trello가 있으니 해당 서비스의 아키텍쳐를 참조해 보자. 그리고 SKT에서 코너스톤이라는 이름의 Framework도 백본을 사용하여 "웹앱"을 만들 수 있도록 안드로이드/iOS용 런타임 환경도 제공한다. 백본은 가벼우면서 광범위하게 많이 사용을 하고 있지만 개발 복잡성 비교에서 중간정도에 위치한다. 


<Trello 아키텍쳐>



브라우져에서 동작하는 애플리케이션의 템플릿이 .html 이면서 <chart ../> 와 같이 사용자 정의 html tag를 사용하여 View단을 좀 더 단순화 및 컴포넌트화 할 수 있는 AngularJS를 사용하기로 한다. AngularJS에는 여러 장점이 있지만 "웹 애플리케이션을 견고하게 만드는 방법" 블로깅을 통해 백본대신 왜 AngularJS를 선택했는지 갈음한다. AngularJs 프레임워크 기반의 SPA 개발을 위하여 쉽게 접근하는 방법은 도구를 통하여 하는 것인데, 오스마니님이 개발한 Yeoman을 통하여 AngularJS기반 프로젝트를 쉽게 구성할 수 있다. 



1. Yeoman 사용하기

  - 사전에 NodeJS 최신버전이 설치되어 있어야 한다. 

    설치후 npm (Node Package Manager)를 통하여 Yeoman(이하, yo)을 설치함

  - Yo 설치하기  : 가이드를 따라서 설치하면 된다 

    + yo : angular, express 등 관련 framework에 대한 scaffolding 프로젝트를 자동으로 만들어 주는 기능을 한다 

              generator-angular, generator-express와 같이 generator-*가 앞에 붙는다. npm 통하여 설치함 

              generator 만드는 방법은 "generator-angular와 express 연동하기" 블로그 참조한다 

    + bower : client단의 필요한 컴포넌트를 자동설치할 수 있다. 의존성관계 bower.json 파일에 설정한다. npm 과 유사함

    + grunt : 개발된 소스를 테스트, 빌드하는 javascript의 ant과 같은 역할을 한다. Gruntfile.js 에 설정한다 

    


  - yo 설치후 "yo -h" 수행하면 설치된 generator 목록을 볼 수 있다. 또는 yo라고 수행해도 된다 (보여지는 형식만 틀리다)

// generator와 관련된 MUST HAVE generator

$ sudo npm install -g generator-generator


// angularjs 관련 generator 설치 

$ sudo npm install -g generator-angular


// AngularJS 관련 scaffolding 명령 목록 

$ yo -h 

Angular

  angular:app  (default : angular) 

  angular:common

  angular:constant

  angular:controller

  angular:decorator

  angular:directive

  angular:factory

  angular:filter

  angular:main

  angular:provider

  angular:route

  angular:service

  angular:value

  angular:view


// express generator도 설치하자 

sudo npm install -g generator-express


* 여기서 잠깐!

만일 사용하는 OS가 Windows라면 지금 당장 Linux를 설치해서 사용한다. 우리가 배포하고 서비스하는 서버는 Cloud환경일 가능성이 다분히 높기 때문이다. Windows Server같은 곳에서 Node.js를 운영하는 것은 상상조차 하고 싶지 않다. 

  - Vagrant를 Windows에 설치해서 쉽게 Linux환경에 접근토록 하자



2. AngularJS 스케폴딩 만들기

  - yo를 통하여 AngularJS 기반의 SPA 프로젝트를 만든다. 여기에는 Node.js로 동작하는 서버 코드는 생성되지 않는다 

  - GitHub에 SmartVisualization 라고 생성을 한다. 각자의 계정에 만든다 

// GitHub의 저장소 clone 하고 angular 스케폴딩 명령 수행

$ git clone https://github.com/<자신의 아이디>/SmartVisualization.git  SmartVisualization

$ cd SmartVisualization

$ yo angular:app SmartVisualization

[?] Would you like to include Twitter Bootstrap? Yes

[?] Would you like to use the SCSS version of Twitter Bootstrap with the Compass CSS Authoring Framework? No

[?] Which modules would you like to include? (Press <space> to select)

❯⬢ angular-resource.js

 ⬢ angular-cookies.js

 ⬢ angular-sanitize.js


... 모듈들 자동 설치 중략 ...


// npm : package.json

// bower : .bowerrc, bower.json

// grunt : Gruntfile.js

// test 도구인 karma: karma*

// git 관련 : .git*

$ ls -alrt

-rw-r--r--   1 nulpulum  staff    11  2 21  2013 .gitattributes

-rw-r--r--   1 nulpulum  staff   415  4 16 00:02 .editorconfig

-rw-r--r--   1 nulpulum  staff   394  6 10 21:31 .jshintrc

-rw-r--r--   1 nulpulum  staff    56  6 10 21:31 .gitignore

-rw-r--r--   1 nulpulum  staff    44  6 10 21:31 .bowerrc

-rw-r--r--   1 nulpulum  staff   120  7 15 06:32 .travis.yml

-rw-r--r--   1 nulpulum  staff  1304  8 12 05:33 karma.conf.js

-rw-r--r--   1 nulpulum  staff  1348  8 12 05:33 karma-e2e.conf.js

drwxr-xr-x   5 nulpulum  staff   170  9 25 08:51 ..

drwxr-xr-x   5 nulpulum  staff   170  9 25 09:04 test

-rw-r--r--   1 nulpulum  staff   404  9 25 09:04 bower.json

-rw-r--r--   1 nulpulum  staff  8217  9 25 09:04 Gruntfile.js

drwxr-xr-x  12 nulpulum  staff   408  9 25 09:04 app

drwxr-xr-x  16 nulpulum  staff   544  9 25 09:04 .

drwxr-xr-x  39 nulpulum  staff  1326  9 25 09:04 node_modules

-rw-r--r--   1 nulpulum  staff  1466  9 25 09:05 package.json


  - Yeoman을 통하여 AngularJS 프로젝트 스케폴딩을 만들었다면 Grunt명령을 통하여 테스팅을 위한 서버를 기동할 수 있다 

// 명령을 수행하면 Node.js를 기반으로 서버가 기동하고 9000 port로 Chrome브라우져가 자동 실행되어 호출된다

// Grunt는 Java의 Ant와 같은 도구이다 

$ grunt server

Running "server" task


Running "clean:server" (clean) task

Cleaning .tmp...OK


Running "concurrent:server" (concurrent) task


    Running "copy:styles" (copy) task

    Copied 2 files

    

    Done, without errors.


    Elapsed time

    copy:styles  15ms

    Total        16ms


    Running "coffee:dist" (coffee) task


    Done, without errors.


    Elapsed time

    coffee:dist  10ms

    Total        10ms


Running "autoprefixer:dist" (autoprefixer) task

File ".tmp/styles/bootstrap.css" created.

File ".tmp/styles/main.css" created.


Running "connect:livereload" (connect) task

Started connect web server on localhost:9000.


Running "open:server" (open) task


Running "watch" task

Waiting...


// 브라우져 자동 실행 및 9000 포트 자동 호출된 결과물 


다음은 AngularJS 에 대하여 알아보자



<참조>

  - Client MV* Framework에대한 ToDoMVC 개발 복잡성 비교

  - Yeoman의 generator-angular와 express 연동하기

  - Vagrant를 통하여 개발환경 꾸미기

posted by 윤영식
2013. 9. 23. 11:40 NodeJS/Concept

Node.js 위에 기본적으로 Express.js를 많이 사용하지만 좀 더 추상화 되고 MVC 다운 프레임워크를 사용해 보고자. Express.js 및 Socket.io 와 다양한 데이터베이스를 추상화해서 사용할 수 있는 기능을 갖춘 Sails.js Framework을 사용해 보도록 한다. 



1. 사용 배경

  - Enterprise 급 MVC Framework : Express.js + Socket.io 기본 설정되어 사용

  - Waterline 이라는 adapter를 통하여 다양한 Database의 호환성 보장 

    + 흐름 : Model 객체 <-> Waterline <-> Databases 

    + 지원 데이터베이스 : MySql, PostgreSql, MongoDB, etc

    + 데이터베이스 연결을 하지 않으면 파일 또는 디스크에 백만개 밑으로 key:value 저장하는 node-dirty의 어뎁터를 사용한다

  - 손쉬운 설치 및 RESTful Web Services 개발

  - socket.io 및 redis 통합 

  - SPA (Single Page Application) 개발을 위한 client framework (backbone, ember, angular)의 adapter 제공 

    + Yeoman generator 제공 : Generator Sails AngularJS



2. 설치 및 사용

  - Yeoman을 기본 설치한다 : Yeoman 이해와 설치하기

  - "generator-sails-angular" 설치 후 "yo" 명령수행 : 빨간색의 Sails-angular 선택 (주의, sails 0.9.4 버전과 호환안됨)

// generator 설치

$ sudo npm install -g generator-sails-angular generator-angular


// yeoman 명령 수행

$ yo

[?] What would you like to do? (Use arrow keys)

 ❯ Run the Angular generator (0.3.1)

   Run the Backbone generator (0.1.7)

   Run the Bootstrap generator (0.1.3)

   Run the Express generator (0.1.0)

   Run the Generator generator (0.2.0)

   Run the Mocha generator (0.1.1)

   Run the Sails-angular generator (0.0.1)

   Update your generators

   Install a generator

   Find some help

   Get me out of here!


// 선택을 하면 Server에 Sails 환경과 Client에 AngularJS 환경이 동시에 설정된다 

// Client는 기본 Twitter bootstrap을 사용하고 Client 추가 Component는 "bower install <Component명칭>" 을 사용하여 설치한다 

// Server는 "npm install <module명칭>"을 사용하여 설치한다


  - "sails lift" 수행 (2013.09.23현재 0.9.4 버전으로 sails upgrade : sudo npm update sails -g )

$ sails lift

info:

info:

info:    Sails.js           <|

info:    v0.9.4              |\

info:                       /|.\

info:                      / || \

info:                    ,'  |'  \

info:                 .-'.-==|/_--'

info:                 `--'-------'

info:    __---___--___---___--___---___--___

info:  ____---___--___---___--___---___--___-__

info:

info: Server lifted in `/Users/prototyping/sails/test`

info: To see your app, visit http://localhost:1337

info: To shut down Sails, press <CTRL> + C at any time.


debug: --------------------------------------------------------

debug: :: Mon Sep 23 2013 10:05:04 GMT+0900 (KST)

debug:

debug: Environment : development

debug: Port : 1337

debug: --------------------------------------------------------



3. Sails MVC 이해하기 

  - Model 

    + Waterline 이라는 Sails Adapter를 통하여 다양한 데이터베이스의 객체로 사용되어 진다 

    + 단, Waterline의 형식(JSON 포멧)에 맞추어서 Model을 정의한다 : 모든 데이터베이스에 추상화된 정의이다 

    + /config/adapters.js 파일에 Database 환경 설정을 한다 또한 관련 데이터 베이스 adapter를 설치해야 함 

       예) MongoDB 설치 

$ npm install sails-mongo --save

npm http 200 https://registry.npmjs.org/sails-mongo

npm http GET https://registry.npmjs.org/sails-mongo/-/sails-mongo-0.9.5.tgz

.. 중략 ..

    + /api/models/ 디렉토리 밑에 <Model>.js 파일이 위치한다

// model 생성  

$ sails generate model Person


// 생성 내역 : Model의 맨앞자는 자동으로 대문자로 변경됨 

$ /api/models> cat Person.js

/*---------------------

:: Person

-> model

---------------------*/

module.exports = {

attributes : {

// Simple attribute:

// name: 'STRING',


// Or for more flexibility:

// phoneNumber: {

// type: 'STRING',

// defaultValue: '555-555-5555'

// }

}

};/


// CRUD : 기본 Deferred Promised 지원 

생성 : Person.create

조회 : Person.findOne/find

갱신 : Person.update

제거 : Person.destroy



  - Controller

    + view와 model 사이의 제어 역할을 한다

    + /api/controllers/ 디렉토리에 위치함 

// comment controller 만들기 : 최종 명령 tag뒤에 스페이스가 있으면 안된다. 

// controller 뒤 첫번째 인자 : comment가 controller의 명칭

// controller 두번째 인자이후 : comment의 메소드 명칭들 열거  

$ sails generate controller comment create like tag 


// 결과 

$ /api/controllers> cat CommentController.js

/*---------------------

:: Comment

-> controller

---------------------*/

var CommentController = {

// To trigger this action locally, visit: `http://localhost:port/comment/create`

create: function (req,res) {

// This will render the view: /views/comment/create.ejs

res.view();

},


// To trigger this action locally, visit: `http://localhost:port/comment/like`

like: function (req,res) {

// This will render the view: /views/comment/like.ejs

res.view();

},


// To trigger this action locally, visit: `http://localhost:port/comment/tag`

tag: function (req,res) {

// This will render the view:/views/comment/tag.ejs

res.view();

}


};

module.exports = CommentController; 


// 브라우져 호출 

http://localhost:1337/comment/create (like, tag) 


  - Route

    + RESTful 호출을 uri를 제어하고 싶다면 Route를 지정한다 

    + /config/routes.js 파일안에 정의한다 

    + controller와 action을 정의한다 

    + home의 경우 

       1) /config/routes.js 에 하기와 같이 home 컨트롤러 정의 

       2) /api/controllers/HomeController.js 존재 : index액션(펑션) 존재

       3) /views/home/index.ejs 파일 존재 : 서버에서 rendering되어 클라이언트 응답 

// Routes

// *********************

// 

// This table routes urls to controllers/actions.

//

// If the URL is not specified here, the default route for a URL is:  /:controller/:action/:id

// where :controller, :action, and the :id request parameter are derived from the url

//

// If :action is not specified, Sails will redirect to the appropriate action 

// based on the HTTP verb: (using REST/Backbone conventions)

//

// action을 정의하지 않으면 다음의 기본 형식을 따른다 

// GET:     /:controller/read/:id

// POST:   /:controller/create

// PUT:      /:controller/update/:id

// DELETE:/:controller/destroy/:id

//

// If the requested controller/action doesn't exist:

//   - if a view exists ( /views/:controller/:action.ejs ), Sails will render that view

//   - if no view exists, but a model exists, Sails will automatically generate a 

//       JSON API for the model which matches :controller.

//   - if no view OR model exists, Sails will respond with a 404.

//

module.exports.routes = {

// To route the home page to the "index" action of the "home" controller:

'/' : {

controller : 'home'

}


// If you want to set up a route only for a particular HTTP method/verb 

// (GET, POST, PUT, DELETE) you can specify the verb before the path:

// 'post /signup': {

// controller : 'user',

// action : 'signup'

// }


// Keep in mind default routes exist for each of your controllers

// So if you have a UserController with an action called "juggle" 

// a route will be automatically exist mapping it to /user/juggle.

//

// Additionally, unless you override them, new controllers will have 

// create(), find(), findAll(), update(), and destroy() actions, 

// and routes will exist for them as follows:

/*


// Standard RESTful routing

// (if index is not defined, findAll will be used)

'get /user': {

controller : 'user',

action : 'index'

},

'get /user/:id': {

controller : 'user',

action : 'find'

},

'post /user': {

controller : 'user',

action : 'create'

},

'put /user/:id': {

controller : 'user',

action : 'update'

},

'delete /user/:id': {

controller : 'user',

action : 'destroy'

}

*/

};


  - View

    + Sails는 기본 ejs를 사용한다 

    + /views/ 밑에 위치한다 

    + Partial 파일을 include 할 수 있다 

    + 메인 파일 : layout.ejs 

   


  - Adapter

    + 데이터베이스를 바꾸고 싶다면 /config/adapters.js 에서 내용을 바꾼다 : MongoDB를 사용한다면 mongo adapter를 설치해야 한다

// Configure installed adapters

// If you define an attribute in your model definition, 

// it will override anything from this global config.

module.exports.adapters = {


// If you leave the adapter config unspecified 

// in a model definition, 'default' will be used.

'default': 'memory',

// In-memory adapter for DEVELOPMENT ONLY

// (data is NOT preserved when the server shuts down)

        // Sails-dirty는 Node-Dirty의 Adapter를 memory or disk에 JSON 을 저장하는 store이다. 백만개 밑으로 저장시 사용

memory: {

module: 'sails-dirty',

inMemory: true

},


// Persistent adapter for DEVELOPMENT ONLY

// (data IS preserved when the server shuts down)

// PLEASE NOTE: disk adapter not compatible with node v0.10.0 currently 

// because of limitations in node-dirty

// See https://github.com/felixge/node-dirty/issues/34

disk: {

module: 'sails-dirty',

filePath: './.tmp/dirty.db',

inMemory: false

},


// MySQL is the world's most popular relational database.

// Learn more: http://en.wikipedia.org/wiki/MySQL

mysql: {

module : 'sails-mysql',

host : 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS',

user : 'YOUR_MYSQL_USER',

password : 'YOUR_MYSQL_PASSWORD',

database : 'YOUR_MYSQL_DB'

}

};

   + generator-sails-angular 를 yeoman통하여 설치하였다면 SPA를 개발하는 것이므로 실제로 ejs 쓸 일이 거의 없고, Client 단에서 AngularJS 정의에 따라 Routing과 View Control이 발생한다. 따라서 Controller를 통하여 수행할 action에서 response.view() 하는 것이 아니라, response.json(<JSON Object>) 만을 응답으로 주면 될 것으로 보인다. 또한 업무적인 처리는 action에서 구현하면 된다   

   + response.send(형식)

res.send(); // 204

res.send(new Buffer('wahoo'));

res.send({ some: 'json' });

res.send('<p>some html</p>');

res.send('Sorry, cant find that', 404);

res.send('text', { 'Content-Type': 'text/plain' }, 201);

res.send(404);

   + response.json(형식)

res.json(null);

res.json({ user: 'tj' });

res.json('oh noes!', 500);

res.json('I dont have that', 404);


* Redis나 데이터베이스의 외부 인터페이스 라이브러리는 Custom Adapter 개발을 수행한다. (만드는 방법



3. MongoDB 연결하여 RESTful 호출 테스트 

  - mongodb 환경설정 : sails-mongo 어뎁터가 설치되어 있어야 함 

$ cd config && vi adapters.js  이동하여 mongodb 설정 입력


////////////////////////

// adapters.js 수정 내역 

module.exports.adapters = {

  // If you leave the adapter config unspecified 

  // in a model definition, 'default' will be used.

  'default': 'mongo',


  // In-memory adapter for DEVELOPMENT ONLY

  memory: {

    module: 'sails-memory'

  },


  // Persistent adapter for DEVELOPMENT ONLY

  // (data IS preserved when the server shuts down)

  disk: {

    module: 'sails-disk'

  },


  // sails v.0.9.0

  mongo: {

    module   : 'sails-mongo',

    host     : 'localhost',

    port     : 27017,

    user     : '',

    password : '',

    database : 'sailsdb'

  }

};

  

  - Person model 수정

// ProjectName/models/Person.js  수정 내역 

module.exports = {

  attributes: {

  name: 'string',

  phone: 'string',

  address: 'string',

  sex: 'string',

  etc: 'string' 

  }

};


  - PersonController의 create 수정 : Promise 구문 형식 사용가능  

// ProjectName/controllers/PersonController.js  수정 내역

module.exports = {

  create: function (req,res) {

    Person.create({ 

      name: req.param('name'),

      phone: req.param('phone'),

      address: req.param('address'),

      sex: req.param('sex'),

      etc: req.param('etc')

    }).done(function(err, person){

      if(err) throw err;

      res.json(person);

    });

  },

  .. 중략 ..

}


  - Postman을 통하여 호출 테스트 : Postman chrome extension을 설치한다. 


  - mongo shell 에서 person collection 조회 : model이 mongodb에서 collection과 맵핑된다  

///////////////////

// mongo shell 확인 

mongo

MongoDB shell version: 2.4.5

connecting to: test

show dbs

local 0.078125GB

sailsdb 0.203125GB

use sailsdb

switched to db sailsdb

show collections

person

system.indexes

> db.person.find();

{ "name" : "도원", "phone" : "1004", "address" : "seoul", "sex" : "man", "etc" : "developer", "createdAt" : ISODate("2013-09-23T02:28:51.281Z"), "updatedAt" : ISODate("2013-09-23T02:28:51.281Z"), "_id" : ObjectId("523fa76377e9f89562000001") }



4. MongoDB의 _id를 sequence number로 바꾸기 

  - "_id" : ObjectId("523fa76377e9f89562000001") 라고 나오는 것을 "_id" : 1 씩 증가하는 정수로 바꾸기 

  - 최대한 sails-mongo adapter의 api를 사용하여 구현한다 

  - sequence document 만들기 

> db.seq.insert(

 {

   _id: 'personid',

   seq: 0

 });

> db.seq.find();

{ "_id" : "personid", "seq" : 0 }


  - Sails Model을 정의한다 

$ sails generate model seq

$ cd models && vi Seq.js


// Seq.js 내역 

// _id를 통하여 여러 model의 sequence 값을 구분하기 위해 사용한다 

module.exports = {

  attributes: {

  _id: 'string',

  seq: 'integer'

  }

};


  - Person의 모델에서 명시적으로 _id 를 integer로 정의한다 

module.exports = {

  attributes: {

  _id: 'integer',

  name: 'string',

  phone: 'string',

  address: 'string',

  sex: 'string',

  etc: 'string'

  }

};


  - PersonController.js 내역 수정 : Gist 소스 파일

  /**

   * /person/create

   */ 

  create: function (req,res) {

      // seq 번호를 얻어온다 

      Seq.find({_id: 'personid'}).done(function(err, seqObj) {

        if(err) throw err;

        // 배열임을 주의  

        var seqNo = seqObj[0].seq;

        seqNo++;

        console.log('>>> seqNo is', seqNo);

        

        // insert시에 _id 값을 넣어준다 

        Person.create({ 

          _id: seqNo, 

          name: req.param('name'),

          phone: req.param('phone'),

          address: req.param('address'),

          sex: req.param('sex'),

          etc: req.param('etc')

        }).done(function(err, person){

          if(err) throw err;

          res.json(person);

        });


       // sails-mongo의 api에 한번에 select & update해주는 findAndModify 메소드가 없는 관계로 seqNo를 update해준다 

        Seq.update({_id: 'personid'}, {seq: seqNo}).done(function(err, seqObj){

          if(err) throw err;

        })

      });

  },


  - Postman으로 테스트 데이터를 입력한다 


  - mongo shell로 mongodb에 저장된 내역을 확인해 보자 

// postman통하여 데이터를 2개 넣었을 경우 

> db.seq.find();

{ "_id" : "personid", "seq" : 2, "updatedAt" : ISODate("2013-09-23T09:47:29.548Z") }


> db.person.find();

{ "_id" : 1, "name" : "윤도원-8", "phone" : "1004", "address" : "목동", "sex" : "사람", "etc" : "모비콘 블로깅", "createdAt" : ISODate("2013-09-23T09:45:36.391Z"), "updatedAt" : ISODate("2013-09-23T09:45:36.391Z") }

{ "_id" : 2, "name" : "윤도원-9", "phone" : "1004", "address" : "목동", "sex" : "사람", "etc" : "모비콘 블로깅", "createdAt" : ISODate("2013-09-23T09:47:29.547Z"), "updatedAt" : ISODate("2013-09-23T09:47:29.547Z") }

>

 

 

5. Sails를 통항 WebApp 만들기

  - SailsCast Site 참조

  - SailsCast GitHub 소스

  - 유튜브 동영상 25 강좌 보기


 

<참조>

  - Sails.js + AngularJS + MongoDB 데모

  - MongoDB auto increment Sequence Field 방법

  - Mongoose Sequence Table 통한 auto increment plugin  : express 와 mongoose 사용시 해당 plugin을 사용

posted by 윤영식
2013. 9. 4. 14:05 AngularJS/Start MEAN Stack

Mobile 서비스 개발을 위하여 JavaScript 기반의 기술 스택을 선택하여 사용하다 보니 1년사이에 MongoDB, Express.js, Angular.js, Node.js를 공부하고 솔루션에 적용하게 되었다. 그간 해당 기술들을 추상화한 프레임워크들을 사용하여 개발을 진행해 왔다. 그러나 가끔 풀리지 않는 문제에 대한 근원적인 해결책을 찾는데는 다시 처음부터 추상화 내역에 대한 이해가 바탕이 되지 않으면 풀리지 않았다. 


즉, 기본 스택을 추상화한 스택사용시에는 추상화 영역에 대한 사용경험과 전반적인 이해가 없이는 오히려 시간공수만 더 늘어나는 격이 된다. 예로 Express와 MongoDB를 좀 더 수월하게 사용키위하여 Trains, Sails Framework들을 사용해 보았고 나름의 장점은 있으나 왠지 남의 옷을 입은듯한 느낌이랄까? 문제 발생시 빠른 대응이 어려웠다 (개인적으론 Trains Framework의 Node.js단의 IoC 방식을 좋아한다)


따라서 소규모 팀으로 개발하면서 하나의 가치를 빠른 시간안에 전달하고자 한다면 기본 Stack기반으로 진행하면서 필요한 시점에 스스로 추상화 모듈을 만들어 사용하는 방법이 좋지 않을까 생각한다. (Don't Invent Wheel 이니 초기엔 쓸만한 것을 GitHub에서 찾아 응용하고 없으면 개발하자) 그런 의미에서 mean.io 에서 기본 Stack에 충실하면서 현재 사용되는 최신 개발 기술들 - Jade, Bootstrap, Mongoose, Bower, Grunt 같은 - 도 함께 접목되어 적당히 어려운 상태이므로 개발시 집중(Flow)을 가능케 하는 구조를 가지고 있다. (너무 어려우면 흥미를 읽고, 너무 쉬우면 긴장감이 떨어진다. 적당히 어려우면 흥미와 긴장감을 주어 개발시 집중을 가능케 한다) 



1. 설치

  - 홈페이지에서 zip 파일 다운로드 

  - 프로젝트로 이동해서 

$ cd mean


// 사전에 node.js 설치 

// Node, npm 설치 shell

$ npm install .

 

// 사전에 mongodb 설치하고 start

$ grunt



2. 접속 

  - http://localhost:3000/

    + Sign Up 할 수 있다

    + 테스트 글을 CRUD 할 수 있다 

    


  - MongoDB 

> show collections

articles

sessions

system.indexes

users


해당 스택을 통해 SPA(Single Page Application) 개발에 대한 Lean(신속한) 스타트업을 시도하자.

 


<참조>

  - mean.io

posted by 윤영식
2013. 8. 14. 14:32 NodeJS/Modules

Node.js 프로젝트에서 사용된 자신의 모듈을 공유하고 싶다면 NPM Regsitry를 이용하면 된다. Publis Registry에 등록하고 사용하는 방법에 대해 알아보자 



1. NPM Registry

  - NPM Registry는 누구나 Node.js에서 사용할 수 있는 모듈을 일정한 양식만 갖춘다면 등록할 수 있고,  

    npm 명령을 통하여 Module Registry 에서 필요한 모듈을 설치할 수 있다

  - 홈페이지 : https://npmjs.org/

  - 홈페이지에서 계정을 미리 생성해 놓자 : 자신이 등록한 모듈 목록을 볼 수 있다

    




2. 배포전 준비하기 

  - .npmignore 파일 : npm 저장소에 배포하지 않을 것들에 대해 열거한다. 해당 파일이 없을 경우 .gitignore를 사용한다 

// .gitignore 내역 

.DS_Store

.git*

node_modules/

  - 모듈이 이미 GitHub에 등록되고 npm init을 통하여 package.json 파일 내역이 정확히 장성되었음을 가정한다 (참조)

// package.json 내역 

// 주의)

// npm init으로 해당 파일 생성시 engines 내역은 포함되지 않는 관계로 사용하는 Node.js 버전을 명시하여 준다 

// name : 값은 대문자와 띄워쓰기 없이 소문자로 이어서 작성한다 

// version : semantic versioning을 사용한다 major.minor.patch

// main : 프로그램 진입점

// test : 테스트 프로그램 수행 스크립트 명령 (npm test)

{

  "name": "mobiconstatistics",

  "version": "0.0.1",

  "description": "include statistics function such as the moving average",

  "main": "./lib/mobiconStatistics.js",

  "engines": { "node": ">= 0.10.0" }, 

  "dependencies": {

    "underscore": "~1.5.1"

  },

  "devDependencies": { 

    "mocha": "latest",

    "should": "~1.2.2"

  },

  "scripts": {

    "test": "mocha test/*.js"

  },

  "repository": {

    "type": "git",

    "url": "https://github.com/ysyun/mobiconStatistics.git"

  },

  "keywords": [

    "mobicon",

    "statistics",

    "movingAverage"

  ],

  "author": "yun youngsik",

  "license": "MIT",

  "bugs": {

    "url": "https://github.com/ysyun/mobiconStatistics/issues"

  }

}




3. 배포하기 

  - https://www.npmjs.org/ 사이트에 가입을 한다.

  - 모듈 디렉토리로 이동하고 사용자를 추가합니다 : npm adduser 

  - 가입했던 username과 password등을 입력하면 된다.

Users/development/node-modules/mobiconStatistics> npm adduser

Username: (ysyun)

Password: 

Email: 

  - 모듈 배포전 npm install이 잘 되는지 테스트 합니다 

    test 폴더를 하나 만들어서 기존에 만든 모듈을 install 해봅니다. test 폴더에 node_modules/mobiconstatistics 모듈을 설치되면 성공!

/Users/development/node-modules/mobiconStatistics> mkdir ../mobiconStatistics-installTest

/Users/development/node-modules/mobiconStatistics> cd ../mobiconStatistics-installTest

/Users/development/node-modules/mobiconStatistics-installTest> npm install ../mobiconStatistics

npm http GET https://registry.npmjs.org/underscore

npm http 304 https://registry.npmjs.org/underscore

mobiconstatistics@0.0.1 node_modules/mobiconstatistics

└── underscore@1.5.1


/Users//development/node-modules/mobiconStatistics-installTest/node_modules/mobiconstatistics> ls

-rw-r--r--  1   staff  1096  8 13 17:09 LICENSE

drwxr-xr-x  3   staff   102  8 13 17:31 test

drwxr-xr-x  3   staff   102  8 13 17:41 lib

-rw-r--r--  1   staff    49  8 14 11:26 .travis.yml

-rw-r--r--  1   staff   904  8 14 11:34 README.md

-rw-r--r--  1   staff  1881  8 14 14:19 package.json

drwxr-xr-x  3   staff   102  8 14 14:19 node_modules

  - 모듈을 배포합니다 : npm publish

Users//development/node-modules/mobiconStatistics> npm publish                          

npm http PUT https://registry.npmjs.org/mobiconstatistics

npm http 201 https://registry.npmjs.org/mobiconstatistics

npm http GET https://registry.npmjs.org/mobiconstatistics

npm http 200 https://registry.npmjs.org/mobiconstatistics

npm http PUT https://registry.npmjs.org/mobiconstatistics/-/mobiconstatistics-0.0.1.tgz/-rev/1-b8e34dc09489f8c4c9ece305d250264a

npm http 201 https://registry.npmjs.org/mobiconstatistics/-/mobiconstatistics-0.0.1.tgz/-rev/1-b8e34dc09489f8c4c9ece305d250264a

npm http PUT https://registry.npmjs.org/mobiconstatistics/0.0.1/-tag/latest

npm http 201 https://registry.npmjs.org/mobiconstatistics/0.0.1/-tag/latest

+ mobiconstatistics@0.0.1

  - 모듈이 잘 배포되었는지 확인합니다 : http://npmjs.org/package/<모듈명>

    예) https://npmjs.org/package/mobiconstatistics

    




4. 모듈 사용하기 

  - 이제 npm registry로 자신의 모듈이 배포되었으므로 프로젝트에 첨부하여 사용할 수 있게 되었습니다. 

// mobiconstatistics 모듈 추가하기 

/Users/development/smart-solutions/SmartStatistics> npm install mobiconstatistics --save

npm http GET https://registry.npmjs.org/mobiconstatistics

npm http 200 https://registry.npmjs.org/mobiconstatistics

mobiconstatistics@0.0.1 node_modules/mobiconstatistics


// 프로젝트의 package.json에 mobiconstatistics 모듈 추가

/Users/development/smart-solutions/SmartStatistics> cat package.json

{

  "name": "smartstatistics",

  "version": "0.0.0",

  "dependencies": {

    "sails": "0.8.9",

    "express": "~3.2.6",

    "ejs": "~0.8.4",

    "underscore": "~1.5.1",

    "mobiconstatistics": "0.0.1"

  },

  - 트위터에서 npm registry에 배포된 모듈중 괜찮은 것들을 정리하여 트윗을 해준다 : @nodenpm

    



<참조>

  - Node.js 소개 in opentutorial

  - npm 이란? 

  - Node Module NPM Registry 등록하기

  - npm developer 가이드

posted by 윤영식
2013. 8. 12. 17:39 Dev Environment/Docker

가상머신 관리도구인 Vagrant를 통하여 Node.js + MongoDB 개발환경을 구축해 본다.



1. 설치 

  - VirtualBox 다운로드 및 설치

  - Vagrant 다운로드 및 설치

  - Vagrant 환경 설정

    + 프로젝트 디렉토리를 하나 만든다. 또는 기존 Project가 있으면 디렉토리로 이동한다. VirtualBox에 원하는 이미지를 다운로드하여 설치한다. 이미지는 Vagrant에서 패키징한 Box를 다운로드할 수 있는 별도 사이트 제공한다 

    + Box는 기본설정과 OS가 설치된 VM 템플릿 이미지이다 

// 형식 : vagrant box add [title] [download-url] 

$ vagrant box add centos64 http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.4-x86_64-v20130427.box

Downloading or copying the box...

    + 프로젝트를 초기화 한다

vagrant init centos64


// 결과 : 환경설정 파일 1개 생성됨 

Vagrantfile 



2. 가상머신 기동

  - Vagrant 통해 가상머신 기동하기 

// 기동

$ vagrant up

Bringing machine 'default' up with 'virtualbox' provider...

[default] Importing base box 'centos64'...

[default] Matching MAC address for NAT networking...

[default] Setting the name of the VM...

[default] Clearing any previously set forwarded ports...

[default] Creating shared folders metadata...

[default] Clearing any previously set network interfaces...

[default] Preparing network interfaces based on configuration...

[default] Forwarding ports...

[default] -- 22 => 2222 (adapter 1)

[default] Booting VM...

[default] Waiting for VM to boot. This can take a few minutes.

[default] VM booted and ready for use!

[default] Mounting shared folders...

[default] -- /vagrant


// VirtualBox VM이 자동으로 수행된 것을 볼 수 있다 

// VM 들어가기 : 같은 디렉토리면 ssh를 n개까지 open 가능 

// ssh를 통하여 별도의 VM 으로 들어갈 수가 있는 것이다. 

// 단, vagrant init [title] 된 Vagrantfile 파일이 같은 디렉토리에 있어야 함

$ vagrant ssh

Welcome to your Vagrant-built virtual machine.

[vagrant@localhost ~]$ 


// 정지

$ vagrant halt



3. Node.js & MongoDB, etc 개발환경 구축하기 

  - 제일 먼저 yum update 수행, 프로젝트 파일과 관계없는 운영 미들웨어 및 데이터베이스 설정이다. 

  - CentOS 64bit Node.js 설치하기 

    + 컴파일해서 설치함

  - CentOS 64bit MongoDB 설치하기 

  - CentOS 64bit Git 설치하기

  - Yeoman work flow 환경도 설치

    + yeoman, grunt, bower 설치 : sudo npm install -g yo grunt-cli bower

    + yeoman generator 설치 : sudo npm install -g generator-webapp

  - Sails.js Framework 기반 개발을 위하여 설치

    + sudo npm install -g sails@0.8.9

    + 버전 지정안하면 최신 버전인 0.9.5 설치됨



4. 애플리케이션 활용하기 

  - 개발환경을 구축하고 자신의 로컬머신에 있는 프로젝트 파일을 VM에도 설치해야 하는가?

    재배포 필요없이 로컬에 있는 파일을 VM에 sync folder 기능을 이용하여 Share 할 수 있다 

  - 프로젝트 파일 공유 : Vagrantfile 내역 (참조)

// 형식 

config.vm.synced_folder "[내 로컬머신의 디렉토리 절대경로]", "[VM에 로딩할 절대경로와 가상디렉토리명 지정]"


// 설정 예

config.vm.synced_folder "/Users/development/smart-solutions/SmartStatistics", "/home/vagrant/SmartStatistics"


// VM reloading 및 결과 

/Users/development/smart-solutions/SmartStatistics> vagrant reload

[default] Attempting graceful shutdown of VM...

[default] Setting the name of the VM...

.. 중략 ..

[default] Mounting shared folders...

[default] -- /vagrant

[default] -- /home/vagrant/SmartStatistics


/Users/development/smart-solutions/SmartStatistics> vagrant ssh

Last login: Mon Aug 12 08:09:35 2013 from 10.0.2.2

Welcome to your Vagrant-built virtual machine.

[vagrant@localhost ~]$ sudo iptables -F

[vagrant@localhost ~]$ cd SmartStatistics/

[vagrant@localhost SmartStatistics]$ pwd

/home/vagrant/SmartStatistics

  - 로컬과 VM의 파일을 서로 Share하였고 서버를 뛰우면 VM에서도 동일 Port로 뜰 것이다. 예) Sails는 default 1337 port를 사용한다 

     VM은 1337 port 를 사용하고 로컬 머신은 1338 port를 사용해서 port forwarding을 한다. 

     즉, 로컬 머신의 브라우져에서 http://localhost:1338 호출하면 VM의 1337 port를 통하여 서비스가 이루어진다(Port Forwarding)

  - 포트 충돌 해결 : Vagrantfile 내역 (참조)

// 형식 

config.vm.network :forwarded_port, guest: [VM에서 사용할 port번호], host: [내 로컬머신에서 사용할 port 번호]


// 설정 예 

config.vm.network :forwarded_port, guest: 1337, host: 1338


// VM reloading 및 결과

/Users/development/smart-solutions/SmartStatistics> vagrant reload

[default] Attempting graceful shutdown of VM...

[default] Setting the name of the VM...

[default] Forwarding ports...

.. 중략 ..

[default] -- 22 => 2222 (adapter 1)

[default] -- 1337 => 1338 (adapter 1)

[default] Booting VM...

[default] Mounting shared folders...

[default] -- /vagrant

[default] -- /home/vagrant/SmartStatistics

 

- vagrant VM

[vagrant@localhost SmartStatistics]$ netstat -na | grep 1337

tcp        0      0 0.0.0.0:1337                0.0.0.0:*                   LISTEN


- local my machine

/Users/development/smart-solutions/SmartStatistics> netstat -na|grep 1338

tcp4       0      0  *.1338                 *.*                    LISTEN

  - 테스트 수행 : port forwarding이 안될 경우 "sudo iptables -F" 를 통하여 강제 재설정을 해준다. 그리고 다시 curl 수행하여 체크 

// curl 이용하여 호출을 했는데 결과값이 나오지 않으면 iptables 에 대한 설정을 해준다 

/Users/development/smart-solutions/SmartStatistics> curl -v http://localhost:1338/

* About to connect() to localhost port 1338 (#0)

*   Trying ::1...

* Connection refused

*   Trying 127.0.0.1...

* connected

* Connected to localhost (127.0.0.1) port 1338 (#0)


// vagrant ssh (port forwarding이 안될 경우)

// 하기 명령을 .bash_profile 에 넣어서 자동화 한다 

/Users/development/smart-solutions/SmartStatistics> vagrant ssh

Last login: Mon Aug 12 08:09:35 2013 from 10.0.2.2

Welcome to your Vagrant-built virtual machine.

[vagrant@localhost ~]$ sudo iptables -F

  - 로컬 머신의 브라우져에서 호출 : "http://localhost:1338"



5. Package 만들기 

  - 기존에 쓰던 VM 이미지를 Vagrant의 Box로 만들어서 개발환경을 미리 패키징할 수 있다  

// 형식 : vagrant package --base <target> --output <output>.box



6. Provisioning 하기

   

  - Vagrant up 수행시 최초 실행되는 매크로 관리 도구인 Chef를 사용한다

  - Chef Solo Provisioning 을 하면 Chef Server가 필요없이 사용할 수 있다 

  - Opscode Cookbooks 에서 원하는 receipe 를 내려받아서 설정해 놓으면 자동 실행된다 



<참조>

  - Vagrant 설치 및 기동

  - SKPlanet의 Vagrant 설치 및 자료

  - Vagrant, Chef 살펴보기 

  - Chef Server 설치하기 튜토리얼

  - KTH의 Chef 블로깅

    


posted by 윤영식
2013. 7. 20. 12:25 NodeJS/Concept

Smart Dashboard라는 장난감을 하나 만들면서 서버단을 Node.js로 코딩을 하고 있다. Node.js에서 가장 햇갈리는 부분 그리고 인식의 전환을 해야하는 것들을 "Art of Node"에서 잘 정리해 주고 있다. Node.js의 핵심은 무엇인지 알아보자.



1. Callback

  - Node.js 는 I/O Platform 이다 : filesystm과 network I/O를 위한 플랫폼이다. Gateway와 같다고 생각해 보자~~ 


  


  - Callback은 node.js에서 나온 개념이 아니라 이미 있던 개념 : C언어에서 "함수포인터"와 같다 

  - Async가 기본이기 때문에 결과에 대한 처리를 요청하는 Callback function을 파라미터로 넘긴다 

  - 다음 예는 undefined가 나온다. Async이기 때문에 addOne() 호출(invoke)하고 바로 console.log(myNumber)으로 이동해 버린다. File I/O보다 Memory I/O가 훨씬 더 빠른다. 

var fs = require('fs') // require is a special function provided by node
var myNumber = undefined // we don't know what the number is yet since it is stored in a file

function addOne() {
  fs.readFile('number.txt', function doneReading(err, fileContents) {
    myNumber = parseInt(fileContents)
    myNumber++
  })
}

addOne()

console.log(myNumber) // logs out undefined -- this line gets run before readFile is done

  - 다음 예가 정확한 예이고 Closure개념을 결합하여 정보를 받아서 출력하고 있다. callback을 addOne 함수의 파라미터로 넘겨서 fs.readFile내부의 doneReading Callback 펑션에서 Closure객체로 callback()을 호출하는 것이다

var fs = require('fs')
var myNumber = undefined

function addOne(callback) {
  fs.readFile('number.txt', function doneReading(err, fileContents) {
    myNumber = parseInt(fileContents)
    myNumber++
    callback()
  })
}

function logMyNumber() {
  console.log(myNumber)
}

addOne(logMyNumber)

  - 이것에 대한 Pseudo Pattern 정리하면 다음과 같다 

function addOne(thenRunThisFunction) {
  waitAMinute(function waitedAMinute() {
    thenRunThisFunction()
  })
}

addOne(function thisGetsRunAfterAddOneFinishes() {})

  - Callback을 하다보면 간혹 이런 가독성이 떨어지는 문구를 볼 수 있다. 이에 대한 해결은 Defer/Promise 를 사용한다. Java 진영에서는 Future라고 한다. 주로 Async 프로그래밍시 Callback을 사용할 때의 문제점을 serial하게 해결할 수 있는 do -> then -> error 처리가 가능함 

a(function() {
  b(function() {
    c()
  })
})



2. Events

  - Node.js는 Even Machine (about ruby on rails or twisted of Python) 이다 

  - Observer Pattern을 사용하고, Publish/Subscribe 개념을 갖는다 

  - on 펑션을 통하여 Subscribe 하고, emit 펑션을 통하여 Publish 한다

  - 하기 예는 Subscribe하여 처리할 Handler를 Callback 펑션 정의(Definition)을 미리한다. 그리고 connect() 펑션에 인자로 넣는다 

var chatClient = require('my-chat-client')

function onConnect() {
  // have the UI show we are connected
}

function onConnectionError(error) {
  // show error to the user
}

function onDisconnect() {
 // tell user that they have been disconnected
}

function onMessage(message) {
 // show the chat room message in the UI
}

chatClient.connect(
  'http://mychatserver.com',
  onConnect,
  onConnectionError,
  onDisconnect,
  onMessage
)

  - 위 예를 다른 형태로 변경하면, 즉, connect() 메소드에서 작용하는 원칙은 다음과 같다. on(<EventName>, <Callback Function>) 를 통하여 Subscribe하는 것과 동일하다 

var chatClient = require('my-chat-client').connect()

chatClient.on('connect', function() {
  // have the UI show we are connected
}) 

chatClient.on('connectionError', function() {
  // show error to the user
})

chatClient.on('disconnect', function() {
  // tell user that they have been disconnected
})

chatClient.on('message', function() {
  // show the chat room message in the UI
})

  


3. Stream

  - Stream I/O 를 통하여 끊김없이 file system, network I/O를 수행할 수 있다. 성능의 병목없이 물흐르듯 데이터가 흘러간다. 

  - Substack사의 Stream Handbook을 보자

  - stream module은 노드의 코어모듈이다 : require('stream')

  - 하기 코드는 data.txt 파일읽는 것이 다 끝나면 Callback의 data로 전달이 된다. 이때 data.txt파일이 크다면 그만틈의 메모리를 사용하게 되고 병목이 생길 수 있다. Async 방식을 고려한다면 성능상의 문제를 야기할 수 있겠다. data.txt파일 읽으면서 http응답 병목발생. 

var http = require('http');
var fs = require('fs');

var server = http.createServer(function (req, res) {
    fs.readFile(__dirname + '/data.txt', function (err, data) {
        res.end(data);
    });
});
server.listen(8000);
  - 위의 예제를 Stream 방식으로 변경한다. 파일을 읽어서 일정 크기단위(Chunk)로 요청한 Client로 응답을 준다.  
var http = require('http');
var fs = require('fs');

var server = http.createServer(function (req, res) {
    var stream = fs.createReadStream(__dirname + '/data.txt');
    stream.pipe(res);
});
server.listen(8000);
  - .pipe() 의미

.pipe() is just a function that takes a readable source stream src and hooks the output to a destination writable stream dst: - 읽을 소스 스트림 src가 있고 쓸 스트림으로 output을 연결해 주는 펑션일 뿐이다

src.pipe(dst)

  - 스트림 체이닝(Chaining)의 표현
a.pipe(b).pipe(c).pipe(d)

위 아래 동일 표현

a.pipe(b);
b.pipe(c);
c.pipe(d);

the command-line to pipe programs

a | b | c | d 

  - 스트림의 5가지 : readable, writable, transform, duplex, classic
  - Readable : pipe의 읽을 소스에 대한 처리를 별도로 해주어 output destination으로 보낸다. readableStream.pipe(dst) 
var Readable = require('stream').Readable;

var rs = new Readable;
rs.push('beep ');
rs.push('boop\n');
rs.push(null);

rs.pipe(process.stdout);
$ node read0.js
beep boop
  - Writable : 소스를 읽고서 표현하는 output에 대하여 별도로 지정한다. src.pipe(writableStream)
var Writable = require('stream').Writable;
var ws = Writable();
ws._write = function (chunk, enc, next) {
    console.dir(chunk);
    next();
};

process.stdin.pipe(ws);
$ (echo beep; sleep 1; echo boop) | node write0.js 
<Buffer 62 65 65 70 0a>
<Buffer 62 6f 6f 70 0a>
  - classic방식은 node v0.4 버전때 처음 나왔다. 
  - duplex는 a.pipe(b).pipe(a) 와 같이 양방향 파이프
  - transform은 read/write filter 이다

 


4. Modules

  - 모듈개념으로 npm(Node Package Manager)를 통하여 필요한 것을 설치한다 : 34,000 개가량의 모듈이 등록되어 있음

  - 모듈 찾기 npm search <ModuleName> : npm search pdf

  - package.json 안에 해당 모듈이 사용하는 다른 모듈 정보와 모듈의 일반 정보(이름, 버전, 저장소등)가 있다 : npm init

  - node코드에서 require('some-module'); 를 찾는다. require 동작 순서

  • if a file called some_module.js exists in the current folder node will load that, otherwise - 현재 디렉토리에 있는지 찾는다 
  • node looks in the current folder for a node_modules folder with a some_module folder in it - 현재 디렉토리에 node_modules 디렉토리가 있는지 찾고 있으면 그 밑으로 some_module 폴더가 있는지 찾는다  
  • if it doesn't find it, it will go up one folder and repeat step 2 - 위 두개다 찾지 못하면 윗쪽 폴더로 가서 두번째 처럼 다시 node_module 폴더를 찾아간다 


  • <참조>

      - art of node 원문

      - 함수 포인터에 대한 이해

      - Closure 알아가기

      - NHN이 이야기하는 함수형 언어와 클로져 이야기

      - Substack사의 Stream Handbook

    posted by 윤영식
    2013. 5. 25. 17:11 Meteor

    미티어는 Full Stack Framework를 지향한다. 미티어는 클라이언트와 서버단의 코드를 통합적으로 지원하며 Node.js와 MongoDB를 기본 스택으로 사용한다. 따라서 단일 언어로 자바스크립트를 사용한다. 미티어 개념을 이해하고 설치 사용해 본다.


    1. 미티어 개념이해 

      - 사용하는 서브 프레임워크들

        + Connect, SocketJS, Handlebars, Stylus, CoffeeScript

        + Node.js, MongoDB

      - 박준태님의 파워포인트

        + 현재 버전 : v0.6.3

        + 윈도우 버전 설치 존재 



    2. 설치하기 

      - 설치 : meteor를 설치하면 자체적으로 node, mongodb 의 binary가 함께 설치된다 (Linux, Mac기준)

    $ curl https://install.meteor.com | /bin/sh


    // 설치된 경로 : ~/.meteor 폴더 밑으로 설치됨

    $ ls 

     meteor -> tools/latest/bin/meteor

     packages

     tools

     releases


    // mongodb binary와 bin 밑에 node binary가 존재하여 별도로 수행한다

    // 기본 포트는 node - 3000 port, mongodb - 3002 port 를 사용한다 

    $ cd tools/latest && ls

    LICENSE.txt examples launch-meteor mongodb tools

    bin include lib share


      - 웹애플리케이션 하나 만들고 수행하기 

    $ meteor --help

    sage: meteor [--version] [--release <release>] [--help] <command> [<args>]


    With no arguments, 'meteor' runs the project in the current

    directory in local development mode. You can run it from the root

    directory of the project or from any subdirectory.


    Use 'meteor create <name>' to create a new Meteor project.


    Commands:

       run             [default] Run this project in local development mode

       create          Create a new project

       update          Upgrade this project to the latest version of Meteor

       add             Add a package to this project

       remove          Remove a package from this project

       list            List available packages

       bundle          Pack this project up into a tarball

       mongo           Connect to the Mongo database for the specified site

       deploy          Deploy this project to Meteor

       logs            Show logs for specified site

       reset           Reset the project state. Erases the local database.

       test-packages   Test one or more packages


    See 'meteor help <command>' for details on a command.


    $ meteor create webapp && cd webapp

    $ meteor run 또는 meteor


    // 수행을 하면 어떤 프로세스가 수행이 될까?

    // node 프로세스는 2개가 수행된다 (3000 port listen)

    $ ps -ef | grep node

      ~/.meteor/tools/11f45b3996/bin/node /Users/nulpulum/.meteor/tools/11f45b3996/tools/meteor.js

      ~/.meteor/tools/11f45b3996/bin/node /Users/nulpulum/prototyping/meteor/webapp/.meteor/local/build/main.js --keepalive


    // mongodb 가 3002 port로 webapp 수행한 위치 밑으로 mongodb file system 저장소를 자동으로 셋팅한다 

    $ ps -ef | grep mongodb

    ~/.meteor/tools/11f45b3996/mongodb/bin/mongod --bind_ip 127.0.0.1 --smallfiles --port 3002 --dbpath /to/path/webapp/.meteor/local/db


      - 브라우져에서 http://localhost:3000/ 을 호출하면 기본 파일인 webapp.html이 수행된다 

      - 미티어가 사용하는 MongoDB쪽을 살펴보자 

    // 미티어가 사용하는 

    $ meteor mongo

    MongoDB shell version: 2.4.3

    connecting to: 127.0.0.1:3002/meteor

    > show dbs

    local 0.03125GB

    meteor (empty)  <--- meteor 저장소가 새롭게 생성되었다 (주의 : 3번의 기존 프로그램 수정하기 해야 나옴)

    > use meteor

    switched to db meteor

    > show collections


      - 애플리케이션을 만들고 다음과 같이 하위 디렉토리를 만들면 자동으로 파일을 인식한다 (참조)

    webapp 디렉토리 밑으로 

      client      – This folder contains any JavaScript which executes only on the client.

      server    – This folder contains any JavaScript which executes only on the server.

      common – This folder contains any JavaScript code which executes on both the client and server.

      lib          – This folder contains any JavaScript files which you want to execute before any other JavaScript files.

      public     – This folder contains static application assets such as images.

      .meteor  - 미티어가 자동으로 만들어주는 폴더 

        local - build - app 밑에 webapp.js 가 존재하여 해당 파일을 사용함 

      



    3. 기존 프로그램 수정해 보기 

      - 해당 동영상을 보고서 수정해 본다 


      - webapp.html 기존코드 삭제 후 수정

    <head>
    <title>webapp</title>
    </head>
     
    <body>
    hi dowon
    > : encoding sign sleeping
    {{> color_list }}
    </body>
     
    <template name="color_list">
    {{#each colors }}
    <ul>
    {{> color_info }}
    </ul>
    {{/each }}
     
    <div class="footer">
    <button>Like!</button>
    </div>
    </template>
     
    <template name="color_info">
    <li class="{{maybe_selected}}">
    {{likes}} people like {{ name }}
    </li>
    </template>


      - webapp.js 기존코드 삭제 후 수정

    // create common store collections to mongodb
    Colors = new Meteor.Collection("colors");
     
    if (Meteor.isClient) {
    // UX Coding
    Template.color_list.colors = function() {
    // likes: -1 = descending
    // name: 1 = ascending
    // return Colors.find({},{sort:{likes:-1, name:1}});
    return Colors.find({},{sort:{name:1}});
    }
     
    Template.color_info.maybe_selected = function() {
    console.log('---> 2: ' + this._id);
    return Session.equals('session_color', this._id) ? "selected" : '';
    }
     
    // UX event
    Template.color_info.events = {
    'click' : function() {
    console.log('---> 1: ' + this._id);
    Session.set('session_color', this._id);
    }
    }
     
    Template.color_list.events = {
    'click button' : function() {
    // 정보를 업데이트하면 동시에 열려 있는 브라우져 정보가 업데이트 된다
    Colors.update(Session.get('session_color'), {$inc: {likes:1}})
    }
    }
    }
     
    if (Meteor.isServer) {
    // 서버를 시작한다
    Meteor.startup(function () {
    // code to run on server at startup
    });
    }


      - webapp.css 수정

    .selected {
    background-color: yellow;

    }


     - 테스트

        + Like! 버튼을 누르면 양쪽의 브라우져로 숫자 증가값이 Multicasting 된다. 

        + 크롬의 Dev Tool에서 직접 insert 하여 테스팅. ex) Colors.insert({ likes : 1, name : 'blue' })



    4. 클라우드에 배포하여 테스트하기

      - 클라우드에 배포하기 : 스마트 패키징

    // xxx.meteor.com 포멧으로 자신만의 xxx를 지정한다

    $ meteor deploy dowon-color.meteor.com

    Deploying to dowon-color.meteor.com.  Bundling...

    Uploading...

    Now serving at dowon-color.meteor.com


      - 브라우져에서 dowon-color.meteor.com을 호출한다 

        + 크롬 DevTools에서 MongoDB  API와 똑같이 insert를 한다. (클라이언트에서 서버의 MongoDB로 저장!!!)

        



    <참조>

      - 미티어 한글 번역 : http://docs-ko.meteor.com/

      - 미티어 디렉토리 구분하여 프로젝트 만들기

      - 채팅프로그램 만들기

      - Introduction to Meteor (필독)

      - 미티어는 이미 백본에 대해서 패키지로 통합을 하였다. AngularJS에 대한 통합은 현재 투표중이다.

         + meteor와 angularjs에 대한 통합 시도 소스

         + 미티어 로드맵에서 Wishlist에서 볼 수 있다. (Trello에서 로드맵을 추천받고 있습니다. 저도 한표. 현재 총 52표!)

         

    posted by 윤영식
    2013. 5. 7. 09:37 NodeJS/Concept

    서비스 또는 솔루션을 만들기 위해 Node.js와 Express.js를 선택하였다면  그 다음 고민은 애플리케이션 개발을 위하여 필요한 스택을 선정하는 일이다. 크게는 로깅, 환경설정, 통신등 기본적인 부분들을 직접 개발하지 말고 이미 만들어진 바퀴를 공짜로 구해서 달아보자 



    1. 준비하기 

      - Programming Javascript Application Book 책의 내용을 발췌한 것이다 

      - Node 버전관리 NVM 설치 

        + https://github.com/creationix/nvm

        + 설치 : nvm install [버전]

        + 수행 : nvm run [버전]

        + 확인 : nvm ls  (설치된 버전 목록을 보여줌)

        + 가능 : nvm ls-remote (설치 가능 버전 목록을 보여줌)

      - nvm 설치 명령 : git 사전 설치요구 : 수행시 nvm.sh 에 syntax오류가 나오면 nvm.sh을 copy하고 기존것 삭제후 다시 paste하여 만듦

    $ curl https://raw.github.com/creationix/nvm/master/install.sh | sh


        + 기타 명령들 

    $ nvm help


    Node Version Manager


    Usage:

        nvm help                          Show this message

        nvm install [-s] <version>  Download and install a <version>

        nvm uninstall <version>     Uninstall a version

        nvm use <version>           Modify PATH to use <version>

        nvm run <version> [<args>]  Run <version> with <args> as arguments

        nvm ls                            List installed versions

        nvm ls <version>             List versions matching a given description

        nvm ls-remote                 List remote versions available for install

        nvm deactivate                Undo effects of NVM on current shell

        nvm alias [<pattern>]      Show all aliases beginning with <pattern>

        nvm alias <name> <version>    Set an alias named <name> pointing to <version>

        nvm unalias <name>                Deletes the alias named <name>

        nvm copy-packages <version> Install global NPM packages contained in <version> to current version


    Example:

        nvm install v0.4.12            Install a specific version number

        nvm use 0.2                    Use the latest available 0.2.x release

        nvm run 0.4.12 myApp.js   Run myApp.js using node v0.4.12

        nvm alias default 0.4        Auto use the latest installed v0.4.x version


      - 프로젝트 만들고 초기화 하기 

        + 디렉토리 만들고 해당 디렉토리로 이동

        + npm init 수행하여 package.json 파일 만들기 : 이름, 버전, 설명, 키워드, 저장소위치, 라이센스 및 기타 정보등을 입력한다 

    $ mkdir basic && cd basic

    $ npm init

    This utility will walk you through creating a package.json file.

    It only covers the most common items, and tries to guess sane defaults.


    See `npm help json` for definitive documentation on these fields

    and exactly what they do.


    Use `npm install <pkg> --save` afterwards to install a package and

    save it as a dependency in the package.json file.


    Press ^C at any time to quit.

    name: (basic) JavaScript-Programming

    version: (0.0.0) 0.0.1

    description: This is a javascript programming

    entry point: (index.js)

    test command: grunt test

    git repository: https:/github.com/ysyun/javascriptacular

    keywords: angularjs

    author: yun dowon

    license: (BSD) MIT

    About to write to /Users/prototyping/express/basic/package.json:


    {

      "name": "JavaScript-Programming",

      "version": "0.0.1",

      "description": "This is a javascript programming ",

      "main": "index.js",

      "scripts": {

        "test": "grunt test"

      },

      "repository": {

        "type": "git",

        "url": "https:/github.com/ysyun/javascriptacular"

      },

      "keywords": [

        "angularjs"

      ],

      "author": "yun dowon",

      "license": "MIT"

    }



    Is this ok? (yes)

    npm WARN package.json JavaScript-Programming@0.0.1 No README.md file found!

    $ ls -lart

    -rw-r--r--  1 nulpulum  staff  360  5  7 09:22 package.json

    drwxr-xr-x  3 nulpulum  staff  102  5  7 09:22 .


      - 모듈 설치시에 package.json 파일에 자동 기록을 위하여 npm install --save [module] 처럼 --save 옵션을 준다 



    2. 준비할 스택들

    • Mout Like Underscore / LoDash. Stuff that should probably be included in JavaScript.

    • Express Web application framework.

    • Nconf Application config.

    • Hogan Mustache for express. (Jade 또는 EJS 를 써도 됨)

    • Superagent Communicate with APIs.

    • Socket.io Realtime communications (websockets).

    • Q Promises.

    • Async Asynchronous functional utilities.

    • Bunyan Logging. (Winston도 많이 사용)

    • Tape Testing. (Mocha, Karma 도 사용)

    • Cuid Better than guid/uuid for web applications.

    • Derby.js Add realtime, collaborative MVC to express. (Sails.js 또는 Bone.js 도 사용)

    • Node-http-proxy Proxy your service APIs.



    3. Express 사용하기 

      - Routing 을 이용한다

      - Middleware를 통하여 중간에 Hooking 하여 처리하고 다음으로 next() 호출 넘기는 use 를 사용한다 

    // Add some data to the request object that your other
    // midleware and routes can use.
    app.use(function (req, res, next) {
      req.foo = 'bar';
      next();

    });

        + 전체 예제 

    'use strict';
    var express = require('express'),
     
      // Create app instance.
      app = express(),
     
      // Use the `PORT` environment variable, or port 44444
      port = process.env.PORT || 44444;
     
    // The new middleware adds the property `foo` to the request
    // object and sets it to 'bar'.
    app.use(function (req, res, next) {
      req.foo = 'bar';
      next();
    });
     
    app.get('/', function (req, res) {
      res.setHeader('Content-Type', 'text/plain');
     
      // Send the value passed from the middleware, above.
      res.end(req.foo);
    });
     
    app.listen(port, function () {
      console.log('Listening on port ' + port);

    });


    // 결과 : use를 설정한 순서대로 처리된다 

    $ curl http://localhost:44444/
    bar



    4. SailsJS

      - 난 sails를 Full Stack Server Side Data-Oriented API Framework 이라고 말하고 싶다.

      - Front-end 단이 SPA로 개발될 경우

      - Back-end를 API 서버로 확장함 

      - Node.js + Express.js + MongoDB 와 연결 및 JSON Memory DB 사용가능

        + sails-angularjs-yeoman 의 로그인 데모

        + sails-mongodb 연결

        + sails-redis 연결



    <참조>

       - 원문 : Getting start with Node and Express 

       - middleware의 next() 사용법 : Node.js의 connect 이해하기

       - AngularJS와 MongoDB 연결 Bridge


    posted by 윤영식
    2013. 3. 14. 11:48 NodeJS/Concept

    그동안 혼자 고민하여 찾아 해메였던 많은 부분이 채수원씨의 발표자료안에 잘 정리되어 있고, 결과물을 만들며 얻은 팁을 공유하고 있다. 발표 자료를 통해 느낀점을 적어본다 (채수원씨는 2011년 KOSTA에서 MongoDB 수강하며 알게 됨. 강사였음 ^^)



    1) Devnote 사용하는 모듈들 보며 느낀점 

      - 미경험 모듈 : zombie.js, node-i18n, jake, winston, gitfs 등

      - 경험 모듈 : 대부분 노드에서 가장 많이들 사용하는 모듈로 결정

      - 테스트 모듈 : step 코드의 간결함이 좋음

      - 개발툴로 CodeMirror 사용해 봐야겠다. 현재는 Sublime Text 2 를 사용중 임

      - 서버쪽 템플릿은 Jade, SPA(Single Page Application) 개발로 Backbone.js를 사용한다면 Underscore.js를 사용하자 

      



    2) Devnote 아키텍쳐보며 느낀점

      - 스토리지를 gitfs로 했는데 해당 부분을 mongodb로 바꾸고 싶은 욕구가 생김

      - 테스트 자동화에서 브라우저 애뮬레이션으로 CasperJS를 추가하고 싶음  

      - view 영역에서 Backbone.js 가 들어갔으면 함

      - 전부 자바스크립트로 개발 및 테스트 환경을 꾸몄다. 물론 소스도 자바스크립트이다. 

        이제 JavaScript 도 Java처럼 개발과 테스트 환경 생태계가 엄청나게 빨리 성숙해 지고 있다. 이젠 대세~~~ 정대세 ^^;

      - CoffeeScript를 본격적으로 사용하자. 기본 문법수준에서 알고 조금씩 했지만 이젠 가급적 커피로 가자고 결심함 ^^;

        코드의 가독성과 컴파일된 .js 파일의 자체 모듈패턴 적용이 좋음 

      - i18n 을 고려해야 겠다고 생각함 

      - git 스타일의 파일 관리면 버전관리가 가능하겠구나 생각함. 위키니깐~~

      - socket-io 를 통하여 real-time 피드백을 받는다는 개념이 좋음. 역시 피드백은 바로 받아 바로 처리해야~~

      - Markdown 은 배우기도 쉽다. 그리고 소스 문서화 툴 Groc 에서도 사용하니 필히 익혀서 쓰자 

      - 우리 솔루션에도 Twitter Bootstrap CSS 스타일을 따라야 겠다고 생각함. 눈에 익숙한 것이 거부감을 줄여줄 것이라 생각함



    3) 발표자료

      - 시월의 맑은 하루 (OctoberSky.js) 페북모임에 관심이 감 

      - source map 설정을 통해서 자바스크립트 디버깅 가독성있게 하기를 테스트 해봐야 겠음 

      - 자바스크립트 개발자라면 Front-end <-> Back-end 개발 다 하자. 

        페이스북이 그렇게 개발한다. 그리고 테스트팀도 없다. 개발자들이 기본 TDD를 한다 

      



    <참조>

      - 채수원씨 발표자료

      - DevNote : https://github.com/nforge/devnote

      - CodeMirror : https://github.com/marijnh/CodeMirror

      - Markdown 포멧 설명문법설명, GitHub GFM

    posted by 윤영식
    2013. 3. 12. 17:41 카테고리 없음

    Sublime Text 2에서 Node.js 수행을 위한 Path 설정하기 


    1) 설정 

      - Ctrl + Shift + P => "Nodejs::Default File Settings" 검색하여 환경파일 오픈 (Node.js.sublime-settings)

    // 기본 설정 내역

    {

      // save before running commands

      "save_first": true,

      // if present, use this command instead of plain "node"

      // e.g. "/usr/bin/node" or "C:\bin\node.exe"

      "node_command": false,

      // Same for NPM command

      "npm_command": false,


      "expert_mode": false,


      "ouput_to_new_tab": false

    }


    // 변경 설정 내역 

    {

      // save before running commands

      "save_first": true,

      // if present, use this command instead of plain "node"

      // e.g. "/usr/bin/node" or "C:\bin\node.exe"

      "node_command": "/usr/local/bin/node",

      // Same for NPM command

      "npm_command": false,


      "expert_mode": false,


      "ouput_to_new_tab": false

    }


    2) 실행

      - Ctrl + R 



    <참조> 

      - 원문 http://stackoverflow.com/questions/12124544/can-not-run-node-app-with-nodejs-sublime-plugin

    posted by 윤영식
    2013. 3. 11. 11:51 NodeJS/Concept

    Node.js 에서 가장 많이 사용하는 Express 프레임워크를 다시 둘러보면서 Connect를 살펴보았다. 데이터 스트림속에서 특정 객체를 가르키는 기술을 쿼리라고 본다. 예로 jQuery는 DOM내에서 특정 노드 객체를 쉽게 찾을 수 있고, Backbone은 el를 통하여 객체를 Fragment화해서 특정 위치에 Render해 준다



    1. 자바스크립트 쿼리의 시대

      - 제일 쿼리 : Underscore.js 

        + 클라이언트 사이드의 Backbone.js의 핵심이 된다


      - 제이 쿼리 : jQuery 

        + 두말이 필요없다. 브라우져 호환성과 셀렉터 그리고 다양한 위젯과 스마트 디바이스 지원까지 


      - 제삼 쿼리 : Backbone.js 

        + Client MV* Framework

        + 클라이언트 SPA (Single Page Web Application) 개발시 기본이 되는 프레임워크 


      - 삼.오 쿼리 : Node.js 

        + 서버 사이드 엔진 (Async I/O, One Thread)

        + Modern Web App 개발


      - 제사 쿼리 : Express & Connect 

        + Server MV* Framework

        + Node.js가 Java의 JVM 이라면 Connect 는 Java의 WAS(Web Application Server) or Apache 기능을 갖춘 middleware 이다

        + Express는 Connect 모듈을 통해 RESTful Web Services를 쉽게 만들게 해주는 Java의 Spring Framework 이다 


      - 제오 쿼리 : MongoDB

        + NoSQL

        + 최신버전의 REPL은 V8 engine (Node.js와 동일한 구글의 JavaScript 엔진 사용예정)

        + HA를 위한 쉬운 Replica Set 지원

        + Scale-out을 위한 Replica Set단위의 Sharding 지원



    2. Connect 살펴보기

      - Node.js에서 모자란 부분을 확장하여 미들웨어 개념으로 제공을 한다.

        미들웨어는 단지 펑션일 뿐이고 사용자가 얼마든지 확장하여 사용할 수 있다.

        Connect가 HTTP 서비스에 대하여 많은 부분 추상화를 하였고, Express가 Connect를 또 Wrapping 하고 있다

      - 출처 :  http://www.senchalabs.org/connect/

      - logger

        + 아파치의 format string을 생각하면 된다 

        + 아파치처럼 %d 와 같은 옵션이 아니라 :date 같은 옵션이다

        + 아파치처럼 다양한 옵션을 제공하진 않는다. %b 와 같이 전송 바이트수 옵션이 없다


      - csrf

        + Cross-site request 로 인한 위조 방지 

        + _csrf 필드가 hidden으로 추가됨 (req.session._csrf, req.body._csrf, req.query._csrf, req.headers['x-csrf-token'])


      - compress

        + gzip 압축지원


      - basicAuth

        + callback(user, pass) 를 지원하여 true를 리턴하면 접근 허용


      - bodyParser

        + request body 파싱 및 appplication/json, application/x-www-form-urlencoded, multipart/form-data 파싱 

        + Express 에서 사용 : express.bodyParser()


      - json

        + JSON 요청 파싱하여 req.body에 파싱한 오브젝트 제공


      - urlencoded

        + x-www-form-urlencoded 요청 파싱하여 req.body에 파싱한 오브젝트 제공


      - multipart

        + multipart/form-data 요청 파싱하여 req.bodyd와 req.files 에 파싱한 오브젝트 제공

        + connect.multipart({uploadDir: path}) 경로를 설정한다 


      - timeout

        + 요청에 대한 timeout (ms 단위), 기본 5000 (ms, 즉 5초)

        + err.status=408 이면 next()를 통하여 timeout 응답페이지 처리함 


      - cookieParser

        + 헤더 쿠키를 파싱하여 req.cookies 객체 제공

        +  req.secret  스트링 전달 가능 


      - session

        + session() 사용하려면 cookieParser()를 반드시 사용해야 한다 (세션아이디 sid 저장)

        + req.session 으로 접근 

        + SessionStore는 MemoryStore 이고 별도 구현하려면 규정된 몇가지 메소드를 구현하면 됨 

           세션 클러스터링에 대한 구현이 필요할 수 있겠다 


      - cookieSession

        + 쿠키 사용 


      - methodOverride

        + req.originalMethod 로 form의  method(GET, POST 같은)가 넘어가는 것을 _method로 override

     

      - responseTime

        + X-Response-Time 헤더 표시 (milliseconds)


      - staticCache

        + 정적 파일 서비스를 위한 메모리 캐싱. 기본 128 개의 오브젝트 캐싱

        + 각 256k 사이즈로 32mb 까지 캐싱가능 

        + Least-Recently-Used (LRU) 알고리즘 사용 : 사용빈도 높은 것이 캐싱

        + respondFormCache() 통하여 캐싱 응답 status=304


      - static 

        + 루트 경로 지정

        + express.static(__dirname + '/public') 형태로 사용함 


      - directory

        + 주어진 루트 경로의 목록

        + . (dot) 파일은 숨김


      - vhost

        + VirtualHost통한 sub domain 설정


      - favicon

        + 설정하지 않으면 /public/favicon.ico 찾음 


      - limit 

        + request body 크기 제한  

        + 5mb, 200kb, 1gb 등으로 설정


      - query

        + query-string을 자동으로 파싱

        + req.query 오브젝트 생성


      - errorHandler

        + error 처리 핸들러에게 stack traces 제공

        + text or json형태로 에러 메세지 응답 

        + /public/error.html 참조



    3. 테스트하기 

      - GitHub connect Test Doc

      - GitHub connect Test Source

      - 테스트는 mocha에 should를 적용하여 BDD 수행한다. (참조)

    $ git clone https://github.com/senchalabs/connect.git

    Cloning into 'connect'...

    remote: Counting objects: 15253, done.

    remote: Compressing objects: 100% (4656/4656), done.

    remote: Total 15253 (delta 10035), reused 14692 (delta 9538)

    Receiving objects: 100% (15253/15253), 3.10


    $ cd connect 

    $ npm install .

    $ mocha --reporter list 

    ... 중략 ...

        at Server.app (/Users/nulpulum/git-repositories/connect/lib/connect.js:65:37)

        at Server.EventEmitter.emit (events.js:99:17)

        at HTTPParser.parser.onIncoming (http.js:1928:12)

        at HTTPParser.parserOnHeadersComplete [as onHeadersComplete] (http.js:111:23)

        at Socket.socket.ondata (http.js:1825:22)

        at TCP.onread (net.js:404:27)

      ․ connect.urlencoded() should accept a limit option: 2ms

      ․ connect.urlencoded() should support all http methods: 1ms

      ․ connect.urlencoded() should parse x-www-form-urlencoded: 1ms

      ․ utils.uid(len) should generate a uid of the given length: 1ms

      ․ utils.parseCacheControl(str) should parse Cache-Control: 0ms

      ․ utils.mime(req) should return the mime-type from Content-Type: 0ms

      ․ connect.vhost() should route by Host: 2ms

      ․ connect.vhost() should support http.Servers: 1ms

      ․ connect.vhost() should support wildcards: 1ms

      ․ connect.vhost() should 404 unless matched: 2ms

      ․ connect.vhost() should treat dot as a dot: 1ms


      192 tests complete (2 seconds)


      - mocha 수행하면 열라 에러 많이 뜬다 ㅠ.ㅠ; 멘붕온다. 해당 메세지가 정상적인 것인지 아닌지 살펴보자

    // mocha.opts 내역
    --require should
    --require test/support/http

     --growl


      - connect의 Code Coverage 보기 



    <참고>

      - Express 가이드 한글화 (필독)

      - Connect의 다양한 Samples in GitHub

      - 지난주 부터 수강하고 있는 KOSTA 이복영강사님 강의에서는 제사쿼리를 MongoDB 라고 지칭하였고, 제* 쿼리를 도용하여 내 나름으로 Express & Connect를 하나 더 넣었다

      - Connect 개념 설명 및 간단 예제

      - JavaScript Code Coverage 프레임워크


    posted by 윤영식
    prev 1 2 next