3

私は現在、Webサービスを実装するためにMongoDBでCasbahを使用しています。今のところ問題ありません。私もScalaを使っています。

しかし、多くのfind / findOneタイプのクエリを実行するために、Casbahよりも優れたものがあるかどうかを知りたいと思っていました。

私は、クエリをより簡単で読みやすくする、タイプセーフなScalaベースのDSLであるRogueに出くわしました。

そこで、Rogueに移行することが役立つかどうかを知りたいと思いました。そうすれば、Webサービスプロジェクトが大きく複雑になるにつれて、Rogueによるクエリのサポートが役立つ可能性があります。

続けるべきか、もっと良いものにシフトすべきかを知りたかっただけです。

4

1 に答える 1

12

現時点では、RogueはLiftのMongoDB-Recordシステムに対してのみ機能します。

フルタイプの安全性を提供する理由の1つは、Lift-Recordに強力で明確に定義されたオブジェクト構造を使用していることです。完全な構造が整っているため、「自由形式」のクエリを実行する能力ははるかに低くなります。これが、最近のScalaWebinarで行ったLift-Record+Rogueのデモの意味です。私がこれを行ってから、Lift&Rogueでいくつかの変更が加えられたため、コードは少し古くなっている可能性がありますが、それでも代表的なものです。これは、MongoDBのLift-Recordモデルです。

object LiftRecordDemo extends Application {
  // We'll use enums for Type and Subtype
  object EventType extends Enumeration {
    type EventType = Value
    val Conference, Webinar = Value
  }

  object EventSubType extends Enumeration {
    type EventSubType = Value
    val FullDay = Value("Full Day")
    val HalfDay = Value("Half Day")
  }



  class MongoEvent extends MongoRecord[MongoEvent] with MongoId[MongoEvent] {
    def meta = MongoEvent

    object name extends StringField(this, 255)
    object eventType extends EnumField(this, EventType) 
    object eventSubType extends OptionalEnumField(this, EventSubType)
    object location extends JsonObjectField[MongoEvent, EventLocation](this, EventLocation)  {
      def defaultValue = EventLocation(None, None, None, None, None, None, None)
    }

    object hashtag extends OptionalStringField(this, 32)
    object language extends OptionalStringField(this, 32)
    object date extends JsonObjectField[MongoEvent, EventDate](this, EventDate) {
      def defaultValue = EventDate(new DateTime, None)
    }

    object url extends OptionalStringField(this, 255)
    object presenter extends OptionalStringField(this, 255)

  }

  object MongoEvent extends MongoEvent with MongoMetaRecord[MongoEvent] {
    override def collectionName = "mongoEvents"
    override def formats = super.formats + new EnumSerializer(EventType) + new EnumSerializer(EventSubType)
  }


  case class EventLocation(val venueName: Option[String], val url: Option[String], val address: Option[String], val city: Option[String], val state: Option[String], val zip: Option[String], val country: Option[String]) extends JsonObject[EventLocation] {
    def meta = EventLocation
  }

  object EventLocation extends JsonObjectMeta[EventLocation]

  case class EventDate(start: DateTime, end: Option[DateTime]) extends JsonObject[EventDate] {
    def meta = EventDate
  }

  object EventDate extends JsonObjectMeta[EventDate]
}

ご覧のとおり、強く型付けされた安全なクエリを利用するには、事前にMongoDBデータモデルを定義する必要があります...Rogueはコンパイル時にそのほとんどを適用します。このモデルに対する不正な例を次に示します。

  // Tell Lift about our DB
  val mongoAddr = MongoAddress(MongoHost("127.0.0.1", 27017), "scalaWebinar")

  MongoDB.defineDb(DefaultMongoIdentifier, mongoAddr)

  // Rogue gives us a saner approach, although still hobbled by some
  // of Lift-MongoDB-Record's limits on embedded docs

  val q = MongoEvent where (_.eventType eqs EventType.Webinar)

  println("Rogue created a Query '%s'\n\n".format(q))

  for (x <- MongoEvent where (_.eventType eqs EventType.Webinar)) {
    println("Name: %s Presenter: %s\n".format(x.name, x.presenter))
  }

  // Rogue can also do sorting for you, which is useful

  println("\n\n\n")

  for (x <- MongoEvent where (_.eventType eqs EventType.Conference) 
                       orderAsc(_.language) andDesc(_.name)) { 
    println("Name: %s Language: %s\n".format(x.name, x.language))
  }
  val start = new DateTime(2011, 2, 1, 0, 0, 0, 0)
  val end = new DateTime(2011, 3, 1, 0, 0, 0, 0)

    /** The following would be nice but unfortunately, 
      doesn't work because of lift's current embedded doc
      implementation
    */
  //val dateQ = MongoEvent where (_.date.start after start) 
                           //and (_.date.end before end)

