Статьи

Создание REST API с помощью AWS SimpleDB и Node.js

SimpleDB — это удаленная база данных, предлагаемая Amazon Web Services (AWS). Мир хранилищ данных обычно делится на SQL и NoSQL, основываясь на использовании (или неиспользовании) языка SQL. Хранилища данных NoSQL обычно основаны на более простой настройке ключ / значение. SimpleDB охватывает эту строку — это хранилище ключей / значений, и он также может использовать вариант SQL для извлечения. Большинство языков SQL основаны на схеме, которая размещает строки и столбцы данных, но SimpleDB — это база данных без схемы, что делает ее очень гибким хранилищем данных.

В модели базы данных SimpleDB у вас есть элементы, атрибуты и значения. Каждая строка в базе данных является элементом и может быть идентифицирована уникальным и назначаемым именем элемента. Каждый элемент может иметь до 256 пар атрибутов и значений . Неожиданным аспектом SimpleDB является то, что атрибут может иметь более одной пары на элемент. Я думаю, что лучший способ думать о SimpleDB — думать о электронной таблице, но вместо каждого пересечения столбца / строки, представляющего одно значение, он представляет массив значений.

Сетка, иллюстрирующая отношение атрибута имени элемента

Эта диаграмма представляет два элемента, хранящихся в домене SimpleDB . Термин « домен» аналогичен «таблице» в других базах данных.

Первый столбец — это имя элемента — это единственный столбец, в котором у вас может быть только одно значение, и вы можете рассматривать его как уникальный индексный столбец.

Другие четыре столбца ( домашние животные , автомобили , мебель и телефоны ) представляют атрибуты, которые в данный момент находятся в этом домене — вы не ограничены этим, поэтому каждый элемент может иметь совершенно уникальный набор атрибутов. В этих данных атрибут домашних животных в элементе personInventory1 имеет три пары; выраженный в JSON, это будет выглядеть примерно так:

1
2
3
{ «Name» : «pets», «Value» : «dog» },
{ «Name» : «pets», «Value» : «cat» },
{ «Name» : «pets», «Value» : «fish» }

С другой стороны, элемент personInventory2 имеет только одну пару:

1
{ «Name» : «pets», «Value» : «cat» }

Хотя вам не нужно указывать одни и те же атрибуты для каждого элемента, вам необходимо указать хотя бы одну пару. Это означает, что у вас не может быть «пустого» предмета. Каждый атрибут может иметь значение до 1 КБ, поэтому это означает, что каждый элемент функционально ограничен 256 КБ из-за ограничения в 1 КБ и ограничения в 256 пар.

Распространяется SimpleDB, у которого есть некоторые отличительные черты, которые вы должны понимать и учитывать при разработке своего приложения. Распределенная база данных означает, что целая группа компьютеров будет отвечать на ваши запросы, и ваши данные будут реплицироваться на этих серверах. Этот дистрибутив будет полностью прозрачен для вашей программы, но он создает возможность проблем согласованности — ваши данные изначально не могут быть гарантированно представлены на всех серверах.

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

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

В отличие от многих сервисов AWS, в Amazon отсутствует поставляемая консоль для управления SimpleDB. К счастью, в браузере есть хорошая консоль управления в виде плагина Google Chrome, SdbNavigator . В SdbNavigator вы можете добавлять или удалять домены, вставлять, обновлять и удалять элементы, изменять атрибуты и выполнять запросы.

Теперь, когда мы познакомились с сервисом SimpleDB, давайте начнем писать наш REST-сервер. Для начала нам нужно установить AWS SDK. Этот SDK обрабатывает не только SimpleDB, но и все сервисы AWS, поэтому вы, возможно, уже включили его в свой файл package.json. Чтобы установить SDK, выполните в командной строке следующее:

1
npm install aws-sdk —-save

Чтобы использовать SimpleDB, вам также необходимо получить учетные данные AWS , которые включают в себя ключ доступа и секретный ключ. SimpleDB — это услуга с оплатой по факту, но в настоящее время AWS предоставляет щедрое бесплатное пособие для SimpleDB.

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

После установки AWS SDK и получения учетных данных вам необходимо настроить SimpleDB в своем коде. В этом примере мы будем использовать учетные данные AWS, хранящиеся в файле JSON в вашем домашнем каталоге. Во-первых, вам нужно включить модуль SDK, создать объект AWS и, наконец, настроить интерфейс SimpleDB.

