Реализация репозитория для работы с базами данных
|
|
2 years ago | |
|---|---|---|
| .husky | 2 years ago | |
| src | 2 years ago | |
| .c8rc | 2 years ago | |
| .commitlintrc | 2 years ago | |
| .editorconfig | 2 years ago | |
| .eslintignore | 2 years ago | |
| .eslintrc.cjs | 2 years ago | |
| .gitignore | 2 years ago | |
| .mocharc.cjs | 2 years ago | |
| .prettierrc | 2 years ago | |
| LICENSE | 2 years ago | |
| README.md | 2 years ago | |
| mocha.setup.js | 2 years ago | |
| package.json | 2 years ago | |
| tsconfig.json | 2 years ago |
Абстракция для работы с базами данных для Node.js
npm install @e22m4u/js-repository
Опционально устанавливаем адаптер. Например, если используется MongoDB, то для подключения потребуется установить адаптер mongodb как отдельную зависимость.
npm install @e22m4u/js-repository-mongodb-adapter
Список доступных адаптеров:
| адаптер | описание |
|---|---|
memory |
виртуальная база в памяти процесса (для разработки и тестирования) |
mongodb |
MongoDB - система управления NoSQL базами (требует установки) |
Модуль позволяет спроектировать систему связанных данных, доступ к которым осуществляется посредством репозиториев. Каждый репозиторий имеет собственную модель данных, которая описывает структуру определенной коллекции в базе, а так же определяет связи к другим коллекциям.
flowchart LR
A[Datasource]-->B[Data Model]-->С[Repository];
Определения источников и моделей хранятся в экземпляре класса
Schema, и первым шагом будет создание данного
экземпляра.
import {Schema} from '@e22m4u/js-repository';
const schema = new Schema();
Интерфейс Schema содержит три основных метода:
defineDatasource(datasourceDef: object): this -
добавить источникdefineModel(modelDef: object): this - добавить
модельgetRepository(modelName: string): Repository - получить
репозиторийИсточник описывает способ подключения к базе и используемый адаптер.
Если адаптер имеет настройки, то они передаются вместе с объектом
определения источника методом defineDatasource, как это
показано ниже.
schema.defineDatasource({
name: 'myMongo', // название нового источника
adapter: 'mongodb', // название выбранного адаптера
// настройки адаптера mongodb
host: '127.0.0.1',
port: 27017,
database: 'data'
});
Параметры источника:
name: string уникальное названиеadapter: string выбранный адаптерПри желании можно использовать встроенный адаптер
memory, который хранит данные в памяти процесса. У него нет
специальных настроек, и он отлично подходит для тестов и
прототипирования.
schema.defineDatasource({
name: 'myMemory', // название источника
adapter: 'memory', // выбранный адаптер
});
Когда источники определены, можно перейти к описанию моделей данных. Модель может определять как структуру какого-либо объекта, так и являться отражением реальной коллекции базы.
Представьте себе коллекцию торговых точек, у каждой из которых
имеются координаты lat и lng. Мы можем заранее
определить модель для объекта координат методом defineModel
и использовать ее в других коллекциях.
schema.defineModel({
name: 'latLng', // название новой модели
properties: { // поля модели
lat: DataType.NUMBER, // поле широты
lng: DataType.NUMBER, // поле долготы
},
});
Параметры модели:
name: string уникальное название (обязательно)datasource: string выбранный источник данныхproperties: object определения полей моделиrelations: object определения связей моделиПараметр properties принимает объект, ключи которого
являются именами полей, а значением тип поля или объект с
дополнительными параметрами.
schema.defineModel({
name: 'latLng',
properties: {
lat: DataType.NUMBER, // краткое определение поля "lat"
lng: { // расширенное определение поля "lng"
type: DataType.NUMBER, // тип допустимого значения
required: true, // исключает null и undefined
},
},
});
Типы данных:
DataType.ANYDataType.STRINGDataType.NUMBERDataType.BOOLEANDataType.ARRAYDataType.OBJECTМодель latLng всего лишь описывает структуру объекта
координат, тогда как торговая точка должна иметь реальную таблицу в
базе. По аналогии с предыдущим примером, добавим модель
place, но дополнительно укажем источник данных в параметре
datasource
schema.defineModel({
name: 'place',
datasource: 'myMemory', // выбранный источник данных
properties: {
name: DataType.STRING, // поле для названия торговой точки
location: { // поле объекта координат
type: DataType.OBJECT, // допускать только объекты
model: 'latLng', // определение структуры объекта
},
},
});
В примере выше мы использовали модель latLng как
структуру допустимого значения поля location. Возможный
документ данной коллекции может выглядеть так:
{
"id": 1,
"name": "Burger King",
"location": {
"lat": 32.412891,
"lng": 34.7660061
}
}
Стоит обратить внимание, что мы могли бы не объявлять параметр
properties, при этом теряя возможность проверки данных
перед записью в базу.
schema.defineModel({
name: 'place',
adapter: 'myMemory',
// параметр "properties" не указан
});
Параметры поля:
type: string тип допустимого значения
(обязательно)itemType: string тип элемента массива (для
type: 'array')model: string модель объекта (для
type: 'object')primaryKey: boolean объявить поле первичным ключомcolumnName: string переопределение названия
колонкиcolumnType: string тип колонки (определяется
адаптером)required: boolean объявить поле обязательнымdefault: any значение по умолчаниюВ отличие от latLng, модель place имеет
источник данных с названием myMemory, который был объявлен
ранее. Наличие источника позволяет получить репозиторий по названию
модели.
const rep = schema.getRepository('place');
Методы:
create(data, filter = undefined)replaceById(id, data, filter = undefined)replaceOrCreate(data, filter = undefined)patch(data, where = undefined)patchById(id, data, filter = undefined)find(filter = undefined)findOne(filter = undefined)findById(id, filter = undefined)delete(where = undefined)deleteById(id)exists(id)count(where = undefined)Создадим торговую точку методом create используя
репозиторий из примера выше. Метод возвращает документ, который был
записан в базу, включая присвоенный идентификатор.
const place = await rep.create({
"name": "Burger King",
"location": {
"lat": 32.412891,
"lng": 34.7660061
},
});
console.log(place);
// {
// "id": 1,
// "name": "Burger King",
// "location": {
// "lat": 32.412891,
// "lng": 34.7660061
// }
// }
Добавленный в базу документ можно полностью заменить зная его
идентификатор. Воспользуемся методом replaceById, который
перезапишет данные по значению первичного ключа.
// {
// "id": 1,
// "name": "Burger King",
// "location": {
// "lat": 32.412891,
// "lng": 34.7660061
// }
// }
const result = rep.replaceById(place.id, {
name: 'Starbucks',
address: 'Sukhumvit Alley'
});
console.log(result);
// {
// "id": 1,
// "name": "Starbucks",
// "address": "Sukhumvit Alley"
// }
Если вы знакомы с методом PUT в архитектуре REST, когда документ
добавляется, если его не существовало, или же обновляется существующий,
то replaceOrCreate работает схожим образом. Если параметр
data передаваемый первым аргументом будет содержать
идентификатор, то метод будет вести себя как replaceById, в
противном случае будет создан новый документ.
// {
// "id": 1,
// "name": "Starbucks",
// "address": "Sukhumvit Alley"
// }
const result = rep.replaceOrCreate({
id: 1,
name: 'Airport',
city: 'Antalya',
code: 'AYT'
});
console.log(result);
// {
// "id": 1,
// "name": "Airport",
// "city": "Antalya"
// "code": "AYT"
// }
В примере выше был передан первичный ключ id для поиска
и замены существующего документа. Теперь рассмотрим создание документа с
новым идентификатором.
const result = rep.replaceOrCreate({
name: 'Airport',
city: 'Bangkok',
code: 'BKK',
});
console.log(result);
// {
// "id": 2,
// "name": "Airport",
// "city": "Bangkok",
// "code": "BKK"
// }
В отличие от replaceById, данный метод не удаляет поля,
которые не были переданы, что позволяет обновить только часть документа,
не затрагивая другие данные.
// {
// "id": 2,
// "name": "Airport",
// "city": "Bangkok",
// "code": "BKK"
// }
const result = rep.patchById(place.id, {
city: 'Moscow',
code: 'SVO'
});
console.log(result);
// {
// "id": 2,
// "name": "Airport",
// "city": "Moscow",
// "code": "SVO"
// }
Создаем модель user
schema.defineModel({
name: 'user', // название модели
adapter: 'myMemory', // выбранный источник
properties: { // поля модели
name: 'string',
age: 'number',
},
});
Получаем репозиторий модели user
const userRep = schema.getRepository('user');
Добавляем новую запись методом create
const fedor = await userRep.create({
name: 'Fedor',
age: 24,
});
console.log(fedor);
// {
// id: 1,
// name: 'Fedor',
// age: 24,
// }
Изменяем данные методом patchById
const result = await userRep.patchById(
fedor.id,
{age: 30},
);
console.log(result);
// {
// id: 1,
// name: 'Fedor',
// age: 30,
// }
Удаляем по идентификатору методом deleteById
await userRep.deleteById(fedor.id); // true
Выполняет операции чтения и записи определенной коллекции.
Методы:
create(data, filter = undefined)replaceById(id, data, filter = undefined)replaceOrCreate(data, filter = undefined)patch(data, where = undefined)patchById(id, data, filter = undefined)find(filter = undefined)findOne(filter = undefined)findById(id, filter = undefined)delete(where = undefined)deleteById(id)exists(id)count(where = undefined)Получение репозитория модели:
import {Schema} from '@e22m4u/js-repository';
const schema = new Schema();
// создаем источник
schema.defineDatasource({name: 'myDatasource', adapter: 'memory'});
// создаем модель
schema.defineModel({name: 'myModel', datasource: 'myDatasource'});
// получаем репозиторий по названию модели
const repositorty = schema.getRepository('myModel');
Переопределение конструктора:
import {Schema} from '@e22m4u/js-repository';
import {Repository} from '@e22m4u/js-repository';
import {RepositoryRegistry} from '@e22m4u/js-repository';
class MyRepository extends Repository {
/*...*/
}
const schema = new Schema();
schema.get(RepositoryRegistry).setRepositoryCtor(MyRepository);
// теперь schema.getRepository(modelName) будет возвращать
// экземпляр класса MyRepository
Большинство методов репозитория принимают объект filter
для фильтрации возвращаемого ответа. Описание параметров объекта:
where (условия выборки данных из
базы)
примеры:
where: {foo: 'bar'} поиск по значению поля
foo
where: {foo: {eq: 'bar'}} оператор равенства
eq
where: {foo: {neq: 'bar'}} оператор неравенства
neq
where: {foo: {gt: 5}} оператор "больше"
gt
where: {foo: {lt: 10}} оператор "меньше"
lt
where: {foo: {gte: 5}} оператор "больше или равно"
gte
where: {foo: {lte: 10}} оператор "меньше или равно"
lte
where: {foo: {inq: ['bar', 'baz']}} равенство одного из
значений inq
where: {foo: {nin: ['bar', 'baz']}} исключение значений
массива nin
where: {foo: {between: [5, 10]}} оператор диапазона
between
where: {foo: {exists: true}} оператор наличия значения
exists
where: {foo: {like: 'bar'}} оператор поиска подстроки
like
where: {foo: {ilike: 'BaR'}} регистронезависимая версия
ilike
where: {foo: {nlike: 'bar'}} оператор исключения подстроки
nlike
where: {foo: {nilike: 'BaR'}} регистронезависимая версия
nilike
where: {foo: {regexp: 'ba.+'}} оператор регулярного
выражения regexp
where: {foo: {regexp: 'ba.+', flags: 'i'}} флаги
регулярного выражения
order (упорядочить записи по
полю)
примеры:
order: 'foo' порядок по полю foo
order: 'foo ASC' явное указание порядка
order: 'foo DESC' инвертировать порядок
order: ['foo', 'bar ASC', 'baz DESC'] по нескольким
полям
limit (не более N записей)
примеры:
limit: 0 не ограничивать
limit: 14 не более 14-и
skip (пропуск первых N записей)
примеры:
skip: 0 выборка без пропуска
skip: 10 пропустить 10 объектов выборки
include (включение связанных данных в
результат)
примеры:
include: 'foo' включение связи foo
include: ['foo', 'bar'] включение foo и
bar
include: {foo: 'bar'} включение вложенной связи
foo
Определяет настройки и способ подключения к базе.
Параметры:
name: string название нового источникаadapter: string выбранный адаптер базы данныхПример:
schema.defineDatasource({
name: 'myDatasource',
adapter: 'memory',
});
Адаптер может иметь параметры, которые передаются при определении источника.
Пример:
schema.defineDatasource({
name: 'myDatasource',
adapter: 'mongodb',
// параметры адаптера:
host: '127.0.0.1',
port: 27017,
});
Описывает набор полей и связей к другим моделям.
Параметры:
name: string название новой моделиdatasource: string выбранный источник данныхproperties: object определения полей моделиrelations: object определения связей моделиПример:
schema.defineModel({
name: 'myModel',
datasource: 'myDatasource',
properties: {...}, // см. ниже
relations: {...}, // см. ниже
});
Параметр properties описывает набор полей и их
настройки.
Типы:
stringnumberbooleanarrayobjectanyПример:
schema.defineModel({
// ...
properties: {
prop1: 'string',
prop2: 'number',
prop3: 'boolean',
prop4: 'array',
prop5: 'object',
prop6: 'any',
},
});
Расширенные параметры:
type: string тип хранимого значенияitemType: string тип элемента массива (для
type: 'array')model: string модель объекта (для
type: 'object')primaryKey: boolean объявить поле первичным ключомcolumnName: string переопределение названия
колонкиcolumnType: string тип колонки (определяется
адаптером)required: boolean объявить поле обязательнымdefault: any значение по умолчанию для
undefinedПример:
schema.defineModel({
// ...
properties: {
prop1: {
type: 'string',
primaryKey: true,
},
prop2: {
type: 'boolean',
required: true,
},
prop3: {
type: 'number',
default: 100,
},
prop3: {
type: 'string',
// фабричное значение
default: () => new Date().toISOString(),
},
prop4: {
type: 'array',
itemType: 'string',
},
prop5: {
type: 'object',
model: 'objectModel',
},
},
});
Параметр relations описывает набор связей к другим
моделям.
Понятия:
Типы:
belongsTo - ссылка на целевую модель находится в
источникеhasOne - ссылка на источник находится в целевой модели
(one-to-one)hasMany - ссылка на источник находится в целевой модели
(one-to-many)referencesMany - массив ссылок на целевую модель
находится в источникеПараметры:
type: string тип связиmodel: string целевая модельforeignKey: string поле для идентификатора целиpolymorphic: boolean|string объявить связь
полиморфной*discriminator: string поле для названия целевой модели
(для polymorphic: true)i. Полиморфный режим belongsTo позволяет динамически
определять цель связи, где имя целевой модели хранится в отдельном поле,
рядом с foreignKey
Связь заказа к покупателю через поле customerId
schema.defineModel({
// ...
relations: {
// ...
customer: {
type: 'belongsTo',
model: 'customer',
foreignKey: 'customerId', // опционально
},
},
});
Полиморфная версия
schema.defineModel({
// ...
relations: {
// ...
customer: {
type: 'belongsTo',
polymorphic: true,
foreignKey: 'customerId', // опционально
discriminator: 'customerType', // опционально
},
},
});
Связь покупателя к заказу, как обратная сторона
belongsTo
schema.defineModel({
// ...
relations: {
// ...
order: {
type: 'hasOne',
model: 'order',
foreignKey: 'customerId', // поле целевой модели
},
},
});
Обратная сторона полиморфной версии belongsTo
schema.defineModel({
// ...
relations: {
// ...
order: {
type: 'hasOne',
model: 'order',
polymorphic: 'customer', // имя связи целевой модели
},
},
});
Явное указание foreignKey и
discriminator
schema.defineModel({
// ...
relations: {
// ...
order: {
type: 'hasOne',
model: 'order',
polymorphic: true, // true вместо имени модели
foreignKey: 'customerId', // поле целевой модели
discriminator: 'customerType', // поле целевой модели
},
},
});
Связь покупателя к заказам, как обратная сторона
belongsTo
schema.defineModel({
// ...
relations: {
// ...
orders: {
type: 'hasMany',
model: 'order',
foreignKey: 'customerId', // поле целевой модели
},
},
});
Обратная сторона полиморфной версии belongsTo
schema.defineModel({
// ...
relations: {
// ...
orders: {
type: 'hasMany',
model: 'order',
polymorphic: 'customer', // имя связи целевой модели
},
},
});
Явное указание foreignKey и
discriminator
schema.defineModel({
// ...
relations: {
// ...
orders: {
type: 'hasMany',
model: 'order',
polymorphic: true, // true вместо имени модели
foreignKey: 'customerId', // поле целевой модели
discriminator: 'customerType', // поле целевой модели
},
},
});
Связь покупателя к заказам через поле orderIds
schema.defineModel({
// ...
relations: {
// ...
orders: {
type: 'referencesMany',
model: 'order',
foreignKey: 'orderIds', // опционально
},
},
});
npm run test
MIT