- PVSM.RU - https://www.pvsm.ru -
Перевод статьи с DZone. Оригинал: https://dzone.com/articles/applications-for-tarantool-part-1-stored-procedure [1].
Я хочу поделиться своим опытом создания приложений для Tarantool, и сегодня мы поговорим об установке этой СУБД, о хранении данных и об обращении к ним, а также о записи хранимых процедур.
Tarantool — это NoSQL/NewSQL-база данных, которая хранит данные в оперативной памяти, но может использовать диск и обеспечивает согласованность с помощью тщательно спроектированного механизма под названием «журнал упреждающей записи» (write-ahead log, WAL). Также Tarantool может похвастаться встроенным LuaJIT-компилятором (JIT — just-in-time), который позволяет выполнять Lua-код.
Мы рассмотрим создание Tarantool-приложения, реализующего API для регистрации и аутентификации пользователей. Его возможности:
За примером хранимой процедуры для Tarantool мы обратимся к первому этапу, точнее к получению кода подтверждения регистрации. Можете зайти в GitHub-репозиторий [2] и выполнять все действия по ходу повествования.
В сети есть подробные инструкции по установке под различные ОС. К примеру, для установки Tarantool под Ubuntu вставьте в консоль и выполните этот скрипт:
curl http://download.tarantool.org/tarantool/1.9/gpgkey | sudo apt-key add -
release=`lsb_release -c -s`
sudo apt-get -y install apt-transport-https
sudo rm -f /etc/apt/sources.list.d/*tarantool*.list
sudo tee /etc/apt/sources.list.d/tarantool_1_9.list <<- EOF
deb http://download.tarantool.org/tarantool/1.9/ubuntu/ $release main
deb-src http://download.tarantool.org/tarantool/1.9/ubuntu/ $release main
EOF
sudo apt-get update
sudo apt-get -y install tarantool
Проверим успешность установки, введя tarantool
и войдя в интерактивную консоль администратора.
$ tarantool
version 1.9.0-4-g195d446
type 'help' for interactive help
tarantool>
Здесь вы уже можете попробовать программировать на Lua. Если не знакомы с этим языком, то вот короткое руководство для начала: http://tylerneylon.com/a/learn-lua [3].
Теперь напишем наш первый скрипт для создания пространства, в котором будут храниться все пользователи. Оно аналогично таблице в реляционной БД. Сами данные хранятся в кортежах (массивах, содержащих записи). Каждое пространство должно иметь один первичный индекс и может иметь несколько вторичных индексов. Индексы могут быть определены по одному или нескольким полям. Вот схема пространства нашего сервиса аутентификации:
Мы используем индексы двух типов: HASH
и TREE
. Индекс HASH
позволяет искать кортежи с помощью полного совпадения первичного ключа, который должен быть уникальным. Индекс TREE
поддерживает неуникальные ключи, позволяет искать по началу составного индекса и организовывать сортировку ключей, поскольку их значения упорядочены внутри индекса.
Пространство session
содержит специальный ключ (session_secret
), используемый для подписывания куков сессии. Хранение ключей сессии позволяет при необходимости разлогинивать пользователей на стороне сервера. Также у сессии есть опциональная ссылка на пространство social
. Это нужно для проверки сессий тех пользователей, которые входят по учётным данным соцсетей (проверяем валидность хранимого токена OAuth 2).
Прежде чем начать писать само приложение, давайте взглянем на структуру проекта:
tarantool-authman
├── authman
│ ├── model
│ │ ├── password.lua
│ │ ├── password_token.lua
│ │ ├── session.lua
│ │ ├── social.lua
│ │ └── user.lua
│ ├── utils
│ │ ├── http.lua
│ │ └── utils.lua
│ ├── db.lua
│ ├── error.lua
│ ├── init.lua
│ ├── response.lua
│ └── validator.lua
└── test
├── case
│ ├── auth.lua
│ └── registration.lua
├── authman.test.lua
└── config.lua
Пути, определённые в переменной package.path
, используются для импорта Lua-пакетов. В нашем случае пакеты импортируются относительно текущей директории tarantool-authman
. Но если нужно, то пути импорта легко можно расширить:
-- Prepending a new path with the highest priority
package.path = “/some/other/path/?.lua;” .. package.path
Перед созданием первого пространства давайте положим все необходимые константы в отдельные модели. Нужно задать имя каждому пространству и индексу. Также необходимо определить порядок полей в кортеже. К примеру, так выглядит модель authman/model/user.lua
:
-- Our package is a Lua table
local user = {}
-- The package has the only function — model — that returns a table
-- with the model’s fields and methods
-- The function receives configuration in the form of a Lua table
function user.model(config)
local model = {}
-- Space and index names
model.SPACE_NAME = ‘auth_user’
model.PRIMARY_INDEX = ‘primary’
model.EMAIL_INDEX = ‘email_index’
-- Assigning numbers to tuple fields
-- Note thatLua uses one-based indexing!
model.ID = 1
model.EMAIL = 2
model.TYPE = 3
model.IS_ACTIVE = 4
-- User types: registered via email or with social network
-- credentials
model.COMMON_TYPE = 1
model.SOCIAL_TYPE = 2
return model
end
-- Returning the package
return user
При обработке пользователей нам понадобятся два индекса: уникальный по ID и неуникальный по адресу почты. Когда два разных пользователя регистрируются с учётными данными соцсетей, они могут указать одинаковые адреса или вообще их не указать. А для пользователей, регистрирующихся обычным способом, приложение проверит уникальность почтовых адресов.
Пакет authman/db.lua
содержит метод для создания пространств:
local db = {}
-- Importing the package and calling the model function
-- The config parameter is assigned a nil (empty) value
local user = require(‘authman.model.user’).model()
-- The db package’s method for creating spaces and indexes
function db.create_database()
local user_space = box.schema.space.create(user.SPACE_NAME, {
if_not_exists = true
})
user_space:create_index(user.PRIMARY_INDEX, {
type = ‘hash’,
parts = {user.ID, ‘string’},
if_not_exists = true
})
user_space:create_index(user.EMAIL_INDEX, {
type = ‘tree’,
unique = false,
parts = {user.EMAIL, ‘string’, user.TYPE, ‘unsigned’},
if_not_exists = true
})
end
return db
UUID будет выступать в роли ID пользователя, и для поиска с полным совпадением мы станем использовать индекс HASH
. Индекс для поиска по почте будет состоять из двух частей: (user.EMAIL, ‘string’
) — пользовательский адрес почты, (user.TYPE, ‘unsigned’
) — тип пользователя. Напомню, что типы определяются чуть раньше в модели. Составной индекс позволяет искать не только по всем полям, но и по первой части индекса. Так что мы можем искать только по адресу почты (без типа пользователя).
Войдём в консоль администратора и используем пакет authman/db.lua
.
$ tarantool
version 1.9.0-4-g195d446
type ‘help’ for interactive help
tarantool> db = require(‘authman.db’)
tarantool> box.cfg({})
tarantool> db.create_database()
Отлично, мы только что создали первое пространство. Не забывайте: прежде чем вызывать box.schema.space.create
, нужно с помощью метода box.cfg
сконфигурировать и запустить сервер. Теперь можно внутри созданного пространства выполнять какие-то простые действия:
-- Creating users
tarantool> box.space.auth_user:insert({‘user_id_1’, ‘example_1@mail.ru’, 1})
— -
- [‘user_id_1’, ‘example_1@mail.ru’, 1]
…
tarantool> box.space.auth_user:insert({‘user_id_2’, ‘example_2@mail.ru’, 1})
— -
- [‘user_id_2’, ‘example_2@mail.ru’, 1]
…
-- Getting a Lua table (array) with all the users
tarantool> box.space.auth_user:select()
— -
- — [‘user_id_2’, ‘example_2@mail.ru’, 1]
— [‘user_id_1’, ‘example_1@mail.ru’, 1]
…
-- Getting a user by the primary key
tarantool> box.space.auth_user:get({‘user_id_1’})
— -
- [‘user_id_1’, ‘example_1@mail.ru’, 1]
…
-- Getting a user by the composite key
tarantool> box.space.auth_user.index.email_index:select({‘example_2@mail.ru’, 1})
— -
- — [‘user_id_2’, ‘example_2@mail.ru’, 1]
…
-- Changing the data in the second field
tarantool> box.space.auth_user:update(‘user_id_1’, {{‘=’, 2, ‘new_email@mail.ru’}, })
— -
- [‘user_id_1’, ‘new_email@mail.ru’, 1]
…
Уникальные индексы не позволяют вводить неуникальные значения. Если нужно создать записи, которые уже могут находиться в пространстве, используйте операцию upsert
(update/insert). Полный список доступных методов приведён в официальной документации: https://tarantool.org/doc/1.9/book/box/box_space.html [4].
Давайте расширим пользовательскую модель возможностью регистрации пользователей:
function model.get_space()
return box.space[model.SPACE_NAME]
end
function model.get_by_email(email, type)
if validator.not_empty_string(email) then
return model.get_space().index[model.EMAIL_INDEX]:select({email, type})[1]
end
end
-- Creating a user
-- Fields that are not part of the unique index are not mandatory
function model.create(user_tuple)
local user_id = uuid.str()
local email = validator.string(user_tuple[model.EMAIL]) and
user_tuple[model.EMAIL] or ‘’
return model.get_space():insert{
user_id,
email,
user_tuple[model.TYPE],
user_tuple[model.IS_ACTIVE],
user_tuple[model.PROFILE]
}
end
-- Generating a confirmation code sent via email and used for
-- account activation
-- Usually, this code is embedded into a link as a GET parameter
-- activation_secret — one of the configurable parameters when
-- initializing the application
function model.generate_activation_code(user_id)
return digest.md5_hex(string.format(‘%s.%s’,
config.activation_secret, user_id))
end
В нижеприведённом коде использованы два стандартных пакета Tarantool — uuid
и digest
— и один созданный пользователем — validator
. Но сначала их нужно импортировать:
-- standard Tarantool packages
local digest = require(‘digest’)
local uuid = require(‘uuid’)
-- Our application’s package (handles data validation)
local validator = require(‘authman.validator’)
При определении переменных мы используем оператор local
, ограничивающий их область видимости текущим блоком. Если так не делать, переменные будут глобальными, а нам этого нужно избегать из-за возможных конфликтов имён.
Теперь создадим основной пакет authman/init.lua
, в котором будут храниться все API-методы:
local auth = {}
local response = require(‘authman.response’)
local error = require(‘authman.error’)
local validator = require(‘authman.validator’)
local db = require(‘authman.db’)
local utils = require(‘authman.utils.utils’)
-- The package returns the only function — api — that configures and
-- returns the application
function auth.api(config)
local api = {}
-- The validator package contains checks for various value types
-- This package sets the default values as well
config = validator.config(config)
-- Importing the models for working with data
local user = require(‘authman.model.user’).model(config)
-- Creating a space
db.create_database()
-- The api method creates a non-active user with a specified email
-- address
function api.registration(email)
-- Preprocessing the email address — making it all lowercase
email = utils.lower(email)
if not validator.email(email) then
return response.error(error.INVALID_PARAMS)
end
-- Checking if a user already exists with a given email
-- address
local user_tuple = user.get_by_email(email, user.COMMON_TYPE)
if user_tuple ~= nil then
if user_tuple[user.IS_ACTIVE] then
return response.error(error.USER_ALREADY_EXISTS)
else
local code = user.generate_activation_code(user_tuple[user.ID])
return response.ok(code)
end
end
-- Writing data to the space
user_tuple = user.create({
[user.EMAIL] = email,
[user.TYPE] = user.COMMON_TYPE,
[user.IS_ACTIVE] = false,
})
local code = user.generate_activation_code(user_tuple[user.ID])
return response.ok(code)
end
return api
end
return auth
Отлично! Теперь пользователи могут создавать аккаунты.
tarantool> auth = require(‘authman’).api(config)
-- Using the api to get a registration confirmation code
tarantool> ok, code = auth.registration(‘example@mail.ru’)
-- This code needs to be sent to a user’s email address so that they
-- can activate their account
tarantool> code
022c1ff1f0b171e51cb6c6e32aefd6ab
Продолжение следует
Автор: danikin
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/nosql/276736
Ссылки в тексте:
[2] GitHub-репозиторий: https://github.com/mailru/tarantool-authman
[3] http://tylerneylon.com/a/learn-lua: http://tylerneylon.com/a/learn-lua/
[5] Источник: https://habrahabr.ru/post/352430/?utm_campaign=352430
Нажмите здесь для печати.