Статьи

Поддержка NoSQL в Lift

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

Автором этой статьи является Тимоти Перетт, автор книги « Лифт в действии».


Лифт в действии

Простая функциональная веб-платформа для Scala

Тимоти Перретт

 

Различные продукты широко объединены в рамках так называемого движения NoSQL, поскольку все они избегают SQL в пользу специализированного интерфейса. Примеры включают в себя пользовательские интерфейсы связи, такие как Thrift, пользовательские форматы данных, такие как BSON, и пользовательские конструкции запросов, такие как MapReduce. В этой статье автор Lift in Action Тим Перретт (Tim Perrett) рассматривает интеграцию Lift с хранилищами NoSQL и абстракции Record, предоставляемые фреймворком.

 Вы также можете быть заинтересованы в …

 


Мы также можем оценить элегантную логику, лежащую в основе нормализации схемы базы данных, но чаще всего существует несоответствие между этой структурой и парадигмами современного веб-программирования. Разработчики часто помещают системы ORM, такие как Mapper, между кодом своего приложения и базовой СУБД, чтобы получить более внушительное ощущение доступа к своим данным.

 

Отдельные организации все чаще задаются вопросом, существует ли, возможно, лучший, более естественный способ работы со своими данными, который лучше подходил бы для различных специализированных проблемных областей. Хотя эти проблемные области довольно сильно различаются, различные продукты широко объединены в рамках так называемого движения NoSQL, поскольку все они избегают SQL в пользу специализированного интерфейса. Примеры включают в себя пользовательские интерфейсы связи, такие как Thrift, пользовательские форматы данных, такие как BSON, и пользовательские конструкции запросов, такие как MapReduce.

П р и м е ч а н и е — Движение NoSQL все еще является относительно новой разработкой, и, если у вас не было времени исследовать его, вам может быть интересно, какова цель всех этих специализированных технологий. Большинство решений NoSQL предназначены для решения конкретного случая использования, как правило, из отрасли, из которой поставляется поставщик. Хотя многие люди находят эти технологии полезными в общем смысле, нет необходимости беспокоиться о них, если они не соответствуют вашему варианту использования.

Реляционные базы данных по-прежнему хорошо подходят для большинства приложений. В настоящее время доступно множество хранилищ NoSQL, поэтому следующая часть специально посвящена интеграции Lift с хранилищами NoSQL и абстракциям Record, которые предоставляет инфраструктура.

Поддержка NoSQL в Lift

NoSQL выпускается во многих вариантах, и каждый магазин предоставляет различные функции. Поддержка Lift для различных бэкэндов росла довольно органично, поскольку сцена NoSQL расширялась и развивалась. На момент написания этой статьи Lift предоставляет готовую поддержку NoSQL для CouchDB и MongoDB .

И Couch, и Mongo — это так называемые хранилища данных, ориентированные на документы. По сути, это означает, что вместо использования таблиц, как в системах реляционных баз данных, в безсхемных документах JSON хранится информация, где каждый документ имеет свойства и коллекции, к которым можно получить доступ, как и к любому другому документу JSON. Вы можете получить конкретный документ, запросив определенный ключ. Например, представьте, что вы запрашиваете конкретный номер ISBN, чтобы получить интересующий вас объект книги из коллекции книг. Вы можете считать эти ключи аналогичными первичным ключам в таблицах системы управления реляционными базами данных (RDBMS).

Запись также предоставляет определенные идиомы, так что разные механизмы хранения имеют сходную, если не идентичную операционную семантику. Как правило, записи могут быть созданы и сохранены следующим образом:

MyThing.createRecord.fieldOne("value").fieldTwo("tester").save

Это верно как для реализаций CouchDB, так и для MongoDB, которые рассматриваются здесь, и, как правило, должно иметь место для большинства реализаций Record.

Без лишних слов давайте пройдемся по некоторым основным функциям, которые предоставляет каждая абстракция, прежде чем углубленно исследовать абстракцию MongoDB.

CouchDB

