Приложение «Книжный магазин»
Приложение 2.0
12
Авторские права
© Postgres Professional, 2020 год.
Авторы: Егор Рогов, Павел Лузанов
Использование материалов курса
Некоммерческое использование материалов курса (презентации,
демонстрации) разрешается без ограничений. Коммерческое
использование возможно только с письменного разрешения компании
Postgres Professional. Запрещается внесение изменений в материалы
курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу:
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за
любые повреждения и убытки, включая потерю дохода, нанесенные
прямым или непрямым, специальным или случайным использованием
материалов курса. Компания Postgres Professional не предоставляет
каких-либо гарантий на материалы курса. Материалы курса
предоставляются на основе принципа «как есть» и компания Postgres
Professional не обязана предоставлять сопровождение, поддержку,
обновления, расширения и изменения.
2
Темы
Обзор приложения «Книжный магазин 2.0»
Схема данных
Интерфейс с клиентской частью
Разграничение доступа
3
Книжный магазин
Интернет-магазин для покупателей
поиск книг
детальная информация о выбранной книге
корзина для зарегистрированных пользователей
«Админка» для сотрудников
заказ книг у поставщика
установка розничной цены
фоновые задания: запуск, просмотр результатов
В этом курсе, как и в DEV1, мы будем использовать приложение
«Книжный магазин», но новой версии 2.0. Клиентская часть полностью
готова, а серверную мы будем улучшать по мере прохождения курса.
Как и раньше, клиентская часть состоит из интернет-магазина и
«админки». Однако со времен DEV1 магазин вырос и число
покупателей увеличилось.
Теперь, чтобы приобрести книги, покупатели должны регистрироваться
на сайте и входить под своим именем. В магазине теперь есть корзина,
а книги имеют цену.
Админка позволяет заказывать книги и устанавливать них цену.
Добавление книг и авторов из админки исключено из приложения 2.0.
Зато добавилась возможность запускать фоновые задания (например,
отчеты) и просматривать их результаты после завершения.
Приложение предназначено исключительно для демонстрации
концепций, излагаемых в этом курсе. Оно умышленно сделано крайне
упрощенным и не может служить образцом проектирования
реальных систем.
4
Демонстрация
В этой демонстрации мы показываем приложение «Книжный магазин
2.0» в том виде, в котором оно будет после завершения всех
практических заданий. Приложение доступно в браузере виртуальной
машины курса по адресу http://localhost/.
Приложение состоит из двух частей, представленных вкладками.
- «Книжный магазин» — это интерфейс веб-пользователя, в котором он
может просматривать и покупать книги.
- «Админка» — интерфейс сотрудников магазина, в котором они могут
заказывать книги и устанавливать их цену, а также выполнять фоновые
задания.
В учебных целях вся функциональность представлена на одной общей
веб-странице. Если какая-то часть функциональности недоступна из-за
того, что на сервере нет подходящего объекта, приложение сообщит
об этом. Также приложение выводит текст запросов, которые оно
посылает на сервер.
Приложение позволяет выбрать сервер для подключения. В основном
мы будем использовать значение по умолчанию.
Клиентская часть приложения разработана Ильей Баштановым, за что
авторы выражают ему признательность и благодарность. Исходный код
клиента приложения не является темой курса, но может быть получен
в git-репозитории https://git.postgrespro.ru/pub/dev2app.git.
5
Основные сущности
Книга
название
формат издания
кол-во страниц
рейтинг
...
Автор
фамилия
имя
отчество
Операция
изменение кол-ва
цена
дата
Пользователь
имя
почта
Розничная
цена
цена
даты действия
авторство
порядок указания
книги в корзине
количество
Сеанс
токен
Основные сущности нашей базы данных практически не изменились
со времен курса DEV1. Это:
- Книга. К книгам добавились атрибуты.
- Автор. Книги и авторы связаны отношением многие-ко-многим
(авторство).
- Операции с книгами: покупки в магазине и поступления на склад.
К атрибутам добавилась цена книги.
Новые сущности:
- Розничная цена. Мы рассматриваем цену как отдельную сущность,
а не атрибут книги, поскольку она может меняться со временем, то есть
имеет диапазон дат действия.
- Пользователь веб-магазина. Определяется именем (логином)
и имеет почтовый адрес.
Пользователь может класть книги в корзину, и поэтому связан с книгами
отношением многие-ко-многим. (Отдельной сущности для корзины мы
не предусматриваем.)
- Сеанс работы пользователя с магазином. Сеанс связан с вопросами
аутентификации и разграничения доступа.
6
Основные таблицы
books
book_id
title
format
pages
rating
votes_up
votes_down
...
onhand_qty
authors
author_id
last_name
first_name
middle_name
operations
operation_id
book_id
qty
price
at
users
user_id
username
email
retail_prices
book_id
price
effective
authorships
author_id
book_id
seq_num
cart_items
user_id
book_id
qty
sessions
user_id
auth_token
Основные таблицы базы данных представлены на слайде.
В качестве идентификаторов используются суррогатные ключи,
генерируемые с помощью последовательностей.
Связи многие-ко-многим представлены дополнительными таблицами:
- authorships — авторство;
- cart_itemsкниги в корзине.
Отметим, что в таблице книг books имеется дополнительный столбец
onhand_qty, содержащий текущее количество книг на складе магазина,
и обновляемый триггером по таблице операций.
С расчетом рейтинга книг (rating) связаны еще два столбца: голоса
пользователей «за» (votes_up) и «против» (votes_down).
7
Интерфейс с клиентом
схема webapi
интерфейсные функции магазина
схема empapi
интерфейсные функции админки
схема public – таблицы и внутренние функции
register_user
login
logout
get_cart
checkout
get_catalog
get_image
cast_vote
add_to_cart
remove_from_cart
get_catalog
receipt
set_retail_price
get_tasks
get_programs
task_results
run_program
роль
web
роль
emp
Клиентский API серверной части полностью построен на функциях.
Интерфейсные функции веб-магазина доступны только пользователю
web и размещены в схеме webapi:
- аутентификация и разграничение доступа;
- работа с каталогом книг;
- операции с корзиной.
Интерфейсные функции админки доступны только пользователю emp
и размещены в схеме empapi:
- работа с каталогом книг;
- фоновые задания.
Аутентификация в админке не предусмотрена.
Все таблицы, а также внутренние функции, не открытые напрямую для
клиента, размещены в схеме public.
Интерфейсные функции объявлены как SECURITY DEFINER, чтобы
иметь доступ к объектам базы данных. С помощью механизма
привилегий по умолчанию (ALTER DEFAULT PRIVILEGES) доступ
к функциям в схеме webapi автоматически выдается только
пользователю web, в схеме empapi — пользователю emp, а доступ
к функциям в схеме public отбирается у роли public.
(Вопросы разграничения доступа рассматриваются в курсе DEV1.)
Сейчас мы рассмотрим интерфейс подробнее, но обратите внимание,
что информационная панель веб-клиента показывает, какие вызовы он
отправляет на сервер.
8
Аутентификация
users
user_id
username
email
sessions
user_id
auth_token
webapi.register_user
webapi.login public.check_auth
webapi.logout
токен
Аутентификация пользователей интернет-магазина происходит на
клиенте. С точки зрения базы данных клиент всегда представлен одной
ролью web.
Новый пользователь регистрируется вызовом register_user. Для
простоты мы не используем пароли (но если бы использовали, то хеш
пароля хранился бы в таблице users).
Чтобы иметь возможность совершать покупки, пользователь должен
войти в систему. Это выполняет функция login. Она создает сеанс
пользователя, который определяется токеном (UUID).
Токен возвращается клиенту и дальше клиент передает его как
параметр во все функции, связанные с покупками. Каждая такая
функция первым делом проверяет правильность токена с помощью
вызова check_auth, который определяет по токену имя пользователя.
Наконец, вызов logout завершает сеанс.
Функционал истечения срока сеанса не реализован, но может быть
легко добавлен.
10
Каталог книг
webapi.get_catalog
empapi.get_catalog
public.get_catalog
webapi.get_image
webapi.cast_vote
empapi.receipt
books
book_id
title
format
pages
rating
votes_up
votes_down
...
operations
operation_id
book_id
qty
price
at
retail_prices
book_id
price
effective
public.get_retail_price
public.get_catalog
empapi.set_retail_price
Список книг (конечно, вместе с авторами, хотя эти таблицы не показаны
на слайде для краткости) нужен как магазину, так и админке — но
с разным набором полей. Всю возможную информацию выбирает
закрытая функция public.get_catalog, получая на вход параметры
поиска. Функции webapi.get_catalog и empapi.get_catalog реализованы
как обертки вокруг public.get_catalog.
Получение обложки книги выполняет функция webapi.get_image.
Обложки не входят в get_catalog, чтобы клиент как можно быстрее
отобразил результаты поиска, а обложки подгружал в фоновом режиме.
Голосование «за» или «против» книги выполняется функцией
webapi.cast_vote. Функция допускает голосование несколько раз, но
работает только для зарегистрированных пользователей.
Заказ книги у поставщика в админке выполняет функция
empapi.receipt. Она создает соответствующую операцию с книгой.
За установку розничной цены на книгу отвечает функция
empapi.set_retail_price. Текущую цену возвращает функция
public.get_retail_price, которую клиент никогда не вызывает напрямую,
но она используется во многих интерфейсных функциях.
12
Корзина
books
book_id
title
format
pages
rating
votes_up
votes_down
...
onhand_qty
operations
operation_id
book_id
qty
price
at
cart_items
user_id
book_id
qty
webapi.get_cart
webapi.remove_from_cart
webapi.add_to_cart
webapi.checkout
Все функции, относящиеся к покупке, работают от имени конкретного
пользователя магазина и поэтому требуют токена.
Функция webapi.add_to_cart добавляет в корзину одну книгу или
убирает из корзины одну книгу. Функция webapi.remove_from_cart
полностью удаляет всю позицию из корзины.
Функция webapi.get_cart возвращает содержимое корзины.
Функция webapi.checkout совершает покупку, убирая позиции из
корзины и создавая соответствующие операции с книгами.
Весь процесс взаимодействия с пользователем во время покупки книг
не может быть выполнен в рамках одной транзакции СУБД, поскольку
этот процесс занимает неизвестное (большое) время. Вместо этого мы
позволяем пользователю добавлять книги в корзину, не обращая
внимания на наличие необходимого количества книг на складе. Каждое
добавление происходит в отдельной (очень короткой) транзакции, так
что в базе данных не возникает никаких долговременных блокировок.
Фактическая проверка количества происходит только в транзакции при
покупке: если на складе будет недостаточно книг, ограничение
целостности в базе данных не позволит выполнить транзакцию..
(В реальной системе необходимо было бы предусмотреть
резервирование товара и фиксацию цены на время, отведенное для
оплаты онлайн. Конечно, и это действие тоже не должно приводить
к длинным транзакциям.)
14
Фоновые задания
programs
program_id
name
func
tasks
task_id
program_id
status
params
started
finished
result
...
empapi.get_programs
empapi.get_tasks
empapi.task_results
empapi.run_program
public.register_program
Для фоновых заданий используются еще две таблицы, не показанные
на основной схеме:
- programs — программы, которые можно запускать. У программы есть
название и имя «исполняемой» функции.
- tasks — собственно задания, т. е. экземпляры программ. Задание
имеет статус (запланировано к запуску, работает и т. п.), значения
параметров, дату запуска и окончания работы, результат выполнения
и другие.
Программа регистрируется функцией public.register_program. Клиент
не вызывает эту функцию, поэтому она находится в схеме public.
Задание ставится на выполнение функцией empapi.run_program.
Несколько функций предназначены для получения информации:
- о списке программ — empapi.get_programs;
- о списке заданий — empapi.get_tasks;
- о результате выполнения — empapi.task_results.