Ноябрь 2015

Том 30, номер 12

Доступ к данным - Aurelia и DocumentDB

Джули Лерман | Ноябрь 2015

Исходный код можно скачать по ссылке

Julie LermanВ позапрошлом номере вы видели, как я исследовала неизведанные миры. В рубрике за сентябрь я углубилась в механизм связывания с данными в новомодной клиентской JavaScript-инфраструктуре Aurelia, взаимодействующей с серверной частью ASP.NET 5 Web API для получения и сохранения данных. Тот Web API на внутреннем уровне использовал SQL Server и Entity Framework. В рубрике за июнь я исследовала сервис базы данных DocumentDB на основе Microsoft Azure NoSQL. В этой рубрике я также создам ASP.NET MVC 5 Web API, но он будет использовать клиентскую .NET-библиотеку DocumentDB для взаимодействия с базой данных DocumentDB.

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

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

Для начала я поделюсь своим планом (который строился практически в вакууме из-за отсутствия у меня опыта работы с JavaScript и многими его API, средствами и шаблонами), а затем раскрою то, что на каких-то этапах казалось лучшими способами достичь моей цели. Если бы вместо этого я направила вас прямо по правильному пути, все шансы за то, что вы предугадали бы мой выбор и попытались бы найти способ получше, но в результате попали бы в те же тупики, в которых я уже побывала. Конечно, в итоге я расскажу о правильном пути, хотя на то, чтобы охватить всю историю, одной статьи мало.

Самые лучшие планы: «Насколько трудно это сделать?»1

Azure DocumentDB — это сервис, и прямое взаимодействие с ним осуществляется либо по синтаксису SQL, либо через HTTP-вызовы RESTful. В предыдущей статье вместо работы на столь низком уровне я воспользовалась преимуществами одного из многих API, созданных Microsoft для DocumentDB, — .NET API. Это позволило мне задействовать LINQ для выражения и выполнения запросов к своей базе данных, например:

return Client.CreateDocumentQuery(Collection.DocumentsLink)
             .Where(d => d.Id == id)
             .AsEnumerable()
             .FirstOrDefault();

Я написала Web API, который делал эту работу за меня.

В демонстрации для связывания с данными через Aurelia я могла легко выдавать HTTP-вызовы к другому Web API — тому, который использовал Entity Framework для обращений к базе данных SQL Server. Я могла бы просто заменить тот Web API на созданный мной для взаимодействия с DocumentDB. И в таком случае моя задача была бы решена, а статья в этой рубрике получилась бы рекордно короткой. Какая скукота.

Вместо этого я сочла, что будет куда интереснее организовать прямое REST-взаимодействие, поддерживаемое DocumentDB. Оно казалось достаточно простым. В документации на DocumentDB показано довольно много примеров HTTP-запросов, выдаваемых к DocumentDB, которые, как я думала, можно будет задействовать. Один из таких примеров приведен на рис. 1.

Рис. 1. Пример HTTP-запроса к DocumentDB

POST https://contosomarketing.documents.azure.com/dbs/
  XP0mAA==/colls/XP0mAJ3H-AA=/docs HTTP/1.1
x-ms-DocumentDB-isquery: True
x-ms-date: Mon, 18 Apr 2015 13:05:49 GMT
authorization: type%3dmaster%26ver%3d1.0%26sig
  [A HASH GOES HERE]
x-ms-version: 2015-04-08
Accept: application/json
Content-Type: application/query+json
Host: contosomarketing.documents.azure.com
Content-Length: 50
{
  query: "SELECT * FROM root WHERE (root.Author.id = 'Don')",
  parameters: []
}

1 Отсылка к популярному сериалу «How Hard Can It Be?» (2011 г.), который в России показывался под названием «Насколько трудно это сделать?». — Прим. ред.

Но все оказалось не так просто. Для начала основной ключ (master key), который вы видите в авторизации, не является тем, что вы хотели бы предоставлять внутри клиентского приложения. Более того, это на самом деле не тот основной ключ, который выдается на странице настроек DocumentDB портала Azure; это хеш ключа плюс дополнительная информация. В статье «Access Control on DocumentDB Resources» (bit.ly/1O9dBfP) описывается, как сконструировать эту строку, но даже это не позволило мне проверить API-вызовы в Fiddler.