Одним из первых магазинов NoSQL, появившихся в популярной ИТ-культуре, был CouchDB. Вообще говоря, у Куша и Монго есть много общего, но в основном они глубокие.

Couch, как правило, превосходит в сценариях, где у вас репликация мастер-мастер, обычно встречается в приложениях, которые отключаются или требуют синхронизации баз данных. Хорошим примером может служить почтовый клиент, синхронизирующийся с сервером — локальная база данных, скорее всего, устареет, если пользователь на некоторое время отключится от сети. По сути, если ваша проблема требует возможной согласованности распределенных узлов хранения или вам требуется интерфейс MapReduce, CouchDB является хорошим кандидатом для оценки.

Возможная последовательность

С появлением распределенных систем быстро стало очевидно, что создание распределенных систем (особенно хранилищ данных), которые поддерживают свойства ACID (атомарность, согласованность, изоляция, долговечность), будет чрезвычайно трудным, и что такие системы вряд ли будут масштабироваться. потребностям огромных систем, которые строятся сейчас и смотрят в будущее.

Lift предоставляет абстракцию Record для взаимодействия с CouchDB и позволяет вам взаимодействовать с Couch таким образом, чтобы следовать идиомам Record наличия контекстно-богатых полей. Прежде чем пытаться использовать модуль Couch, убедитесь, что вы включили зависимость в определение вашего проекта SBT :

val couch = "net.liftweb" %% "lift-couchdb" % liftVersion

Чтобы начать использовать интеграцию Lift с Couch, для вашего класса загрузки требуется небольшой объем настройки :

import net.liftweb.couchdb.{CouchDB, Database}

import dispatch.{Http, StatusCode}

 

val database = new Database("bookstore")

database.createIfNotCreated(new Http())

CouchDB.defaultDatabase = database

Код в этом примере довольно прост и должен быть достаточно понятным, за исключением, возможно, нового оператора Http () . Клиент CiftDB от Lift построен поверх проекта HTTP Dispatch , чтобы обмениваться данными с сервером Couch. По сути, этот оператор передает CouchDB запись транспортного средства, с помощью которого он может выполнять HTTP-вызовы. В этом конкретном случае база данных определяется и указывается в объекте конфигурации CouchDB, поэтому вам не нужно передавать информацию о соединении позже, при условии, что вы хотите общаться только с одним сервером Couch.

С настроенным соединением базы данных вы можете начать взаимодействовать с CouchDB, определив специализированные классы записи, как подробно описано в следующем листинге.

Перечисление 1, Реализующее основную запись CouchDB

import net.liftweb.record.field._

import net.liftweb.couchdb.{CouchRecord,CouchMetaRecord}

 

class Book private () extends CouchRecord[Book]{    #A

  def meta = Book

 

  val title = new StringField(this, "")             #B

  val publishedInYear = new IntField(this, 1990)    #B

}

object Book extends Book with CouchMetaRecord[Book] #A

#A Определяет типы полей

#B Реализует типы диванов

CouchDB требует, чтобы в каждом конкретном объекте было реализовано несколько различных полей для управления системами управления версиями и редакциями, обе из которых автоматически обрабатываются для вас супертипом CouchRecord .

В CouchMetaRecord и базы данных типов дают вам различные удобные методы для взаимодействия с видом предоставляемых Couch для взаимодействия с сохраненными документами: как создавать и запросы. Запросы CouchDB по существу используют эти представления Map-Reduce для получения данных стиля запроса. Сами представления могут быть предварительно созданы и затем использованы в вашем приложении во время выполнения.

Чтобы создать представление с использованием lift-couchdb, вы можете сделать что-то вроде этого:

import net.liftweb.json.Implicits.{int2jvalue, string2jvalue}

import net.liftweb.json.JsonAST.{JObject}

import net.liftweb.json.JsonDSL.{jobject2assoc, pair2Assoc, pair2jvalue}

 

