Общие сведения
Тестовое задание для Бекенд(далее ТЗ) является универсальным тестом на способность писать JSON API проекты на любом стеке технологий. Позволяет дифференцировать оценку результата в широких пределах.
ТЗ глобально состоит из двух частей:
- Endpoint выборки статистики - далее п.1. ТЗ.
- Endpoint "покупки" конкретного количества книг в конкретном магазине - далее п.2. ТЗ.
При проверке ТЗ следует обратить пристальное внимание в т.ч. и на полное соответствие результата выполнения самому ТЗ.
Некоторые кандидаты идут на упрощение условий, например "продают" не заданное кол-во книг, а "не больше" заданного, что существенно упрощает реализацию. И т.д. Это не позволяет проверить часть чеклиста.
В более узком контексте мы проверяем качество решения при следующих ограничениях:
- РСУБД.
- RESTful JSON API.
- MVC (или близкий) фреймворк.
- Тестирование.
Каждый пункт чеклистов подразумевает обоснование выбора/решения/т.п. В случае выбора, нужно привести список достоинств и недостатков. Если недостатков нет, скорее всего у кандидата нет опыта использования того, что он выбрал.
РСУБД
Фундаментальным моментом в решении ТЗ является моделирование данных в БД. Оно может как привести к полной невозможности реализовать ТЗ, так и существенно повлиять на код реализации.
Чеклист(для РСУБД):
- Выбор СУБД: тип(РСУБД, NoSQL, etc) и модель(PostgreSQL, MySQL, MSSQL, MongoDB, etc).
- Моделирование таблиц и связей между ними.
- Наличие Foreign Key constraints и индексов для всех внешних ключей таблиц. Отсутствие - тяжелая ошибка для middle+.
- Наличие NOT NULL constraints и unique индексов для соответствующих полей.
- Наличие индексов, необходимых для выборок и сортировок из п.1 ТЗ.
- Соответствие типов столбцов и ключей хранимым в них данным.
- Использование механизма миграции схемы данных БД (часто есть в фреймворках "из коробки").
- Использование механизма инициализации схемы данных БД с 0 (часто есть в фреймворках "из коробки").
- Использование механизма инициализации данных БД (часто есть в фреймворках "из коробки").
RESTful JSON API
Подавляющее количество наших API мы реализуем в этом формате.
RESTful часть накладывает ограничения на именование ендпоинтов и HTTP глаголы.
JSON часть - на HTTP параметры и тело запросов и ответов.
Использование более продвинутого формата тела ответа JSON:API приветствуется.
Кандидат может использовать другие подходы (RPC, JSON-RPC, GraphQL), если
- продемонстрирует уверенное владение стандартным RESTful JSON API,
- обоснует свой выбор,
- продемонстрирует уверенное решение проблем, присущих его выбору.
Чеклист(для RESTful JSON API):
- В RESTful endpoint - это ресурс, который совсем необязательно называется как модель. Соответственно для п.1. ТЗ
api/books<api/publishers/:id<api/publishers/:id/books<<api/stats<api/publishers/:id/statistics. - В RESTful endpoint - имя существительное. Соответственно для п.2. ТЗ POST
api/books/:id/sell- плохо, POSTapi/order, POSTapi/shops/:id/books/:id/order, PUTapi/shops/:id/books/:id/copies- отлично. - В RESTful использование HTTP глаголов строго регламентировано. П.1. ТЗ - это GET endpoint, п.2. ТЗ - POST или PUT в зависимости от дизайна ресурса.
- Если фреймворк имеет DSL для роутинга и стандартные имена для HTTP глаголов(
resource(s)иactionsв RoR), то должны использоваться они, а не "ручное" описание роутинга с нестандартными действиями. - Выбор/сравнение с аналогами библиотеки (де)сериализации данных в JSON.
- Соответствие структуры JSON таковому из ТЗ(либо доказательство эквивалентности в случае выбора JSON:API).
- Формат данных для первичных/внешних ключей БД, если они более 2^53.
- Неймспейс
api/и версионирование, вопреки распространенной практике, считаю необязательными по опыту многочисленных реальных аутсорс проектов. Часто их лепят, потому что "так принято". Если кандидат сможет действительно доказать, что он может поддерживать версионирование API - большой плюс.
MVC (или близкий) фреймворк
Фреймворк "из коробки" предлагает набор абстракций вроде Model(доступ к данным), View(представление данных) и Controller(управление потоком запрос-ответ).
Как показывает опыт, нужен еще как минимум слой для бизнес логики, а то и для валидации, и авторизации и т.д.
Недопустимо размазывание и даже исключительное помещение бизнес-логики между/в один из слоев MVC.
Очень важно не только понимание, но и практическое следование этому правилу у всех кандидатов от уровня middle.
Нарушать его можно разве что в нано-проектах, где всегда "максимум 2.5 endpoint".
View
View в JSON API принято называть serializers. Существует множество библиотек(как входящих в фреймворки, так и отдельных) с разными подходами к описанию маппинга данных на JSON представление.
Нельзя путать их с низкоуровневыми библиотеками сериализации Hash структур в строки(в терминах RoR библиотека Oj - это не serializer, в отличие от библиотеки AMS).
Чеклист:
- Выбор библиотеки сериализации.
- Сравнение с альтернативами(2, а лучше 3+): скорость, функционал, проблемы.
- Использование только стандартных для библиотеки именований классов/методов и dsl маппинга.
- Ничего сверх dsl маппинга и приватных методов в случае, если он сложный.
Controller
Чеклист:
- Роутинг (плохо, но значительная часть фреймворков "размазывает" роутинг по контроллерам).
- Использование только стандартных для фреймворка именований классов/методов (отсутствие нестандартных
actionsв терминах RoR). - Валидация входных параметров (непосредственно или через слой валидации)
- Получение/изменение данных БД через слой бизнес логики, где сами данные из слоя Model
- Сериализация данных с помощью слоя View.
- Ничего сверх вышеперечисленного.
Model
Этот слой может быть стандартным из фреймворка, либо быть реализован сторонней библиотекой.
Чеклист:
- Выбор между шаблонами ActiveRecord/Data Mapper/Repository и т.п. и библиотеками их реализующими (если только он есть - например, RoR навязывает ActiveRecord).
- Использование только стандартных для библиотеки именований классов/методов и dsl.
- Описание структуры данных, связей между моделями, константы.
- Описание "базовых" выборок, зависящих от констант, например "выбрать объекты, чей статус равен константе".
- Ничего сверх вышеперечисленного.
Слой бизнес логики
Часто реализуются в виде сервисных классов.
В свою очередь, этот слой в реальных приложениях разделяется на слои валидации(forms), аутентификации(policies), выборки данных(queries) и т.д. - чем больше и сложнее приложение, тем больше слоев.
Чеклист(сервисные классы - общий):
- Хранится отдельно от остальных слоев.
- Единый интерфейс(по возможности).
- Интерфейс имеет какие-то преимущества в разрезе языка программирования(instace call() для Ruby).
- Не нарушен SRP.
- Опционально Dependency Injection.
Чеклист(сервисные классы - п.1. ТЗ):
- Выборка реализована верно согласно ТЗ.
- Выборка реализована за 1, максимум 2 запроса к БД.
- N+1 выборка - тяжелая ошибка для middle+.
- Выборка использует dsl из слоя Model и минимум SQL. В идеале - без SQL (если позволяет слой Model, если нет - вернуться к п.1. из чеклиста Model).
Чеклист(сервисные классы - п.2. ТЗ):
- Продается ровно заданное кол-во заданных книг в заданном магазине либо ни одной.
- Разные ответы клиенту в случаях когда продано успешно и когда не продано ничего.
- Согласованное изменение данных (транзакционность, в случае если модель данных подразумевает изменение более 1 записи 1 таблицы).
- Логическая устойчивость к параллельным запросам - почти всегда это блокировки уровня БД (SELECT ... FOR UPDATE like запросы). Гении выкручиваются хитрыми структурами данных и/или нестандартными уровнями изоляции транзакций - требовать доказательств, что это работает.
- Понимание потенциальных проблем с предыдущим пунктом (deadlocks, снижение производительности).
Тестирование
Кандидат должен мочь продемонстрировать знание нижеперечисленного, наличие всего-всего - необязательно.
Чеклист:
- Выбор библиотеки/фреймворка для тестирования.
- Изолированность и случайный порядок прохождения тестов(часто есть "из коробки").
- Использование фабрик/фикстур/Faker-like библиотек.
- Unit тесты.
- Интеграционные тесты.
- Тест на формат JSON ответа.
- Качество тестов(многие фреймворки автоматически генерируют множество малополезных dsl тестов). Если есть огрехи в выполнении ТЗ и есть на это "зеленые" тесты - тяжелая ошибка middle+.
- Качество кода тестов: читаемость, минимализм, DRY (многие фреймворки содержат идиомы вроде
shared_contextиshared_examplesиз RSpec). - Скорость тестов.