<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">
	<channel>
		<title>Outsider's Dev Story</title>
		<link>http://blog.outsider.ne.kr/</link>
		<description>Stay Hungry. Stay Foolish. Don't Be Satisfied.</description>
		<language>ko</language>
		<pubDate>Wed, 19 Jun 2013 02:44:31 +0900</pubDate>
		<generator>Textcube 1.9.0 : Beta 6</generator>
		<image>
		<title>Outsider's Dev Story</title>
		<url>http://blog.outsider.ne.kr/attach/1/1303945783.jpg</url>
		<link>http://blog.outsider.ne.kr/</link>
		<width>50</width>
		<height>50</height>
		<description>Stay Hungry. Stay Foolish. Don't Be Satisfied.</description>
		</image>
		<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/rss_outsider_dev" /><feedburner:info uri="rss_outsider_dev" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
			<title>Scala의 partially applied function과 partial function 이해하기</title>
			<link>http://feedproxy.google.com/~r/rss_outsider_dev/~3/y5IZ5FNqvZA/953</link>
			<description>&lt;p&gt;이 글은 Szabolcs Andrási가 작성한 &lt;a href="http://sandrasi-sw.blogspot.kr/2012/03/understanding-scalas-partially-applied.html"&gt;Understanding Scala's partially applied functions and partial functions&lt;/a&gt;를 번역한 글이다. 스칼라 스터디에서 정대원님이 partial function을 설명하는 걸 듣고 관심이 생겨서 찾아보다가 번역을 하게 되었다. 참고로 번역은 원 글을 쓴 Szabolcs Andrási의 허락을 받고 올린다.(항상 그렇듯이 번역품질은 보장 못하니 영어되시는 분들은 원문을....) 그리고 이 글에서는 번역이 애매한 용어는(partial function같은 ) 그냥 원문을 그대로 사용했다.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;&lt;br /&gt;
비슷해 보이는 &lt;strong&gt;partially applied function&lt;/strong&gt;과 &lt;strong&gt;partial function&lt;/strong&gt; 이 두 용어는 함수형 프로그래밍 언어를 처음 사용하는 사람들에게는 꽤 헷갈리는 용어이다. 스칼라가 비슷한 이름의 두 함수 타입의 차이점을 이해하는데 도움이 주지 않으므로 꽤 어렵다. 예제를 통해서 두 함수의 차임점을 설명해 보고자 한다.&lt;/p&gt;

&lt;p&gt;먼저 몇가지 용어를 정의하고 시작하자.&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;스칼라에서 메서드는 자바처럼 클래스(또는 객체)의 일부분이고 완전한 시그니처(이름, 인자, 반환값, 추상메서드가 아니면 메서드를 구현하는 바디)를 가진다. 예를 들면 다음과 같다.&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="brush: scala"&gt;class Math {
  def add(a: Int, b: Int): Int = a + b
}
&lt;/pre&gt;

&lt;p&gt;편의성을 위해서 반환값은 생략할 수 있으므로 스칼라는 메서드의 마지막 표현식에서 타입을 추론한다.&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;반면 스칼라 함수(Scala function)는 사실 클래스다. 스칼라에는 여러가지 갯수의 인자를 갖는 함수를 나타내는 트레이트(trait)들이 있다. 파라미터가 없으면 &lt;code&gt;Function0&lt;/code&gt;이고 파라미터가 하나면 &lt;code&gt;Function1&lt;/code&gt;, 파라미터가 둘이면 &lt;code&gt;Function2&lt;/code&gt;같은 방식으로 &lt;code&gt;Function22&lt;/code&gt;까지 있다. 이러한 트레이트로 함수는 트레이트(실제 파라미터 수에 따락 적합한 트레이트)를 믹스인한 클래스로 메서드를 가진다. 여기서 가장 중요한 메서드는 함수바디의 구현이 있는 &lt;code&gt;apply&lt;/code&gt;메서드다. &lt;code&gt;apply&lt;/code&gt;메서드의 인자 갯수는 믹스인한 트레이트 이름의 수와 완전히 같다. 즉, &lt;code&gt;Function0&lt;/code&gt;이면 0개이고 &lt;code&gt;Function1&lt;/code&gt;이면 1개, &lt;code&gt;Function2&lt;/code&gt;이면 2개 등이다. 예를 들면 다음과 같다.&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="brush: scala"&gt;object add extends Function2[Int, Int, Int] {
  override def apply(a: Int, b: Int): Int = a + b
}
&lt;/pre&gt;

&lt;p&gt;스칼라는 여러 가지 편의문법을 제공하는데 특수한 &lt;code&gt;apply&lt;/code&gt;문법도 편의문법 중 하나이다. 심볼이름 뒤에 괄호와 함께 인자 목록을 작성하면 스칼라는 해당 이름을 가진 객체의 &lt;code&gt;apply&lt;/code&gt; 메서드를 호출하도록 변환한다. 위 예제에서 &lt;code&gt;add.apply(1, 2)&lt;/code&gt; 대신 &lt;code&gt;add(1, 2)&lt;/code&gt;로 작성해서 같은 결과를 얻을 수 있다.&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;함수 리터럴이나 익명 함수는 함수를 정의하는 대체 문법이다. &lt;code&gt;FunctionN&lt;/code&gt; 트레이트를 믹스인하고 &lt;code&gt;apply&lt;/code&gt; 메서드를 오버라이딩하는 새로운 클래스를 생성하는 대신 괄호안에 함수 파라미터명의 목록을 작성하고 우측방향의 화살표를 쓴 다음 함수의 바디를 쓸 수 있다. 예를 들면 다음과 같다.&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="brush: scala"&gt;(a: Int, b: Int) =&amp;gt; a + b
&lt;/pre&gt;

&lt;p&gt;이러한 함수 리터럴에서 스칼라 컴파일러는 &lt;code&gt;FunctionN&lt;/code&gt; 트레이트 중 하나를 믹스인한 &lt;code&gt;function&lt;/code&gt; 객체를 생성한다. &lt;code&gt;=&amp;gt;&lt;/code&gt;의 좌측부분은 파라미터 목록이 되고 우측부분은 &lt;code&gt;apply&lt;/code&gt; 메서드의 구현부가 된다. 여기서 보듯이 &lt;code&gt;function&lt;/code&gt;의 단축형식일 뿐이다.&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;함수값(function value)은 &lt;code&gt;FunctionN&lt;/code&gt; 트레이트를 확장하는 클래스의 인스턴스다. 예를 들어 함수 리터럴은 일단 &lt;code&gt;FunctionN&lt;/code&gt; 트레이트를 믹스인하는 클래스로 컴파일되고 런타임에서 인스턴스화된다. 여기서 생성된 인스턴스는 함수값이다. 함수값이 객체기 때문에 변수에 저장할 수 있지만 동시에 함수이기도 해서 괄호를 사용하는 함수호출 표기법으로 호출할 수 있다.(사실 변수에 할당된 함수클래스 인스턴스의 &lt;code&gt;apply&lt;/code&gt; 메서드를 호출하도록 변환될 것이다.) 예를 들면 다음과 같다.&lt;/li&gt;
&lt;/ul&gt;

&lt;pre class="brush: scala"&gt;val add = (a: Int, b: Int) =&amp;gt; a + b
add(1, 2) // returns 3
&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;Partially applied function&lt;/h2&gt;

&lt;p&gt;이제 용어에 익숙해졌으므로 &lt;strong&gt;partially applied function&lt;/strong&gt;을 얘기할 차례이다. 지금부터는 메서드와 함수라는 용어를 같은 의미로 바꿔가면서 사용할 수 있다. 엄밀히 말하자면 같은 의미가 아니지만 &lt;strong&gt;partially applied functions&lt;/strong&gt;를 설명할 때는 그리 중요하지 않으므로 여기서는 같은 의미로 사용할 것이다.&lt;/p&gt;

&lt;p&gt;일반적으로 메서드나 함수를 호출할 때는 필요한 인자를 전부 전달하고 함수는 받은 인자를 가지고 값을 계산한다. 하지만 스칼라에서는 반드시 인자를 전부 전달해야 하는 것은 아니고 인자 중에서 일부만 전달하거나 인자를 전혀 전달하지 않을 수도 있다. 이러한 경우에 누락된 인자가 있으므로 스칼라는 값을 계산하지 않고 대신 제공된 인자로 &lt;strong&gt;partially applied function&lt;/strong&gt;를 자동적으로 생성하고 &lt;strong&gt;partially applied function&lt;/strong&gt;에서 &lt;strong&gt;function value&lt;/strong&gt;를 생성한다. 생성한 &lt;strong&gt;partially applied function&lt;/strong&gt;은 &lt;code&gt;FunctionN&lt;/code&gt; 트레이트를 믹스인하고 여기서 &lt;code&gt;N&lt;/code&gt;은 누락된 함수 인자의 갯수이다. 생성된 함수의 &lt;code&gt;apply&lt;/code&gt;메서드 바디는 &lt;strong&gt;partially applied function&lt;/strong&gt;의 &lt;code&gt;apply&lt;/code&gt;메서드에 전달된 인자와 원래 제공된 인자로 인자를 모두 채워서 원래 함수의 &lt;code&gt;apply&lt;/code&gt; 메서드를 호출한다. 꽤 복잡해 보이기는 하지만 예제로 보면 별로 복잡하지 않다.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;val add = (a: Int, b: Int) =&amp;gt; a + b
val inc = add(_: Int, 1)
inc(10) // returns 11
&lt;/pre&gt;

&lt;p&gt;이 예제에서는 다음과 같은 과정이 진행된다. &lt;code&gt;Int&lt;/code&gt;인자는 제공하지 않고 다른 인자는 숫자 1로 지정해서 &lt;code&gt;add(Int, Int)&lt;/code&gt; 함수를 호출한다. 여기서 누락한 인자는 언더스코어(_)로 대치했다. 인자 중에서 일부만 제공했기 때문에 &lt;code&gt;add(Int, Int)&lt;/code&gt;를 실행할 수 없어서 &lt;code&gt;inc&lt;/code&gt;는 함수의 결과를 저장하는 것이 아니라 생성된 &lt;strong&gt;partially applied function&lt;/strong&gt;에서 인스턴스화된 &lt;strong&gt;function value&lt;/strong&gt;에 대한 참조를 저장할 것이다. 여기서 생성된 함수의 타입은 무엇일까? 누락된 인자의 갯수가 하나뿐이므로 타입은 &lt;code&gt;Function1&lt;/code&gt;이 될 것이다. &lt;code&gt;Function1&lt;/code&gt;은 두가지 타입의 파라미터를 가지는데 하나는 함수의 입력 파라미터이고 다른 하나는 결과 타입이다. &lt;code&gt;add&lt;/code&gt;의 누락된 인자 타입은 &lt;code&gt;Int&lt;/code&gt;이고 결과 타입도 &lt;code&gt;Int&lt;/code&gt;이므로 두 타입 파라미터는 모두 &lt;code&gt;Int&lt;/code&gt;가 된다. 그리고 이 함수의 &lt;code&gt;apply(Int)&lt;/code&gt; 메서드는 정확히 무엇을 할까? &lt;code&gt;apply&lt;/code&gt;에 전달한 파라미터와 고정된 인자값 &lt;code&gt;1&lt;/code&gt;로 원래의 &lt;code&gt;add&lt;/code&gt;함수를 호출한다. 이제 모든 내용을 알았으므로 직접 &lt;strong&gt;partially applied function&lt;/strong&gt;를 구현할 수도 있지만 스칼라 컴파일러가 이 작업을 해주므로 직접 할 필요는 없다.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;object inc extends Function1[Int, Int] {
  override def apply(v: Int): Int = add(v, 1)
}
inc(10) // returns 11
&lt;/pre&gt;

&lt;p&gt;또 다른 경우는 인자를 전혀 제공하지 않은 경우이다. 누락된 인자를 언더스코어로 나타낼 수 있지만 스칼라는 더 간단한 형식을 지원하고 있다. 전체 파라미터 목록 대신에 함수 이름뒤에 언더스코어 문자를 하나 붙힐 수 있다. 즉, &lt;code&gt;add _&lt;/code&gt;처럼 작성할 수 있다.(함수 이름과 언더스코어 사이에 공백이 있음에 주의해야 한다! 이 공백 문자는 반드시 넣어주어야 하고 공백이 없으면 스칼라 컴파일러가 &lt;code&gt;add_&lt;/code&gt;를 호출하는 함수로 생각할 것이다.) 언더스코어문자까지도 없애서 더 간단하게 사용할 수도 있는데 코드가 다른 함수를 인자로 받는 &lt;strong&gt;higer order function&lt;/strong&gt;인 경우이다. 예를 들어 &lt;code&gt;Array(1, 2, 3).foreach(println)&lt;/code&gt;처럼 &lt;code&gt;GenTraversableOnce&lt;/code&gt; 트레이트가 정의한 &lt;code&gt;foreach&lt;/code&gt; 메서드는 파라미터로 함수를 받는다. 여기서 (&lt;code&gt;scala.Predef&lt;/code&gt; 객체에 정의된) &lt;code&gt;println(Any)&lt;/code&gt; 메서드를 배열의 &lt;code&gt;foreach&lt;/code&gt; 메서드에 전달한다. &lt;code&gt;println&lt;/code&gt; 전달하는 인자가 없고 &lt;code&gt;foreach&lt;/code&gt;가 함수를 받으므로 &lt;code&gt;println&lt;/code&gt;뒤에 언더스코어를 붙히지 않아도 된다. 스칼라 컴파일러는 &lt;code&gt;println&lt;/code&gt; 메서드에서 딱 하나의 인자를 받는 &lt;strong&gt;partially applied function&lt;/strong&gt;를 생성한다. &lt;code&gt;foreach&lt;/code&gt; 메서드가 배열을 순회하면서 각 요소를 &lt;strong&gt;partially applied가 적용된 println&lt;/strong&gt;에 인자로 전달하므로 배열의 각 요소가 콘솔에 출력된다.&lt;/p&gt;

&lt;h2&gt;Partial function&lt;/h2&gt;

&lt;p&gt;이름은 비슷하지만 &lt;strong&gt;partial function&lt;/strong&gt;은 &lt;strong&gt;partially applied function&lt;/strong&gt;와 전혀 관계가 없다. 하지만 수학을 공부했다면 여기서 &lt;strong&gt;partial function&lt;/strong&gt;이 의미하는 것을 알 것이다. &lt;strong&gt;X&lt;/strong&gt;에서 &lt;strong&gt;Y&lt;/strong&gt;로의 &lt;strong&gt;partial function&lt;/strong&gt;은 함수 &lt;strong&gt;ƒ: X' → Y&lt;/strong&gt;이고 여기서 &lt;strong&gt;X'&lt;/strong&gt;는 &lt;strong&gt;X&lt;/strong&gt;의 서브셋이다. 즉, 함수는 &lt;strong&gt;X&lt;/strong&gt;의 모든 요소에 대해 정의된 것이 아니다. 가장 명확한 예제가 나눗셈 함수인데 나눗셈 함수는 숫자를 다른 숫자로 메핑하지만 0에는 매핑된 숫자가 없다. 더 형식적으로 적으면 &lt;strong&gt;ƒ(x, y) = x / y&lt;/strong&gt;이고 &lt;strong&gt;x, y ∈ ℝ&lt;/strong&gt;이고 &lt;strong&gt;y ≠ 0&lt;/strong&gt;이다.&lt;/p&gt;

&lt;p&gt;스칼라의 &lt;strong&gt;partial function&lt;/strong&gt;은 이와 동일하다. 즉, 가능한 입력 인자의 서브셋에 대해서만 정의된 함수이다. 실제로 어떻게 구현하는 지 보기 전에 패턴매칭을 먼저 보자. 패턴매칭은 이 글의 범위를 벗어나기 때문에 여기서는 변수 정의부터 패턴매칭의 표현식까지 스칼라가 패턴을 많이 사용하고 있다는 것을 아는 것만으로도 충분하다. 매턴매칭에서 &lt;code&gt;match&lt;/code&gt; 표현식의 &lt;code&gt;case&lt;/code&gt; 문으로 다수의 경우의 수 중에서 선택할 수 있다.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;args(0) match {
  case "foo" =&amp;gt; println("bar")
  case _ =&amp;gt; println("?")
}
&lt;/pre&gt;

&lt;p&gt;다양한 종류의 패턴이 존재하지만 간단히 말해서 &lt;code&gt;case&lt;/code&gt; 키워드 뒤에 오는 것은 무엇이든지 간에 모두 패턴이라고 할 수 있고 표현식이 해당 패턴에 일치한다면 화살표의 우측부분을 실행한다. &lt;code&gt;case&lt;/code&gt;문(case sequence)을 사용할 수 있는 곳에만 &lt;code&gt;match&lt;/code&gt;표현식을 사용할 수 있는 것은 아니다. 사실 함수리터럴을 사용할 수 있는 곳에는 모두 중괄호안의 &lt;code&gt;case&lt;/code&gt;문을 사용할 수 있다. 그 이유는 이 생성과정이 함수리터럴이기 때문이다. 하지만 좀 더 일반적으로 이야기하자면 일반적인 함수나 메서드가 해당 파라미터 목록으로 딱 하나의 진입점만을 갖는 반면에 &lt;code&gt;case&lt;/code&gt;문은 여러 진입점을 가지면서 각 진입점은 각자의 파라미터(패턴으로 정의한)를 가진다. 또 다른 차이점은 하나의 함수는 딱 하나의 함수 바디를 가지지만 &lt;code&gt;case&lt;/code&gt;문은 &lt;code&gt;case&lt;/code&gt;형식만큼의 다양한 함수 바디를 가진다는 것이다. 즉, 각 &lt;code&gt;case&lt;/code&gt;문(또는 진입점)의 우측부분이 함수바디가 된다. 이러한 일반화를 통해 &lt;code&gt;case&lt;/code&gt;문이 인자의 서브셋(어떤 패턴매칭이든)에서만 해석하고 그외의 다른 경우에는 정의되지 않으므로 &lt;strong&gt;partial function&lt;/strong&gt;이 된다.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;val div: (Double, Double) =&amp;gt; Double = {
  case (x, y) if y != 0 =&amp;gt; x / y
}
&lt;/pre&gt;

