Техническое задание. HR-система найма¶
Об этом материале
Статус: версия 1.1. Уточнения выдаются по ходу курса и нумеруются как дополнения. Трассируемость: требования FR, BR, AC и NFR пронумерованы; ссылки в лабораторных идут по номерам. Приёмка: раздел 13. Частично выполненная работа не принимается.
Документ описывает требования к системе, которую вы разрабатываете в течение семестра. Он написан со стороны заказчика: здесь сказано, что система должна делать и каким правилам подчиняться, но не сказано, из каких классов её собрать. Архитектурные решения — ваши, и защищать их будете вы. Требования пронумерованы; в лабораторных и на защите ссылки идут по этим номерам, поэтому нумерацию не меняйте.
1. Назначение и контекст¶
Компания нанимает сотрудников. Сейчас процесс живёт в почте и таблицах: рекрутер получает резюме на общий ящик, пересылает нанимающему менеджеру, тот отвечает «берём на техсобес», и всё это теряется. Никто не может ответить, на каком этапе конкретный кандидат, сколько человек застряло на тестовом задании и почему кандидата отклонили полгода назад.
Система должна стать единственным местом, где живёт процесс найма: от публикации вакансии до выхода человека на работу.
Что считается успехом. Рекрутер за один экран видит всех кандидатов по вакансии с текущими этапами. Любое решение по кандидату зафиксировано: кто, когда, с какой формулировкой. Кандидат в любой момент может посмотреть статус своего отклика, не написав письмо. Ни одно решение нельзя тихо переиграть задним числом.
2. Действующие лица и роли¶
В системе два независимых вида ролей, и путать их нельзя.
2.1. Системные роли — что человеку позволено делать в приложении¶
| Роль | Права |
|---|---|
| Администратор | заводит сотрудников, ведёт справочник профессиональных ролей, назначает роли |
| Рекрутер | создаёт, публикует и закрывает вакансии, настраивает пайплайн и назначения на этапы |
| Сотрудник | базовая роль: видит этапы, на которые назначен, и принимает решения на них |
Системная роль отвечает на вопрос «какие экраны и операции доступны». Она не даёт права принимать решения по конкретному кандидату.
2.2. Профессиональные роли — кто человек в компании¶
Справочник, который ведёт администратор: Сеньор-разработчик, Тимлид, Системный
аналитик, Руководитель подразделения. Роль может быть привязана к подразделению:
«тимлид подразделения Платежи» — это та же роль Тимлид с областью действия Платежи.
Профессиональная роль отвечает на вопрос «кого можно поставить на этап отбора». Именно она позволяет сказать «техническое интервью проводит любой сеньор-разработчик», не перечисляя людей поимённо.
2.3. Внешний участник¶
Кандидат — человек вне компании. Учётной записи не имеет, доступ к своему отклику получает по ссылке с токеном.
Один сотрудник может иметь несколько системных и несколько профессиональных ролей одновременно. Право принять решение по конкретной кандидатуре не даётся ни одной из них напрямую — оно вытекает из назначения на этап (раздел 8).
3. Глоссарий предметной области¶
Термины этого раздела обязательны к использованию в коде, тестах и документации. Если в коде появляется сущность, которой нет в глоссарии, — либо она лишняя, либо глоссарий надо дополнить и защитить дополнение.
| Термин | Значение |
|---|---|
| Вакансия | Открытая позиция в компании, на которую идёт набор |
| Пайплайн | Упорядоченный набор этапов отбора, заданный для конкретной вакансии |
| Этап | Одна ступень отбора: скрининг, тестовое задание, интервью, оффер |
| Кандидат | Человек, откликнувшийся хотя бы на одну вакансию |
| Отклик | Действие кандидата: заявка на конкретную вакансию |
| Кандидатура | Продвижение конкретного кандидата по конкретной вакансии |
| Продвижение | Перевод кандидатуры на следующий этап пайплайна |
| Отклонение | Решение прекратить рассмотрение кандидатуры; необратимо |
| Оффер | Предложение о работе с ограниченным сроком действия |
| Заметка | Текстовый комментарий сотрудника к кандидатуре |
| История | Неизменяемая последовательность решений по кандидатуре |
| Тестовое задание | Этап, результат которого приходит из внешнего сервиса |
| Резюме | Файл, приложенный кандидатом к отклику |
| Системная роль | Набор прав на операции в приложении |
| Профессиональная роль | Позиция человека в компании, возможно с областью действия — подразделением |
| Назначение | Указание, кто проводит конкретный этап: поимённо или по профессиональной роли |
| Исполнитель этапа | Сотрудник, имеющий право принять решение на данном этапе данной кандидатуры |
| Правило решения | Сколько положительных решений нужно для продвижения: одно любое или от каждого |
| Позднее связывание | Определение конкретных исполнителей в момент решения, а не в момент отклика |
| Основание | Причина, по которой сотрудник оказался исполнителем: лично или по такой-то роли |
4. Обзор процесса¶
%%{init: {'themeVariables': {'noteBkgColor': 'transparent', 'noteBorderColor': '#c9a227'}}}%%
flowchart LR
A["Рекрутер<br/>создаёт вакансию"] --> B["Задаёт пайплайн"]
B --> C["Публикует"]
C --> D["Кандидат<br/>откликается"]
D --> E["Появляется<br/>кандидатура"]
E --> F{"Решение<br/>сотрудника"}
F -->|одобрено| G["Следующий этап"]
G --> F
F -->|отклонено| H["Отклонён<br/><i>терминально</i>"]
G -->|этапы кончились| I["Оффер"]
I -->|принят| J["Нанят"]
I -->|отказ или истёк срок| H
Исходник диаграммы
flowchart LR
A["Рекрутер<br/>создаёт вакансию"] --> B["Задаёт пайплайн"]
B --> C["Публикует"]
C --> D["Кандидат<br/>откликается"]
D --> E["Появляется<br/>кандидатура"]
E --> F{"Решение<br/>сотрудника"}
F -->|одобрено| G["Следующий этап"]
G --> F
F -->|отклонено| H["Отклонён<br/><i>терминально</i>"]
G -->|этапы кончились| I["Оффер"]
I -->|принят| J["Нанят"]
I -->|отказ или истёк срок| H
5. Функциональные требования¶
Формулировка «система должна» означает обязательное требование. Требования, помеченные (★), необязательны и оцениваются дополнительно.
5.1. Вакансии¶
- FR-01. Рекрутер создаёт вакансию: заголовок, описание, подразделение, вилка зарплаты, город, формат работы. Созданная вакансия находится в состоянии «черновик» и не видна кандидатам.
- FR-02. Рекрутер задаёт пайплайн вакансии: упорядоченный список этапов, у каждого — название и тип (
Screening,Assessment,Interview,Offer). - FR-03. Рекрутер публикует вакансию. С этого момента она видна в публичном списке и принимает отклики.
- FR-04. Рекрутер закрывает вакансию. Закрытая вакансия не принимает новые отклики.
- FR-05. Публичный список опубликованных вакансий доступен без аутентификации, с фильтрами по подразделению, городу и формату работы и с постраничной выдачей.
- FR-06. Сотрудник видит список своих вакансий с количеством кандидатур на каждом этапе.
- FR-07. Рекрутер задаёт назначения для каждого этапа в отдельности. Назначение — это список целей, каждая из которых либо конкретный сотрудник, либо профессиональная роль (с областью действия или без). Один этап может сочетать оба вида: «тимлид подразделения Платежи и любой системный аналитик».
- FR-07а. Для каждого этапа рекрутер задаёт правило решения:
Любой— достаточно решения одного исполнителя,Все— требуется решение каждого назначенного.
5.2. Отклик¶
- FR-08. Кандидат откликается на опубликованную вакансию, указав имя, электронную почту, телефон (необязательно) и приложив резюме.
- FR-09. В ответ на отклик система выдаёт кандидату ссылку с токеном отслеживания.
- FR-10. По этой ссылке кандидат без аутентификации видит название вакансии, текущий этап и дату последнего изменения. Заметки сотрудников и причину отклонения он не видит.
- FR-11. Кандидат может отозвать свой отклик по той же ссылке, пока кандидатура не в терминальном состоянии.
5.3. Работа с кандидатурами¶
- FR-12. Сотрудник видит список кандидатур по вакансии с фильтром по этапу и статусу и сортировкой по дате отклика.
- FR-13. Сотрудник открывает карточку кандидатуры: анкета, ссылка на резюме, текущий этап, полная история решений, заметки.
- FR-14. Исполнитель текущего этапа принимает положительное решение. Кандидатура переходит на следующий этап, когда набрано столько решений, сколько требует правило решения этапа.
- FR-15. Исполнитель текущего этапа отклоняет кандидатуру с обязательным указанием причины.
- FR-16. Сотрудник добавляет заметку к кандидатуре. Заметки не удаляются и не редактируются.
- FR-17. На последнем этапе пайплайна продвижение означает выдачу оффера со сроком действия.
- FR-18. Кандидат принимает или отклоняет оффер по ссылке отслеживания.
- FR-19. Оффер, не принятый до истечения срока, автоматически переводит кандидатуру в отклонённые с системной причиной.
- FR-20. (★) Полнотекстовый поиск по кандидатам: имя, почта, содержимое заметок.
5.4. Уведомления¶
- FR-21. При появлении кандидатуры на этапе уведомление получают исполнители этого этапа: поимённо назначенные — лично, назначенные по роли — все текущие обладатели роли.
- FR-22. При продвижении и при отклонении кандидат получает уведомление на указанную почту.
- FR-23. За сутки до истечения срока оффера кандидат получает напоминание.
- FR-24. Уведомление отправляется не более одного раза на одно событие, даже при повторной обработке или перезапуске системы.
- FR-25. Отказ почтового сервиса не должен отменять или откатывать бизнес-решение, которое уже принято.
5.5. Внешний сервис тестовых заданий¶
- FR-26. При переводе кандидатуры на этап типа
Assessmentсистема отправляет во внешний сервис запрос на выдачу задания. - FR-27. Результат приходит обратно вебхуком: идентификатор задания, балл, вердикт.
- FR-28. Один и тот же результат может прийти несколько раз; повторные доставки не должны приводить к повторному изменению состояния.
- FR-29. Результат, относящийся к неизвестной или уже завершённой кандидатуре, отбрасывается с записью в журнал, без ошибки для отправителя.
5.6. Администрирование¶
- FR-30. Администратор заводит сотрудников и назначает им системные роли.
- FR-31. Сотрудник входит по почте и паролю, получает пару токенов доступа и обновления.
- FR-32. (★) Журнал действий: кто, когда и какое решение принял, с фильтром по сотруднику и периоду.
5.7. Роли и назначения¶
- FR-33. Администратор ведёт справочник профессиональных ролей: название, признак «привязана к подразделению».
- FR-34. Администратор назначает сотруднику профессиональную роль и отзывает её. Роль с областью действия назначается вместе с подразделением.
- FR-35. Сотрудник видит входящую очередь: кандидатуры, ожидающие его решения, с указанием вакансии, этапа и основания — лично или по роли.
- FR-36. В карточке кандидатуры видно, кто назначен на текущий этап, кто уже принял решение и чьё решение ещё ожидается.
- FR-37. Рекрутер может переопределить назначение на текущем этапе конкретной кандидатуры, не меняя пайплайн вакансии. Переопределение фиксируется в истории.
- FR-38. Каждое решение фиксирует не только автора, но и основание: лично или по такой-то роли.
- FR-39. (★) Отчёт по вакансиям, у которых на каком-либо этапе не осталось ни одного исполнителя: назначенный поимённо сотрудник уволен или роль ни за кем не закреплена.
6. Бизнес-правила¶
Это ядро задания. Каждое правило обязано быть выражено в коде в одном месте и покрыто отдельным тестом с именем, содержащим номер правила.
- BR-01. Откликнуться можно только на опубликованную вакансию.
- BR-02. Один и тот же адрес почты не может иметь две одновременно активные кандидатуры по одной вакансии. После терминального исхода повторный отклик разрешён.
- BR-03. Кандидатура движется строго по порядку этапов своего пайплайна. Пропуск этапа и возврат назад запрещены.
- BR-04. «Отклонён» — терминальное состояние. Никакие действия над отклонённой кандидатурой невозможны.
- BR-05. «Нанят» — терминальное состояние.
- BR-06. Кандидатуры по закрытой вакансии нельзя продвигать. Отклонить — можно.
- BR-07. Пайплайн фиксируется в момент отклика. Последующее редактирование вакансии не изменяет ход уже существующих кандидатур и применяется только к новым откликам.
- BR-08. Пайплайн содержит от одного до десяти этапов, названия этапов внутри вакансии уникальны, последний этап всегда имеет тип
Offer. - BR-09. Причина отклонения обязательна, не короче десяти символов.
- BR-10. История решений только дополняется. Изменение и удаление записей истории невозможно ни через какой сценарий.
- BR-11. Срок действия оффера — не менее одного и не более тридцати календарных дней с момента выдачи.
- BR-12. Резюме принимается в форматах PDF, DOC, DOCX размером не более 5 МБ.
- BR-13. Токен отслеживания даёт доступ ровно к одной кандидатуре и не даёт доступа ни к чему другому.
- BR-14. Все моменты времени хранятся и сравниваются в UTC.
- BR-15. Два сотрудника не могут одновременно применить конкурирующие решения к одной кандидатуре: второе должно быть отклонено, а не перезаписать первое.
- BR-16. У каждого этапа опубликованной вакансии есть хотя бы одно назначение. Публикация вакансии, у которой есть этап без назначений, запрещена.
- BR-17. Решение по кандидатуре принимает только исполнитель её текущего этапа. Назначение на другие этапы той же вакансии права решать не даёт.
- BR-18. Право по роли определяется на момент решения, а не на момент отклика. Сотрудник, получивший роль вчера, может принимать решения по кандидатурам, откликнувшимся месяц назад.
- BR-19. Отзыв роли немедленно лишает сотрудника права решать на этапах, назначенных этой роли. Решения, принятые до отзыва, остаются в силе и из истории не исчезают.
- BR-20. Правило
Вседопустимо только для назначений на конкретных людей. Сочетать его с назначением по роли запрещено: множество обладателей роли не фиксировано. - BR-21. При правиле
Всекандидатура продвигается, когда положительное решение принял каждый назначенный. Любое отрицательное решение отклоняет кандидатуру немедленно, не дожидаясь остальных. - BR-22. Один сотрудник принимает решение на одном этапе одной кандидатуры не более одного раза. Повторная попытка отклоняется, изменить своё решение нельзя.
- BR-23. Назначения фиксируются снимком вместе с пайплайном (уточнение BR-07): поимённые назначения — по идентификатору сотрудника, ролевые — по идентификатору роли, состав обладателей роли не фиксируется.
- BR-24. Переопределение назначения (FR-37) действует только на одну кандидатуру и не изменяет ни вакансию, ни другие кандидатуры.
⚠️ BR-07, BR-15 и BR-18 — те правила, которые чаще всего обнаруживаются слишком поздно. BR-07 и BR-23 меняют модель данных, BR-15 — способ работы с транзакциями, BR-18 — устройство проверки прав. Продумайте их до того, как начнёте писать код четвёртой лабораторной.
Обратите внимание на пару BR-23 и BR-18: пайплайн фиксируется, а состав ролей — нет. Это не противоречие, а осознанное решение заказчика, и вам предстоит объяснить, где проходит граница между «зафиксировано» и «вычисляется каждый раз».
7. Состояния кандидатуры¶
%%{init: {'themeVariables': {'noteBkgColor': 'transparent', 'noteBorderColor': '#c9a227'}}}%%
stateDiagram-v2
[*] --> ВРаботе: отклик
ВРаботе --> ВРаботе: продвижение<br/>на следующий этап
ВРаботе --> ОфферВыдан: продвижение<br/>с последнего этапа
ОфферВыдан --> Нанят: кандидат принял
ВРаботе --> Отклонён: решение сотрудника
ВРаботе --> Отозван: кандидат отозвал
ОфферВыдан --> Отклонён: отказ кандидата<br/>или истёк срок
Отклонён --> [*]
Отозван --> [*]
Нанят --> [*]
Исходник диаграммы
stateDiagram-v2
[*] --> ВРаботе: отклик
ВРаботе --> ВРаботе: продвижение<br/>на следующий этап
ВРаботе --> ОфферВыдан: продвижение<br/>с последнего этапа
ОфферВыдан --> Нанят: кандидат принял
ВРаботе --> Отклонён: решение сотрудника
ВРаботе --> Отозван: кандидат отозвал
ОфферВыдан --> Отклонён: отказ кандидата<br/>или истёк срок
Отклонён --> [*]
Отозван --> [*]
Нанят --> [*]
Внутри состояния «в работе» кандидатура дополнительно характеризуется текущим этапом пайплайна. Смешивать статус и этап в одно поле нельзя: этапы у каждой вакансии свои, а набор статусов один на всю систему.
8. Права доступа¶
Доступ проверяется в два шага. Сначала системная роль: разрешена ли операция вообще. Затем назначение: имеет ли этот человек отношение к этому объекту. Первый шаг отвечается по токену, второй — только обращением к данным.
| Действие | Кандидат | Сотрудник | Рекрутер | Админ |
|---|---|---|---|---|
| Смотреть публичные вакансии | да | да | да | да |
| Создавать, публиковать, закрывать вакансию | нет | нет | да | нет |
| Настраивать пайплайн и назначения | нет | нет | да | нет |
| Откликаться | да | нет | нет | нет |
| Смотреть кандидатуру | своя | назначен на любой её этап | по своим вакансиям | все |
| Добавлять заметку | нет | назначен на любой её этап | по своим вакансиям | нет |
| Продвигать и отклонять | нет | исполнитель текущего этапа | нет | нет |
| Переопределять назначение этапа | нет | нет | по своим вакансиям | нет |
| Вести справочник ролей, назначать роли | нет | нет | нет | да |
Как определяется исполнитель¶
%%{init: {'themeVariables': {'noteBkgColor': 'transparent', 'noteBorderColor': '#c9a227'}}}%%
flowchart TB
S["Сотрудник хочет принять решение<br/>по кандидатуре"] --> A{"Кандидатура<br/>в терминальном<br/>состоянии?"}
A -->|да| NO["403"]
A -->|нет| B["Взять назначения<br/>ТЕКУЩЕГО этапа<br/>из снимка пайплайна"]
B --> C{"Назначен<br/>поимённо?"}
C -->|да| YES["Решение разрешено"]
C -->|нет| D{"Есть ли у него<br/>сейчас одна из<br/>назначенных ролей?"}
D -->|нет| NO
D -->|да| E{"Область роли<br/>совпадает с<br/>подразделением<br/>вакансии?"}
E -->|нет| NO
E -->|да| YES
YES --> F{"Уже принимал<br/>решение на этом<br/>этапе?"}
F -->|да| NO
Исходник диаграммы
flowchart TB
S["Сотрудник хочет принять решение<br/>по кандидатуре"] --> A{"Кандидатура<br/>в терминальном<br/>состоянии?"}
A -->|да| NO["403"]
A -->|нет| B["Взять назначения<br/>ТЕКУЩЕГО этапа<br/>из снимка пайплайна"]
B --> C{"Назначен<br/>поимённо?"}
C -->|да| YES["Решение разрешено"]
C -->|нет| D{"Есть ли у него<br/>сейчас одна из<br/>назначенных ролей?"}
D -->|нет| NO
D -->|да| E{"Область роли<br/>совпадает с<br/>подразделением<br/>вакансии?"}
E -->|нет| NO
E -->|да| YES
YES --> F{"Уже принимал<br/>решение на этом<br/>этапе?"}
F -->|да| NO
- AC-01. Право принять решение даёт только назначение на текущий этап кандидатуры — лично или через профессиональную роль. Ни системная роль, ни владение вакансией такого права не дают.
- AC-02. Роль в токене доступа не является достаточным основанием. Проверка требует чтения снимка назначений кандидатуры и актуального состава ролей сотрудника.
- AC-03. «По своим вакансиям» для рекрутера означает: он создал эту вакансию. Передача вакансии другому рекрутеру в объём не входит.
- AC-04. Видимость шире права действовать: назначенный на любой этап видит карточку кандидатуры целиком, включая историю и заметки, но действовать может только на своём этапе, когда до него дошла очередь.
- AC-05. Отсутствие прав на объект возвращается как
403, если объект существует и пользователь аутентифицирован;404— если объект не существует или скрыт от этой категории пользователей. - AC-06. Ответ
403не должен раскрывать, кто именно назначен на этап.
9. Требования к HTTP-интерфейсу¶
Контракт фиксирован: приёмочные тесты курса обращаются именно по этим адресам. Внутреннее устройство — на ваше усмотрение, но пути, коды и формат ошибок менять нельзя.
9.1. Основные ресурсы¶
POST /api/auth/login вход сотрудника
POST /api/auth/refresh обновление токена
GET /api/vacancies публичный список (фильтры, пагинация)
GET /api/vacancies/{id} публичная карточка
POST /api/vacancies создать черновик
PUT /api/vacancies/{id}/pipeline задать пайплайн вместе с назначениями
POST /api/vacancies/{id}/publish опубликовать
POST /api/vacancies/{id}/close закрыть
GET /api/roles справочник профессиональных ролей
POST /api/roles завести роль
GET /api/employees список сотрудников
POST /api/employees завести сотрудника
PUT /api/employees/{id}/system-roles задать системные роли
POST /api/employees/{id}/roles назначить профессиональную роль
DELETE /api/employees/{id}/roles/{roleId} отозвать профессиональную роль
POST /api/vacancies/{id}/applications отклик (без аутентификации)
GET /api/applications список для сотрудника
GET /api/applications/{id} карточка кандидатуры
POST /api/applications/{id}/advance продвинуть
POST /api/applications/{id}/reject отклонить
POST /api/applications/{id}/notes добавить заметку
GET /api/applications/{id}/history история решений
PUT /api/applications/{id}/assignees переопределить исполнителей текущего этапа
GET /api/inbox кандидатуры, ждущие решения текущего сотрудника
GET /api/tracking/{token} статус для кандидата
POST /api/tracking/{token}/withdraw отзыв отклика
POST /api/tracking/{token}/offer/accept принять оффер
POST /api/tracking/{token}/offer/decline отклонить оффер
POST /api/resumes загрузка файла резюме
GET /api/resumes/{id} скачивание (по правам)
POST /api/webhooks/assessments результат тестового задания
GET /health состояние сервиса
9.2. Пайплайн с назначениями¶
Тело PUT /api/vacancies/{id}/pipeline. Обратите внимание: assignees — разнородный
список, где вид цели определяется полем kind.
{
"steps": [
{
"name": "Скрининг резюме",
"type": "Screening",
"decisionRule": "Any",
"assignees": [
{ "kind": "employee", "employeeId": "018f2a..." }
]
},
{
"name": "Техническое интервью",
"type": "Interview",
"decisionRule": "Any",
"assignees": [
{ "kind": "role", "roleId": "018f31...", "scope": null }
]
},
{
"name": "Знакомство с командой",
"type": "Interview",
"decisionRule": "All",
"assignees": [
{ "kind": "employee", "employeeId": "018f44..." },
{ "kind": "employee", "employeeId": "018f45..." }
]
},
{
"name": "Оффер",
"type": "Offer",
"decisionRule": "Any",
"assignees": [
{ "kind": "role", "roleId": "018f52...", "scope": "Платежи" }
]
}
]
}
Второй этап читается как «любой обладатель этой роли», четвёртый — как «руководитель
подразделения Платежи», третий — как «оба этих человека, каждый лично». Попытка задать
decisionRule: "All" вместе с целью вида role возвращает 409 с кодом BR-20.
9.3. Коды ответов¶
| Код | Когда |
|---|---|
200 / 201 |
успех; 201 с заголовком Location при создании |
400 |
ошибка формата или валидации входных данных |
401 |
нет или недействителен токен |
403 |
аутентифицирован, но не имеет прав на объект |
404 |
объект не существует или не должен быть виден этому пользователю |
409 |
нарушено бизнес-правило (BR-01 … BR-24) |
422 |
не используется в этом проекте |
Разделение 400 и 409 принципиально: 400 — «вы прислали ерунду», 409 —
«данные корректны, но так делать нельзя по правилам предметной области».
9.4. Формат ошибки¶
Все ошибки возвращаются в формате RFC 7807 с обязательным полем code, по которому
приёмочный тест опознаёт конкретное правило:
{
"type": "https://hr.example/errors/rejected-application-is-terminal",
"title": "Кандидатура находится в терминальном состоянии",
"status": 409,
"code": "BR-04",
"detail": "Кандидатура 018f2a… отклонена 12.03.2026 и не может быть продвинута",
"traceId": "00-4bf92f…-01"
}
Текст detail предназначен человеку и может меняться. code — часть контракта.
10. Нефункциональные требования¶
Эти требования одинаковы для любого стека. Если выбранная экосистема не позволяет выполнить какое-то из них разумными средствами, это выясняется до начала работы, а не на пятой неделе.
- NFR-01. Язык и фреймворк — на выбор исполнителя; выбор согласуется на первой лабораторной и фиксируется отдельным ADR. Хранилище — реляционная СУБД, по умолчанию PostgreSQL.
- NFR-02. Всё окружение поднимается одной командой
docker compose up, включая базу и почтовую заглушку. - NFR-03. Схема базы управляется миграциями. Миграции применяются отдельным инструментом, а не при старте приложения.
- NFR-04. Приложение не хранит состояние в памяти процесса: два экземпляра за балансировщиком работают корректно.
- NFR-05. Список любых сущностей выдаётся только постранично; страница по умолчанию — 20 записей, максимум — 100.
- NFR-06. Все логи структурные, каждая запись содержит идентификатор запроса, сквозной для всей цепочки обработки, включая фоновые задачи.
- NFR-07. Пароли хранятся только в виде хеша с солью, алгоритмом, предназначенным для паролей.
- NFR-08. Секреты и строки подключения не хранятся в репозитории.
- NFR-09. Спецификация OpenAPI генерируется из кода и доступна по
/openapi.json. - NFR-10. Идентификаторы сущностей — UUID версии 7 или иные монотонно возрастающие по времени создания.
- NFR-11. Сборка, тесты и архитектурные тесты выполняются в CI на каждый push.
- NFR-12. Время ответа на чтение списка из 10 000 кандидатур — не более 300 мс на девелоперской машине.
11. Что в объём не входит¶
Чтобы не тратить время: этого делать не надо, и на защите за это баллов нет.
- Пользовательский интерфейс. Достаточно Swagger UI и коллекции запросов.
- Микросервисная декомпозиция. Проект — модульный монолит плюс, при необходимости, один фоновый обработчик.
- Реальная интеграция с job-бордами, календарями, мессенджерами.
- Мультиарендность, локализация, тёмная тема, мобильное приложение.
- Иерархия подразделений. Подразделение — плоский справочник; вложенности нет.
- Делегирование прав, замещение на время отпуска, эскалация по срокам.
- Настоящий сервис тестовых заданий: он заменяется заглушкой, которую вы напишете сами.
12. Карта требований по лабораторным¶
| № лабораторной | Что закрывается | Требования |
|---|---|---|
| 1. Моделирование | глоссарий, диаграммы, границы, открытые вопросы | разделы 3–8, 14 |
| 2. Домен | правила и состояния, модульные тесты | BR-01 … BR-14, BR-20 … BR-24 |
| 3. Сценарии | сценарии и порты, тесты на тест-даблах | FR-01 … FR-19 |
| 4. Инфраструктура | хранение, миграции, конкурентный доступ | BR-07, BR-10, BR-15, NFR-03 |
| 5. HTTP | контракт, ошибки, пагинация | раздел 9, NFR-05, NFR-09 |
| 6. Безопасность | вход, две системы ролей, назначения, доступ к объектам | FR-31, FR-33 … FR-38, BR-16 … BR-19, AC-01 … AC-06, NFR-07 |
| 7. События | уведомления, гарантия однократности | FR-21 … FR-25 |
| 8. Приём извне | вебхук, повторные доставки, срок оффера | FR-19, FR-26 … FR-29 |
| 9. Защита | наблюдаемость, CI, решения и их обоснование | NFR-06, NFR-11 |
13. Критерии приёмки¶
Работа принимается, когда выполнены все пункты. Частично — не принимается.
| Критерий | |
|---|---|
| ☐ | docker compose up поднимает систему, /health отвечает 200 |
| ☐ | Миграции применяются отдельной командой на чистой базе без ручных действий |
| ☐ | Все модульные, интеграционные и архитектурные тесты зелёные в CI |
| ☐ | Каждое правило BR-01 … BR-24 покрыто тестом, имя теста содержит номер правила |
| ☐ | Приёмочный набор курса проходит полностью |
| ☐ | В docs/adr/ не менее пяти записей о принятых решениях |
| ☐ | Диаграммы лежат в репозитории как Mermaid и соответствуют текущему коду |
| ☐ | В README описан запуск, структура решения и раздел «что я упростил бы для стартапа» |
| ☐ | Код, включая имена тестов и веток, на английском; по-русски — комментарии и документы |
| ☐ | Каждая лабораторная влита отдельным pull request'ом после ревью |
| ☐ | Секретов в истории репозитория нет |
14. Намеренно открытые вопросы¶
Здесь заказчик не дал ответа. Это не упущение: решение принимаете вы, а на защите объясняете, почему выбрали именно его. Ответы «как в туториале» и «так проще» не принимаются.
- Что такое кандидат — самостоятельная сущность с историей всех откликов или просто анкета внутри отклика? Что произойдёт, если один человек откликнется на пять вакансий с разными телефонами?
- Где заканчивается вакансия и начинается кандидатура? Кто из них отвечает за соблюдение BR-03?
- Как именно вы фиксируете пайплайн в момент отклика (BR-07) и чем платите за выбранный способ?
- Отзыв кандидатом и отклонение сотрудником — одно состояние с разной причиной или два разных? Что изменится в отчётности от вашего выбора?
- Оффер — это этап пайплайна, отдельное состояние кандидатуры или самостоятельная сущность со своим жизненным циклом?
- Как обеспечивается BR-15 — блокировкой при чтении, версией строки или порядковым номером решения? Что произойдёт при отказе второго сотрудника: ошибка или переигрывание?
- Кто отвечает за истечение срока оффера (FR-19): фоновая задача, проверка при чтении или и то и другое?
- Назначение на этап — это одно понятие с двумя видами цели или два разных понятия с общим интерфейсом? Как вы выразите «либо человек, либо роль» так, чтобы третий вид цели добавлялся без правки всех проверок?
- BR-23 требует зафиксировать ролевое назначение по идентификатору, а BR-18 — вычислять обладателей роли каждый раз. Где в вашей модели проходит граница между снимком и живой ссылкой и почему именно там?
- Профессиональная роль с областью действия — это одна сущность с необязательным полем, пара «роль плюс подразделение» или две разные роли? Что произойдёт, когда подразделения станут иерархическими и появится «тимлид любой команды внутри Разработки»?
- Правило
Всетребует хранить, кто уже высказался. Где живёт это состояние — в кандидатуре, в отдельной записи по этапу или выводится из истории? Чем платите за каждый вариант? - Сотрудник назначен поимённо и уволился, а этап с правилом
Всеуже начат. Что делает система: блокирует кандидатуру навсегда, требует вмешательства рекрутера или тихо исключает выбывшего? - Кто получает уведомление, если этап назначен роли, которой обладают сорок человек? Все сорок, или система устроена иначе — и если иначе, то как это согласуется с FR-21?