블로그 내 검색

2013. 9. 16.

개발자를 위한 ‘共感(공감)’ 세미나 12회 발표자료 : Introduce Yeoman

9월 6일 열린 공감 세미나에서 발표한 자료입니다.
간단하게 죽 얽어보고 개념을 잡은뒤 뒷부분 간단한 튜토리얼이 포함된 구성입니다 :)

Reveal.js

http://javarouka.github.io/pt/yeoman/


SlideShare

http://www.slideshare.net/javarouka/introduce-yeoman

2013. 6. 6.

MVC, MVP, MVVM 의 이해

과연…?

만일 열명의 소프트웨어 개발자를 한 방에 두고 Model – View – Controller 패턴에 대해 토론하라고 한다면, 아마 토론이 끝났을 때 서로 다른 열두개의 의견이 나올 것이다.
MVC 패턴은 프로그래밍을 배우는 사람들, 특히 웹 프로그래머에게는 아주 친숙한 패턴이다. 한때 SI 업계를 휩쓸었던 Struts가 MVC를 표방했었고 그 뒤로 대부분의 웹 프레임워크들은 MVC가 아니면 도태되는 듯이 당연하다는 듯 수식어를 붙이고 등장했다.

하지만 어떤가? 다들 MVC를 완벽히 이해하고 쓰고 있는 것일까.

이 패턴에 대해 정확하게 이해하고 쓰기란 참 쉽지 않은 것 같다. 이 글을 읽는 당신이 당장 MVC 패턴에  대해 자신있게 5분간 말할 수 있다면 이 글을 그만 읽어도 된다.
41224_97606099
사실 이 글을 쓰는 필자 조차도 누군가 갑자기 MVC에 대해 묻는다면 버벅댈 것이다. 글을 쓰려고 수많은 조사를 한 이 순간에도 말이다.

위의 죠쉬 스미스의 말처럼, MVC 패턴에 대해서는 명확한 정의를 내리기 어려운 것 같다.
이번에 다룰 개념들은 MVC, MVP, MVVM 에 대해서이다.

다시 말하지만 여기 쓰여진 것들이 정답은 아니다. 구현과 상황, 이해도에 따라 다른 개념으로 해석하거나 적용될 수 있다.

이 글은 필자가 이해하고 있는 수준에서 이야기할 것이다. 만일 잘못되었거나 다른 의견이 있다면 댓글로 달아주면 아주 기쁘게 토론할 수 있겠다.

Model 과 View 그리고 서로간의 관계

오늘 설명할 프레임워크들에서 변하지 않는 공통적인 용어 두개의 일반적 의미부터 짚고 넘어가 보자.

Model

모델은 도메인 개체 그 자체이다.

도메인은 프로그램 내에서 동일한 의미를 갖는 대상이다. 보통 Data 그 자체와 동일시 되지만, 이 외의 데이터를 조작할 수 있는 기능이 추가되기도 한다. 구현과 상황에 따라서는 단순히 데이터소스에 접근하는 DAO 역할을 하거나,  파일 시스템 자체가 되거나, 라이브러리 세트가 된다.

View

디스플레이.

사용자(Client. 사람이 아닌 기계 혹은 동물일수도 있다. 어플리케이션이 이용되는 타겟이라고 생각하자) 에게 제공되는 UI Layer를 말한다. 더욱 큰 의미를 포함하면 눈으로 이미지가 곁들여지거나 글자로 이루어지지 않은 바이트 코드의 나열이나 음악 파일등이라도, Client 가 이해할 수 있다면 View가 될 수 있다.

보통 Web Application 등에서는 View는 HTML/CSS 로 렌더링된 화면을 가르킨다.

둘의 관계..

MVC를 비롯한 여러 프레임워크들이 나온 이유는 명확하다.

각 계층의 분리로 재활용성을 높이고 중복을 막자는 것이다. 각 계층의 강한 결합이 발생한다면 애초에 프레임워크를 적용하는 의미가 없다.

이 둘의 의존성을 어떻게 제어하느냐에 따라 MVC, MVP, MVVM … etc 등으로 나뉘게 된다.

Controller, Presenter, ViewModel…?

이들은 모델과 뷰의 통신을 담당한다. 이들의 차이점을 한번 훓어보자.

Controller

모든 입력은 Controller에서 처리된다.
특정 입력이 들어오면 Controller는 그 입력에 해당하는 Model을 선택하여 처리하게 한다.
Controller는 입력에 따라 Model을 업데이트하고, 결과에 따라 다른 View를 선택한다. Controller는 View를 선택할 수 있기에 View를 여러개 관리할 수 있다.

일반적으로 View는 Model을 사용하여 업데이트를 하지만, Model을 관리하는 것은 Controller이므로 사실상 View는 Model을 직접적으로 생성하거나 관리하는 것은 아니다. Model을 이용하거나 이용될 뿐이다.