&lt;p&gt;위 예제는 앞에서 얘기한 나눗셈 함수의 구현이다. 다양한 사례를 보여주지는 않지만 &lt;code&gt;(x, y) if y != 0&lt;/code&gt;은 &lt;strong&gt;partial function&lt;/strong&gt;을 설명하기에 충분하다. 0으로 나누는 경우를 제외하고는 모든 숫자가 쌍으로 정의되어 있다. &lt;code&gt;div(1, 0)&lt;/code&gt;를 호출하면 무엇을 출력할까? 이 함수에 0으로 나누는 경우에 대한 패턴을 정의하지 않았으므로 &lt;code&gt;MatchError&lt;/code&gt;이 발생하면서 실패할 것이다.&lt;/p&gt;

&lt;p&gt;하지만 이 방법으로 &lt;code&gt;case&lt;/code&gt;문을 정의하는 데는 두가지 작은 문제점이 존재한다. 첫번째 문제는 입력인자가 유효한 값의 서브셋이라면 테스트할 방법이 없다는 것이다.(대신 함수 자체를 호출한다.) 예외를 피하려면 함수호출을 &lt;code&gt;try-catch&lt;/code&gt; 블럭으로 감싸야 하지만 코드가 지저분해진다. 두번째 문제는 &lt;code&gt;case&lt;/code&gt;문에서 사용한 패턴에서 가능한 값을 스칼라 컴파일러가 전부 알고 있다면 모든 경우에 대해서 정의하지 않을 경우 경고 메시지를 보여줄 것이다. 이러한 경우에 누락된 패턴값에서는 런타임 예외가 발생하므로 컴파일러의 경고를 들어야 한다. 하지만 &lt;strong&gt;partial function&lt;/strong&gt;에서는 전부가 아닌 일부만 구현하기를 원할 것이다.&lt;/p&gt;

&lt;p&gt;다행히 스칼라는  이 두가지 문제를 해결하는 우아한 방법을 제공한다. &lt;strong&gt;partial function&lt;/strong&gt;를 생성하고자 한다고 컴파일러에게 알려주면 합수가 입력인자에 정의되었는지 검사할 뿐만 아니라 누락된 패턴을 경고하지 않을 것이다. 그러면 &lt;strong&gt;partial function&lt;/strong&gt;를 원한다고 어떻게 컴파일러에게 알려줄 수 있는가? 아주 간단하다. 그냥 &lt;code&gt;PartialFunction&lt;/code&gt; 타입으로 함수를 정의하기만 하면 된다. &lt;code&gt;div&lt;/code&gt;함수를 다시 보자. 이번에는 실제로 &lt;strong&gt;partial function&lt;/strong&gt;이다.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;val div: PartialFunction[(Double, Double), Double] = {
  case (x, y) if y != 0 =&amp;gt; x /y
}
&lt;/pre&gt;

&lt;p&gt;기본적으로 &lt;code&gt;PartialFunction&lt;/code&gt;은 특별한 &lt;code&gt;Function1&lt;/code&gt;이다. &lt;code&gt;PartialFunction&lt;/code&gt;은 &lt;code&gt;Function1&lt;/code&gt;가 가진 모든 메서드를 가지고 그외 몇가지 편리한 메서드를 더 가지고 있다. 예를 들어 함수가 입력인자에 정의되었는지 검사할 수 있는 &lt;code&gt;isDefinedAt()&lt;/code&gt;가 있다.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;div.isDefinedAt(1, 0) // returns false
&lt;/pre&gt;

&lt;p&gt;이 메서드를 사용해서 &lt;code&gt;try-catch&lt;/code&gt;블럭을 피할수도 있고 &lt;code&gt;MatchError&lt;/code&gt; 예외를 잡는것보다 더 표현력이 좋다.&lt;/p&gt;

&lt;p&gt;스칼라에 익숙해 질수록 &lt;strong&gt;partially applied function&lt;/strong&gt;과 &lt;strong&gt;partial function&lt;/strong&gt;이 많은 곳에서 사용되고 있다는 것을 알게 될 것이다. 예를 들어 &lt;strong&gt;higher order function&lt;/strong&gt;을 사용할 때 실제로는 &lt;strong&gt;partially applied function&lt;/strong&gt;으로 동작한다. 또는 &lt;code&gt;try-catch&lt;/code&gt; 블락에서 &lt;code&gt;catch&lt;/code&gt;블락이 본질적으로는 &lt;code&gt;partial function&lt;/code&gt;이라는 것을 알게될 것이다. 이 글이 &lt;strong&gt;partially applied function&lt;/strong&gt;과 &lt;strong&gt;partial function&lt;/strong&gt;에 대한 속설을 명확하게 하는데 도움이 되기를 바하고 이제 자신의 함수를 직접 작성할 수 있다.(그리고 사용할 수 있는 곳을 찾아야 할 것이다.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.outsider.ne.kr/953?commentInput=true#entry953WriteComment"&gt;댓글 쓰기&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description>
			<category>Scala</category>
			<category>case</category>
			<category>function literal</category>
			<category>Function Value</category>
			<category>partial function</category>
			<category>Partially Applied Function</category>
			<category>pattern match</category>
			<category>Scala</category>
			<author>outsideris@gmail.com (Outsider)</author>
			<guid isPermaLink="false">http://blog.outsider.ne.kr/953</guid>
			<comments>http://blog.outsider.ne.kr/953#entry953comment</comments>
			<pubDate>Wed, 19 Jun 2013 02:41:23 +0900</pubDate>
		<feedburner:origLink>http://blog.outsider.ne.kr/953</feedburner:origLink></item>
		<item>
			<title>grunt-usemin을 사용한 JavaScript/CSS 파일 팩키징</title>
			<link>http://feedproxy.google.com/~r/rss_outsider_dev/~3/TjDvA7bvOc8/952</link>
			<description>&lt;p&gt;요즘은 프론트앤드도 규모가 상당히 커졌기 때문에 프로젝트를 할 때 CSS나 자바스크립트의 패키징에 고민이 많이 되는데 적당한 해결책을 찾지 못하고 있었다. 내가 원하는 패키징 요구사항은 다음과 같았다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;개발할 때는 원하는 대로 모듈화해서 파일별로 나누어서 개발한다.(CSS나 JS)&lt;/li&gt;
&lt;li&gt;프로덕션에 나갈 때는 모듈화된 파일을 하나의 파일로 합쳐야 한다.(의존 라이브러리도 포함해서)&lt;/li&gt;
&lt;li&gt;하나로 합쳐진 파일을 압축할 수 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AMD나 &lt;a href="http://requirejs.org/"&gt;require.js&lt;/a&gt;등의 접근방법도 있었지만 어차피 사용하는 거라면 모를까 패키징을 위해서 AMD를 도입하는 것은 좀 과도해 보였다. require.js를 패키징용으로만 쓸 수 있다고 들었는데 별로 안써봐서 그런지 정확히 어떻게 하는지 잘 모르겠다. 꽤 단순해 보이는 요구사항이지만 이렇게 패키징을 관리하기 어려운 이유는 HTML에서 JS나 CSS를 인클루드하고 있기 때문이다. 그래서 개발에서 사용할 때는 여러 JS 파일을 인클루드하고 있지만 프로덕션에 나갈때는 JS 파일이 하나로 합쳐질 것이므로 HTML도 하나의 파일만 인클루드 해야한다. 오랫동안 이러한 도구를 찾았지만 마땅히 맘에 드는 도구를 찾지 못하고 있었다. Ruby에는 &lt;a href="https://github.com/sstephenson/sprockets"&gt;Sprockets&lt;/a&gt;라는 좋은 패키징 도구가 있다고 들었는데 Ruby환경이 써보기 어려웠고 도구는 여러가지가 있었지만 내 필요사항에 과도해 보이거나 딱 맘에 들지 않았다.&lt;/p&gt;

&lt;h2&gt;grunt-usemin&lt;/h2&gt;

&lt;p&gt;그러다가 찾아낸 것이 &lt;a href="https://github.com/yeoman/grunt-usemin"&gt;usemin&lt;/a&gt;이다. usemin은 &lt;a href="http://yeoman.io/"&gt;Yeoman&lt;/a&gt;에서 만든 &lt;a href="http://gruntjs.com/"&gt;Grunt&lt;/a&gt; 플러그인인데 프론트앤드 빌드에는 거의 Grunt(&lt;a href="http://blog.outsider.ne.kr/910"&gt;grunt는 이전 글 참고&lt;/a&gt;)를 쓰고 있었기 때문에 나에게는 쓰기 편했고 usemin에 기능도 내가 찾던 모습 그대로였다. usemin이 Grunt 플러그인이므로 usemin을 사용하려면 Grunt를 사용하고 있어야 하고 usemin에서 &lt;code&gt;concat&lt;/code&gt;, &lt;code&gt;uglify&lt;/code&gt;, &lt;code&gt;cssmin&lt;/code&gt;, &lt;code&gt;requirejs&lt;/code&gt;를 사용하므로 해당 플러그인이 이미 설치되어 있어야 한다.(물론 플로그인은 사용에 따라 선택해서 사용한다.)&lt;br /&gt;
다음과 같은 &lt;code&gt;package.json&lt;/code&gt;이 있다고 하자.&lt;/p&gt;

&lt;pre class="brush: javascript"&gt;{
  "name": "usemin-example",
  "version": "0.0.0",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib": "~0.7.0"
  }
}
&lt;/pre&gt;

&lt;p&gt;Grunt를 사용하기 위해서 &lt;code&gt;grunt&lt;/code&gt;를 설치하고 공식 플러그인인 &lt;code&gt;grunt-contrib&lt;/code&gt;를 설치를 설치했다. Grunt의 공식플러그인은 모두 &lt;code&gt;grunt-contrib&lt;/code&gt;라는 접두사가 붙는데 &lt;code&gt;grunt-contrib-concat&lt;/code&gt;같은 식으로 필요한 것만 설치할 수도 있지만 여기서는 그냥 공식 플러그인을 모두 설치했다.&lt;/p&gt;

&lt;pre class="brush: javascript"&gt;module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
  });

  // Load the plugin.
  grunt.loadNpmTasks('grunt-contrib');
};
&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;Gruntfile.js&lt;/code&gt;는 위와 같이 작성했다. 기본 뼈대만 있고 앞에서 설치한 &lt;code&gt;grunt-contrib&lt;/code&gt;를 불러왔을 뿐 다른 설정은 추가하지 않았다. 이제 npm 명령어로 &lt;code&gt;grunt-usemin&lt;/code&gt;을 설치한다.&lt;/p&gt;

&lt;pre class="brush: shell"&gt;$ npm install grunt-usemin --save-dev
&lt;/pre&gt;

&lt;h2&gt;usemin 사용&lt;/h2&gt;

&lt;p&gt;예를 들어 다음과 같은 HTML 파일이 있다고 하자.&lt;/p&gt;

&lt;pre class="brush: html"&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;usemin example&amp;lt;/title&amp;gt;
    &amp;lt;link href="../public/css/a.css" media="all" rel="stylesheet" type="text/css" /&amp;gt;
    &amp;lt;link href="../public/css/b.css" media="all" rel="stylesheet" type="text/css" /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;!-- html code here --&amp;gt;
    &amp;lt;!-- html code here --&amp;gt;
    &amp;lt;!-- html code here --&amp;gt;
    &amp;lt;!-- html code here --&amp;gt;

    &amp;lt;script src="../public/js/a.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src="../public/js/b.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src="../public/js/c.js"&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;

&lt;p&gt;HTML에서 필요한 CSS 파일과 JavaScript파일을 인클루드하고 있고 개발할 때는 당연히 필요에 따라 모듈화해서 개발하는 것이 편하다. JavaScript와 CSS 파일은 예제를 위해서 다음과 같은 코드만 간단히 들어 있다.(주석은 파일을 구분하기 위해서 넣은것 뿐이다.)&lt;/p&gt;

&lt;pre class="brush: javascript"&gt;// public/js/a.js
;var from_file_A = function() {
  return "A";
};

// public/js/b.js
;var from_file_B = function() {
  return "B";
};

// public/js/c.js
;var from_file_C = function() {
  return "C";
};
&lt;/pre&gt;

&lt;pre class="brush: css"&gt;/* public/css/a.css */
html,
body {
  margin: 0;
  padding:0;
  border:0
}

/* public/css/b.css */
a {
  text-decoration: none;
}
&lt;/pre&gt;

&lt;p&gt;여기서 usemin을 적용하려면 병합/압축할 JavaScirpt와 CSS 부분을 usemin이 인식할 수 있는 주석으로 감싼다.&lt;/p&gt;

&lt;pre class="brush: html"&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;usemin example&amp;lt;/title&amp;gt;

    &amp;lt;!-- build:css ../public/css/style.min.css --&amp;gt;
    &amp;lt;link href="../public/css/a.css" media="all" rel="stylesheet" type="text/css" /&amp;gt;
    &amp;lt;link href="../public/css/b.css" media="all" rel="stylesheet" type="text/css" /&amp;gt;
    &amp;lt;!-- endbuild --&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;!-- html code here --&amp;gt;
    &amp;lt;!-- html code here --&amp;gt;
    &amp;lt;!-- html code here --&amp;gt;
    &amp;lt;!-- html code here --&amp;gt;

    &amp;lt;!-- build:js ../public/js/script.min.js --&amp;gt;
    &amp;lt;script src="../public/js/a.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src="../public/js/b.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src="../public/js/c.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;!-- endbuild --&amp;gt;

  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;

&lt;p&gt;usemin의 주석은 다음과 같은 형식을 취하고 있다.&lt;/p&gt;

&lt;pre class="brush: html"&gt;&lt;!-- build:&lt;type&gt;(alternate search path) &lt;path&gt; --&gt;
&lt;!-- endbuild --&gt;
&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;type&amp;gt;&lt;/code&gt;은 &lt;code&gt;js&lt;/code&gt;아니면 &lt;code&gt;css&lt;/code&gt;이고 &lt;code&gt;alternate search path&lt;/code&gt;는 옵션값인데 기본적으로 현재 HTML 파일에 상대적으로 파일을 찾는데 추가적으로 찾아야 할 경로를 지정해 줄 수 있다. &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt;는 병합/압축된 파일의 경로 및 파일명을 지정한다. 보다시피 단순히 HTML 주석일 뿐이므로 개발할 때 이상태로 그대로 사용할 수 있다.&lt;/p&gt;

&lt;h2&gt;usemin의 Grunt 설정&lt;/h2&gt;

&lt;p&gt;추가적으로 해야할 작업은 Grunt의 설정이다. usemin은 2가지 단계로 나누어 지는데 &lt;code&gt;useminPrepare&lt;/code&gt;와 &lt;code&gt;usemin&lt;/code&gt;이다. &lt;code&gt;useminPrepare&lt;/code&gt;에서는 &lt;code&gt;concat&lt;/code&gt;, &lt;code&gt;uglify&lt;/code&gt;, &lt;code&gt;cssmin&lt;/code&gt;, &lt;code&gt;requirejs&lt;/code&gt;을 위한 설정을 추가하는 과정이고 &lt;code&gt;usemin&lt;/code&gt;에서 실제 HTML 파일까지 변경한다. Grunt에서 usemin을 사용해야 하므로 다음과 같이 &lt;code&gt;Gruntfile.js&lt;/code&gt;에 &lt;code&gt;grunt-usemin&lt;/code&gt;을 추가해준다.&lt;/p&gt;

&lt;pre class="brush: javascript"&gt;...

// Load the plugin.
grunt.loadNpmTasks('grunt-contrib');
grunt.loadNpmTasks('grunt-usemin'); // 추가!!

...
&lt;/pre&gt;

&lt;h3&gt;useminPrepare&lt;/h3&gt;

&lt;p&gt;먼저 &lt;code&gt;useminPrepare&lt;/code&gt;를 설정하자.&lt;/p&gt;

&lt;pre class="brush: javascript"&gt;...

// Project configuration.
grunt.initConfig({
  'useminPrepare': {
    html: 'views/index.html'
  },
  'usemin': {
    html: ['views/*.html']
  }
});

...
&lt;/pre&gt;

