Защита и Rogue для возможностей MongoDB — запросов

#scala #mongodb #casbah

#scala #mongodb #касба

Вопрос:

В настоящее время я использую Casbah с MongoDB для реализации веб-сервиса. Пока у меня с этим проблем нет. Я тоже использую Scala.

Однако мне было просто любопытно узнать, есть ли что-то лучше, чем Casbah, для выполнения большого количества запросов типа find / findOne.

Я наткнулся на Rogue, типобезопасный DSL на основе Scala, который, как утверждается, упростит выполнение запросов и сделает их более удобочитаемыми.

Итак, я хотел знать, было бы полезно перейти на Rogue, чтобы по мере того, как проект веб-сервиса становится больше и сложнее, это могло бы помочь получить поддержку Rogue для запросов?

Просто хотел выяснить, следует ли мне продолжать или перейти на что-то лучшее.

Ответ №1:

На данный момент Rogue работает только с системой записи MongoDB Lift.

Одна из причин, по которой он обеспечивает полную безопасность типов, заключается в том, что он использует надежную, четко определенную структуру объекта для записи Lift. У вас гораздо меньше возможностей выполнять запросы «свободной формы», поскольку у вас есть для этого полная структура. Вот что я имею в виду, говоря о демонстрации Lift-Record Rogue, которую я сделал для недавнего вебинара Scala. С тех пор, как я это сделал, в Lift amp; Rogue кое-что изменилось, поэтому код может немного устареть, но все еще является репрезентативным. Это модель записи о подъеме для MongoDB:

 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'nn".format(q))

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

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

  println("nnn")

  for (x <- MongoEvent where (_.eventType eqs EventType.Conference) 
                       orderAsc(_.language) andDesc(_.name)) { 
    println("Name: %s Language: %sn".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, у нас есть встроенный DSL, который разработан, чтобы максимально точно имитировать встроенную модель запросов MongoDB. Это работает против любой произвольной базовой модели, но многое делает для обеспечения соблюдения уровней безопасности типов, где это возможно. Вот (слегка устаревший, поскольку он взят из той же презентации) пример выполнения запросов 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("nnTesting 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)
  

Примечательно, что мы выполняем запросы к модели свободной формы, но используем операторы DSL вместо вложенных определений MongoDB. Существует множество фантастических сопоставлений операторов, вплоть до оператора $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, есть два типа операторов.

Операторы простого слова, в которых крайняя левая часть инструкции является оператором $. Примерами этого являются $set , $rename и т.д. Подробнее смотрите в спецификациях для операторов Bareword.

«Основные» операторы — это операторы, которые существуют в правой части инструкции, такие как $type . Смотрите Спецификации для основных операторов для получения дополнительной информации.

Я знаю, что это довольно подробный ответ, но я хотел убедиться, что вы понимаете свои варианты, а также ограничения обоих решений. Casbah предоставит вам DSL, который соответствует MongoDB и устраняет некоторые синтаксические проблемы (имейте в виду также, что Casbah предоставляет getAs[T] метод DBObject для запроса значения из DBObject как определенного типа, который люди часто упускают из виду); многие пользователи не знают о существовании DSL, прежде чем они начнут искать что-то для выполнения встроенного. Однако, по мнению некоторых пользователей, DSL запросов Casbah также выглядит немного «грубоватым»… как автор этого я предпочитаю его простоту и элегантность, поскольку мне нужно помнить только синтаксис ** MONGODB * для выполнения запросов, а не MongoDB и другой DSL. Он также основан на запросах в свободной форме и не предоставляет таких же структурированных, безопасных для типов во время компиляции средств, которые предоставляет Rogue.

Для Rogue, напротив, также требуется полностью определенная модель записи, которая подходит не для каждого приложения.

Однако я хотел бы услышать, что можно улучшить в любом продукте, если для ваших нужд есть «золотая середина», которой ни один продукт не соответствует должным образом.

Комментарии:

1. «Я предпочитаю простоту и элегантность, поскольку мне нужно помнить только синтаксис * MONGODB для выполнения запросов, а не MongoDB и другой DSL». — Это, вероятно, может заставить меня на данный момент остаться с Casbah. Я не хочу усложнять безопасность типов во время компиляции.

2. И я думаю, я просто хотел убедиться, что все, что можно сделать с помощью Rogue, также можно сделать с помощью самой Casbah, не усложняя ее реализацию. Пока Casbah работает для меня довольно хорошо, и я, возможно, не захочу использовать другую модель данных.

3. Вы также можете рассмотреть возможность добавления в [Salat][ github.com/novus/salat/wiki/Quick-start ] который сопоставляет объекты с Casbah и поддерживает запросы Casbah.