Controller는 Model을 조작하고 View를 선택하지만 View를 직접 업데이트하진 않는다.
이 경우에는 View와 Model의 관계가 어느정도 있으며

  • View가 Model을 직접 사용하여 업데이트가 되거나,
  • 아니면 Model은 자신과 관련돤 View 들에게 Notify 해줘서 View가 업데이트 되는 방식도 있고,
  • View가 polling 을 통해 Model의 변화를 알아채고 스스로 업데이트하는 방식도 있다.
어느것이 되었든 View와 Model의 어느정도의 의존성은 피할 수 없다. 이것이 Controller를 사용하는 MVC의 문제점이라면 문제점이다.
MVC 패턴의 좋은 구성은 최대한 이 둘의 의존성을 낮추는게 관건이다.

조금 억지스러운 예를 들어보자.
View와 Model이 클럽에 온 소극적인 남녀라면 Controller는 이 둘을 부킹해주는 실력좋은 웨이터이다. 여기서 소극적인 이라는게 중요하다. 물론 웨이터는 수 많은 요청에 의해 오늘도 매번 부킹에 성공할 것이다.

Presenter

View와 Model 사이의 상호작용을 담당한다.
또한, 이 경우 사용자의 입력 처리는 Controller 가 아닌 View가 담당한다.

View가 이벤트를 Presenter에 전달하면 이벤트에 맞춰 Presenter는 Model을 조작하고 그 결과를 다시 View에 바인딩하여 View의 요소들이 업데이트된다.

Controller는 단순히 View를 선택하고 Model을 조작한 뒤 실제 회면 갱신은 View와 Model의 상호작용으로 이루어지지만 Presenter 를 사용한 MVP에서는 Presenter가 Model을 조작하고 관리하며 변경이 있으면 Model에 따라 View를 업데이트하게 된다.

실제 구현체로 생각해본다면 Presenter는 Model과 View의 인스턴스를 모두 가지고 있어야 할 것이다. View가 섬이고 Model이 육지라면 그 사이를 이어주는 다리와 같다고 보면 될 듯 싶다.

View와 Presenter는 1:1 관계로 맺어지며 Presenter는 보통 Model보다는 View에 더 닮은 구조로 디자인된다. MVC와는 다르게 View와 Model의 관계가 전혀 없으며 단지 View는 Presenter라는 것을 하나씩 가지고 있게 된다. 그리고 입력 처리를 View에서 처리한다는 점도 큰 차이점이 된다.

이 녀석을 사용하면 View와 Model의 의존관계가 완전히 없어진다.

ViewModel

View의 표현을 담당한다고 보면 되겠다.

MVP와 매우 비슷하지만 큰 차이점이라면 비중이 View에 좀더 치우쳐 있다는 점이며, Presenter는 View에 상당한 의존성이 있었지만, Controller, Presenter의 위치인 ViewModel은 View와 아무런 관련이 없다.

여러 View들은 하나의 ViewModel을 선택하여 바인딩하고 업데이트를 받는다.

ViewModel의 디자인은 View보다는Model에 비슷하게 구성된다. 보통 ViewModel이 변경되면 자동으로 View에 업데이트되는 방식으로 구현된다.
Model이 순수한 Model이라면 ViewModel은 View를 위한 모든 커스터마이징을 제공하는 Model이다.
좀 억지를 부려보면, ViewModel이 회사라면 View는 근무하는 사원에 가깝다. 회사는 사원의 여러 근무에 대한 환경을 제공한다. 이윤을 위해서라면 회사는 그 무엇과도 거래하고 연결한다.

결론

정리하고보니 더욱 애매한 것 같다. 더욱이 패턴의 개념은 구현에 따라 조금씩 변하고 달라지기에 이 셋을 완전히 구분해서 완벽히 이해한다는것도 쉽지 않은 일이다.

더구나 글의 서두에서 밝혔듯이 Model, View의 개념만으로도 개발자들은 제각각의 생각과 구현을 갖고 있을 수 있다. 게다가 만일 이 셋보다 더 획기적인 것이 등장한다면 새로운 용어와 함께 새로운 개념을 이해하려고 시간을 보내고 무슨차이가 있는지 고민이 늘어날 것이다.

이 셋의 차이점을 줄줄 외우는 것보다 이러한 개념의 구분이 어떠한 장점이 있으며, 뭐가 현재 상황에 더 적합한지를 파악하고 사용하는 것이 더 좋을 것 같다.

참고자료

2013. 5. 15.

자바스크립트의 여러 정수 변환법과 성능

정수형 변환

자바스크립트 integer 형 정수형 변환은 native로 parseInt가 있다.
하지만 이 외에도, bit 연산자를 활용한 방법도 있고, 심지어 이쪽이 더욱 빠르다.

JavaScript의 정수형 변환에는 다음과 같은 방법이 쓰인다.
// 777.77 을 정수형으로 변환 시도
// 결과는 전부 10진 정수 [777]
parseInt(777.77, 10)
Math.floor(777.77)
~~777.77
777.77 | 0
777.77 >> 0
777.77 >>> 0
777.77 << 0
우연히 MDN에서 Array.indexOf의 구현을 보다가 구현 내용중에
var len = array.length >>> 0
이라는 구문이 있어서  '>>>' 의 정체가 뭘까 검색해보다가 정수형 변환법까지 거슬러 올라가게 되었다.

