Guava Cache

 

cache을 사용하는가?

캐시의 목적은 응답 속도의 향상을 위해서이다. 매번 DB을 쿼리하여 데이터를 가져온다면 속도가 높을 수가 없다. 이를 줄이기 위한 방법 중 하나는 DB의 성능 향상도 있겠지만 근본적이 방법은 DB 쿼리를 줄이는 것이다. , 자주 사용되는 데이터를 메모리에 저장해 두었다가 사용하면 속도가 월등히 빨라 질것이다.

캐시의 기본적인 형태는 /값 형태의 데이터 구조로 표현된다. 데이터를 호출하는 측에서 키를 캐시에 넘기면 그에 해당하는 데이터가 반환되는 구조이다.

 

 그럼 어떤 데이터를 Cache 해야 하나?

1.       캐시에 들어갈 데이터는 자주 사용하면서도 변경은 자주 일어나지 않는(즉 읽기는 많지만 쓰기는 적은) 데이터가 적합하다.

-       변경이 자주 일어나는 데이터일 경우 변경 될 때 마다 동기화 시키기 위해 캐시와 DB간의 트랜잭션이 자주 발생하여 오히려 성능 저하가 발생할 수 있다.

2.       데이터의 크기가 너무 크지 않은 것을 선택하는 것이 적합하다.

-       자주 사용하는 데이터를 Caching할수록 성능 향상에 도움이 될 것인데, 크기가 너무 크다면 그만큼 메모리를 많이 소모하게 되므로 애플리케이션의 메모리 성능에 악영향을 미칠 수 있기 때문이다.

 

Guava Cache?

 

Google Guava Library : https://github.com/google/guava

 

GoogleGuava Cache는 캐시를 쉽게 사용할 수 있도록 다양한 기능을 제공하는 오픈 소스 라이브러리이다. 간단한 코드를 통해  캐시 크기, 캐시 시간, 데이터 로딩 방법, 데이터 Refresh 방법 등을 제어할 수 있다.

Goolge Guava Apache Commons 에서 제공하지 않는 유용한 Utility성 기능들이 상당히 많다

Cache expire되더라도 DB 등의 요청은 한 번만 날라가고 그 뒤에 동시에 들어온 데이터 요청은 첫번째 요청이 끝나 캐시 데이터가 다 채워진 그 결과만 받아가게 처리하여 부하를 줄여주는 역할을 할 수 있다.

 

Cache Type

Guava에서는 2가지 타입의 cache을 제공한다.

-       LoadingCache 

n   캐시 미스가 발생하면 자동으로 데이터를 로드 한다.

n   LoadingCache.get(key)을 호출하면 key에 해당하는 데이터를 반환하는데, 데이터가 없다면 먼저 데이터 로딩을 수행한다.

-       Cache : 데이터를 자동으로 로드 하지 않는다.

위 두 타입 중에 LoadingCache을 많이 사용한다.

 

간단한 LoadingCache 구현

 

1

2

3

4

5

6

7

8

private static CacheLoader<String, String> loader = new CacheLoader<String, String>() {

@Override

    public String load(String key) throws Exception {

        return key.toUpperCase();

    }

};

 

private static LoadingCache<String, String> cache = CacheBuilder.newBuilder().build(loader);

cs

 

 

LoadingCache CacheBuilder을 이용해 구현한다. build() 메서드 호출 시 CacheLoader 구현체를 넘겨야 한다.

CacheLoader key값을 이용해 원천 데이터를 캐시에 Load하는 역할을 수행한다. CacheLoader을 구현할 때는 load() 메서드를 Override하여 원천 데이터를 가져오는 코드를 구현해야 한다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@Test

public void testGet() throws Exception {

assertThat(cache.size()).isZero();

 

// cache miss 발생한다. CacheLoader 호출한다.

String result = cache.getUnchecked("simple test");

assertThat(result).isEqualTo("SIMPLE TEST");

 

assertThat(cache.size()).isEqualTo(1);

 

// cache hit 발생한다

result = cache.getUnchecked("simple test");

assertThat(result).isEqualTo("SIMPLE TEST");

}

Colored by Color Scripter

cs

 

