Когда я начинал его делать, то ставил целью сделать решение, которое:
- можно легко встраивать в уже существующие проекты;
- быстро можно создать хоть что-то работающее;
- максимально лаконичные и выразительные конструкции;
- разумно использовал возможности современного PHP.
Итак, с чего можно начать? Конечно же с исходников! Посмотреть их можно на github
Ну и чтобы не вдаваться в пространные рассуждения давайте сразу начнём с рабочего примера.
Первым делом нам понадобится .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_<name-part>, вы дали понять сервису, что это обработчик URL’а <name-part> Т.е. в нашем случае это будет что-то вроде localhost/ping
Символы подчёркивания в названии метода меняются на — . Т.е. метод action_hello_world будет доступен по URL’у 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 (методы записи);
- при запуске сервиса передаём не один класс с логикой, а несколько.
Вот собственно и всё на сегодня. Другие возможности фреймворка я рассмотрю в следующих статьях. Их много. А пока можете сами посмотреть, что там есть интересного.
Комментарии (22)
AlexLeonov
18.12.2019 20:01+5Поставил плюс за старания. Больше пока ставить не за что.
- Оформите, как пакет composer, укажите все зависимости, в том числе версии PHP
- Внимательно читайте PSR и применяйте каждый пункт к своему коду
- Настройте автоматический прогон тестов через travis и повесьте бейджик
- Узнайте, что такое «автозагрузка» и почему не нужно писать бесконечные require_once
Я мог бы продолжить этот список и дальше, но на ближайшие месяцы вам хватит.
vlreshet
18.12.2019 20:11+1Каждый уважающий себя разработчик должен построить свой велосипед, это нормально) Но вот с публикацией вы поторопились, советую спрятать в черновики.
Psih
18.12.2019 20:17+4Привязка к Apache и .htaccess — плохо. На nginx придётся допиливать, т.к. стандартный try_files не отработает из-за необходимости специфичной rewrite rule. Не надо так делать.
SDKiller
18.12.2019 21:40Первым делом нам понадобится .htaccess...
Дальше даже читать не стал
mikechips
17.12.2019 23:17А что, у вас для Апача есть много других вариантов? Не все пользуются nginx, особенно если речь заходит про шаред-хостинги
index0h
18.12.2019 00:50+1Помимо PSR-ов, composer-ов, да и просто хороших практик (https://github.com/index0h/php-conventions):
- Роутинг на основании имен методов — это дико хреновая идея. Что если мне нужен экшн с GET: /user/{userName}/profile/articles/{articleId}/comments/{headCommentId} при этом выражения внутри скобок отвечают неким регуляркам, и еще один экшн POST с таким же путем ?
- Юзайте суперглобальные переменные только для создания некого Request объекта в самом начале выполнения.
- Ваше разбиение на модули в вендоре не имеет смысла, пример: application не может без конкретной реализации router, хотя router в другом пакете. Причем не приколочен гвоздями через автозагрузку, а наглухо приварен require.
- Тут на конце 2019 есть namespace, категорически рекомендую.
vlreshet
18.12.2019 02:02github.com/index0h/php-conventions
Не вижу форка ниоткуда — это ваш собственный набор?
ghost404
18.12.2019 11:39раз такое дело, оставлю здесь на почитать про Чистый код
https://github.com/peter-gribanov/clean-code-php
urands
18.12.2019 02:07Одобряю, но хотелось бы узнать что у данного фреймворка с авторизацией и сессиями? Беглый взгляд по коду их не нашёл.
xEpozZ
18.12.2019 09:55Микросервисы на пхп? Может просто допилим архитектуру проекта и все?
Композер, глобальные переменные, тесты, роутинг. Изучайте
PaulZi
Стандартный комментарий про тесты, composer и
20192020 год.gdever Автор
А что не так с тестами? Они есть. По папкам /tests разложены.