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

Publication

Category

Recent Post

'My Projects/Jandi'에 해당되는 글 2

  1. 2015.01.21 [Jandi] 애플리케이션 모듈 리팩토링
  2. 2015.01.07 [Jandi] 팀 커뮤니케이션 서비스
2015. 1. 21. 16:15 My Projects/Jandi

2주차 진행을 하며 기존 웹 저장소를 별도로 구성해 완전히 별도의 애플리케이션을 구축할 예정이었으나 프러덕션 저장소의 업무로직의 복잡도와 지속적인 개발로 인한 신규 저장소 소스의 불일치 문제가 발생하였다. 따라서 완전히 새로운 저장소로 별도 애플리케이션을 개발하기 보다 Lagacy 코드를 리팩토링 하고 테스트 코드를 신규로 넣고 동시에 연속적인 빌드 환경을 구축하기로 한다. 



1. 모듈 리팩토링


  - AngularJS에서 하나로 묶여 있는 모듈을 여러개로 쪼개는 작업을 진행한다. 

    + 다행히 기존 애플리케이션이 generator-angular-fullstack 으로 되어 있기 때문에 별도로 작업했던 내용을 적용한다. 

    + jandiApp 모듈만 존재한다면 애플리케이션을 위한 추상화 모듈과 공통 모듈로 두가지를 생성한다. 

  - web_client 폴더는 jandi의 웹 애플리케이션 저장소 이름이다. 

    + web_client/client 폴더 밑으로 components 폴더를 만들고 그 밑으로 app와 base 폴더를 만든다. 

    + components/app는 애플리케이션과 연관이 있는 대표 모듈 폴더이다. 

    + components/base는 jandi외에도 어디서나 사용 가능한 그리고 웹에서 bower로 다운로드한 모듈의 폴더이다. 

$ cd web_client/client/

$ mkdir components && cd components

$ mkdir app

$ mkdir base


  - app와 base에 대한 대표 모듈을 만든다.

    + components/app/app.framework.js 파일 생성

    + components/base/base.farmework.js 파일 생성

    + app.framework 모듈은 base.framework 모듈을 의존한다. 

// app.framework.js 내역 

(function() {

  'use strict';


  angular

    .module('app.framework', ['base.framework']);

    

})();


// base.framework.js 내역 

(function() {

  'use strict';


  angular

    .module('base.framework', []);

    

})();


  - 자바스크립트 로딩 우선순위를 지정한다. 

    + index.html 파일의 app.js 보다 먼저 로딩 될 수 있도록 app.framework.js와 base.framework.js를 지정한다. 

    + grunt serve 명령 수행시 app.framework.js와 base.framework.js 파일이 자동 inject 되지 않도록 ingore 설정을 한다. 

    + watch와 inject에 경로를 설정하고 설정앞에 ! 주면 exclude하겠다는 의미이다. 

// index.html 일부 내역 

<!-- build:js({.tmp,client}) app/app.js -->

<script src="components/base/base.framework.js"></script>

<script src="components/app/app.framework.js"></script>

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

<!-- injector:js -->

<script src="assets/javascripts/angulartics-ga-custom.js"></script>



