![Specialty-кофейни на Кипре Specialty-кофейни на Кипре](https://habrastorage.org/getpro/habr/upload_files/174/371/fd4/174371fd4dbfe2957ba262dcb5f29839.png)
Недавно выдалось свободное время и я сделал простой проект про specialty-кофейни на Кипре: сайт и телеграм-бот по всем канонам «большой» разработки. Люблю хороший кофе ?
Делюсь своим процессом разработки и рекомендациями как сделать всё задуманное без потери времени.
Update
Как и планировал, добавил тесты. Сначала swagger-php генерит openapi.yaml на основе аттрибутов кода, а затем spectator проверяет ответы API на совпадение с openapi-спецификацией. Популярный L5-Swagger в данном случае избыточен, т.к. основан на том же swagger-php с добавкой Swagger UI.
Цели проекта изначально простые:
-
карта кофеен на сайте
-
просмотр подробностей о кофейне
-
переход в большую карту или приложение Google Maps для маршрута, отзывов и т.д.
-
список кофеен в боте
-
поиск кофейни по названию в боте
-
поиск ближайшей кофейни в боте
-
случайная кофейня в боте
-
всё это в минимальном и понятном стиле
Кроме того — не уходить в перфекционизм и бесконечную разработку.
Вот это, на самом деле, очень сложно, потому что хочется сделать лучше, чем у других, обвеситься бэйджиками всевозможных линтеров и выразить в коде все свои таланты. Хотя достаточно, чтобы сервис просто работал, метрики считались, а ошибки логгировались. Это стоит тех самых 20% усилий, которые принесут 80% результата.
Поэтому декомпозируем всё на мелкие задачи, выкидываем всё длиннее нескольких часов и то, что невозможно оценить.
Честно говоря, я срывался несколько раз: первый раз зацепился за идею запустить сервер Caddy без конфига из консоли примерно caddy php_fastcgi 9000
, но увы, так можно запускать только обратный прокси и/или файл-сервер; потом ещё добавились хитрые редиректы; в общем, убил на это 2 дня.
Ещё день пришлось потратить на неудачно выбранную библиотеку для Telegram-бота.
В целом всё удалось, код проекта открыт, велкам в пул-реквесты. Адрес сайта — в конце статьи, чтобы меньше походило на рекламу.
Для достижения целей было решено реализовать REST API микросервис, фронтэнд-сайт (подробнее во второй части) и бэкэнд для бота (подробнее в третьей части). Деплой — на что-нибудь современное управляемое с free tier, а не VPS или shared-хостинг. Бот вообще хорошо ложится на идеологию Serverless/FaaS.
Первым шагом я зарегистрировал домен: 20 евро из собственного кармана — хорошая мотивация не потратить их впустую реализовать задуманное.
К слову про регистрацию: сложно предугадать, как сложится судьба проекта: возможно, получится выгодно продать, а, возможно, захочется избавиться от него без следов. Поэтому лучше регистрировать все внешние сервисы на отдельные аккаунты: почта, домен, хостинг, аналитика, мониторинг и т.д. Дополнительно получится использовать free tier’ы и триалы.
REST API микросервис
У меня есть хороший опыт работы с Laravel и Symfony, поэтому для быстрой реализации я выбрал знакомую и лёгкую технологию. Потом обязательно перепишу на Go. А использование свежей версии PHP 8.1 позволило написать чуть меньше кода и получить чуть выше производительность. Promoted properties, readonly и строгая типизация сильно облегчают разработку.
Для бОльшего облегчения из Laravel удалены неиспользуемые пакеты и сервисы: получился почти Lumen.
В composer.json можно отметить пакеты как «установленные» и они не будут устанавливаться на самом деле. Очень удобно для выпиливания избыточных полифилов, например, так:
"replace": { "symfony/polyfill-ctype": "*", "symfony/polyfill-iconv": "*", "symfony/polyfill-intl-grapheme": "*", "symfony/polyfill-intl-idn": "*", "symfony/polyfill-mbstring": "*", "symfony/polyfill-php72": "*", "symfony/polyfill-php73": "*", "symfony/polyfill-php80": "*", "symfony/polyfill-php81": "*", "dragonmantank/cron-expression": "*", "egulias/email-validator": "*", "league/commonmark": "*", "league/flysystem": "*", "symfony/mime": "*", "symfony/var-dumper": "*", "tijsverkoyen/css-to-inline-styles": "*" }
Ещё можно отключить platform-check, чтобы не проверять версию PHP при каждом запросе, и ограничиться проверкой при установке пакетов. Также полезно включить classmap-authoritative, чтобы классы загружались только из созданной composer’ом карты, а не из каждого use, но это будет мешать разработке, поэтому достаточно включить при деплое.
Итоговый composer.json и config/app.php. На подобную оптимизацию ушло менее часа, поэтому ok. Но более глубокая оптимизация потребует гораздо больше времени, так что не сейчас.
Архитектура
Сервис построен на single-action контролерах, которые выбирают данные из моделей. Репозитория нет, поскольку считаю его избыточным для простейших запросов без дополнительной логики.
Входные данные валидируются отдельными Request’ами, выходные — заворачиваются в GeoJson Resourse’ы. Один класс — одна ответственность.
На момент разработки фронтэнда был всего один endpoint /cafes со списком всех кофеен: это позволило быстро запустить API и не мокать его для других частей проекта. Во время разработки бота я добавил ещё несколько endpoint’ов.
БД
В качестве БД для начала используется SQLite — это позволило не тратить время на развёртывание классических MySQL/PostgreSQL. Более того, я уверен, что при нагрузке в 100 посещений в день и нескольких десятках или сотнях записей в таблицах, SQLite — отличное решение для микропродакшена.
Сидирование данными производится из обычного массива в database/seeders/CafeSeeder.php в процессе деплоя. В дальнейшем планирую сделать 1-2 консольные команды для редактирования данных, потому что гораздо быстрее, чем любая визуальная админка.
Поиск
API умеет в полнотекстовый поиск благодаря Scout c драйвером «collection«: он позволяет искать по каждому полю модели обычным «LIKE %smth%» запросом и не требует полнотекстовых индексов в БД. Подключение заняло 15 минут, поэтому ok.
Статика
В сервисе есть немного обязательных статических файлов:
-
robots.txt с запретом индексации
-
favicon.ico, который любят многие сервисы
-
humans.txt
-
и т.д.
Тесты
В качестве отдельных Feature-тестов используются http-request’ы в PHPStorm по всем endpoint’ам с правильными и ошибочными данными. Проверка — глазами, но в CI не положишь ¯\_(ツ)_/¯
Написание нормальных тестов — первоочередная задача и я планирую использовать для этого Pest https://pestphp.com/.
Конфигурация
Laravel, в отличии от Symfony, не читает конфигрурацию из файла .env.local чтобы переопределить/дополнить конфигурацию из .env, да и вообще не рекомендует хранить .env в репозитарии. Это хороший подход, но он не очень удобен, когда параметров конфигурации много.
Можно сделать чуть иначе: записать локальные параметры в .env (и не добавлять его в репозиторий), а все боевые параметры и названия параметров-секретов — в .env.production и добавить его в репозиторий. Параметр APP_ENV=production, а также сами секреты необходимо записать средствами хостинга и/или деплоя.
В таком случае .env.local заменит (не дополнит!) конфигурацию из .env.production, а перечисление всех используемых параметров (даже без значений) в .env.production упростит понимание проекта. Бесполезный, в данном случае, .env.example удаляем.
Мониторинг
По окончании первого этапа разработки добавляем в проект Sentry: в .env.production достаточно указать пустое значение SENTRY_LARAVEL_DSN (для наглядности), а фактическое значение записать в секрет.
Деплой
Для размещения сервера используется платформа Fly.io с управляемыми microVM Firecracker. В отличии от популярного Heroku, никогда не спит, имеет хороший free tier и позволяет разместить как статику, так и любой сервер приложений. Кроме того, есть различные стратегии деплоя и отката изменений, health check’и и география размещения на выбор.
Настроить среду выполнения можно автоматически командой flyctl launch из каталога приложения — сервис сам определелит необходимые компоненты и соберёт конфиг fly.toml и Dockerfile. Либо можно написать свои конфиг и Dockerfile.
У меня уже был Dockerfile для подобных проектов, поэтому использовал его. Бонусом получилось запустить все сервисы под непривилегированным пользователем.
В относительно свежих версиях Docker реализовано кеширование слоёв, поэтому нет смысла писать все инструкции в одной команде RUN. Наоборот, лучше размещать «тонкие» слои RUN и COPY в порядке увеличения частоты изменений данных в них.
Например, дистрибутивы и пакеты ОС меняются редко, поэтому слой RUN apk add ...
может быть в самом начале Dockerfile.
Пакеты composer обновляются чаще, но не так часто как исходный код проекта, поэтому слои COPY composer.* .
и RUN composer install --no-autoloader --no-dev --no-interaction --no-scripts
могут быть указаны в середине Dockerfile и смогут браться из кеша.
Ну а COPY --chown=www-data:www-data . .
, RUN composer dump-autoload --classmap-authoritative --no-interaction
и возможные другие команды, затрагивающие исходный код проекта могут быть в самом конце и будут выполняться, только если изменится сам код проекта, а не пакеты ОС или зависимости composer.
Итоговый Dockerfile.
Внутри контейнера находится классический PHP-FPM на Alpine и проксирующий сервер Caddy. Он чуть легче и проще в настройке, чем привычный Nginx и состоит из одного бинарника и одного необязательного конфига.
{ admin off auto_https off log skip_install_trust } {$APP_URL_INSECURE}:8080 { root * /var/www/html/public php_fastcgi 127.0.0.1:9000 file_server encode gzip header -Server } :8080 { respond 404 header -Server }
Тут «file_server» отвечает за раздачу статики.
Платформа Fly.io сама терминирует https и управляет сертификатами, поэтому приложению в контейнере достаточно обрабатывать обычный http-трафик.
Главный минус при размещении PHP-приложений — необходимость использования прокси-сервера или балансера, их совместный запуск и работа. В данном случае пришлось использовать достаточно тяжёлый supervisor, который тянет за собой Python. Но это быстрое и работающее решение, позволяющее не застревать в разработке и настройке.
Итоговый supervisord.conf
CI/CD
Тут всё просто: Github Action из одного workflow и тот же самый flyctl.
Благодарности
Спасибо @kvasilenkoза код-ревью проекта.
На этом этапе микросервис API работает, размещён в production-окружении и доступен всем пользователям. План-минимум выполнен 🙂
Репозиторий API, сайт https://specialtycoffee.cy
Во второй части расскажу про создание фронтэнда и в третьей — про телеграм-бота.
ссылка на оригинал статьи https://habr.com/ru/articles/677288/
Добавить комментарий