블로그 이미지
Peter Note
Web & LLM FullStacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2013. 10. 20. 21:12 Testing, TDD/Test First

AngularJS 기반하에 TDD를 수행하거나 테스트 코드를 작성할 때 적합한 툴과 개념에 대해 이해를 하자



1. Unit Testing

  - 단위 기능 테스트 

  - 추천 단위 테스트 툴

    + Mocha : 단순하며 유연한 테스트 프레임워크 (추천)

    + Jasmine : Behavior-Driven Development Framework for testing Javascript (추천)

    + QUnit : jQuery에서 사용

  - 단위 테스트 만들기 

    + 사용자 스토리 기반으로 만들어 보자 

       "사용자로써 나의 앱에 로그인을 하면 대시보드 페이지를 본다"

    + Story -> Features -> Units 로 구분을 한다 

       "로그인 폼"을 하나의 기능으로 선택한다

       하나의 스토리는 여러 기능의 합집합이며, 각 기능의 정상작동 검증(Verification)을 거쳐서 하나의 스토리는 완성된다 

    + 로그인 폼에 대한 Spec을 만든다 (BDD)

describe('user login form', function() {


    // critical : 기능의 검증

    it('ensure invalid email addresses are caught', function() {});

    it('ensure valid email addresses pass validation', function() {});

    it('ensure submitting form changes path', function() { });


    // nice-to-haves

    it('ensure client-side helper shown for empty fields', function() { });

    it('ensure hitting enter on password field submits form', function() { });


});

   

  - 위의 테스트 Spec을 AngularJS로 단위 테스트 만들기 

    + 파일 : angular.js, angular-mocks.js, app.js(모듈명-App), loginController.js(LoginCtrl),  loginService.js(LoginService)

    + beforeEach에서 angular.mock.module 통해 모듈을 셋업한다 

       beforeEach에서 angular.mock.inject 통해 controller에서 $scope 셋업한다 (참조) 

    + it 에서 inject 통하여 service를 셋업한다 (참조)

describe("Unit Testing Examples", function() {


  beforeEach(angular.mock.module('App')); 


  it('should have a LoginCtrl controller', function() {

    expect(App.LoginCtrl).toBeDefined();

  });


  it('should have a working LoginService service', inject(['LoginService',

    function(LoginService) {

      expect(LoginService.isValidEmail).not.to.equal(null);


      // test cases - testing for success

      // 검증을 위한 테스트 데이터 

      var validEmails = [

        'test@test.com',

        'test@test.co.uk',

        'test734ltylytkliytkryety9ef@jb-fe.com'

      ];


      // test cases - testing for failure

      var invalidEmails = [

        'test@testcom',

        'test@ test.co.uk',

        'ghgf@fe.com.co.',

        'tes@t@test.com',

        ''

      ];


      // you can loop through arrays of test cases like this

      for (var i in validEmails) {

        var valid = LoginService.isValidEmail(validEmails[i]);

        expect(valid).toBeTruthy();

      }

      for (var i in invalidEmails) {

        var valid = LoginService.isValidEmail(invalidEmails[i]);

        expect(valid).toBeFalsy();

      }


    }]) // end inject 

  ); // end it 

}); // end describe



2. Integration Testing 

  - 통합 테스트는 end-to-end 테스트(E2E)이고, 수행순서대로 차례로 수행해 보는 것이다. 

     그리고 애플리케이션과 UI의 상태를 검증한다. 심지어 Visual적인 부분에 대한 검증도 가능하다 

  - 추천 도구 

    + Karma : AngularJS에서 공식사용 (추천)

    + CasperJS : headless browser로 사용 

    + PhantomJS : headles brower

  - 통합 테스트 만들기 

    + "사용자로써 나의 앱에 로그인하여 대시보드 페이지를 본다"

describe('user login', function() {


  // 수행 및 결과 상태의 검증

  it('ensures user can log in', function() {

    // expect current scope to contain username

  });

  it('ensures path has changed', function() {

    // expect path to equal '/dashboard'

  });


});


  - 위의 테스트 Spec을 AngularJS로 통합 테스트 만들기 (참조)

    + 최근에는 Angular Scenario Test Runner와 같은 Protractor E2E test framework이 새롭게 소개되고 있다 

describe("Integration/E2E Testing", function() {


  // start at root before every test is run

  beforeEach(function() {

    browser().navigateTo('/');

  });


  // test default route

  it('should jump to the /home path when / is accessed', function() {

   // 루트 패스로 이동 - 로그인 화면 

    browser().navigateTo('#/');

    expect(browser().location().path()).toBe("/login");

  });


  it('ensures user can log in', function() {

    // 로그인 화면인지 체크

    browser().navigateTo('#/login');

    expect(browser().location().path()).toBe("/login");


    // assuming inputs have ng-model specified, and this conbination will successfully login

    // 테스트 데이터를 통해 브라우져 로그인 submit 버튼 click 이벤트 발생시킴 

    input('email').enter('test@test.com');

    input('password').enter('password');

    element('submit').click();


    // logged in route

    // 로그인이 성공했을 경우 대시보드 패스에 있는지 검증

    expect(browser().location().path()).toBe("/dashboard");


    // my dashboard page has a label for the email address of the logged in user

    // DIV의 id="email"의 html 값이 로그인시 입력한 값인지 검증 

    expect(element('#email').html()).toContain('test@test.com');

  });


  it('should keep invalid logins on this page', function() {

    // 로그인 화면 인지 검증

    browser().navigateTo('#/login');

    expect(browser().location().path()).toBe("/login");


    // assuming inputs have ng-model specified, and this conbination will successfully login

    // 잘 못된 값을 넣었을 가지고 로그인 버튼 클릭 

    input('email').enter('invalid@test.com');

    input('password').enter('wrong password');

    element('submit').click();


    // DIV id="message"에 failed메세지 있는지 검증 

    expect(element('#message').html().toLowerCase()).toContain('failed');


    // logged out route

    // 제대로 로그인을 못했으므로 로그인 패스 그대로인지 검증 

    expect(browser().location().path()).toBe("/login");

  }); // end it 

}); // end describe 



3. Mocking Services and Modules in AngularJS

  - 목(Mock) 서비스를 이용한 테스트 방법 

  - 목 오브젝트를 사용하는 이유는 외부적인 요소 의존적인 부분을 제거하고 테스트 하기 위해서 이다 

  - ngMock.$httpBackend : service에서 http login 호출을 하면 응답 JSON 객체를 넘겨주는 mock 객체를 만든다 

it('should get login success',

  inject(function(LoginService, $httpBackend) {


    $httpBackend.expect('POST', 'https://api.mydomain.com/login')

      .respond(200, "[{ success : 'true', id : 123 }]");


    LoginService.login('test@test.com', 'password')

      .then(function(data) {

        expect(data.success).toBeTruthy();

    });


  $httpBackend.flush();

});



<참조>

  - 원문 : Best Practices in AngularJS

  - AngularJS Controller Unit Test 방법

  - AngularJS Service Unit Test 방법

  - Full Spectrum Testing(E2E) in Angular with Karma (필독)

  - Introducing E2E Testing in Angular 동영상

    

posted by Peter Note
2013. 10. 10. 11:19 MongoDB/Concept

MongoDB가 나오게 된 이유와 개념/구성에 대하여 알아보자



어느 방향을 선택할 것인가

  - 500만 : trend

  - 1000만 : culture

  - 2000만 : 안끼면 소외된다 

  소셜네트워크가 IT와 만나고 다시 스마트폰을 만나면서 새로운 방향을 제시하였다. 이런 방향을 지원하기 위하여 많은 OSS(Open Source Software)가 나왔고, HTTP 통신(Stateless)을 통하여 유입되는 어마어마한 사용자를 수용하기(규모의 확장) 위하여 사람들의 생각을 저장하고 활용해야 하는 필요성이 커졌다. 사람의 생각을 Key=Value로 많은 데이터를(BigData) 저장할 수 있어야 한다. 이러한 사상은 예전의 Client-Server 사상이 아닌 HTTP Web상의 통합과 유입을 처리하는 SNS(Social Network Service), SND(Social Network Data), SNG(Social Network Game) 사상으로 가고 있다. SNS는 HTTP 프로토콜을 통하여 Big Data Stream을 만들어 Real Time으로 반응하는 것이다. 



기술의 흐름에 따른 선택

1) 전통적 웹기술

  - 전통 pc application 개발 기술

  - static 기술

  - dynamic 기술


3) 모바일 기술

  - embedded 기술 (고립된 기술) : iphone, android 등 mobile application 개발 기술 또는 Sencha 기술 

  - open api 기술 : web services 기술 


4) 웹앱(WebApp) 기술 : 3세대 웹

  - SPA(스타) : single page application 기술 (javascript, json)

  - MVC framework을 client 사이트로 내림 = Fluent MVC = Stream

    (스트림은 융합을 쉽게 이루어지게 한다. 예) 디지털TV, 무인자동차) 

    + client Controller는 없다. View가 Controller를 대신한다 

    + MOV : Model + Operation + View가 있다. 여기서 Operation = Functional 이 된다. (Functional Language, Async) 

    + DRY : Don't Repeatly Yourself 반복하지 않는 프레임워크    

    



  - SNS : Modern Web = Smart Device (TV, Phone등), 반대개념 일반 Web 임

  - Real Time 기술 : websocket 기술이 들어감 

  - Javascript가 소통 언어가 되고 JSON이 데이터 포멧이 되어 end-to-end에서 커뮤니케이션의 실체가 된다. 

    + 기존 ORM-Object Relational Mapping- 기술은 사용하지 말자~~

    + client : 메모리, 브라우져 사용

    + server : 모듈 서비스. 일반 모듈의 대표는 Node.js 이고, special module = DB module    

  - Stream의 통합 : Edge 통합

    + Facebook이 초창기 사용하였으나 지금은 웹앱, mongodb를 사용하지 않는다 (초기 기술 적용에 대한 실패일까?)


  - 최종 모습

    + client : Backbone, Handlebar(mustache)

    + server : Node.js, Redis(맵데이터=view테이블), MongoDB(MapReducing, NoSQL)

      MapReduce를 통하면 BI(Business Intelegence)를 구현한다

    + bigdata : 큰단위 - Hadoop + HBase,  작은단위 - Redis + MongoDB로 보아도 된다

    + Agile 개발 : Schemaless 로 에자일 개발 프로세스에 적합하다. 작은 변화에도 기만하게 반응할 수 있다

     


MongoDB 이해하기 

  - 기존 RDB

    + join = projection 

    + 성능 optimization(최적화)를 위하여 schema와 join이 중요함 : 정규화의 필요(normalization)   

    

 

  - 현재 상황 

    + 성능 최적화를 위하여 찢어 놓았던(normalization)것을 고객 서비스를 위하여 다시 합쳐야 한다

    + SQL문이 8Kbytes 가 넘어 가고 현재 무한히 SQL을 쓸 수 있지만 늪이 된다. (튜닝을 위하여 컨설턴트가 필요해짐)

   - MongoDB

    + Table = Collection

    + Tuple = Document    

    

    


MongoDB 구성

  - Replica Set을 기본으로 가져가자 : HTTP를 통하여 묶는다 (Primary-Secondary)

  - Fail-Over를 통하여 High Availability(HA)를 충족한다 

    

  

  - Scale-Out 확장을 통하여 무한히 대응할 수 있다

    + Sharding을 통하여 데이터를 분산하여 처리하고 그안에 Replica-Set이 구성된다 

    + mongos는 router 이다     

    


  - 전체 MongoDB 구조 

    + Config Server : Replica-set, Shard 구성

    + Replica-set : Fail-Over (primary, secondary)

    + Sharding : Data Scale-out 

    

  


SNS는 이제 RealTime Stream Data 시대

  - 하나로 보이지만 여러곳에서 데이터가 통합되어 온다. 

    하기 그림에서 영상 화면 하나가 여러 곳에서 데이터가 모여져서 보여지듯이.

  - mongoDB의 GridFS가 된다 

  

   


MongoDB 개념 및 용어 정리 


MongoDB는 Service로 접근해야 한다! (애플리케이션 접근 옳지 않아~~)



<참조>

  - .Net Framework Frontier Facebook Group

  - KTH  MongoDB 어떻게 사용할 것인가?

posted by Peter Note
2013. 10. 8. 09:06 Lean Agile Culture/Lean Startup

장강일님 페이스북 글 발췌 


"더 나은 의사결정을 내리는 7단계"

1. Set a deadline.

의사결정의 반대는 지지부진 의사결정을 미루는 것이다. 우유부단함(indecision)은 다른 사람들이 당신을 대신해 의사결정내릴 때까지 망설이게 만든다. 그러므로, 중요한 의사결정은 마감일(deadline)이 있어야 한다. 당신이 "자율적으로(will)"으로 결정을 할 수 있는 최종기한을 뜻한다.

2. Discover the knowns.

최선의 판단을 하기 위해선, 의사결정에 필요한 정보들을 획득해야한다. 그런데, 정보를 취합할 때, 수확체감(diminishing returns)의 법칙이 적용된다는 것을 상기하라. 너무 세세한 디테일들에 과도하게 사로잡히기 시작할 때, 정보 수집을 멈추는 것이 좋다.

3. Gather relevant inputs.

