소셜네트워크가 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)
의사결정의 반대는 지지부진 의사결정을 미루는 것이다. 우유부단함(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단계로 다시 돌아가라. 그간 실행하면서 터득한 경험을 활용해 더 다듬고, 더 나은 의사결정을 내려라.
플랫폼의 핵심 기능중 하나가 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 복잡함등의 제거, 웹뿐만 아니라 애플리케이션도 인증 가능
디자인이라고 말하면 일반 개발자들은 포토샵과 일러스트레이터를 다루는 그래픽 디자이너를 생각하게 된다. 하지만 특정 도메인을 표현하는 제품 입장에서 디자이너와 함께 일하기 위하여 일반적으로 기대하는 수준은 사뭇 다르다. 나 또한 B2B 솔루션을 3개 정도 런칭해 보았지만 일반 그래픽만 다루던 디자이너와 일했을 때 도메인 컨텍스트를 이해시키고 설득하는데 많은 시간과 노력 그리고 스트레스를 어떻게 받게 되었는지 잘 안다. 어떻게 하면 훌륭한 디자인을 할 수 있는지? 자신의 Golden Path는 어떻게 만들 수 있는지 알아보자
- 정보를 시각화 하는 인포그래픽에 대한 접근법은 어떻게 될까? 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) == 출시하기
프러덕트 디자인을 한다는 것은 이제 예전의 단순 그래픽 디자인에서 -> 결과물(프러덕트)의 전반적인 미션과 비젼을 이해하고 -> 어떻게 사람과 컴퓨터가 상호작용해야 하는지 이해한 후 사용자 경험을 극대화 하는 작업으로 변하였다. 최근은 모바일기기의 확대로 그 경험의 비쥬얼에 플랫디자인이 대세를 이루고 있으며 이는 사용에게 궁극적으로 감정적 만족감과 행복감을 주는 방향으로 가고 있다.
차트는 많은 정보를 내포한다. 사람들끼리 차트를 공유하고 싶을 경우 보통은 엑셀에서 작업을 하고, 보고서를 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에서 차트를 공유한다
자바스크립트의 상속과 성능향상을 위하여서는 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객체로 정의
+ CSS : bootstrap -> font-awesome -> bootflat 순으로 설정
+ JS : jquery -> bootstrap 순으로 설정
<!DOCTYPE html><html><head><title>Bootflat Template</title><metaname="viewport"content="width=device-width, initial-scale=1.0"><!-- Bootstrap --><linkhref="bootstrap/bootstrap.min.css"rel="stylesheet"media="screen"><linkhref="css/font-awesome.min.css"rel="stylesheet"media="screen"><linkhref="css/bootflat.css"rel="stylesheet"media="screen"><linkhref="css/bootflat-extensions.css"rel="stylesheet"media="screen"><linkhref="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>
AngularJS는 구글이 후원하고 있고 2013년 구글I/O에서도 2개의 세션에서 소개가 될 정도로 관심이 높은 차기 웹 클라이언트단의 애플리케이션 개발을 위한 프레임워크이다. MVC -Model View Controller - 기반으로 역할을 나누고, SoC (separate of concer)이라는 관심의 분리를 위하여 모듈에 대한 DI (Dependency Injection)을 사용한다.
<!-- 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>
- 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>
SPA 방식의 개발을 위해서는 Client에 MV* Framework을 선택해야 한다. 초창기는 Backbone.js(이하, 백본)를 많이 사용하였고, 구현체로 Trello가 있으니 해당 서비스의 아키텍쳐를 참조해 보자. 그리고 SKT에서 코너스톤이라는 이름의 Framework도 백본을 사용하여 "웹앱"을 만들 수 있도록 안드로이드/iOS용 런타임 환경도 제공한다. 백본은 가벼우면서 광범위하게 많이 사용을 하고 있지만 개발 복잡성 비교에서 중간정도에 위치한다.
브라우져에서 동작하는 애플리케이션의 템플릿이 .html 이면서 <chart ../> 와 같이 사용자 정의 html tag를 사용하여 View단을 좀 더 단순화 및 컴포넌트화 할 수 있는 AngularJS를 사용하기로 한다. AngularJS에는 여러 장점이 있지만 "웹 애플리케이션을 견고하게 만드는 방법" 블로깅을 통해 백본대신 왜 AngularJS를 선택했는지 갈음한다. AngularJs 프레임워크 기반의 SPA 개발을 위하여 쉽게 접근하는 방법은 도구를 통하여 하는 것인데, 오스마니님이 개발한 Yeoman을 통하여 AngularJS기반 프로젝트를 쉽게 구성할 수 있다.
End User가 모니터링 중인 미들웨어(JBoss)에 대한 Resource Usage/TPS/Response Time등의 분기별 통계 정보를 요구한다. 3개월 또는 6개월 단위 raw data를 그냥 엑셀에 담아서 전달하는 것은 의미가 없고, 엑셀의 차트를 그려도 일정한 패턴을 찾기 어렵지 않을까 생각되어 이동평균선 차트를 만들어서 전달해 주기로 하였다. Raw Data는 엑셀파일로 export가 되므로 .csv 확장자로 파일을 업로드하면 바로 이동평균 차트가 나오는 서비스를 만들어 널리 편히 쑤메 그 패턴(우상향, 우하향)을 인지하여 미들웨어가 정상적으로 운영되고 있는지 장기적인 관점(Capcity Plan)에서 판단근거를 세우고자 한다
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 제공
// 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에서 구현하면 된다
/*!
* 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().
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;
}));
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 하게 호출할 수 있다
몽고디비를 하둡의 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존재