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

Publication

Category

Recent Post

2014. 6. 10. 16:34 My Services/PlayHub

지인이 프론트앤드 개발자를 구인하고 있어서 소개드립니다. 자세한 사항은 하기 내용 참조하세요




로앤컴퍼니와 함께 대한민국 법률시장의 대중화와 선진화를 만들어 갈 동료를 찾습니다. 함께 갑시다!


*채용안내: http://lawcompany.co.kr/jobs

*소개영상: http://youtu.be/5br8hf1ukRQ (by 무한도전 정준하)


<이런 분들과 함께 하고 싶습니다!>

- 법률과 IT의 융합을 통해 모든 국민에게 기여하는 서비스를 만들고 싶은 당신
- 미개척된 새로운 시장이 자신의 노력으로 변화하는 것을 경험하고 싶은 당신
- 서로를 자극하는 역량있는 사람들과 함께 재밌고 열정적으로 일을 하고 싶은 당신


<채용절차>

- 실력과 잠재력 중심 선발 (나이, 성별, 학력 무관)
- 이력서, 자기소개서, 포트폴리오 제출 (형태 제한 없음)
recruit@lawcompany.co.kr로 접수 후 1주일 이내에 회신해 드립니다.
- 2번의 면접을 통해 실력, 잠재력, 커뮤니케이션 능력, 팀과의 조화 여부 등을 확인합니다.


<참고사항>

- 출근은 오전 10시 (서초역 1분, 교대역 3분 거리)
- 당연하게도 월급은 섭섭지 않게, 제 때 드립니다.
- 서울에 연고가 없는 경우, 거주비 일부를 지원합니다.
- 고기를 자주 먹습니다. 야식은 더 자주 먹습니다.
- 치과 상담, 스케일링 치료 무료
- 가로수길 M모 카페, 모든 음료 하루 한잔 무료
- 변호사와 법률 상담 무료 및 법률 문제 해결 적극 지원





posted by 윤영식
2013. 11. 9. 07:08 My Services/PlayHub

PlayHub는 다양한 협업도구를 묶어서 부족한 2%를 채워주기 위한 서비스이다. 단순 매쉬업을 넘어서 정보를 자체 저장하고 확장할 수 있는 기능을 가지고 있다. 일반 협업도구에 어떤 불편한 점을 개선하려는 것일까?



PlayHub Flow

  - 로그인 하기 

    + Facebook 과 Google+ 를 통하여 로그인을 한다. 

    + OAuth를 통하여 얻은 정보를 사용자 정보로 사용한다 

    



  - 최초 로그인을 했을 때의 빈 화면

    + 좌측의 메뉴가 최초에는 열려있게 만드는 것이 좋겠다. 최대한 클릭수를 줄이는 경험을 주어야 한다 (AirBnB 참조)

    


  - 상단 좌/우 메뉴 버튼

    + 메뉴는 Side Slider Menu이다 (Sidr 또는 jQueryMobile 참조)

    + 좌측 메뉴 : 페이스북과 유사하다. Team:Project = 1:1 기준이다. 1개팀은 1개프로젝트만 가능하다

    + 우측 메뉴 : 등록한 친구 목록과 친구들과 채팅한 내역들이 나온다 

    


  - 팀 협업도구 연결하기 

    + 협업을 할 프로젝트 협업도구의 연결(로그인)과 팀명칭을 등록한다  

    


  - 팀 활동 내역

    + 등록한 팀을 선택하면 협업도구에 등록된 항목이 보인다

    + 좌측은 나의 Task가 우측은 팀원들의 Task 카드가 보인다 

    + 카드를 클릭하면 상세내역을 볼 수 있다   

    


  - 카드 상세내역 

    + Google Doc, DropBox, Trello 별로 보여지는 UI가 틀리게 나온다 

    + Google Doc은 기획자가 DropBox는 디자이너가 Trello는 개발자가 주로 사용함을 전제로 한다 

    + 카드형태별 하단에는 코멘트(카드에 대한 요청사항)를 달 수 있고 답변 할 수 있다 

    


  - 채팅하기 

    + 선택한 멤버들과 채팅을 한다 

    + 채팅 멤버추가는 팀단위 멤버와 상관없이 사용자가 선택한 사람과 채팅하는 것이다 

    


  - 메세지 확인하기

    + 자신의 카드에 코멘드(요청사항)를 달아놓은 것을 메세지에서 본다 

    + 자신에게 채팅신청이 왔으나 놓친 채팅 목록을 본다 

    


본 디자인은 개인적인 아이데이션이고 팀과 함께 더 좋은 방안과 UX/UI를 협의하여 완성해 갈 예정이다. 사람들과 대화를 통하여 정반합의 모델을 만들어 가는 재미가 쏠쏠하다



<참조>

  - mash up의 유래 

  - Good UI 전략

  - UX Design for HTML/CSS Mobile WebApp

posted by 윤영식
2013. 11. 9. 07:08 My Services/PlayHub

Angular 기반 SPA를 위한 Push 환경은 Node 기반의 Socket.io를 사용한다. Angular.js의 장점중 하나가 DI(Dependency Injection)이고 이를 Node에서 구현한 프레임워크인 express-train (축약, train)을 사용한다. train은 특정 폴더에 .js 파일을 놓으면 자동으로 DI되어 사용을 할 수 있게 해준다. 일정한 규약만을 지키면 개발 코드를 모듈화 하여 관리할 수 있고 테스트 할 수 있다. train은 express에 DI 기능을 접목해 놓은 것으로 보면 된다





1. Express-Train 설치하기 

  - 설치 : npm install -g express-train 

  - 새로운 Node 환경 프로젝트 생성

// train이 사용하는 기본 템플릿을 기반으로 자동으로 palette.fronend.node 디렉토리에 기본 폴더와 파일을 생성해 준다 

$ train new palette.frontend.node 