의사 결정 이후에, 다른 사람들이 그 결정된 내용을 실행해야 한다면, "반드시" 그 사람들의 관점을 의사결정 내리기 전에 반영하라. 이 과정을 빠뜨리게 되면, 그 사람들은 당신이 내린 의사결정을 자신들의 것으로 수용(own)하지 않을 것이다. 당연히 매끄럽게 실행되기도 힘들다.

4. Decide.

결정하다(decide)의 어원은 라틴 말로 절단(cut off)에서 나왔다. 결정은 논쟁을 중단시키는 것이며, 다른 결정을 내릴 가능성들을 쳐내는 것이다. 일단 결정을 내리면, 바로 실행으로 나아가라. 그렇지 않으면, 당신이 내린건 진짜 의사결정이 아니다.

5. Explain your reasoning.

(3단계에서) 다른 사람들이 자신들의 관점을 피력할 기회를 주었다면, 이제 그들에게 왜 다른 옵션을 취하지 않고 이러한 의사결정을 내렸는지 설명하라. 그들은 기꺼이 당신의 의사결정에 따를 준비가 되어 있을 것이다.

6. Never second guess.

일단 의사결정을 내린 뒤에는 그 결정에 의구심을 갖지도 말고, 회의적인 시각을 지닌 사람들에게 심각하게 귀 기울이지 마라. 실제 실행에 온 힘을 기울이고, 결과를 얻기 전까지는 말이다. 이미 내려진 판단에 의심을 가질수록 우유부단함에 물들게 되며, 자연히 실행력도 떨어지게 된다.

7. Observe the results

정말로 진실되게 그리고 갖은 힘을 다해 실행에 주력했다면, 이제 한 걸음 뒤로 물러나 그 결과를 엄정하게 살펴보라. 기대하는 성과를 거두었다면 축하하라. 만약 그렇지 않다면, 8단계로 넘어가라.

8. Adjust the decision

명심하라!!! 꽉 막힌 완고함(Bullheadedness)은 우유부단함 못지 않게 당신을 대책없는 사람으로 전락시킨다. 오히려, 더 많은 자원과 노력을 낭비하게 만들면서 말이다. 그러므로, 기대하는 결과를 얻지 못했다면, 1단계로 다시 돌아가라. 그간 실행하면서 터득한 경험을 활용해 더 다듬고, 더 나은 의사결정을 내려라.



<참조>

  - 장강일 페이스북

posted by Peter Note
2013. 10. 6. 21:09 Protocols/OAuth

플랫폼의 핵심 기능중 하나가 Open API이다. 해당 기능을 이용하려면 인증(Authetication)하고 권한 부여(Authorization)할 수 있는 약속을(프로토콜) 새롭게 만들어야 했고 그 결과물이 OAuth이다. 초기 1.0에서 보안결함을 해결한 1.0a 와 최신 2.0 버전을 가장 많이 사용을 하고 있다. 




개념

예전의 OpenID방식은 인증(로그인)만을 처리하는 것이고, RESTful Open API 호출시 권한 체크를 하지 않으므로 이에 대한 권한체크도 필요하다. OAuth는 두개의 역할을 다 수행한다 

  - 인증 : 우리가 흔히 사용하는 Login시 ID/Password 를 통하여 인증을 받는다 - Request_Token

  - 권한 : 권한이 있는 기능만을 호출할 수 있다 - Access_Token



버전

  - v1.0 : 보안 결함 발견 v1.0a가 IETF 표준 프로토콜, 웹만 인증 가능

  - v2.0 : v1.0의 key_signature 복잡함등의 제거, 웹뿐만 아니라 애플리케이션도 인증 가능 



V1.0 

  - User : 특정 서비스에 사용하는 유저 예) Smart Visualization 사용자

    Consumer : 특정 서비스이면서 OAuth 제공자(서비스 제공자)에게 인증과 권한을 요청 예) Smart Visualization

    Service Provider : OAuth 제공자 예) 트위터

  


  - Request Token 으로 인증을 처리하고 Access Token으로 권한

  - 장점

1. 컨슈머가 아이디/패스워드를 가지지 않고 API를 사용할 수 있음

2. 필요한 API에만 제한적으로 접근할 수 있도록 권한 제어 가능

3. 사용자가 서비스 프로바이더의 관리 페이지에서 권한 취소 가능

4. 패스워드 변경 시에도 인증 토큰은 계속 유효함.

  - 총정리

  
- 동영상 강좌 
  


  

V2.0 

  - 3가지 종류 존재 : bearer token 방식 사용. 나머지 두가지는 계속 수정중 

  - 간단해 졌고, 더 많은 인증 방법을 제공, 대형 서비스로 확장을 지원한다 

  


사용하기 

  - 다양한 언어별 라이브러리 존재 



<참조>

  - OAuth v2.0 만능 도구상자 (KTH)

  - OAuth v1.0와 춤을 (NHN)

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

[OAuth] EveryAuth 이용해서 Twitter 인증하기  (0) 2013.10.05
posted by Peter Note
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 Peter Note
2013. 10. 5. 15:42 My Services/Smart Visualization

디자인이라고 말하면 일반 개발자들은 포토샵과 일러스트레이터를 다루는 그래픽 디자이너를 생각하게 된다. 하지만 특정 도메인을 표현하는 제품 입장에서 디자이너와 함께 일하기 위하여 일반적으로 기대하는 수준은 사뭇 다르다. 나 또한 B2B 솔루션을 3개 정도 런칭해 보았지만 일반 그래픽만 다루던 디자이너와 일했을 때 도메인 컨텍스트를 이해시키고 설득하는데 많은 시간과 노력 그리고 스트레스를 어떻게 받게 되었는지 잘 안다. 어떻게 하면 훌륭한 디자인을 할 수 있는지? 자신의 Golden Path는 어떻게 만들 수 있는지 알아보자 



프러덕트 디자이너가 작업전 알아야 할 것은?

  - 프러덕트 디자인을 하기전 디자이너는 회사의 미션과 비젼을 알아야 한다 (참조)

  - 프러덕트의 아키텍쳐를 알아야 한다 

    아키텍쳐는 기술적 표현보다는 좀 더 추상화되고 구조화된 표현이다 

    

   + Facebook 예

   

  + 구조화된 표현

   



프러덕트 디자인의 접근법

  - 디자인시 4개의 층이 있고 Top->Down으로 접근한다 

  - Outcome -> Structure -> Interaction -> Visual

    최종 결과물의 모습 : 우리가 해결하길 원하는 사람들의 문제는 무엇인가? 서비스(프러덕트)를 정확히 정의한다 (서비스 참조)

    레이아웃 구조 : 의도한 시스템 구축을 위해 필요한 컴포넌트와 그들의 관계에 대해서 맵핑하는 디자인을 한다  

    화면 상호작용 : 애니메이션효과 화면전환의 흐름등의 상호작용을 자세히 디자인한다

    시안의 시각화 : 아름다운 그리드, 색감, 타이포그라피, 아이콘등의 look and feel의 아름다움을 디자인한다 

  



2013년 트랜드인 Flat Design 관점 

  - 애플 iOS7에서 플랫디자인을 선보이고, 구글은 전사 서비스를 플랫디자인으로 도배하고 있다. 

  - 프러덕트 디자이너가 Visual 단계에서 바라본 플랫디자인의 요소 5가지 (참조)

     + Use of simple elements 

        심플한 아이콘 : 사각, 원형의 단순 모형에 배치, 컬러, 형태로 표현 -> 사용자가 UI 를 쉽게 인식하고 사용토록 만든다 

     + Absence of depth 

        배제된 효과 : 그림자, 입체감, 그라데이션등의 현실감있게 하는 3D적 효과를 배제하여 직관적으로 만든다  

     + Typography

        타이포그래피 : 화려한 폰트를 배제하고 간결한 폰트를 사용한다. 예) 산세리프 

     + Color

        단순한 칼라 : 2~3가지 또는 5~6가지의 색상만을 사용하여 간결하고 과감하고 표현한다. 핑크, 그린, 블루가 대세  

     + Minimalism

        미니멀리즘 : 미니멀리즘과 부합 - 간단하고 심플하게 표현으로 다양한 웹, 모바일에 적응이 쉽다  

    



서비스 디자인이라는 관점

  - 서비스을 만들 때 우리는 어떻게 접근하여야 할까 단지 디자인이 문제가 아니라 UX라는 사용자 경험을 고려하여 사람과 컴퓨터의 상호작용을 생각해 보아야 하고, 우리가 만들려는 프로덕트나 서비스에 어떻게 녹아 있어야 하는지 연구하고 새롭게 창조하는 과정에 대해 알아보자 

  - 서비스 디자인 이란 무엇인가? - 개념 이해


  - 서비스 디자인을 어떻게 하는지 알아보자 - 접근 방법 


- 서비스 디자인 프로세스 
  Discover -> Define -> Develop -> Deliver

  



InfoGraphic 에서 바라본 디자인 관점 예  

  - 정보를 시각화 하는 인포그래픽에 대한 접근법은 어떻게 될까? 10단계에 대해 알아보자 (참조)

  1) 자료 모으기 - Gathering Data

  2) 모든 것을 알기 - Reading Everything

  3) 서사구조를 찾아내기 - Finding the narrative

  4) 문제를 확인하기 - Identifying Problems

  5) 계층을 만들기 - Creating Hierarchy

  6) 뼈대를 만들기 - Building a Wireframe

  7) 표현형식 고르기 - Choosing a Fromat

  8) 시각적 접근법 결정하기 - Determining a Visual Approach

  9) 개선과 시험 : Refinement and Testing

  10) 세상에 출시하기 : Releasing into the World 

  


  - 프러덕트 디자이너 관점에 인포그래픽을 바라 본 다면 다음과 같이 나눌 수 있지 않을까 개인적으로 작위적 분류를 해본다 

     (특성상 상호작용은 제외함)

     1) ~ 4) == OutCome

     5) ~ 6) == Structure

     7) ~ 9) == Visual 

  - 서비스 다지안 관점에서도 작위적으로 분류해 본다 

     1) ~ 4) == 탐색 

     5) ~ 6) == 정의 

     7) ~ 9) == 만들기

     10) == 출시하기  



프러덕트 디자인을 한다는 것은 이제 예전의 단순 그래픽 디자인에서 -> 결과물(프러덕트)의 전반적인 미션과 비젼을 이해하고 -> 어떻게 사람과 컴퓨터가 상호작용해야 하는지 이해한 후 사용자 경험을 극대화 하는 작업으로 변하였다. 최근은 모바일기기의 확대로 그 경험의 비쥬얼에 플랫디자인이 대세를 이루고 있으며 이는 사용에게 궁극적으로 감정적 만족감과 행복감을 주는 방향으로 가고 있다.



<참조> 

  - 원문 : 디자인의 드리블화 

  - 플랫 디자인의 특징

  - 몇가지 플랫 디자인 예들

  - 놀라운 인포그라픽을 만드는 10단계

  - 빅데이터 관점의 서비스 디자인

  - HCI Cousera 강좌 : Human Computer Interaction

posted by Peter Note
2013. 9. 30. 16:57 My Services/Smart Visualization

차트는 많은 정보를 내포한다. 사람들끼리 차트를 공유하고 싶을 경우 보통은 엑셀에서 작업을 하고, 보고서를 MS-Word 또는 HWP로 만들어서 메일을 보내거나 채팅창으로 파일을 전송하게 된다. 여러 복잡한 과정을 통하여 자신의 데이터에 대하여 의미를 내포한 차트로 공유하기까지의 과정이 너무 길고 많은 시간이 소요된다. 그리고 지속적인 관심을 가진 데이터라면 이들에 대한 히스토리는 개별 문서마다 보관이 될 것이고, 합쳐서 보고자 할 때 번거로운 재작업을 거쳐야 한다. 우리가 사용하는 페이스북이나 기존 SNS 사용방식처럼 데이터를 시각화한 차트에 대해 공유하여 볼 수 있고, 코멘트를 달고 관련 차트끼리 연결지어서 더 낳은 정보로 만들어 갈 수 있으면 얼마나 재미있을까? 관련성있는 차트들을 모아 보면서 회의에 사용하거나 바로 스마트 기기에서 확인함으로써 업무 효율화를 가져올 수도 있다


이러한 가치하에서 Smart Visualization에 대한 목업화면을 그려 보았다. 



Share Place 

  - Search 입력에 어느 그룹, 코멘트, 의견등의 내용을 검색하여 관련있는 차트 목록을 볼 수 있다

  - 공유하는 공간이다. 차트별로 상단에 자신의 것인지, 어느 그룹과 공유되는 것인지 보이고 등록 시간이 나온다 

     Place : personal 또는 group-01

  - 상단 Place 또는 시간을 클릭하면 해당 차트에 대한 상세 내역을 보여주는 화면이 나온다 

  - 차트 하단에는 해당 차트에 대한 Comment와 관련 차트(Related Chart)를 연결지을 수 있다 

  - 상단 우측에는 현재 채팅진행중이 확인되지 않은 내역 건수와 메세지 건수를 Badge 로 보여준다



Register Place

  - 일정 형식의 .csv 파일을 업로드한다 

  - 해당 차트의 Place (속할 그룹)과 차트 종류를 선택하고 등록한다 



Slide Menu

  - "Smart Visualization" 좌측의 아이콘을 클릭하면 메뉴가 나온다 

  - 개인 또는 그룹을 검색하여 친구맺기를 할 수 있고, 상대가 응답을 해주면 상호 연결이 되어 Share Place에서 차트를 공유한다 

  - 기존 대화목록이나 메세지를 열람할 수 있다

  - 메뉴 상단에 "사용자"검색이나 "그룹" 검색이 들어가겠다

