Mezon - php фреймворк для создания микросервисов
Когда я начинал его делать, то ставил целью сделать решение, которое:
- можно легко встраивать в уже существующие проекты;
- быстро можно создать хоть что-то работающее;
- максимально лаконичные и выразительные конструкции;
- разумно использовал возможности современного PHP.
Итак, с чего можно начать? Конечно же с исходников! Посмотреть их можно на github — https://github.com/alexdodonov/mezon
Ну и чтобы не вдаваться в пространные рассуждения давайте сразу начнём с рабочего примера.
Первым делом нам понадобится .htaccess , в котором мы настроим несколько правил:
# use mod_rewrite for pretty URL support
RewriteEngine on
RewriteRule ^([a-z0-9A-Z_\/\.\-\@%\ :,]+)/?(.*)$ index.php?r=$1&%{QUERY_STRING} [L]
RewriteRule ^/?(.*)$ index.php?r=index&%{QUERY_STRING} [L]
Далее можно создавать свой первый сервис. В нём сделаем один эндпоинт, который будет обрабатывать метод GET и возвращать сообщение, что у него всё хорошо. Этакий health check.
Для начала нам надо подключить наш фреймворк:
require_once (’vendor/service/service.php’);
Потом создаём класс для микросервиса:
class TodoService extends ServiceBase implements ServiceBaseLogicInterface
{ /* class body */ }
Здесь у нас:
ServiceBase — это базовый класс сервиса с самым основным и самым утилитарным функционалом;
ServiceBaseLogicInterface — это интерфейс, который нужно реализовать любому классу, если он хочет предоставлять обработчики эндпоинтов. Пока этот интерфейс никаких особых требований на ваш класс не налагает. Просто сделан для более строгой типизации.
Потом заводим первый обработчик эндпоинта:
public function action_ping()
{
return (’I am alive!’);
}
После чего запускаем наш первый микросервис:
Service::start(’TodoService’);
Сложив всё вместе, получим:
/* Service class */
class TodoService extends ServiceBase implements ServiceBaseLogicInterface {
/* First endpoint */
public function action_ping() {
return (’I am alive!’);
}
}
Service::start(’TodoService’);
Может возникнуть резонные вопрос — а по какому URL’у доступен этот функционал? Дело в том, что определив метод с префиксом action_ , вы дали понять сервису, что это обработчик URL’а Т.е. в нашем случае это будет что-то вроде https://localhost/ping/
Символы подчёркивания в названии метода меняются на — . Т.е. метод action_hello_world будет доступен по URL’у https://localhost/hello-world/
Погнали дальше.
Точно так же как для приложений с GUI неплохо бы использовать MVC (или другой паттерн с разделением визуальной составляющей и логики), так же и в микросервисе. Вещи, которые могут быть разнесены, лучше разнести.
Т.е. в нашем случае пусть класс сервиса продолжает выполнять утилитарные функции по инициализации сервиса и запуску нужного обработчика, а логику вынесем в отдельный класс. Для этого доработаем наш код следующим образом:
class TodoLogic extends ServiceBaseLogic
{
/* First endpoint */
public function action_ping() {
return (’I am alive!’);
}
}
class TodoService extends ServiceBase {
}
Service::start(’TodoService’, ’TodoLogic’);
Тут у нас появился класс с логикой
class TodoLogic extends ServiceBaseLogic
Унаследованные от базового класса ServiceBaseLogic (в нём минимум функций, так что позже подробно рассмотрим его).
Класс TodoService перестал имплементировать интерфейс ServiceBaseLogicInterface (на самом деле он никуда не делся, просто его теперь имплементирует класс ServiceBaseLogic).
После выноса логики, класс TodoService получился пустым и его можно безболезненно выпилить, сократив код ещё больше:
class TodoLogic extends ServiceBaseLogic {
/* First endpoint */
public function action_ping() {
return (’I am alive!’);
}
}
Service::start(’ServiceBase’, ’TodoLogic’);
Здесь уже за старт сервиса отвечает класс ServiceBase а не наш.
Погнали ещё дальше.
В процессе использования своего фреймворка у меня в определённый момент стали получаться классы с логикой монструозного размера. Что с одной стороны раздражало моё чувство прекрасного, с другой стороны Sonar возмущался, с третьей стороны концепцию разделения методов на методы чтения и методы записи (см. CQRS) не понятно было как реализовывать.
Поэтому в определённый момент появилась возможность группировать обработчики эндпоинтов по тому или иному признаку по разным классам логики, и при необходимости либо запускать их в рамках одного сервиса, либо безболезненно разносить по разным.
Т.е. можно либо в рамках одного сервиса сделать всю CRUD логику. А можно разделить на два сервиса:
— один предоставляет методы чтения;
— а другой предоставляет методы модификации данных.
Давайте теперь в наш пример добавим метод создания сущности и метод получения списка сущностей:
class TodoSystemLogic extends ServiceBaseLogic {
public function action_ping() {
return (’I am alive!’);
}
}
/* Read logic implementation */
class TodoReadLogic extends ServiceBaseLogic {
public function action_list() {
return (’List!’);
}
}
/* Write logic implementation */
class TodoWriteLogic extends ServiceBaseLogic {
public function action_create() {
return (’Done!’);
}
}
Service::start(’ServiceBase’, [
’TodoSystemLogic’,
’TodoReadLogic’,
’TodoWriteLogic’
]);
Коснёмся только изменений:
— появились классы TodoSystemLogic (системные методы), TodoReadLogic (методы чтения), TodoWriteLogic (методы записи);
— при запуске сервиса передаём не один класс с логикой, а несколько.
Вот собственно и всё на сегодня. Другие возможности фреймворка я рассмотрю в следующих статьях. Их много. А пока можете сами посмотреть, что там есть интересного: https://github.com/alexdodonov/mezon