// 수행 http://localhost:4000 호출 

$ train run 

  - 디렉토리 구조 

    + app : auto-injected 로 표현된 것 .js 파일을 해당 디렉토리에 놓으면 다른 곳에서 파일 명칭으로 불러와서 (function parameter DI 방식) 사용을 할 수 있다 

    + bin : 이것은 node_modules/express-train 밑에 존재한다 

    + test : Mocha를 이용한다

    + public : Angular.js 기반으로 작성한 모든 static 파일들이 이곳에 놓일 것이다. 엄밀히 따지면 Distributed된 Frontend파일이 놓인다

app

  /controllers      -- application controllers (**autoinjected**)

  /lib                 -- application specific modules (**autoinjected**)

  /middleware    -- application middleware (**autoinjected**)

  /models          -- application models (**autoinjected**)

  /public            -- static content (html, js, css, etc) : SPA모든 파일을 해당 폴더 밑에 복사할 것이다  

  /views            -- view templates (loaded into express' view engine) : 서버단의 파일 jade, ejs, hbs 같은 서버 template 파일

  index.js           -- index file exports the express train application : train run 시에 수행되는 파일 based on Node.js


bin                   -- executable scripts : 

doc                  -- documentation

config              -- environmental configuration files : train 환경 설정 파일 

test                 -- tests : 서버 테스트 파일

package.json    -- npm package.json (needs to have express-train as a dependency)

  - train의 환경파일 : development와 production 환경파일을 구분함 

{

  "cookie_secret": "boggle at the situation",

  "session":{

    "key": "boggle at the situation",

    "secret": "boggle at the situation"

  },

  "client_port": 3000,

  "request_timeout": 1000,

  // RESTful 호출 접두사 Restanuglar 참조 

  "restful_api_version": "/api/v1",

  "server_name": "first",

  // REDIS 연결 Client Library 환경 설정

  "data_broker": {

    "port" : 6379,

    "host" : "localhost",

    "auth" : ""

  },

  // MongoDB 환경 설정

  "mongodb":{

    "uri": "mongodb://localhost/playhub_palette"

  },

  "playhub_channel_file": "channels.json",

  // Winston 에러 로그가 쌓일 MongoDB 정보 

  "winston_mongo": {

    "level": "error",

    "db": "playhub_palette",

    "host": "localhost",

    "port": 27017,

    "collection": "sd_logs"

  },

  // Winston 로그 파일의 명칭 설정

  "winston_file": {

    "filename": "sd.log",

    "exception": "sd_exception.log",

    "maxSize": 20480,

    "maxFiles": 12,

    "colorize": true

  },

  "character_encoding":"utf-8"

}



2. Express-Train 파일 사용하기 

  - auto-injected 되는 폴더에 모듈로 운용하고 싶은 .js 파일을 놓는다. 주로 MVC 파일과 Utility 또는 Service 파일들이다 

  - auto-inject 환경내역은 node_modules/express-train/lib/app.js 파일안에 이미 설정되어 있다.

// app.js 내역중 일부 

// autoinject라는 옵션을 주면 해당 지정된 폴더의 .js파일을 메모리에 로딩하고 DI를 한다 

// 이때 주의할 것은 상호 참조가 되지 않도록 하기 위한 폴더 전략이 중요하다. 위에서 부터 아래순서로 로딩되어 DI 된다 

var LOCATIONS = createApplication.locations = {

    pkg:{

        path: '../package.json'

    },

    config:{

        path: '../config'

    },

    logs:{

        path: '../logs'

    },

    models: {

        path: 'models',

        autoinject: true,

        aggregateOn: 'models'

    },

    views: {

        path: 'views'

    },

    lib: {

        path: 'lib',

        autoinject: true

    },

    controllers: {

        path: 'controllers',

        autoinject: true

    },

    pub: {

        path: 'public'

    },

    middleware: {

        path: 'middleware',

        autoinject: true

    }, 

   // 만약 app밑에 services폴더를 만든다면! 

    services: {

       path: 'services',

       autoinject: true

    },

}; 


// app.js 에서 express를 생성하고 views와 public 폴더를 강제 설정하고 있다

// _.each에서 autoinject 폴더의 모든 파일을 읽고 DI를 수행해 준다. 이때 DI되는 .js 파일은 Singleton 패턴이다 

// tree.constant로 저장된 객체를 DI 받을 수 있다

function createApplication(dir, locs) {

    catchErrors();


    var app = express(),

        locations = _.defaults((locs || {}), LOCATIONS);

        tree = new nject.Tree();


    app.set('views', path.join(dir, locations.views.path));

    app.set('public', path.join(dir, locations.pub.path));


    var config = loadConfig(path.join(dir, locations.config.path));

    tree.constant('config', config);


    _.each(_.where(locations, {autoinject: true}), function(location) {

        traverseAndRegister(path.join(dir, location.path), tree, location.aggregateOn)

    });


    //allow override of app

    if(!tree.isRegistered('app')) {

        tree.constant('app', app);  

    }


    //log path 하나 추가함 

    var logPath = path.join(dir, locations.logs.path);

    tree.constant('logPath', logPath);


    return tree.resolve();

}


  - 폴더 전략 : app.js에 설정된 순서에-위에서 아래로- 따라 로딩됨, 참조가 많은 것은 주로 service가 되지 않을까 함

    + lib/1level : 가장 기본적인 파일 참조하는 것이 없는 것들 - util, logger, db connection, constant

    + lib/2level : express-train의 파일들  - route, views, middleware 설정

    + lib/3level : 1, 2level의 .js를 사용하는 파일들 

////////

// 형식

module.exports = function(app, config, logger, ...) {

  // app, config, logger 등 파일이름을 대소문자 구분하여 넣으면 자동 DI를 해준다. 객체는 train에서 Singleton으로 관리한다 

}


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

// 예) lib/1level/logger.js

// MongoDB에 winston logger를 이용하여 에러발생 내역을 저장하거나 파일로 떨굼 


var winston = require('winston'),

  path = require('path');


module.exports = function(app, config, logPath) {


  winston.info('[1level: log config] init');

  require('winston-mongodb').MongoDB;


  var dbOpts = {

    level: config.winston_mongo.level,

    db: config.winston_mongo.db,

    collection: config.winston_mongo.collection,

    host: config.winston_mongo.host,

    port: config.winston_mongo.port

  };

  winston.info('[1level: log config] DB option is ' + JSON.stringify(dbOpts) );


  var fileName = path.join(logPath, config.winston_file.filename);

  winston.info('[1level: log config] general log : ' + fileName);

  var exceptionFileName = path.join(logPath, config.winston_file.exception);

  winston.info('[1level: log config] exception log : ' + exceptionFileName);


  var fileOpts = {

    filename: fileName,

    maxsize: config.winston_file.maxSize,

    maxFiles: config.winston_file.maxFiles,

    colorize: config.winston_file.colorize

  }


  var exceptionOpts = {

    filename: exceptionFileName,

    maxsize: config.winston_file.maxSize,

    maxFiles: config.winston_file.maxFiles,

    colorize: config.winston_file.colorize

  }


  var logger = new winston.Logger({

    transports: [

      new winston.transports.Console(),

      new winston.transports.File(fileOpts),

      new winston.transports.MongoDB(dbOpts)

    ],

    exceptionHandlers: [

      new winston.transports.Console(),

      new winston.transports.File(exceptionOpts),

      new winston.transports.MongoDB(dbOpts)

    ]

  });


//  testing

// logger.log(config.winston_mongo.level, '[log config] Test logging module completely');

//  logger.info('==============>> hi info');

//  logger.warn('==============>> hi warn');

//  logger.error('==============>> hi error');  <- MongoDB에도 쌓인다 


  return logger;

}


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

// 예) lib/2level/ 또는 lib/2level에서 logger사용하기 

// lib/2level/views.js 파일에서 

// logger.js 파일에서 파일명칭을 - 대소문자 구분 - Function Parameter DI 해준다 

module.exports = function (applogger) {

  logger.info('[2level: view engine - jade] init');

  app.set('view engine', 'jade');

};




방법-1. Angular 프로젝트 배포후 Express-Train 프로젝트로 통합 

  - 엄밀히 따지면 합치는 시점은 개발이 다 된후 수행한다. 즉, SPA와 Node 파트의 Git 저장소를 틀리게 가져감을 전제로 한다 

    + SPA 저장소 : palette.frontend

    + Node저장소 : palette.frontend.node 

  - 개발이 된후 SPA와 Node 통합은 다음 순서로 하자 

    + Yeoman을 통하여 생성된 Angular 스케폴딩 파일은 별도로 개발한다 -> grunt build 수행 -> dist 폴더에 배포파일이 생성된다 

    + palette.frontend.node/app/public/ 와 palette.frontend.node/app/views 폴더 밑의 모든 파일을 삭제한다

    + palette.frontend.node/app/public/ 밑으로 dist/* 모든 파일을 복사한다 

// 폴더구조 

playhub

  |_ palette.frontend         (별도 git repository)

  |_ palette.frontend.node (별도 git repository)


// Angular 기반 frontend 파일 

playhub$ cd palette.frontend

playhub$ grunt build 


// Express-train에서 사용하지 않을 파일 삭제  

playhub$ cd ../palette.frontend.node/app/public

playhub$ rm -rf *

playhub$ cd ../views

playhub$ rm -rf *


// Angular 기반 SPA frontend파일을 Express-train의 app/public/ 폴더 밑으로 복사 

playhub$ cp -rf ../../../palette.frontend/dist/* ../public/

  - 테스트 하기 

$ cd palette.frontend.node

train run 

// http://localhost:4000/ 으로 호출한다 



방법-2. Express-Train 프로젝트를 Angular 프로젝트로 통합

  - Angular 프로젝트(palette.frontend)의 grunt, bower, yo, karma 기능을 그대로 사용한다 

  - Express-Train 프로젝트(palette.frontend.node)는 express-train기반으로 이미 스케폴딩된 서버단 애플리케이션이다. 

    즉 palette.frontend.node의 디렉토리 구조를 변경시켜보자  

// express-train 프로젝트로 이동하기 

$ cd palette.frontend.node


// express-train의 bin 디렉토리를 palette.frontend.node로 이동하기  

$ cd node_modules/

$ cd express-train/

$ ls -arlt

total 80

drwxr-xr-x   3 nulpulum  staff    102  9 17 17:20 test

-rw-r--r--   1 nulpulum  staff     90  9 17 17:20 index.js

-rw-r--r--   1 nulpulum  staff     82  9 17 17:20 .npmignore

-rw-r--r--   1 nulpulum  staff  13393  9 26 19:13 ReadMe.md

drwxr-xr-x   4 nulpulum  staff    136 11  8 14:14 bin

drwxr-xr-x   5 nulpulum  staff    170 11  8 14:14 boilerplates

-rw-r--r--   1 nulpulum  staff  14976 11  8 14:14 package.json

drwxr-xr-x   4 nulpulum  staff    136 11  8 14:14 lib

drwxr-xr-x  12 nulpulum  staff    408 11  8 14:14 node_modules

drwxr-xr-x  11 nulpulum  staff    374 11  8 14:14 .

drwxr-xr-x  13 nulpulum  staff    442 11  8 14:14 ..

// bin을 palette.frontend.node 루트로 이동시킨다 

$ mv bin ../../


// bin으로 들어가서 train run 명령을 수행 - 내부적으로 commander 모듈을 사용한다 

$ train run

module.js:340

    throw err;

          ^

Error: Cannot find module 'commander'

    at Function.Module._resolveFilename (module.js:338:15)

    at Function.Module._load (module.js:280:25)

    at Module.require (module.js:364:17)

    at require (module.js:380:17)

    at Object.<anonymous> (/Users/nulpulum/development/playground/playhub/palette.frontend.node/bin/train:3:15)

    at Module._compile (module.js:456:26)

    at Object.Module._extensions..js (module.js:474:10)

    at Module.load (module.js:356:32)

    at Function.Module._load (module.js:312:12)

    at Function.Module.runMain (module.js:497:10)


// 에러가 발생하므로 commander, underscore, handlebars, boilerplate 모듈을 설치한다 

$ cd .. && npm install commander --save 

$ npm install underscore --save

$ npm install handlebars --save

$ npm install boilerplate --save-dev

$ cd bin && train run 

module.js:340

    throw err;

          ^

Error: Cannot find module '../../lib/app'

    at Function.Module._resolveFilename (module.js:338:15)

    at Function.Module._load (module.js:280:25)

    at Module.require (module.js:364:17)

    at require (module.js:380:17)

    at Object.<anonymous>(/Users/nulpulum/development/playground/playhub/palette.frontend.node/bin/commands/console.js:4:13)

    at Module._compile (module.js:456:26)

    at Object.Module._extensions..js (module.js:474:10)

    at Module.load (module.js:356:32)

    at Function.Module._load (module.js:312:12)

    at Module.require (module.js:364:17)


// 에러 다시 수정 하기 

$ cd ../node_modules/express-train/

$ ls -alrt

total 80

drwxr-xr-x   3 nulpulum  staff    102  9 17 17:20 test

-rw-r--r--   1 nulpulum  staff     90  9 17 17:20 index.js

-rw-r--r--   1 nulpulum  staff     82  9 17 17:20 .npmignore

-rw-r--r--   1 nulpulum  staff  13393  9 26 19:13 ReadMe.md

drwxr-xr-x   5 nulpulum  staff    170 11  8 14:14 boilerplates

-rw-r--r--   1 nulpulum  staff  14976 11  8 14:14 package.json

drwxr-xr-x   4 nulpulum  staff    136 11  8 14:14 lib

drwxr-xr-x  12 nulpulum  staff    408 11  8 14:14 node_modules

drwxr-xr-x  10 nulpulum  staff    340 11  9 15:19 .

drwxr-xr-x  16 nulpulum  staff    544 11  9 15:29 ..


// express-train의 lib밑의 모든파일을 bin 밑으로 이동시킨다 

$ mv lib/* ../../bin/

// index.js 파일을 이동한 bin 디렉토리에 같이 놓는다 

$ mv index.js ../../bin/


// express-train 은 삭제한다 

$ cd .. && rm -rf express-train

$ cd ../../bin/

// 최종 lib이 bin 밑으로 들어갔다 

$ ls -alrt

total 32

-rw-r--r--   1 nulpulum  staff    64  9 17 17:20 runner.js

-rw-r--r--   1 nulpulum  staff  3594  9 27 14:06 app.js

drwxr-xr-x  11 nulpulum  staff   374 11  9 15:19 ..

-rw-r--r--   1 nulpulum  staff    89 11  9 15:56 index.js

-rwxr-xr-x   1 nulpulum  staff   785 11  9 16:02 train

drwxr-xr-x   7 nulpulum  staff   238 11  9 16:02 .

drwxr-xr-x   3 nulpulum  staff   102 11  9 16:08 commands


// commands 폴더에서 runt.js를 제외한 사용하지 않는 나머지 파일은 삭제하거나 나중을 위해서 다른 위치로 백업한다 

$ cd commands && rm console.js cycle.js new.js boilerplate.js


// commands 폴더의 run.js 안에 내역을 수정 한다 

// 기존 내역 : var appPath = path.join(process.cwd(), '../app/index.js');

// 수정 내역 

var appPath = path.join(process.cwd(), './index.js');


// lib밑에 있는 index.js가 가르키는 파일 수정 

// 기존 내역 : app: require('./lib/app')

// 수정 내역

// module.exports = {

//   app: require('./app.js')

// };


var paletteApp = require('./app.js');

module.exports = paletteApp(__dirname);


// palette.frontend.node 루트의 app 디렉토리명칭을 api 라고 변경한다 

$ cd ../../ && mv app api 

  

// 다음으로 직접 app.js안의 인식하는 디렉토리 경로를 상대경로로 변경한다 

$ cd bin && vi app.js

//위치 변경하기 

var LOCATIONS = createApplication.locations = {

    pkg:{

        path: '../package.json'

    },

    config:{

        path: '../config'

    },

    logs:{

        path: '../logs'

    },

    models: {

        path: '../api/models',

        autoinject: true,

        aggregateOn: 'models'

    },

    views: {

        path: '../api/views'

    },

    lib: {

        path: '../api/lib',

        autoinject: true

    },

    controllers: {

        path: '../api/controllers',

        autoinject: true

    },

    // 향후 palette.frontend의 grunt build시에 dist 폴더 밑으로 되는 것을 public 폴더로 바꿀 것이다 

    pub: {

        path: '../public'

    },

    middleware: {

        path: '../api/middleware',

        autoinject: true

    }

};


// api 디렉토리 밑에 있는 public 디렉토리를 루트로 옮겨서 테스트 해본다 

$ mv app/public .


// bin 디렉토리 밑에 있는 train 쉘 실행하기 

$ train run 

[express train application listening on 4000]


  - Express-Train기반의 서버구조를 만들었고, 이제 이것을 palette.frontend 쪽으로 옮겨보자 

// palette.frontend.node 프로젝트의 package.json 명칭을 변경 - 나중에 palette.frontend쪽의 package.json과 합치기 위함

$ mv package.json packate.json.node


// public 폴더는 삭제한다 

$ rm -rf public 


// 옮기기 

$ mv * ../palette.frontend/

mv: rename node_modules to ../palette.frontend/node_modules: Directory not empty

mv: rename test to ../palette.frontend/test: Directory not empty

$ cd node_modules

$ mv * ../../palette.frontend/node_modules/


// palette.frontend 에서 dist를 public으로 명칭 변경하여 테스트 

$ cd ../../palette.frontend

$ mv dist public 

$ cd bin

$ train run

[express train application listening on 4000]


// 이제 grunt build를 수행할 때 dist 디렉토리를 만드는 것이 아니라 public 으로 만들어 보자 

$ cd .. && rm -rf public 


// grunt 환경파일의 내역 수정 

$ vi Gruntfile.js

// 변경내역 

 grunt.initConfig({

    yeoman: {

      // configurable paths

      app: require('./bower.json').appPath || 'app',

      dist: 'public'  // 'dist'를 'public'으로 변경한다 

    },


// 다시 grunt로 빌드를 하면 public 폴더가 생성된다 

$ grunt build 

$ cd bin && train run 

[express train application listening on 4000]


// 최종 palette.frontend의 합친 구조

  - api : Node 서버 

  - app : SPA Angular

  - public : app 배포 디렉토리이면서 bin/train run 수행시 인식되는 서비스 폴더


// palette.frontend git 저장소에 push하기전에 package.json.node의 내용을 package.json에 통합한다 

{

  "name": "palette",

  "version": "0.0.1",

  "dependencies": {

    "express": "3.2.x",

    "express-train": "1.0.x",

    "connect-timeout": "latest",

    "connect-mongodb": "latest",

    "mongoose": "latest",

    "async": "latest",

    "express-hbs": "latest",

    "passport": "latest",

    "winston": "latest",

    "commander": "~2.0.0",

    "underscore": "~1.5.2",

    "handlebars": "~1.1.2",

    "nject": "~0.1.6"

  },

  "devDependencies": {

    "grunt": "~0.4.1",

    "grunt-contrib-copy": "~0.4.1",

    "grunt-contrib-concat": "~0.3.0",

    "grunt-contrib-coffee": "~0.7.0",

    "grunt-contrib-uglify": "~0.2.0",

    "grunt-contrib-compass": "~0.5.0",

    "grunt-contrib-jshint": "~0.6.0",

    "grunt-contrib-cssmin": "~0.6.0",

    "grunt-contrib-connect": "~0.5.0",

    "grunt-contrib-clean": "~0.5.0",

    "grunt-contrib-htmlmin": "~0.1.3",

    "grunt-contrib-imagemin": "~0.2.0",

    "grunt-contrib-watch": "~0.5.2",

    "grunt-autoprefixer": "~0.2.0",

    "grunt-usemin": "~0.1.11",

    "grunt-svgmin": "~0.2.0",

    "grunt-rev": "~0.1.0",

    "grunt-concurrent": "~0.3.0",

    "load-grunt-tasks": "~0.1.0",

    "grunt-google-cdn": "~0.2.0",

    "grunt-ngmin": "~0.0.2",

    "time-grunt": "~0.1.0",

    "karma-ng-scenario": "~0.1.0",

    "grunt-karma": "~0.6.2",

    "karma-script-launcher": "~0.1.0",

    "karma-chrome-launcher": "~0.1.0",

    "karma-firefox-launcher": "~0.1.0",

    "karma-html2js-preprocessor": "~0.1.0",

    "karma-jasmine": "~0.1.3",

    "karma-requirejs": "~0.1.0",

    "karma-coffee-preprocessor": "~0.1.0",

    "karma-phantomjs-launcher": "~0.1.0",

    "karma": "~0.10.4",

    "karma-ng-html2js-preprocessor": "~0.1.0",

    "mocha": "latest"

  },

  "engines": {

    "node": ">=0.8.0"

  },

  "main": "./app/index.js",

  "scripts": {

    "test": "grunt test",

    "start": "cd bin && train run",

    "server-test": "mocha test"

  }

}


// npm 명령으로 scripts 설정 내역 호출하기 

$ npm start

[express train application listening on 4000]


  - 최종 완성본 저장소 : MyPlayHub GitHub Repository

// git clone 하여 train run 수행시 다음과 같이 오류가 발생할 경우 

$ train run

env: node\r: No such file or directory


// dos2unix 패키지를 설치하여 수정하여 준다 

$ brew install dos2unix

==> Installing dos2unix dependency: gettext

==> Downloading http://ftpmirror.gnu.org/gettext/gettext-0.18.3.tar.gz

.. 중략 ..


$ dos2unix train

dos2unix: converting file train to Unix format ...

$ train run 

[express train application listening on 4000]



방법-3. Yeoman Generator FullStack을 통한 통합

  - Express + Angular의 통합

  - Angular는 클라이언트의 파일 전송과 암호화를 위하여 Lint와 압축을 위해 Grunt를 사용함 

  - Express는 서버단이므로 압축이 필요없음 

// FullStack generator 설치 

$ sudo npm install -g generator-angular-fullstack

// FullStackTest라는 애플리케이션 명칭으로 스케폴딩 생성

$ yo angular-fullstack FullStackTest

// 빌드하여 클라이언트 파일 Lint 및 압축 

// public 폴더밑으로 배포본이 만들어짐 

$ grunt build 

... 중략 ...

Elapsed time

useminPrepare:html                   25ms

concurrent:dist                          5s

autoprefixer:dist                        113ms

concat:public/scripts/modules.js  23ms

copy:dist                                  640ms

cdnify:dist                                 21ms

ngmin:dist                                263ms

cssmin:public/styles/main.css     33ms

uglify:dist                                  28ms

uglify:public/scripts/plugins.js      373ms

uglify:public/scripts/modules.js   134ms

usemin:html                             482ms

usemin:css                              152ms

Total                                        8s



<참조> 

  - Angular-Express-Train-Seed

  - Yeoman : generator-fullstack with angular and express

  - dos2unix 설치하여 사용하기

posted by 윤영식
2013. 10. 30. 14:31 My Services/PlayHub

프론트 개발을 위해 준비해야할 것들은 무엇이 있을까? 우선 개발 및 테스트 프레임워크와 라이브러리 관리, 테스트/코드커버리지/빌드 자동화 수행등 준비할 것들이 많다. 이것을 한번에 준비하고 자동화할 방법은 없을까?



1. Yeoman

  - yo : 코드 스케폴딩 역할 (프로젝트를 시작할 때 최초에 디렉토리구조와 템플릿 파일일 제공해 준다) generator-* 로 시작

  - grunt : test, lint, concatenate, build등 Java의 Ant와 같은 Task를 자동 수행

  - bower : 내부적으로 사용하는 컴포넌트의 버전 의존성 관리 및 설치/업데이트/삭제 관리

  - 참조 



2. Addy Osmani의 Frontend Workflow

  - 자동화는 반복적인 작업을 효율적으로 만드는 과정

  - Frontend Automation = Scaffolding + Download Libraries + Download Template + Download Framework



3. PlayHub Workflow 만들기 

  - 자신의 개발환경에 GitHub 저장소 설정하기

    + mac 또는 windows

    + nitrous.io 같은 PaaS 에도 설정가능 

// Node Version Manager 설치 

$ curl https://raw.github.com/creationix/nvm/master/install.sh | sh

$ exit

logout

Connection to 127.0.0.1 closed.


// 현재 CentOS에 설치된 Node version 정보가 나오고 원하는 버전정보를 선택하여 사용할 수 있다. 명령은 help 참조

$ nvm list

       N/A

current: v0.11.5


// playhub 디렉토리 만듦 - 해당 디렉토리 밑으로 playhub의 저장소를 놓을 것임 

$ mkdir playhub 


// playhub의 저장소를 git clone하기 위하여 자신의 ssh 공개키가 없다면 생성한다

// 생성방법 참조

$ cd .ssh

$ ssh-keygen -t rsa -C ysyun@yuwin.co.kr  // 이후 쭉 enter

Generating public/private rsa key pair.

Enter file in which to save the key (/home/vagrant/.ssh/id_rsa):

Enter passphrase (empty for no passphrase):

Enter same passphrase again:

Your identification has been saved in /home/vagrant/.ssh/id_rsa.

Your public key has been saved in /home/vagrant/.ssh/id_rsa.pub.

The key fingerprint is:

8a:32:de:ef:de:fd:22:8f:8d:38:70:b0:d7:56:e1:da ysyun@yuwin.co.kr

The key's randomart image is:

+--[ RSA 2048]---+

|                            |

|          .                 |

|         . .                |

|    .     o                |

|     o .S+               |

|    o.o.+ E              |

|  o .+..                  |

| . +  .o.=.              |

|  . .+=.+o+o.         |

+------------------+

$ ssh-add id_rsa

Could not open a connection to your authentication agent.


// 두개의 id_rsa 파일 생성 

$ ls -lart

-rw-rw-r--  1 vagrant vagrant  399 2013-10-30 05:59 id_rsa.pub

-rw-------  1 vagrant vagrant 1675 2013-10-30 05:59 id_rsa

drwx------  2 vagrant root    4096 2013-10-30 05:59 .


$ cat id_rsa.pub

ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAyvfJRNE/Kx+VwuGcQX8+PYbFadT6mCLZ1.. 중략 ..XS0U0uQ== ysyun@yuwin.co.kr


// GitHub -> "Account Settings" -> "SSH Keys"에서 SSH key값 등록



// 이제 GitHub에서 PlayHub의 Palette를 clone 해 보자 (git 기본 설치를 전제로 함)

$ cd ../playhub/

$ git clone git@github.com:playground-ko/palette.git

Initialized empty Git repository in /home/vagrant/playhub/palette/.git/

.. 중략 ..


$ ls - lart

drwxr-xr-x  5 vagrant vagrant 4096 2013-10-30 06:07 palette


// submodule에 대하여 초기화 하고 업데이트 한다 (Git 환경만들기 참조)

$ cd palette

$ git submodule init

Submodule 'backend' (git@github.com:playground-ko/palette.backend.git) registered for path 'backend'

Submodule 'frontend' (git@github.com:playground-ko/palette.frontend.git) registered for path 'frontend'

$ git submodule update

Initialized empty Git repository in /home/vagrant/playhub/palette/backend/.git/

.. 중략 ..

Submodule path 'backend': checked out 'f8276822e8a12a15f2af19e0c62cbd74a9172d84'

Initialized empty Git repository in /home/vagrant/playhub/palette/frontend/.git/

.. 중략 ..

Submodule path 'frontend': checked out 'e1b7201011321e1df2cc990c4eb2042c765c8a3c'


// playhub 디렉토리로 이동하여 bankend, frontend 저장소를 별도로 받아서 개발을 시작한다 

$ cd ..

$ git clone git@github.com:playground-ko/palette.backend.git

Initialized empty Git repository in /home/vagrant/playhub/palette.backend/.git/

.. 중략 ..

$ git clone git@github.com:playground-ko/palette.frontend.git

Initialized empty Git repository in /home/vagrant/playhub/palette.frontend/.git/

.. 중략 ..


  - yo/grunt/bower, generator-angular 를 설치 또는 업그레이드 하고, frontend 디렉토리로 이동하여 프로젝트를 만든다 

// yo를 설치하면 grunt와 bower가 같이 설치된다 

sudo npm install -g yo

// 현재 버전은 1.0.4 (2013.10.31 기준)

$ yo --version

1.0.0-rc.1.4

$ grunt --version

grunt-cli v0.1.9

grunt v0.4.1

$ bower --version

1.2.7


// 이미 설치되어 있다면 최신버전으로 업데이트  한다 

$ sudo npm update -g yo

$ sudo npm update -g grunt-cli

$ sudo npm update -g bower


// angular framework 설치

sudo npm install -g generator-angular


// angular 웹앱을 만든다 

$ cd  palette.frontend/

$ yo angular Palette


// 빌드하면 배포할 수 있는 dist 배포 디렉토리가 나온다. 

// dist는 온전한 html, css, js 파일들이고 이것은 어느 웹서버에 가져다 놓아도 구동이 된다

// 즉, 굳이 node.js위에 구동되지 않아도 됨 

// 기본적으로 build를 하면 관련 js 파일을 minification하여 사이즈를 줄여준다 

// Sublime Text에 Grunt build를 설정할 수 있다

$ grunt build

// karma 통하여 unit, e2e 테스트 수행

$ grunt test

// node.js 기반으로 서버가 열리면서 브라우져(Chrome)도 자동으로 열린다 

$ grunt server


// dist 배포본은 python을 통하여 간단히 테스트 해 볼 수도 있다 

// 브라우져에서 8000 port를 열어서 배포본이 정상적으로 작동하는지 아이체킹

$ cd dist 

python -m SimpleHTTPServer 8000


이제 Frontend를 Yeoman을 통하여 개발해 보자



4. BootFlat 적용하기 

  - yeoman의 generator-bootstrap은 v2.* 버전이 적용되어 있다. Bootstrap v3.* 버전이 적용된 BootFlat 확장판을 적용하기 위한 방법은 기존 블로깅을 참조한다. 

  - Twitter Bootstrap 고도화



<참조>

  - Yeoman을 통해 AngularJS 프로젝트 자동 구성하기

  - nvm 설치 방법

  - GitHub을 위한 SSH 키 생성방법

  - Sublime Text에 Grunt build 설정하기

  - BootFlat Homepage

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

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




아키텍쳐 

  - 구성 

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

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

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

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

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

  - 장단점

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

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



Git 서브모듈 만들기 

  - git submodule <command> 를 사용한다 

  - 생성 순서 

    + GitHub에 Main 저장소를 만든다 

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

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

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

    + GitHub으로 git push 수행하기 

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

Cloning into 'myplayhub'...

remote: Counting objects: 3, done.

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

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


$ cd myplayhub/

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

Cloning into 'webapp'...

remote: Counting objects: 3, done.

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

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

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

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

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


$ git status

# On branch master

# Changes to be committed:

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

#

# new file:   .gitmodules

# new file:   webapp

#

$ git branch

* master


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

[master 2fdaa1b] add submodule webapp

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

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

 2 files changed, 4 insertions(+)

 create mode 100644 .gitmodules

 create mode 160000 webapp


$ cat .gitmodules

[submodule "webapp"]

path = webapp

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


$ git push

Counting objects: 4, done.

Delta compression using up to 4 threads.

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

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

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

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

   0871412..2fdaa1b  master -> master



  - 전체 소스 받아오기 

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

    + git submodule init 명령으로 submodule 등록

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

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

Cloning into 'myplayhub'...

remote: Counting objects: 6, done.

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

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

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


$ cd myplayhub/

$ git status

# On branch master

nothing to commit, working directory clean


$ git submodule init

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


$ git submodule update

Cloning into 'webapp'...

remote: Counting objects: 3, done.

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

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

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

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


$ cd webapp/


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

$ ls

README.md

$ git status

# HEAD detached at 1bfbcac

nothing to commit, working directory clean


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

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

$ git branch

* (detached from 1bfbcac)

  master


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

$ git checkout master

Switched to branch 'master'


// 해결이 되었다 

~/myplayhub/webapp on  master

$ git branch

* master


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

$ git pull 


  - 서브모듈 삭제

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

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

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

$ git rm webapp

rm 'webapp'


$ vi .gitmodules

webapp submodule 삭제 


$ git status

# On branch master

# Changes to be committed:

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

#

# deleted:    webapp

#

# Changes not staged for commit:

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

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

#

# modified:   .gitmodules


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

[master 8305c3c] delete webapp submodule

 2 files changed, 4 deletions(-)

 delete mode 160000 webapp


$ git status

# On branch master

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

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

#

nothing to commit, working directory clean


$ git push




Git 브랜치 전략

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

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

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

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

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

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

    + 필요없을시 삭제한다 

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

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

    + master로 merge를 한다 

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

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


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

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



<참조>

  - Git 서브모듈 명령어 설명

  - Git Submodule 관리 팁

  - GitHub SSH 만들기 

posted by 윤영식
2013. 10. 28. 16:49 My Services/PlayHub

SRS는 Software Requirements Specification 약어로 서비스를 만드는 내역의 처음과 끝이 담겨져 있다. "SRS의 중요성"이라는 블로그에서 SRS의 중요성을 인지하고, 몇번의 서비스을 하면서 SRS를 써오고 있다. 여기서 오해하지 말아야 할것은 SRS은 양식이나 포멧이 아니다. 즉, 한장으로 표현될 수도 있고, 몇장으로 정해진 양식에 따라서 써질 수도 있다. 처음은 문서-doc-파일에 정의해 보았으나 내용의 확인 및 업데이트가 불편하여 Trello에 SRS를 정의 하고 있다. 




Trello에 SRS 작성하기 

  - 보드를 하나 만든다 

  - 보드에 챕터별로 리스트를 만든다 

  - 챕터에 하위 Task를 만들어 정의된 내역을 편집하면 된다 

* 각 챕터에 대한 자세한 내역은 해당 파일을 참조한다 

SRS_iPhone_PIMSplus.doc

* 참조 URL : http://sep522.googlecode.com/svn-history/r88/trunk/ 의 doc/srs 밑에 문서가 있다  




  - Trello에 SRS를 작성하면 접근성과 유지보수성이 높아진다. SRS는 기획자, 디자이너, 개발자 누구나 접근하여 쉽게 찾고 업데이트 할 수 있어야 한다 

  - 한가지 아쉬운 점은 문서의 버전관리가 안된다는 점이다. 보완을 위하여 좀 더 자세한 내역은 Google Doc을 이용하고 링크를 걸어 놓는 방법을 사용하자 

  - Playground 멤버 PlayHub의 Trello SRS



<참조>

  - SRS의 중요성 - 전규현

  - PlayHub의 Trello SRS

posted by 윤영식
2013. 10. 24. 15:56 My Services/PlayHub

플레이허브(PlayHub, 이하 플브)는 요즘 제가 놀고 있는 플레이그라운드 Public 페북그룹의 첫번째 파일럿 프로젝트입니다. 어제 일을 잘 기억하지 못하는 습성으로 서비스 런칭과정을 하나하나 정리 해보려 합니다. "서비스디자인" 부터 "개발" "런칭"까지 진행 스토리와 개발 노하우를 공유할 수 있었으면 좋겠습니다. 




플레이그라운드 모임의 가치 

플레이그라운드는 플레이허브 파일럿 프로젝트를 수행하는 주체입니다. 모임의 가치는 다음과 같습니다

  - Fun
  - Interesting
  - Something Cool
  - Together

"재밌게 즐기면서 만들어 낸 서비스를 통해서 먼저 스스로 재미를 느끼고, 그렇게 만들어진 서비스를 통해서 수익을 만들어 내고, 덕분에 경제적 한계성에서 벗어나 재미없는 일로부터 벗어날 수 있는 환경을 만들고, 그러한 환경에서 보다 스스로에게 가치있고 의미있는 일을 해보자는 것"


  저는 여기에 하나의 명제를 더 던져보려 합니다. 

"이런 어지러운 세상의 문제점을 해결해 줄 수 있는 서비스를 사람들에게 제공하고 싶다"


  여하튼 이런 생각들에 동참하신 분이 초기에는 200명가량 되었는데, 적극적 참여의사를 밝힌 분들만 추려서 플레이그라운 Members 페북그룹이 결성되었습니다. 여기서 저는 앞으로 열심히 놀 계획을 가지고 있습니다. 참여를 원하시면 이곳을 참조하세요



플레이그라운드(Playground) 파일럿 프로젝트 

  파일럿 프로젝트의 의미는 "서로 알아가기""협업환경 익숙해지기" 입니다. 


  - 하지만 40명가량의 Member분들이 기획:디자인:개발 = 1:1:2 비율이고 의견을 조율해 본봐 서로간의 사용하는 협업도구가 상이하여 우리만의 통합 협업 허브 시스템이 필요하겠구나 생각했습니다. 

  - PlayHub는 PlayGround에서 사용하는 협업도구들의 중요항목을 모아서 기획,디자이너,개발자이 함께 사용할 수 있는 Hub 시스템입니다.

    + Trello : Agile Scrum 도구 

    + GitHub : Social Repository

    + Redmine or Mantis : Issue 시스템

    + DropBox : 파일 공유

    + Google Docs : 문서 공유 

   등에서 모은 데이터를 UX&UI 가미하여 시각화하는 작업을 거칠 것 같습니다. 


  저의 꿈은 "좋은 개발 협업 문화"를 만들고, "글로벌 소프트웨어"를 만드는 것입니다. 이것은 "소프트웨어 개발의 모든 것"라는 책을 통해 알게된 부분에 대한 결심과도 같습니다. 이제 우리에게 필요한 것은 이러한 문화를 만들어야 하고, 더 넓은 세상을 바라 보아야 한다고 생각합니다. 이것은 스마트폰으로 촉발되어 서비스 산업이 국지적인 것이 아니라 글로벌을 염두에 두고 진행되어야 하는 시대가 되었기 때문입니다. 



플레이허브(PlayHub)의 가치 

  좋은 개발 협업 문화 만들기를 위한 하나의 밑거름이 될 것이라 믿습니다

 

  - 이러한 가치는 적극적이고 자발적으로 참여하는 기획, 디자인, 개발자 분들의 노력으로 만들어 갈 수 있다고 믿습니다. 

  - 웹앱 서비스를 통하여 스마트기기 어디에서나 능동적 알람(Push)을 통하여 온라인 협업을 가능토록 해줄 것이라 믿습니다.  

  - 앞으로 이런 Software Engineer가 되어야 겠다 생각해 봅니다. 스스로의 실천이 꿈을 만들어 가리라 믿습니다 

1. 팀이 없는 것처럼 협업하라. 같이 일을 하게 되면 자리를 옮겨서 같이 해라.

2. 지시하지 말고 토론하라.

3. 자신이 무엇을 하고 있고, 무엇을 잘 하는지, 무엇을 하고 싶어하는지 알려라.

4. 핵심기능·기술에만 먼저 집중하여 작게 시작하여 완성하고, 자신을 성장시키며 제품도 같이 성장시켜라

5. 자신보다 더 똑똑한 사람을 뽑아라. 단 팀플레이어만.

6. 자신과 생각이 다른 사람들과 가까이 하라. 불편함을 우정으로 풀어라.

7. 빠른 성장과 진행을 위해 팀을 작게 만들어라.

8. 잘못되어 가는 것이 보이면 빨리 뒤집어라. 고칠것이 있으면 자신이 고쳐라.

9. 자신이 만들고 있는 것이 어떤 유저의 어떤 문제를 해결하는지 자신에게 물어라

10. 항상 유저를 찾고 그들과 소통하라

11. 지식 공유를 하지 않는다는 것은 자신이 성장하고 있지 않다는 이야기다.

12. 결코 어른이 되지 마라. 기술에 대한 열정과 마음은 그대로 남아 있어라.



<참조>

  - 개발자도 기획자다. 좋은 개발자 되기

posted by 윤영식
prev 1 next