데이터 시각화 차트의 공유에 벗어나는 레이아웃은 제거할 것이다.



* 개념이 유사한 사이트

  https://plot.ly/


  https://infogr.am/


posted by Peter Note
2013. 9. 28. 17:45 Languages/JavaScript

자바스크립트의 상속과 성능향상을 위하여서는 Prototype Pattern을 갖는 Prototypal Inheritance를 이용한다.




개념

  - ECMAScript 5에서 Object.create 로 가능하다 

var myCar = { name: "Ford Escort", drive: function () { console.log( "Weeee. I'm driving!" ); }, panic: function () { console.log( "Wait. How do you stop this thing?" ); } }; // Use Object.create to instantiate a new car var yourCar = Object.create( myCar ); // Now we can see that one is a prototype of the other 

console.log( yourCar.name );

 

  - 오브젝트 확장할 때 

var vehicle = { getModel: function () { console.log( "The model of this vehicle is.." + this.model ); } };

// 두번째 인자에서 확장한다 Mixin... var car = Object.create(vehicle, { "id": { value: MY_GLOBAL.nextId(), // writable:false, configurable:false by default enumerable: true }, "model": { value: "Ford", enumerable: true } }); 


  - Object.create를 사용하지 않고 Construct Function의 prototype 프로퍼티를 이용하여 prototypal inheritance 구현 

var vehiclePrototype = { init: function ( carModel ) { this.model = carModel; }, getModel: function () { console.log( "The model of this vehicle is.." + this.model); } }; function vehicle( model ) { function F() {};  // 일급 클래스 펑션의 prototype을 vehiclePrototype객체로 정의 

// 상단 이미지에서 Foo의 프로퍼티인 propertype 변수에 Foo.prototype 객체를 참조한다

F.prototype = vehiclePrototype;

// new를 하면 vechiclePrototype객체가 f 변수에 할당 된다 var f = new F(); f.init( model ); return f; } var car = vehicle( "Ford Escort" ); 

console.log(car.getModel());

다른 방법으로 proto 전달 

var beget = (function () { function F() {} return function ( proto ) { F.prototype = proto; return new F(); }; 

})();



다양한 Object creation으로 메소드 확장하기

  - 메소드안에 정의 

    + getInfo는 Car 생성때 마다 생성되므로 메모리를 차지한다 (안좋음) 

function Car(make, model, level, color, warranty) {
    this.make     = make;
    this.model    = model;
    this.level    = level;
    this.color    = color;
    this.warranty = warranty;
    
    this.getInfo = function() {
        return this.make + ', ' + this.model + ', ' + this.level + ', '+ this.color + ', ' + this.warranty;
    };
}
 
var aCar = new Car('Acura', 'TL', 'Sport', 'blue', 5);

console.log(aCar.getInfo());  //displays Acura, TL, Sport, blue, 5 


  - 메소드를 외부에서 Prototype에 지정하기 

    + 필요한 시점에 메소드를 추가할 수 있다 예) Car.prototype.accelerate 

function Car(make, model, level, color, warranty) {
    this.make     = make;
    this.model    = model;
    this.level    = level;
    this.color    = color;
    this.warranty = warranty;
}
 
Car.prototype.getInfo = function() {
  return this.make +','+ this.model +','+ this.level +','+ this.color +','+ this.warranty;
};
 
var aCar = new Car('Acura', 'TL', 'Sport', 'blue', 5);
alert(aCar.getInfo());  //displays Acura, TL, Sport, blue, 5
 
Car.prototype.accelerate = function() {
    return 'How fast?';
};

console.log(aCar.accelerate()); //displays "How fast?"


  - Prototype Pattern을 이용

    + prototype에 필요한 메소드를 Literal 객체로 지정한다 

function Car(make, model, level, color, warranty) {
    this.make     = make;
    this.model    = model;
    this.level    = level;
    this.color    = color;
    this.warranty = warranty;
}
 
Car.prototype = {
    getInfo: function () {
      return this.make +','+ this.model +','+ this.level +','+ this.color +','+ this.warranty;
    }

};


  - Revealing Prototype Pattern을 이용

    + prototype에 객체를 생성하여 할당한다 

var UsedCar = function(mileage) {
    //Define a variable unique to each instance of UsedCar
    this.mileage = mileage;
};
 

UsedCar.prototype = new Car('Honda', 'Civic', 'LX', 'gray', 2);


// Revealing Module Pattern 형식

UsedCar.prototype = function () { }();



<참조>

  - 원문 : The Prototype Pattern

  - Some Prototype Pattern 사용하기 

  - Revealing Prototype Pattern 사용하기

  - 드미트리 소스시코브의 JavaScript Core

posted by Peter Note
2013. 9. 28. 16:18 AngularJS/Concept


posted by Peter Note
2013. 9. 27. 20:03 My Services/Smart Visualization

AngularJS Framework기반 SPA 프로젝트를 Yeoman으로 시작하면 기본 CSS Framework로 Twitter Bootstrap v2.3.2 기반으로 index.html 에 설정이 된 것을 AngularJS 기반 SPA 스타일로 간단히 고쳐 보았다. 좀 더 다양한 아이콘과 확장기능을 사용하기 위하여 CSS Framework들을 검토해 보아야 하고, Smart Visualization은 Bootflat을 확장판을 사용토록 한다



1. Bootflat

  - http://www.flathemes.com/

  - Bootstrap v3.0 을 기본으로 한 확장판이다 

  - Bootstrap css 파일과 확장된 css를 링크하므로 Bootstrap에서 모자란 부분을 보완할 수 있다

  - Bootflat을 다운로드 파일의 내부 디렉토리 구조

    + bootstrap은 v3.0 

    + js : jquery, bootstrap, IE에서 html5 지원 Html5shivRWD를 위한 Respond, 체크박스 확장 iCheck

    + fonts : icon과 font 확장을 위한 FontAwesome

    + 그외 Bootflat을 위한 확장 .css 파일들

bootstrap/
├── bootstrap/
│   ├── bootstrap.css
│   ├── bootstrap.min.css
├── css/
│   ├── bootflat.css
│   ├── bootflat-extensions.css
│   ├── bootflat-square.css
│   ├── font-awesome.min.css
├── js/
│   ├── jquery-1.10.1.min.js
│   ├── html5shiv.js
│   ├── respond.min.js
│   ├── bootstrap.js
│   ├── bootstrap.min.js
│   ├── jquery.icheck.js
├── fonts/
│   ├── FontAwesome.otf
│   ├── fontawesome-webfont.eot
│   ├── fontawesome-webfont.svg
│   ├── fontawesome-webfont.ttf
│   ├── fontawesome-webfont.woff
└── img/
    └── check_flat/ 

└── default.png


  - Bootflat 시작을 위한 index.html 

    + CSS : bootstrap -> font-awesome -> bootflat 순으로 설정

    + JS : jquery -> bootstrap 순으로 설정

<!DOCTYPE html>
<html>
    <head>
      <title>Bootflat Template</title>
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <!-- Bootstrap -->
      <link href="bootstrap/bootstrap.min.css" rel="stylesheet" media="screen">
      <link href="css/font-awesome.min.css" rel="stylesheet" media="screen">
      <link href="css/bootflat.css" rel="stylesheet" media="screen">
      <link href="css/bootflat-extensions.css" rel="stylesheet" media="screen">
      <link href="css/bootflat-square.css" rel="stylesheet" media="screen">

      <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
      <!--[if lt IE 9]>
        <script src="js/html5shiv.js"></script>
        <script src="js/respond.min.js"></script>
      <![endif]-->
    </head>
    <body>
      <h1>Hello, world!</h1>

      <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
      <script src="//code.jquery.com/jquery.js"></script>
      <!-- Include all compiled plugins (below), or include individual files as needed -->
      <script src="js/bootstrap.min.js"></script>
    </body> 

</html>



2. Smart Visualization 프로젝트에 포팅하기

  - Yeoman으로 생성된 프로젝트의 Bootstrap 버전은 2.3.2 이다 

  - 기존 SPA를 위하여 설치된 모듈 목록은 bower 명령으로 확인한다

    + bootstrap-sass 버전 충돌

    + jquery 버전 충돌

    + es5-shim : ECMAScript5 지원

$ bower list

SmartVisualization#0.0.0 /Users/prototyping/SmartVisualization

├── angular#1.0.8 (latest is 1.2.0-rc.2)

.. 중략 ..

├─┬ angular-scenario#1.0.8 (latest is 1.2.0-rc.2)

│ └── angular#1.0.8

├─┬ bootstrap-sass#2.3.2 (latest is 3.0.0)

│ └── jquery#1.9.1 (2.0.3 available)

├── es5-shim#2.0.12 (latest is 2.1.0)

├── jquery#1.9.1 (latest is 2.0.3)

└── json3#3.2.5


  - Bootstrap 업그레이드 하기 

    + bower를 통하여 업그레이드 하기 : app/bower_components/ 밑으로 설치됨 (.bowerrc 파일에서 위치 변경가능)

// bower.json에 기록하고 물리적 파일 설치 

$ bower install bootstrap --save

bower bootstrap#* cached git://github.com/twbs/bootstrap.git#3.0.0


// bower.json에 기록된 내용과 물리적 파일 삭제

$ bower uninstall bootstrap-sass --save

bower uninstall  bootstrap-sass


$ bower list

.. 중략...

├─┬ bootstrap#3.0.0

│ └── jquery#1.9.1 (2.0.3 available)

    + app/index.html 파일 리팩토링

// CSS 

기존

<link rel="stylesheet" href="styles/bootstrap.css">


변경

<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">


// JS

기존

<script src="bower_components/bootstrap-sass/js/bootstrap-affix.js"></script>

<script src="bower_components/bootstrap-sass/js/bootstrap-alert.js"></script>

.. 중략 ..

<script src="bower_components/bootstrap-sass/js/bootstrap-collapse.js"></script>

<script src="bower_components/bootstrap-sass/js/bootstrap-tab.js"></script>


