DankeMary / how-to-develop-perfect-crud

👨‍💻 Сборник хороших практик по разработке и оформлению типичного Backend приложения

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to develop perfect CRUD

Что это?

Данная статья - попытка собрать воедино все хорошие практки которые полезно знать и применять при разработке бэкэнд приложения.

Её можно использовать его как полноценный чеклист который будет вам полезен, если вы:

  • Начинаете новый проект с нуля и сразу хотите заручиться набором хороших практик.
  • Получили тестовое задание и всерьез настроены реализовать его и доказать чего стоите.

Здесь только про Backend?

Так как автор имеет экспертизу только в бэкэнде то и большая часть советов будет относиться к разработке бэкэнд приложений. В прочем многие из советов будут полезны и для других специальностей.

Слишком много всего, это точно нужно?

Чем больше из всего набора фич и практик вы реализуете тем более качественным будет результат. Реализовать все может быть избыточно и потребовать много времени, рассчитайте время и силы.

Table of Contents

Repository

  • Код должен храниться в публичном/приватном Git репозитории (Github / Gitlab / Bitbucket)
  • README должен содержать информацию о проекте, инструменты и технологии, инструкцию по настройке и запуску приложения
  • Должен быть настроен Continuous Integration (Gitlab CI / Github Actions)
    • Интегрируй прогон тестов в CI для каждой фича ветки и мастер ветки
    • Интегрируй прогон линтера в CI для каждой фича ветки и мастер ветки.
  • Будет огромным плюсом если будет настроен Continuous Delivery - деплой приложения на сервер
  • Осмысленная история коммитов. Можно использовать практику Conventional commits
  • Используйте feature branches, pull requests
  • Необязательно: настроенный dependabot

Code Style

Перед разработкой приложения:

  • Настроен редактор или IDE:
    • VS Code
    • Visual Studio
    • PyCharm
    • IDEA
    • Vim, emacs
  • Установлен EditorConfig плагин для твоего редактора
  • Установлены наиболее популярные инструменты по верификации качества кода, например
    • Rubocop for Ruby
    • Pylint/Black/PEP8 for Python

Tests

  • Установлены библиотеки для написания тестов различных видов (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)

Infrastructure around Code

  • Установлены Docker и docker-compose
  • В репозитории есть Dockefile с помощью которого можно собрать приложение в Docker Container (как это делать правильно)
  • Все зависимости приложения (PostgreSQL, S3, Redis, Kafka, RabbitMQ) описаны в docker-compose.yml
  • Настройка приложения и запуск должны делаться максимально просто и прозрачно (желательно в 1 команду)

Configuration

API Design

  • API должен быть описан по REST (если не требуется иначе)
  • Должна быть настроена Swagger спецификация которую можно открыть в браузере
  • Формат данных: JSON (если не требуется другого)
  • API должен иметь настроенные таймауты на запросы.

Authorization & Authentication

Аутентификация – процедура проверки подлинности, например, проверка подлинности пользователя путем сравнения введенного им пароля с паролем, сохраненным в базе данных.

В качестве аутентификации по API можно использовать:

  • HTTP Basic Auth (простой путь)
  • JSON Web Tokens (посложнее)

Не нужно реализовывать аутентификацию самостоятельно! Используйте готовые библиотеки.

Авторизация – предоставление определенному лицу прав на выполнение определенных действий. Например: пользователь которого забанил администратор не может публиковать комменты (хотя он прошел аутентификацию на сайте).

MVC Explanation

Цель: разделить обязанности в коде между компонентами:

Controller

  • Принимает тело запроса, валидирует его на соответствие API
  • Проверяет authorization + authentification
  • Вызывает Service, передает ему данные
  • На основе возвращаемого значения от Service вызывает код формирующий нужный ответ API (через View)

Model

  • Хранит только описание схемы данных и связи с другими моделями
  • Бизнес логики хранит по минимуму а лучше не хранит вообще
  • Используется для того чтобы делать запросы к БД на чтение и запись

Service

  • Принимает данные от контроллера, валидирует тело
  • Использует Model для чтения или записи данных в БД.
  • Отвечает за бизнес-логику приложения

View

  • Отвечает за то чтобы на основе данных сформировать API ответ.

CRUD: Validations

Перед тем как сохранять данные в БД обязательно:

  • отвалидируйте данные на тип (там где ожидается строка пришла строка, где int там int итп)
  • и соответствие тела запроса API (если пользователь отправил поля которые не имеет права отпралять в БД мы должны их игнорировать)

CRUD: Database

  • Всегда используйте ORM (это проще и безопаснее) если в задании не указано что нужно писать чистый SQL
  • Используйте механизм миграций чтобы создавать таблицы и другие сущности в вашей БД (Rails Migrations, Flask-Migrate, etc)
  • При описании таблиц важно сразу указать всем столбцам необходимые constraints (NULLABLE, DEFAULT VALUE, UNIQUE, PRIMARY KEY)
  • При описании таблиц важно сразу указать индексы для столбцов по которым ожидается поиск.
  • Для защиты API от перебора можно использовать как PRIMARY KEY uuid вместо serial

P.S. При описании миграций полезно подсматривать сюда, чтобы не написать миграцию которая может заблокировать БД.

CRUD: Operations

LIST (HTTP GET)

  • Для каждого ресурса в ответе должно присутствовать ID.
  • Ресурсы должны быть отсортированными по какому либо признаку, например по времени создания.
  • API должен поддерживать пагинацию (чтобы не возвращать все сущности из БД за раз) Разбор вариантов пагинации
  • Количество запросов к БД в рамках запроса должно быть фиксированным (Отсутствует N+1 проблема)

API не должно возвращать все поля модели. Пример: если наше API возвращает список постов то оно должно возвращать:

  • ID
  • Название поста
  • Имя автора
  • Первые несколько предложений статьи (превью)

Полный текст поста для этого эндпоинта не нужен.

READ (HTTP GET)

  • Возвращаем полностью ресурс со всеми полями, ничего особенного

CREATE (HTTP POST)

  • Валидируем данные на предмет полей которые пользователь не имеет права изменять в БД а следовательно передавать.
  • Делаем в БД INSERT
  • Возвращаем в ответ ID и содержимое.

Задачи со звездочкой:

  • Убедиться что реализованное API идемпотентно: Подробнее
  • Настроить Rate Limiter чтобы защитить БД от спама и мусора

UPDATE (HTTP PUT/PATCH)

  • Разобраться в чем отличие между PUT и PATCH в HTTP
  • Реализовать обновление согласно выбранному методу
  • Валидировать тело запроса на предмет полей которые пользователь не имеет права изменять в БД а следовательно передавать.
  • Проверка права на редактирование у пользователя
    • Например API не должно позволять пользователю редактировать чужие комментарии :)

DESTROY (HTTP DELETE)

  • Реализовать удаление предварительно проверив наличие сущности в БД и права на удаление у пользователя

Дополнительно: реализовать soft удаление (скрываем от пользователя, оставляем в БД)

External API Calls, Long-running tasks

Если требуется в рамках API выполнять запросы к внешним системам или генерировать отчеты/выполнять долгие запросы к БД то стоит подумать о том чтобы делать эти операции за пределами HTTP запроса. Для этого может понадобиться очередь, например:

  • Celery for Python
  • Sidekiq for Ruby

Logs and Metrics

WIP: Security

WIP: Cache

WIP: Full Text Search

About

👨‍💻 Сборник хороших практик по разработке и оформлению типичного Backend приложения