Упрощение доступа к DynamoDB в NodeJS с помощью ORM
В настоящее время мы используем aws-sdk для взаимодействия с DynamoDB.
Поскольку DynamoDB - это NoSQL-хранилище ключей-значений, при работе в команде возникает множество проблем. Существует большая вероятность ошибок в синтаксисе того, как мы взаимодействуем с базой данных.
Нам нужен последовательный способ выполнения задач. И сегодня мы посмотрим, как это сделать с помощью потрясающей библиотеки Dynamoose.
Проблема существующего подхода
Честно говоря, даже работая с dynamodb уже довольно долгое время, мне трудно разобраться в ней. Особенно это касается некоторых специальных свойств, которые мы должны использовать для обновления записи.
ExpressionAttributeNames
и ExpressionAttributeValues
сами по себе являются еще одной проблемой для понимания и работы с ними.
И знаете что? Они даже не безопасны для типов!
Давайте рассмотрим следующий пример. Этот код взят из реального проекта, в котором мы обновляем модель с именем member
. Детали не важны. Просто посмотрите на синтаксис!
const params = {
TableName: this.tableName,
Key: {
Customer: team.Customer,
Id: team.Id
},
UpdateExpression:
'set \
#n= :name,\
Description = :description,\
Active= :active,\
UpdateDate= :updateDate,\
UpdatedBy= :updatedBy',
ExpressionAttributeValues: {
':active': team.Active ?? false,
':name': team.Name,
':description': team.Description,
':updateDate': Date.now(),
':updatedBy': updatedBy
},
ExpressionAttributeNames: { '#i': 'Id', '#n': 'Name' },
ConditionExpression: 'attribute_exists(Customer) AND attribute_exists(#i)',
ReturnValues: 'ALL_NEW'
};
С этим кодом есть несколько проблем.
- Выражения
UpdateExpression
иConditionExpression
- это обычные строки. Поэтому, если мы пропустим хоть один символ, мы напишем неправильный запрос к БД, что станет кошмаром для поиска. - Кроме того, у нас нет никакой проверки (
validation
) моделей, которые мы вводим в базу данных. - Также у нас нет автозаполнения.
- А API сложен в понимании.
Каково же решение проблемы?
Существует множество библиотек для решения именно этой проблемы (потому что мы не первые, кто испытывает эту боль), и после некоторого копания я думаю, что лучшей библиотекой является dynamoose.
Почему Dynamoose - это отличный выбор?
- Поддержка Typescript
- Поддержка проверки
- Автоматическое преобразование объектов
- Очень интуитивно понятный API (использующий знакомые слова, такие как
get
,put
и т.д.)
Эта библиотека повторяет стиль API очень популярной библиотеки mongoose (для MongoDB). Возможно, вы уже догадались об этом по названию :)
Давайте перепишем наши запросы, чтобы узнать, как dynamose улучшает качество нашего кода.
Шаг 1: Установка зависимости
Давайте сначала установим зависимости!
npm i dynamoose
Шаг 2: Создание класса модели
Давайте создадим модель для наших данных, которые будут храниться в dynamoDB. Это необязательно. Но это приведет к возникновению ошибок в сценарии, которые уберегут вас от нежелательных опечаток.
import { Document } from 'dynamoose/dist/Document';
export class ExampleModel extends Document {
Id = '';
Module = '';
Description = '';
}
Довольно просто, правда?
Шаг 3: Создание схемы
Схема - это спецификация модели в базе данных.
Здесь мы можем указать различные свойства для отдельных полей. В дальнейшем они будут использоваться для проверки и гарантируют, что в базу данных не попадут "грязные" данные.
Вот некоторые из них:
required
→ является ли свойство обязательным или нет
default
→ значение базы данных по умолчанию для свойства
validate
→ валидация поля
getter
иsetter
→ способ, которым мы хотим получить обратно значение поля
timestamp
→ автоматическое значениеCreatedAt
иUpdatedAt
для модели БД (которое мы регулярно используем)
Таким образом, в нашем случае пример схемы будет выглядеть так:
import * as dynamoose from 'dynamoose';
export const ExampleSchema = new dynamoose.Schema(
{
Id: {
type: String,
hashKey: true,
required: true
},
Module: {
type: String,
rangeKey: true,
required: true
},
Description: {
type: String,
required: false,
default: ""
}
},
{
timestamps: {
createdAt: 'CreateDate',
updatedAt: 'UpdateDate'
}
}
);
Обратите внимание, что в нижней части схемы мы указали свойство timestamps
. Это позволит нам автоматически генерировать временные метки.
Шаг 4: Создание репозитория
Библиотека следует именованию MongoDB, поэтому она идентифицирует свое хранилище как модель. Мы будем использовать модель, но более разумным способом.
Мы создадим хранилище для разделения всей логики базы данных. Ниже приведен пример создания функций для операций GET
, CREATE
и UPDATE
.
import * as dynamoose from 'dynamoose';
import { ExampleModel } from './ExampleModel';
import { getTableName } from '@amagroup.io/amag-corelib';
import { Model } from 'dynamoose/dist/Model';
import { ExampleSchema } from './ExampleSchema';
import { CreateExampleRequest } from './create-survey/CreateSurveyRequest';
import { UpdateExampleRequest } from './update-survey/UpdateSurveyRequest';
export default class ExampleRepository {
private dbInstance: Model<ExampleModel>;
constructor(environment: string) {
const tableName = getTableName(environment, 'Example');
this.dbInstance = dynamoose.model<ExampleModel>(tableName, ExampleSchema);
}
createExample = async (request: CreateExampleRequest) => {
return await this.dbInstance.create({
Id: request.Id,
Module: request.Module
});
};
updateExample = async (request: UpdateExampleRequest) => {
return await this.dbInstance.update({
Id: request.Id,
Module: request.Module,
Description: request.Description
});
};
getExampleById = async (id: string, moduleName: string) => {
return await this.dbInstance.get({ Id: id, Module: moduleName });
};
}
Таким образом, вся логика базы данных теперь инкапсулирована! А также посмотрите на функцию updateExample, которая теперь похожа на любую другую функцию.
Вы просто указываете первичный ключ и ключ сортировки, а также поля, которые вы хотите обновить. И все готово!
А если вы попытаетесь передать какой-нибудь неизвестный ключ, который не определен в вашей модели, вы получите ошибку сценария на этапе компиляции!
Последний шаг: Используйте это в своём коде!
Теперь мы будем использовать этот репозиторий для взаимодействия с базой данных.
const repository = new ExampleRepository('dev');
const response = await repository.createExample(request);
Итак, теперь у нас есть CRUD-приложение, в котором мы можем воспользоваться преимуществами dynamoose, чтобы избавиться от мучений с запросами к базе данных.
На сегодня это всё. Хорошего дня! :D