변경 : 필요한 모듈만 설정원하면 기존과 같이 bower_components/bootstrap/js/* 안에서 선택한다

<script src="bower_components/bootstrap/dist/js/bootstrap.min.js">

   + 브라우져에서 정상여부 확인하기 

$ grunt server


// 브라우져에서 Eye Check 수행

Bootstrap v3.0 으로 가면서 Bootstrap v2.3.x에서 사용한 CSS class 값이 없기 때문에 비정상적으로 나온다 

    + Bootstrap v3.0 스타일로 index.html의 RWD Header Menu 리팩토링 

// Bootply에서 Bootstrap v3.0에 맞게 redesign 한 후 copy 한다 

// 기존 : v2.3.x 

<div class="navbar navbar-inverse navbar-fixed-top">

      <div class="navbar-inner">

        <div class="container">

          <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">

            <span class="icon-bar"></span>

            <span class="icon-bar"></span>

            <span class="icon-bar"></span>

          </button>

          <a class="brand" href="#">Smart Visualization</a>

          <div class="nav-collapse collapse">

            <ul class="nav">

              <li class="active"><a href="#">Home</a></li>

              <li><a href="#about">MovingAverage</a></li>

            </ul>

          </div><!--/.nav-collapse -->

        </div>

      </div>

    </div>


// 변경 : v3.0.0

  <div class="navbar navbar-inverse navbar-fixed-top">

      <div class="container">

        <div class="navbar-header">

          <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">

            <span class="icon-bar"></span>

            <span class="icon-bar"></span>

            <span class="icon-bar"></span>

          </button>

          <a class="navbar-brand" href="#">Smart Visualization</a>

        </div>

        <div class="collapse navbar-collapse">

          <ul class="nav navbar-nav">

            <li class="active"><a href="#">Home</a></li>

            <li><a href="#about">MovingAverage</a></li>

          </ul>

        </div><!--/.nav-collapse -->

      </div>

    </div>


// 브라우져 확인 결과 정상적으로 나옴


  - Font-Awesome 설치하기 

// font awesome 설치 

$ bower install font-awesome --save

bower font-awesome#*            cached git://github.com/FortAwesome/Font-Awesome.git#3.2.1

bower font-awesome#*          validate 3.2.1 against git://github.com/FortAwesome/Font-Awesome.git#*


// index.html에 font-awesome 설정 추가

<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">

<link href="bower_components/font-awesome/css/font-awesome.min.css" rel="stylesheet" media="screen">


// 브라우져에서 확인 -> 정상


  - jQuery v1.9.1 에서 v1.10.1로 업그레이드 하기 

// 기존 uninstall

$ bower uninstall jquery --save

bower conflict      bootstrap depends on jquery

[?] Continue anyway? Yes

bower uninstall     jquery


// 변경 install : 버전 명시 

$ bower install jquery#1.10.1 --save

bower jquery#>= 1.9.0           cached git://github.com/components/jquery.git#2.0.3

bower jquery#>= 1.9.0         validate 2.0.3 against git://github.com/components/jquery.git#>= 1.9.0

bower jquery#1.10.1             cached git://github.com/components/jquery.git#1.10.1

bower jquery#1.10.1           validate 1.10.1 against git://github.com/components/jquery.git#1.10.1

bower jquery#1.10.1            install jquery#1.10.1

jquery#1.10.1 app/bower_components/jquery


// 브라우져에서 확인 -> 정상


  - jQuery iCheck 설치

$ bower install jquery-icheck --save


// index.html에 추가 설정

<script src="bower_components/jquery/jquery.js"></script>

<script src="bower_components/jquery-icheck/jquery.icheck.min.js"></script>


  - bootflat관련 css 파일 복사 

기존에 존재하던 bootstrap.css 파일은 삭제한다 


// index.html에 css 설정 

<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">

<link href="bower_components/font-awesome/css/font-awesome.min.css" rel="stylesheet" media="screen">

<link href="styles/bootflat.css" rel="stylesheet" media="screen">

<link href="styles/bootflat-extensions.css" rel="stylesheet" media="screen">

<link href="styles/bootflat-square.css" rel="stylesheet" media="screen">

<link rel="stylesheet" href="styles/main.css">


// 브라우져에서 테스트 -> 정상 




posted by Peter Note
2013. 9. 25. 16:52 My Services/Smart Visualization

AngularJS는 구글이 후원하고 있고 2013년 구글I/O에서도 2개의 세션에서 소개가 될 정도로 관심이 높은 차기 웹 클라이언트단의 애플리케이션 개발을 위한 프레임워크이다. MVC -Model View Controller - 기반으로 역할을 나누고, SoC (separate of concer)이라는 관심의 분리를 위하여 모듈에 대한 DI (Dependency Injection)을 사용한다. 




1. AngularJS 어떻게 배워야 하지?

  - AngularJS에 대한 개념적 이해와 익히는 방법을 알아보자 

  - 전반적으로 다시 한번 복습해 보고

  - 이젠 AngularJS만의 길을 가보자 



2. AngularJS 스케폴딩 둘러보기 

  - SmartVisualization/app 폴더는 다음과 같다 

    + index.html : 최초 호출 페이지

    + bower_components : bower에 의해서 설치된 client에서 사용하는 library들. 

       bower 를 통해 install 하면 JavaScript 링크가 index.html에 자동 삽입된다 

    + scripts : angular javascript 파일 폴더들 

    + views : angular의 template 파일은 .html 확장자이다. 보통 partial 파일이라고 한다

       


  - index.html 가 메인 페이지이다 

    + ng-app 를 통하여 AngularJS 애플리케이션 명칭을 정의

    + ng-view 는 views 폴더의 partial html이 표현되는 영역이다. DOM을 보게 되면 해당 영역만 변경이 이루어 진다 

    + app.js 가 AngularJS의 메인 수행 파일이고 일반적으로 partial view에 대한 routing을 정의한다 

    + controller/main.js 은 AngularJS의 컨트롤러 파일이다. partial html 파일이 여러개 있더라도 동일한 controller를 사용할 수 있다. 

<!doctype html>

<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->

<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->

<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->

<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->

  <head>

    <meta charset="utf-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <title></title>

    <meta name="description" content="">

    <meta name="viewport" content="width=device-width">

    <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->


        <!-- build:css(.tmp) styles/main.css , 부트스트랩의 RWD까지 적용되어 있다 -->

        <link rel="stylesheet" href="styles/bootstrap.css">

        <link rel="stylesheet" href="bower_components/bootstrap-sass/bootstrap-responsive-2.3.2.css">

        <link rel="stylesheet" href="styles/main.css">

        <!-- endbuild -->

</head>

  <!-- yo angular:app SmartVisualization 명령수행으로 App 접미어를 붙여서 프로젝트 명칭을 만들었다 --> 

  <body ng-app="SmartVisualizationApp">

    <!--[if lt IE 7]>

      <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>

    <![endif]-->


    <!--[if lt IE 9]>

      <script src="bower_components/es5-shim/es5-shim.js"></script>

      <script src="bower_components/json3/lib/json3.min.js"></script>

    <![endif]-->


    <!-- Add your site or application content here : ng-view 영역이 실제 partial html 파일로 대체되는 영역 -->

    <div class="container" ng-view=""></div>


    <!-- angular.js 자체에 light jquery를 내장하고 있지만 정식버전 jquery를 사용하고 싶다면 

          하기와 같이 angular 위에 놓으면 된다 -->

    <script src="bower_components/jquery/jquery.js"></script>

    <script src="bower_components/angular/angular.js"></script>


        <!-- build:js scripts/plugins.js, 사용하지 않는 부트스트랩 자바스크립트는 제거한다 -->

        <script src="bower_components/bootstrap-sass/js/bootstrap-affix.js"></script>

        <script src="bower_components/bootstrap-sass/js/bootstrap-alert.js"></script>

        .. 중략 ..

        <script src="bower_components/bootstrap-sass/js/bootstrap-scrollspy.js"></script>

        <script src="bower_components/bootstrap-sass/js/bootstrap-collapse.js"></script>

        <script src="bower_components/bootstrap-sass/js/bootstrap-tab.js"></script>

        <!-- endbuild -->


        <!-- build:js scripts/modules.js -->

        <script src="bower_components/angular-resource/angular-resource.js"></script>

        <script src="bower_components/angular-cookies/angular-cookies.js"></script>

        <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

        <!-- endbuild -->


        <!-- build:js({.tmp,app}) scripts/scripts.js, 업무적으로 추가되는 파일은 이곳에 넣는다

              단, yo 명령으로 controller 또는 service를 생성하면 자동으로 파일명이 추가된다  -->

        <script src="scripts/app.js"></script>

        <script src="scripts/controllers/main.js"></script>

        <!-- endbuild -->

</body>

</html>


  - AngularJS 메인 애플리케이션 파일인 app.js 을 보자 

    + module : ng-app에서 정의한 SmartVisualizationApp 명칭이 들어가고 

      [] 에는 별도로 파일을 구분하였다면 의존관계에 있는 모듈을 정의 한다 

    + templateUrl : index.html 위치에서 partial html 상대위치를 정의한다 

    + controller : main.html 안의 model 제어를 위한 controller명칭을 정의한다 (controller/main.js 소스에 정의된 컨토롤러 명칭)

    + redirectTo : http://localhost:9000/ 을 넣으면 무조건 views/main.html이 보여지도록 그외의 url에 대한 redirect를 정의한다 

'use strict';


angular.module('SmartVisualizationApp', [])

  .config(function ($routeProvider) {

    $routeProvider

      .when('/', {

        templateUrl: 'views/main.html',

        controller: 'MainCtrl'

      })

      .otherwise({

        redirectTo: '/'

      });

  });


  - main.html 은 partial html 파일이다   

    + 이곳에 이동평균에 관련된 view가 정의될 것이다 

<div class="hero-unit">

  <h1>'Allo, 'Allo!</h1>

  <p>You now have</p>

  <ul>

      <li ng-repeat="thing in awesomeThings">{{thing}}</li>

  </ul>

  <p>installed.</p>

  <h3>Enjoy coding! - Yeoman</h3>

</div>



3. index.html 메인 뷰페이지 RWD 수행

  - RWDResponsive Web Design의 약어로 반응형 웹 디자인이라고 한다. 

    RWD의 목적은 사용자에게 최적의 뷰 경험을 제공하는데 있다. 

    웹 서비스를 만들 때 중요한 요소로 검색엔진 최적화, 웹 접근성, 반응형 웹 디자인을 필수로 고려해야 한다. (참조

  - RWD는 시각화 표현에 대한 부분이고 웹에서는 CSS가 이를 담당하며, 가장 유명한 CSS Framework으로 Twitter Bootstrap이 있다

    하지만 Bootstrap 이외에 GroundWorks, Zurb Foundation, Amazing Web Library 들도 있으니 구미에 맞는 것을 선택한다.

  - Bootstrap을 사용하게 되면 몇가지 유용한 도구와 서비스들이 있다. 

    여기서는 Bootstrap 기반으로 화면의 Layout을 만들어 주는 Bootply를 사용한다 

 


  - 화면에서 가운데 메뉴 "Drag-and-Drop"을 선택하면 팝업창에서 3가지 Layout 옵션이 나온다. 여기선 "Simple"을 선택한다

  

  

  - Editing 화면에서 내역을 수정하고 상단의 "Run"을 클릭하면 변경 내역을 다양한 기기 해상도로 테스트해 볼 수 있다 

    + yo angular는 bootstrap 2.3* 기반이니 주의한다. Bootply는 boostrap v3.*도 지원한다 (default v2.3*)

  


  - Bootply의 html 내역중 메뉴 관련 내용을 copy하여 index.html에 추가한다

    ng-view 부분에 main.html 내역이 표시될 것이다 

// index.html 추가 부분

<body ng-app="SmartVisualizationApp">

    <!--[if lt IE 7]>

      <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>

    <![endif]-->


    <!--[if lt IE 9]>

      <script src="bower_components/es5-shim/es5-shim.js"></script>

      <script src="bower_components/json3/lib/json3.min.js"></script>

    <![endif]-->


    <div class="navbar navbar-inverse navbar-fixed-top">

      <div class="navbar-inner">

        <div class="container">

          <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">

            <span class="icon-bar"></span>

            <span class="icon-bar"></span>

            <span class="icon-bar"></span>

          </button>

          <a class="brand" href="#">Smart Visualization</a>

          <div class="nav-collapse collapse">

            <ul class="nav">

              <li class="active"><a href="#">Home</a></li>

              <li><a href="#about">MovingAverage</a></li>

            </ul>

          </div><!--/.nav-collapse -->

        </div>

      </div>

    </div>


    <!-- Add your site or application content here -->

    <div class="container" ng-view=""></div>


  - main.html 전체 내역을 다음과 같이 바꾼다. bootstrap의 container css class로 감싸준다. 

    app.js에서 루트 '/' 호출은 /views/main.html 로 라우팅되므로 index.html의 ng-view에서 main.html 보여지게 된다 

<div class="container">

  <h1>Smart Visualization Service</h1>

  <p>

    Job Less, Feel More - 인위적인 작업을 적게 하면 더 행복해 질 수 있다고 믿습니다.<br> 

    단순 파일 업로드만으로 데이터 통계를 미려한 차트로 표현해 드립니다.<br>

    .csv 파일에 시간과 수치값만 있다면 SVG Chart로 통계 변환 수치를 보여드립니다

  </p>

  

</div> <!-- /container -->


  - 변경 내역을 저장하고 확인해 보자 

$ grunt server


// 브라우져 사이즈를 줄여보면 RWD 경험을 제공한다 



<참조>

  - 견고한 Web Applicatoin을 만드는 방법 with AngularJS

  - Think in AngularJS Way : 접근 방법에 대한 조언

  - Bootstrap Layout 전문 서비스 Bootply

  - Boostrap기반의 유용한 도구와 서비스들

posted by Peter Note
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 Peter Note
2013. 9. 24. 11:37 My Services/Smart Visualization

"엑셀로 통계 차트 그리기 어렵다" 

"쉽고 빠르게 이동평균 선을 보고 싶다"



왜?

End User가 모니터링 중인 미들웨어(JBoss)에 대한 Resource Usage/TPS/Response Time등의 분기별 통계 정보를 요구한다. 3개월 또는 6개월 단위 raw data를 그냥 엑셀에 담아서 전달하는 것은 의미가 없고, 엑셀의 차트를 그려도 일정한 패턴을 찾기 어렵지 않을까 생각되어 이동평균선 차트를 만들어서 전달해 주기로 하였다. Raw Data는 엑셀파일로 export가 되므로 .csv 확장자로 파일을 업로드하면 바로 이동평균 차트가 나오는 서비스를 만들어 널리 편히 쑤메 그 패턴(우상향, 우하향)을 인지하여 미들웨어가 정상적으로 운영되고 있는지 장기적인 관점(Capcity Plan)에서 판단근거를 세우고자 한다



들어가면서

  - 비즈니스 모델 캔버스 : 이런건 작성안함. 내가 필요해서 하는 서비스임. 

  - Lean Development : Agile Scrum방식으로 진행을 한다. 빠르게 만들고 배포하고 피드백받아 학습하면서 고도화해 간다 

    + 도구로 Trello을 사용할 예정이며 간략히 Specification(SRS)도 이곳에 정리한다 

    + Public 저장소로 GitHub을 사용하고, GitHub Commit때 마다 Travis를 통하여 지속적인 배포를 한다  

  - Lean UX : Service Desgin 에 대해서 조금 접근해 본다. 

    + 누군가 같이 할 수 있다면(3명가량) 서비스 디자인 툴킷 사용해서 워크샵 해보고 싶다 

  - RWD : Responsive Web Design (반응형 웹) 을 통해 다양한 스마트 기기에서 볼 수 있도록 한다 

  - 그동안 검토한 기술과 환경들에 대한 파일럿 성격의 서비스 구현체를 만들어 보는데 의의가 있다



어떻게 

  - SPA : Single Page Application으로 만들것이다. SPA Framework으로 AngularJS를 사용한다 

  - MEAN : MongoDB + Express + AngularJS + NodeJS를 기술스택으로 사용한다 

  - Yeoman : Yeoman의 scafolding 기능을 통하여 SPA 환경을 최초에 구성하고 개발할 것임 (include Grunt, Bower)

  - D3.js : Data Driven Documents Framework을 기반하여 HTML5의 svg를 이용한 차트 라이브러리인 NVD3.js 를 사용한다 

  - Heroku : PaaS에 서비스를 올려서 공개한다 

  - 서비스 소개는 GitHub에 프로젝트 Page를 만들어서 공개한다. 소스도 물론 퍼블릭 공개한다 

  - 서비스 진행과정은 블로그에 정리하고 SPA 초기 개발하는 분들이 참조토록 한다 



무엇을

  - Smart Visualization : 엑셀 수치 데이터를 이동평균 차트로 보여주는 서비스 

  


향후 하고 싶은 것

  - 다양한 통계 정보를 자동으로 차트에 표현해 보고 싶다 

  - Hadoop을 이용하여 BigData 연산 배치 처리 결과를 차트로 표현해 보고 싶다 

 

posted by Peter Note
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 Peter Note
2013. 9. 14. 12:18 Languages/JavaScript

Mediator 패턴은 충돌을 해결하고 협력할 수 있도록 해주는 것이다. 즉 시스템에 많은 컴포넌트들이 있다면 서로의 커뮤니케이션을 중재하고 제어한다. 예로 공항 관제실을 생각해 보자. 비행기가 컴포넌트이고 이들의 이착륙을 관리하는 관제실이 Mediator가 된다. 




개념 이해하기 

  - Mediator

    Colleague 오븐젝트와 통신하기 위한 인터페이스를 정의한다 

  - ConcreteMediator

    Colleague 클래스를 알고 Colleague 오브젝트의 레퍼런스를 가지고 있는다. 

    Colleague 끼리 메세지를 전달하고 통신토록 해준다 

  - Colleague Classes

    Mediator 오브젝트에 레퍼런스를 유지한다. Mediator를 통하여 다른 Colleague와 통신한다 



PubSub 만들기 

  - Mediator Core

  - publish() 와 subscribe() 

var mediator = (function(){

    // Storage for topics that can be broadcast or listened to

    var topics = {};


    // Subscribe to a topic, supply a callback to be executed

    // when that topic is broadcast to

    var subscribe = function( topic, fn ){

        if ( !topics[topic] ){ 

          topics[topic] = [];

        }

        topics[topic].push( { context: this, callback: fn } );

        return this;

    };


    // Publish/broadcast an event to the rest of the application

    var publish = function( topic ){

        var args;

        if ( !topics[topic] ){

          return false;

        } 


        args = Array.prototype.slice.call( arguments, 1 );

        for ( var i = 0, l = topics[topic].length; i < l; i++ ) {

            var subscription = topics[topic][i];

            subscription.callback.apply( subscription.context, args );

        }

        return this;

    };


    return {

        publish: publish,

        subscribe: subscribe,

        installTo: function( obj ){

            obj.subscribe = subscribe;

            obj.publish = publish;

        }

    };


}());

  - Jack Lawson의 Mediator.js 를 가져다 쓰면 된다 

/*! * Mediator.js Library v0.9.5 * https://github.com/ajacksified/Mediator.js * * Copyright 2013, Jack Lawson * MIT Licensed (http://www.opensource.org/licenses/mit-license.php) * * For more information: http://thejacklawson.com/2011/06/mediators-for-modularized-asynchronous-programming-in-javascript/index.html * Project on GitHub: https://github.com/ajacksified/Mediator.js * * Last update: June 13 2013 */ (function(global, factory) { 'use strict';

// 다양한 환경에서 적용할 수 있도록 모듈에 대한 export if(typeof exports !== 'undefined') { // Node/CommonJS exports.Mediator = factory(); } else if(typeof define === 'function' && define.amd) { // AMD define('mediator-js', [], function() { global.Mediator = factory(); return global.Mediator(); }); } else { // Browser global global.Mediator = factory(); } }(this, function() { 'use strict'; // We'll generate guids for class instances for easy referencing later on. // Subscriber instances will have an id that can be refernced for quick // lookups.

// 글로벌 유니크 아이디를 생성하는 로직이다. 등록된 Colleague 오브젝트에 (즉, Subscriber Instances)

// 아이디를 부여해 준다 function guidGenerator() { var S4 = function() { return (((1+Math.random())*0x10000)|0).toString(16).substring(1); }; return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); } // Subscribers are instances of Mediator Channel registrations. We generate // an object instance so that it can be updated later on without having to // unregister and re-register. Subscribers are constructed with a function // to be called, options object, and context. // Colleague인 Subscriber 펑션 정의 function Subscriber(fn, options, context){ if(!(this instanceof Subscriber)) { return new Subscriber(fn, options, context); } this.id = guidGenerator(); this.fn = fn; this.options = options; this.context = context; this.channel = null; }

// Subscriber Prototype 객체 정의

// Subscriber에 등록한 fn, context, options를 변경할 수 있다 Subscriber.prototype = { // Mediator.update on a subscriber instance can update its function,context, // or options object. It takes in an object and looks for fn, context, or // options keys. update: function(options){ if(options){ this.fn = options.fn || this.fn; this.context = options.context || this.context; this.options = options.options || this.options; if(this.channel && this.options && this.options.priority !== undefined) { this.channel.setPriority(this.id, this.options.priority); } } } }; // 채널을 통하여 Subscriber 의 등록/제거 관리 

// Subscriber에 정보 publish function Channel(namespace, parent){ if(!(this instanceof Channel)) { return new Channel(namespace); } this.namespace = namespace || ""; this._subscribers = []; this._channels = []; this._parent = parent; this.stopped = false; } // A Mediator channel holds a list of sub-channels and subscribers to be fired // when Mediator.publish is called on the Mediator instance. It also contains // some methods to manipulate its lists of data; only setPriority and // StopPropagation are meant to be used. The other methods should be accessed // through the Mediator instance. // Mediator 채널이 sub-channel 목록을 가지고 있다 

// Channel Prototype 객체 정의 Channel.prototype = { addSubscriber: function(fn, options, context){ var subscriber = new Subscriber(fn, options, context); if(options && options.priority !== undefined){ // Cheap hack to either parse as an int or turn it into 0. Runs faster // in many browsers than parseInt with the benefit that it won't // return a NaN. options.priority = options.priority >> 0; if(options.priority < 0){ options.priority = 0; } if(options.priority >= this._subscribers.length){ options.priority = this._subscribers.length-1; } this._subscribers.splice(options.priority, 0, subscriber); }else{ this._subscribers.push(subscriber); } subscriber.channel = this; return subscriber; }, // The channel instance is passed as an argument to the mediator subscriber, // and further subscriber propagation can be called with // channel.StopPropagation().

// Mediator에 등록한 Subscriber에 아규먼트 전달 멈춤 stopPropagation: function(){ this.stopped = true; },


getSubscriber: function(identifier){ var x = 0, y = this._subscribers.length; for(x, y; x < y; x++){ if(this._subscribers[x].id === identifier || this._subscribers[x].fn === identifier){ return this._subscribers[x]; } } }, // Channel.setPriority is useful in updating the order in which Subscribers // are called, and takes an identifier (subscriber id or named function) and // an array index. It will not search recursively through subchannels. // subchannel을 돌면서 우선순위를 업데이트 한다 setPriority: function(identifier, priority){ var oldIndex = 0, x = 0, sub, firstHalf, lastHalf, y; for(x = 0, y = this._subscribers.length; x < y; x++){ if(this._subscribers[x].id === identifier || this._subscribers[x].fn === identifier){ break; } oldIndex ++; } sub = this._subscribers[oldIndex]; firstHalf = this._subscribers.slice(0, oldIndex); lastHalf = this._subscribers.slice(oldIndex+1); this._subscribers = firstHalf.concat(lastHalf); this._subscribers.splice(priority, 0, sub); },

// sub channel 등록/삭제 addChannel: function(channel){ this._channels[channel] = new Channel((this.namespace ? this.namespace + ':' : '') + channel, this); }, hasChannel: function(channel){ return this._channels.hasOwnProperty(channel); }, returnChannel: function(channel){ return this._channels[channel]; }, removeSubscriber: function(identifier){ var x = this._subscribers.length - 1; // If we don't pass in an id, we're clearing all if(!identifier){ this._subscribers = []; return; } // Going backwards makes splicing a whole lot easier. for(x; x >= 0; x--) { if(this._subscribers[x].fn === identifier || this._subscribers[x].id === identifier){ this._subscribers[x].channel = null; this._subscribers.splice(x,1); } } }, // This will publish arbitrary arguments to a subscriber and then to parent // channels. // 퍼블리싱 : subscriber에 아규먼트 전달 publish: function(data){ var x = 0, y = this._subscribers.length, called = false, subscriber, l, subsBefore,subsAfter; // Priority is preserved in the _subscribers index. for(x, y; x < y; x++) { if(!this.stopped){ subscriber = this._subscribers[x]; if(subscriber.options !== undefined && typeof subscriber.options.predicate === "function"){ if(subscriber.options.predicate.apply(subscriber.context, data)){ subscriber.fn.apply(subscriber.context, data); called = true; } }else{ subsBefore = this._subscribers.length; subscriber.fn.apply(subscriber.context, data); subsAfter = this._subscribers.length; y = subsAfter; if (subsAfter === subsBefore - 1){ x--; } called = true; } } if(called && subscriber.options && subscriber.options !== undefined){ subscriber.options.calls--; if(subscriber.options.calls < 1){ this.removeSubscriber(subscriber.id); y--; x--; } } } if(this._parent){ this._parent.publish(data); } this.stopped = false; } };

// Mediator 펑션 function Mediator() { if(!(this instanceof Mediator)) { return new Mediator(); } this._channels = new Channel(''); } // A Mediator instance is the interface through which events are registered // and removed from publish channels. Mediator.prototype = { // Returns a channel instance based on namespace, for example // application:chat:message:received // sub channel은 namespace 기반으로 만들어 진다 getChannel: function(namespace){ var channel = this._channels, namespaceHierarchy = namespace.split(':'), x = 0, y = namespaceHierarchy.length; if(namespace === ''){ return channel; } if(namespaceHierarchy.length > 0){ for(x, y; x < y; x++){ if(!channel.hasChannel(namespaceHierarchy[x])){ channel.addChannel(namespaceHierarchy[x]); } channel = channel.returnChannel(namespaceHierarchy[x]); } } return channel; }, // Pass in a channel namespace, function to be called, options, and context // to call the function in to Subscribe. It will create a channel if one // does not exist. Options can include a predicate to determine if it // should be called (based on the data published to it) and a priority // index. // sub channel에 namespace 기반으로 Subscriber 등록하기 subscribe: function(channelName, fn, options, context){ var channel = this.getChannel(channelName); options = options || {}; context = context || {}; return channel.addSubscriber(fn, options, context); }, // Pass in a channel namespace, function to be called, options, and context // to call the function in to Subscribe. It will create a channel if one // does not exist. Options can include a predicate to determine if it // should be called (based on the data published to it) and a priority // index. // 한번 호출하고 끝내기 once: function(channelName, fn, options, context){ options = options || {}; options.calls = 1; return this.subscribe(channelName, fn, options, context); }, // Returns a subscriber for a given subscriber id / named function and // channel namespace // 아이디와 명칭으로 Subscriber 얻어오기 (identifier === GUID) getSubscriber: function(identifier, channel){ return this.getChannel(channel || "").getSubscriber(identifier); }, // Remove a subscriber from a given channel namespace recursively based on // a passed-in subscriber id or named function. remove: function(channelName, identifier){ this.getChannel(channelName).removeSubscriber(identifier); }, // Publishes arbitrary data to a given channel namespace. Channels are // called recursively downwards; a post to application:chat will post to // application:chat:receive and application:chat:derp:test:beta:bananas. // Called using Mediator.publish("application:chat", [ args ]); // 퍼블리싱 publish: function(channelName){ var args = Array.prototype.slice.call(arguments, 1), channel = this.getChannel(channelName); args.push(channel); this.getChannel(channelName).publish(args); } };

// 일반적으로 사용하는 이름으로도 Aliasing // Alias some common names for easy interop Mediator.prototype.on = Mediator.prototype.subscribe; Mediator.prototype.bind = Mediator.prototype.subscribe; Mediator.prototype.emit = Mediator.prototype.publish; Mediator.prototype.trigger = Mediator.prototype.publish; Mediator.prototype.off = Mediator.prototype.remove; // Finally, expose it all. Mediator.Channel = Channel; Mediator.Subscriber = Subscriber; Mediator.version = "0.9.5"; return Mediator; })); 



사용해 보기 

  - Plunker에서 수행한 내역 보기 : http://plnkr.co/edit/k9dT9Ms7xzbx77GhVraA?p=preview

  - HTML

<!DOCTYPE html>

<html>


  <head>

    <script data-require="jquery@*" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>

    <link data-require="bootstrap-css@*" data-semver="3.0.0" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />

    <link data-require="bootstrap@*" data-semver="3.0.0" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />

    <script data-require="bootstrap@*" data-semver="3.0.0" src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>

    <link rel="stylesheet" href="style.css" />

    <script src="mediator.js"></script>

    <script src="script.js"></script>

  </head>


  <body>

    <h1>Chat</h1>

    <form id="chatForm">

      <label for="fromBox">Your Name:</label>

      <input id="fromBox" type="text" />

      <br />

      <label for="toBox">Send to:</label>

      <input id="toBox" type="text" />

      <br />

      <label for="chatBox">Message:</label>

      <input id="chatBox" type="text" />

      <button type="submit">Chat</button>

    </form>

    <div id="chatResult"></div>

  </body>


</html>


  - JavaScript

// create Mediator.js

var mediator = new Mediator();


$("#chatForm").submit(function(){

    // Collect the details of the chat from our UI

    var text = $( "#chatBox" ).val(),

        from = $( "#fromBox" ).val(),

        to = $( "#toBox" ).val();

        

    // newMessage 채널로 퍼블리싱 하기 

    // Publish data from the chat to the newMessage topic

    mediator.publish( "newMessage" , { message: text, from: from, to: to } );

    return false;

});


// Append new messages as they come through

function displayChat( data ) {

    var date = new Date(),

        msg = data.from + " said \"" + data.message + "\" to " + data.to;

  

    $( "#chatResult" ).prepend("<p>" + msg + " (" + date.toLocaleTimeString() + ")</p>");

}


// Log messages

function logChat( data ) {

    if ( window.console ) {

        console.log( data );

    }

}


// newMessage 명칭으로 Subscriber 등록하기 

// Subscribe to new chat messages being submitted

// via the mediator

mediator.subscribe( "newMessage", displayChat );

mediator.subscribe( "newMessage", logChat );


  - 수행내역



<참조>

  - 원문 : Mediator Pattern

  - OODesign : Mediator Pattern

  - http://thejacklawson.com/Mediator.js/

  - 오늘의 명언 : 내가 감동하면 남도 감동할 수 있다. 그것이 SNS정신이다 

posted by Peter Note
2013. 9. 13. 21:19 MongoDB/Prototyping

MongoDB에 구글의 도서검색 내역을 넣고, 여기서 도서의 Description을 하둡으로 분석하여 추천도서를 만들어 보자 



구글도서에서 Description을 MongoDB에 저장하기 

  - 이클립스에서 Maven Project를 하나 생성하고, pom.xml 을 다음과 같이 구성한다 

    자바에서 몽고디비를 사용하기 위한 드라이버와 구글 검색결과(JSON)을 파싱하기위한 JSON라이브러리를 추가한다 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mobiconsoft</groupId>

  <artifactId>booksearch</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <dependencies>

  <dependency>

  <groupId>org.mongodb</groupId>

  <artifactId>mongo-java-driver</artifactId>

  <version>2.11.2</version>

  </dependency>


  <dependency>

  <groupId>junit</groupId>

  <artifactId>junit</artifactId>

  <version>4.10</version>

  </dependency>

 

  <dependency>

  <groupId>org.json</groupId>

  <artifactId>json</artifactId>

  <version>20090211</version>

  </dependency>

  </dependencies>

</project>

  - BookSearcher.java 코딩 (편의상 import 구문 제외)

public class BookSearcher {


  // 도서검색 

  public String searchBooks(String keyword) {

    URL url = null;

    try {

      url = new URL("https://www.googleapis.com/books/v1/volumes?q=" + keyword);

    } catch (MalformedURLException e) {

      e.printStackTrace();

    }

    

    StringBuffer sb = new StringBuffer();

    String line;

    try {

      URLConnection urlConn = url.openConnection();

      BufferedReader br = new BufferedReader(new InputStreamReader(urlConn.getInputStream(), "utf-8"));

      while((line = br.readLine()) != null) sb.append(line);

    } catch(IOException e) {

      e.printStackTrace();

    }

    

    return sb.toString();

  }

  

  // 도서 검색 결과에서  items찾아와 저장한다 

  public void saveBooks(String books) {

    Mongo mongo = null;

    try{

      mongo = new MongoClient("localhost", 27017);

    } catch(Exception e) {

      e.printStackTrace();

      throw new RuntimeException();

    }

    

   // 몽고디비 db는 books-db 이고 컬렉션은 books 로 만들어짐 

    mongo.setWriteConcern(new WriteConcern(1, 2000));

    DB bookDB = mongo.getDB("books-db");

    DBCollection bookColl = bookDB.getCollection("books");

    

    try{

      JSONObject json = new JSONObject(books);

      JSONArray items = json.getJSONArray("items");

      for( int i=0; i<items.length(); i++) {

        DBObject doc = new BasicDBObject();

        // search-book key로 value가 들어간다

        doc.put("search-book", (DBObject)JSON.parse(items.getJSONObject(i).toString()));

        bookColl.save(doc);

      }

    } catch(JSONException e) {

      e.printStackTrace();

    }

  }

}

  - 테스트 해보자 

    JUnit 테스트전에 mongodb를 기동한다 

// 몽고디비 

$ ../bin/mongod -dbpath=/Users/dowon/Documents/mongodb/database


// 테스트 

public class BookSearcherTest {

  

    private BookSearcher bookSearcher;

    

    @Before

    public void setUp() throws Exception {

      this.bookSearcher = new BookSearcher();

    }


    // search 결과 보기 

    @Test

    public void testSearchBooks() throws Exception {

      String result = this.bookSearcher.searchBooks("nosql");

      //assertNotNull(result);

      System.out.println(result);

    }

    

    // 데이터 저장하기 

    @Test

    public void testSaveBooks() throws Exception {

      String result = this.bookSearcher.searchBooks("nosql");

      this.bookSearcher.saveBooks(result);

    }

}


// 테스트 성공후 mongo 쉘을 통하여 확인 

> use books-db

switched to db books-db

> show collections

books

system.indexes

> db.books.find().length();

10

> db.books.find()

{ "_id" : ObjectId("5232e69cda06561b2e11306c"), "search-book" : { "saleInfo" : { "saleability" : "NOT_FOR_SALE", "isEbook" : false, "country" : "KR" }, "id" : "tv5iO9MnObUC", "searchInfo" : { "textSnippet" : "They provide examples, practical solutions, and expert education in new technologies, all designed to help programmers do a better job. wrox.com Programmer Forums Join our Programmer to Programmer forums to ask and answer programming ..." }, "etag" : "HX8hesQgrJM", "volumeInfo" : { "pageCount" : 408, "averageRating" : 3, "infoLink" : "http://books.google.co.kr/books?id=tv5iO9MnObUC&dq=nosql&hl=&source=gbs_api", "printType" : "BOOK", "publisher" : "John Wiley & Sons", "authors" : [  "Shashank Tiwari" ], "canonicalVolumeLink" : "http://books.google.co.kr/books/about/Professional_NoSQL.html?hl=&id=tv5iO9MnObUC", "title" : "Professional NoSQL", "previewLink ... 중략 ...



MongoDB Hadoop Connector 사용하기  

  - 몽고디비와 하둡을 연결하는 방법을 제공한다 

     https://github.com/mongodb/mongo-hadoop 에서 1.1.x 의 Core 다운로드 한다 (mongo-hadoop-core_1.1.2-1.1.0.jar)

  - Input-Output으로 몽고디비를 사용할 경우   

    

 - 분석을 위하여 Pig, MR을 할 경우

    

  - ETL처럼 처리후 별도의 저장소로 던져질 경우

    ETL from MongoDB

      

    ETL to MongoDB

        


  - Eclipse에 새로운 Book Search Mapper와 Reducer 프로젝트를 만들고 pom.xml 을 만든다 

    mongo-hadoop-core  파일을 maven에 등록되어 있지 않기때문에 수동으로 .m2/repository에 만들어 주어야 한다 

    예)

    > 레파지토리 : /Users/dowon/.m2/repository

    > 파일위치 : mongo-hadoop-core/mongo-hadoop-core_1.1.2/1.1.0/mongo-hadoop-core_1.1.2-1.1.0.jar

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mobiconsoft</groupId>

  <artifactId>booksearch_mapreduce</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <dependencies>

    <dependency>

      <groupId>org.apache.hadoop</groupId>

      <artifactId>hadoop-core</artifactId>

      <version>1.1.2</version>

    </dependency>

    

    <dependency>

      <groupId>org.mongodb</groupId>

      <artifactId>mongo-java-driver</artifactId>

      <version>2.11.2</version>

    </dependency>

    

    <!-- 수동 설정 --> 

    <dependency>

      <groupId>mongo-hadoop-core</groupId>

      <artifactId>mongo-hadoop-core_1.1.2</artifactId>

      <version>1.1.0</version>

    </dependency>

  </dependencies>

  

  <build>

    <plugins>

      <plugin>

        <artifactId>maven-antrun-plugin</artifactId>

        <configuration>

          <tasks>

            <copy file="target/${project.artifactId}-${project.version}.jar"

              tofile="/Users/dowon/Documents/input/${project.artifactId}-${project.version}.jar" />

          </tasks>

        </configuration>

        <executions>

          <execution>

            <phase>install</phase>

            <goals>

              <goal>run</goal>

            </goals>

          </execution>

        </executions>

      </plugin>

    </plugins>

  </build>

  

</project>

  - Mapper와 Reducer 클래스를 코딩 

// Mapper

public class BookSearchMapper extends Mapper<Object, BSONObject, Text, IntWritable> {

  

  private final static IntWritable ONE = new IntWritable();

  private Text word = new Text();

  

  protected void map(Object key, BSONObject value, Context context) 

    throws IOException, InterruptedException {

    BasicDBObject anItem = (BasicDBObject)value.get("search-book");

    BasicDBObject volumeInfo = (BasicDBObject)anItem.get("volumeInfo");

    String description = volumeInfo.getString("description");

    if(description == null || description.trim().length() <= 0) return;

    

    StringTokenizer st = new StringTokenizer(description);

    while(st.hasMoreTokens()) {

      word.set(st.nextToken());

      context.write(word, ONE);

    }

  }

}


// Reducer

public class BookSearcherReducer extends

  Reducer<Text, IntWritable, Text, IntWritable> {

  

  protected void reduce(Text key, Iterable<IntWritable> values, Context context) 

    throws IOException, InterruptedException {

    int sum = 0;

    for(final IntWritable value : values) sum += value.get();

    context.write(key, new IntWritable(sum));

  }

}

  - Job을 만든다

public class MongoJob extends MongoTool {

  static {

    Configuration.addDefaultResource("mongo-default.xml");

    Configuration.addDefaultResource("mongo-book.xml");

  }

  

  public static void main(String[] args) throws Exception {

    Configuration conf = new Configuration();

    

    JobHelper.addJarForJob(conf, "/Users/dowon/.m2/repository/mongo-hadoop-core/mongo-hadoop-core_1.1.2/1.1.0/mongo-hadoop-core_1.1.2-1.1.0.jar:"

          + "/Users/dowon/.m2/repository/org/mongodb/mongo-java-driver/2.11.2/mongo-java-driver-2.11.2.jar");

    

    System.exit(ToolRunner.run(conf, new MongoJob(), args));

  }

}

  - 리소스 xml을 만든다 

    https://github.com/mongodb/mongo-hadoop/blob/master/examples/treasury_yield/src/main/resources/mongo-defaults.xml 

   에서 xml 정보를 copy 하여 mongo-book.xml 을 만든 후 하기 내용을 수정하여 입력해야 한다

<property>

    <!-- Class for the mapper -->

    <name>mongo.job.mapper</name>

    <value>booksearch_mapreduce.BookSearchMapper</value>

  </property>

  <property>

    <!-- Reducer class -->

    <name>mongo.job.reducer</name>

    <value>booksearch_mapreduce.BookSearcherReducer</value>

  </property>

  <property>

    <!-- InputFormat Class -->

    <name>mongo.job.input.format</name>

    <value>com.mongodb.hadoop.MongoInputFormat</value>

  </property>

  <property>

    <!-- OutputFormat Class -->

    <name>mongo.job.output.format</name>

    <value>com.mongodb.hadoop.MongoOutputFormat</value>

  </property>

  <property>

    <!-- Output key class for the output format -->

    <name>mongo.job.output.key</name>

    <value>org.apache.hadoop.io.Text</value>

  </property>

  <property>

    <!-- Output value class for the output format -->

    <name>mongo.job.output.value</name>

    <value>com.mongodb.hadoop.io.BSONWritable</value>

  </property>

  <property>

    <!-- Output key class for the mapper [optional] -->

    <name>mongo.job.mapper.output.key</name>

    <value>org.apache.hadoop.io.Text</value>

  </property>

  <property>

    <!-- Output value class for the mapper [optional] -->

    <name>mongo.job.mapper.output.value</name>

    <value>org.apache.hadoop.io.IntWritable</value>

  </property>

  <property>

    <!-- Class for the combiner [optional] -->

    <name>mongo.job.combiner</name>

    <value>booksearch_mapreduce.BookSearcherReducer</value>

  </property>

  - "Mave Build..." clean install 하여 .jar  파일을 만든다 (참조에 첨부파일)

 - 다음 하둡 runtime(start-all.sh) 을 수행한다 

  - 하둡 수행 쉘을 만든다 

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

// mongodb.sh 내역 

#!/bin/sh


export REPO=/Users/dowon/.m2/repository

export MONGO_DRIVER=$REPO/org/mongodb/mongo-java-driver/2.11.2/mongo-java-driver-2.11.2.jar

export MONGO_HADOOP=$REPO/mongo-hadoop-core/mongo-hadoop-core_1.1.2/1.1.0/mongo-hadoop-core_1.1.2-1.1.0.jar

export HADOOP_CLASSPATH=$MONGO_DRIVER:$MONGO_HADOOP

export HADOOP_USER_CLASSPATH_FIRST=true


hadoop jar booksearch_mapreduce-0.0.1-SNAPSHOT.jar booksearch_mapreduce.MongoJob



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

/// 수행하기 

$ mongodb.sh

2013-09-13 20:53:24.630 java[1431:1203] Unable to load realm info from SCDynamicStore

13/09/13 20:53:24 INFO util.MongoTool: Created a conf: 'Configuration: core-default.xml, core-site.xml, mongo-default.xml, mongo-book.xml, mapred-default.xml, mapred-site.xml' on {class booksearch_mapreduce.MongoJob} as job named '<unnamed MongoTool job>'

13/09/13 20:53:24 INFO util.MongoTool: Mapper Class: class booksearch_mapreduce.BookSearchMapper

13/09/13 20:53:24 INFO util.MongoTool: Setting up and running MapReduce job in foreground, will wait for results.  {Verbose? false}

13/09/13 20:53:25 INFO util.MongoSplitter: MongoSplitter calculating splits

13/09/13 20:53:25 INFO util.MongoSplitter: use range queries: false

.. 중략 ...

13/09/13 20:53:41 INFO mapred.JobClient:     Spilled Records=1428

13/09/13 20:53:41 INFO mapred.JobClient:     Map output bytes=14049

13/09/13 20:53:41 INFO mapred.JobClient:     Total committed heap usage (bytes)=269619200

13/09/13 20:53:41 INFO mapred.JobClient:     Combine input records=1299

13/09/13 20:53:41 INFO mapred.JobClient:     SPLIT_RAW_BYTES=195

13/09/13 20:53:41 INFO mapred.JobClient:     Reduce input records=714

13/09/13 20:53:41 INFO mapred.JobClient:     Reduce input groups=714

13/09/13 20:53:41 INFO mapred.JobClient:     Combine output records=714

13/09/13 20:53:41 INFO mapred.JobClient:     Reduce output records=714

13/09/13 20:53:41 INFO mapred.JobClient:     Map output records=1299



MongoDB에서 결과값 확인하기 

  - 브라우져에서 결과값을 확인하고 싶다면 몽고디비에 옵션으로 --rest 를 주면 28017 포트로 RESTful 하게 호출할 수 있다 

 $ ./bin/mongod -dbpath=/Users/dowon/Documents/mongodb/database --rest 

  - 결과 화면 

    결과값은 out 컬렉션에 생성이 된다


<참조>

  - 검색 책 정보 

books.json


  - 이클립스 project workspace

booksearch.tar


  - 하둡기동후 수행하는 쉘 

mongodb.sh


  - 반출한 booksearch mapreducer jar 파일 

booksearch_mapreduce-0.0.1-SNAPSHOT.jar






posted by Peter Note
2013. 9. 12. 20:45 Big Data

몽고디비를 하둡의 Input/Output의 Store를 사용하면 어떨까? 어차피 몽고디비는 Document Store 이며 Scale-Out을 위한 무한한 Sharding(RDB의 Partitioning) 환경을 제공하니 충분히 사용할 수 있을 것이다. Store에 저장된 데이터의 Batch Processing Engine으로 하둡을 사용하면 될 일이다. 




Mongo-Hadoop Connector 소개

  - Hadoop을 통하면 Mongo안에 있는 데이터를 전체 코어를 사용하면서 병렬로 처리할 수 있다 

  - 하둡포멧으로 Mongo를 BSON format을 파일로 저장하거나, MongoDB에 바로 저장할 수 있는 Java API존재

  - Pig + Hive를 사용할 수 있음 

  - AWS의 Amazon Elastic MapReduce 사용



Batch Processing Model 종류
  - 사실 MongoDB에서도 Aggregation Framework을 제공하여 MapReduce프로그래밍을 JavaScript로 개발 적용할 수 있다다
  - 시간단위 Batch Processing은 요렇게도 사용할 수 있겠다 


  - 데이터가 정말 Big 이면 하둡을 이용하여 Batch Processing을 해야겠다. 여기서 몽고디비를 "Raw Data Store" 와 "Result Data Store"로 사용한다 


  - MongoDB & Hadoop : Batch Processing Model 전체 내역을 보자 



<참조>

  - 결국 처리된 데이터는 표현되어야 한다 : Data Visualization Resources

  - MongoDB 넌 뭐니? NoSQL에 대한 이야기 (조대협)

posted by Peter Note
2013. 9. 11. 19:20 Big Data

Mapper & Reducer를 .jar로 배포하고 직접 하둡명령으로 수행하는 방법에 대하여 알아보자 



MapReduce 프로그램 

  - Writable Interface는 Value에서 사용한다

  - Mapper 인터페이스 

    Mapper<K1, V1, K2, V2>의 형태 : key는 WritableComparable를 구현해야 하며, value는 Writable를 구현해야 함.


  - Reducer 인터페이스 

    reducer는 여러가지 매퍼로부터 생성된 결과를 받고, key/value 쌍의 key에 대해 데이터를 정렬하고 동일한 key에 대한 모든 값을 그룹핑 함.


  - 이전의 WordCount에 대한 것을 직접 코딩하였는데, 맵퍼-TokenCountMapper-, 리듀서-LongSumReducer-를 사용해서 동일하게 만들 수 있다



hadoop 명령어로 .jar 직접 수행하기

  - pom.xml 에 MapReduce Jar파일을 만들어 특정위치로 복사하는 플러그인 설정을 넣는다 

<build>

  <plugins>

    <plugin>

      <artifactId>maven-antrun-plugin</artifactId>

      <configuration>

        <tasks>

          <copy file="target/${project.artifactId}-${project.version}.jar"

            tofile="/Users/dowon/Documents/hadoop-jobs/${project.artifactId}-${project.version}.jar" />

        </tasks>

      </configuration>

      <executions>

        <execution>

          <phase>install</phase>

          <goals>

            <goal>run</goal>

          </goals>

        </execution>

      </executions>

    </plugin>

  </plugins>

</build>

  - 기존 WordCount에 대한 WordCount3 복사본을 만들고 TokenCountMapper와 LongSumReducer로 변형한다

    즉 직접 코딩하지 말고 하둡에서 제공하는 클래스를 사용한다 

import java.io.IOException;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapred.FileInputFormat;

import org.apache.hadoop.mapred.FileOutputFormat;

import org.apache.hadoop.mapred.JobClient;

import org.apache.hadoop.mapred.JobConf;

import org.apache.hadoop.mapred.lib.LongSumReducer;

import org.apache.hadoop.mapred.lib.TokenCountMapper;


public class WordCount3 {


  public static void main(String[] args) throws IOException {

    // 1. configuration Mapper & Reducer of Hadoop

    JobConf conf = new JobConf(WordCount3.class);

    conf.setJobName("wordcount3");

    

    // 2. final output key type & value type

    conf.setOutputKeyClass(Text.class);

    conf.setOutputValueClass(LongWritable.class);

    

    // 3. in/output format 

    conf.setMapperClass(TokenCountMapper.class);

    conf.setCombinerClass(LongSumReducer.class);

    conf.setReducerClass(LongSumReducer.class);

    

    // 4. set the path of file for read files

    //    input path : args[0]

    //    output path : args[1]

    FileInputFormat.setInputPaths(conf, new Path(args[0]));

    FileOutputFormat.setOutputPath(conf, new Path(args[1]));

    

    // 5. run job

    JobClient client = new JobClient();

    client.setConf(conf);

    JobClient.runJob(conf);

  }

}

  - eclipse의 프로젝트를 선택하고 "Run As"에서 "Maven build..."를 선택하여 "clean install" 입력하고 "run"버튼을 클릭한다 


  - 결과로 배포가 성공으로 나오면 된다  : /Users/dowon/Documents/hadoop-jobs 디렉토리에 *.jar 파일 생성을 확인한다 


  - 하둡 데몬들을 수행하기 전 NameNode에 대해서 format을 하고 수행한다 

// name node 포멧

hadoop namenode -format


// .bash_profile 에 PATH 설정

set -o vi

export JAVA_HOME=/Library/Java/Home

export H_HOME=~/Documents/hadoop-1.2.1

export PATH=.:$PATH:$JAVA_HOME/bin:$H_HOME/bin:/usr/bin

alias ll='ls -alrt'

alias cdh='cd $H_HOME'


// 하둡 데몬 수행

// 50030 : job-tracker 접속 포트

// 50070 : NameNode 접속 포트

$ start-all.sh 

  - input 의 위치를 지정하여 준다 (만일, NameNode를 포멧하였다면)

// 위치가 하기와 같다면 

$ pwd

/Users/dowon/Documents/input

$ ls

total 16

-rw-r--r--   1 dowon  staff   22  9 11 19:26 file01

-rw-r--r--   1 dowon  staff   21  9 11 19:26 file02


// input 을 HDFS에 만든다 

$ hadoop fs -put . input


1) http://localhost:50070/으로 접속하여 "Browser the filesystem"을 클릭하면 볼 수 있다 

2) /user/dowon/input 경로로 만들어 졌음을 알 수 있다 

  - hadoop 명령어로 생성된 jar 파일을 수행해 보자 

// 경로가 다음과 같고, WordCount3.class가 들어있는 .jar 파일이 존재한다 

$ pwd

/Users/dowon/Documents/hadoop-jobs

$ ls

-rw-r--r--   1 dowon  staff  5391  9 11 19:45 MapReduce-1.0.0-SNAPSHOT.jar


// 명령어 수행 

$ hadoop jar *.jar WordCount3 /user/dowon/input /user/dowon/output3


1) 네임노드에 접속해서 /user/dowon에 들어가 보면 "output3"이 생성된 것을 볼 수있다

2) output3으로 들어가면 결과값을 지니 파일이 존재한다 

