Архитектура
Платформа состоит из восьми бизнес-сервисов и Gateway. Браузер обращается к Gateway по HTTP, WebSocket и SSE; внутренние вызовы выполняются по gRPC поверх TLS. PostgreSQL хранит предметные данные, RabbitMQ доставляет события и задания, Redis обслуживает восстанавливаемое состояние, объектное хранилище — фотографии и логотипы.
Объяснение выбора стека, устройства Module Federation, логирования и возможностей параллельной разработки — в разделе «Технологии и командная разработка». Правила доступа для участников и операторов собраны в ролевой модели.
Источники: итоговая архитектура, возможности сервисов, контракты и хранилища. Ниже описано согласованное устройство; наличие архитектурного требования не заменяет проверку конкретной реализации. Отдельные условия выпуска документов 29 и 30 указаны в индексе.
Общая схема
flowchart LR
UI["Продуктовый интерфейс"] --> GW["Gateway: public"]
ADMIN["Admin UI"] --> AGW["Gateway: admin"]
subgraph SERVICES["Бизнес-сервисы, gRPC / TLS"]
ID["Identity / Accounts"]
C["Candidates"]
E["Employers"]
O["Opportunities"]
R["Recruitment"]
CH["Chat"]
N["Notifications"]
B["Backoffice"]
end
GW --> SERVICES
AGW -->|"Разрешённые административные методы"| SERVICES
SERVICES --> PG[("PostgreSQL: база каждого сервиса")]
SERVICES -->|"Outbox"| MQ["RabbitMQ: события и задания"]
MQ --> SERVICES
SERVICES -.-> REDIS[("Redis")]
C --> S3[("Объектное хранилище")]
E --> S3
C --> FSP["Адаптер реестра ФСП"]
ID --> SMTP["Postfix / SMTP: письма безопасности"]
Принципы
- Один владелец данных. Сервис имеет собственную логическую базу PostgreSQL и роль доступа. Чужие таблицы не читаются напрямую; используются RPC или допустимые проекции событий.
- Предметные проверки у владельца. Gateway маршрутизирует запрос, проверяет токен, формирует доверенные метаданные, преобразует JSON и protobuf, применяет лимиты и собирает составной ответ. Правила публикации, офера или раскрытия остаются в сервисах.
- Три вида взаимодействия. Внутри сервиса — вызов модуля; для актуальных проверок и чтений — синхронный gRPC; для длительной работы и доставки изменений — очередь и worker.
- Worker не становится отдельным доменным сервисом. API, издатель outbox, обработчики очередей и планировщик одной области используют общие модули и базу, но могут запускаться разными процессами.
- Разрешения проверяет сервер. Кнопка в интерфейсе, старый токен, результат подбора или кэш не заменяют актуальные проверки состояния аккаунта, членства и согласия на раскрытие.
Владельцы данных
| Сервис | Чем владеет | Основные взаимодействия |
|---|---|---|
| Identity / Accounts | Аккаунты, email, хеши паролей, подтверждение почты, восстановление доступа, сессии, JWT/JWKS, состояние и тип аккаунта, служебные права, согласия с документами | Выдача и проверка полномочий, блокировка, координация удаления, письма безопасности |
| Candidates | Профиль инженера, контакты, фото, анкеты, достижения, привязка ФСП, выбор достижений, видимость анкет, блокировки компаний | Безопасное представление анкеты, импорт ФСП, события для поиска, проверка раскрытия через Recruitment |
| Employers | Карточки компаний, членства, роли OWNER/HR, приглашения |
Проверка полномочий представителей, управление организацией |
| Opportunities | Вакансии, публикация, публичная или закрытая видимость, назначения HR | Обновление поиска; проверка адресного доступа инженера через Recruitment |
| Recruitment | Поисковые проекции, объяснения подбора, избранное, интересы, оферы, снимки условий, разрешения на раскрытие | Проверки владельцев, создание событий для Chat и Notifications |
| Chat | Диалоги оферов, сообщения, отметки доставки и прочтения | Проверки доступа к диалогу и компании, актуальное отображение личности, доставка в реальном времени |
| Notifications | Уведомления, настройки, прочтение и повторная доставка | Обработка событий интереса, офера, ответа и сообщения |
| Backoffice | Жалобы, обращения, назначения операторов, решения, попытки исполнения, аудит | Получение допустимых материалов и выполнение решений через владельцев объектов |
Страница компании собирается из Employers и Opportunities, анкета — из Candidates и Recruitment, чат — из Chat и разрешённого контекста Recruitment. Составление ответа не обходит проверки владельца и не создаёт общий кэш персональных данных.
Аутентификация и сессии
По решению DEC-28 используется собственная аутентификация по email и паролю внутри Identity. Отдельный внешний провайдер входа не требуется.
Для корпоративной поставки описан вариант подключения Keycloak и других совместимых OIDC-провайдеров. Это расширение через адаптер Identity/Gateway с привязкой внешней личности и сохранением внутренних сессий; в текущую email/password-реализацию готовый OIDC-вход не включён.
Gateway обслуживает /auth/* на том же origin, что и интерфейс. Непрозрачный refresh-токен находится в HttpOnly cookie gm_session, короткоживущий access JWT — в памяти frontend. Контракт предусматривает EdDSA, срок access-токена до пяти минут, aud=geek-api, идентификатор сессии sid и время аутентификации auth_time. Identity публикует JWKS; сервисы проверяют подпись, алгоритм, issuer и audience.
Пароли хранятся как хеши Argon2id. Ссылки подтверждения почты и сброса пароля одноразовые, их секреты хешируются в базе и передаются во фрагменте URL. Refresh-токен меняется при использовании; повторное применение старого токена отзывает всю семью сессии.
Для служебных писем настроен Postfix: Identity создаёт задание, worker отправляет письмо по SMTP. Настройка и отправка подтверждены владельцем проекта; сценарии и ссылки описаны в разделе почты.
Административный профиль использует отдельные cookie, ADMIN-семьи и aud=geek-admin-api. По решению D-12 каждый ADMIN-вызов проверяет сессию через основную базу Identity. PRODUCT-вызовы сохраняют локальную проверку JWT с ограниченным сроком действия до 300 секунд после отзыва; это не отменяет актуальных проверок блокировки аккаунта, ресурсов и членства.
Обязательный account_type при регистрации и сборка /me только для соответствующей стороны — требования выпуска документа 29. Подробности: спецификация входа, Gateway, Identity.
Внешний API
Предметные REST-маршруты имеют префикс /api/v1; /auth/* обслуживается отдельно. Неизвестные поля запросов отклоняются, значение me определяется из проверенного токена.
| Группа путей | Владелец |
|---|---|
/me, /legal-documents, /me/legal-acceptances, /me/deletion |
Identity |
/me/profile, /me/questionnaires, /questionnaires, /me/achievements, /me/fsp, /reference-data, /me/company-blocks |
Candidates |
/companies, /me/companies |
Employers |
/vacancies/{id}, /companies/{id}/vacancies |
Opportunities |
/search/*, /matches/*, /me/favorites/*, /me/interests, /companies/{id}/interests, /offers, /me/offers, /me/disclosures |
Recruitment |
/chats, /chats/{id}/messages, /chats/{id}/read, /chats/{id}/delivery |
Chat |
/me/notifications, /me/notification-settings |
Notifications |
/complaints, /support-requests |
Backoffice, пользовательская подача |
Чтение и изменение обычно возвращают 200, создание — 201, принятая фоновая операция — 202 с объектом Operation, идемпотентное удаление или отзыв — 204. Переходы состояния возвращают обновлённый ресурс с version. WebSocket чата и SSE уведомлений завершаются на Gateway, который получает внутренние потоки gRPC.
В базовом плане тестирования учтены 143 RPC и 119 уникальных публичных маршрутов, включая auth, health/readiness и потоковые каналы. Это счётчики покрытия указанной версии контракта, а не подтверждение исполнения всех тестов и не неизменный размер API после расширений.
Сквозные операции
Публикация анкеты. Candidates одной транзакцией сохраняет состояние и событие outbox. Издатель отправляет событие в RabbitMQ, обработчик Recruitment обновляет поисковую проекцию. Владелец сразу видит сохранённую анкету; каталог может обновиться позже. При выдаче результатов повторно проверяется актуальная видимость.
Интерес. Recruitment проверяет владельца и доступность анкеты через Candidates, вакансию через Opportunities, ограничения и блокировки. Сохранение интереса и события выполняется совместно. Notifications уведомляет компанию; интерес не раскрывает контакты и не создаёт офер автоматически.
Офер. Проверяются аккаунт сотрудника, членство в компании, вакансия, анкета, блокировки, защита от повторного обращения и условия предложения. Проверка дубля и сохранение выполняются локальной транзакцией. Событие отдельно получают Chat для создания диалога и Notifications для уведомления инженера.
Принятие и раскрытие. Ответ инженера и разрешённый компании объём данных фиксируются одной транзакцией Recruitment. При последующем чтении Candidates заново получает актуальный объём разрешения и проверяет права компании. Старое принятие офера не даёт бессрочного обхода отзыва согласия.
Сообщение. Chat сохраняет сообщение вместе с событием. Повтор запроса после потери сети не создаёт дубль. Redis Pub/Sub передаёт межпроцессные сигналы; история остаётся в PostgreSQL и позволяет восстановить состояние после переподключения. Сохранение, доставка и прочтение — разные состояния; подтверждение брокера не равно прочтению человеком.
Уведомление. Обработчик проверяет получателя, настройки и текущий доступ. Уведомление и отметка обработки фиксируются до подтверждения RabbitMQ. Основной пользовательский канал — внутри приложения; email, push и Telegram описаны как дальнейшие адаптеры. Письма безопасности Identity являются отдельным механизмом.
Модерация. Backoffice хранит решение и управляет исполнением; публикацию скрывает Candidates или Opportunities, аккаунт блокирует Identity. Решение считается применённым после подтверждения владельца. Подробности — в администрировании.
Очереди, события и фоновые задания
Transactional Outbox связывает изменение предметных данных с событием: обе записи попадают в одну транзакцию PostgreSQL. Отдельный издатель отправляет устойчивые сообщения с publisher confirms и обязательной маршрутизацией, после чего отмечает отправку.
Каждый сервис-подписчик имеет свою очередь. Несколько обработчиков одного сервиса конкурируют внутри неё; два разных сервиса не должны делить одну очередь событий. Задание направляется владельцу операции, событие получают все заинтересованные подписчики.
| Владелец | Примеры фоновой работы |
|---|---|
| Candidates | Импорт и обновление ФСП, обработка фото, события анкеты и блокировок |
| Employers | Обработка логотипа, изменения карточки и полномочий |
| Opportunities | Изменение публикации и видимости вакансии |
| Recruitment | Индексация, пересчёт подбора, события интереса, офера, ответа и раскрытия |
| Chat | Создание диалога, события сохранения и прочтения сообщений |
| Notifications | Создание уведомлений и доставка обновлений интерфейсу |
| Identity | Служебные операции аккаунта, блокировки, координация стирания |
| Backoffice | Повтор исполнения решений и сверка результатов |
Доставка выполняется как минимум один раз. Получатель одной транзакцией сохраняет результат и отметку обработки; повтор сообщения не должен повторять бизнес-эффект. Длительный внешний вызов не удерживает транзакцию БД. Временные ошибки получают ограниченные повторы с задержкой, окончательные — запись в очередь неуспешных сообщений (DLQ) с причиной. Для устойчивых очередей предусмотрен тип quorum.
События содержат необходимые идентификаторы и минимальные данные, без контактов и полных текстов переписки. Планировщик хранит время следующего запуска и идентификатор операции в PostgreSQL и создаёт задание через outbox. Контракты: обмен сообщениями.
Хранение и поиск
Логические базы: identity, candidates, employers, opportunities, recruitment, chat, notifications, backoffice. Предметные записи, операции, outbox и отметки обработки находятся у своего сервиса. RabbitMQ — транспорт, Redis — восстанавливаемый кэш, сигналы и счётчики лимитов.
Recruitment использует индексы и полнотекстовый поиск PostgreSQL. Поисковая проекция содержит профессиональные сведения и безопасные факты достижений; контакты остаются в Candidates. Кэш ограничен временем и контекстом пользователя, компании и версии данных; закрытые вакансии, контакты и неопубликованные анкеты не должны попадать в общую выдачу. Фотография до раскрытия не должна быть доступна по угадываемой публичной ссылке.
Поведение при сбоях
| Сбой | Требуемое поведение |
|---|---|
| RabbitMQ недоступен | Сохранённые изменения и outbox остаются в PostgreSQL; доставка и проекции догоняют после восстановления |
| Издатель outbox остановлен | События накапливаются; возраст неотправленных записей должен наблюдаться |
| Worker упал до commit | Неподтверждённое сообщение доставляется повторно |
| Worker упал после commit, до ack | Повтор обнаруживается по отметке обработки и не создаёт второй эффект |
| Redis недоступен | Предметные данные и история сообщений сохраняются; история используется для восстановления сигналов. Защитные лимитеры могут отклонять операции по своему контракту |
| ФСП недоступна | Профиль работает с последними импортированными фактами и видимым временем синхронизации |
| Chat недоступен | История сохраняется; интерфейс показывает недоступность переписки |
| Notifications недоступен | События ждут в очереди до восстановления |
| Проверка прав недоступна | Закрытые данные и ограниченные действия не выдаются по устаревшему кэшу |
| PostgreSQL недоступен | Зависимые записи останавливаются; несохранённый результат не подтверждается брокеру |
| Сообщение попало в DLQ | Причина доступна оператору; повтор запускается после устранения причины |
Архитектура предусматривает наблюдение за ошибками и задержками API, возрастом outbox, очередями, повторами, DLQ, лагом поиска, синхронизацией ФСП и потоковыми соединениями. Фактически описанное покрытие стенда приведено в инфраструктуре. Контакты, токены и тексты переписки не включаются в обычные логи.
Прикладные логи формирует Pino в JSON: Gateway записывает запросы, статусы и длительность, диагностика gRPC и очередей сохраняет контекст ошибок. Идентификаторы помогают сопоставить обращение с обработкой в сервисах. Prometheus собирает и предметные показатели — регистрации, подтверждения почты, результаты административных операций, — и ресурсы приложений и узла. Подробнее: формат логов и подключение Kibana, бизнес- и ресурсные метрики, дашборды Grafana.
Технологии и запуск
Backend использует TypeScript, NestJS и Fastify; protobuf определяет gRPC-контракты. Frontend построен на React и Vite с пакетом @geek.moscow/design-sys. Локальная инфраструктура запускается через Compose, кластерная — через k3s/Kubernetes. Образы Docker отмечаются первыми восемью символами SHA коммита, манифесты собираются kustomize. Структура кода описана в репозиториях.
Возможности платформы · Все разделы · Репозитории и структура кода