01
02
03
04
05
06
07
08
09
10
11
var
  aws = require(‘aws-sdk’),
  simpledb;
 
aws.config.loadFromPath(process.env[‘HOME’] + ‘/aws.credentials.json’);
 
//We’ll use the Northern Virginia datacenter, change the region / endpoint for other datacenters http://docs.aws.amazon.com/general/latest/gr/rande.html#sdb_region
simpledb = new aws.SimpleDB({
  region : ‘US-East’,
  endpoint : ‘https://sdb.amazonaws.com’
});

Обратите внимание, что мы используем конкретную конечную точку и регион. Каждый центр обработки данных полностью независим, поэтому, если вы создадите домен с именем « mysuperawesomedata » в Северной Вирджинии, он не будет реплицироваться или присутствовать, например, в центре обработки данных Сан-Паулу.

Объект SimpleDB, который вы создали с new aws.SimpleDB — это то место, где будут основаны все ваши методы взаимодействия с SimpleDB. В AWS SDK для SimpleDB есть только несколько методов :

Пакетные операции

  • batchDeleteAttributes
  • batchPutAttributes

Управление доменом и информация

  • CreateDomain
  • deleteDomain
  • domainMetadata
  • listDomains

Манипуляция предметом / атрибутом

  • deleteAttributes
  • GetAttributes
  • putAttributes

Запросы

  • Выбрать

В этом уроке мы будем иметь дело только с манипуляцией и запросами предметов / атрибутов; в то время как другие категории полезны, многие приложения не будут их использовать.

Используя SdbNavigator, введите ключи доступа и безопасности в инструмент, выберите «US-East» и нажмите «Подключиться».

Интерфейс подключения к SdbNavigator

После успешного подключения давайте создадим домен для тестирования. Нажмите Добавить домен .

Добавление домена через SdbNavigator

Затем введите доменное имя ‘sdb-rest-tut’ и нажмите OK .

Ввод имени нового домена

Теперь, когда вы создали домен, давайте введем некоторые тестовые данные. Нажмите Добавить свойство и добавьте свойство с именем «colors». Как правило, я обычно называю свойства во множественном числе, чтобы отразить многозначную природу SimpleDB.

Добавление имени свойства в запись

Наконец, мы нажмем « Добавить запись», чтобы создать наш первый элемент SimpleDB. В столбце ItemName () введите свое уникальное имя элемента. Причуда SdbNavigator заключается в том, что по умолчанию он принимает только одно значение для каждого элемента, но это скрывает тот факт, что свойство может содержать несколько значений. Чтобы ввести несколько значений, нажмите S вдоль правого края столбца свойства.

Ввод уникального имени предмета

В новом поле выберите Array, чтобы ввести несколько значений. В столбце « Значение» введите «красный», затем нажмите « Добавить значение» и введите «синий».

Ввод типа данных элемента

Наконец, нажмите Обновить, чтобы сохранить изменения в этой строке.

Обновление типа данных элемента

Теперь, когда мы ввели некоторые тестовые данные, давайте сделаем наш первый запрос SimpleDB от Node. Мы просто получим все в Домене, который на данный момент будет всего лишь одной строкой.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
var
  aws = require(‘aws-sdk’),
  simpledb;
 
aws.config.loadFromPath(process.env[‘HOME’] + ‘/aws.credentials.json’);
 
simpledb = new aws.SimpleDB({
  region : ‘US-East’,
  endpoint : ‘https://sdb.amazonaws.com’
});
 
simpledb.select({
  SelectExpression : ‘select * from `sdb-rest-tut` limit 100’
}, function(err,resp) {
  if (err) {
    console.error(err);
  } else {
    console.log(JSON.stringify(resp,null,’ ‘));
  }
});

Ответ будет зарегистрирован в консоли. Вот ответ, аннотированный для объяснения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
{
 «ResponseMetadata»: {
  «RequestId»: «…», //Every request made to SimpleDB has a request ID
  «BoxUsage»: «0.0000228616» //This is how your account is charged, as of time of writing US-East region is 14 US cents per hour, so this request costs 0.00032 cents + the transfer cost (if you are currently outside of your free tier)
 },
 «Items»: [ //For a Select, your response will be in the «Items» object property
  {
   «Name»: «myfirstitem», //this is the itemName()
   «Attributes»: [ //these are the attribute pairs
    {
     «Name»: «colors», //attribute name
     «Value»: «red» //value — note that every Value is a string, regardless of the contents
    },
    {
     «Name»: «colors», //Since the attribute name is repeated, we can see that `colors` has more than one value
     «Value»: «blue»
    }
   ]
  }
 ]
}

