Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Scala로 MongoDB를 연결할 때 DTO대신 Implicit Type Conversion 사용하기

Scala로 MongoDB를 만져보고 있습니다. MongoDB는 이번에 처음 만져보는 것이긴 한데 처음 써보는 NoSQL에 재미를 제법 느끼고 있습니다. 아직 NoSQL에 대해서 별로 파악이 안되기는 했지만 MongoDB는 Java로 된  mongo-java-driver가 제공되고 있는데다가(MongoDB에서는 다양한 언어에 제한 Driver를 제공하고 있습니다.) 튜토리얼이 잘 되어 있어서 단순한 수준의 사용에서는 그다지 어렵지 않았습니다.


val m = new Mongo
val db = m.getDB("mongodb.db.test")
val col = db.getCollection("posts")

val query = new BasicDBObject
val cur = col.find(query)
var tweets:List[TweetPost] = Nil

while(cur.hasNext) {
    val obj = cur.next
    val tw = new TweetPost(obj.get("postId").asInstanceOf[Long],
                           obj.get("userName").asInstanceOf[String],
                           obj.get("userScreenName").asInstanceOf[String],
                           obj.get("createdAt").asInstanceOf[java.util.Date],
                           obj.get("post").asInstanceOf[String])
    tweets = tw :: tweets
}
tweets

test 디비의 posts 컬렉션에서 데이터를 가져오는 부분인데 일반적으로 너무 많이 쓰는 형식이기 때문에 Scala이기는 하지만 거의 습관처럼 Java에서 하던 방식 그대로 작성하였습니다. 디비에서 SELECT해와서 루프돌면서 하나씩 꺼내와서 DTO에 담고 DTO를 컬렉션에 넣어서 컬렉션을 리턴하는 현태입니다. 아래 코드는 DTD 입니다.


class TweetPost(val postId:Long, val userName:String, val userScreenName:String, val createdAt:java.util.Date, val post:String)

Java Bean과 동일한 형태는 아니지만 Scala에서는 위 코드처럼 간단하게 DTO객체를 만들 수 있습니다.



일반적인 RDBMS라면 그냥 위의 코드로 사용을 했겠지만 MongoDB이다 보니 코드를 작성하고나니 약간 고민에 빠졌습니다. 이 고민의 시작은 일단 일일이 asInstanceOf를 사용해서 캐스팅을 해줘야하는 하는데 Scala스럽지도 않으면서 MongoDB스럽지도 않은 생각이 들었습니다. MongoDB는 스키마가 없는 유연함을 가지고 있는데 디비에서 꺼내오자마자 형식을 픽스에서 DTO에 넣는 것이 과연 맞는 것인가하는 생각이었습니다. 물론 이 고민은 여러사람하고 얘기를 해보다 보니 근본적으로 좀 잘못되기는 했습니다. 동접타입 언어가 아닌이상 언어레벨에서 타입을 유연하게 하는 것이 어렵고 사실 사용하려면 디비가 고정되어 있지 않다고 하더라두 아무렇게나 막 넣는 것도 아니고 가져온 데이터를 어느 순간에는 형식을 고정해야 할 필요가 있기 때문에 완전히 유연하게 한다는 것이 잘못된 생각이기도 했고 로직상으로도 픽스를 하지 않고 사용한다는 것도 문제가 있기 때문입니다.

하지만 그럼에도 타입을 픽스하는 것은 그렇다 치더라도 저런식으로 일일이 변환해서 DTO로 넣어주는 것이 그다지 Scala스럽지 않게 느껴져서 이런저런 고민을 해보고 scala-driver도 사용해보았지만 사용방법이 너무 어려워서 적용이 쉽지도 않았던 데다가 사용법도 편하지 않았던터라 아래처럼 변경하였습니다.


class TweetPost(obj: DBObject) {
    def postId:Long = obj.get("postId").asInstanceOf[Long]
    def userName:String = obj.get("userName").asInstanceOf[String]
    def createdAt:java.util.Date = obj.get("createdAt").asInstanceOf[java.util.Date]
    def post:String = obj.get("post").asInstanceOf[String]
    def profileImageURL:String = obj.get("profileImageURL") match {
        case x:String => x.toString
        case _ => "http://s.twimg.com/a/1283564528/images/default_profile_3_normal.png"
        }
}

DTO클래스를 위와같이 변경하였습니다. 이 클래스는 DTO가 아닌 스칼라에서 제공하는 implicit 타입컨버전을 사용하도록 하기 위함입니다.


implicit def converters(obj:DBObject) = new TweetPost(obj)

// ... 중간코드 생략
while(cur.hasNext) {
    val obj = cur.next

    tweets = obj :: tweets
}


이제 데이터를 컬렉션에 추가하는 것은 위처럼 변경할 수 있습니다. MongoDB에서 데이터를 가져온 DBObject를 일일이 TweetPost로 전환할 필요 없이 DBObject자체를 컬렉션에 추가한 뒤에 상단에 DBObject에 대해서 TweetPost로 Implicit 타입컨버전이 되도록 선언하였습니다. 이렇게 선언하면 DBObject에는 없는 메서드를 사용하면 자동으로 TweetPost로 타입컨버전이 발생하면서 TweetPost의 메서드가 호출됩니다. obj.postId를 하면 postid를 가져올 수 있습니다.

완전히 만족스럽지는 않고 또 아직 적용만 한 상태라 사용하다보면 또 어떤 단점이 있을지 모르겠지만 일단은 사용하기도 좀더 편하고 좀더 스칼라스러워 진 것 같습니다. 물론 캐스팅을 하는 코드는 여전히 존재하고 비즈니스 로직에서 TweetPost쪽으로 코드가 이동했을 뿐이기는 하지만 이렇게 하면 스키마가 약간 달라진다고 하더라도 일일이 DTO를 변경할 필요없이 TweetPost에 메서드만 추가해서 사용할 수 있기 때문에 좀더 편해진 것 같습니다.(누가 더 좋은 생각 있으시면 공유 좀...)
2010/09/19 04:55 2010/09/19 04:55