// Gruntfile.js 일부 내역

 watch: {

            injectJS: {

                files: [

                    '<%= yeoman.client %>/{app,components}/**/*.js',

                    '!<%= yeoman.client %>/{app,components}/**/*.spec.js',

                    '!<%= yeoman.client %>/{app,components}/**/*.mock.js',

                    '!<%= yeoman.client %>/components/base/base.framework.js',

                    '!<%= yeoman.client %>/components/app/app.framework.js',

                    '!<%= yeoman.client %>/app/app.js'],

                tasks: ['injector:scripts']

            }, 

... 중략 ....

injector: {

            options: {},

            // Inject application script files into index.html (doesn't include bower)

            scripts: {

                options: {

                    transform: function(filePath) {

                        filePath = filePath.replace('/client/', '');

                        filePath = filePath.replace('/.tmp/', '');

                        return '<script src="' + filePath + '"></script>';

                    },

                    starttag: '<!-- injector:js -->',

                    endtag: '<!-- endinjector -->'

                },

                files: {

                    '<%= yeoman.client %>/index.html': [

                        [

                            '{.tmp,<%= yeoman.client %>}/assets/javascripts/*.js',

                            '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.js',

                            '!{.tmp,<%= yeoman.client %>}/components/base/base.framework.js',

                            '!{.tmp,<%= yeoman.client %>}/components/app/app.framework.js',

                            '!{.tmp,<%= yeoman.client %>}/app/app.js',

                            '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.spec.js',

                            '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.mock.js']

                    ]

                }

            },


  모듈 리팩토링 준비가 되었다. 리팩토링할 대상을 선정한다. generator-angular-fullstack으로 애플리케이션을 생성하면 client/app/app.js 파일이 메인 파일이 된다. 먼저 app.js안에 애플리케이션 모듈 set과 config, run에 다양한 설정이 되어 있다면 이들을 base.framework 또는 app.framework으로 분리할 수 있는지 점검한다. 일반적으로 다음과 같은 상태로 app.js를 설정하고 한다. 





  위와 같이 app.js 한 곳에 다양한 설정들이 있는 것을 리팩토링을 통해 모듈단위로 나눌것이다. 이렇게 할 수 있는 것은 메인 모듈이 의존하는 모듈을 설정하면서 의존모듈을 로딩하면서 의존모듈의 config, run을 수행해 주고 그리고 자신의 모듈을 로딩하기 때문이다. app.js안에 app.framework과 base.framework 모듈의 의존관계를 설정하고 로그를 찍어보자. 먼저 의존관계의 모듈 config와 최종 애플리케이션 모듈인 jandiApp config를 로딩한 후 run을 다시 의존 모듈부터 수행시킨다. 즉, 애플리케이션을 최초 호출할 때 config, run 은 최초 한번 수행됨을 이용해서 각 기능의 초기화를 모듈단위로 나누는 것이다. 

// app.js 의존관계 설정 

angular.module('jandiApp', [

    'base.framework',

    'app.framework',

    ... 중략 ...]);


// config, run의 수행 순서 로그 

=========> Base framework Module config

=========> App framework Module config

=========> jandiApp Module config

=========> Base framework Module run

=========> App framework Module run

=========> jandiApp Module run




2. Config 모듈 생성 


 최초 환경 설정 부분을 모듈로 만들어서 app.framework 모듈에 의존성을 설정해 보자. app.js의 run 메소드에는 api 버전과 서버주소에 대한 기본 환경 설정이 있다. 이것을 config.js라는 별도의 모듈로 빼서 환경설정에 대한 부분을 독립시킨다. 

  - config.js 모듈 파일 생성 : 최초 환경 셋업이 필요한 부분은 config 모듈의 run 메소드에 설정한다. 즉, app.js 있던것을 옮겨옴 

  - config.service.js 서비스 생성 : 해당 서비스를 통해 어디서나 주입을 받아 사용토록 한다. 

$ cd web_client/components/app

$ mkdir config && cd config


// app.js 일부내역 

app.run(function() {

$rootScope.server_address   = configuration.api_address + "inner-api/";

      $rootScope.server_uploaded  = configuration.api_address;

$rootScope.api_version      = configuration.api_version;

$rootScope.configuration    = configuration;  

});


// components/app/config/config.js 내역 

(function() {

  'use strict';


  angular

    .module('app.config', [])

    .constant('configuration', {

      name                : 'local',

      api_address         : 'http://www.jandi.com/',

      api_version         : '2',

      ga_token            : 'UA-222222-1',

      ga_token_global     : 'UA-1111111-1',

      mp_token            : 'abcdefghijklmn',

      base_url            : '.jandi.io',

      base_protocol       : 'http://',

      main_address        : 'http://www.jandi.com/main/#/',

      app_store_address   : 'xxxx',

      play_store_address  : 'yyyy'

    })

    .run(run);


  /* @ngInject */

  function run($rootScope, config) {

    // server address, server api version 

    config.init($rootScope);

  }


})();


// components/app/config/config.service.js 내역 

(function() {

  'use strict';


  angular

    .module('app.config')

    .service('config', config);

    

  /* @ngInject */

  function config(configuration) {

    var self = this;

    this.init = init; 


    function init($rootScope) {

      // TODO : maybe remove next factoring

      // compatibility for current version

      $rootScope.server_address   = configuration.api_address + "api/";

      $rootScope.server_uploaded  = configuration.api_address;

      $rootScope.api_version      = configuration.api_version;

      

      // configuration constant service       

      configuration.server_address  = configuration.api_address + "api/";

      configuration.server_uploaded = configuration.api_address;

      configuration.api_version     = configuration.api_version;


      // config service

      self.server_address  = configuration.api_address + "inner-api/";

      self.server_uploaded = configuration.api_address;

      self.api_version     = configuration.api_version; 

    }

  }

})();


  config.js 에서 app.config 모듈을 run 하면서 config.service.js 를 주입받아 최초에 init()을 하고 있다. 파라미터로 $rootScope를 전달하는데 이는 초기 app.js에서 환경값을 $rootScope에 설정해서 사용하기 때문이다. 향후에는 $rootScope를 통해 접근하는 것을 없애고 config 서비스 통해 필요한 정보를 접근토록 할 예정으로 1차로 기존 버전의 애플리케이션 수정없이 환경설정으로 뺄 수 있는 상태로 리팩토링한다. AngularJS에서 초기 설정 정보를 접근하기 위한 원칙을 다음과 같이 세우면 좋다. 


  - AngularJS의 서비스는 singleton object 이므로 초기 값을 변수로 가지고 있고 접근자 API를 노출한다. 

  - AngularJS 서비스에 $scope, $rootScope를 절대로 주입하지 않는다. 오직 controller와 module.run 에서만 주입을 받아 사용한다. 

  - 따라서 서비스는 서비스를 주입받아 공통정보에 접근해야 한다. 


원칙에 따라 하나씩 모듈로 쪼개고 app.js에는 업무적인 로직에만 집중할 수 있도록 만든다. 향후 components/app/* 모듈은 사용자가 사용하는 웹화면과 관리자가 사용하는 웹화면에 동시에 적용되는 애플리케이션과 관련된 공통 모듈이 들어갈 것이다. 현재까지 모듈의 의존관계 모습은 다음과 같이 app.js의 jandiApp 의존관계는 app.framework 밖에 없다. 하지만 서로 의존관계가 설정되어 있기 때문에 jandiApp 모듈의 AngularJS 모든 컴포넌트는 base.framework과 app.framework의 의존관계 설정 모듈의 컴포넌트를 주입받아 사용할 수 있게 된다. 

// app.js 의존관계 

angular

  .module('jandiApp', [

    'app.framework'

  ]);


// app.framework.js 의존관계

 angular

    .module('app.framework', [

        'base.framework',

        'app.config'

      ])

    .run(run)

    .config(config);


// base.framework.js 의존관계

  angular

    .module('base.framework', [

      'ui.router',

      'ui.bootstrap',

      'gettext'

      'ngCookies',

      'ngResource',

      'ngSanitize',

      'ngAnimate'

    ])


  좀 더 구조적인 관계의 모듈 설정을 통해 구조적인 애플리케이션을 개발하는 첫단추를 꽤었다. 


'My Projects > Jandi' 카테고리의 다른 글

[Jandi] 팀 커뮤니케이션 서비스  (0) 2015.01.07
posted by 윤영식
2015. 1. 7. 11:29 My Projects/Jandi

Tosslab의 잔디는 팀 그룹간에 원활한 커뮤니케이션을 위한 도구이다. 채팅 및 파일공유를 할 수 있어서 업무 커뮤케이션 툴로 사용하기에 적당하고 모바일, 웹에서 접근할 수 있다. 



잔디의 기술


  잔디는 MEAN Stack 기술을 사용하고 모바일은 네이티브로 개발한다. 업무적인 커뮤니케이션 도구는 사무실에서 웹을 통해 사용할 경우가 많아서 특히 웹에 대한 사용성이 좋아야 한다. 블로그에서는 프론트앤드 영역만을 다루도록 한다. 기존에는 AngularJS v1.2.* 기반으로 되어 있고 개발을 진행함에 따라 웹 애플리케이션의 복잡도가 증가하여서 애플리케이션을 재구성할 필요가 발생하였다. 




모듈단위 애플리케이션 구성


 애플리케이션 복잡도를 단순화 시키고 애플리케이션을 구조화하는 방안이 필요하다. 애플리케이션을 구조화 한다는 것은 거시적인 부분과 미시적인 부분의 조합으로 이루어진다. 먼저 거시적인 부분은 아키텍쳐레벨로 AngularJS의 모듈을 구성하는 것이다. 


 

  AngularJS 모듈은 컴포넌트의 그룹이다. 모듈안에는 다양한 컴포넌트를 가지고 있고 컴포넌트 성격별로 구분해 놓을 필요가 있다. 위의 예에서 가장 기본으로 있어야할 AngularJS에서 제공하는 모듈 묶음과 어느 프로젝트에서나 쓰일만한 모듈을 묶어놓은 공통 모듈이 있을 것이다. 그리고 업무적으로 추상화할 필요가 있는 업무 공통 모듈이 존재하고 업무를 처리하는 업무 모듈식으로 나눌 수 있다. 하위 모듈은 상위 모듈을 참조할 수 없고 상위 모듈만이 하위모듈안의 컴포넌트를 DI(Dependency Injection) 방식으로 사용을 할 수 있다. 




제너레이터를 통한 애플리케이션 구성


 기존의 애플리케이션을 보기하면서 다시 재구성하기 위해 무엇부터 시작을 해야할까? 


  - Yeoman Generator를 선정한다. generator-angular-fullstack을 사용할 예정이다. 

  - 자바스크립트 코딩 스타일은 구글의 자바스크립트 스타일 가이드를 따른다. 

  - AngularJS의 코딩 스타일은 존파파의 AngularJS 스타일 가이드를 따른다.


  generator-angular-fullstack의 템플릿을 존파파의 가이드에 따라 변경한 프로젝트를 만들어 본다. 불필요한 예제는 제거하고 몇가지 영역의 템플릿만을 취할 것이다. 먼저 generator-angular-fullstackgenerator-ng-component를 각각 git clone 하고 명칭을 generator-jdi, generator-jdi-component라고 바꾸고 내부의 .git폴더를 제거한 후 각 템플릿의 내용을 파파죤스 가이드에 따라서 수정을 한다. 

$ git clone https://github.com/DaftMonk/generator-angular-fullstack.git

$ git clone https://github.com/DaftMonk/generator-ng-component.git


$ mv generator-angular-fullstack generator-jdi

$ mv generator-ng-component generator-jdi-component


$ cd generator-jdi && rm -rf .git

$ cd generator-jdi-component && rm -rf .git


 파파죤스 가이드에 따라 generator-jdi-component/templates 폴더 밑의 모든 앵규러 컴포넌트를 다음과 같은 형식으로 수정한다. 

   + IIFE : Immediately Invoke Function Expression

   + 앵귤러 모듈 변수를 만들지 않음 

   + 컴포넌트의 펑션을 밖으로 뺌

   + DI 하는 부분은 ng-annotation을 이용함으로 주입받는 펑션위에 /* @ngInject */ 를 둚

(function() { 

  'use strict';


  angular

    .module('<%= scriptAppName %>')

    .controller('<%= classedName %>Ctrl', <%= classedName %>Ctrl);


  /* @ngInject */

  function <%= classedName %>Ctrl($scope) {

         

  }

})();


 수정한 내역에 대해 테스트는 다음과 같이 진행을 하면 된다.  먼저 테스트용 test 폴더를 하나 만든다. 테스트 폴더 밑에 node_modules 폴더를 만들고 밑으로 generator-jdi와 generator-jdi-component 에 대해 심볼릭 링크를 건다. 각 generator-* 폴더로 들어가 npm install을 해준다. 이제는 test밑에서 "yo jdi Jandi" 명령을 치면 애플리케이션 생성 프롬프트를 묻게 된다. 애플리케이션 템플릿은 generator-jdi/app/templates 밑에 존재하고 여기에 있는 파일도 파파죤스 스타일 가이드에 따라 수정을 해준다. 

$ mkdir test && cd test

$ mkdir node_modules && cd node_modules 


$ ln -s <fullPath>/generator-jdi  generator-jdi

$ ln -s <fullPath>/generator-jdi-component generator-jdi-component


// 둘은 같은 디렉토리에 있어야 함 

$ cd <fullPath>/generator-jdi-component && npm install

$ cd <fullPath>/generator-jdi && npm install 


$ cd test 

$ yo jdi Jandi


  각 수정한 generator-jdi, generator-jdi-component는 링크를 참조한다. 이제는 누구나 사용할 수 있도록 npm repository에 등록을 한다. npm publish 로 package.json 파일에 등록된 내역을 기반으로 배포가 된다. 따라서 clone한 generator의 버전을 따르지 않고 최초 0.0.1 버전으로 수정해 배포한다. 

$ cd generator-jdi-component 

$ npm publish

+ generator-jdi-component@0.0.1


$ cd generator-jdi

$ npm publish

+ generator-jdi@0.0.1


 이제 글로벌로 generator-jdi만을 설치해서 어디서나 "yo jdi <App명칭>" 명령을 수행해 최초 애플리케이션 골격을 만들 수 있다. 

$ npm install -g generator-jdi

$ mkdir web_tiger && cd web_tiger

$ yo jdi Jandi



'My Projects > Jandi' 카테고리의 다른 글

[Jandi] 애플리케이션 모듈 리팩토링  (0) 2015.01.21
posted by 윤영식
prev 1 next