최초 캐시를 호출하기 전에는 캐시 사이즈가 0인 상태이다. 이후 캐시에서 값을 가져오기위해 getUnchecked("simple test");을 호출하면 캐시 miss 가 발생하여 CacheLoader을 호출하여 값을 가져오게 된다. 가져온 값은 캐시에 저장되므로 캐시의 사이즈는 1로 증가하게 된다. 이후 같은 키 값("simple test")으로 데이터를 호출하게 되면 캐시 hit이 발생하여 CacheLoader을 거치지 않고 캐시에 저장되어 있는 값을 반환하게 된다. 아래는 "simple test"키에 대한 호출이 한 번 일어난 이후의 cache 객체의 상태를 보여준다.

 

 

LocalCache key value가 저장되어 있는걸 확인할 수 있다.

 

동시성

캐시 인스턴스는 내부적으로 ConcurrentHashMap과 유사하게 구현되어 있고 thread-safe을 보장한다. 동시에 여러 개의 스레드가 같은 key에 대해서 요청을 하더라도 CacheLoader load() 메서드는 각 key에 대해 한번만 호출된다. 데이터를 요청한 모든 스레드에게 호출 결과가 반환되고, 해당 값은 캐시에 저장된다.  (using the equivalent of putIfAbsent)

 

Method

캐시에서 키와 관련된 값을 가져오는 메서드 2가지 있다.

-        get() : 데이터를 로딩하는 중 Checked Exception이 발생할 경우 ExecutionException을 던진다. 그러므로 예외 처리 코드를 반드시 작성해주어야 한다.

-       getUnchecked() : get()과 달리 CheckedException을 던지지 않는다. 그러므로 CacheLoader CheckedException을 던지지 않는 상황에서만 사용해야 한다. 예외가 발생하면 RuntimeException을 던진다.

 

Eviction

리소스 제약으로 모든 데이터를 캐시 할 수 없다. 그렇기 때문에 어떤 시점에 유지할 필요하 없는 데이터를 없애는 시점을 결정해야 한다. Guava에서는 아래와 같이 3가지 방법을 제공한다.

-       size-based eviction : 캐시 사이즈의 제한을 설정하여 제거

-       time-based eviction: 시간 기반으로 제거

-       reference-based eviction: 참조 기반으로 제거

 

1.       size-based eviction

캐시가 maximumSize (long)으로 설정한 크기 이상으로 커지면 최근에 또는 매우 자주 사용되지 않는 항목을 제거하려고 시도한다. 또는 다른 캐시 항목에 다른 "가중치"가 있는 경우 weigher(Weigher)가 있는 가중치 함수와 maximumWeight가 있는 최대 캐시 가중치를 지정할 수 있습니다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()

        .maximumSize(1000)

           .maximumWeight(100000)

           .weigher(new Weigher<Key, Graph>() {

              public int weigh(Key k, Graph g) {

                    return g.vertices().size();

              }

        })

           .build(

               new CacheLoader<Key, Graph>() {

                     public Graph load(Key key) { // no checked exception

                           return createExpensiveGraph(key);

                     }

                });

Colored by Color Scripter

cs

 

-       maximumSize(long) : 캐시에 저장될 엔트리의 개수를 설정한다. 사이즈가 100이면 100개의 키-값 쌍이 저장될 수 있다.

-       maximumWeight(long) : 캐시에 저장될 엔트리의 크기를 설정한다.

-       weigher(Weigher) : 가중치

 

2.       time-based eviction

현재 캐시에 저장되어 있는 각각의 엔트리에 대해 아래의 기준 시간 이후에 지정한 시간이 지나면 자동으로 캐시에서 삭제한다

 

1

2

3

4

5

LoadingCache<String, NaverUser> userCache = CacheBuilder

            .newBuild()

            .expireAfterAccess(10, TimeUnit.MINUTES)

            .expireAfterWrite(10, TimeUnit.MINUTES)

            .build(loader);

Colored by Color Scripter

cs

-       expireAfterAccess(long, TimeUnit)

n   엔트리가 처음으로 생성 된 시간

n   가장 최근에 엔트리의 값이 바뀐 시간

n   가장 마지막으로 접근했던 시간

-       expireAfterWrite(long, TimeUnit)

n   엔트리가 처음으로 생성 된 시간

n   가장 최근에 엔트리의 값이 바뀐 시간

 

3.       reference-based eviction

Guava를 사용하면 항목의 GC를 허용하거나 키 또는 값에 약한 참조를 사용하거나 값에 대해 부드러운 참조를 사용하여 캐시를 설정할 수 있습니다.

 

-       weakKeys(),weakValues() : 값이나 키가 weakReference로 감싸진다. reference가 없어지고 weakreference만 남아있으면 바로 GC된다..

