위의 구조에서 my_package는 패키지입니다. 이 패키지에는 module1.py와 module2.py라는 두 개의 모듈과, sub_package라는 하위 패키지가 포함되어 있습니다. 하위 패키지 sub_package에는 module3.py라는 모듈이 포함되어 있습니다.
이러한 구조에서는 다음과 같이 모듈을 가져올 수 있습니다:
from my_package import module1
from my_package.sub_package import module3
패키지와 모듈의 차이점
•구조:
•모듈은 단일 .py 파일로 구성됩니다.
•패키지는 여러 모듈(그리고 하위 패키지)을 포함할 수 있는 디렉토리입니다.
•범위:
•모듈은 단일 파일에 포함된 코드 단위입니다.
•패키지는 모듈과 하위 패키지를 조직화하는 더 큰 단위입니다.
•내포 관계:
•모듈은 패키지 안에 포함될 수 있습니다.
•패키지는 모듈들을 그룹화하는 역할을 하며, 패키지 내에 또 다른 패키지를 포함할 수도 있습니다.
요약
•모듈: Python 코드가 담긴 단일 .py 파일. 모듈은 함수, 클래스, 변수 등의 코드를 포함할 수 있으며, 다른 Python 코드에서 import를 통해 재사용할 수 있습니다.
•패키지: 모듈들을 조직화하는 디렉토리 구조. 패키지는 모듈과 다른 하위 패키지를 포함할 수 있으며, 패키지로 인식되기 위해서는 일반적으로 __init__.py 파일이 필요합니다.
프론트앤드 자바스크립트 개발이 점점 복잡해 짐에 따라 모듈 패턴으로 코드를 작성하고 단일 책임 원칙(Single Responsibility Principle)을 지키는 것이 좋다. 모듈 코드를 작성한 후 모듈을 로딩하고 배포(번들링)하는 다양한 방법들이 존재한다. 먼저 Module Loader에 대해 살펴보고 다음 글에서 Module Bundler에 대해 정리해 본다.
모듈 패턴
모듈패턴을 사용하는 이유
- 유지보수성(Maintainability): 단일 책임 원칙에 따라 필요한 기능을 담고 있으면서 별도 폴더와 파일로 유지하면 변경이나 확장 발생시 찾고 수정하기 쉽다. 전제 조건은 외부에 노출하는 API를 일관되게 유지하는 것이 중요하다.
- 이름공간(Namespacing): 자바스크립트에서 전역변수를 통한 개발을 하지 않는다. 이를 위해 즉시실행함수표현(IIFE)를 사용하여 전역변수의 오염을 방지하는데, 모듈 패턴 또한 전역변수 오명을 방지한다.
- 재사용성(Resuability): 모듈의 성격을 잘 나누어 놓으면 다음 프로젝트에서 그대로 사용해 쓸 수 있다. 보통 SDK나 Base Framework을 만들어 놓으면 초기 구축 비용을 최소로 할 수 있다.
JohnPapa Angular 스타일 가이드를 보면 특별히 모듈을 지원하는 라이브러리의 도움없이 자바스크립트 모듈 패턴 방식으로 앵귤러 v1 코드를 작성토록 가이드하고 있고, 앵귤러 팀에서도 공식적으로 추천하고 있다.
(function () {
'use strict';
angular
.module('a3.common.action')
.factory('currentAction', currentAction);
/* @ngInject */
function currentAction(ActionType, stateManager) {
return {
setDashboard: setDashboard,
setWorkspace: setWorkspace
};
function setDashboard(dashboardId) {
var action = {
...
};
stateManager.dispatch(action);
}
function setWorkspace(workspaceId, taskerId) {
var action = {
...
};
stateManager.dispatch(action);
}
}
})();
순수 자바스크립트로 모듈 단위로 만든 후 상호 운영은 어떻게 해야할까? 일단 index.html에 설정을 하고 사용하는 순서에 index.html에 script 태그를 통해 로딩을 하는 간단한 방식을 생각해 볼 수 있다. 하지만 필요한 시점에 자바스크립트에서 로딩을 해서 사용하는 방식을 명시적으로 하려면 별도 로더의 도움이 필요하다.
CommonJS & AMD & UMD
CommonJS는 지정한 코드를 동기적으로 로딩하는 방식으로 서버 사이드의 Node.js에서 사용한다.
- Object 만을 대상으로 한다.
- module.exports 구문으로 Object를 export 한다
- require 구문으로 Object를 import 한다.
// 파일명: module.js
function module() {
this.hi = function () { return 'hi'; }
}
module.exports = module;
// 사용하는 파일: test.js
var module = require('module');
var m = new module();
m.hi();
위에서 require를 차례로 호출하면 동기적으로 하나씩 로딩을 한다. 즉, 비동기적이지 않기 때문에 로딩이 전부 되어야 수행이 된다. 브라우져에서 동기적으로 모듈 파일을 로딩하게 되면 모든 파일이 로딩된 후 화면이 실행되므로 성능 이슈를 야기할 수 있다. 따라서 CommonJS는 Node.js에서 주로 사용하고 브라우져에서는 사용하지 않는다.
jquery, angular 파일에 대해 비동기적으로 로딩한다. AMD대표적 구현체로는 RequireJS가 있다.
UMD(Universal Module Definition)은 AMD와 CommonJS의 기능을 둘다 지원하는 것이다.
- AMD, CommonJS를 고려한다
- 구현체로 SystemJS를 들 수 있다. SystemJS는 Universal dynamic module loader로 AMD, CommonJS뿐만 아니라 브라우져의 global scripts와 NodeJS 패키지 로딩을 하고 Traceur 또는 Babel 과 같이 작동할 수도 있다. 특히, Angular 2에서 사용한다.
- 아래와 같이 CommonJS와 AMD를 체크하여 사용할 수도 있다. 구현 방식에 대한 다양한 예를 참조한다.
브라우져에서 ES6 module loader가 아닌 SystemJS (Universal module loader)를 사용할 경우 System.import 호출로 AMD, CommonJS, ES6 모듈 형식을 로딩할 수 있게 API를 제공하고 패키지 메니져로 JSPM을 사용할 수도 있다. JSPM은 무저항 브라우져용 모듈 패키지 메니져 (frictionless browser package management)로써 ES6 module loader가 작동하지 않는 곳에서 사용하는 Polyfill 이면서 AMD, CommonJS, Globals 자바스크립트 모듈 형식을 로딩할 수 있다.
Native JS
자바스크립트 ES2015 (ES6)에서 모듈 로더를 공식지원한다. ES2015는 모듈의 importing과 exporting을 제공한다. (참조) 간결관 syntax와 비동기 로딩과 cyclic dependencies에 대해 보다 잘 지원을 한다.
- import, export 를 사용한다.
// app.js
export let count = 1;
export function hi() {
return 'hi-' + count++;
}
// test.js
import * as app from './app';
console.log(app.hi());
console.log(app.count);
모듈로더에 대해 정리를 해보자. 자바스크립트 개발시 모듈 패턴에 입각하여 개발할 때 다양한 방식을 사용할 수 있으나
- NodeJS 기반 서버 사이드 개발은 CommonJS 이고
- Browser 기반 클라이언트 사이드 개발은 AMD구현체 중 하나인 RequireJS 를 사용한다.
2주차 진행을 하며 기존 웹 저장소를 별도로 구성해 완전히 별도의 애플리케이션을 구축할 예정이었으나 프러덕션 저장소의 업무로직의 복잡도와 지속적인 개발로 인한 신규 저장소 소스의 불일치 문제가 발생하였다. 따라서 완전히 새로운 저장소로 별도 애플리케이션을 개발하기 보다 Lagacy 코드를 리팩토링 하고 테스트 코드를 신규로 넣고 동시에 연속적인 빌드 환경을 구축하기로 한다.
모듈 리팩토링 준비가 되었다. 리팩토링할 대상을 선정한다. 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 서비스 생성 : 해당 서비스를 통해 어디서나 주입을 받아 사용토록 한다.
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'
])
좀 더 구조적인 관계의 모듈 설정을 통해 구조적인 애플리케이션을 개발하는 첫단추를 꽤었다.
SNS의 정신은 감동, 배려, 책임이다. 기술이 아니다, 자바스크립트를 행복하게 사용하려면 모듈단위의 개발이 되어야 한다. 모듈 패턴을 알아보자.
종류
- 견고한 애플리케이션을 개발하기 위하여 모듈단위 개발이 필요하다
- 모듈을 구현하기 위한 방법
> The Module pattern
> Object literal notation
> AMD modules
> CommonJS modules
> ECMAScript Harmony modules
Object Literals
- curly braces {} 를 사용한다
- new 키워드가 필요없다
- 오브젝트에 프로퍼티의 동적 추가가 가능하다. 예) myObjectLiteral.sencondKey = value;
var myObjectLiteral = {
variableKey: variableValue,
functionKey: function() {...},
someObject: {...}
}
Module Pattern
- class처럼 private, public를 캡슐화 하여 정의하는 방법이다
- 별도 name space 사용으로 global scope의 오염을 방지한다
- Closure와 IIFE (Immediately Invoked Function Expression) 을 사용한다
var testModule = (function() {
var counter = 0;
return { // Closure
incrementCounter: function() {
return counter++;
},
resetCounter: function() {
console.log('counter is', counter);
counter = 0;
}
};
})(); // IIFE
testModule.incrementCounter();
testModule.resetCounter();
// output : counter is 1
- 예제에서 counter는 global scope에서 숨겨져이다. 즉, private variable같다, counter 접근은 두개의 method로만 가능하다. 물론 function도 private 으로 정의가능하다
var myNamespace = (function () {
var myPrivateVar, myPrivateMethod;
// A private counter variable
myPrivateVar = 0;
// A private function which logs any arguments
myPrivateMethod = function( foo ) {
console.log( foo );
};
return {
// A public variable
myPublicVar: "foo",
// A public function utilizing privates
myPublicFunction: function( bar ) {
// Increment our private counter
myPrivateVar++;
// Call our private method using bar
myPrivateMethod( bar );
}
};
})();
Module Pattern 변형
- Import mixins : global scope의 변수를 module에 import 하는 방법
jQuery 와 Underscore 객체를 익명함수의 파라미터로 받아서 import 한다
// Global module
var myModule = (function ( jQ, _ ) {
function privateMethod1(){
jQ(".container").html("test");
}
function privateMethod2(){
console.log( _.min([10, 5, 100, 2, 1000]) );
}
return{
publicMethod: function(){
privateMethod1();
}
};
// Pull in jQuery and Underscore
}( jQuery, _ ));
myModule.publicMethod();
- Export : global module 만들기
var module = {} 객체를 만들어서 return 하여 export 한다
// Global module
var myModule = (function () {
// Module object
var module = {},
privateVariable = "Hello World";
function privateMethod() {
// ...
}
module.publicProperty = "Foobar";
module.publicMethod = function () {
console.log( privateVariable );
};
return module;
var results = ModelName.query({ search : 'all' }, onSuccessFn, onFailureFn);
//get a specific record
var record = ModelName.get({ id : 123 }, onSuccessFn, onFailureFn); //onSuccessFn and onFailureFn are optional callback functions where you can further customize the response
//create a new ModelName record
var record = new ModelName();
//update that record
record.someAttr = 'someValue';
record.$save();
//or if you prefer to submit your data in a different way
ModelName.save({
id : record.id
},{
somePostObject : {
attr1 : 'value',
attr2 : 'value2'
}
});
//destroy the record (and include a token)
record.destroy({ token : record.token });
9. Directives
- HTML 코드를 통하여 만들어 놓은 플러그인과 링크를 설정해 주는 것이다. 애플리케이션내에서 블럭을 격리시켜준다. (그냥 컴포넌트 개념을 생각하며 되겠다. 화면 구성을 위하여 UI 컴포넌트 추가하는 것 처럼)
- 웹앱을 구조화하는데 도움을 준다
- Plugin, Validation(유효성검사), Dynamic text properties 또는 i18n, l10n 에도 활용한다
// link 는 Controller와 Directive가 $scope 를 통하여 데이터를 공유하는 중요 통로이다