Поскольку мы будем создавать REST-сервер, который хранит данные в SimpleDB, важно понимать, что делает REST-сервер. REST означает RE презентационный Государственный перевод. Сервер REST — это просто сервер, который использует стандартные механизмы HTTP в качестве интерфейса для ваших данных. Часто REST используется для обмена данными между серверами, но вы можете использовать REST-серверы с клиентом через библиотеки JavaScript, такие как jQuery или Angular. Как правило, однако, конечный пользователь не будет взаимодействовать напрямую с сервером REST.

Интересно, что AWS SDK фактически использует протокол REST для взаимодействия с SimpleDB, поэтому может показаться странным создание сервера REST для другого сервера REST. Вы не захотите использовать API SimpleDB REST напрямую, потому что вам нужно аутентифицировать ваши запросы, что может поставить под угрозу безопасность вашей учетной записи AWS. Кроме того, написав сервер, вы сможете добавить слой абстракции и проверки в ваше хранилище данных, что значительно облегчит работу с остальным приложением.

В этом уроке мы будем строить основные функции CRUD + L, а именно: C reate, R ead, U pdate, D elete и L ist. Если вы думаете об этом, вы можете разбить большинство приложений на CRUD + L. С REST вы будете использовать ограниченное количество путей и несколько HTTP-методов или глаголов для создания интуитивно понятного API. Большинство разработчиков знакомы с некоторыми из HTTP-глаголов, а именно GET и POST, так как они чаще всего используются в веб-приложениях, но есть и несколько других .

операция HTTP-глагол
Создайте ПОЧТА
Читать ПОЛУЧИТЬ
Обновить ПОЛОЖИЛ
удалять УДАЛЯТЬ
Список ПОЛУЧИТЬ

Обратите внимание, что Read и List используют один и тот же глагол; мы будем использовать немного разные пути, чтобы провести различие между ними. Мы используем POST для представления Create как создание, не считается идемпотентным. Идемпотентность означает, что несколько идентичных вызовов будут иметь один и тот же результат для пользователя и ваших данных, поэтому обновление (или PUT) будет считаться идемпотентом.

В качестве нашего примера мы создадим персональный сервер инвентаризации — базу данных для сохранения того, что у вас есть. Вот как будут выглядеть пути:

операция HTTP-глагол Путь
Создайте ПОЧТА / инвентарь
Читать ПОЛУЧИТЬ / Инвентарь / 1234
Обновить ПОЛОЖИЛ / Инвентарь / 1234
удалять УДАЛЯТЬ / Инвентарь / 1234
Список ПОЛУЧИТЬ / инвентарь

1234 — это заполнитель для идентификатора человека (ID) — отметим, что «create» и «list» не имеют идентификатора. В случае создания будет сгенерирован идентификатор, и со списком мы получим все имена, поэтому нам не нужен конкретный идентификатор.

Для начала давайте установим Express , инфраструктуру HTTP-сервера Node.js:

1
npm install express —-save

Express управляет большинством мелочей при настройке сервера, но он не включает никаких средств для обработки тела HTTP-запроса, поэтому нам нужно установить другой модуль, body-parser , чтобы мы могли прочитать тело запроса.

1
npm install body-parser —save

Body-parser имеет несколько различных вариантов разбора тела HTTP-запроса. Мы будем использовать метод json() для удобства чтения, но переключение на другой метод просто bodyParser метод на объекте bodyParser . Нам нужен bodyParser метод bodyParser методах create и update, поэтому мы можем просто включить его в эти конкретные маршруты.

Поскольку каждое имя itemName SimpleDB должно быть уникальным, мы можем автоматически создать новое имя itemName для каждого вновь создаваемого элемента. Мы собираемся использовать модуль cuid , который является упрощенным способом генерации уникальных идентификаторов.

1
npm install cuid —save

SimpleDB ожидает, что атрибуты будут в формате пары имя / значение атрибута:

1
2
3
4
5
6
[
    { «Name» : «attribute1», «Value» : «value1» },
    { «Name» : «attribute1», «Value» : «value2» },
    { «Name» : «attribute2», «Value» : «value3» },
    { «Name» : «attribute3», «Value» : «value4» }
]

Ваш сервер, безусловно, может просто принимать и передавать значения в этом формате непосредственно в SimpleDB, но это противоречит тому, как часто структурируются данные, и это сложная концепция для работы. Мы будем использовать более интуитивную структуру данных, массив объектов / значений:

1
2
3
4
{
    «attribute1» : [«value1″,»value2»],
    «attribute2» : [«value3″,»value4»]
}

Вот базовый сервер на основе Express с операцией создания:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
var
  aws = require(‘aws-sdk’),
  bodyParser = require(‘body-parser’),
  cuid = require(‘cuid’),
  express = require(‘express’),
   
  sdbDomain = ‘sdb-rest-tut’,
   
  app = express(),
  simpledb;
 
aws.config.loadFromPath(process.env[‘HOME’] + ‘/aws.credentials.json’);
simpledb = new aws.SimpleDB({
  region : ‘US-East’,
  endpoint : ‘https://sdb.amazonaws.com’
});
 
//create
app.post(
  ‘/inventory’,
  bodyParser.json(),
  function(req,res,next) {
    var
      sdbAttributes = [],
      newItemName = cuid();
     
    //start with:
    /*
      { attributeN : [‘value1′,’value2′,..’valueN’] }
    */
    Object.keys(req.body).forEach(function(anAttributeName) {
      req.body[anAttributeName].forEach(function(aValue) {
        sdbAttributes.push({
          Name : anAttributeName,
          Value : aValue
        });
      });
    });
    //end up with:
    /*
      [
        { Name : ‘attributeN’, Value : ‘value1’ },
        { Name : ‘attributeN’, Value : ‘value2’ },
        …
        { Name : ‘attributeN’, Value : ‘valueN’ },
      ]
    */
 
    simpledb.putAttributes({
      DomainName : sdbDomain,
      ItemName : newItemName,
      Attributes : sdbAttributes
    }, function(err,awsResp) {
      if (err) {
        next(err);
      } else {
        res.send({
          itemName : newItemName
        });
      }
    });
  }
);
 
 
app.listen(3000, function () {
  console.log(‘SimpleDB-powered REST server started.’);
});

Давайте запустим ваш сервер и попробуем. Отличным способом взаимодействия с сервером REST является использование инструмента cURL . Этот инструмент позволяет вам сделать HTTP-запрос с любым глаголом прямо из командной строки. Чтобы попробовать создать элемент на нашем REST-сервере, нам нужно активировать несколько дополнительных параметров:

1
curl -H «Content-Type: application/json» -X POST -d ‘{«pets» : [«dog»,»cat»], «cars» : [«saab»]}’ http://localhost:3000/inventory
вариант Цель
-ЧАС Добавьте строку в заголовок HTTP
-ИКС Определите, какой глагол будет использоваться
-d Данные для отправки в теле HTTP-запроса

После выполнения команды вы увидите ответ JSON с вашим вновь созданным itemName или ID. Если вы переключитесь на SdbNavigator, вы должны увидеть новые данные при запросе всех элементов.

Теперь давайте создадим базовую функцию для чтения элемента из SimpleDB. Для этого нам не нужно выполнять запрос, поскольку мы будем получать itemName или ID из пути запроса. Мы можем выполнить запрос getAttributes с этим itemName или ID.

Если бы мы остановились здесь, у нас была бы функциональная, но не очень дружелюбная форма наших данных. Давайте преобразуем массив Имя / Значение в ту же форму, которую мы используем для приема данных (атрибут: массив значений). Для этого нам нужно будет пройти каждую пару имя / значение и добавить ее в новый массив для каждого уникального имени.

Наконец, давайте добавим itemName и вернем результаты.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//Read
app.get(‘/inventory/:itemID’, function(req,res,next) {
  simpledb.getAttributes({
    DomainName : sdbDomain,
    ItemName : req.params.itemID //this gets the value from :itemID in the path
  }, function(err,awsResp) {
    var
      attributes = {};
       
    if (err) {
      next(err);
    } else {
      awsResp.Attributes.forEach(function(aPair) {
        // if this is the first time we are seeing the aPair.Name, let’s add it to the response object, attributes as an array
        if (!attributes[aPair.Name]) {
          attributes[aPair.Name] = [];
        }
        //push the value into the correct array
        attributes[aPair.Name].push(aPair.Value);
      });
      res.send({
        itemName : req.params.itemID,
        inventory : attributes
      });
    }
  });
});