3) 만일 명령을 재수행하고 싶다면 output3 디렉토리리 삭제해야 한다 

    $ hadoop fs -rmr /user/dowon/output3


eclipse에서 수행하지 않고 반출된 .jar 파일을 가지고 hadoop명령으로 수행하는 방법을 알아보았다. 


<참조>

  없음 


posted by Peter Note
2013. 9. 9. 21:52 Big Data

Eclipse하에서 하둡코딩시 Maven을 기본으로 하여 외부 라이브러리 의존성을 관리하자.



Hadoop 역할

  - 분산된 파일을 처리하는 순서

   > input HDFS으로 들어오기

   > Job 수행 : 읽어서 로직처리

   > 결과를 파일 또는 DB에 넣는다 

  - Tera 단위의 데이터가 이미 HDFS에 있을 경우 해당 데이터를 처리하는데 하둡의 쓰임새가 있다

  - HDFS와 MapReduce의 이해 



Maven Project 만들기

  - Maven Project 선택하고 "Create a simple project" 선택한다  


  - 메이븐의 GroupID와 ArtifactID 설정한다 


  - 최종 생성 내역 

    MapReduce 프로그래밍을 여기서 하게 되고, 단위 테스트 프로그래밍도 할 수 있다


  - pom.xml  에 hadoop 관련 라이브러리 의존관계를 넣는다.  (파란색이 추가부분)

    추가하고 저장을 하면 자동으로 의존관계 라이브러리를 다운로드 받는다

    이클립트 좌측 "Project Explorer"의 "Maven Dependencies"에서 관련 파일들이 추가된 것을 확인할 수 있다 

