itleague / microservice
General library for microservices
Requires
- php: ^7.4
- ext-json: *
- barryvdh/laravel-ide-helper: ^2.7
- flipbox/lumen-generator: ^8.0
- guzzlehttp/guzzle: ^7.0.1
- illuminate/redis: ^8.6
- laravel/lumen-framework: ^8.0
- opis/closure: ^3.6
- phabloraylan/lumen-middleware-trim-or-convert-strings: ^1.0
- predis/predis: ^1.1
- vladimir-yuldashev/laravel-queue-rabbitmq: ^11.0
README
Описание
Библиотека упрощает и ускоряет разработку новых микросервисов, особенно при использовании базы postgres. В основном состоит из хелперов и абстрактных классов, в которых уже прописана базовая логика большинства микросервисов: многоязычность, кэширование ответов, работа с полями типа file, аутентификация пользователя, валидация входящих данных, обработка ошибок и формирование ответов.
Установка
composer require itleague/microservice
Включает следующие библиотеки
- laravel/lumen-framework
- illuminate/redis
- predis/predis
- guzzlehttp/guzzle
- flipbox/lumen-generator: дополнительные команды artisan для Lumen. Очень помогает в разработке
- barryvdh/laravel-ide-helper: библиотека формирует метафайлы для использования IDE. Также может формировать phpDoc для моделей. Помогает в разработке и описании моделей
- phabloraylan/lumen-middleware-trim-or-convert-strings: включает 2 middleware для триминга строк из запросов и превращения пустых строк в null
- vladimir-yuldashev/laravel-queue-rabbitmq: библиотека для работы с очередями rabbitmq
- agpopov/laravel-pg-extensions: расширение для работы с PostgreSQL. Добавлено большое количество методов для миграции.
- ukfast/laravel-health-check: расширение для healthcheck'ов. Настраивается для каждого сервиса согласно документации. Можно использовать доп. классы
ITLeague\Microservice\HealthCheck\RedisHealthCheck
иITLeague\Microservice\HealthCheck\RabbitMQHealthCheck
Многоязычность
Полностью готовый функционал для работы с т. н. переводимыми полями. В первую очередь необходимо создать таблицу для хранения списка используемых языков:
php artisan microservice:languages-table
Будет создана новая миграция для таблицы. Один из языков определяется как язык по умолчанию.
Далее, для любой сущности можно определить некий набор полей, значения которых отличаются для разных языков, вынести их в отдельную таблицу. Значения на запрашиваемом языке будут автоматически подтягиваться в поле translation базовой сущности. При отсутствии необходимых переводов подтягиваются значения на языке по умолчанию.
Для корректной работы функционала необходимо в модели базовой сущности подключить трейт
ITLeague\Microservice\Traits\Models\Translatable
, а в модели с переводимыми полями — трейт
ITLeague\Microservice\Traits\Models\ModelForTranslatedAttributes
Язык возвращаемых сущностей определяется заголовком запроса Accept-Language
. Также в ответе присутствует
заголовок Content-Language
, который содержит двухбуквенный код используемого языка.
Заполнение переводимых значений происходит прямой передачей всего набора полей в базовую сущность. Необходимо только определить fillable поля моделей.
Для удобства фильтрации и сортировки к базовой модели добавлены scopes whereTranslatable
, orderByTranslatable
и orderByDescTranslatable
. Также для базовой модели есть scope joinTranslatableTable
, который джоинит к запросу таблицу с переводимыми полями и таблицу с языками. Стоит учитывать, что эти scopes решают только самые простые случаи. Для каких-то более сложных запросов можно брать за основу содержимое этих методов и писать своё.
Миграции
Основная документация по расширению laravel-pg-extensions
есть тут. Я взял собственный форк этого репозитория и добавил некоторые методы:
primaryUuid
: добавляет первичный ключ типа uuid с автоматической генерацией значенийimmutable
: делает значения в колонке неизменяемыми. Особенно актуально для первичных ключей типа uuiddropImmutable
: удаляет триггер неизменности значений колонкиarray
: создаёт поле типаARRAY
. Вместе с ней добавляется хранимая процедура для преобразования строки в формате json (которую может сформировать Laravel) в данные в формате ARRAY для PostgreSQL.watchUpdate
/watchInsert
: добавляет поляcreate_at
,created_by
,updated_at
,updated_by
и триггер, который обновляет соответствующие даты средствами БД. Рекомендуется добавлять метод к таблицам основных сущностейwatchDelete
: добавляет поляdeleted_at
,deleted_by
и триггер, который запрещает удаление записей из таблицы. Также рекомендуется добавлять к таблицам основных сущностейaddWatchColumns
: добавляет поляcreate_at
,created_by
,updated_at
,updated_by
без триггера. Имеет смысл вызывать отдельно, когда нужно сделать необязательными поляcreated_by
иupdated_by
addSoftDeleteColumns
: просто добавляет поляdeleted_at
иdeleted_by
touchParent
: метод добавляет триггер, который меняет дату родительской записи при обновлении текущей записи с foreign key. Вызывается непосредственно для ForeignKeyDefinition.enum
: создаёт поле типаENUM
(а не текстовое поле с constraint, как было изначально). В моделях работать с ним можно как с обычной строкой. В PostgreSQL с типомENUM
есть некоторые ограничения при работе со списком доступных значений (см. документацию PostgreSQL)
Работа с полями типа array
На текущий момент реализованы только корректное чтение и запись данных в поля типа ARRAY
. Для использования функционала необходимо в свойстве casts
модели указать необходимый тип массива. Тип можно выбрать из готовых наследников класса ITLeague\Microservice\Casts\ArrayCast
или написать свой по подобию. Также к модели необходимо подключить трейт ITLeague\Microservice\Traits\Models\WithArrayAttribute
. После этого со значением можно работать как с обычным php-массивом.
Работа с полями типа file
В базе поля типа file хранятся в виде UUID файла в хранилище. В ответах такие поля возвращаются в виде метаданных самого файла (см. документацию для сервиса storage). Всё, что необходимо для задания такого типа полей - у модели заполнять свойство file параметрами: permission, sizes, force, is_multiple.
При force равном true не будет происходить подтверждения файла при обновлении значения поля.
Если is_multiple равно true, то поле базы должно быть типа uuid[]. Работа с этим полем в модели ровно такая же, как с обычным массивом. Информацию по миграциям для таких полей см. в соответствующем разделе.
Важно! Сохранять модели с файловыми полями могут только авторизованные пользователи.
Кэширование ответов
Для кэширования ответов используется паттерн Repository с декоратором. Ответы для всех get-запросов кэшируются. Кэш сбрасывается при обновлении/добавлении/удалении/восстановлении сущности. Есть возможность указать время кэширования и доп. сущности, кэш которых будет сбрасываться вместе с текущим.
Для правильной работы необходимо для каждой сущности создать интерфейс, класс репозитория и класс декоратора Caching.
Наследоваться можно от обычного интерфейса ITLeague\Microservice\Repositories\Interfaces\RepositoryInterface
или
его Restorable версии, которая включает в себя ещё и метод restore
для восстановления удалённой сущности.
Также уже готов абстрактный класс ITLeague\Microservice\Http\Controllers\ResourceController
(и Restorable версия),
в котором есть 5 методов для CRUD api.
Шина сообщений на основе RabbitMQ
RabbitMQ используется для асинхронной передачи сообщений между сервисами. Один сервис может отправить событие в шину
с помощью фасада MicriserviceBus
MicroserviceBus::push('event_name', $data)
Другой сервис может подписаться на это событие
MicroserviceBus::listen('event_name', Handler::class)
Важно! Подписка на события должна происходить после регистрации провайдера библиотеки!
В качестве обработчика события может выступать любой класс с интерфейсом ITLeague\Microservice\Http\Bus\EventHandler
.
Запуск воркера для прослушивания событий у сервиса происходит командой из laravel-queue-rabbitmq
php artisan rabbitmq:consume
Воркер желательно перезапускать после каждого коммита. Также для стабильной работы необходимо подключить супервизор.
Базовый класс для запросов в другие сервисы
В библиотеке есть абстрактный класс ITLeague\Microservice\Http\Services\BaseService
, в котором можно использовать
методы client()
(возвращает объект http-клиента для отправки запросов) и query()
(непосредственно отправляет запрос
и обрабатывает от сервиса).
В качестве примера использования можно посмотреть фасад Storage в библиотеке. Но для корректной работы класса требуется только наличие трёх настроек: base_uri, prefix и timeout
Остальной функционал базового класса сущности
Использование метода getUnfilledAttributes
При создании/обновлении каждой сущности все значения полей, которые не входят в набор fillable-полей, помещаются в
массив unfilled. Его можно получить методом getUnfilledAttributes
и использовать, например, в событии saved
для
заполнения связанных сущностей. Именно так, кстати, заполняются переводимые поля сущности. Посмотреть пример можно в трейте ITLeague\Microservice\Traits\Models\Translatabe
Создание дополнительных фильтров и сортировок сущности
По умолчанию любую сущность можно фильтровать по её идентификатору. В конструкторе модели можно задать дополнительные фильтры и сортировки. Для этого необходимо заполнить свойства модели filters и sorts соответственно. Ключами являются названия полей, значениями - closure с описанием логики фильтра и сортировки.
Также необходимо добавить новые фильтры и сортировки в правила валидации сущности (см. далее).
Запрос из базы только необходимых связей сущности
Для всех запрашиваемых сущностей есть возможность указать только необходимые в ответе поля. И если из таблицы сущности
в базе выбираются все поля, то связанные сущности из других таблиц запрашивать необязательно, если они не требуются
в ответе. Для этого у сущности существует свойство eagerLoad
, в котором перечисляются все отношения сущности. Если
какое-то из этих отношений не требуется в get-запросе, то оно не будет доставаться из базы.
Аутентификация
Для каждого запроса обрабатываются заголовки x-authenticated-userid
и x-authenticated-scope
.
В результате запрос либо остаётся неавторизованным, либо создаётся экземпляр класса ITLeague\Microservice\Models\User
,
унаследованный от GenericUser
. В дальнейшем аутентификацию можно проверять фасадом Auth
.
Также добавлен полный доступ ко всем эндпоинтам для супер-админа.
Валидация входящих данных
Идея в том, чтобы валидировать все входящие данные до передачи их в бд. Для этого у каждой сущности есть свойство
rules
со следующими ключами:
- store - набор правил валидации для сохранения сущности. Включает в себя переводимые поля и остальные поля, которые попадают в unfilled.
- update - набор правил для обновления сущности. Обычно отличается от store отсутствием правила для первичного ключа
и отсутствием правила
required
. - filter - набор правил валидации фильтра, который используется при запросе списка сущностей. Фильтр по первичному
ключу уже задан для всех сущностей, но для использования его необходимо также указать в правилах. Для удобства в
валидатор добавлены правила
array_or_integer
иarray_or_string:length
. - sort - правило, ограничивающее список полей, по которым возможно сортировать список сущностей. Для удобства
есть правило
sort_in:field_1,field_2...
.
Также есть валидация параметра fields
. Никакие правила специально задавать не нужно, используется набор ключей
соответствующего ресурса сущности. Необходимо только указать для ресурса трейт
ITLeague\Microservice\Traits\FilterableResource
и обернуть возвращаемый методом toArray
массив в метод
fields
.
Формирование ответов
Для единообразного формирования ответов используется трейт ITLeague\Microservice\Traits\ApiResponse
, который
можно подключить для любого класса, возвращающего ответы. В основном это контроллеры и хендлер обработки
ошибок.
Обработка ошибок
Для обработки всех исключений уже подключен ITLeague\Microservice\Exceptions\Handler
. Пока нет возможности добавлять
в него свои типы исключений.
Если переменная APP_DEBUG
установлена в true
, то используется штатный рендер ошибок Lumen. Иначе ошибки выводятся в
виде json-объекта.
Файловое хранилище
В классе ITLeague\Microservice\Http\Services\StorageService
реализованы все методы для работы с файловым хранилищем. Доступ к нему может осуществляться через фасад ITLeague\Microservice\Facades\Storage`
За доп. информацией по ним см. документацию сервиса storage
Базовый абстрактный класс для тестов
К базовому классу TestCase
от Lumen добавлены свойства Faker\Factory
faker, user/admin/superAdmin
(экземпляры класса
ITLeague\Microservice\Models\User
с соответствующими правами). Также повешен постоянный 204 ответ на обращения
к файловому хранилищу. Ещё есть стандартная структура возвращаемых ошибок.