-       softKeys(),softValues() : 값이나 키가 softReference로 감싸진다.

VO vs DTO

 

VO(Value Object)

-       데이터 그 자체로 의미 있는 것을 담고 있는 객체이다.

-       DTO와 동일한 개념이나 차이점은 Read–Only 속성 객체이다.

-       간단한 독립체( Entity ) 의미하는 작은 객체를 의미한다

-       관계데이터베이스의 레코드에 대응되는 자바클래스이다.

n   형태는 Database레코드를 구성하는 필드들을 VO Attribute로 하고 해당 변수에 접근 할 수 있는 Getter Setter 메소드의 조합으로 클래스를 형성되어진 클래스이다.

n   거의 불변성을 가지고 equals()로 비교할 때 객체의 모든 값을 비교해야 한다.

 

색상으로 예를 들어보겠다. 색상 중에 빨강과 초록을 RGBA로 나타내면 RGBA(255,0,0,0) RGBA(0,255,0,0) 표현된다. 나아가서 코드명이 붙은 색상도 존재할 거이다. 코드로 보면 아래와 같다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

class Color {

 

private int R,G,B,A;

 

public Color(int r,int g, int b, int a){…}

 

//getters and setters

 

public final static Color RED = new Color(255,0,0);

 

public final static Color GREEN = new Color(0,255,0,0);

 

}

Colored by Color Scripter

cs

 

위와 같은 색상 객체가 있고 빨강과 초록이 있다. 코드에서 빨강 또는 초록을 사용할 때, Color.RED, Color.GREEN 를 사용하면 된다. , 값 객체 Color.RED, Color.GREEN 자체로서 의미가 있게 된다.

 

DTO(Data Transfer Object)

-       전송되는 데이터의 컨테이너이다.

-       VO와 동일하게 데이터를 저장하여 사용하도록 하는 부분에서 필요하다.

-       VO와 비교를 하여 보면 DTO는 같은 시스템에서 사용되는 것이 아닌 다른 시스템으로 전달하는 작업을 처리하는 객체이다.

-       Layer간의 통신 용도로 오가는 객체를 말하기도 한다.

-       현재의 개발 환경에서 보통 데이터는 다음과 같이 흐름으로 이동한다.

n   서버 측 : Database Column Data -> DTO -> API(JSON or XML) -> Client

n   클라이언트 측 : Server -> API(JSON or XML) -> DTO -> View or Local Database System

 

VO DTO 정리 :

-       VO : 사용 되는 값이 객체로 표현 되며, 값 변경이 없는 경우를 말한다.

-       DTO : 데이터의 전송을 위한 객체이며, 비지니스 로직까지 담아서 사용하기 한다.

 

그런데 VO/DTO에 대한 논쟁도 많고 반드시 사용 용도를 구분하는 건 무의미해 보인다. 개발팀 내에서 용도를 정해서 사용하면 좋을 것 같다.

예를 들어 외부 시스템과 데이터 통신을 할 경우에는 DTO, DB에서 가져오는 DataVO로 정의해서 사용한다고 약속을 하면, 향후 DTO/VO를 수정할 경우 좀 더 고민할 여지가 생길것 같다.

 

참고 사이트

http://mrgamza.tistory.com/49

https://martinfowler.com/bliki/ValueObject.html

http://parkgaram.com/blog/vo-%EC%99%80-dto-%EC%B0%A8%EC%9D%B4/

'IT > JAVA' 카테고리의 다른 글

GC 튜닝 절차  (0) 2018.11.09
Java Method Signature  (0) 2017.12.08
String VS SringBuffer VS StringBuilder  (0) 2016.10.14
Annotaion  (0) 2016.05.09

 

 Handlebars  코딩 해보기

 

1)    기본적인 구조를 통해 이해

 

Handlebar 템플릿은 아래처럼 script태그 안에 type “text/x-handlebars-template” 등으로 설정하고 위치시킨다. 그리고 템플릿 구조에 {{ }} 감싸진 부분이 데이터가 binding되는 부분이다.