val design: JObject =

  ("language" -> "javascript") ~

  ("views" -> ("oldest" ->

    (("map" -> "function(doc) {

if (doc.type == 'Book'){ emit(doc.title, doc.publishedInYear); }}") ~

    ("reduce" -> "function(keys, values) {

return Math.max.apply(null, values); }"))))


Http(database.design("design_name") put design)     #1

# 1 Создает новый дизайн

Если вы не слишком знакомы с Couch, это может выглядеть несколько странно. Это специализированная функция CouchDB MapReduce, которая получает самый старый документ книги. Ключевая строка отправляет дизайн с присвоенным именем «design_name» в базу данных (# 1). Как только это будет сделано, вы можете выполнить запрос через мета-запись Book, как показано ниже:

val book = Book.queryView("design_name", "oldest")

Эта строка вызывает Couch и выполняет предопределенное представление, чтобы получить самый старый заголовок Книги, хранящийся в базе данных.

CouchDB сам по себе является большой темой, но на высоком уровне это должно дать вам представление о том, как работает реализация Lift.

MongoDB

MongoDB, как и CouchDB, является ориентированным на документы хранилищем, но вместо того, чтобы использовать предварительно написанные представления для получения данных запросов, Mongo лучше подходит для создания динамических запросов, аналогичных тем, которые вы могли бы построить с использованием традиционного SQL. Mongo использует собственный синтаксис запросов, а не MapReduce, который, хотя и поддерживается, предназначен для агрегирования данных, а не для запросов общего назначения.

Mongo использует собственный двоичный протокол для связи между вашим приложением и хранилищем данных, что обычно дает более гибкий интерфейс программирования, чем это возможно с HTTP. Кроме того, MongoDB позиционирует себя как база данных NoSQL общего назначения, которая была разработана с нуля для использования в интернет-приложениях.

В отличие от реализации CouchDB, поддержка Mongo в Lift состоит из двух частей: lift-mongo предоставляет тонкую оболочку Scala вокруг драйвера MongoDB для Java, а lift-mongo-record обеспечивает интеграцию для использования Record с Mongo.

Для начала убедитесь, что вы добавили зависимость в свой проект и вызвали update из оболочки SBT:

val mongo = "net.liftweb" %% "lift-mongodb-record" % liftVersion

По умолчанию Lift предполагает, что сервер MongoDB настроен на одной и той же машине (localhost), поэтому для разработки и тестирования, скорее всего, вам не понадобится конфигурация в вашем классе загрузки . Но если вам нужно указать, где находится ваша установка Mongo, просто добавьте следующие строки:

import net.liftweb.mongodb.{MongoDB, DefaultMongoIdentifier,

  MongoAddress, MongoHost}

 

MongoDB.defineDb(

  DefaultMongoIdentifier,

  MongoAddress(MongoHost("localhost", 27017), "your_db"))

Вызов MongoDB.defineDb, по сути, сообщает драйверу MongoDB, где найти сервер MongoDB. В следующих примерах предполагается, что установка MongoDB является локальной установкой по умолчанию.

Теперь, когда соединение готово, следующее — определить вашу запись Монго. Следующий листинг показывает самый простой пример.

Базовая реализация перечисления 2 Mongo Record

import net.liftweb.record.field._

import net.liftweb.mongodb.record.{MongoRecord,MongoMetaRecord,MongoId}

 

object Book extends Book with MongoMetaRecord[Book]      #1

 

class Book private () extends MongoRecord[Book]          #1

➥      with MongoId[Book]{                              #1

  def meta = Book

 

  object title extends StringField(this, "")

  object publishedInYear extends IntField(this, 1990)

}

# 1 Расширяет классы монго

Это почти идентично приведенному выше примеру CouchDB, единственное изменение — два супертипа, которые теперь называются MongoRecord и Mongo-MetaRecord (# 1) . MongoRecord поддерживает специализированные запросы для внутреннего хранилища, как это делает CouchRecord .

MongoDB занимается коллекциями . Эти коллекции можно рассматривать как похожие на таблицы, и каждая создаваемая вами запись MongoDB обычно представляет коллекцию. По умолчанию в коллекции будет использоваться множественное имя класса — в данном случае — Books . Каждый документ в коллекции будет представлен экземпляром сущности Book .

Предположим, вы хотите выполнить пару запросов:

import net.liftweb.json.JsonDSL._

 

Book.findAll("title" -> "Lift in Action")

Book.findAll("publishedInYear" -> ("$gte" -> 2005))

Book.findAll("$where" -> "function() {

  return this.publishedInYear == '2011'}")

Здесь есть три разных запроса, но первый должен быть достаточно понятным: Монго будет искать названия, соответствующие «Lift in Action». Вторая строка определяет запрос диапазона , который будет получать все документы , где publishedInYear является больше , чем 2005. И, наконец, последняя строка использует специальный MongoDB конструкта запроса $ , где и проходит функцию JavaScript , чтобы ограничить набор результатов. Mongo имеет целый набор этих специальных идентификаторов, которые описаны на http://www.mongodb.org / display / DOCS / Advanced + Queries, но, используя абстракцию Lift, вы можете использовать любые комбинации, которые вы предпочитаете.

Это основы использования NoSQL с Record. Независимо от этих двух разных магазинов, вы можете увидеть, как Record обеспечивает определенную степень единообразия, что позволяет плавно менять резервное хранилище позднее, а также взаимодействовать с другой инфраструктурой Lift.

Давайте возьмем информацию из этой части и реализуем пример книжного магазина с MongoDB .

Книжный магазин с MongoDB

Решения NoSQL имеют несколько иной способ обработки своих данных, и во многих отношениях это существенно меняет то, как мы, разработчики, должны моделировать наши объекты. В частности, с MongoDB более идиоматично хранить информацию, используя встроенные документы, которые отображаются в виде коллекций для данного объекта, если в большинстве случаев эти данные не меняются. На практике данные просто копируются в каждый документ. Иногда полезно иметь ссылку, но это зависит от вашего варианта использования.

С книгой , издателем , и Автором отношениями, Книга лицо будет действительно главное взаимодействие , потому что как только книга имеет издатель , это в значительной степени неизменные-то же самое верно Автор . Имея это в виду, нетрудно просто встроить соответствующие документы Publisher и Author, чтобы они отображались как свойства сущности Book .

СОВЕТ : При использовании Mongo общее практическое правило заключается в том, что вы встраиваете и копируете данные, когда это кажется разумным, и прибегаете к ссылкам на отдельные объекты, когда этого требует вариант использования. Вообще говоря, попытайтесь расположить ваши сущности Mongo так, чтобы наиболее часто доступный аспект был верхним уровнем, а другие аспекты были либо встроенными документами, либо, в меньшей степени, ссылочными сущностями. Классический сценарий — это одно сообщение в блоге, в котором много комментариев; комментарии добавляются непосредственно к документу почтовой сущности.

Давайте добавим эти два дополнительных поля для Publisher и Author в запись Book , как показано в следующем листинге.

Перечисление 3 полная сущность Book, использующая MongoRecord

import net.liftweb.record.field._

import net.liftweb.mongodb.{JsonObject,JsonObjectMeta}

import net.liftweb.mongodb.record.{MongoRecord,MongoMetaRecord,MongoId}

import net.liftweb.mongodb.record.field._

 

class Book private () extends MongoRecord[Book]

    with MongoId[Book]{

  def meta = Book

 

  object title extends StringField(this, "")

  object publishedInYear extends IntField(this, 1990)

 

  object publisher

  extends JsonObjectField[Book, Publisher]

      ➥(this, Publisher) {

    def defaultValue = Publisher("", "")             #1

  }

 

  object authors extends

    MongoJsonObjectListField[Book, Author](this, Author) #2

}

 

object Book extends Book with MongoMetaRecord[Book]

 

case class Publisher(name: String, description: String) #3

    extends JsonObject[Publisher] {                     #3

  def meta = Publisher                                  #3

}                                                       #3

 

object Publisher extends JsonObjectMeta[Publisher]

 

case class Author(firstName: String,                    #4

    lastName: String)                                   #4

    extends JsonObject[Author] {                        #4

  def meta = Author                                     #4

}                                                       #4

object Author extends JsonObjectMeta[Author]            #4

# 1 Встроенный издатель

# 2 Встроенный список авторов

# 3 Определение издателя

# 4 Определение автора

Здесь происходит значительное количество, помимо первоначальной реализации в листинге 2. Во-первых, обратите внимание на объект издателя в # 1. Этот внутренний объект расширяет Json-ObjectField , что означает, что он содержит вложенный документ Mongo. В этом конкретном случае поле сообщается, что оно должно ожидать тип издателя, определенный в # 3.

Само определение Publisher представляет собой простой класс case, который расширяет JsonObject и имеет сопутствующий объект с именем JsonObjectMeta .

То же самое верно для класса Author, определенного в # 4, но, поскольку одна книга может иметь несколько авторов, авторы свойства сущности расширяют MongoJson-ObjectListField (# 2). Как вы можете себе представить, он содержит список документов, в отличие от одного документа, требуемого издателем , поэтому на практике вы можете рассматривать это поле как простой список или массив документов.

Теперь, когда у вас есть MongoRecord for Book , вы можете поэкспериментировать с созданием и запросом экземпляров Book:

scala> import sample.model.mongo._

import sample.model.mongo._

 

scala>Book.createRecord

.title("sample")

.authors(Authors(List(Author("tim","perrett"))))

.publisher(Publisher("Manning","")).save

res2: sample.model.mongo.Book = class sample.model.mongo.Book={...}

 

scala>Book.findAll

res3: List[sample.model.mongo.Book] = List(...)

 

scala>Book.find("title" -> "Lift in Action")

res21: net.liftweb.common.Box[sample.model.mongo.Book] = ...

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

Going Rogue: типобезопасные монго запросы

Если вы хотите использовать Rogue, обязательно добавьте зависимость в определение вашего проекта и запустите обновление из оболочки SBT :

val rogue = "com.foursquare" %% "rogue" % "1.0.2"

Когда Rogue присутствует в вашем проекте, вы можете создавать запросы, просто добавив следующий оператор импорта:

import com.foursquare.rogue.Rogue._

Это позволяет вам взаимодействовать с Mongo, используя DSL:

Book where (_.publishedInYeargte 1990) fetch()

 

Book where (_.title eqs "Lift in Action") limit(1) fetch()

Это вершина айсберга, и абстракция может делать целый ряд других вещей, которые несколько выходят за рамки этого раздела. Если вы хотите узнать больше о Rogue, загляните в инженерный блог Foursquare и, в частности, в статью о Rogue и безопасности типов или в репозиторий Foursquare на github.com .

В этой статье вы увидели поддержку NoSQL, которую Lift предоставляет из коробки через абстракцию Record. NoSQL через Record может принимать различные формы, но в этой статье основное внимание было уделено CouchDB и MongoDB, где показано, как использовать эти захватывающие новые технологии и при этом иметь знакомую семантику Lift и интеграцию с инфраструктурой, такой как LiftScreen .

Резюме

Вы узнали о поддержке Lift хранилищ данных NoSQL, в частности CouchDB и MongoDB. Эти системы предлагают совсем другой тип операций хранения данных, чем традиционные СУБД, но фасад Record, с которым вы работаете в своем приложении Lift, практически не отличается. В случае CouchDB вы можете запускать довольно сложные функции MapReduce прямо из вашего кода Scala. При использовании реализации MongoDB вы можете использовать стандартный синтаксис запроса Mongo или выложить поверх абстракции Rogue Foursquare, чтобы взаимодействие с Mongo было безопасным и приятным.


Вот некоторые другие названия Мэннинга, которые могут вас заинтересовать:

 


Ниланджан Райчаудхури

 


Enterprise AOP с приложениями Spring

Рамнивас Ладдад

 


Дебашиш Гош