// pom.xml 내역

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>kr.mobiconsoft.hadoop</groupId>

  <artifactId>MapReduce</artifactId>

  <version>1.0.0-SNAPSHOT</version>

  

  <dependencies>

  <dependency>

  <groupId>org.apache.hadoop</groupId>

  <artifactId>hadoop-core</artifactId>

  <version>1.1.2</version>

         </dependency>

  </dependencies>

  

</project>


// 결과 



Word Counting MapReduce 구현하기 

  - file 2개 생성하고 유사한 word를 넣는다 

// file01

hello world bye world


// file02

hi world hello dowon

  - Mapper Class를 생성 


import java.io.IOException;

import java.util.StringTokenizer;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapred.MapReduceBase;

import org.apache.hadoop.mapred.Mapper;

import org.apache.hadoop.mapred.OutputCollector;

import org.apache.hadoop.mapred.Reporter;


/**

 * K1 : read key type

 * V2 : read value type

 * K2 : write key type

 * V2 : write value type

 */

//public class WordCountMapper implements Mapper<K1, V1, K2, V2> {

public class WordCountMapper extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable> {


// map 결과는 reducer로 자동으로 던져진다 

public void map(LongWritable key, Text value,

OutputCollector<Text, IntWritable> output, Reporter reporter)

throws IOException {

// TODO Auto-generated method stub

String line = value.toString();

StringTokenizer tokenizer = new StringTokenizer(line);

while(tokenizer.hasMoreTokens()) {

Text outputKey = new Text(tokenizer.nextToken());

// Hadoop 에서 wrapping한 Integer 타입의 객체를 넣어줌 

// param1: outputKey, param2: outputValue

output.collect(outputKey, new IntWritable(1));

}

}

}


  - Reducer Class 생성