{{#users}} {{/users}} 안에 들어있는 html 태그들은 items라는 배열의 길이만큼 반복하게 된다. 그리고 내부의 {{name}},{{id}},{{email}} 배열 요소 객체의 name,id,email 속성값이 바인딩 된다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<script id="entry-template" type="text/x-handlebars-template">

<table>

    <thead> 

        <th>이름</th> 

        <th>아이디</th> 

        <th>메일주소</th> 

    </thead> 

    <tbody> 

        {{#users}} 

        <tr> 

            <td>{{name}}</td> 

            <td>{{id}}</td> 

            <td><a href="mailto:{{email}}">{{email}}</a></td> 

        </tr> 

        {{/users}} 

    </tbody> 

</table>

</script>

Colored by Color Scripter

cs

 

실제 템플릿과의 바인딩은 자바스크립트를 통해서 이루어진다. 아래는 자바스크립트를 통해 Binding하는 코드이다.

아래 코드를 보면 먼저 앞에서 작성한 Handlebar Template의 내용을 가져온다. Handlebars.compile 메소드를 통해 Template을 컴파일한 뒤, 리턴 된 함수의 Parameter에 바인딩할 데이터를 Parameter로 넣으면 데이터가 BindingHTML Template이 리턴된다. HTML TemplateDOM에 추가하면 끝난다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

//핸들바 템플릿 가져오기

var source = $("#entry-template").html(); 

 

//핸들바 템플릿 컴파일

var template = Handlebars.compile(source); 

 

//핸들바 템플릿에 바인딩할 데이터

var data = {

        users: [

            { name"홍길동1", id: "aaa1", email: "aaa1@gmail.com" },

            { name"홍길동2", id: "aaa2", email: "aaa2@gmail.com" },

            { name"홍길동3", id: "aaa3", email: "aaa3@gmail.com" },

            { name"홍길동4", id: "aaa4", email: "aaa4@gmail.com" },

            { name"홍길동5", id: "aaa5", email: "aaa5@gmail.com" }

        ]

}; 

 

//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성

var html = template(data);

 

//생성된 HTML DOM 주입

$('body').append(html);

Colored by Color Scripter

cs

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//Handlebar 템플릿을 가져오기

var source = $("#entry-template").html();

//Handlebar 템플릿을 precompile

var template = Handlebars.compile(source);

//Handlebar 템플릿에 바인딩할 데이터

var data = {

items:[

{name"shoes", price:"5000", intro:"www.a.com"},

{name"T-shirt", price:"1000", intro:"www.b.com"},

{name"Jeans", price:"2000", intro:"www.c.com"}

]

};

//Handlebar 템플릿에 데이터를 바인딩해서 HTML 생성

var itemList = template(data);

//생성된 HTML DOM 주입

$('body').append(itemList);

cs

 

아래와 같은 결과가 출력된다.

 

1

2

3

4

5

6

이름    아이디    메일주소

홍길동1    aaa1    aaa1@gmail.com

홍길동2    aaa2    aaa2@gmail.com

홍길동3    aaa3    aaa3@gmail.com

홍길동4    aaa4    aaa4@gmail.com

홍길동5    aaa5    aaa5@gmail.com

cs

 

 

참고로 링크 부분의 malito는 저 링크를 클릭했을 때 바로 전자메일을 보낼 수 있게 해주는 태그이다.

 

코드를 통해 알 수 있듯이 실제 템플릿이 데이터를 컴파일하고, 바인딩해주는 부분이 모두 자바스크립트 코드에서 이루어진다. Handlebars.compile 메서드를 이용해 템플릿을 컴파일하고, 리턴 된 함수의 ParameterBinding된 데이터를 Parameter로 넣으면 Binding HTML템플릿이 리턴 된다. 그리고 이 HTML템플릿을 DOM에 추가함으로써 구현이 완료된다.

 

 

2)    사용자 정의 Helper 사용해보기

 

Handlebar Mustache를 기초로 개발되었지만, Mustache와의 차별 점은 바로 사용자 정의 Helper이다. Mustache는 단순하게 주입된 데이터를 바인딩해주는 템플릿 엔진이었다면 Handlebar if, unless 등의 기본 Helper뿐만 아니라 사용자 정의 Helper를 추가해서 좀 더 템플릿 엔진에서 간단한 로직을 포함시킬 수 있다는 것이다. 아래는 기본적인 사용자 정의 Helper예시이다.

 

1

2

{{!-- 사용자 정의 Helper email id 인자로 넘긴다 --}}

<td><a href="mailto:{{email id}}">{{email id}}</a></td>

cs

 

앞의 예시에서 HTML코드에서  email라고만 되었던 부분에 {{email id}} 형태로 바뀌고, idParameter를 전달해주고 있다. , email이라는 이름의 사용자 정의 Helper가 사용되고 있는걸 알 수 있다. 이를 해석하면 email이라는 이름의 사용자 정의 Helper를 호출하면서 Parameterid 값을 전달한다는 의미이다.

전체 코드를 보면 아래와 같다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

<script id="entry-template" type="text/x-handlebars-template">

<table>

    <thead> 

        <th>이름</th> 

        <th>아이디</th> 

        <th>메일주소</th> 

    </thead> 

    <tbody> 

        {{#users}} 

        <tr> 

            <td>{{name}}</td> 

            <td>{{id}}</td> 

            

            {{!-- 사용자 정의 헬퍼인 email id 인자로 넘긴다 --}}

            <td><a href="mailto:{{email id}}">{{email id}}</a></td> 

        </tr> 

        {{/users}} 

    </tbody> 

</table>

</script>

Colored by Color Scripter

cs

 

그리고 {{!--   --}}를 통행 주석을 표시해 주고도 있다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

//핸들바 템플릿 컴파일

var template = Handlebars.compile(source); 

 

//핸들바 템플릿에 바인딩할 데이터

var data = {

        users: [

            { name"홍길동1", id: "aaa1" },

            { name"홍길동2", id: "aaa2" },

            { name"홍길동3", id: "aaa3" },

            { name"홍길동4", id: "aaa4" },

            { name"홍길동5", id: "aaa5" }

        ]

}; 

 

//커스텀 헬퍼 등록 (id 인자로 받아서 전체 이메일 주소를 반환)

Handlebars.registerHelper('email'function (id) {

  return id + "@daum.net";

});

 

//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성

var html = template(data);

 

//생성된 HTML DOM 주입

$('body').append(html);

cs

 

스크립트 부분을 보면 Binding할 데이터에 email이 없는걸 확인할 수 있다. 그 대신 Custom Helper가 등록하는 코드가 구현되고 있는걸 확인할 수 있다. Handlebars.registerHelper라는 메소드를 사용하여 사용자 정의 Helper를 등록하고 있다. 첫번째 ParameterHelper의 이름을 넣고, 두 번째 Parameter에서 Helper의 동작을 정의한다. 위 코드에서는 id을 인자로 받아서 ‘@daum.net’이라는 문자열을 더해주는 로직이다 

결과는 아래와 같다.

 

1

2

3

4

5

6

7

 

이름    아이디    메일주소

홍길동1    aaa1    aaa1@daum.net

홍길동2    aaa2    aaa2@daum.net

홍길동3    aaa3    aaa3@daum.net

홍길동4    aaa4    aaa4@daum.net

홍길동5    aaa5    aaa5@daum.net

cs

 

 

3)    Handlebar에서 제공하는 기본 Helper

 

Handlebar if, unless 등의 기본 Helper와 사용자 정의 Helper를 이용해서 간단한 로직을 구현할 수 있다하지만 if의 경우 오직 true/false만 판별할 수 있기 때문에 복잡한 조건은 Handlebar를 통해서 처리할 수 없다. 사용자 정의 helper를 통해 가능하지만, Temple에 로직을 배제하는 사상과 맞지 않기 때문에 가능하면 사용하지 않는게 좋다.

그럼 Handlebars에서 제공하는 기본 Helper를 알아보자

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

<script id="entry-template" type="text/x-handlebars-template">

<table>

    <thead> 

        <th>이름</th> 

        <th>아이디</th> 

        <th>메일주소</th>

        <th>요소정보</th>

    </thead> 

    <tbody> 

        {{!-- {{#each users}}  {{#users}} 로도 대체 가능하다 --}}

        {{#each users}}

        <tr>

            {{!-- {{name}}  {{this.name}}  같고 {{.}}  현재 name id 포함하고 있는 오브젝트를 가리킨다 --}}

            <td>{{name}}</td> 

            <td>{{id}}</td> 

            

            {{!-- 사용자 정의 헬퍼인 email id 인자로 넘긴다 --}}

            <td><a href="mailto:{{email id}}">{{email id}}</a></td> 

            

            {{!-- 요소의 순번은 @index 혹은 @key 잡을  있는데,

            array object 모두 잡을  있는 key  나아보인다 --}}

            

            {{#if @first}}

                <td> 아이템 ({{@key}} 번째 요소)</td>

            {{else if @last}}

                <td>마지막 아이템 ({{@key}} 번째 요소)</td>

            {{else}}

                <td>중간 아이템 ({{@key}} 번째 요소)</td>

            {{/if}}

        </tr> 

        {{/each}}

    </tbody> 

</table>

</script>

Colored by Color Scripter

cs

 

 

배열 등을 반복해주는 each Helper의 경우에는 #배열명으로 대체가 가능하다. @기호와 함께 몇 번째 인덱스인지도 구분이 가능하다. @first로 첫번째 인덱스를 알 수 있고, @last로 마지막 인덱스를 확인할 수 있다. 또한 해당 인덱스가 몇 번째 인덱스인지, 오브젝트라면 어떤 오브젝트인지 @key를 통해 알 수 있다. 앞에서 설명했듯이 if true/false 만 판별이 가능하며, elseelse if 를 지원하며, unless if의 반대되는 Helper이다.

 

 

4)    partial template 등록 및 사용

 

다른 템플릿엔진처럼 HandlebarHandlebar 템플릿을 다른 Handlebar 템플릿의 일부로 포함시킬 수 잇다. 이를 통해 템플릿의 재 사용성과 가독성을 높일 수 있고, 좀 더 객체지향적인 프로그래밍이 가능하다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

<!-- 핸들바 템플릿 -->

<script id="partial-template" type="text/x-handlebars-template">

    {{!-- #unless 헬퍼는 #if  정반대 기능을 한다--}}

    <h1>리스트 {{#unless users}}<small>사용자 리스트가 없습니다.</small>{{/unless}}</h1>

</script>

 

<!-- 핸들바 템플릿 -->

<script id="entry-template" type="text/x-handlebars-template">

 

{{!-- 조각 템플릿은 #>  사용해서 포함시킬  있다--}}

{{#> commonHeader}}

    partial template 로드 실패시 보여지는 내용

{{/commonHeader}}

 

<table>

    <thead> 

        <th>이름</th> 

        <th>아이디</th> 

        <th>메일주소</th> 

        <th>요소정보</th>

    </thead> 

    <tbody> 

        {{!-- {{#each users}}  {{#users}} 로도 대체 가능하다 --}}

        {{#each users}} 

        <tr> 

            <td>{{name}}</td> 

            <td>{{id}}</td> 

            

            {{!-- 사용자 정의 헬퍼인 email id 인자로 넘긴다 --}}

            <td><a href="mailto:{{email id}}">{{email id}}</a></td> 

            

            {{!-- 요소의 순번은 @index 혹은 @key 잡을  있는데,

            array object 모두 잡을  있는 key  나아보인다 --}}

            

            {{#if @first}}

                <td> 아이템 ({{@key}} 번째 요소)</td>

            {{else if @last}}

                <td>마지막 아이템 ({{@key}} 번째 요소)</td>

            {{else}}

                <td>중간 아이템 ({{@key}} 번째 요소)</td>

            {{/if}}

        </tr> 

        {{/each}}

    </tbody> 

</table>

</script>

Colored by Color Scripter

cs

 

위의 예를 보면, 앞서 나온 예제와는 다른 부분이 있다. Template을 담고 있는 Script 태그가 한 개 더 있다.

 

1

2

3

4

<script id="partial-template" type="text/x-handlebars-template">

{{!-- #unless 헬퍼는 #if  정반대 기능을 한다--}}

<h1>리스트 {{#unless users}}<small>사용자 리스트가 없습니다.</small>{{/unless}}</h1>

</script>

Colored by Color Scripter

cs

 

Entry-template 안을 잘 보면, #> 기호와 함께 partial template을 삽입해 주는 부분을 발견 할 수 있다. 이는 #>와 함께 partial template의 이름을 넣어주면 해당 위치에 partial template이 삽입된다. partial template의 이름은 자바스크립트에서 등록한다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

//핸들바 템플릿 컴파일

var template = Handlebars.compile(source); 

 

//핸들바 템플릿에 바인딩할 데이터

var data = {

        users: [

            { name"홍길동1", id: "aaa1" },

            { name"홍길동2", id: "aaa2" },

            { name"홍길동3", id: "aaa3" },

            { name"홍길동4", id: "aaa4" },

            { name"홍길동5", id: "aaa5" }

        ]

}; 

 

//조각 템플릿을 'commonHeader' 라는 이름으로 등록

Handlebars.registerPartial('commonHeader', partial);

 

//커스텀 헬퍼 등록 (id 인자로 받아서 전체 이메일 주소를 반환)

Handlebars.registerHelper('email'function (id) {

  return id + "@daum.net";

});

 

//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성

var html = template(data);

 

//생성된 HTML DOM 주입

$('body').append(html);

cs

 

위 코드를 보면, entry-template 과 마찬가지로 partial-template을 가져온 뒤, Handlebars.registerPartial 메소드를 사용해서 가져온 TemplatecommonHeader라는 이름으로 등록해주고 있는 것을 확인할 수 있다. 이렇게 등록만 해주면 데이터를 Binding해주는 시점에서 partial template을 삽입해주게 되고, partial template에도 주어진 데이터와 Binding될 부분이 있다면 데이터 Binding역시 처리되게 된다.

 

 

5)    withas 그리고 상대 경로 참조


지금까지 살펴본 내용들은 기본적인 Handlebars의 문법이었다면, 이런 문법을 조합한 좀더 복잡한 구조를 살펴보자. 자주 있는 상황은 아니지만, 이중 혹은 삼중의 Loop을 돌리는 상황에서 상위 Loop의 값을 하위 Loop에서 참조해야 하는 상황에서 활용할 수 있다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

<!-- 핸들바 템플릿 -->

<script id="entry-template" type="text/x-handlebars-template">

 

    <table>

        <thead> 

            <th>이름</th> 

            <th>아이디</th> 

            <th>취미</th>

        </thead> 

        <tbody> 

            {{!-- {{#each users}}  {{#users}} 로도 대체 가능하다 --}}

            {{#each users as |user userId|}} 

                <tr> 

                    <td>{{name}}</td> 

                    <td>{{id}}</td> 

                    <td>

                      {{#each hobbys as |hobby hobbyId|}}

                        {{!-- 처음이 아닌 경우에는 쉼표(,) 넣기 --}}

                        {{#unless @first}}, {{/unless}}

 

                                                {{!-- 상위 이터레이션의 인덱스 넘버를 가져올  --}}

                                                {{@../index}} ==

                        {{!-- 상위 이터레이션의 인덱스 넘버는 아래와 같은 방식도 가능하다 --}}

                        {{userId}}

 

                        {{!-- 상대경로로 참조한 name  myName 이라는 변수로 할당 --}}

                        {{#with ../this.name as |myName|}}

                          {{!-- 상대경로를 참조해서 #hobby 현재값 출력 --}}

                          {{myName}}  {{hobbyId}} 취미 {{hobby}}

                        {{/with}}

                      {{/each}}

                    </td>

                </tr> 

            {{/each}}

        </tbody> 

    </table>

 

</script>

Colored by Color Scripter

cs

 

../ 를 사용해서 상위의 경로를 참조할 수 있고, with as 를 활용해서 이렇게 참조한 상위의 값을 다른 이름으로 할당해서 사용하고 있다.

상위 Loop에서 참조하고 있는 user item에 있는 name myName으로 할당하여 활용하고 있다. 그리고 상대 경로를 참조하여 hobby의 값도 출력해주고 있다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

//핸들바 템플릿 가져오기

var source = $("#entry-template").html();

 

//핸들바 템플릿 컴파일

var template = Handlebars.compile(source); 

 

//핸들바 템플릿에 바인딩할 데이터

var data = {

        users: [

            { name"홍길동", id: "aaa", hobbys: ['축구''야구''농구'] },

            { name"이순신", id: "bbb", hobbys: ['족구''피구''탁구'] },

            { name"이성계", id: "ccc", hobbys: ['수구''배구''농구'] },

            { name"장영실", id: "ddd", hobbys: ['축구''피구''농구'] },

            { name"장보고", id: "eee", hobbys: ['배구''야구''족구'] }

        ]

}; 

 

//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성

var html = template(data);

 

//생성된 HTML DOM 주입

$('body').append(html);

Colored by Color Scripter

cs

 

결과는 아래와 같다.

1

2

3

4

5

6

이름    아이디    취미

홍길동    aaa    0 == 0 홍길동  0 취미 축구 , 0 == 0 홍길동  1 취미 야구 , 0 == 0 홍길동  2 취미 농구

이순신    bbb    1 == 1 이순신  0 취미 족구 , 1 == 1 이순신  1 취미 피구 , 1 == 1 이순신  2 취미 탁구

이성계    ccc    2 == 2 이성계  0 취미 수구 , 2 == 2 이성계  1 취미 배구 , 2 == 2 이성계  2 취미 농구

장영실    ddd    3 == 3 장영실  0 취미 축구 , 3 == 3 장영실  1 취미 피구 , 3 == 3 장영실  2 취미 농구

장보고    eee    4 == 4 장보고  0 취미 배구 , 4 == 4 장보고  1 취미 야구 , 4 == 4 장보고  2 취미 족구

cs

 

 

6)    lookup을 활용한 변수 참조


 lookup Helper를 활용하면 특정 상대 경로에 있는 변수 값을 참조할 수 있다. lookup Helper로 참조할 수 있는 변수 값은 with as 를 활용하는 방법으로 거의 대부분 참조 가능하다. 개인적으로는 with as 를 활용하는 것이 더 명시적이기 때문에 선호한다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

<!-- 핸들바 템플릿 -->

<script id="entry-template" type="text/x-handlebars-template">

 

    <table>

        <thead> 

            <th>이름</th> 

            <th>아이디</th> 

            <th>취미</th>

        </thead> 

        <tbody> 

            {{!-- {{#each users}}  {{#users}} 로도 대체 가능하다 --}}

            {{#each users as |user userId|}} 

                <tr> 

                    <td>{{name}}</td> 

                    <td>{{id}}</td> 

                    <td>

                      {{#each hobbys as |hobby hobbyId|}}

                          {{!-- 

                                                    상대경로를 참조해서 상위 이터레이션의 name 값을 가져오는 것은

                          with 헬퍼를 사용해서 {{#with ../this.name as |myName|}}

                           같이 처리해줄 수도 있고 아래와 같이

                                                    lookup 활용해서 처리해줄 수도 있다.

                                                --}}

                        {{lookup ../this "name"}}  {{hobbyId}} 취미 {{hobby}}

                      {{/each}}

                    </td>

                </tr> 

            {{/each}}

        </tbody> 

    </table>

 

</script>

Colored by Color Scripter

cs

 

Lookup을 통해 상대 경로에  name값을 찾아서 출력해주고 있다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

//핸들바 템플릿 가져오기

var source = $("#entry-template").html();

 

//핸들바 템플릿 컴파일

var template = Handlebars.compile(source); 

 

//핸들바 템플릿에 바인딩할 데이터

var data = {

        users: [

            { name"홍길동", id: "aaa", hobbys: ['축구''야구''농구'] },

            { name"이순신", id: "bbb", hobbys: ['족구''피구''탁구'] },

            { name"이성계", id: "ccc", hobbys: ['수구''배구''농구'] },

            { name"장영실", id: "ddd", hobbys: ['축구''피구''농구'] },

            { name"장보고", id: "eee", hobbys: ['배구''야구''족구'] }

        ]

}; 

 

//핸들바 템플릿에 데이터를 바인딩해서 HTML 생성

var html = template(data);

 

//생성된 HTML DOM 주입

$('body').append(html);

Colored by Color Scripter

cs

 

 

1

2

3

4

5

6

이름    아이디    취미

홍길동    aaa    홍길동  0 취미 축구 홍길동  1 취미 야구 홍길동  2 취미 농구

이순신    bbb    이순신  0 취미 족구 이순신  1 취미 피구 이순신  2 취미 탁구

이성계    ccc    이성계  0 취미 수구 이성계  1 취미 배구 이성계  2 취미 농구

장영실    ddd    장영실  0 취미 축구 장영실  1 취미 피구 장영실  2 취미 농구

장보고    eee    장보고  0 취미 배구 장보고  1 취미 야구 장보고  2 취미 족구

cs

 

 

2.    Handlebar의 장점

 

  • precompile할 수 있기 때문에 성능개선에 도움이 된다.

  • Helper를 자유롭게 추가할 수 있다.

  • JADE와 같은 문법에 비해 친숙하면서도 가독성이 높다.

  • Grunt 등으로 빌드 자동화에 precompile 과정을 포함시키기 쉽다.

  • 템플릿에는 로직 코드를 넣지 않는 것이 일반적이다.

    • 컴파일한 템플릿에서 오류가 발생했을 때 디버깅도 어렵다.

    • 실제 문법에서도 간단한 분기문, 배열, 반복문 정도를 지원한다.

 


 

참고사이트

 

페이지: http://handlebarsjs.com/

깃허브: https://github.com/wycats/handlebars.js/

http://ohgyun.com/427

http://sailboat-d.tistory.com/40

http://jojoldu.tistory.com/23

http://tmondev.blog.me/220398995882

http://programmingsummaries.tistory.com/381

https://blog.outsider.ne.kr/939

http://aroundck.tistory.com/957

http://uncle-bae.blogspot.kr/2016/06/handlebars-expression.html


'IT > Handlebar' 카테고리의 다른 글

Handlebar란  (0) 2017.01.12

+ Recent posts