Чтобы проверить это, нам нужно снова использовать curl. Попробуйте заменить [cuid] на itemName или ID, возвращенные из нашего примера создания элемента ранее в этом руководстве.

1
curl -D- http://localhost:3000/inventory/[cuid]

Обратите внимание, что мы используем   -D- вариант. Это сбросит заголовок HTTP, чтобы мы могли видеть код ответа.

Другим аспектом REST является осмысленное использование ваших кодов ответов . В текущем примере, если вы укажете несуществующий идентификатор для curl, вышеприведенный сервер потерпит крах, потому что вы пытаетесь выполнить forEach несуществующий массив. Мы должны учитывать это и возвращать значимый код ответа HTTP, указывающий, что элемент не был найден.

Чтобы предотвратить ошибку, мы должны проверить наличие переменной awsResp.Attributes . Если он не существует, давайте установим код состояния на 404 и завершим http-запрос. Если он существует, то мы можем предоставить ответ с атрибутами.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
app.get(‘/inventory/:itemID’, function(req,res,next) {
  simpledb.getAttributes({
    DomainName : sdbDomain,
    ItemName : req.params.itemID
  }, function(err,awsResp) {
    var
      attributes = {};
       
    if (err) {
      next(err);
    } else {
      if (!awsResp.Attributes) {
        //set the status response to 404 because we didn’t find any attributes then end it
        res.status(404).end();
      } else {
        awsResp.Attributes.forEach(function(aPair) {
          if (!attributes[aPair.Name]) {
            attributes[aPair.Name] = [];
          }
           
          attributes[aPair.Name].push(aPair.Value);
        });
        res.send({
          itemName : req.params.itemID,
          inventory : attributes
        });
      }
    }
  });
});

Попробуйте его с новым кодом и несуществующим идентификатором, и вы увидите, что сервер возвращает 404.

Теперь, когда мы знаем, как использовать status чтобы изменить значение, мы также должны обновить то, как мы реагируем на POST / create. Хотя ответ 200 технически корректен, так как означает «ОК», более проницательный код ответа будет 201, что означает «создано». Чтобы внести это изменение, мы добавим его в метод статуса перед отправкой.

1
2
3
4
5
res
 .status(201)
 .send({
   itemName : newItemName
 });

Обновление обычно является наиболее сложной операцией для любой системы, и этот REST-сервер не является исключением.

Природа SimpleDB также делает эту операцию более сложной. В случае сервера REST, обновление — это то, где вы заменяете всю часть хранимых данных; SimpleDB, с другой стороны, представляет отдельные пары атрибут / значение под itemName.

Чтобы позволить обновлению представлять отдельный фрагмент данных, а не набор пар имя / значение, нам нужно определить схему для целей нашего кода (даже если SimpleDB не нуждается в ней). Не волнуйтесь, если сейчас это неясно — продолжайте читать, и я проиллюстрирую это требование.

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

1
schema = [‘pets’,’cars’,’furniture’,’phones’],

С SimpleDB вы не можете хранить пустую пару атрибут / значение, и SimpleDB не имеет никакой концепции отдельных элементов, поэтому мы будем предполагать, что если SimpleDB не возвращает значение, оно не существует. Аналогично, если мы попытаемся обновить элемент SimpleDB пустой парой атрибут / значение, он будет игнорировать эти данные. Взять, к примеру, эти данные:

01
02
03
04
05
06
07
08
09
10
{
  «itemName»: «cil89uvnm00011ma2fykmy79c»,
  «inventory»: {
    «cars»: [],
    «pets»: [
      «cat»,
      «dog»
    ]
  }
}

Логически мы знаем, что cars , будучи пустым массивом, не должны иметь значений, а pets должны иметь два значения, но как насчет phones и furniture ? Что вы делаете с этими? Вот как мы переводим этот запрос на обновление для работы с SimpleDB:

  • Положите атрибут pet со значением для cat .
  • Положите атрибут pet со значением для dog .
  • Удалить атрибуты для cars .
  • Удалить атрибуты для phones .
  • Удалить атрибуты для furniture .