Однако более серьезная проблема в том, что я все равно не смогла бы использовать это в клиентском приложении. Согласно «Securing Access to DocumentDB Data» (bit.ly/1N2ZiuF), в архитектуру рекомендуется ввести промежуточный уровень, который имеет защищенный доступ к вашему основному ключу и способен генерировать и возвращать ключи ресурсов для использования в клиентском приложении.

Поэтому я так и сделала. Я создала ASP.NET Web API, используя .NET-клиент для DocumentDB, чтобы возвращать по требованию корректно сформированный ключ ресурса. Это потребовало включения всего того кода, который был частью моей предыдущей статьи по DocumentDB, где я определила учетную запись Azure, клиент базы данных, базу данных внутри клиента и наборы внутри базы данных. Помимо этого, у меня был контроллер из 85 строк кода, который подключался к базе данных, проверял пользователя, потом проверял, очищал и заново создавал разрешения этого пользователя для операции, которую я собиралась выполнить, затем генерировал, хешировал и возвращал маркер ресурса запросившему клиенту. Это был весьма сложный в восприятии и сопровождении код.

После создания сервиса следующим шагом нужно было сделать так, чтобы мое приложение Aurelia вызывало этот сервис для получения маркера данной операции, а затем повторно использовало этот маркер в этой операции. В конечном итоге это не сильно отличается от операций защиты, используемых нами, например, для работы с сервисами Windows Communication Foundation (WCF). Но все это делалось для очень-очень простенького приложения. А в результате я по-прежнему не имела возможности сконструировать корректный запрос с маркерами ресурсов, генерируемыми на клиентской стороне (JavaScript), потому что DocumentDB требовал большего. DocumentDB отказывался авторизовать мои запросы на получение данных. И я решила, что прямая выдача собственных RESTful-вызовов из клиентского приложения через HTTP к моему сервису DocumentDB — это тупиковый путь. Но я предвидела, что наличие DocumentDB отразится на вариантах подключения, и поэтому планировала вернуться к этой идее в будущем.

Тем не менее, еще не все было потеряно. DocumentDB также имеет JavaScript SDK. Этот SDK должен знать, как конструировать RESTful-вызовы к DocumentDB в моих интересах, даже если я использовала его более высокоуровневые методы. Я поместила SDK в клиентское решение, понимая, что я могла бы позволить ему конструировать запрос для меня, используя маркеры ресурсов, запрошенные из моего ResourceTokenGenerator Web API. Мне показалось, что я наконец нащупала правильный путь, но в итоге опять уперлась в стену: не было никакого способа разрешить CORS (cross-origin resource sharing), а значит, вызовы из моего клиентского приложения в одном домене, адресованные сервису в другом домене, оказывались запрещены.

К этому моменту, исчерпав все силы и утратив последний интерес к выдаче RESTful-вызовов без оболочки, которая делала бы это за меня, и по-прежнему не желая просто переключиться на существующий Web API, чтобы заставить приложение Aurelia взаимодействовать с DocumentDB, я направилась по другому пути.

Успех: DocumentDB, Express, Aurelia и Node.js

Кроме SDK для .NET- и JavaScript-клиентов, DocumentDB также предоставляет Node.js SDK (bit.ly/1LifOa1). Это позволяет с помощью Node.js (JavaScript-реализации, работающей на серверной стороне во многом аналогично логике отделенного кода в ASP.NET) легко обращаться к DocumentDB. Все трудные части конфигурирования, аутентификации и формирования вызовов RESTful API обернуты в методы этого SDK. Я решила, что, следуя по этому пути, я смогу позволить моему приложению Aurelia взаимодействовать с DocumentDB. Это означало, что меня вновь ожидает забег с уймой препятствий. Я никогда не касалась Node.js и являюсь известной невеждой в JavaScript; более того, нужно было осваивать дополнительный API, Express, который обертывает большую часть базовой функциональности, облегчая использование Node.js. Но и это еще не все. При первом погружении в Aurelia мне пришлось привыкать работать в командной строке и использовать SublimeText — текстовый редактор, который гораздо практичнее для веб-разработки, чем Notepad. Поскольку большинство операций в том более раннем приложении выполнялось на клиентской стороне, я могла вести отладку прямо в браузере. Но теперь я отлаживала код Node.js, который находится на сервере. Как оказалось, отличным инструментом для этого является Visual Studio Code — новое пополнение в семействе Visual Studio.