&lt;p&gt;Grunt의 설정에서 &lt;code&gt;useminPrepare&lt;/code&gt;부분을 추가해서 html에 사용할 HTML 파일을 지정한다. 여기서 HTML파일은 앞에서 usemin 주석을 사용한 HTML파일들이다. 위처럼 특정 파일을 지정해도 되고 배열(&lt;code&gt;[]&lt;/code&gt;)을 사용해서 여러파일을 지정할 수도 있고 &lt;code&gt;**/*.html&lt;/code&gt;처럼 한꺼번에 다수의 파일을 지정할 수도 있다.(파일 지정에 관한 규칙은 Grunt의 규칙이다.) 추가로 &lt;code&gt;uglify&lt;/code&gt;와 &lt;code&gt;cssmin&lt;/code&gt; 옵션도 지정할 수 있는데 js/css 압축에 다른 도구를 사용한다면 여기서 지정할 수 있다. &lt;code&gt;dest&lt;/code&gt;로 최종 출력파일을 만들 기본 디렉토리를 지정할 수도 있다. &lt;code&gt;grunt useminPrepare&lt;/code&gt;를 실행하면 다음과 같이 출력된다.&lt;/p&gt;

&lt;pre class="brush: shell"&gt;$ grunt useminPrepare
Running "useminPrepare:html" (useminPrepare) task
Going through views/index.html to update the config
Looking for build script HTML comment blocks

Found a block:
    &lt;!-- build:css ../public/css/style.min.css --&gt;
    &amp;lt;link href="../public/css/a.css" media="all" rel="stylesheet" type="text/css" /&amp;gt;
    &amp;lt;link href="../public/css/b.css" media="all" rel="stylesheet" type="text/css" /&amp;gt;
    &amp;lt;!-- endbuild --&amp;gt;
Updating config with the following assets:
    - public/css/a.css
    - public/css/b.css

Found a block:
    &lt;!-- build:js ../public/js/script.min.js --&gt;
    &amp;lt;script src="../public/js/a.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src="../public/js/b.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script src="../public/js/c.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;!-- endbuild --&amp;gt;
Updating config with the following assets:
    - public/js/a.js
    - public/js/b.js
    - public/js/c.js

Configuration is now:

  cssmin:
  { 'public/css/style.min.css': 'public/css/style.min.css' }

  concat:
  { 'public/css/style.min.css': [ 'public/css/a.css', 'public/css/b.css' ],
  'public/js/script.min.js':
   [ 'public/js/a.js',
     'public/js/b.js',
     'public/js/c.js' ] }

  uglify:
  { 'public/js/script.min.js': 'public/js/script.min.js' }

  requirejs:
  {}

Done, without errors.
&lt;/pre&gt;

&lt;p&gt;지정한 HTML파일에서 usemin 주석을 사용한 부분을 찾아내서 해당 파일들을 찾아온 것을 볼 수 있다. 이 출력내용을 보면 추측할 수 있지만 &lt;code&gt;useminPrepare&lt;/code&gt;는 &lt;code&gt;concat&lt;/code&gt;, &lt;code&gt;uglify&lt;/code&gt;, &lt;code&gt;cssmin&lt;/code&gt;, &lt;code&gt;requirejs&lt;/code&gt;을 위한 설정을 해주는 단계이다. 즉, 실제 &lt;code&gt;useminPrepare&lt;/code&gt;만 실행하면 아무런 일도 일어나지 않고 &lt;code&gt;concat&lt;/code&gt;, &lt;code&gt;uglify&lt;/code&gt;, &lt;code&gt;cssmin&lt;/code&gt;, &lt;code&gt;requirejs&lt;/code&gt;을 실행할 대상 파일과 설정등을 &lt;code&gt;useminPrepare&lt;/code&gt;가 HTML 파일에 설정한 내용을 바탕으로 자동으로 해준다. 그래서 실제로 사용할 때는 &lt;code&gt;grunt useminPrepare concat uglify cssmin&lt;/code&gt;처럼 사용해야 실제로 병합되거나 압축된 파일이 만들어진다. &lt;code&gt;grunt useminPrepare concat uglify cssmin&lt;/code&gt;를 사용한 뒤에 &lt;code&gt;public/js/script.min.js&lt;/code&gt;와 &lt;code&gt;public/css/style.min.css&lt;/code&gt;파일을 열어보면 다음과 같은 파일이 만들어진다.&lt;/p&gt;

&lt;pre class="brush: javascript"&gt;var from_file_A=function(){return"A"},from_file_B=function(){return"B"},from_file_C=function(){return"C"};
&lt;/pre&gt;

&lt;pre class="brush: css"&gt;html,body{margin:0;padding:0;border:0}a{text-decoration:none}
&lt;/pre&gt;

&lt;p&gt;html에서 주석만으로 지정한 파일을 자동으로 한 파일로 합쳐준다.&lt;/p&gt;

&lt;h3&gt;usemin&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;usemin&lt;/code&gt;에서는 HTML에서 주석으로 지정한 부분을 병합/압축된 파일을 참조하도록 교체해준다.&lt;/p&gt;

&lt;pre class="brush: javascript"&gt;...

grunt.initConfig({
  'useminPrepare': {
    html: 'views/index.html'
  },
  'usemin': {
    html: ['views/*.html']
  }
});

...
&lt;/pre&gt;

&lt;p&gt;위처럼 &lt;code&gt;usemin&lt;/code&gt;에 대한 설정을 추가한다. &lt;code&gt;useminPrepare&lt;/code&gt;와 동일한 파일이라면 &lt;code&gt;html: ['&amp;lt;%= useminPrepare.html%&amp;gt;']&lt;/code&gt;와 같이 지정해서 &lt;code&gt;useminPrepare&lt;/code&gt;에 사용한 html파일설정을 그대로 가져올 수도 있다. &lt;code&gt;css&lt;/code&gt;옵션으로 css파일에서도 동일한 교체작업을 수행할 수 있다는데 이건 안해봐서 정확히 어떻게 쓰는지 모르겠고 css에서는 어떤때 교체를 해야하는지도 잘 모르겠다. 그외에도 &lt;code&gt;dirs&lt;/code&gt;로 참조파일을 검색할 폴더를 지정하거나 참조파일에 대한 기본 디렉토리를 &lt;code&gt;basedir&lt;/code&gt;로 변경할 수 있다. 이제 &lt;code&gt;grunt usemin&lt;/code&gt;을 사용하면 HTML이 다음과 같이 변경된다.&lt;/p&gt;

&lt;pre class="brush: html"&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;usemin example&amp;lt;/title&amp;gt;

    &amp;lt;link rel="stylesheet" href="../public/css/style.min.css" media="all"&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;!-- html code here --&amp;gt;
    &amp;lt;!-- html code here --&amp;gt;
    &amp;lt;!-- html code here --&amp;gt;
    &amp;lt;!-- html code here --&amp;gt;

    &amp;lt;script src="../public/js/script.min.js"&amp;gt;&amp;lt;/script&amp;gt;

  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;

&lt;p&gt;보다시피 깔끔하게 압축된 파일만 참조하도록 변경됐다.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;&lt;br /&gt;
실제로 써보니 상당히 유연해서 아주 사용하기 쉽다. 개발할때는 자연스럽게 모듈화해서 여러 파일로 나누어서 개발할 수 있고 단순히 HTML파일의 주석만으로 설정할 수 있기 때문에 병합/압축에 사용할 파일을 개발하면서 간단히 추가하고 뺄 수 있다. 그리고 병합/압축을 하는 과정과 준비하는 과정(&lt;code&gt;useminPrepare&lt;/code&gt;)이 분리되어 있기 때문에 사용하는 기능만을 선택적으로 사용할 수 있다. 개발은 다수의 파일을 인클루드해서 개발하고 최종적으로 프로덕션에 내보낼 때만 Grunt로 변환해서 내보내면 된다.&lt;/p&gt;

&lt;p&gt;Grunt 명령어를 많이 사용해야 하는데 Grunt가 별칭기능을 제공하므로 다음과 같이 &lt;code&gt;build&lt;/code&gt;등의 이름으로 &lt;code&gt;usemin&lt;/code&gt;을 사용하는 과정을 모두 적어두면 &lt;code&gt;grunt build&lt;/code&gt;같은 명령어로 이 모든 과정을 한꺼번에 실행할 수 있다.&lt;/p&gt;

&lt;pre class="brush: javascript"&gt;grunt.registerTask('build', ['useminPrepare', 'concat', 'uglify', 'cssmin', 'usemin']);
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.outsider.ne.kr/952?commentInput=true#entry952WriteComment"&gt;댓글 쓰기&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description>
			<category>Javascript</category>
			<category>build</category>
			<category>concat</category>
			<category>CSS</category>
			<category>cssmin</category>
			<category>grunt</category>
			<category>JavaScript</category>
			<category>mangle</category>
			<category>minify</category>
			<category>packaging</category>
			<category>uglify</category>
			<category>usemin</category>
			<author>outsideris@gmail.com (Outsider)</author>
			<guid isPermaLink="false">http://blog.outsider.ne.kr/952</guid>
			<comments>http://blog.outsider.ne.kr/952#entry952comment</comments>
			<pubDate>Mon, 17 Jun 2013 01:17:53 +0900</pubDate>
		<feedburner:origLink>http://blog.outsider.ne.kr/952</feedburner:origLink></item>
		<item>
			<title>Slick 유닛테스트에서 Session관련 중복코드 제거하기</title>
			<link>http://feedproxy.google.com/~r/rss_outsider_dev/~3/ZQnFI3cX6sA/951</link>
			<description>&lt;p&gt;&lt;a href="http://blog.outsider.ne.kr/950"&gt;이전 포스팅&lt;/a&gt;에서 &lt;a href="http://www.playframework.com/"&gt;Play 2.1&lt;/a&gt;에 &lt;a href="http://slick.typesafe.com/"&gt;Slick&lt;/a&gt;을 연동하는 방법을 설명했는데 이때 작성한 테스트 코드를 다시 보자.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;package models

import org.scalatest.FunSpec
import org.scalatest.matchers.ShouldMatchers
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession

class UserSpec extends FunSpec with ShouldMatchers {

  describe("example") {
    it("사용자를 추가하고 조회한다") {
      Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
        // given
        Users.ddl.create
        Users.add(new User("outsider", "Outsider", "example@gamil.com"))
        // when
        val results = Users.findAll
        // then
        results.size should equal(1)
      }
    }
    it("사용자를 추가하고 조회한다 2") {
      Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
        // given
        Users.ddl.create
        Users.add(new User("outsider", "Outsider", "example@gamil.com"))
        // when
        val results = Users.findAll
        // then
        results.size should equal(1)
      }
    }
  }
}
&lt;/pre&gt;

&lt;p&gt;테스트를 하나 더 추가했는데 여기서 보듯이 Slick을 돌리기 위해서 &lt;code&gt;Session&lt;/code&gt;이 필요하기 때문에 디비 설정을 위한 세션을 유닛테스트마다 추가해 주어야 한다. 즉, 다음과 같은 코드를 모든 유닛테스트마다 추가해 주어야 한다.&lt;/p&gt;

&lt;pre class="brush: java"&gt;Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
  Users.ddl.create
}
&lt;/pre&gt;

&lt;p&gt;보통 하나의 스펙에서는 같은 디비설정을 사용할 것이고 DDL 정의도 동일할 것이므로 이는 상당한 중복을 발생시키고 불필요한 중복코드로 테스트코드를 읽는데도 방해가 된다. 이 문제를 어떻게 해결해야 할지 알 수 없어서 &lt;a href="http://stackoverflow.com/questions/16758228/how-can-i-remove-codes-creating-session-in-unit-test-for-play-framework-and-slic"&gt;스택오버플로우에 질문&lt;/a&gt; 올렸더니 Slick의 메인개발자인 &lt;a href="https://github.com/szeiger"&gt;Stefan Zeiger&lt;/a&gt;가 직접 대답을 해주었다. 덕분에 유닛테스트를 다음과 같이 수정할 수 있다.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;package models

import org.scalatest.{BeforeAndAfter, FunSpec}
import org.scalatest.matchers.ShouldMatchers
import scala.slick.driver.H2Driver.simple._

class UserSpec extends FunSpec with BeforeAndAfter with ShouldMatchers {

  implicit var session: Session = _

  before {
    session = Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver").createSession()
    Users.ddl.create
  }

  after {
    session.close()
  }

  describe("example") {
    it("사용자를 추가하고 조회한다") {
      // given
      Users.add(new User("outsider", "Outsider", "example@gamil.com"))
      // when
      val results = Users.findAll
      // then
      results.size should equal(1)
    }
    it("사용자를 추가하고 조회한다 2") {
      // given
      Users.add(new User("outsider", "Outsider", "example@gamil.com"))
      // when
      val results = Users.findAll
      // then
      results.size should equal(1)
    }
  }
}
&lt;/pre&gt;

&lt;p&gt;일단 임포트문에서 &lt;code&gt;threadLocalSession&lt;/code&gt;을 제거해주고 유닛테스트 앞뒤로 실행할 &lt;code&gt;before&lt;/code&gt;와 &lt;code&gt;after&lt;/code&gt;를 사용하기 위해서 ScalaTest의 &lt;code&gt;BeforeAndAfter&lt;/code&gt; 트레이트를 믹스인한다. 테스트에서 사용할 &lt;code&gt;Session&lt;/code&gt; 변수를 선언하고 &lt;code&gt;before&lt;/code&gt;에서 세션을 생성하고 DDL 문을 생성하고 &lt;code&gt;after&lt;/code&gt;에서 세션을 닫아준다. 이렇게 작성하면 각 유닛테스트에는 테스트에 필요한 코드만 넣을 수 있고 마찬가지로 각 테스트는 독립적인 세션하에서 실행할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.outsider.ne.kr/951?commentInput=true#entry951WriteComment"&gt;댓글 쓰기&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description>
			<category>Scala</category>
			<category>Scala</category>
			<category>ScalaTest</category>
			<category>Slick</category>
			<category>Unit Test</category>
			<author>outsideris@gmail.com (Outsider)</author>
			<guid isPermaLink="false">http://blog.outsider.ne.kr/951</guid>
			<comments>http://blog.outsider.ne.kr/951#entry951comment</comments>
			<pubDate>Fri, 07 Jun 2013 02:12:42 +0900</pubDate>
		<feedburner:origLink>http://blog.outsider.ne.kr/951</feedburner:origLink></item>
		<item>
			<title>Play 2.1에 Slick 연동하기</title>
			<link>http://feedproxy.google.com/~r/rss_outsider_dev/~3/jecDNUb97rA/950</link>
			<description>&lt;p&gt;최근 scala 학습을 위해서 개인 프로젝트에 scala를 사용하면서 &lt;a href="http://www.playframework.com/"&gt;play&lt;/a&gt;를 사용하기로 했다. play는 1.0때 사용하고 2.0은 거의 안써봤는데 scala에서는 웹프레임워크의 선택권도 많지 않고 Play 2.0이 &lt;a href="http://typesafe.com/"&gt;Typesafe&lt;/a&gt; 스택이기도 해서 선택했다. Play 2.0에 대한 생각도 여러가지로 들긴 하는데 이건 좀 더 써보고 하기로 하고 Play는 데이터접근 계층에 &lt;a href="http://www.playframework.com/modules/scala-0.9.1/anorm"&gt;Anorm&lt;/a&gt;을 사용하는데 Typesafe 스택이기도 하면서 요즘 좀더 대세인 &lt;a href="http://slick.typesafe.com/"&gt;Slick&lt;/a&gt;을 사용하기로 했다. Slick에 대한 자세한 얘기는 내용이 많아 다음으로 미루고 Play 2.1에서 Slick을 연동하는 방법을 을 살펴보자.&lt;/p&gt;

&lt;h2&gt;Play Framework와 Slick 연동&lt;/h2&gt;

&lt;p&gt;Slick을 통합하면서 원하는 봐는 간단했다. &lt;strong&gt;데이터접근 계층에 Slick을 사용하고 데이터접근 계층만 별도로 테스트케이스를 작성할 수 있어야 한다. 그리고 어플리케이션에서는 사용하는 RDBMS를 사용하고 테스트케이스에서는 메모리디비등으로 교체해서 테스트할 수 있어야 한다.&lt;/strong&gt; 당연한 요구사항이지만 문서도 많지 않고 관련글이 많지 않아서 이 설정만 하는데도 꽤 어려웠다. 검색해보면 Play2에 Slick을 통합하는 몇가지 플러그인이 있기는 한데 Play 2.0이 자세히 파악안된 상태라서 이 문제를 모듈로 해결하는게 맞는 접근인지 약간 애매했고 어느 모듈이 잘 유지가 되는지 잘 몰랐기에 직접 구현하는 방법을 선택했다. 나중에 &lt;a href="https://twitter.com/ducky_h"&gt;홍덕기&lt;/a&gt;님이 알려주셔서 보니 &lt;a href="https://github.com/freekh/play-slick"&gt;play-slick&lt;/a&gt; 모듈이 괜찮아 보이고 개발자가 Typesafe직원이므로 유지도 잘 될듯 하다.&lt;/p&gt;

&lt;p&gt;검색을 해보면 Slick과 관련한 글이 많지 않은데(Slick은 정말 많지 않으므로 ScalaQuery로 검색해 보는 것도 도움이 된다.) 아주 기본적인 사용법을 설명할 글 정도밖에 없다. 하지만 어플리케이션을 작성한다고 생각하면 당연히 모델클래스가 다수 존재하게 되므로 한 파일에서 상단에서 디비연결을 하고 하단에서 Slick을 사용하는 방식으로는 코드를 작성하지 않는다. 당연히 디비를 관리하는 클래스나 객체가 별도로 있고 여기서 커넥션을 받아서 각 모델클래스가 사용하는 것이 자연스럽고 테스트코드를 작성할 때는 이 디비 관리 클래스의 커넥션 정보를 바꿔서 테스트할 수 있어야 하는데 대다수의 예제는 그냥 하나의 파일에서 디비설정하고 Slick 사용법을 알려주는 정도가 전부였다.&lt;/p&gt;

&lt;p&gt;그나마 찾은 글이 &lt;a href="http://www.blogeek.com.ar/2012/11/24/play-framework-2-2-1-scala-with-slick-made-easy-with-example/"&gt;Play! Framework 2 (2.1) Scala with Slick made easy (With example)&lt;/a&gt;라는 글이었고 이 글은 &lt;a href="https://github.com/slick/slick-examples"&gt;Github에 예제&lt;/a&gt;도 올라와 있어서 참고하기에 괜찮았다. 이 예제는 &lt;a href="http://korean-nerdism.blogspot.kr/2013/02/cake-pattern.html"&gt;Cake 패턴&lt;/a&gt;을 이용하고 있고 나에겐 약간 어색하게 느껴지는 Dal(Data Access Layer)라는 클래스를 사용해서 디비연결을 관리하고 있었고 테스트하는 방법까지 깔끔하게 설명하고 있었다. Play 2.0도 많이 모르고 Cake패턴도 잘 모르는지라 소스구조를 자세히 이해할 수 있지만 잘 돌아갔는데 막상 실제 개발을 하려고 보니 첫번재 테스트케이스는 잘 돌아갔지만 두번째 테스트케이스를 작성하니 세션이 종료되었다는 오류가 발생했다.(예제를 위한 예제일 뿐인가. ㅠㅠ) 그리고 이 예제에서는 테스트케이스마다 &lt;code&gt;FakeApplication(additionalConfiguration = inMemoryDatabase())&lt;/code&gt;를 사용하고 있는데(이는 Play가 제공하는 기능인듯하다.) 이 &lt;code&gt;FakeApplication&lt;/code&gt;이 내부에서 어떻게 동작하든지 상관없이 데이터접근 계층을 테스트하는데 어플리케이션을 실행한다는 게 나로써는 좀 이상해 보였다.&lt;/p&gt;

&lt;p&gt;그러다가 &lt;a href="http://www.jroller.com/ouertani/entry/getting_started_with_play_2"&gt;Getting Started with Play 2.1 , Scala 2.10 and Slick 0.11.1&lt;/a&gt;라는 글을 찾아서 여기에서 제시하는 방법으로 변경했고 지금은 잘 동작하고 있다.&lt;/p&gt;

&lt;h2&gt;Slick 의존성 추가&lt;/h2&gt;

&lt;p&gt;Play 자체를 설명하기 위한 글은 아니므로 &lt;a href="http://blog.outsider.ne.kr/944"&gt;지난번에 소개한 Activator&lt;/a&gt;의 &lt;code&gt;hello-play&lt;/code&gt;로 Play 프로젝트를 구성한다. 다음은 프로젝트의 sbt설정인 &lt;code&gt;project/Build.scala&lt;/code&gt;파일이다.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;import sbt._
import Keys._
import play.Project._

object ApplicationBuild extends Build {

  val appName         = "hello-play"
  val appVersion      = "1.0-SNAPSHOT"

  val appDependencies = Seq(
    // Select Play modules
    jdbc,      // The JDBC connection pool and the play.api.db API
    //anorm,     // Scala RDBMS Library
    //javaJdbc,  // Java database API
    //javaEbean, // Java Ebean plugin
    //javaJpa,   // Java JPA plugin
    //filters,   // A set of built-in filters
    javaCore,  // The core Java API

    // WebJars pull in client-side web libraries
    "org.webjars" % "webjars-play" % "2.1.0",
    "org.webjars" % "bootstrap" % "2.3.1",

    // for slick
    "com.typesafe.slick" %% "slick" % "1.0.0",
    "postgresql" % "postgresql" % "9.1-901-1.jdbc4",
    "org.scalatest" % "scalatest_2.10" % "1.9.1" % "test"

    // Add your own project dependencies in the form:
    // "group" % "artifact" % "version"
  )

  val main = play.Project(appName, appVersion, appDependencies).settings(
    scalaVersion := "2.10.1"
    // Add your own project settings here
  )

}
&lt;/pre&gt;

&lt;p&gt;JDBC를 사용해야 하므로 의존성에서 jdbc의 주석을 풀어주고(기본으로는 주석처리가 되어 있다.) &lt;code&gt;for slick&lt;/code&gt;부분에 의존성을 추가한다. slick을 추가하고 사용할 RDBMS의 jdbc 드라이버를 추가했다.(여기서는 PostgreSQL), 그리고 Play의 기본 테스트프레임워크인 &lt;a href="http://etorreborre.github.io/specs2/"&gt;spec2&lt;/a&gt; 대신 &lt;a href="http://www.scalatest.org/"&gt;ScalaTest&lt;/a&gt;를 사용하기 위해서 ScalaTest도 추가했다.&lt;/p&gt;

&lt;h2&gt;모델 작성&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;model&lt;/code&gt; 패키지를 추가하고 다음과 같은 &lt;code&gt;User.scala&lt;/code&gt;를 작성한다.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;package models

import slick.driver.PostgresDriver.simple._

case class User(
  id: String,
  name: String,
  email: String
)

object Users extends Table[User]("users") {
  def id = column[String]("id")
  def name = column[String]("name")
  def email = column[String]("email")
  def * = id ~ name ~ email &amp;lt;&amp;gt; (User, User.unapply _)
}
&lt;/pre&gt;

&lt;p&gt;이는 일반적인 Slick의 모델 클래스이고 &lt;code&gt;User&lt;/code&gt; 모델은 &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt; 컬럼을 가지고 디비에서는 &lt;code&gt;users&lt;/code&gt;라는 테이블명을 가진다. &lt;code&gt;*&lt;/code&gt; 메서드는 모델 객체를 매핑해주는 함수인데 여기서는 크게 신경쓰지 않아도 된다. 그리고 PostgreSQL을 사용할 것이므로 Slick의 &lt;code&gt;PostgresDriver&lt;/code&gt;를 임포트했다. 이제 &lt;code&gt;app&lt;/code&gt; 디렉토리 아래에 &lt;code&gt;Global&lt;/code&gt;이라는 파일을 만들어서 다음의 내용을 추가한다.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;import play.api.db.DB
import play.api.GlobalSettings

import slick.driver.PostgresDriver.simple._
import Database.threadLocalSession

import play.api.Application
import play.api.Play.current
import models.Users

object Global extends GlobalSettings {

  override def onStart(app: Application) {

    lazy val database = Database.forDataSource(DB.getDataSource())

    database .withSession {
      Users.ddl.create
    }
  }
}
&lt;/pre&gt;

&lt;p&gt;Play 2.0을 이제 막 접해서 자세히는 모르지만 Global은 플레이의 전역 설정을 하는 부분으로 보이고 여기서 어플리케이션을 시작할 때 데이터베이스를 설정하게 된다. 그래서 데이터베이스 연결을 위한 객체를 생성하고 최초 연결시 DDL, 즉 테이블을 정의에 따라 생성한다. 앞에서 &lt;code&gt;User&lt;/code&gt; 모델에 대한 객체만 생성하였지만 Slick이 자동으로 DDL 문을 생성하고 여기서 디비에 테이블을 생성한다.&lt;/p&gt;

&lt;h2&gt;테스트 작성&lt;/h2&gt;

&lt;p&gt;테스트를 작성하기 전에 앞에서 작성했던 &lt;code&gt;models/User.scala&lt;/code&gt;파일의 &lt;code&gt;Users&lt;/code&gt; 객체에 다음과 같이 &lt;code&gt;add&lt;/code&gt;와 &lt;code&gt;findAll&lt;/code&gt; 메서드를 추가한다.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;object Users extends Table[User]("users") {
  def id = column[String]("id")
  def name = column[String]("name")
  def email = column[String]("email")
  def * = id ~ name ~ email &amp;lt;&amp;gt; (User, User.unapply _)

  def add(user: User)(implicit session: Session) = {
    Users.insert(user)
  }

  def findAll()(implicit session: Session) = {
    (for {
      user &amp;lt;- Users
    } yield user).list

  }
}
&lt;/pre&gt;

&lt;p&gt;슬릭은 아직 파악중이라 자세히 설명하기는 어렵지만 위와 같이 &lt;code&gt;Session&lt;/code&gt;이 필요하므로 함수마다 &lt;code&gt;implicit&lt;/code&gt; 파라미터가 필요하다. &lt;code&gt;add&lt;/code&gt; 함수는 &lt;code&gt;User&lt;/code&gt; 모델의 객체를 받아서 &lt;code&gt;users&lt;/code&gt;테이블에 추가하고 &lt;code&gt;findAll&lt;/code&gt;함수에서는 &lt;code&gt;users&lt;/code&gt;테이블의 데이터를 모두 조회해서 &lt;code&gt;List&lt;/code&gt;로 변환해서 반환한다. Slick 자체를 설명하는 글은 아니지만 위처럼 디비를 조회할 때 스칼라의 컬렉션을 다루듯이 for comprehension같은 문법으로 SELECT 문을 작성한다.(참고로 여기서 사용한 방법은 Slick의 lifted embedding을 사용한 방법이다.)&lt;/p&gt;

&lt;p&gt;테스트 폴더에 &lt;code&gt;models&lt;/code&gt; 패키지를 추가하고 &lt;code&gt;UserSpec.scala&lt;/code&gt; 파일을 다음의 내용으로 추가한다.&lt;/p&gt;

&lt;pre class="brush: scala"&gt;package models

import org.scalatest.FunSpec
import org.scalatest.matchers.ShouldMatchers
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession

class UserSpec extends FunSpec with ShouldMatchers {

  describe("example") {
    it("사용자를 추가하고 조회한다") {
      Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
        // given
        Users.ddl.create
        Users.add(new User("outsider", "Outsider", "example@gamil.com"))
        // when
        val results = Users.findAll
        // then
        results.size should equal(1)
      }
    }
  }
}
&lt;/pre&gt;

&lt;p&gt;앞에서 얘기했듯이 &lt;a href="http://www.scalatest.org/"&gt;ScalaTest&lt;/a&gt;로 작성한 유닛테스트고 사용자를 추가한뒤에 다시 가져오는 테스트케이스이다. 그리고 코드에서 보듯이 어플리케이션은 PostgreSQL을 사용하지만 유닛테스트에서는 &lt;a href="http://www.h2database.com/"&gt;H2&lt;/a&gt; 데이터베이스를 사용하고 있다.&lt;/p&gt;

&lt;p style="text-align: center;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/7421112702.gif" width="550" height="112" alt="SBT에서 실행한 테스트가 성공한 화면" title="" /&gt;&lt;/p&gt;

&lt;p&gt;SBT에서 테스트를 실행하면 위와 같이 정상적으로 실행되는 것을 볼 수 있다. 다만 hello-play 템플릿을 생성하면 기본으로 생기는 통합테스트가 몇가지 있는데 어플리케이션을 실행하는 다른 테스트와 충돌이 일어나므로 실행이 제대로 안되면 다른 어플리케이션 테스트는 제거한 뒤에 모델을 테스트해야 한다.(이 문제에 대한 해결은 나중에...)&lt;/p&gt;

&lt;h2&gt;Play 2.1의 디비설정&lt;/h2&gt;

&lt;p&gt;Play 어플리케이션에서 데이터베이스를 사용하려면 &lt;code&gt;conf/application.conf&lt;/code&gt;에서 데이터베이스 설정부분에 주석을 풀고 다음과 같이 수정해 주어야 한다.&lt;/p&gt;

&lt;pre class="brush: java"&gt;db.default.driver=org.postgresql.Driver
db.default.url="jdbc:postgresql://localhost:5432/example"
db.default.user=sa
db.default.password="PASSWORD"
&lt;/pre&gt;

&lt;p&gt;여기서는 PostgreSQL을 사용했지만 다른 데이터베이스를 사용한다면 그에 맞게 설정해 주어야 한다.(물론 각 모델에서 임포트하는 드라이버도 수정해 주어야 한다.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.outsider.ne.kr/950?commentInput=true#entry950WriteComment"&gt;댓글 쓰기&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description>
			<category>Scala</category>
			<category>H2</category>
			<category>JDBC</category>
			<category>Play Framework</category>
			<category>PostgreSQL</category>
			<category>SBT</category>
			<category>Scala</category>
			<category>ScalaTest</category>
			<category>Slick</category>
			<category>Unit Test</category>
			<author>outsideris@gmail.com (Outsider)</author>
			<guid isPermaLink="false">http://blog.outsider.ne.kr/950</guid>
			<comments>http://blog.outsider.ne.kr/950#entry950comment</comments>
			<pubDate>Thu, 06 Jun 2013 23:59:29 +0900</pubDate>
		<feedburner:origLink>http://blog.outsider.ne.kr/950</feedburner:origLink></item>
		<item>
			<title>Github Data Challenge용 개인 프로젝트 : Popular Convention</title>
			<link>http://feedproxy.google.com/~r/rss_outsider_dev/~3/awps08dmNu0/949</link>
			<description>&lt;p&gt;4월 초에 &lt;a href="https://github.com/blog/1450-the-github-data-challenge-ii"&gt;Github에 블로그에 올라온 The GitHub Data Challenge II&lt;/a&gt;의 공지를 보았다. 작년에 처음 진행하고 이번에 두번째인데 해카톤같은 행사는 유심히 보는 편이고 특히 Github는 정말 사랑하는 서비스이기 때문에 할만한게 없을까 고민을 몇일했다. 이때 다른 해커톤 행사도 국내에 있었는데 아무래도 Github쪽에 마음이 더 많이 쏠렸고 오픈소스의 표준 저장소가 되어버린 Github에 정말 많은 데이터들이 쌓여있을 것이므로 이것으로 많은걸 할 수 있을것 같았지만 (항상 그렇듯이) 별다른 생각은 떠오르지 않았다.&lt;/p&gt;

&lt;p&gt;원래 이 글은 사용자들이 접속했다가 버그로 사이트 죽을까봐 대회가 끝나면 올릴려고 했는데 5월 20일쯤에 나온다던 결과는 6월이 되도 올라오지 않기에 그냥 올린다.&lt;/p&gt;

&lt;h2&gt;동기&lt;/h2&gt;

&lt;p&gt;그러다가 작년 말 &lt;a href="http://dailyjs.com/"&gt;DailyJS&lt;/a&gt;에서 JavaScript 개발자들을 대상으로 진행했던 &lt;a href="http://dailyjs.com/2012/12/24/javascript-survey-results/"&gt;설문조사&lt;/a&gt;가 생각났다. 이 설문조사에 여러가지 설문내용이 포함되었지만 "콤마를 앞에 쓰냐 뒤에 쓰냐?", "메서드 뒤에 공백문자를 넣느냐?"등의 질문들이 있었다. 이 설문조사가 생각난 순간 Github에 거의 모든 오픈소스의 소스코드가 들어 있는데 이걸 굳이 설문조사로 파악할 필요가 있을까 하는 생각이 들었다. 실제로 개발자들이 사용한 상당수의 소스코드가 Github에 올라가 있고 이걸 분석할 수 있다면 개발자들이 어떤 관례를 사용해서 코드를 작성하는 지도 알 수 있을 것이라는 생각이 들었다.&lt;/p&gt;

&lt;h2&gt;프로토타이핑&lt;/h2&gt;

&lt;p&gt;먼저 생각한게 실제로 가능할지 궁금했기 때문에 프로토타이핑에 들어갔다. Github 데이터챌린지는 대부분의 행사와 달리 어떤 특별한 제약이 없다. 그냥 Github 데이터가 있으니 표현해봐! 정도말고는 아무런 제약도 없다. 그리고 Github에 올라오는 전체 타임라인을 구글 &lt;a href="https://bigquery.cloud.google.com/"&gt;BigQuery&lt;/a&gt;와 &lt;a href="http://www.githubarchive.org/"&gt;Github Archive&lt;/a&gt; 사이트를 통해서 제공하고 있다. 여기서 전체 타임라인은 로그인했을 때 팔로잉한 사람들이 하는 활동이 나오듯이 Github의 모든 유저가 하는 활동(push, 이슈처리, watch등등)을 매 시간별로 제공하고 있다. 처음에는 Github Archive를 모르고 BigQuery를 사용하려고 했지만 무척이나 어려웠다.(Google은 너무 커서그런지 API 좀 사용하려면 문서 찾아서 테스트해보는게 엄청 힘들다. ㅠㅠ) 한창 고생하다가 Github Archive에서는 그냥 JSON파일을 압축해서 제공하고 있는 것을 알게 되었고 이는 그냥 wget등으로 바로 내려받아 볼 수 있었고 시간별로 누가 어떤 행위를 했는지가 모두 기록되어 있었다.(참고로 Github Archive는 &lt;a href="https://github.com/igrigorik"&gt;Ilya Grigorik&lt;/a&gt;라는 구글 개발자가 만든 사이트이다.)&lt;/p&gt;

&lt;p&gt;타임라인을 기반으로 실제 코드를 얻을 수 있는지 API 테스트에 들어갔고 &lt;a href="http://developer.github.com/v3/git/commits/"&gt;commits API&lt;/a&gt;를 통해서 커밋에 대한 패치내용을 함께 받을 수 있는 걸 알게 되었고 이걸 바탕으로 구현할 수 있을것 같았다.&lt;/p&gt;

&lt;h2&gt;실제 구현&lt;/h2&gt;

&lt;p&gt;빠르게 작업해야 했기에(실 서비스라기 보다는 깃헙 챌린지에 타게팅한 것이므로) Node.js를 자연스레 사용했고 당시에 &lt;a href="http://hubot.github.com/"&gt;Hubot&lt;/a&gt;을 만지면서 오랜만에 사용해보는 &lt;a href="http://coffeescript.org/"&gt;CoffeeScript&lt;/a&gt;에 재미를 다시 느끼고 있었기에 커피스크립트를 사용해서 구현하기로 했다. 보통은 개인 프로젝트에서 연습삼아 테스트코드도 좀 엄격하게 짜는 편이지만 품질과 생산성의 경계에서 좀 고민이 있었기에 이번에는 러프하게 기능만 확인해 볼 수 있는 수준에서만 테스트코드를 작성하면서 구현했다.&lt;/p&gt;

&lt;p&gt;일단 데이터 분석은 푸시된 커밋을 기반으로 하기로 했다. 매 시간별로 올라온 푸시의 각 커밋의 패치내용을 가져와서 분석하기로 했다. 원 저장소에 더 많은 소스가 있을 것이지만 이럴 경우 수정된 내용을 파악하기가 어렵고 어느 저장소를 타게팅할지도 애매했기 때문에 패치된 소스를 기반으로 추가된 라인만을 기준으로 분석하기로 했다. 하지만 현재 Github는 인증을 할 경우 한시간에 5,000개의 API 요청제한이 있는데 정확한 하긴을 어려웠지만 매시간마다 갱신되지 않는것 같았고(기준시간을 모르므로 하루에 120,000개(5,000 * 24)가 아닌것 같다는 얘기다.) 한 시간 분량에 올라오는 Push가 5천개까지는 안됐지만 하나의 Push에는 커밋이 한개만 있을 수도 있지만 수십개도 있을 수 있을 수 있으므로 커밋마다 보내야 하는 API수는 매 시간 5,000개가 훨씬 넘어갔다. 결국 저장소를 watch와 fork수로 정렬해서 인기있는 저장소를 기준으로만 분석하고 나머지는 그냥 포기하기로 했다.&lt;/p&gt;

&lt;p&gt;흐름은 복잡하진 않았다. Github Archive에서 json을 받아와서 MongoDB에 해당 시간을 이름으로 컬렉션을 만들어서 import시킨다. 이를 큐로 사용할 컬렉션에 넣어놓고 배치가 돌면서 처리하지 않은 데이터에서 Push 이벤트만 가져와서 순차적으로 Github API에서 커밋데이터를 가져오고 파일명에 기반해서 언어별 파서를 돌려서 분석하고자 했던 각 컨벤션에 대한 점수를 매긴다.(A 컨벤션에 적용되면 +1같은 식으로) 이를 모아서 각 점수를 디비에 다시 넣는다. 생각보다 데이터가 많아서 데이터를 모으는데 시간이 걸리므로 추가적으로 배치를 돌아서 이를 시간대별로 통계를 낸 데이터를 따로 모으다. 이를 화면에 내려주고 화면에서 보여준다.&lt;/p&gt;

&lt;h2&gt;Popular Convention&lt;/h2&gt;

&lt;p&gt;이렇게 만든게 &lt;a href="http://sideeffect.kr/popularconvention"&gt;Popular Convention on Github&lt;/a&gt;이다. 지금은 배치는 멈춰있어서 더이상 수집하지 않는다.&lt;/p&gt;

&lt;p style="text-align: center;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/2081781981.gif" width="550" height="524" alt="Popular Convention 프로젝트 홈페이지" title="" style="cursor: pointer;" onclick="open_img('http://blog.outsider.ne.kr/attach/1/2081781981.gif'); return false;" /&gt;&lt;/p&gt;

&lt;p&gt;현재는 자바스크립트, 자바, 파이썬, 스칼라만 분석을 했다.&lt;/p&gt;

&lt;p style="text-align: center;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/7476887608.gif" width="550" height="828" alt="Popular Convention에서 자바스크립트의 컨벤션 통계 화면" title="" /&gt;&lt;/p&gt;

&lt;p&gt;그래프는 &lt;a href="http://d3js.org/"&gt;d3&lt;/a&gt;를 사용했고 디자인은 요즘 유행하는 &lt;a href="http://designmodo.github.io/Flat-UI/"&gt;FlatUI&lt;/a&gt;를 사용했다.&lt;/p&gt;

&lt;h2&gt;아쉬운 점&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;컨벤션 분석을 전체코드로 수행하지 않고 패치에 수정된 부분만을 기준으로 하다보니 의미적인 분석은 전혀 할 수 없고 그냥 텍스트 분석밖에는 할 수 없다.(실제로는 그냥 정규식으로 비교한거지만 ㅡㅡ;;) 그러다 보니 초기에 구상했던 자바스크립트는 간단한 정도로 분석할 수 있었지만 언어별 특성에 따른 분석을 거의 할 수 없었기에 결과적으로 언어별 관례 분석이라지만 거의 차이가 없어졌다. ㅠㅠ 그리고 자바스크립트에 비해서 다른 언어는 관례가 좀 더 고정되어 있었기에 분석한 내용이 큰 의미를 갖기가 어려워졌다.&lt;/li&gt;
&lt;li&gt;d3.js를 처음 사용해 보고 예제를 보면서 화려한 비쥬얼라이제이션을 생각했었다. 처음에는 여러가지 그래프형식으로 보여주고 애니메이션으로 변화되면서 화려하게 보여주려고 했지만 처음 사용해보는 d3.js는 생각보다 쉽지 않았고 대충 통밥으로 활용할 수 있는 수준을 넘어섰기에 간단한 도넛그래프외에는 제대로 그려낼 수가 없었다. 결국 시간의 압박으로 포기했지만 d3.js의 강력함은 확실히 느꼈기에 시간내서 배워둘만하다.&lt;/li&gt;
&lt;li&gt;앞에서 테스트코드를 엄격히 작성하지 않았다고 했는데 꼭 그 때문만은 아니지만 하나의 함수에 로직을 너무 떼려넣어서 오류가 은근히 많이 발생했고 실제 Github 데이터를 끌어오면서 예상치 못한 데이터나 문자때문에 발생하는 오류 등 때문에 뒤로 갈수록 디버깅이 너무 힘들었다.&lt;/li&gt;
&lt;li&gt;Node.js의 비동기적인 특성과 callback기반은 편하기도 하지만 예외처리나 흐름제어가 역시 만만치 않다. 결국 이때문에 한 함수에 많은 로직을 넣게 된 것도 있다.&lt;/li&gt;
&lt;li&gt;MQ를 쓸 수 있을만한 요소가 꽤 있었음에도 그냥 로직내에서만 처리하려다 보니 독립적으로 동작하게 만들 수 있었을 것 같은 부분도 상당한 의존성을 서로 가지게 되었다.&lt;/li&gt;
&lt;li&gt;데이터가 많아질 때의 고려가 제대로 되지 않아서 데이터를 수집할 수록 문제가 터져나왔다. MongoDB에 대한 많은 고려를 하지 않고 그냥 눈앞의 편의에 데이터를 임베딩해서 막 떼려넣었더니만 데이터가 증가하면서 문제가 속속들이 터져나오기 시작했고 페이지 로딩시간도 너무 올래걸려서 데이터도 캐싱하는 등의 로직을 계속 넣어줘야 했다.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;에필로그&lt;/h2&gt;

&lt;p&gt;잠시 프로토타이핑하다가 필 받아서 4월내내 다른 일 미뤄두고 이 프로젝트에만 매달렸다. 이래저래 즐거운 시간이었다. 새로운 것들도 많이 써보기도 했고 반은 학습목적인 개인 프로젝트는 제대로 결과물이 나오지 않고 계속 새로운 라이브러리를 시도해보거나 반복적인 리팩토링으로 프로젝트진도가 잘 나가지 않는 특성이 있는 편인데 이번에는 마감기한이 있다가 보니 적절한 선에서 기술적인 부분과 시간적인 부분을 조정해 나가야 했기 때문에 좀 더 흥미로웠는지도 모르겠다.(회사에서는 흔히 겪는 상황이지만 회사에서는 협의라는 별도의 과정이 따로 필요하므로.. ㅋ) 소스는 엉망이지만 어쨌든 &lt;a href="https://github.com/outsideris/popularconvention"&gt;Github&lt;/a&gt;에 올려져 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.outsider.ne.kr/949?commentInput=true#entry949WriteComment"&gt;댓글 쓰기&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description>
			<category>Programming</category>
			<category>CoffeeScript</category>
			<category>Convention</category>
			<category>d3.js</category>
			<category>FlatUI</category>
			<category>GitHub</category>
			<category>MongoDB</category>
			<category>node.js</category>
			<category>OpenAPI</category>
			<category>visualization</category>
			<category>개인 프로젝트</category>
			<author>outsideris@gmail.com (Outsider)</author>
			<guid isPermaLink="false">http://blog.outsider.ne.kr/949</guid>
			<comments>http://blog.outsider.ne.kr/949#entry949comment</comments>
			<pubDate>Wed, 05 Jun 2013 02:34:54 +0900</pubDate>
		<feedburner:origLink>http://blog.outsider.ne.kr/949</feedburner:origLink></item>
		<item>
			<title>블로그 호스팅 서버 이전</title>
			<link>http://feedproxy.google.com/~r/rss_outsider_dev/~3/XOtbqOYbzV0/946</link>
			<description>&lt;p&gt;이번에 블로그를 다른 서버로 이전했다. 기존에는 PHP 웹호스팅을 이용했고 2004년부터 줄곧 같은 업체를 이용하고 있었지만 (내 기억이 맞다면) 그 사이에 운영회사도 한번인가 바뀌었는데 PHP 4.4로 운영하면서 5는 전혀 지원계획이 없다는 얘기를 듣고 작년 가을쯤에 호스팅 업체를 갈아타기로 맘을 먹었다. 이 업체가 계속 호스팅을 적극적으로 할려고 하는지도 의문이었고... 결심하고 곧 이전할 생각에 2-3개월씩만 연장을 하면서 귀차니즘에 거의 1년가까이 이전을 못하고 있었다. 최근에는 같은 서버에 뭔 서비스가 들어왔는지 수시로 디비에서 랙이 걸려서 모니터링으로 &lt;a href="http://www.uptimerobot.com/"&gt;UptimeRobot&lt;/a&gt;을 이용하고 있는데 2-3일에 한번씩은 속도가 느려져서 블로그가 다운되었다는 알림이 오는 상황까지 이르러서 귀차니즘을 이겨내고 이전작업을 진행했다.&lt;/p&gt;

&lt;p&gt;작년에 &lt;a href="http://blog.outsider.ne.kr/791"&gt;코로케이션서버&lt;/a&gt;를 이용하면서 원래는 블로그도 코로케이션 서버로 이동하려고 했지만 아무래도 코로케이션 서버에는 이것저것 설치해서 테스트해보다 보니 지속적으로 서비스하는 블로그를 같이 운영하기에는 좀 불안해서 다른 호스팅 업체를 찾고 있었다. 하지만 최근에 지인들의 추천으로 &lt;a href="http://digitalocean.com/"&gt;DigitalOcean&lt;/a&gt;을 알게되서 도저히 거부할 수 없는 놀라운 스펙과 가격에 호스팅업체 대신에 직접 운영하기로 선택하고 디지털 오션으로 갈아탔다.&lt;/p&gt;

&lt;p&gt;주말내내 작업했는데 여러가지로 업그래이드를 하다보니 이전 작업이 꽤 힘들었다.&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;디지털오션에 서버를 신청하니 1분내에 셋팅을 했다. 아무 생각없이 우분투 13.04를 선택하고(12.04를 선택하는게 나았을지도) PHP를 설치했더니 5.4가 설치되었다.(PHP 개발자가 아니라 PHP는 잘 모른다.)&lt;/li&gt;
&lt;li&gt;내 서버에서는 기존에 XE로 거의 방치상태인 홈페이지와 텍스트큐브기반의 블로그를 운영하고 있었다.&lt;/li&gt;
&lt;li&gt;블로그가 훨씬 중요했기에(홈페이지는 상황안되면 버릴수도...) 텍스트큐브를 먼저 설치했다.(MySQL은 5.5.29)&lt;/li&gt;
&lt;li&gt;처음에는 최신 안정버전인 &lt;a href="http://www.textcube.org/"&gt;텍스트큐브&lt;/a&gt; 1.8.6을 설치했다. 약간의 오류를 해결해가면서 설치는 무사히 했지만 관리자 페이지가 오류가 나면서 접속이 안되었고 이는 PHP 5.4가 제대로 지원되지 않아서 발생된 문제로 보였다.&lt;/li&gt;
&lt;li&gt;그래서 5.4를 지원한다는 텍스트큐브 1.9 베타6를 다시 설치했다. 오류가 꽤 발생했는데 검색해가면서 소스를 약간 수정하니 무사히 설치를 할 수 있었고 관리자 페이지도 정상이었다. &lt;/li&gt;
&lt;li&gt;이제 &lt;a href="http://www.xpressengine.com/"&gt;XE&lt;/a&gt; 설치에 들어갔다. XE는 하위호환성 유지가 엉망이라 과거에 업그래이드하면서 스킨을 매번 재작업해주는 고통을 겪은뒤로는 업그래이드를 잘 안했기 때문에 기존에는 1.4.4를 사용하고 있었고 최신 버전은 1.7.7이었고 PHP 5.x라고 써있었지만 역시나 제대로 지원하지 않았다.&lt;/li&gt;
&lt;li&gt;XE를 설치하면서도 코드를 꽤 수정했는데 정확히 잘 기억이 나지 않는다. 정리해두었더라면 좋았겠지만 당시에는 그럴만한 여유가 없었다.&lt;/li&gt;
&lt;li&gt;XE 설치를 완료하고 XE는 설정을 이전해 주지 않기 때문에 기존의 설정을 보면서 새로 설정했다. 관리자페이지가 많이 달라져서 헷갈렸지만 XE에서는 대부분의 메뉴관리를 그냥 스킨을 통해서 했기 때문에 기본 설정은 어느정도 할 수 있었다. 데이터를 다 이전하고 보니 댓글이 이전되지 않는 버그가 있어서 수정하고 다시 이전을 하고 일부 게시판은 마이그레이션도구로 제대로 데이터가 다 받아지지 않아서(할때마다 용량이 다르다.) 여러번 시도해서 겨우 데이터를 다 이전했다. 일부 위젯이나 자질구레하게 안돌아가는게 있었지만 크게 중요하지 않으므로 그냥 무시하고 패스했다. &lt;/li&gt;
&lt;li&gt;이제 가장 중요한 데이터인 블로그의 데이터를 이전해 올 차례였다. 텍스트큐브는 데이터 및 설정을 하나의 XML로 거의 전부 내려주기 때문에 내려받은 XML로 이전을 시도했는데 잘 되지 않았다. 기존에는 업로드(이건 용량크면 거의 되지 않는다.), 서버에 올려놓는 방식, URL로 가져오는 방식 3가지가 있었는데 1.9에는 서버에 올려놓는 방식이 없어서 URL로 가져오는 방식을 시도했는데 계속 실패했다. 해당 소스를 열어보닌 소스상으로는 3가지 방식이 다 있어서 소스를 직접 수정해서 서버의 올려진 파일을 이용하도록 했더니 정상적으로 데이터를 복원할 수 있었다.(참고로 아주 작은 용량이 아니라면 첨부파일을 포함하지 않은 XML만 복구하고 attach폴더는 FTP로 올리는게 훨 낫다.)&lt;/li&gt;
&lt;li&gt;1.9에서는(1.8에서도 마찬가지일지도) 1.7.8에서 사용하던 일부 플러그인은 제대로 동작하지 않고 그중 일부는 사용하기로 하면 500에러가 발생하는 경우가 있으니 하나하나 테스트하면서 플러그인을 설정해야 한다. &lt;/li&gt;
&lt;li&gt;1.9에서는 WYSIWYG 에디터로 tinyMCE가 기본으로 붙어있는데 이게 제대로 동작을 하지 않는다. 뭔 문제인지 상단에 아이콘도 안나타나고 일단 이게 있으면 파일첨부에 업로드버튼이 나타나질 않는다. Xquared 에디터(스프링노트시절 좋아하던 에디터였는데)도 있는데 이것도 동작하지 않는다. tinyMCE는 꺼지지도 않으므로 plugins 폴더에서 삭제해 주어야 한다.&lt;/li&gt;
&lt;li&gt;어차피 WYSIWYG 에디터가 없어진 관계로 이번 기회에 마크다운으로 갈아타기로 했다. 문서를 항상 마크다운으로 작성하기 때문에 글도 마크다운으로 쓸 수 있다면 여러가지 이점이 많다. 기존에 있는 마크다운 플러그인에서 마크다운 변환라이브러리를 Github Flavored Markdown을 지원하는 &lt;a href="https://github.com/egil/php-markdown-extra-extended"&gt;php-markdown-extra-extended&lt;/a&gt;로 갈아치웠다.(신텍스 하일라이트를 위해서 소스코드에 언어를 지정하기 위해서...) 그리고 코드 하일라이트는 기존의 글도 정상적으로 보여주어야 하므로 마크다운 라이브러리가 코드용으로 생성하는 마크업을 &lt;a href="http://alexgorbatchev.com/SyntaxHighlighter/"&gt;코드하일라이트&lt;/a&gt;가 지원하도록 수정했고 기타 마크다운으로 변환되는 스타일도 적절하게 변환되도록 스타일을 수정했다.&lt;/li&gt;
&lt;li&gt;DNS를 디지털 오션으로 변경하고 나니 나는 5-6시간 지나니까 접속이 가능했고 유입자가 생기는걸 보니 사람들도 DNS 변경의 적용이 되고 있는듯 하다. 기존의 서버에 댓글등이 올라오면 곤란하기에 어제 자정부로 기존의 블로그는 접속되지 않도록 내려버렸다.&lt;/li&gt;
&lt;li&gt;오늘 지인분이 페이징이 제대로 안된다고 해서 보니 URL이 잘못들어가고 있는데 이건 아무리봐도 어디서 문제인지 잘 모르겠어서 그냥 임시로 문자열을 변환해서 처리되도록 변경해버렸다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;막상 쓰고 보니 과정이 무척 길다.. 작업할 때도 힘들어서 몇번이나 포기하고 싶었지만 겨우 참아냈다. 기존과는 다음의 사항이 변경되었다.&lt;br /&gt;
&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;웹서버로 Apache 대신 &lt;a href="http://nginx.org/"&gt;Nginx&lt;/a&gt;를 사용한다. 최신 안정버전인 1.4.1이다.&lt;/li&gt;
&lt;li&gt;PHP 연동은 php-fpm을 사용했다.(잘은 모르지만 이게 좋다는것 같길래...)&lt;/li&gt;
&lt;li&gt;PHP 5.4를 사용한다.&lt;/li&gt;
&lt;li&gt;MySQL 5.5를 사용한다. MySQL 연결방식으로 MySQL과 MySQLi 중에서 고르게 되어 있던데 MySQLi가 더 좋다는것 같던데 여러 문제를 겪던 도중 더 새로운 문제는 만들고 싶지 않아서 그냥 MySQL을 사용했다.&lt;/li&gt;
&lt;li&gt;웹호스팅이 아닌 가상서버를 사용한다. 그러므로 웹호스팅처럼 다른 서비스때문에 내 블로그도 느려질 염려는 없다. 대신 보안을 직접 신경써야 한다.(누가 털진 않겠지 ㅠㅠ)&lt;/li&gt;
&lt;li&gt;서버는 샌프란시스코에 있다.(응?)&lt;/li&gt;
&lt;li&gt;이제 Flavored Markdown으로 블로깅을 할 수 있게 됐다.(아자!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;기존에는 그냥 블로그를 &lt;code&gt;wget&lt;/code&gt;으로 측정했을 때 0.05초 정도가 나왔고 크롬개발자도구로 특정 글이 로딩되는 속도를 측정하면 3.2초 안팎이 나왔다.(심도있는 테스트는 아니다.)&lt;/p&gt;

&lt;p style="text-align: center;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1819088525.gif" width="550" height="107" alt="사용자 삽입 이미지" title="" /&gt;&lt;/p&gt;

&lt;p&gt;이전 후에 측정해 보니 &lt;code&gt;wget&lt;/code&gt;으로 측정하면 0.9초정도가 나오고 크롬 개발자 도구로 측정하면 4.5초 안팎이 나왔다.&lt;/p&gt;

&lt;p style="text-align: center;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/8592980189.gif" width="550" height="122" alt="사용자 삽입 이미지" title="" /&gt;&lt;/p&gt;

&lt;p&gt;서버사양, nginx도입, 텍스트큐브 버전업 등으로 상당한 속도 향상을 기대했지만 실제로는 오히려 떨어졌는데 페이지 로딩 그래프에서 보듯이 이는 서버가 샌프란시스코에 있기 때문에 물리적인 네트워크 접속시간에 상당한 시간이 소요되는 것으로 보인다.(1.9에서 서버캐시가 제대로 동작안하는것 같기도 하고...) 하지만 기존 호스팅의 속도는 상당히 양호할 때 측정한 것으로 속도가 시시각각 들쭉날쭉했기 때문에 원활한 속도를 보여주는 이전후가 체감상으로는 훨씬 쾌적하다. 자질구레하게 1.9에서 손보아야 할 것들이 좀 있기는 하지만 무사히 블로그서버를 이전해서 맘의 큰 짐을 던 듯하다.&lt;/p&gt;

&lt;p&gt;덧) 결론... 아직 쓴지 몇일 안되었지만 디지털오션 정말 짱이다... 코로케이션 걷어버리고 이쪽으로 갈아타는게 나을지도... ㅡㅡ;;&lt;/p&gt;

&lt;p&gt;덧2) 블로그의 시대가 끝났다는 구글의 입장(?)에 전혀 동의 못하는 입장으로써 텍스트큐브는 정말 아쉬운 프로젝트다. 블로깅툴로써의 완성도 면에서는 오픈소스로 원활히 진행된 면에서나 국내에서 내노라 할만한 오픈소스 프로젝트라고 생각한다. 정말 워드프레스와도 견줄만한 도구라고 생각하는데 국내에서 블로그붐이 꺼지면서 프로젝트도 급격히 기울게 된 것같아서 너무 아쉽다. 그래도 느리게나마 조금씩 개선해 나가는데 응원을 보내지만 예전의 활발했던 모습이 생각나면서 한켠으로 아쉬움이 크다.(텍스트큐브 관련도 글을 한번 쓰고 싶을정도인데 ㅠㅠ) 얼마전 &lt;a href="https://github.com/Needlworks/Textcube"&gt;Github로 운영&lt;/a&gt;하면서 간간히 커밋이 올라오고 있는 것을 보면서 PHP만 아니라면 공헌하고 싶을 정도다..(PHP를 몰라서.. ㅠ) 일단 프로젝트가 어떻게 흘러가고 있는지 분위기라도 살펴보고...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.outsider.ne.kr/946?commentInput=true#entry946WriteComment"&gt;댓글 쓰기&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description>
			<category>BlaBlaBla~</category>
			<category>blog</category>
			<category>digitalocean</category>
			<category>hosting</category>
			<category>MySQL</category>
			<category>PHP</category>
			<category>virtual server</category>
			<category>XE</category>
			<category>텍스트큐브</category>
			<author>outsideris@gmail.com (Outsider)</author>
			<guid isPermaLink="false">http://blog.outsider.ne.kr/946</guid>
			<comments>http://blog.outsider.ne.kr/946#entry946comment</comments>
			<pubDate>Mon, 03 Jun 2013 23:07:26 +0900</pubDate>
		<feedburner:origLink>http://blog.outsider.ne.kr/946</feedburner:origLink></item>
		<item>
			<title>IntelliJ에 Scala 환경설정하기</title>
			<link>http://feedproxy.google.com/~r/rss_outsider_dev/~3/eZ9BWO0-t5c/945</link>
			<description>IntelliJ를 구입한건 &lt;a href="http://blog.jetbrains.com/blog/2012/12/20/jetbrains-end-of-the-world-clearance-sale-24-hours-only/" target="_blank"&gt;작년에 세상의 종말이 예상되던 때&lt;/a&gt;이지만 본격적으로 쓰기 시작한건 최근의 일이다. 기존에는 Scala 코딩을 vi에서 하다가 &lt;a href="http://scala-ide.org/" target="_blank"&gt;ScalaIDE&lt;/a&gt;에 워크시트가 도입된 다음부터 ScalaIDE를 쓰고 있다가 최근에 IntelliJ로 갈아탔다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="5"&gt;Scala 설정&lt;/font&gt;&lt;/b&gt;&lt;br&gt;IntelliJ에서(12 버전 기준) Scala를 사용하려면 Scala 플러그인을 설치해 주어야 한다. &lt;font color="#CC9900"&gt;[Preferences] - [Plugins]&lt;/font&gt;에서 &lt;font color="#CC9900"&gt;[Browse repositories...]&lt;/font&gt;를 눌러서 Scala 플러그인을 다음과 갈이 설치해준다. (SBT 플러그인이나 Play 2.0 플러그인도 별도로 있다.)&lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1345264751.gif" alt="플러그인 저장소에서 Scala 플러그인을 검색한 화면 " height="301" width="550" /&gt;&lt;/div&gt;&lt;br&gt;플러그인을 설치한 다음에 IntelliJ를 재시작하고나면 스칼라를 IntelliJ에서 사용할 수 있다.&lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1306967773.gif" alt="IntelliJ의 New Project에서 Scala Module을 선택한 화면 " height="404" width="550" /&gt;&lt;/div&gt;&lt;br&gt;이제 프로젝트를 생성할 때 Scala Module이 생긴것을 볼 수 있다. 당연히 Scala는 설치가 되어 있어야 하고 Scala Home이 제대로 설정되어 있지 않다면 지정해 주면 된다. &lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1066214321.gif" alt="생성한 스칼라 프로젝트에서 Scala Class를 새로 생성하는 메뉴를 띄운 화면 " height="407" width="500" /&gt;&lt;/div&gt;&lt;br&gt;스칼라 플러그인을 설치했기 때문에 프로젝트에서 Scala 클래스 파일을 생성할 수 있다. &lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1258198492.gif" alt="Scala Class 생성시 클래스의 종류를 선택하는 화면 " height="160" width="400" /&gt;&lt;/div&gt;&lt;br&gt;클래스를 생성할 때 Class, Object, Trait 중에 골라줄 수 있다. 어차피 스칼라는 파일명과 클래스명을 일치시키지 않아도 되므로 편한대로 생성하고 소스를 고쳐줘도 상관없다.&lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1149657325.gif" alt="IntelliJ 컨텍스트에서 Run 'Hello.main()'을 선택한 화면" height="591" width="400" /&gt;&lt;/div&gt;&lt;br&gt;해당 파일에서 우클릭을 하면 위처럼 컨텍스트메뉴에 Scala 파일을 실행할 수 있는 메뉴가 나타난다. 실행하면 바로 실행하고 다음과 같이 실행결과를 볼 수 있다.&lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1235283013.gif" alt="Scala 코드를 실행하고 Run 창에 결과를 출력한 화면 " height="427" width="550" /&gt;&lt;/div&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="5"&gt;Scala Console&lt;/font&gt;&lt;/b&gt;&lt;br&gt;위의 컨텍스트 메뉴에서 보았듯이 파일을 실행하는 것 외에도 Scala Console을 실행할 수도 있다. &lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1361097121.gif" alt="Scala Console에서 REPL을 사용하는 화면 " height="229" width="550" /&gt;&lt;/div&gt;&lt;br&gt;Run 창에 스칼라의 REPL이 실행되고 여기서 바로 코드를 테스트해 볼 수 있다. 터미널에서 REPL을 사용할 때와 차이점은 코드를 입력하고 엔터대신 &lt;font color="#CC9900"&gt;Ctrl + Enter(&lt;/font&gt;맥에서는 &lt;font color="#CC9900"&gt;Cmd + Enter&lt;/font&gt;)를 입력해야 코드가 실행된다는 점이다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="5"&gt;SBT Console&lt;/font&gt;&lt;/b&gt;&lt;br&gt;앞에서 플러그인을 설치할 때 SBT 플러그인도 설치했다면 IntelliJ 내에서 SBT Console을 사용할 수 있다.&lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1325725359.gif" alt="IntelliJ에서 SBT Console로 SBT를 사용하는 화면 " height="231" width="550" /&gt;&lt;/div&gt;&lt;br&gt;SBT 콘솔을 실행하면 터미널에서 SBT를 사용하던 것과 동일하게 SBT 콘솔을 실행해서 사용할 수 있다. 스칼라 프로젝트는 대부분 SBT를 사용하기 때문에 유용하지만 터미널에서 쓰는게 익숙해서인지 나같은 경우는 SBT를 그냥 터미널에서 사용하는게 더 편하다.&lt;br&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.outsider.ne.kr/945?commentInput=true#entry945WriteComment"&gt;댓글 쓰기&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description>
			<category>Scala</category>
			<category>IntelliJ</category>
			<category>REPL</category>
			<category>SBT</category>
			<category>Scala</category>
			<author>outsideris@gmail.com (Outsider)</author>
			<guid isPermaLink="false">http://blog.outsider.ne.kr/945</guid>
			<comments>http://blog.outsider.ne.kr/945#entry945comment</comments>
			<pubDate>Sat, 01 Jun 2013 00:26:41 +0900</pubDate>
		<feedburner:origLink>http://blog.outsider.ne.kr/945</feedburner:origLink></item>
		<item>
			<title>Typesafe 스택의 초기 사용자를 위한 Activator</title>
			<link>http://feedproxy.google.com/~r/rss_outsider_dev/~3/z6Ixb5H2vn0/944</link>
			<description>&lt;a href="http://www.typesafe.com/" target="_blank"&gt;Typesafe&lt;/a&gt;는 스칼라를 만든 Martin Odersky가 공동창업자로 있는 회사로 스칼라를 위한 회사다. Typesafe가 나온 뒤로 Scala진영에 여러 가지로 영향을 주고 있는데 그 중하나가 &lt;a href="http://typesafe.com/platform" target="_blank"&gt;Typesafe Stack&lt;/a&gt;인데 Typesafe가 직접 Scala의 기술스택을 지정해서 지원해 나감으로써 기술의 안정성이나 관련 리소스들이 차근차근 늘어나가고 있다. Scala쪽 기술이 전체적으로 어려운(?) 경향이 있는데 현재 Typesafe Stack에는 &lt;a href="http://www.scala-lang.org/" target="_blank"&gt;Scala&lt;/a&gt;를 기반으로 웹 프레임워크인 &lt;a href="http://www.playframework.com/" target="_blank"&gt;Play Framework&lt;/a&gt; - 동시성 분산 툴킷 &lt;a href="http://akka.io/" target="_blank"&gt;Akka&lt;/a&gt; - 데이터베이스 접근 라이브러리 &lt;a href="http://slick.typesafe.com/" target="_blank"&gt;Slick&lt;/a&gt; - 빌드도구 &lt;a href="http://www.scala-sbt.org/" target="_blank"&gt;SBT&lt;/a&gt;로 이어지고 기술 스택을 가지고 있다.&lt;br&gt;&lt;br&gt;과거에는 Typesafe가 Typesafe Stack을 사용하는 사람들이 좀 더 쉽게 사용할 수 있도록 &lt;a href="https://github.com/n8han/giter8" target="_blank"&gt;giter8&lt;/a&gt;(지터레이트라고 읽는다.)라는 CLI 템플릿 도구를 사용했다. giter8은 Github 저장소에 .g8로 끝나는 템플릿용 저장소를 생성해서 사용자명과 저장소명을 이용해서 간단히 템플릿을 받아와서 프로젝트의 기본 템플릿을 구성하도록 하는 도구이다. 그래서 Typesafe가 여러 템플릿을 제공해서 giter8을 이용할 수 있도록 하고 있었는데 최근에는 &lt;a href="http://www.typesafe.com/platform/getstarted" target="_blank"&gt;Activator&lt;/a&gt;라는 도구를 이용해서 처음 시작하도록 가이드하고 있다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="5"&gt;Activator 사용하기&lt;/font&gt;&lt;/b&gt;&lt;br&gt;Typesafe에 &lt;a href="http://www.typesafe.com/platform/getstarted" target="_blank"&gt;Getting Started페이지&lt;/a&gt;에서 다운받을 수 있도록 하고 있고 현재는 Developer Preview버전이다. Java로 만들어진 도구이므로 윈도우, 리눅스, 맥에서 모두 사용할 수 있고 용량은 200메가 정도로 꽤 큰 편이다.&lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1339350787.gif" alt="Activator 압축을 푼 폴더구조" height="259" width="500" /&gt;&lt;/div&gt;&lt;br&gt;&lt;font color="#CC9900"&gt;다운받은 zip 파일의 압축을 풀면 위와 같은 파일들이 나타나는데 이 중에서 activator 파일을 실행하면 된다. 실행하면 터미널이 실행되면서 Activator가 실행되고 잠시후 기본 웹 브라우저가 실행되면서 activator 페이지가 열린다.&lt;/font&gt;&lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1085814718.gif" alt="Activator를 웹브라우저에서 띄운 초기화면" height="371" width="550" /&gt;&lt;/div&gt;&lt;br&gt;처음 사용하는 사람도 쉽게 시작할 수 있도록 웹UI를 제공하고 있다. 프로젝트를 사용할 프로젝트 위치를 지정하고 아래에서 원하는 템플릿을 지정하면 된다. 현재(0.1.3버전)는 샘플 어플리케이션인 Reactive Stocks와 기본템플릿으로 Hello Scala!, Hello Akka!, Hello Play Framework!를 제공하고 있다. 템플릿을 선택하고 이름과 위치를 지정한 뒤에 Create 버튼을 누르면 화면 하단에 콘솔 메시지가 출력되고 잠시 뒤에 다음과 같은 화면이 열린다.&lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1128835351.gif" alt="Activator 웹UI에서 프로젝트를 생성한 화면 " height="277" width="550" /&gt;&lt;/div&gt;&lt;br&gt;Activator는 단순 템플릿 생성도구 이상으로 Typesafe Stack을 쉽게 배울 수 있는 가이드 역할을 하고 있다.(꽤 신경을 많이 썼음을 느낄 수 있다.) 위 화면은 Home화면인데 우측 사이드바에서 해당 템플릿에 대한 가이드를 안내하고 있다.&lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1276503403.gif" alt="Activator 웹UI에서 Code 탭의 프로젝트 폴더 구조가 나온 화면 " height="333" width="550" /&gt;&lt;/div&gt;&lt;br&gt;코드 탭에서는 Activator 웹 UI 내에서 직접 프로젝트를 다룰 수 있도록 제공하고 있다. 왼쪽에는 프로젝트의 트리구조가 열리고&lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1210128801.gif" alt="Activator 웹UI에서 코드를 수정하는 화면 " height="170" width="550" /&gt;&lt;/div&gt;&lt;br&gt;웹 IDE의 기능을 가지고 있으므로 웹 브라우저내에서 바로 수정할 수 있다. 파일을 수정하고 상단의 Save 버튼을 누르면 다음과 같이 자동으로(Compile탭의 Options에서 변경가능하다.) 컴파일이 수행되고 Test를 수행한 뒤에 실행을 한다.(아쉽게도 아직 단축키등을 제공하고 있지 않아서 기본적인 학습 이상의 활용은 좀 불편해 보인다.)&lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1317141064.gif" alt="Activator 웹UI의 Compile탭에서 컴파일 로그가 나온 화면 " height="349" width="550" /&gt;&lt;/div&gt;&lt;br&gt;컴파일 및 테스트과정등의 로그 메시지도 Activator내에서 바로 볼 수 있다. &lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1001662775.gif" alt="Activator 웹UI의 Compile 탭에서 컴파일 오류가 출력된 화면 " height="489" width="550" /&gt;&lt;/div&gt;&lt;br&gt;컴파일 오류가 발생하거나 테스트가 실패해도 해당부분에 경고가 뜨고 그 내용을 볼 수 있다. &lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1217522640.gif" alt="Activator 웹UI의 Run 탭에서 실행결과가 출력된 화면 " height="497" width="550" /&gt;&lt;/div&gt;&lt;br&gt;당연히 실행결과도 볼 수 있다.&lt;font color="#CC9900"&gt; 스칼라는 스칼라자체도 배워야 할 부분이 꽤 있지만 SBT나 Play Framework등 기존에 익숙치 않은 도구를 배워야 하는 부분이 많이 있는데 그런 면에서 Activator는 SBT같은 것을 몰라도 Typesafe가 권하는 템플릿 상에서 사용해 볼 수 있기 때문에 초기에 좋은 진입점으로 그 역할을 잘 할 것으로 보인다.&lt;/font&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="5"&gt;커맨드라인에서 Activator 사용하기&lt;/font&gt;&lt;/b&gt;&lt;br&gt;앞에서는 GUI로 Activator를 다루었지만 이는 CLI에 익숙치 않거나 SBT를 모르는 사람을 위해서 웹페이지로 GUI를 제공하고 있을 뿐이지 내부적으로 보면 Scala의 공식 빌드도구인 SBT를 그대로 이용하고 있다. 웹페이지와는 웹소켓을 이용해서 데이터를 주고 받는 것으로 보인다.(그럼 IE에서 되나??) Activator 자체도 jar이므로 그냥 커맨드라인에서 실행이 가능하고 &lt;font color="#CC9900"&gt;Activator로 만든 프로젝트는 Activator에 대한 의존성이 거의없이 SBT로 관리가 되기 때문에 초기에 Activator를 사용하고 자연스럽게 SBT로 넘어갈 수 있다.&lt;/font&gt;(이 부분이 Activator를 더욱 빛나게 해주고 있다.)&lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1330249232.gif" alt="Activator 웹UI에서 IDE용 프로젝트 설정파일을 생성하는 메뉴 화면 " height="268" width="400" /&gt;&lt;/div&gt;&lt;br&gt;실제로도 Code탬에서 Open 버튼을 누르면 IntelliJ나 Eclipse로 이동할 수 있도록 하고 있다. 사용하는 프로젝트를 클릭하면 해당 IDE에 관련한 프로젝트 파일을 자동으로 생성해 주고 IDE에서 프로젝트를 열어서 바로 이어서 개발할 수 있다. &lt;br&gt;&lt;br&gt;&lt;div class="imageblock center" style="text-align: center; clear: both;"&gt;&lt;img src="http://blog.outsider.ne.kr/attach/1/1319691390.gif" alt="터미널에서 activator 명령어를 사용하는 화면 " height="259" width="550" /&gt;&lt;/div&gt;&lt;br&gt;activator가 존재하는 곳에서 위처럼 &lt;font color="#FF7635"&gt;./activator new&lt;/font&gt;로 프로젝트를 생성할 수 있다. 앞에서 말했다시피 웹UI에서 본 것과 동일한 템플릿을 볼 수 있고 원하는 템플릿을 선택하면 현재 위치에 해당 프로젝트 폴더에 템플릿이 구성된다. 위 콘솔에 안내된 대로 해당 프로젝트에서 &lt;font color="#FF7635"&gt;./activator run&lt;/font&gt;이나 &lt;font color="#FF7635"&gt;./activator test&lt;/font&gt;를 이용해서 프로젝트를 실행하거나 테스트할 수 있다. 그리고 &lt;font color="#FF7635"&gt;./activator ui&lt;/font&gt;를 실행하면 다시 앞에서 본 웹UI 화면을 띄울 수 있다. 다만 현재 프로젝트가 activator로 만든 프로젝트라고 하더라도 자동으로 해당 프로젝트를 열어주진 않기 때문에(이건 버그가 아닌가 생각된다.) 위치를 찾아서 열어줘야 한다. &lt;font color="#CC9900"&gt;Scala 프로젝트를 하려면 SBT를 어차피 배워야 하므로 SBT를 이용해서 프로젝트를 빌드하는 것을 추천하지만 익숙치 않다면 Activator를 섞어서 사용하는 것도 좋아보인다.&lt;/font&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;좀 맘에 안드는 부분은 SBT도 비슷한데 현재 위치에서 activator를 사용하도록 하는 점이다. 경로문제 때문에 activator를 PATH에 등록해 놓고 아무 곳에서나 사용할 수가 없다.(그래서 파일을 일부 수정해서 PATH등록해서 쓰고 있다.) 그때문에 Activator를 다운 받은 위치에 이동해서 사용해야 하고 Activator로 생성한 프로젝트에도 자동으로 activator.jar등이 포함된다.(물론 이는 의존성 라이브러리들은 안들어있으므로 앞에서 받은 200메가 짜리는 아니다.) activator같은 도구가 프로젝트마다 포함되어야 할 이유도 모르겠고 굳이 형상관리도 필요없어보이는데 전역으로 등록해서 쓸 수 없고 템플릿 마다 넣도록 한 것은 나로써는 이해하기 어렵다.&lt;br&gt;&lt;br&gt;덧) 0.1.1에서는 Activator UI를 사용하다가 닫고 터미널로 왔을 때 터미널이 좀 먹통처럼 되서 다시 열어야 하는 문제가 있었는데 0.1.3에서는 해결된지 모르겠다.&lt;br&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.outsider.ne.kr/944?commentInput=true#entry944WriteComment"&gt;댓글 쓰기&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description>
			<category>Scala</category>
			<category>Activator</category>
			<category>akka</category>
			<category>giter8</category>
			<category>Play Framework</category>
			<category>SBT</category>
			<category>Scala</category>
			<category>typesafe</category>
			<author>outsideris@gmail.com (Outsider)</author>
			<guid isPermaLink="false">http://blog.outsider.ne.kr/944</guid>
			<comments>http://blog.outsider.ne.kr/944#entry944comment</comments>
			<pubDate>Tue, 28 May 2013 23:59:11 +0900</pubDate>
		<feedburner:origLink>http://blog.outsider.ne.kr/944</feedburner:origLink></item>
		<item>
			<title>"오픈소스 제대로 활동하기" 세미나 후기</title>
			<link>http://feedproxy.google.com/~r/rss_outsider_dev/~3/tSa84rGh-ds/943</link>
			<description>몇 주전 &lt;a href="http://www.facebook.com/groups/567834989912160/" target="_blank"&gt;페이스북 "OSS 개발자포럼" 그룹&lt;/a&gt;의 주최로 &lt;a href="http://gleamynode.net/" target="_blank"&gt;이희승님&lt;/a&gt;이 &lt;a href="http://onoffmix.com/event/15443" target="_blank"&gt;오픈소스 제대로 활동하기&lt;/a&gt;를 주제로 발표하신다는 걸 알고는 냅다 신청해서 지난 토요일(25일)에 갔다가 왔다. 이희승님은 &lt;a href="http://mina.apache.org/" target="_blank"&gt;Mina&lt;/a&gt;, &lt;a href="http://www.jboss.org/infinispan/" target="_blank"&gt;Infinispan&lt;/a&gt;, &lt;a href="http://netty.io/" target="_blank"&gt;Netty&lt;/a&gt; 프로젝트를 만드신 분으로 현재는 트위터에서 일하고 계시고 국내에서 오픈소스쪽 활동으로는 가장 활발하다고 할 수 있다. 이희승님은 이름 정도만 알고 있다가 지난번 &lt;a href="http://blog.outsider.ne.kr/842" target="_blank"&gt;Deview에서 Netty 발표&lt;/a&gt;를 발표도 참 잘하신다는 것을 알게 되어서 이번에는 망설일 이유가 없었다. 오픈소스에 최근 관심도 더 커지기도 했고...&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="5"&gt;오픈소스 활용 vs. 활동&lt;/font&gt;&lt;/b&gt;&lt;br&gt;처음에는 가볍게 오픈소스의 활용과 활동에 대해서 얘기하셨다. 가장 간단한 것은 오픈소스를 단순히 이용하는 것이고 내부적으로 수정해야 할 부분이 있어서 수정해서 사용(internal fork)하기도 하는데 이런 경우 점점 원 프로젝트와 달라지는 부분이 커지기 마련이라 관리가 쉽지 않다. 수정한 내용을 단순히 공개하기도 하는데(external fork) 다른 사람이 사용할 수 있으므로 좀 더 나은 방법이기는 하지만 원 프로젝트에 적용된다는 보장이 없으므로 해당 프로젝트에서 직접 활동하는 것이 더 낫다. &lt;br&gt;&lt;br&gt;블로그나 강연을 통해서 오픈소스에 대한 경험을 공유할 수 있다. 상용 소프트웨어의 경우 동작하는 부분은 그대로 놔두는 경향이 있지만 오픈소스에서는 디자인등을 적극적으로 변경해 나가므로 문서에 제때 반영되지 못하는 경우가 많은데 실 사례를 많이 공유함으로써 사람들이 더 쉽게 사용할 수 있게 된다. 이는 오픈소스 프로젝트와 느슨하게 연결되어 있지만 중요한 부분이고 그 외에 버그리포팅이나 기능추가 요청, 토론참여 등이 있다. &lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="5"&gt;오픈소스 활동의 목적&lt;/font&gt;&lt;/b&gt;&lt;br&gt;그럼 오픈소스 활동을 왜 해야하는것인가? 활동하기 전에 목적을 확실히 할 필요가 있다.&lt;br&gt;&lt;br&gt;가장 많이 사용하는 웹서버를 만드는 것을 가정했을 때 (물론 재미난 과정이겠지만) 스펙 준수나 호환성 테스트를 다 하는 것은 무척 시간이 많이 들기 때문에 이러한 부분 오픈소스를 사용하고 핵심 역량에 집중해서 개발할 수 있다. 그리고 오픈소스에 직접 참여하면 사용하는 오픈소스에 변경해야 할 부분이 생겼을 때 직접 참여해서 적극적으로 변경할 수 있다. 그렇지 않으면 기다리거나 내부적으로만 수정하게 되서 관리가 어려워진다. 적극적으로 참여해서 오픈소스 로드맵에 직접 참여할 수 있고 설사 의견이 반영되지 않는다고 하더라도 그 토론에서 얻을 수 있는 것이 많다. &lt;br&gt;&lt;br&gt;그리고 활동 자체에도 큰 의미를 둘 수 있다. 보통 오픈소스 프로젝트는 다양한 나라에서 다양한 사람들이 참여를 하게 되므로 사람의 성품이나 치적에 대한 편견없이 코드에만 집중해서 커뮤니케이션할 수 있는 능력을 기를 수 있다. 많은 사람이 온라인으로 협업하는 법을 배우게 되므로 얼굴보고 협업하는 것도 한결 쉬워진다. &lt;font color="#CC9900"&gt;사람에 대한 비난 보다는 코드가 중요하다는 것을 많이 배울 수 있다.&lt;/font&gt; 최근에 많아지고 있는데 오픈소스 활동을 채용절차의 일부로 활용하기도 한다. 물론 이는 좋은 코드를 작성했을 때의 이야기이지만 &lt;font color="#CC9900"&gt;오픈소스 활동 자체가 좋은 코드로 발전하는 하나의 과정이라고 할 수 있다. &lt;/font&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="5"&gt;오픈소스 제대로 활동하기&lt;/font&gt;&lt;/b&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="4"&gt;주의할 점&lt;/font&gt;&lt;/b&gt;&lt;br&gt;자주 있는 경우는 아니지만 초기에 소위 찍히면 그 뒤에는 여러가지로 열심히 해도 잘 안되는 경우가 있으므로 처음부터 잘 해야 한다. 버그리포팅을 하는 입장에서는 하나의 이슈이지만 커미터들 입장에서는 수십,수백개의 이슈가 올라오므로 단순 리포팅 자체는 큰 의미가 없다. &lt;font color="#CC9900"&gt;이해하기 어려운 영어나 불충분한 설명으로 버그리포팅을 하게 되면 우선순위가 낮아져서 처리되지 않으므로 최대한 자세하고 명확하게 리포팅해야 하고 서로의 시간을 소중하게 생각할 수 있어야 한다.&lt;/font&gt; 특히 "언제 해결되냐는 식"의 재촉은 찍히는 지름길이고 공식 채널이 아닌 비공식 채널(메일링, IRC , 이슈트래커가 아닌 개인 메일이나 전화)로 연락하면 무조건 공식채널로 연락하라고 하기 마련이다. IRC도 공식 채널이기는 하지만 기록으로 남고 누군가 다시 검색해 볼 수 있는 메일링이나 Stackoverflow를 이용하는 것도 좋은 방법이다.&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="4"&gt;버그 리포트&lt;/font&gt;&lt;/b&gt;&lt;br&gt;버그리포팅은 jira나 github 이슈등의 공식 이슈 트랙커를 사용한다. 버그 리포트를 올리기 전에 유사한 이슈가 있는지 필히 확인해야 하고 이미 존재한다면 기존 이슈에 보충할 내용만 추가하고 vote나 watch 등으로 관심을 표현하는 것이 좋다. 그리고 &lt;font color="#CC9900"&gt;리포팅을 할 때는 사용 버전, OS나 VM등의 환경, 재현방법등을 자세하면서 깔끔하게 적는 것이 좋고 좀더 나아간다면 왜 이런 문제가 발생했다고 생각하는지를 찾아서 같이 올리거나 의심가는 부분의 소스코드를 알려준다면 처리되는 우선순위를 높힐 수 있다.&lt;/font&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="4"&gt;기능 제안&lt;/font&gt;&lt;/b&gt;&lt;br&gt;기능제안도 버그리포팅과 유사하게 이슈트래커를 사용한다. 마찬가지로 중복 등록이 되지 않도록 기존 이슈들을 검색해봐야 하고 기능을 요청하게 된 배경이나 어떤 효과가 있는지, 잠재적으로 예상되는 위험성, 구체적인 구현방법등을 함께 제안하면 좋다.&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="4"&gt;패치 보내기&lt;/font&gt;&lt;/b&gt;&lt;br&gt;이슈에 패치를 첨부하면 커미터들이 리뷰를 하게 된다. 코드가 너무 맘에 들어서 한번에 적용되는 경우는 흔치 않다. 보통은 관례를 맞춰달라거나 자세한 내용을 물어보거나 일부를 바꿔 달라고 하는 경우가 많다.(물론 때로는 칭찬도 듣는다.) 이러한 리뷰에 따라 패치를 다시 첨부하면 되고 해당 프로젝트에 개발자 가이드가 존재한다면 미리 숙지해 두는 것이 빨리 적용되는데 도움이 된다. 이렇게 &lt;font color="#CC9900"&gt;리뷰하는 과정에서 포기하면 참여할 수 있는 좋은 기회를 놓치는 것이므로 끈기있게 해야 하고 커미터의 의견이라고 너무 끌려다니지만 말고 자신의 의견을 개진하는 것이 좋다.&lt;/font&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="4"&gt;Pull Request&lt;/font&gt;&lt;/b&gt;&lt;br&gt;Github가 사실상 오픈소스 코드 저장소의 표준이 되었기 때문에 요즘은 패치를 보내는 일은 많지 않고 대부분 Github의 Pull Request를 이용한다. DVCS의 장점을 이용해서 각 저장소의 변경사항을 자동으로 보여주고 개발자가 리뷰하면 원 저장소에 적용되는 방식으로 모두에게 편리해 졌다.&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="4"&gt;일원이 되기&lt;/font&gt;&lt;/b&gt;&lt;br&gt;프로젝트의 일원이 되는 경우는 패치를 하는 과정에서 커미터가 얘기가 잘 통한다고 느끼거나 좋은 코드를 항상 작성해서 굳이 매번 리뷰를 하지 않아도 되겠다고 느껴서 믿을 만한 수준이 되면 일원이 될 수 있다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="5"&gt;새로운 오픈소스 프로젝트 시작하기&lt;/font&gt;&lt;/b&gt;&lt;br&gt;새로운 오픈소스 프로젝트를 시작하기 전에 "기업과 개인의 이익을 넘어설 수 있는지" 고민해 볼 필요가 있다. 자신이 다른 사람에게 "믿을만한 개발자"라고 말할 수 있는 자격이 있는지 먼저 봐야 하고 내가 하고 싶어서 또는 회사가 원해서가 아니라 실제로 추구하는 가치가 무엇인지를 고민해 볼 필요가 있고 이는 생색내기가 아닌 진짜 오픈소스를 하고 싶다면 고민해 볼만한 일이다. 그리고 이러한 가치가 있다면 장기간 유지해야하고 회사외부로 오픈할 때도 편협하게 외부사람이라고 권한은 안주는 등의 결정에 영향을 줄 수 있고 진정으로 열린 프로젝트인지도 고민해 봐야 한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="5"&gt;사례 소개 : open source @ twitter&lt;/font&gt;&lt;/b&gt;&lt;br&gt;많이 알려졌다 시피 오픈소스에 적극적인 트위터를 사례로 소개해 주셨다. 트위터는 현재 내부에서 개발한 프로젝트를 오픈소스로 공개해서 지속적으로 유지하고 대표적으로는 Bootstrap, finagle, ostrich, zipkin, twemproxy, mysql fork 등이 있다. 그리고 &lt;a href="https://dev.twitter.com/blog" target="_blank"&gt;블로그&lt;/a&gt;를 통해서 기술을 공유하고 있고 내부에서 많이 사용하는 스칼라의 전파를 위해 Scala School이나 Effective Scala등의 문서도 공개하고 있다. 그 외에도 netty, apache hedwig, mesos, pig등의 오픈소스에 적극적으로 참여하고 개발자를 직접 고용해서 오픈소스개발을 하게 하고 있으며 Apache Software Foundation, Linux Foundation, Travis-CI등에 재정적인 지원을 하고 있다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="5"&gt;Epilogue&lt;/font&gt;&lt;/b&gt;&lt;br&gt;아주 긴 시간은 아니었지만 여러 오픈소스를 진행하면서 쌓인 실 경험에 바탕해서 얘기해 주셨기 때문에 알고 있던 내용이라고 하더라도 그 무게가 달랐고 시스템 적인것 외에 실제적인 부분은 잘 몰랐기에 느끼는 부분도 많고 오픈소스에 대한 꿈을 더 크게 품을 수 있었다.(올해 어느정도 발이라도 걸쳐보려는게 목표이긴 한데 ㅠ) Github 덕분에 오픈소스에 참여할 수 있는 기회는 무한하게 열린 상황이지만 가끔 간단한 거라도 리포팅이나 패치를 보내다가 무시당하거나 하면 맘이 상하는 경우가 많았는데 커미터의 입장에서의 이야기를 들어보면서 그런거에 일일이 맘상하면 안되겠다는 생각이 들었다.(뭐 대단한거 보낸것도 아니고...) 질문시간에도 여러 질문이 오갔는데 굳이 여기에 정리하진 않겠다. (궁금한 사람은 Insane님이 정리한 &lt;a href="http://insanehong.kr/post/opensource-mentoring/" target="_blank"&gt;오픈소스 제대로 활동하기 멘토링 후기&lt;/a&gt; 참고)&lt;br&gt;&lt;br&gt;마지막으로 원래도 그런 생각을 좀 하고 있어서 인지는 몰라도 이번 세미나를 들으면서도 사람들이 뭔가 과정을 뛰어넘어서 결과만 바라는 것 같은 느낌이 들었다. 직접적으로 말하면 오픈소스를 개발하고 사람들과 의견을 나누고 실력을 쌓는 과정을 생략하고 유명프로젝트의 커미터를 좀 더 쉽게 될 수 있는 방법이 없나? 하는 생각을 하고 있는 듯하게 느껴졌다.(편견일지도...) 내가 생각하기에 오픈소스에서 가장 중요한 것은 결국 실력이고 사실 실력만 있으면 그외에것은 다 무시할만한 수준이라고 생각한다. 이희승님이 나누어 주신 얘기도 실력은 제외하고 초기에 모를 수 있는 팁을 알려 주신 것이지 저런걸 다 지킨다고 커미터가 되는게 아니라 일단 해당 프로젝트를 직접 개발하고 멋진 코드를 작성할 수 있는 능력이 되야 하는데 그런 과정은 쉽게 넘어가고 싶은게 아닐까 하는 느낌이 들었다.(물론 나도 그런 능력이 아직 안되는데 잘하시는 분들을 얘기한건 아니다.)&lt;br&gt;&lt;br&gt;이희승님이 얘기하셨듯이 오픈소스에 참여하는 과정은 아주 다양하다. 내가 아직 코드를 막 짤 수준이 아니라면 버그 리포팅을 할 수도 있고 질문에 답변을 열심히 달아줄 수도 있고 가이드 같은걸 만들어서 제공할 수도 있다. 몰론 패치를 보낼 수 있다면 더욱 좋겠지만... netty에 올라오는 이슈나 패치에 한국사람이 거의 없다는 이희승님의 얘기에 약간의 안타까움을 느끼면서 어떻게 커미터가 되느냐 마느냐 보다는 그냥 자신이 할 수 있는 부분에서 오픈소스 활동을 다양하게 하는 사람들이 많아졌으면 좋겠다. 그런 가운데서 커미터들도 더 나오고 그러겠지. 내 생각에는 오픈소스는 그게 더 좋은 방법이고 즐거운 방법이기에 그렇게 개발하면서 명예가 따라오는 것이지 명예를 얻기 위해서 오픈소스를 하는건 아니라고 생각한다. 국내 회사들의 오픈소스들을 개발자들이 거의 신경도 안쓰는 이유도 질답중에 이희승님이 비슷한 얘기하셨듯이 오픈만 했다고 다 오픈소스가 아니기 때문이 아닐지... &lt;br&gt;&lt;br&gt;&lt;br&gt;덧) 막판에 좀 흥분한 경향이 있지만 수정할까 하다가.. 그냥... 놔두기로 했다. 보충삼아 약간 관련된 얘기로 전에 올렸던 &lt;a href="http://blog.outsider.ne.kr/924" target="_blank"&gt;사내 프레임워크 만들지 말자&lt;/a&gt;를 링크걸어 본다.&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.outsider.ne.kr/943?commentInput=true#entry943WriteComment"&gt;댓글 쓰기&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description>
			<category>Programming</category>
			<category>세미나 후기</category>
			<category>이희승</category>
			<author>outsideris@gmail.com (Outsider)</author>
			<guid isPermaLink="false">http://blog.outsider.ne.kr/943</guid>
			<comments>http://blog.outsider.ne.kr/943#entry943comment</comments>
			<pubDate>Sun, 26 May 2013 23:51:19 +0900</pubDate>
		<feedburner:origLink>http://blog.outsider.ne.kr/943</feedburner:origLink></item>
		<item>
			<title>Node.js MongoDB 드라이버에서 MapReduce 사용하기</title>
			<link>http://feedproxy.google.com/~r/rss_outsider_dev/~3/gH_v17P_GfM/942</link>
			<description>개인 프로젝트에서는 MongoDB를 주로 사용하곤 하는데 MongoDB 라이브러리로 이것저것 쓰다가 &lt;a href="https://github.com/mongodb/node-mongodb-native" target="_blank"&gt;mongodb-native&lt;/a&gt;가 MongoDB의 공식 Node.js 드라이버가 된 뒤로는 mongodb-native를 주로 사용하고 있다. 난 하둡쪽은 별로 다뤄본 적이 없어서 MapReduce에 대해서는 잘 모르지만 최근에 MapReduce를 처음으로 좀 써보게 되었다. MapReduce를 많이는 모르기 때문에 MapReduce에 대한 설명이라기 보다는 mongodb-native에서의 mapReduce 사용방법에 대한 글이다.&lt;br&gt;&lt;pre class="brush: java"&gt;
&amp;gt; db.example.find()
{ "name" : "outsider", "classOf" : "A", "eng" : 60, "kor" : 70, "math" : 50, "_id" : ObjectId("519877aebb61820000000001") }
{ "name" : "nephilim", "classOf" : "B", "eng" : 95, "kor" : 90, "math" : 100, "_id" : ObjectId("519877aebb61820000000002") }
{ "name" : "arawn", "classOf" : "A", "eng" : 80, "kor" : 80, "math" : 67, "_id" : ObjectId("519877aebb61820000000003") }
{ "name" : "zziuni", "classOf" : "B", "eng" : 70, "kor" : 65, "math" : 73, "_id" : ObjectId("519877aebb61820000000004") }
{ "name" : "fupfin", "classOf" : "A", "eng" : 65, "kor" : 50, "math" : 85, "_id" : ObjectId("519877aebb61820000000005") }
{ "name" : "tenshi", "classOf" : "B", "eng" : 85, "kor" : 70, "math" : 74, "_id" : ObjectId("519877aebb61820000000006") }
{ "name" : "anarcher", "classOf" : "B", "eng" : 70, "kor" : 78, "math" : 87, "_id" : ObjectId("519877aebb61820000000007") }
{ "name" : "nanha", "classOf" : "A", "eng" : 87, "kor" : 84, "math" : 91, "_id" : ObjectId("519877aebb61820000000008") }
{ "name" : "fallroot", "classOf" : "B", "eng" : 93, "kor" : 84, "math" : 84, "_id" : ObjectId("519877aebb61820000000009") }
{ "name" : "dani", "classOf" : "A", "eng" : 88, "kor" : 79, "math" : 77, "_id" : ObjectId("519877aebb6182000000000a") }
&lt;/pre&gt;&lt;br&gt;예를 들어 위와 같은 데이터가 디비에 들어있다고 하자. 각 학생의 리스트고 &lt;font color="#FF7635"&gt;classOf&lt;/font&gt;가 학생들이 속한 반이고 각 과목에 대한 점수가 들어있다. 각 반별로 과목에 대한 평균을 구한다고 할때 물론 그냥 쿼리로도 충분히 수행이 가능하지만 MapReduce를 사용해보자. 이 MapReduce는 다음과 같이 작성할 수 있다.&lt;br&gt;&lt;pre class="brush: javascript"&gt;
var MongoClient = require('mongodb').MongoClient;

MongoClient.connect('mongodb://localhost:27017/mrtest', function(err, db) {
&amp;nbsp; var example = db.collection('example');

&amp;nbsp; var map = function() {
&amp;nbsp; &amp;nbsp; emit(this.classOf, this);
&amp;nbsp; };

&amp;nbsp; var reduce = function(classOf, students) {
&amp;nbsp; &amp;nbsp; var engTotal = korTotal = mathTotal = 0;

&amp;nbsp; &amp;nbsp; students.forEach(function(student) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; engTotal += student.eng
&amp;nbsp; &amp;nbsp; &amp;nbsp; korTotal += student.kor
&amp;nbsp; &amp;nbsp; &amp;nbsp; mathTotal += student.math
&amp;nbsp; &amp;nbsp; });

&amp;nbsp; &amp;nbsp; return {
&amp;nbsp; &amp;nbsp; &amp;nbsp; classOf: classOf,
&amp;nbsp; &amp;nbsp; &amp;nbsp; engAvg: engTotal / students.length,
&amp;nbsp; &amp;nbsp; &amp;nbsp; korAvg: korTotal / students.length,
&amp;nbsp; &amp;nbsp; &amp;nbsp; mathAvg: mathTotal / students.length,
&amp;nbsp; &amp;nbsp; }
&amp;nbsp; };

&amp;nbsp; example.mapReduce(
&amp;nbsp; &amp;nbsp; &amp;nbsp; map,
&amp;nbsp; &amp;nbsp; &amp;nbsp; reduce,
&amp;nbsp; &amp;nbsp; &amp;nbsp; { out: 'mrtemp'},
&amp;nbsp; &amp;nbsp; &amp;nbsp; function(err, coll) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; coll.find().toArray(function(err, arr) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; console.log(arr);
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; });
&amp;nbsp; &amp;nbsp; &amp;nbsp; }
&amp;nbsp; );
});
&lt;/pre&gt;&lt;br&gt;코드가 약간 길지만(?) 그리 복잡하지는 않다. 앞부분은 디비에 연결하는 부분이고 먼저 사용한 map 함수와 reduce함수를 정의해야 한다. MapReduce 개념에 대해서 여기서 다 설명하기는 좀 무리가 있는데 간단히 말하면 map함수를 이용해서 데이터를 특정 키값의 데이터로 모아줄 수 있고 이를 reduce에서 다시 재가공해서 최종 데이터를 만들게 된다. 차례차례 보자.&lt;br&gt;&lt;pre class="brush: javascript"&gt;
var map = function() {
&amp;nbsp; emit(this.classOf, this);
};
&lt;/pre&gt;&lt;br&gt;수행한 쿼리의 모든 도큐먼트에 대해서 map함수가 실행되는데 map 함수내에서 this가 해당 도큐먼트를 가리킨다. 이 함수내에서 어떤 처리가 필요하다면 할 수 있고 최종적으로 reduce로 전달하기 위해서 &lt;font color="#FF7635"&gt;emit()&lt;/font&gt;을 실행해야 하는데 파라미터로 key와 value를 전달해야 한다. 여기서는 반별로 평균을 구할 것이므로 키에 classOf의 값을 전달하고 값에는 해당 도큐먼트를 통째로 전달했다.&lt;br&gt;&lt;pre class="brush: javascript"&gt;
var reduce = function(classOf, students) {
&amp;nbsp; var engTotal = korTotal = mathTotal = 0;

&amp;nbsp; students.forEach(function(student) {
&amp;nbsp; &amp;nbsp; engTotal += student.eng
&amp;nbsp; &amp;nbsp; korTotal += student.kor
&amp;nbsp; &amp;nbsp; mathTotal += student.math
&amp;nbsp; });

&amp;nbsp; return {
&amp;nbsp; &amp;nbsp; classOf: classOf,
&amp;nbsp; &amp;nbsp; engAvg: engTotal / students.length,
&amp;nbsp; &amp;nbsp; korAvg: korTotal / students.length,
&amp;nbsp; &amp;nbsp; mathAvg: mathTotal / students.length,
&amp;nbsp; }
};
&lt;/pre&gt;&lt;br&gt;이번엔 Reduce를 위함 함수이다. map에서 전달한 것과 같이 reduce함수는 키와 배열의 두 파라미터를 받는다. 키 값은 map이 전달한 키값이고 배열에는 같은 키를 가진 값(map이 전달한)의 배열이 전달된다. 즉, 여기서는 classOf의 값이 A와 B 두가지 뿐이므로 reduce는 2번 호출되고 A 키값에 A반 학생들객체의 배열, B 키값에는 B반 학생객체의 배열이 전달되게 된다. 이 Reduce 함수내에서 필요한 작업(여기서는 평균값 계산)을 한 뒤에 최종적으로 MongoDB에 넣을 JSON 객체(MongoDB 이므로)를 만들어서 반환하면 된다.&lt;br&gt;&lt;pre class="brush: javascript"&gt;
example.mapReduce(
&amp;nbsp; &amp;nbsp; map,
&amp;nbsp; &amp;nbsp; reduce,
&amp;nbsp; &amp;nbsp; { out: 'mrtemp'},
&amp;nbsp; &amp;nbsp; function(err, coll) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; coll.find().toArray(function(err, arr) {
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; console.log(arr);
&amp;nbsp; &amp;nbsp; &amp;nbsp; });
&amp;nbsp; &amp;nbsp; }
);
&lt;/pre&gt;&lt;br&gt;실제 MapReduce를 수행하는 부분이다. 컬렉션에 &lt;a href="http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#mapreduce" target="_blank"&gt;mapRecude()함수&lt;/a&gt;가 존재하고 처음 두 파라미터로 Map, Reduce함수를 전달해주면 된다. 세번째 파라미터는 옵션값인데 out은 MapReduce를 수행한 결과가 들어갈 컬렉션을 지정한다. 여기서는 reduce가 2개의 문서(A, B)를 생성하므로 mrtemp라는 컬렉션이 두 문서가 들어가게 된다. 여기서는 쿼리에 어떤 조건을 주지 않았지만 쿼리조건이 필요하다면 query 옵션을 사용할 수 있다. 마지막으로 콜백으로 실행결과와 MapReduce&amp;nbsp; 결과가 들어간 컬렉션을 반환하므로 결과를 조회하려면 해당 컬렉션에서 다시 조회를 해서 데이터를 가져와야 한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&lt;font size="5"&gt;그 외 몇가지 사항들...&lt;/font&gt;&lt;/b&gt;&lt;br&gt;&lt;ul&gt;&lt;li&gt;map, reduce로 전달한 함수는 MongoDB내에서 수행된다. 그러므로 일반적인 자바스크립트처럼 프로그램내의 어떤 변수를 클로저형태로 map, reduce함수로 전달할 수 없다. map, reduce함수는 각각 개별적으로 동작할 수 있어야 한다. &lt;br&gt;&lt;/li&gt;&lt;li&gt;map, reduce 과정을 디버깅하려면 MongoDB의 로그를 봐야한다. 당연히 &lt;font color="#FF7635"&gt;console.log&lt;/font&gt;같은건 안 먹히고 map, reduce 함수내에서 출력해 보고 싶다면 &lt;font color="#FF7635"&gt;print() &lt;/font&gt;함수를 사용하면 MongoDB의 로그에 출력되기 때문에 전달되는 값을 확인하고 싶다면 &lt;font color="#FF7635"&gt;print()&lt;/font&gt;로 디버깅할 수 있다.&lt;/li&gt;&lt;li&gt;MongoDB의 내부를 다 파악해 보지는 않았지만 로그상으로 보면 MapReduce를 수행할 때마다 기존의 맵리듀스용 컬렉션(out으로 지정한)을 drop한 뒤에 다시 생성한다. &lt;br&gt;&lt;/li&gt;&lt;li&gt;MapReduce과정은 순차적으로 처리가 된다. 동시에 여러 MapReduce를 요청한다고 하더라도 한 MapReduce를 모두 처리한뒤에 나머지를 처리하게 된다. 이는 중간에 서로 간섭이 일어나지 않도록 하기 위함으로 보이므로 동시에 여러 요청이 들어가게 된다면 지연시간이 오래걸리므로 유의해서 사용해야 한다.&lt;/li&gt;&lt;li&gt;MapReduce를 잘 몰라서 잘 아는 분께 물어본 봐로는 이러한 동시성 문제나 성능향상을 위한 MapReduce를 Incremental MapReduce라고 부른다고 한다. MongoDB의 &lt;a href="http://docs.mongodb.org/manual/tutorial/perform-incremental-map-reduce/" target="_blank"&gt;Incremental MapReduce&lt;/a&gt;는 별로 좋지 않아보이고 데이터의 타임스탬프를 사용해서 직접 처리를 해주어야 하는 것으로 보인다.(사용해 보진 않았다.) 다른 디비에서는 MapReduce 명령차원에서 Incremental 사용여부를 지정할 수 있기도 한데 MongoDB는 그렇지 않아서 이부분은 신경써서 처리해야할듯 싶다.&lt;br&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.outsider.ne.kr/942?commentInput=true#entry942WriteComment"&gt;댓글 쓰기&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description>
			<category>node.js</category>
			<category>Incremental MapReduce</category>
			<category>MapReduce</category>
			<category>MongoDB</category>
			<category>mongodb-native</category>
			<category>node.js</category>
			<author>outsideris@gmail.com (Outsider)</author>
			<guid isPermaLink="false">http://blog.outsider.ne.kr/942</guid>
			<comments>http://blog.outsider.ne.kr/942#entry942comment</comments>
			<pubDate>Mon, 20 May 2013 01:49:21 +0900</pubDate>
		<feedburner:origLink>http://blog.outsider.ne.kr/942</feedburner:origLink></item>
	</channel>
</rss>