Без какой-либо формы схемы, которая, по крайней мере, определяет атрибуты, мы бы не знали, что phones и furniture нужно было удалить. К счастью, мы можем объединить эту операцию обновления в два запроса SimpleDB вместо пяти: один для размещения атрибутов и один для удаления атрибутов. Самое время извлечь код из функции post / create, которая преобразует атрибут / массив значений. Объект в массив атрибут / значение пары.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
function attributeObjectToAttributeValuePairs(attrObj, replace) {
   var
     sdbAttributes = [];
 
    Object.keys(attrObj).forEach(function(anAttributeName) {
      attrObj[anAttributeName].forEach(function(aValue) {
        sdbAttributes.push({
          Name : anAttributeName,
          Value : aValue,
          Replace : replace //if true, then SimpleDB will overwrite rather than append more values to an attribute
        });
      });
    });
 
   return sdbAttributes;
}

Мы также собираемся внести важные изменения в функцию создания. Мы будем добавлять новый атрибут / значение для всех элементов. Этот атрибут не будет добавлен в схему и фактически доступен только для чтения.

Мы добавим атрибут с именем created и установим значение в 1 . С SimpleDB ограничена возможность проверки наличия элемента перед добавлением атрибутов и значений. При каждом запросе putAttributes вы можете проверять значение и существование одного атрибута — в нашем случае мы будем использовать created и проверять значение 1. Хотя это может показаться странным обходным решением, это обеспечивает очень важную безопасность для запретить операции обновления создавать новые элементы с произвольным идентификатором.

1
2
3
4
newAttributes.push({
  Name : ‘created’,
  Value : ‘1’
});

Поскольку мы будем выполнять пару асинхронных HTTP-запросов, давайте установим модуль async, чтобы упростить обработку этих обратных вызовов.

1
npm install async —-save

Помните, что поскольку SimpleDB распространяется, нет никаких оснований последовательно помещать наши атрибуты, а затем удалять. Мы будем использовать функцию async.parallel чтобы выполнить эти две операции и получить обратный вызов, когда обе будут завершены. Ответы из формы AWS putAttributes и deleteAttributes не предоставляют важной информации, поэтому мы просто отправим пустой ответ с кодом состояния 200, если ошибок нет.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
app.put(
  ‘/inventory/:itemID’,
  bodyParser.json(),
  function(req,res,next) {
    var
      updateValues = {},
      deleteValues = [];
     
    schema.forEach(function(anAttribute) {
      if ((!req.body[anAttribute]) || (req.body[anAttribute].length === 0)) {
        deleteValues.push({ Name : anAttribute});
      } else {
        updateValues[anAttribute] = req.body[anAttribute];
      }
    });
     
    async.parallel([
        function(cb) {
          //update anything that is present
          simpledb.putAttributes({
              DomainName : sdbDomain,
              ItemName : req.params.itemID,
              Attributes : attributeObjectToAttributeValuePairs(updateValues,true),
              Expected : {
                Name : ‘created’,
                Value : ‘1’,
                Exists : true
              }
            },
            cb
          );
        },
        function(cb) {
          //delete any attributes that not present
          simpledb.deleteAttributes({
              DomainName : sdbDomain,
              ItemName : req.params.itemID,
              Attributes : deleteValues
            },
            cb
          );
        }
      ],
      function(err) {
        if (err) {
          next(err);
        } else {
          res.status(200).end();
        }
      }
    );
  }
);

Чтобы принять это за спин, давайте обновим ранее созданную запись. На этот раз мы сделаем из инвентаря только «собаку», удалив все остальные предметы. Опять же, с помощью cURL, запустите команду, заменив [cuid] одним из ваших идентификаторов элементов.

1
curl -H «Content-Type: application/json» -X PUT -d ‘{«pets» : [«dog»] }’ http://localhost:3000/inventory/[cuid]

SimpleDB не имеет понятия удаления элемента, но может удалять атрибуты, как упомянуто выше. Чтобы удалить элемент, нам нужно удалить все атрибуты, и «элемент» перестанет быть.