К счастью, я смогла извлечь выгоду из двух ключевых примеров. На стороне DocumentDB есть полное руководство по созданию небольшого веб-приложения с применением Node.js и DocumentDB (bit.ly/1FljQs6). На стороне Aurelia имеется репозитарий в GitHub, где предлагается скелет приложения Aurelia с уже интегрированной серверной логикой Node.js (bit.ly/1XkMuEX).

Чтобы реализовать мое решение, крайне важно хорошо понимать нижележащую механику того примера, где используется Node.js SDK. Я потрачу оставшееся место в моей статье на детали, которые отсутствовали в высокоуровневом описании в руководстве. Это подготовит вас к пониманию того, как я подключила свой API в конечном решении.

Пошаговый пример с DocumentDB и Node.js предоставляет логику серверной части в Node.js, которая использует DocumentDB Node.js SDK для взаимодействия с DocumentDB. Первая часть этой логики — пара обобщенных вспомогательных классов, которые берут на себя создание экземпляра соединения с базой данных, создание при необходимости сначала база данных и создание экземпляра конкретного набора внутри базы данных, с которым вам нужно работать. Эти классы можно повторно использовать в других приложениях для взаимодействия с любой базой данных DocumentDB, так как DocumentDB позволяет передавать ей информацию об аутентификации и другие сведения, указывающие базу данных и наборы, с которыми вы работаете.

Этот вспомогательный класс начинается со ссылки на DocumentDB SDK для Node.js, уже установленный в решение:

var DocumentDBClient = require('DocumentDB').DocumentClient;

Затем он устанавливает методы, принимающие релевантную информацию о соединении как параметры. Ниже, например, приведено объявление класса с началом метода getOrCreateDatabase, который первым делом определяет запрос для получения базы данных от Azure, а затем использует метод queryDatabases из SDK-класса DocumentClient для выполнения этого запроса. Если результаты пусты, другой вызов (здесь не показан) создаст базу данных. Метод возвращает экземпляр базы данных. Полный листинг класса DocDBUtils см. в статье по ссылке bit.ly/1FljQs6:

var DocDBUtils = {
  getOrCreateDatabase: function (client, databaseId, callback)
  {
    var querySpec = {
      query: 'SELECT * FROM root r WHERE r.id=@id',
      parameters: [{
        name: '@id',
        value: databaseId
      }]
    };

    client.queryDatabases(querySpec).toArray(
      function (err, results) {
    // Дополнительная логика, указывающая обратные вызовы и др.

Вторая часть логики (находится в файле tasklist.js) похожа на контроллер. Она содержит методы для использования экземпляров базы данных и наборов, предоставляемых классом DocDBUtils, чтобы вы могли взаимодействовать с данными. Этот контроллер создан специально для примера, в котором хранятся и извлекаются элементы ToDo — объекты Task. Объект Task инкапсулирован в класс TaskDao, и в классе контроллера вы заметите ссылки на экземпляр TaskDao. У контроллера есть методы для получения, добавления, обновления и удаления объектов Task. Этот класс начинается со ссылок на DocumentDB SDK, а также на только что описанный вспомогательный класс:

var DocumentDBClient = require('DocumentDB').DocumentClient;
var docdbUtils = require('./docdbUtils');

Tasklist.js включает функции, такие как showTasks и addTask. Эти функции следуют соглашениям Node.js, принимая в качестве параметров объекты request и response, которые позволяют Node.js либо передавать запрос от браузера в другой процесс, либо встраивать что угодно в ответ, который возвращается браузеру. Функция showTasks показана на рис. 2.

Рис. 2. Команда showTasks в классе контроллера в tasklist.js

showTasks: function (request, response) {
  var self = this;

  var querySpec = {
    query: 'SELECT * FROM root r WHERE r.completed=@completed',
    parameters: [{
      name: '@completed',
      value: false
    }]
  };

  self.taskDao.find(querySpec, function (err, items) {
    if (err) {
      throw (err);
    }

    response.render('index', {
      title: 'My ToDo List ',
      tasks: items
    });
  });
},

Учтите, что в примере используется одна дополнительная библиотека — Express. Express обертывает функциональность Node.js в более высокоуровневые методы. Заметьте, что функция showTasks вызывает Express-метод render объекта response для рендеринга представления index (например, index.html), передавая элементы, извлеченные из DocumentDB, в свойство tasks, которое будет доступно для использования в файле Index.html.

Контроллер (класс TaskList) является первой точкой входа в серверную логику Node.js, так как он реагирует на перенаправление на веб-сайте. Логика в методах контроллера использует объект taskDAO, чтобы инициировать запросы и обновления, как показано вызовом self.taskDao.find в функции showTasks. Объект taskDAO имеет функцию init, которая подготавливает базу данных и наборы с помощью DocDbUtils. В совокупности все это позволяет напрямую работать с DocumentDB SDK, чтобы определять и выполнять запросы и обновления в его функциях find, getitem и updateItem (рис. 3).

Зависимости рабочего процесса в классах приложения-примера DocumentDb Node.js и SDK
Рис. 3. Зависимости рабочего процесса в классах приложения-примера DocumentDb Node.js и SDK

Сосредоточив серверную логику в Node.js, можно перейти к созданию клиентской части. В пошаговом руководстве на сайте DocumentDB применяется инфраструктура представлений-генерации (view-generation framework), называемая Jade. После подготовки HTML-файлов для представлений и перенаправления (routing) с помощью Jade API ваш UI сможет отвечать на запросы навигации пользователей, вызывая контроллер taskList на сервере, где можно безопасно хранить ключи DocumentDB для авторизации операций над данными.

Следующие шаги: подключение Aurelia к серверной части Node.js

Вспомните, однако, что моей целью было использование в качестве клиентской инфраструктуры Aurelia, а не Jade. Так что следующее, чем мне пришлось заняться, — брать уроки по использованию DocumentDB Node.js SDK и применять их к скелету приложения с поддержкой Node.js, предлагаемому в примере на узле Aurelia в GitHub. Но перенаправление в Aurelia работает несколько иначе, чем в Jade, и справиться с этим оказалось не так-то просто. Моя неопытность в Node.js и Express в сочетании с познаниями JavaScript на уровне «знает достаточно, чтобы натворить дел» сильно усложнила мою задачу. Но в итоге я все же добилась своего благодаря помощи нескольких членов группы разработчиков Aurelia.

В следующей статье я пошагово разберу вопросы, касающиеся крайне важных соединительных частей между контроллером и механизмом перенаправления Aurelia, и покажу, как использовать серверное решение Node.js для взаимодействия с моей DocumentDB, попутно сравнив простоту прямых HTTP-вызовов из Aurelia с Web API.


Джули Лерман (Julie Lerman) — Microsoft MVP, преподаватель и консультант по .NET, живет в Вермонте. Часто выступает на конференциях по всему миру и в группах пользователей по тематике, связанной с доступом к данным и другими технологиями Microsoft .NET. Ведет блог thedatafarm.com/blog и является автором серии книг «Programming Entity Framework» (O’Reilly Media, 2010), в том числе «Code First Edition» (2011) и «DbContext Edition» (2012), также выпущенных издательством O’Reilly Media. Вы можете читать ее заметки в twitter.com/julielerman и смотреть ее видеокурсы для Pluralsight на juliel.me/PS-Videos.

Выражаю благодарность за рецензирование статьи экспертам Райену Крокуру (Ryan CrawCour) и Патрику Уолтерсу (Patrick Walters).