RogueとLift-Recordが素晴らしいものではないと言っているのではなく、厳密に定義されたコンパイル時データモデルで動作することに注意してください。

代わりに、Casbahで同様のコンテキストを使用したい場合は、MongoDBの組み込みクエリモデルを可能な限り模倣するように設計された組み込みDSLがあります。これは、任意の基礎となるモデルに対して機能しますが、可能な場合は型安全性のレベルを強制するためにLOTを実行します。これは、Casbahのクエリの(同じプレゼンテーションから少し日付が付けられた)例です。

  // What about querying?  Lets find all the non-US events

  for (x <- mongo.find(MongoDBObject("location.country" -> 
                        MongoDBObject("$ne" -> "USA")))) println(x)

  /* There's a problem here: We got back the Webinars too because 
     They don't have a country at all, so they aren't "USA"
    */
  println("\n\nTesting for existence of Location.Country:")

  for (x <- mongo.find(MongoDBObject("location.country" -> MongoDBObject(
                       "$ne" -> "USA",
                       "$exists" -> true 
                      )))) println(x)

  // This is getting a bit unwieldy.  Thankfully, Casbah offers a DSL
  val q = $or ("location.country" -> "USA", "location.country" -> "Japan")

  println("\n Created a DBObject: %s".format(q))

  println("\n Querying using DSL Object...")

  for (x <- mongo.find(q)) println(x)

  // It's possible to construct more complex queries too.

  // Lets find everything in February

  println("\n February Events...")
  val start = new DateTime(2011, 2, 1, 0, 0, 0, 0)
  val end = new DateTime(2011, 3, 1, 0, 0, 0, 0)
  val dateQ = "date.start" $gte start $lt end 

  println("\n Date Query: %s".format(dateQ))

  for (x <- mongo.find(dateQ, MongoDBObject("name" -> true, "date" -> true))) println(x)

特に、自由形式のモデルに対してクエリを実行していますが、ネストされたMongoDB定義の代わりにDSL演算子を使用しています。コンパイル時の安全性のためにクラスマニフェストを使用して型をテストするための$type演算子に至るまで、演算子の素晴らしいマッピングがたくさんあります。

