Данная статья - попытка собрать воедино все хорошие практки которые полезно знать и применять при разработке бэкэнд приложения.
Её можно использовать его как полноценный чеклист который будет вам полезен, если вы:
- Начинаете новый проект с нуля и сразу хотите заручиться набором хороших практик.
- Получили тестовое задание и всерьез настроены реализовать его и доказать чего стоите.
Так как автор имеет экспертизу только в бэкэнде то и большая часть советов будет относиться к разработке бэкэнд приложений. В прочем многие из советов будут полезны и для других специальностей.
Чем больше из всего набора фич и практик вы реализуете тем более качественным будет результат. Реализовать все может быть избыточно и потребовать много времени, рассчитайте время и силы.
- Repository
- Code Style
- Tests
- Infrastructure around Code
- Configuration
- API Design
- Authorization & Authentication
- MVC Explanation
- CRUD: Validations
- CRUD: Database
- CRUD: Operations
- External API Calls, Long-running tasks
- Logs and Metrics
- WIP: Cache
- WIP: Full Text Search
- Код должен храниться в публичном/приватном Git репозитории (Github / Gitlab / Bitbucket)
- README должен содержать информацию о проекте, инструменты и технологии, инструкцию по настройке и запуску приложения
- Должен быть настроен Continuous Integration (Gitlab CI / Github Actions)
- Интегрируй прогон тестов в CI для каждой фича ветки и мастер ветки
- Интегрируй прогон линтера в CI для каждой фича ветки и мастер ветки.
- Будет огромным плюсом если будет настроен Continuous Delivery - деплой приложения на сервер
- Осмысленная история коммитов. Можно использовать практику Conventional commits
- Используйте feature branches, pull requests
- Необязательно: настроенный dependabot
Перед разработкой приложения:
- Настроен редактор или IDE:
- VS Code
- Visual Studio
- PyCharm
- IDEA
- Vim, emacs
- Установлен EditorConfig плагин для твоего редактора
- Установлены наиболее популярные инструменты по верификации качества кода, например
- Rubocop for Ruby
- Pylint/Black/PEP8 for Python
- Установлены библиотеки для написания тестов различных видов (unit, integration). Например:
- Pytest for Python
- RSpec for Ruby
- Testify, testcontainers for Golang
- После прогона тестов автоматически считается test coverage
- Старайтесь покрывать ваш код по пирамиде тестирования, так чтобы было больше всего было unit-тестов, меньше интеграционных тестов и при необходимости были end-to-end тесты. Обратите внимание, что для тестов разного уровня могут использоваться разные инструменты. Для end to end тестирования можно использовать Selenium или Cypress. Для интеграционных удобно использовать
testcontainers
- Пишите unit-тесты по паттерну AAA (Arange Act Assert)
- Установлены Docker и docker-compose
- В репозитории есть Dockefile с помощью которого можно собрать приложение в Docker Container (как это делать правильно)
- Все зависимости приложения (
PostgreSQL
,S3
,Redis
,Kafka
,RabbitMQ
) описаны вdocker-compose.yml
- Настройка приложения и запуск должны делаться максимально просто и прозрачно (желательно в 1 команду)
- Приложение должно иметь несколько окружений (development, prod, test)
- Настроен application сервер для production сборки приложения:
- Puma for Ruby
- Gunicorn3 for Python
- Undertow for Java
- При описании конфигурации приложения используйте советы из https://12factor.net
- API должен быть описан по REST (если не требуется иначе)
- Должна быть настроена Swagger спецификация которую можно открыть в браузере
- Формат данных: JSON (если не требуется другого)
- API должен иметь настроенные таймауты на запросы.
Аутентификация – процедура проверки подлинности, например, проверка подлинности пользователя путем сравнения введенного им пароля с паролем, сохраненным в базе данных.
В качестве аутентификации по API можно использовать:
- HTTP Basic Auth (простой путь)
- JSON Web Tokens (посложнее)
Не нужно реализовывать аутентификацию самостоятельно! Используйте готовые библиотеки.
Авторизация – предоставление определенному лицу прав на выполнение определенных действий. Например: пользователь которого забанил администратор не может публиковать комменты (хотя он прошел аутентификацию на сайте).
Цель: разделить обязанности в коде между компонентами:
- Принимает тело запроса, валидирует его на соответствие API
- Проверяет authorization + authentification
- Вызывает Service, передает ему данные
- На основе возвращаемого значения от Service вызывает код формирующий нужный ответ API (через View)
- Хранит только описание схемы данных и связи с другими моделями
- Бизнес логики хранит по минимуму а лучше не хранит вообще
- Используется для того чтобы делать запросы к БД на чтение и запись
- Принимает данные от контроллера, валидирует тело
- Использует Model для чтения или записи данных в БД.
- Отвечает за бизнес-логику приложения
- Отвечает за то чтобы на основе данных сформировать API ответ.
Перед тем как сохранять данные в БД обязательно:
- отвалидируйте данные на тип (там где ожидается строка пришла строка, где int там int итп)
- и соответствие тела запроса API (если пользователь отправил поля которые не имеет права отпралять в БД мы должны их игнорировать)
- Всегда используйте ORM (это проще и безопаснее) если в задании не указано что нужно писать чистый SQL
- Используйте механизм миграций чтобы создавать таблицы и другие сущности в вашей БД (Rails Migrations, Flask-Migrate, etc)
- При описании таблиц важно сразу указать всем столбцам необходимые constraints (NULLABLE, DEFAULT VALUE, UNIQUE, PRIMARY KEY)
- При описании таблиц важно сразу указать индексы для столбцов по которым ожидается поиск.
- Для защиты API от перебора можно использовать как PRIMARY KEY
uuid
вместоserial
P.S. При описании миграций полезно подсматривать сюда, чтобы не написать миграцию которая может заблокировать БД.
- Для каждого ресурса в ответе должно присутствовать ID.
- Ресурсы должны быть отсортированными по какому либо признаку, например по времени создания.
- API должен поддерживать пагинацию (чтобы не возвращать все сущности из БД за раз) Разбор вариантов пагинации
- Количество запросов к БД в рамках запроса должно быть фиксированным (Отсутствует N+1 проблема)
API не должно возвращать все поля модели. Пример: если наше API возвращает список постов то оно должно возвращать:
- ID
- Название поста
- Имя автора
- Первые несколько предложений статьи (превью)
Полный текст поста для этого эндпоинта не нужен.
- Возвращаем полностью ресурс со всеми полями, ничего особенного
- Валидируем данные на предмет полей которые пользователь не имеет права изменять в БД а следовательно передавать.
- Делаем в БД INSERT
- Возвращаем в ответ ID и содержимое.
Задачи со звездочкой:
- Убедиться что реализованное API идемпотентно: Подробнее
- Настроить Rate Limiter чтобы защитить БД от спама и мусора
- Разобраться в чем отличие между PUT и PATCH в HTTP
- Реализовать обновление согласно выбранному методу
- Валидировать тело запроса на предмет полей которые пользователь не имеет права изменять в БД а следовательно передавать.
- Проверка права на редактирование у пользователя
- Например API не должно позволять пользователю редактировать чужие комментарии :)
- Реализовать удаление предварительно проверив наличие сущности в БД и права на удаление у пользователя
Дополнительно: реализовать soft удаление (скрываем от пользователя, оставляем в БД)
Если требуется в рамках API выполнять запросы к внешним системам или генерировать отчеты/выполнять долгие запросы к БД то стоит подумать о том чтобы делать эти операции за пределами HTTP запроса. Для этого может понадобиться очередь, например:
- Celery for Python
- Sidekiq for Ruby
- Настроить Prometheus метрики с информацией о состоянии HTTP API и райнтайме приложения. Рекомендуется использовать готовые пакеты, которые собирают метрики о работе приложения по методикам RED (Rate Error Duration) и USE (Utilization Saturation Errors):
- Логи должны писаться только в stdout