import java.io.IOException;

import java.util.Iterator;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapred.MapReduceBase;

import org.apache.hadoop.mapred.OutputCollector;

import org.apache.hadoop.mapred.Reducer;

import org.apache.hadoop.mapred.Reporter;


/**

 * K1 : Mapper의 K2 와 동일

 * V1 : Mapper의 V2 와 동일 

 */

public class WordCountReducer extends MapReduceBase 

 implements Reducer<Text, IntWritable, Text, IntWritable> {


/**

* V1 에서 values는 Iterator이다. 실제 같은 단어가 여러개 일 경우 

*/

public void reduce(Text key, Iterator<IntWritable> values,

OutputCollector<Text, IntWritable> output, Reporter reporter)

throws IOException {

// TODO Auto-generated method stub

int sum = 0;

while(values.hasNext()) {

sum += values.next().get(); // get Integer value

}

output.collect(key, new IntWritable(sum));

}

}


  - Job Tracker를 생성 : 하단 main 선택한다 


import java.io.IOException;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapred.FileInputFormat;

import org.apache.hadoop.mapred.FileOutputFormat;

import org.apache.hadoop.mapred.JobClient;

import org.apache.hadoop.mapred.JobConf;

import org.apache.hadoop.mapred.TextInputFormat;

import org.apache.hadoop.mapred.TextOutputFormat;