"Casbah's $type operator" should {
  "Accept raw Byte indicators (e.g. from org.bson.BSON)" in {
    // Don't need to test every value here since it's just a byte
    val typeOper = "foo" $type org.bson.BSON.NUMBER_LONG
    typeOper must notBeNull
    typeOper.toString must notBeNull
    typeOper must haveSuperClass[DBObject]
    typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.NUMBER_LONG))
  }

  "Accept manifested Type arguments" in {
    "Doubles" in {
      val typeOper = "foo".$type[Double]
      typeOper must notBeNull
      typeOper.toString must notBeNull
      typeOper must haveSuperClass[DBObject]
      typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.NUMBER))
    }
    "Strings" in {
      val typeOper = "foo".$type[String]
      typeOper must notBeNull
      typeOper.toString must notBeNull
      typeOper must haveSuperClass[DBObject]
      typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.STRING))
    }
    "Object" in {
      "via BSONObject" in {
        val typeOper = "foo".$type[org.bson.BSONObject]
        typeOper must notBeNull
        typeOper.toString must notBeNull
        typeOper must haveSuperClass[DBObject]
        typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.OBJECT))
      }
      "via DBObject" in {
        val typeOper = "foo".$type[DBObject]
        typeOper must notBeNull
        typeOper.toString must notBeNull
        typeOper must haveSuperClass[DBObject]
        typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.OBJECT))
      }
    }
    "Array" in {
      "via BasicDBList" in {
        val typeOper = "foo".$type[BasicDBList]
        typeOper must notBeNull
        typeOper.toString must notBeNull
        typeOper must haveSuperClass[DBObject]
        typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.ARRAY))
      }
      "via BasicBSONList" in {
        val typeOper = "foo".$type[org.bson.types.BasicBSONList]
        typeOper must notBeNull
        typeOper.toString must notBeNull
        typeOper must haveSuperClass[DBObject]
        typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.ARRAY))
      }
    }
    "OID" in {
      val typeOper = "foo".$type[ObjectId]
      typeOper must notBeNull
      typeOper.toString must notBeNull
      typeOper must haveSuperClass[DBObject]
      typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.OID))
    }
    "Boolean" in {
      val typeOper = "foo".$type[Boolean]
      typeOper must notBeNull
      typeOper.toString must notBeNull
      typeOper must haveSuperClass[DBObject]
      typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.BOOLEAN))
    }
    "Date" in {
      "via JDKDate" in {
        val typeOper = "foo".$type[java.util.Date]
        typeOper must notBeNull
        typeOper.toString must notBeNull
        typeOper must haveSuperClass[DBObject]
        typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.DATE))
      }
      "via Joda DateTime" in {
        val typeOper = "foo".$type[org.joda.time.DateTime]
        typeOper must notBeNull
        typeOper.toString must notBeNull
        typeOper must haveSuperClass[DBObject]
        typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.DATE))
      }
    }
    "None (null)" in {
      // For some reason you can't use NONE 
      val typeOper = "foo".$type[Option[Nothing]]
      typeOper must notBeNull
      typeOper.toString must notBeNull
      typeOper must haveSuperClass[DBObject]
      typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.NULL))
    }
    "Regex" in {
      "Scala Regex" in {
        val typeOper = "foo".$type[scala.util.matching.Regex]
        typeOper must notBeNull
        typeOper.toString must notBeNull
        typeOper must haveSuperClass[DBObject]
        typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.REGEX))
      }
    }
    "Symbol" in {
      val typeOper = "foo".$type[Symbol]
      typeOper must notBeNull
      typeOper.toString must notBeNull
      typeOper must haveSuperClass[DBObject]
      typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.SYMBOL))
    }
    "Number (integer)" in {
      val typeOper = "foo".$type[Int]
      typeOper must notBeNull
      typeOper.toString must notBeNull
      typeOper must haveSuperClass[DBObject]
      typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.NUMBER_INT))
    }
    "Number (Long)" in {
      val typeOper = "foo".$type[Long]
      typeOper must notBeNull
      typeOper.toString must notBeNull
      typeOper must haveSuperClass[DBObject]
      typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.NUMBER_LONG))
    }
    "Timestamp" in {
      val typeOper = "foo".$type[java.sql.Timestamp]
      typeOper must notBeNull
      typeOper.toString must notBeNull
      typeOper must haveSuperClass[DBObject]
      typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.TIMESTAMP))
    }
    "Binary" in {
      val typeOper = "foo".$type[Array[Byte]]
      typeOper must notBeNull
      typeOper.toString must notBeNull
      typeOper must haveSuperClass[DBObject]
      typeOper must beEqualTo(nonDSL("foo", "$type", org.bson.BSON.BINARY))
    }

  }

(注:casbah-queryパッケージとそれに関連するインポートをコードにインポートするか、事前変調からのデフォルトの「casbah」インポートを使用する必要があります)。Casbahの仕様は、現在、すべてのDSLオペレーターを完全にカバーしています。ドキュメントは現時点では遅れていますが、仕様はそれらの使用法の優れた入門書として役立ちます。Casbahには、MongoDBと同様に、2種類の演算子があることに注意してください。

ベアワード演算子。ステートメントの左端は$演算子です。この例としては$set、、$renameなどがあります。詳細については、ベアワード演算子の仕様を参照してください。

「コア」演算子は、ステートメントの右側に存在する演算子です。たとえば、$type。詳細については、コア演算子の仕様を参照してください。

私はこれがかなり詳細な答えであることを知っていますが、私はあなたがあなたの選択肢と両方の解決策の限界を理解していることを確認したかったのです。Casbahは、MongoDBに密接にマッピングし、構文上の問題の一部を削除するDSLを提供します(Casbahは、特定のタイプとしての値を要求するgetAs[T]方法を提供します。これは、人々が見落としがちです)。多くのユーザーは、組み込みの機能を実行するために何かを探す前に、DSLが存在することに気づいていませんDBObjectDBObject、CasbahのQuery DSLも、一部の人々の意見では少し「無愛想」に見えます...作成者として、MongoDBと別のDSLの両方ではなく、クエリ用の** MONGODB *構文を覚えておくだけでよいので、シンプルで優雅なものを好みます。 。また、自由形式のクエリに基づいており、Rogueが提供するのと同じ構造化されたコンパイル時タイプの安全で認識可能な機能を提供しません。

対照的に、ローグはそれに対して完全に定義されたレコードモデルも必要としますが、これはすべてのアプリケーションに適合するわけではありません。

ただし、どちらの製品も適切に満たされないニーズの「中間」がある場合、どちらの製品でも改善できる点をお聞きしたいと思います。

于 2011-05-10T15:15:54.490 に答える