여러 검색 결과로 얻은 결론은,
만일 Int32 형 캐스팅이라면 bit 연산이 제일 빠르다는것(http://arnorhs.com/2012/05/30/comparing-the-performance-of-math-floor-parseint-and-a-bitwise-shift/)

제일 느린건 parseIntMath.floor 는 중간.
그런데 Math.floor는 -부호가 붙을 경우 -1 이 더해지므로 애매하다.

parseInt VS Bitwise

parseInt는 두번째 인자로 진법을 받아서 진법 변환도 해주므로 나름 쓸데가 있지만, 단순한 정수형 변환에는 bit 연산이 압도적으로 속도가 차이가 난다.

parseInt에 사족을 달면, 최근 브라우저들 스크립트 엔진에서는 parseInt 시 10진법을 기본값으로 사용하지만 구버전 스크립트는 10진법을 기본으로 사용하지 않고 첫번째 인자를 가지고 멋대로 판단하여 5진법이나 8진법으로 변환해주기도 했다.
// 진법 인자의 있고 없고 차이.
parseInt("0E0") // 0
parseInt("0E0", 10) // 0
parseInt("0E0", 16) // 224

// 두번째 인자가 없을때 해석의 차이.
// 구 브라우저 (ecmascript 3th -)
parseInt("08") // 0

// 모던 브라우저 (ecmascript 5th +)
parseInt("08") // 8

parseInt를 불가피하게 사용해야 한다면 무조건 진법 인자를 붙이는 습관을 들이자.

또 하나 사족을 달면,
Array.indexOf 구현에 사용된 number >>> 0 연산은 부호를 떼버린 unsigned 32int 를 반환한다.(http://ecma-international.org/ecma-262/5.1/#sec-11.7)

정수 변환에는 value >> 0 나 ~~value 이 제일 빠른 해결책인듯 싶다.
만일 부호없는 정수가 필요하다면 value >>> 0.

단점이 있다면 가독성 문제인 듯 싶다. 코드만 봐서는 뭘 하는지 알아먹기 힘드니...

jsperf의 연산자 속도 체크 결과 링크는 여기.

2013. 5. 13.

URL 스트링 보다 쉽게 파싱해보기 (with DOM)

웹프로그래밍 하다보면 간간히 특정 문자열로 window.location 객체를 생성하고 싶은데 Location은 내부 함수라 사용할 수 없다.
new Location("http://www.example.com/?a=b#hash"); // 안됨
이럴땐 보통 프로그래머들은 정규표현식으로 문자열을 분해하곤 한다.
하지만 정규표현식이라는 것이 아주 날카로워서 잘못쓰면 손을 베이기 쉽상이다;

역주적 폭주, 예측못한 문자열을 못집어 내는 문제, 느린 문제...

그런데 A 태그를 활용한 URL 분석 방법이 있었다!
var parser = document.createElement('a');
parser.href = "http://example.com:3000/pathname/?search=test#hash";

parser.protocol; // => "http:"
parser.hostname; // => "example.com"
parser.port; // => "3000"
parser.pathname; // => "/pathname/"
parser.search; // => "?search=test"
parser.hash; // => "#hash"
parser.host; // => "example.com:3000"
심플하고도 강력하다.

관련 링크는 여기
https://gist.github.com/jlong/2428561

2013. 4. 30.

[Book :: Effective Java] 자바에서의 전략 패턴 구현과 함수객체

What is a 'Function-Object'?

자바는 객체지향을 구현하기 위해 모든 것을 클래스라는 일종의 인스턴스 템플릿으로 구현하도록 되어 있다.

단순한 유틸성 기능을 구현할 때에도 XXXUtils 이라는 뭔가 우주에서 날아온 듯한 이름으로 클래스를 만들고 그 안에 static을 사용한 메서드로 기능을 구현한다.

클래스는 자바에서 절대적이다.
자바는 언어 차원에서 모든 걸 클래스화 해야만 하며, 이러한 점은 여러 상황에서 불편한 점이 있을때가 많다.

간단한 예를 들어보자.
공식 API의 Camparator 는 정렬을 위한 함수 객체 인터페이스이다. 자바에서는 정렬 전략으로 사용하기 위해 저 클래스를 풀로 구현해야 한다.

이런 식으로 말이다.
// 이러한 문법적 껍데기를 반드시 선언해야 한다.
public class StringLengthComparator implements Comparator {

    // 우리가 필요한건 이 메소드의 기능 뿐인데도...
    public int compare(String s1, String s2) {
      return s1.length() - s2.length();
    }
}
// 아래와 같이 사용
Arrays.sort(array, new StringLengthComparator());
하지만 일부 언어에서는 함수 포인터(function pointer), 위임(delegate), 람다식(lamda expression) 또는 이와 유사한 기능을 제공하여 프로그램에서 특정 함수의 호출을 저장하거나 전달할 수 있다.
// C의 함수 포인터 방식
#include <stdio.h>
void hello(char *name) {
    printf ("Hi %s\n", name);
}

int main() {
    // 반환값이 void 이고 매개변수가 캐릭터인 함수 포인터 선언
    void (*Func)(char *);

    // 그 포인터에 hello 가르키게 함
    Func = hello;

    // Func 는 변수로 사용할 수 있지만 본질은 함수이다.
    // 함수 포인터 실행
    Func("test");
}

/* ============================================================ */

// C#의 위임 방식
// delegate 타입 선언
public delegate void DoSomething(string command);

// 위임할 메서드
public void Action(string direction){
    // 메서드 구현
}
...
...
// delegate 생성
DoSomething done = new DoSomething(Action);

// 위임 객체를 통해 변수로서 함수를 취급가능하다.
done("left");

/* ============================================================ */ 

// 자바스크립트의 익명 람다 함수
// each의 람다 함수를 지정
$.each(function(key, value) {
    // each 구현
});
그렇다면, 위 기능을 새로 구현해보자.
javascript 에서는 익명 함수를 지원하며, 위와 같은 구현이 아주 간단하다.
// 익명 함수를 사용한 배열 정렬법
arrayObject.sort(function(s1, s2) {
    return s1.length - s2.length;  
})
자바는 위 예제의 자바스크립트처럼 함수(혹은 메서드)만을 생성할 수 없고 반드시 클래스와 쌍으로 움직여야 한다.

그래서 새로운 업무 방식이 생겨 객체에 특정한 알고리즘을 교체하려면 아예 그 객체를 새로 만들거나(설마..), 그 업무 방식에 맞는 새로운 '알고리즘을 구현한 메소드를 포함한 클래스' 를 생성하여 처리 메서드로 전달해야 한다.

위에 굵은 글씨로 표현한 클래스 객체를 '함수 객체' 라고 부른다.

보통은 메서드를 하나만 가지는 인터페이스 형식으로 구현되나, 이 함수 객체를 사용하는 측에 따라서는 메서드 여러개가 구현되어 있을 수도 있다.

전략 패턴 (Strategy Pattern)

함수 객체가 제일 많이 쓰이는 곳은 역시 전략 패턴이다.

전략 패턴(Strategy Pattern) 은 기본적인 목적인 비슷하지만 흐름에서 변화하거나 교체할 수 있는 부분을 따로 분리하여 쉽게 변화시키고 새로 지정할 수 있게 해주는 패턴이다. 여러 메서드를 하나의 클래스에 집어넣은 것보다 유연성이 좋고 잘 디자인 된 전략은 다른 객체에서도 활용할 수 있다.

자바의 전략 패턴은 함수 객체를 사용해야 가능하다.
위에 예시로 든 Comparator 는 전략 패턴의 모범생 같은 구현이라고 볼 수 있겠다.

이제 예제와 함께 더 살펴보자.
만일 한 어느 학원 수강생 처리시스템에서 모든 등록 수강생의 주소를 새로운 주소로 바꾸는 작업을 해야 한다고 가정한다.

보통은 이렇게 처리할 것이다.
public List<Student> changeNewAddress(List<Student> student) {
    for(Student s : student) {
        String newAddr = getNewAddress(s.getAddress);
        s.setAddress(s);
    }
    return student;
}
수강생의 주소를 바꾸는 일 외에 학생들의 프로필 사진도 전부 섬네일화 해야 하는 작업이 생겼다고 가정해보자. 

그렇다면 간단한 구현으로는 새로운 createProfilePhotoToThumb 메소드가 생길 것이다.

하지만 이렇게 접근하지 말고 다른 방법을 써 보자.

일단, 하려는 작업은 둘다 학생에 대한 배치 처리이다.
학생 하나하나를 순회하며 해당 학생의 특정 필드에 대해 작업을 수행한다.
여기서 "새로운 주소 변경" 과 "섬네일 만들기" 는 전략으로 볼 수 있고 위에서 설명한 함수 객체로 구현해볼 수 있다.

그렇다면 먼저 함수 객체 인터페이스는,
interface Transfer<T> {
    T transfer(T value);
}
그렇다면 전략 객체 구현은,
// 들어온 주소 문자열을 새 주소 변경하는 함수 객체 클래스.
class ChangeAdressJob implements Transfer<Student> {
    public Student transfer(Student address) {
        // 구 주소를 새 주소로 바꾸는 로직
    }
}

// 들어온 사진을 섬네일화 하는 함수 객체 클래스
class CreateThumbnail implements Transfer<Student> {
    public Student transfer(Student photo) {
        // 프로필 파일을 분석하여 해당 파일을 섬네일을 만듬
    }
}
그렇다면 이제 저 함수 객체를 사용하는 클래스는 다음과 같을 것이다.
class StudentManager {

    private final List&;ltTransfer> transferStrategy = new LinkedList<Transfer>();

    public List<Student> loadStudents() { ... }

    // 배치 처리.
    // 학생 리스트에 대해 전략 함수 객체(Transfer 구현체)를 하나하나 실행함.
    public void executeBatch(List<Student> students) {
        for(Student s : students) {
            for(Transfer t : transferStrategy) {
                t.transfer(s);
            }            
        }
    }
    
    // 전략 객체를 전략 셋에 추가한다.
    public void addTransfer(Transfer t) {
        transferStrategy.add(t);
    }

    public static void main() {

        StudentManager manager = new StudentManager();
        List<student> students = manager.loadStudents();

        manager.addTransfer(new ChangeAdressJob());
        manager.addTransfer(new CreateThumbnail());

        manager.executeBatch(students);
    }
}

2013. 4. 20.

RequireJS – JavaScript 파일 및 모듈 로더


RequireJS?

RequireJS는 JavaScript 파일과 모듈 로더이다.
RequireJS는 브라우저에 최적화되어 있지만 Rhino나 NodeJS등의 환경에서도 사용할 수 있다. RequireJS같은 모듈 로더를 사용하면 당신의 코드의 성능과 품질이 좋아질 것이다
JavaPython 같은 잘 정립된 언어에서는 객체의 모듈화를 언어 자체에서 잘 지원하지만 JavaScript는 그렇지 않다. 

또한, 프로그램이 커질수록 스크립트가 중복되어 발생하는 경우가 발생할 수 있고 그런 경우에 코드를 common.js, event-binder.js 등의 코드로 나누어 관리하는경우가 많아지는데, 이 경우에도 분리한 코드를 잘 로딩하지 않으면 코드간 의존성이 망가지며 프로그램 자체의 구성이 엉성해지는 경우도 많다. 

RequireJS는 자바스크립트 파일을 동적으로 로딩할 수 있고, AMD 모듈화를 적용한 코드라면 모듈로서도 사용할 수 있으며 (그렇지 않아도 사용할 수 있는 방법이 있다 아래에서 더 살펴보자) 자바스크립트 코드간의 의존성도 줄 수 있다. 

또, 앞서 말했듯이 어떠한 자바스크립트 코드가 실행되려면 다른 스크립트가 먼저 로딩되어야 한다거나 하는 경우가 있는데, 자칫 스크립트 로딩의 순서가 꼬일 경우 에러를 뱉어내며 동작하지 않을 수 있다. 

RequireJS를 사용하면 코드간 의존성을 줌으로서 아예 그러한 경우를 막을 수 있고, 좀 더 체계적인 소스코드를 만들어낼 수 있다. 장점만을 죽 늘어놓았는데 자세한건 좀 더 살펴보자.  

JavaScript에게 모듈이란

먼저 모듈에 대해 간단히 짚고 넘어가보자. 

모듈의 개념은 Divide and Conquer 로 설명되는 각 기능(알고리즘)의 분할과 그 분할의 결합으로 생각해볼 수 있다. 

보통의 성숙된 언어에서는 이러한 모듈화를 언어 차원에서 지원하고 있는데, 예를 들어  java의 경우에는 모듈이 instance로 생성되어지며 모듈끼리의 구분은 package로 구분된다. 

그리고 모듈의 구현은 접근 제어자(private, public 등)의 사용으로 캡슐화를 보장하며, 필요한 것만 공개해서 그 모듈을 사용하려는 사용자가 쓸데없이 많은 지식을 가질 필요 없이 부담없이 사용할 수 있다.

JavaScript는 이러한 모듈화나 접근 제어를 언어레벨에서 명시적인 지원을 하지 않으며 package 등의 구분도 없기에... 파일간의 변수 충돌이나 전역공간 변수 난립(global scope pollution)등의 문제가 발생할 요지가 많다. 

보통 이러한 경우를 고려한 JavaScript의 일반적인 모듈 구현은 다음과 같다
(function(exports) {
  "use strict";

  // private 변수
  var name = "I'am a module";

  // 외부에 공개할 public 객체
  var pub = {};

  pub.getName() {
    return name;
  }

  // 외부 공개
  exports.aModule = pub;

})(exports || global);

(exports || global).aModule.getName(); // I'm a module
이러한 구현은 변수를 private 화 할 수 있으며 그로 인한 캡슐화로 모듈 사용이 쉬운 장점이 있지만, 여러개의 모듈을 선언하면서 exports 객체에 프로퍼티가 겹칠 경우 앞서 선언된 공개 속성은 덮어써지는 문제가 있고,  모듈간 의존성이 있을때 의존성을 정의하기가 매우 어렵다. 

그리고 익명 함수와 exports 객체를 사용하는 애매한 코드로 인해 눈에 잘 들어오지 않는다. 이러한 경우를 해결하기 위해 여러 Module Loader가 공개되어 있는데,  그 중 하나가 RequireJS이다. RequireJS에서는 모듈의 고유성과 의존성을 잘 지원하고 있다. 

RequireJS는 AMD 모듈 로딩 표준을 따르기에 기본적으로 모든 모듈이 비동기적이다.

모든 모듈은 비동기적으로 명시적으로 요청하거나 의존성이 있을 때 로딩(Lazy Loading) 된다. 필요한 자바스크립트 파일을 어플리케이션 시작 전 전부 로딩하지 않고, 실제 필요한 때 (사용자의 입력이나 메소드 호출 등의 특별한 경우) 에 로딩하게 할 수 있어서 전체적인 페이지의 속도 향상에도 도움이 된다.  

 

설치법

설치법은 간단하다. 아래와 같이 그냥 스크립트 태그를 쓰면 끝이다.
<script type="text/javascript" data-main="main" src="/js/lib/requirejs.js"></script>
위 태그를 주의깊게 보면 data-main 이라는 속성에 main 이라는 값이 할당되어 있는데, 이 속성은 옵션으로 이 속성을 주게 되면 requirejs가 전부 로딩되면 저 경로의 속성 이름에 해당하는 파일을 자동으로 로딩한 뒤 실행한다. 

경로는 절대 경로가 아니면 requirejs 기준의 상대 경로를 따른다. 모든 모듈의 경로 또한 requirejs가 로딩되는 경로에 상대적이나, 나중에 설정을 통해 바꿀 수 있다. 
  

모듈 이름과 File Scope

RequireJS에서 모든 모듈은 이름이 주어지며, 모듈 이름은 보통 파일 경로가 된다. 

파일 경로는 사실 FileSystem에서 유일하게 존재할 수 있으므로 같은 이름을 가진 모듈이면 모듈끼리 덮어써지거나 충돌할 일이 없어진다. 

예를 들어보자. 

  • 모듈 파일이 /js/controller/main.js 에 존재한다면 모듈 이름은 "/js/controller/main" 이 된다.
  • 모듈 파일이 /js/controller/list.js 로 새로운 모듈을 생성하여 저장하면 역시 모듈 이름은 "/js/controller/list" 가 된다. 
(JavaScript 파일이고, 상대 경로일 경우 .js가 생략된다. )

이제 실제 모듈을 정의하고 사용하는 방법을 알아보자.  

 

RequireJS 모듈 정의 및 사용

이러한 문법을 사용한다.
// 일반적인 RequireJS 모듈 정의
define(function() {

  "use strict";

  var exports = {
    version: "1.0"
  };

  var private1 = "private1";
  var private2 = "private2";

  exports.doSomething = function() { /* ... do something ... */ };
  exports.stopSomething = function() { /* ... stop something ... */ };

  return exports;
});
모듈의 정의에는 define 이라는 글로벌 함수를 사용하며 인자로 함수 하나를 받는데 그 함수에서 반환하는 객체가 모듈이 된다. 

인자 함수는 일종의 객체 팩토리인 셈이다. 

JavaScript는 함수 자체가 스코프를 생성하므로 이 안에서 필요한 만큼의 private 를 선언하고, 외부 공개가 필요한 객체나 함수는 return 으로 반환하면 된다. 다수를 공개하고 싶다면 객체 형식으로 묶어 반환하면 된다. 정리해보면 다음과 같은 코드이다.
// 의존성이 없는 모듈의 정의
define([팩토리 함수]);
그런데 잊은게 있다. 

분명 앞에서 RequireJS는 모듈간의 의존성을 정의하는 방법을 제공한다고 했다. 이대로는 아까 순수하게 JavaScript로 만든 모듈 코드와 별반 다를게 없어 보인다. 

물론 의존성을 줄 수 있다. 

팩토리 함수 앞 인자로 생성될 모듈이 "의존하고 있는 모듈 이름을 문자열로 담은 배열" 을 주면 된다.
// 의존성이 있는 모듈 정의
define([의존 모듈 이름 배열], [팩토리 함수]);
한번 실제 모듈 코드를 만들어 보자. 일단 파일 구조는 다음과 같다고 가정한다.
  • /main.js
  • /App.js
  • /sub/Logger.js
  • /sub/MainController.js
먼저 만들 것은 간단한 로그 모듈이다. 이 코드는 Logger.js 이다
// @file Logger.js
define(function() {

  "use strict";

  var console = global.console || window.console;
  var exports = {
    version: "0.1.0",
    author: "javarouka"
  };

  exports.log = function() {
    var args = Array.prototype.unshift.call(arguments, new Date().toLocaleString());
    console.log.apply(console, arguments);
  };

  return exports;

})
로그 모듈로 일반적인 로그에 앞에 로그가 찍히는 현재 날자를 추가해준다. 

팩토리 함수 안에 var로 선언된 것들은 private 으로 내부에서만 사용하며, exports 객체의 속성으로 지정된 log 함수만 공개되어 외부에서는 log를 통해 이 모듈을 사용할 수 있다. 

이제 이 로그 모듈에 의존성이 있는 모듈을 만들어보자.
// @file MainController.js
// @dependency Logger.js
define(["sub/Logger"], function(logger) {

  "use strict";

  var exports = {
    type: "controller",
    name: "Main"
  };

  var bindEvent = function() {
    logger.log("bind event...")
    // do something
  };

  var view = function() {
    logger.log("render ui");
    // do something
  };

  exports.execute = function(routeParameters) {
    logger.log(exports.name + " controller execute...");
    // do something
  }

  return exports;

});
일단 팩토리 함수 앞에 인자로 의존성 모듈의 이름 배열을 주는데 상대 경로일 경우 .js 확장자를 생략해야 한다. "sub/Logger" 라고 주면 된다. 

주게 되면 팩토리 함수의 인자로 전달되는데 배열에 지정한 순서대로 전달된다. 

이 모듈에서는 logger 라는 변수로 사용하고 있다. 마지막으로 App.js.
// @file ApplicationContext.js
// @dependency sub/MainController.js
define(["sub/MainController"], function(main) {

  "use strict";

  // do something...

});
App.js 는 MainController.js에 의존성이 있다. App.js 가 로딩되려면 의존성에 따라
  1. Logger.js
  2. MainController.js
  3. App.js
순서대로 로딩되게 될 것이다. 

이제 실제 코드를 사용할 main.js 를 작성하자
// @file main.js
// @dependency App.js, sub/Logger.js
require([ "App", "sub/Logger" ], function(app, logger) {

  "use strict";

  app.start();
  logger.log("Application start");

}, function(err) {
  // ERROR handling
});
모듈 정의가 아닌 단순히 코드를 실행할 때는 require 함수를 사용한다. 

require 는 define과 비슷하게 첫번째 인자로 의존성, 두번째 인자로 실행 코드 함수, 세번째 인자는 옵션으로 에러 핸들러이다. 

실행 코드 함수에서 코드상에서 잡을 수 있는 오류가 나거나,  로딩에 실패할 경우 실행된다.  

여기서 짚고 넘어갈 것이 있다. Logger.js 는 두 부분에서 의존성이 있다. 

이 경우에는 먼저 로딩되는 모듈이나 코드에서 한번 로딩되면, 그 다음에는 모듈을 다시 로딩하지 않고 로딩된 모듈을 다시 사용한다. 

Java Spring의 싱글톤 레지스트리와 살짝 비슷하다. 

정리하면 다음과 같다.
  • define: 모듈을 정의할 때
  • require: 정의된 모듈에 의존성을 주는 코드를 작성할 때
이제 대략적인 사용법은 다 정리된 것 같다. 다음은 설정법을 살펴보자 
  

환경 설정

RequireJS는 몇가지 설정을 통해 사용자의 환경에 더욱 잘 조정해 맞출 수 있다. 

문법은 다음과 같다.
// 이 코드를 RequireJS가 로딩된 뒤 기타 모듈을 로딩하기 전에 둔다.
require.config({
  baseUrl: "/js/some/path", // 모듈을 로딩할 기본 패스를 지정한다.

  // 모듈의 기본 패스를 지정한다
  // 모듈의 이름과 실제 경로를 매핑할 수 있어 별칭(alias) 기능도 할 수 있다
  paths: {
    "javarouka": "modules/javarouka", // 이 모듈은 /js/some/path/module/javarouka.js 경로.

    // 모듈 패스를 배열로 주게 되면 먼저 앞의 URL로 로딩해보고 안되면 다음 경로에서 로딩한다.
    // CDN 등을 사용할 때 좋다.
    "YIHanghee": [
      "https://cdn.example.com/YIHanghee",
      "modules/YIHanghee"
     ]
  },
  waitSeconds: 15 // 모듈의 로딩 시간을 지정한다. 이 시간을 초과하면 Timeout Error 가 throw 된다
});
이러한 설정 파일은 다음과 같이 별도의 분리된 파일로 나눌 수 있다
<script type="text/javascript">
var require = {
  // 설정 내용
};
</script>
<script type="text/javascript" src="/js/requirejs.js"></script>
다음에는 RequireJS 형식으로 작성되지 않은 다른 코드를 RequireJS에 포함시켜 사용하며 코드간 의존성을 주는 방법을 알아보겠다.

 

구 소스코드(global-traditional)와 같이 사용하는 법 - Shim

그런데 ReuireJS를 실무에 바로 적용하려면 어려움이 따른다. 바로 하위 호환성 문제이다.

기존의 구(global - traditional) 코드 대부분은 아마도 define이나 require를 사용하지 않은 소스코드가 많다. 그리고 그것들도 나름의 의존성을 가지고 있을 수 있다. 

이것들을 RequireJS에서도 온전히 사용하려면 그냥은 안된다. 이것을 지원하기 위해 RequireJS에는 shim 이라는 기능이 있다. 설정 파일에 shim 속성으로 미리 구 코드의 의존성을 정의할 수 있다. 

문법은 다음과 같다.
requirejs.config({
  paths: {
    // jquery 로딩 시 필요한 경로를 지정한다.
    'jquery': [ 
      '/js/jquery', 
      '//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min' 
    ],
    // underscore 도 마찬가지.
    'underscore': [ 
      '/js/underscore.min', 
      '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.2/underscore-min' 
    ],
    // backbone
    'backbone': [
      'js/backbone.min',
      '//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min'
    ]
  },
  shim: {
    // underscore에 대한 shim 속성을 정의한다.
    'underscore': {
      // 반환될 객체는 exports속성으로 정의한다.
      // underscore는 아래와 같은 _ 이름으로 공개되는 것이 사용이 직관적이다.
      exports: function() { return _; }
    },
    // shim 속성의 키로 모듈 이름을 지정하고 내부 속성을 나열한다.
    // backbone은 underscore와 jquery 에 의존성이 있다.
    'backbone': {
      // 백본이 의존하는 underscore와 jquery를 deps 속성에 정의
      // 이름은 위에 이미 지정한 별칭(키 이름)으로 해도 된다.
      deps: ['underscore', 'jquery'],
      // 반환될 객체를 정의한다.
      // 문자열로 줄 경우,
      // 글로벌 객체에서 저 속성을 키로 하는 객체가 모듈이 된다.
      // 함수를 줄 경우,
      // 함수의 실행 결과로 리턴되는 객체가 모듈이 된다.
      exports: 'Backbone'
    }
  }
});
먼저 모듈이 로딩될 경로를 paths 속성의 키로 정의한 뒤 shim 속성에서 정의한 코드에 대한 의존성을 정의한다. 


"backbone" 키로 지정된 deps 속성에는 앞에서 했던 define 처럼 배열 형태로 의존성을 정의하고, exports 속성으로 팩토리 함수를 정의했다. 

이렇게 설정해두면 "backbone" 이라는 모듈 이름으로 RequireJS 모듈처럼 사용을 할 수 있다.
exports 속성에 문자열을 주면 그 문자열에 해당하는 전역의 속성이 define에서 팩토리 함수에서 리턴하는 객체가 되며, 함수를 줄 경우 반환되는 객체를 지정해줄 수 있다. 

exports에 함수를 지정하는 경우는 팩토리 함수와 동일하게 이 shim 모듈이 반환하는 모듈을 조정할 때 유용하다. 

가령 prototype.js와 jQuery를 같이  사용할 경우에는 $ 변수 충돌이 일어나므로 반드시 jQuery에서 prototype.js를 로딩하기 전에 jQuery.noConflict 를 호출해야 한다. 

이럴 경우 RequireJS에서는
"jquery": {
  exports: function() {
    var jQuery = jQuery.noConflict();
    return jQuery;
  }
},
"prototype": {
  deps: [ "jquery" ],
  exports: "$"
}
과 같은 방식을 적용할 수 있겠다. 두 모듈이 안전하게 로딩될 것이다.

 

마치며

JavaScript 는 사용하기 편한만큼 편함에 너무 의존하다보면 돌이킬 수 없는 스파게티코드나 중복 코드 발생이 많아질 수 있다. 

RequireJS는 이러한 경우의 한 대안이 될 수 있으며, 모듈 프로그래밍을 통해 좀 더 체계적인 프로그래밍을 가능하게 해 주며, 브라우저 지원도 IE6 이상부터 지원하는 괜찮은 호환율을 보여준다. 

지금 자신의 프로젝트를 보라. 

혹시 스파게티, 반복 코드가 보인다면, 바로 RequireJS를 한번 고려해볼 때다.

2013. 3. 28.

JavaBeans, VO, DTO?

먼저 정의부터 보면......

JavaBeans

위 링크들에 설명된 규칙을 따라 디자인된 자바 객체를 말합니다.
핵심은 "전달 혹은 조작이 가능하도록 속성에 대한 접근자를 제공하며 기본 생성자를 반드시 갖는 객체" 라고 생각되네요.

VO (Value Object)

값 개체 그 자체를 말한다고 합니다. 전 일반적으로 JavaBeans 규약을 따르는(그냥 기본 생성자에 get, set 로 필드에 접근할 수 있다면) 그것을 VO라 불렀는데,
좀더 정확하게는 그런 것이 아니더군요.
값 자체를 나타내며 일반적으로 자바에서는 다른 값들의 집합 객체으로 표현됩니다. 그리고  불변(immutable)입니다!!

이펙티브 자바 책에서도 특별한 이유가 없다면 모든 값 객체는 불변으로 디자인 하는걸 추천했던 것 같습니다. 만일 새로운 값이 필요하다면 새로 new 연산자를 쓰는 것이 추천된다고 합니다

흥미로운 포스트 하나 링크합니다


DTO(Data Transfer Object)

예전 EJB 에서 사용되던 패턴이라고 합니다. 각 계층간 데이터 교환 시에 데이터를 직접 하나하나 전달하거나 가져가지 않고 객체를 하나 만들어서 그것을 전달하는 패턴이죠. EJB에서는 각 계층이 네트워크로 묶일 때가 많아서 계층간 작은 데이터의 작은 교환이 성능 저하로 이어지기에 DTO가 필수였다고 하는군요.
DTO에는 절대 비즈니스 로직같은 특정 액션이 들어가서는 안되고 정말 단순한 값의 전달만을 위한 메서드만을 허용한다고 합니다. 대부분 위에 이야기한 JavaBeans 규약을 따라 만들어지고 사용됩니다. 개념이나 목적의 차이가 있을 뿐 내부 구현은 큰 차이가 없기에 보통 VO라고 부르는 등 많이 섞어 부릅니다.

요약하면

"각 계층간 데이터 교환을 위한 자바빈즈"

최근 풍성한 도메인 방식이 유행하고 있는데 그런 방식에서는 거의 사용될일이 없다고도 볼 수 있겠네요.

참고자료