public class WordCount {


public static void main(String[] args) throws IOException {

// 1. configuration Mapper & Reducer of Hadoop

JobConf conf = new JobConf();

conf.setJobName("wordcount");

conf.setMapperClass(WordCountMapper.class);

conf.setReducerClass(WordCountReducer.class);

// 2. final output key type & value type

conf.setOutputKeyClass(Text.class);

conf.setOutputValueClass(IntWritable.class);

// 3. in/output format 

conf.setInputFormat(TextInputFormat.class);

conf.setOutputFormat(TextOutputFormat.class);

// 4. set the path of file for read files

//    input path : args[0]

//    output path : args[1]

FileInputFormat.setInputPaths(conf, new Path(args[0]));

FileOutputFormat.setOutputPath(conf, new Path(args[1]));

// 5. run job

JobClient.runJob(conf);

}


}

  

  - 최종 모습 


  - eclipse 설정하기 

    main펑션이 있는 WordCount를 수행할 때 input path와 output path를 지정하여 준다 

    이때 output path의 디렉토리는 생성되어 있지 않아야 한다 (target/hadoop-result)

    하단 우측 "run" 클릭 


  - 결과값 

 

  - 결국 이런 처리과정을 수행하게 된다 


  - Mapper와 Reducer 역할 

    Mapper : 소스를 쪼개어 key:value 맵을 여러개 만들고

    Reducer : 여러 Map 값을 하나의 결과값으로 만들어 준다 



단위 테스트 해보기 

  - pom.xml에 mrunit 추가 

 <dependencies>

  <dependency>

  <groupId>org.apache.hadoop</groupId>

  <artifactId>hadoop-core</artifactId>

  <version>1.1.2</version>

  </dependency>

 

  <dependency>

  <groupId>org.apache.mrunit</groupId>

  <artifactId>mrunit</artifactId>

  <version>0.8.0-incubating</version>

  <scope>test</scope>

  </dependency>

  </dependencies>


  - Mapper Test 클래스 생성

    Run As... 에서 JUnit으로 테스트 하여 초록색-성공인지 체크한다 

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mrunit.MapDriver;

import org.junit.Test;


/**

 * 테스트를 통하여 Mapper와 Reducer를 테스트에서 수행하여 검증 할 수 있다 

 * @author dowon

 *

 */

public class WordCountMapperTest {


  @Test

  public void testMap() {

    // 1. 설

    Text value = new Text("Hello World Bye World");

    

    MapDriver<LongWritable, Text, Text, IntWritable> mapDriver = new MapDriver();

    mapDriver.withMapper(new WordCountMapper());

    mapDriver.withInputValue(value);

    

    // 2. 검정 및 실행 

    // 순서를 정확히 해야 에러없이 수행된다. 빼먹어도 에러가 난다 

    mapDriver.withOutput(new Text("Hello"), new IntWritable(1));

    mapDriver.withOutput(new Text("World"), new IntWritable(1));

    mapDriver.withOutput(new Text("Bye"), new IntWritable(1));

    mapDriver.withOutput(new Text("World"), new IntWritable(1));

    mapDriver.runTest();

  }

}


  - Reducer Test 클래스 생성

    Run As... 에서 JUnit으로 테스트 하여 초록색-성공인지 체크한다 

import java.util.Arrays;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mrunit.ReduceDriver;

import org.junit.Test;


public class WordCountReducerTest {

  

  @Test

  public void testReducer() {

      // 1. 설정

    ReduceDriver<Text, IntWritable, Text, IntWritable> reduceDriver = new ReduceDriver();

    reduceDriver.withReducer(new WordCountReducer());

    reduceDriver.withInputKey(new Text("World"));

    reduceDriver.withInputValues(Arrays.asList(new IntWritable(1), new IntWritable(1)));

    

    // 2. 검증 및 실행 

    reduceDriver.withOutput(new Text("World"), new IntWritable(2));

    reduceDriver.runTest();

  }

}



<참조>

  - Maven 기초 사용법

posted by Peter Note
2013. 9. 9. 21:34 Big Data

왜 빅데이터가 이슈가 되고 있을까?  HW와 SW의 가격은 저렴해지고, 표준은 평준화 되고 접근이 수워지고 있다. 그러나 데이터는 복제나 공유가 되지 않고 자사의 데이터가 돈이 되는 시대가 왔다. 그런 의미에서 하둡은 빅데이터를 처리하는 분야의 SW이다.



하둡 개념

  - Input: 분석할 데이터, Output: 결과값 

  - MasterNode: HDFS-분산파일위치 정보지님 (NameNode) 

  - SlaveNode: 분산된 실 데이터를 저장 (DataNode)

  - MapReduce/HDFS Layer 영역으로 나뉨 


  - 역할에 대한 이해하기 


  - JobTracker : Map -> Reduce 할때 Shuffle+Sort의 로직처리가 성능을 좌우한다. 

     즉, Map출력결과 (Mapper) -> Suffle+Sort -> Sorting된 Reduce 입력 (Reducer)

     Mapper/Reducer 프로그래밍도 분산된 것이다 


  - 개념이해하기 



설치하기 

  - http://apache.tt.co.kr/hadoop/common/hadoop-1.2.1/ 에서 hadoop-1.2.1-bin.tar.gz 파일을 다운로드 받는다 

  - 기본 환경은 Mac OS를 사용한다 

  - .bash_profile 안에 JAVA_HOME을 설정한다 : Java버전은 반드시 1.6 이상이어야 한다

$ cat .bash_profile

alias ll='ls -alrt'

set -o vi

export JAVA_HOME=/Library/Java/Home

  - 압축을 푼다. 설치 끝



Standalone 사용하기 

  - Document 메뉴에서 1.2.1로 이동하여 "Single Node Setup"을 클릭 : http://hadoop.apache.org/docs/r1.2.1/single_node_setup.html

  - 간단한 수행

// 폴더를 하나 만들고 xml 환경파일을 복사한다 

$ mkdir input 

$ cp conf/*.xml input 


// 하기 명령을 수행한다 

// input 읽을 꺼리를 주고 결과값을 output에 담아라  

$ bin/hadoop jar hadoop-examples-*.jar grep input output 'dfs[a-z.]+' 


// output  폴더가 자동 생성되고 cat 하였을 때 존재하는 파일의 내역이 하기와 같이 보이면 성공!

$ cat output/*

1 dfsadmin

  - 수행은 어떤 의미일까?

    + NameNode : 하둡전체 관리 =  JobTracker + DataNode

    + JobTracker : 처리역할

    + DataNode : HDFS 위치 (분산파일을 하나의 파일 인것처럼 사용하게 해줄 수 있는것)

  - 하둡은 특정 input이 있고 처리하고 output 결과가 나온다. 현재 예는 로컬 input에 있는 것을 읽고 로컬 output에 생성하였다. 

    그러나 실제에서는 분산으로 처리하므로 local이 아닐 것이다. 


Hadoop  종류

  - 종류

 Local (Standalone) Mode [로컬(독립)모드]

  하둡의 기본모드(아무런 환경설정을 하지 않음): 로컬 머신에서만 실행

  다른 노드와 통신할 필요가 없기 때문에 HDFS를 사용하지 않으며 다른 데몬들도 실행시키지 않음

  독립적으로 MapReduce 프로그램의 로직을 개발하고 디버깅하는데 유용함


 Pseudo-Distributed Mode [가상분산 모드]

  한대의 컴퓨터로 클러스터를 구성하고, 모든 데몬을 실행함.

  독립실행(standalone) 모드 기능 보완

  – 메모리 사용 정도, HDFS 입출력 관련 문제, 다른 데몬 과의 상호작용에서 발생하는 일을 검사


 Fully-Distributed Mode [완전분산 모드]

  분산 저장과 분산 연산의 모든 기능이 갖추어진 클러스터를 구성함

  master - 클러스터의 master 노드로, NameNode와 JobTracker 데몬을 제공

  backup - SNN(Secondary NameNode 데몬)을 제공하는 서버

  slaves - DataNode와 TaskTracker 데몬을 실행하는 slaves 들 



가상의 Standalone Hadoop 실행하기

  - 3가지 기본 환경을 추가한다

  - conf/core-site.xml

<?xml version="1.0"?>

<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>


<!-- Put site-specific property overrides in this file. -->


<configuration>

   <property>

        <name>fs.default.name</name>

        <value>hdfs://localhost:9000</value>

   </property>

</configuration>

  - conf/hdfs-site.xml

<?xml version="1.0"?>

<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>


<!-- Put site-specific property overrides in this file. -->


<configuration>

     <property>

         <name>dfs.replication</name>

         <value>1</value>

     </property>

</configuration>

  - conf/mared-site.xml

<?xml version="1.0"?>

<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>


<!-- Put site-specific property overrides in this file. -->


<configuration>

     <property>

         <name>mapred.job.tracker</name>

         <value>localhost:9001</value>

     </property>

</configuration>


  - NameNode 초기화

    NameNode 가 있는 곳에서 수행한다 

$ bin/hadoop namenode -format

13/09/09 20:51:59 INFO namenode.NameNode: STARTUP_MSG: 

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

STARTUP_MSG: Starting NameNode

STARTUP_MSG:   host = KOSTA17ui-iMac.local/192.168.0.15

STARTUP_MSG:   args = [-format]

STARTUP_MSG:   version = 1.2.1

STARTUP_MSG:   build = https://svn.apache.org/repos/asf/hadoop/common/branches/branch-1.2 -r 1503152; compiled by 'mattf' on Mon Jul 22 15:23:09 PDT 2013

STARTUP_MSG:   java = 1.6.0_45

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

13/09/09 20:52:00 INFO util.GSet: Computing capacity for map BlocksMap

... 중략 ...

13/09/09 20:52:00 INFO common.Storage: Image file /tmp/hadoop-dowon/dfs/name/current/fsimage of size 111 bytes saved in 0 seconds.

13/09/09 20:52:00 INFO common.Storage: Storage directory /tmp/hadoop-dowon/dfs/name has been successfully formatted.

  - NameNode, JobTracker, DataNode를 한번에 띄우기 

$ bin/start-all.sh

starting namenode, logging to /Users/dowon/Documents/hadoop-1.2.1/libexec/../logs/hadoop-dowon-namenode-KOSTA17ui-iMac.local.out

2013-09-09 20:53:36.430 java[7632:1603] Unable to load realm info from SCDynamicStore


// 수행후 브라우져에서 50030, 50070 포트 호출

http://localhost:50030/dfshealth.jsp : JobTracker


http://localhost:50070/dfshealth.jsp : NameNode

  - 가상 HDFS 방식으로 수행해 보자 

    결국 결과값을 HDFS에 넣어주는 것이다 

// NameNode의 HDFS에 conf/* 모든 파일을 input 디렉토리명으로 생성하여 복사한다  

$ bin/hadoop fs -put conf input

2013-09-09 20:58:13.847 java[7966:1603] Unable to load realm info from SCDynamicStore


// 명령을 수행하면 JobTracker가 수행되고 HDFS에 output 디렉토리에 결과값이 생성된다 

// 결과값은 JobTracker가 처리하여 생성된 것이다 

$ bin/hadoop jar hadoop-examples-*.jar grep input output 'dfs[a-z.]+'

2013-09-09 21:01:57.770 java[8017:1603] Unable to load realm info from SCDynamicStore

13/09/09 21:01:58 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

13/09/09 21:01:58 WARN snappy.LoadSnappy: Snappy native library not loaded

13/09/09 21:01:58 INFO mapred.FileInputFormat: Total input paths to process : 17

13/09/09 21:01:58 INFO mapred.JobClient: Running job: job_201309092053_0001

13/09/09 21:01:59 INFO mapred.JobClient:  map 0% reduce 0%

13/09/09 21:02:03 INFO mapred.JobClient:  map 11% reduce 0%

13/09/09 21:02:05 INFO mapred.JobClient:  map 23% reduce 0%

.. 중략..


// NameNode 결과 확인

// http://localhost:50070/ 에서 "Browser the filesystem" 을 클릭한다

// http://localhost:50075/browseDirectory.jsp?dir=%2Fuser%2Fdowon&namenodeInfoPort=50070

해당 브라우져 내역에 신규생성된 "user"밑의 "dowon"밑의 "input" 과 "output"이 보인다 (dowon은 계정명)

Name
Type
Size
Replication
Block Size
Modification Time
Permission
Owner
Group
input
dir



2013-09-09 20:58
rwxr-xr-x
dowon
supergroup
output
dir



2013-09-09 21:02
rwxr-xr-x
dowon
supergroup


// output에 결과값 : part-00000 에 결과내역이 write 되어 있다 

Name
Type
Size
Replication
Block Size
Modification Time
Permission
Owner
Group
_SUCCESS
file
0 KB
1
64 MB
2013-09-09 21:02
rw-r--r--
dowon
supergroup
_logs
dir



2013-09-09 21:02
rwxr-xr-x
dowon
supergroup
part-00000
file
0.05 KB
1
64 MB
2013-09-09 21:02
rw-r--r--
dowon
supergrou


// JobTracker 처리현황 확인 

// http://localhost:50030/jobtracker.jsp


다음에는 eclipse에서 하둡 프로그래밍을 해보자 


<참조>

  - Hadoop 튜토리얼

posted by Peter Note