Поскольку мы определили список атрибутов в нашей схеме, мы будем использовать вызов deleteAttributes чтобы удалить все эти атрибуты, а также created атрибут. Согласно нашему плану, эта операция будет проходить по тому же пути, что и Update, но с использованием глагола delete.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
app.delete(
  ‘/inventory/:itemID’,
  function(req,res,next) {
    var
      attributesToDelete;
     
    attributesToDelete = schema.map(function(anAttribute){
      return { Name : anAttribute };
    });
     
    attributesToDelete.push({ Name : ‘created’ });
     
    simpledb.deleteAttributes({
        DomainName : sdbDomain,
        ItemName : req.params.itemID,
        Attributes : attributesToDelete
      },
      function(err) {
        if (err) {
          next(err);
        } else {
          res.status(200).end();
        }
      }
    );
  }
);

Завершаем наши REST глаголы это список. Для выполнения операции со списком мы будем использовать команду select и язык запросов, подобный SQL. Наша функция списка будет простой, но она послужит хорошей основой для более сложного поиска в дальнейшем. Мы собираемся сделать очень простой запрос:

1
select * from `sdb-rest-tut` limit 100

Когда мы столкнулись с операцией get / read, ответ от SimpleDB не очень полезен, поскольку он сфокусирован на парах атрибут / значение. Чтобы не повторяться, мы реорганизуем часть операции get / read в отдельную функцию и будем использовать ее здесь. Пока мы это делаем, мы также отфильтруем created атрибут (как он будет показан в операции get).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
function attributeValuePairsToAttributeObject(pairs) {
  var
    attributes = {};
   
   
  pairs
    .filter(function(aPair) {
      return aPair.Name !== ‘created’;
    })
    .forEach(function(aPair) {
    if (!attributes[aPair.Name]) {
      attributes[aPair.Name] = [];
    }
     
    attributes[aPair.Name].push(aPair.Value);
  });
   
  return attributes;
}

С помощью операции выбора SimpleDB возвращает значения в массиве Items . Каждый элемент представлен объектом, который содержит itemName (просто Name ) и пары атрибут / значение.

Чтобы упростить этот ответ, давайте вернем все в одном объекте. Сначала мы преобразуем пары атрибут / значение в массив атрибут / значение, как мы это делали в операции чтения / получения, а затем мы можем добавить itemName в качестве идентификатора свойства.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
app.get(
  ‘/inventory’,
  function(req,res,next) {
    simpledb.select({
      SelectExpression : ‘select * from `sdb-rest-tut` limit 100’
    },
    function(err,awsResp) {
      var
        items = [];
      if (err) {
        next(err);
      } else {
        items = awsResp.Items.map(function(anAwsItem) {
          var
            anItem;
           
          anItem = attributeValuePairsToAttributeObject(anAwsItem.Attributes);
           
          anItem.id = anAwsItem.Name;
           
          return anItem;
        });
        res.send(items);
      }
    });
  }
);

Чтобы увидеть наши результаты, мы можем использовать curl:

1
curl -D- -X GET http://localhost:3000/inventory

Валидация сама по себе является предметом, но с кодом, который мы уже написали, у нас есть начало для простой системы валидации.

Пока все, что мы хотим убедиться, это то, что пользователь не может отправлять ничего, кроме того, что находится в схеме. Оглядываясь назад на код, который был написан для update / put, forEach по всей схеме предотвратит добавление любых неавторизованных атрибутов, поэтому нам просто нужно применить нечто похожее к нашей операции create / post. В этом случае мы отфильтруем пары атрибут / значение, исключив любые атрибуты, не относящиеся к схеме.

1
2
3
newAttributes = newAttributes.filter(function(anAttribute) {
  return schema.indexOf(anAttribute.Name) !== -1;
});

В вашем рабочем коде вы, вероятно, захотите более надежную систему проверки. Я бы предложил интегрировать валидатор JSON-схемы, такой как ajv, и создать промежуточное ПО, которое находится между bodyParser и вашей функцией маршрута при операциях создания и обновления.

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

  • Аутентификация
  • пагинация
  • Сложные операции со списком / запросом
  • Дополнительные выходные форматы (xml, csv и т. Д.)

Эта основа для REST-сервера на основе SimpleDB позволяет добавлять промежуточное программное обеспечение и дополнительную логику для создания основы для вашего приложения.

Окончательный код сервера доступен по адресу simpledb-rest-api на GitHub.