Production-ready микросервисная платформа для управления заявками сотрудников: регистрация/JWT-аутентификация, RBAC, CRUD заявок с комментариями и вложениями, полнотекстовый поиск, email-уведомления и полный audit trail — построена на событийной модели поверх Kafka.
Статус: Phase 0–14 завершены. Полная контейнеризация (docker compose up поднимает
весь стек одной командой, включая production-grade React frontend), 53 unit/integration
теста backend (0 падений), CI/CD-пайплайн (.github/workflows/ci.yml: build → test →
static analysis → docker build → local deploy smoke-test → docker push [опционально]).
- Технологический стек
- Архитектура
- Быстрый старт
- Сервисы и порты
- Frontend
- Структура репозитория
- Тестирование
- CI/CD
- Известные ограничения окружения разработки
- Roadmap / возможные улучшения
| Слой | Технология |
|---|---|
| Язык / платформа | Java 17, Spring Boot 3.3.4 |
| API Gateway | Spring Cloud Gateway (реактивный), маршрутизация + JWT-фильтр + rate limiting (Redis) |
| Безопасность | Spring Security 6, JJWT 0.12.6 (access + refresh), BCrypt, RBAC (ADMIN/MANAGER/EMPLOYEE) |
| Данные | PostgreSQL 16, Spring Data JPA/Hibernate, Flyway (своя БД на сервис) |
| Поиск | Elasticsearch 8.x (Spring Data ES + low-level Java Client), русский анализатор |
| Кэш / rate-limit | Redis 7 (JWT blacklist, token-bucket rate limiter) |
| Сообщения | Apache Kafka (KRaft, без ZooKeeper), at-least-once, retry + DLQ, идемпотентные consumers |
| Файлы | MinIO (S3-совместимо) — вложения к заявкам, presigned URLs |
| Тесты | JUnit 5, Mockito, AssertJ, Testcontainers, @EmbeddedKafka, JaCoCo |
| Документация API | springdoc-openapi / Swagger UI на каждом сервисе |
| Мониторинг | Actuator + Prometheus + Grafana (провижининг из коробки), Kibana для логов |
| Логи | Logback + logstash-logback-encoder (JSON), correlation-id через MDC |
| Маппинг | MapStruct, Lombok точечно (DTO/entity) |
| Контейнеризация | Docker multi-stage build (Maven → eclipse-temurin:17-jre-alpine), Docker Compose, NGINX reverse proxy |
| CI/CD | GitHub Actions |
| Сборка | Maven multi-module (parent POM + common + 6 сервисов), Maven Wrapper |
Полное обоснование выбора каждой технологии — docs/DESIGN.md, раздел 8.
Микросервисы, каждый — свой bounded context (DDD), внутри — Hexagonal / Ports & Adapters
(domain → application → adapter/in → adapter/out). Взаимодействие между сервисами —
почти исключительно асинхронное через Kafka (event-carried state transfer); синхронных
REST-вызовов между сервисами в горячем пути нет — Gateway и сервисы валидируют JWT локально.
flowchart TB
subgraph Clients["Clients"]
WEB["Web / Postman / Mobile"]
end
NGINX["NGINX Reverse Proxy"]
GATEWAY["API Gateway\nJWT filter / CORS / Rate limit / Routing"]
subgraph Services["Microservices"]
USER["User Service\nAuth, RBAC, Users"]
TICKET["Ticket Service\nTickets, Comments, Attachments"]
NOTIF["Notification Service\nEmail, Templates"]
SEARCH["Search Service\nElasticsearch"]
AUDIT["Audit Service\nAudit log"]
end
KAFKA["Apache Kafka (KRaft)"]
subgraph Storage["Data & Infra"]
PG[(PostgreSQL)]
ES[(Elasticsearch)]
REDIS[(Redis)]
MINIO[(MinIO)]
end
subgraph Observability["Monitoring"]
PROM["Prometheus"]
GRAF["Grafana"]
end
WEB --> NGINX --> GATEWAY
GATEWAY --> USER & TICKET & SEARCH & AUDIT
USER & TICKET & AUDIT --> PG
USER -. blacklist .-> REDIS
GATEWAY -. rate limit .-> REDIS
TICKET -. attachments .-> MINIO
SEARCH --> ES
USER & TICKET & AUDIT -- events --> KAFKA
KAFKA --> NOTIF & SEARCH & AUDIT
USER & TICKET & NOTIF & SEARCH & AUDIT --> PROM --> GRAF
Остальные диаграммы (ER-модель, sequence diagram сценария создания заявки, deployment
diagram, таблица событий Kafka, поэтапный план реализации, обоснование каждого архитектурного
решения) — в docs/DESIGN.md, это основной design-документ проекта.
docker compose up -d --buildПервая сборка компилирует все 6 сервисов внутри Docker (Maven) — может занять несколько минут. После поднятия стека:
| Что | URL |
|---|---|
| Web-приложение (React SPA) | http://localhost (если порт 80 занят локально — NGINX_PORT=8079 docker compose up -d) — NGINX отдаёт SPA на / и проксирует /api/** на Gateway |
| API через NGINX | http://localhost/api/v1/... |
| API напрямую через Gateway | http://localhost:8080 |
| Frontend напрямую (минуя корневой NGINX) | http://localhost:5173 |
| Grafana | http://localhost:3000 (admin/admin) |
| Prometheus | http://localhost:9090 |
| Kibana | http://localhost:5602 |
| MinIO Console | http://localhost:9001 |
| MailHog (перехват писем) | http://localhost:8025 |
Пример сквозного сценария через Gateway:
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"demo@helpdesk.io","password":"password123","fullName":"Demo User","department":"IT","position":"Engineer"}'
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"demo@helpdesk.io","password":"password123"}'
# -> access/refresh token, далее Authorization: Bearer <token> на /tickets, /search, /audit-logsГлобальный Maven не требуется — используйте Maven Wrapper:
./mvnw clean verify # Linux/macOS
mvnw.cmd clean verify # WindowsИнфраструктура для локального запуска сервисов из IDE:
docker compose up -d postgres kafka redis elasticsearch minio minio-init mailhog| Сервис | Прямой порт | Swagger UI | Назначение |
|---|---|---|---|
| Frontend (React SPA) | 5173 | — | Enterprise UI: дашборд, заявки, поиск, пользователи, мониторинг |
| Gateway | 8080 | — (проксирует) | Единая точка входа, JWT-фильтр, rate limiting, роутинг |
| User Service | 8081 | http://localhost:8081/swagger-ui.html |
Регистрация, логин, refresh/logout, профиль, RBAC |
| Ticket Service | 8082 | http://localhost:8082/swagger-ui.html |
CRUD заявок, комментарии, вложения (MinIO) |
| Notification Service | 8083 | http://localhost:8083/swagger-ui.html |
Kafka-consumer → email-уведомления (Thymeleaf-шаблоны) |
| Search Service | 8084 | http://localhost:8084/swagger-ui.html |
Индексация в Elasticsearch, полнотекстовый поиск/фильтры/агрегации |
| Audit Service | 8085 | http://localhost:8085/swagger-ui.html |
Consumer всех доменных событий + API истории изменений |
Все обращения "снаружи" по назначению идут через Gateway (8080) или NGINX (80); прямые
порты сервисов открыты для отладки/Swagger и для доступа изнутри docker-сети.
Production-grade enterprise UI поверх существующего backend API — без единой правки
Java-кода (см. docs/DESIGN.md; backend остаётся как задокументировано выше).
| Слой | Технология |
|---|---|
| Язык / сборка | TypeScript (strict), React 19, Vite 8 |
| Роутинг | React Router 7 (data router, protected/role-gated routes) |
| Серверное состояние | TanStack Query v5 (кэш, инвалидация, optimistic updates) |
| Клиентское состояние | Zustand (auth/theme/UI/notifications stores, persist в localStorage) |
| HTTP | Axios — единый клиент с JWT-интерцептором и автоматическим refresh на 401 |
| Формы | React Hook Form + Zod-схемы |
| UI-кит | TailwindCSS 4 + shadcn/ui-стиль поверх Radix UI примитивов |
| Таблицы | TanStack Table v8 (сортировка, серверная пагинация) |
| Графики | Recharts (Pie/Bar/Area) — единая цветовая семантика со статус-бейджами |
| Анимации | Framer Motion (переходы страниц, мобильное меню) |
| Command Palette | cmdk (⌘K / Ctrl+K) |
| Toasts | react-hot-toast |
| Даты | Day.js (+ relativeTime, ru locale) |
| Линтинг/форматирование | ESLint 10 (flat config, typescript-eslint) + Prettier |
frontend/src/
├── app/ # providers, router, query client, theme provider
├── layouts/ # AppLayout (sidebar/topbar/breadcrumbs), AuthLayout
├── components/
│ ├── ui/ # shadcn-style примитивы (Button, Dialog, Select, Table, ...)
│ └── common/ # DataTable, SearchInput, Pagination, StatusBadge, ConfirmDialog, ...
├── features/ # по одной папке на бизнес-фичу
│ ├── auth/ # login/register, JWT refresh, useCurrentUser
│ ├── dashboard/ # stats, charts, activity feed, audit API
│ ├── tickets/ # список/детали/CRUD/комментарии/вложения
│ ├── users/ # admin-панель пользователей
│ ├── search/ # Elasticsearch-поиск + агрегации
│ ├── notifications/ # bell dropdown, toast, история (client-derived, см. ниже)
│ ├── monitoring/ # /actuator/health по всем сервисам
│ └── profile/ # профиль, тема оформления
├── store/ # zustand: authStore, themeStore, uiStore, notificationStore
├── services/ # apiClient (axios + interceptors), authEvents
├── types/ # ApiResponse<T>/PageResponse<T>, доменные типы (Ticket, User, ...)
└── hooks/, utils/, constants/, styles/
react-router-dom (createBrowserRouter), защищённые роуты через ProtectedRoute
(редирект на /login без токена) и RoleGuard (ADMIN/MANAGER-only для /users,
/monitoring). Тяжёлые страницы подключены через React.lazy + Suspense —
код-сплиттинг по фичам, а не один бандл. ErrorBoundary оборачивает <Outlet/>
на уровне layout'а.
- Серверное состояние — исключительно TanStack Query: ни одного
useEffectдля загрузки данных, кэш-инвалидация поqueryKey,optimistic updateна смене статуса заявки с rollback при ошибке. - Клиентское состояние — Zustand: JWT-токены и профиль (
authStore, персистится), тема (themeStore), состояние сайдбара/command-palette (uiStore). - Auto-refresh JWT — единый axios-интерцептор ловит 401, дедуплицирует
параллельные refresh-запросы (
refreshPromise-мьютекс), ретраит исходный запрос и обновляет токены в сторе; при неудаче —authEvents.emitSessionExpired()→ редирект на/login. - Notifications без изменения backend — у
notification-serviceнет REST API (это чистый Kafka-consumer → email). Панель уведомлений опрашиваетGET /ticketsкаждые 20с и сравнивает снэпшоты (useNotificationWatcher), формируя реальные события (назначение, смена статуса, новая заявка для ADMIN/MANAGER) — без единой моковой записи, но и без правок Java-кода.
Реализовано строго поверх существующих эндпоинтов (без правок Java):
GET /api/v1/usersиGET /api/v1/ticketsне поддерживают server-side поиск — для этого есть отдельнаяSearch-страница на Elasticsearch; списки Users/Tickets используют быстрый client-side фильтр по уже загруженной странице.- Смена статуса заявки разрешена только исполнителю или ADMIN/MANAGER
(
TicketApplicationService.changeStatus) — UI скрывает контрол статуса для автора заявки без назначения, а не показывает его с последующим 403. - В
UserManagementUseCaseнет смены роли, реактивации после деактивации и смены пароля — соответствующие действия не выведены в UI, а не имитированы моками. GET /api/v1/audit/logs*доступен только ADMIN/MANAGER — вкладка "История" на заявке и Activity Feed на дашборде для EMPLOYEE заменены на собственные заявки./actuator/healthкаждого сервиса проксируется через NGINX по same-origin путям (/health/<service>/..., см.infra/nginx/nginx.conf) — часть сервисов не отдаёт permissive CORS для прямых cross-port запросов из браузера.
| Dashboard | Ticket detail |
|---|---|
![]() |
![]() |
| Search (Elasticsearch) | Users admin |
|---|---|
![]() |
![]() |
| Dark theme | Mobile |
|---|---|
![]() |
![]() |
cd frontend
npm install
npm run dev # Vite dev server :5173, проксирует /api и /health/* на локальные сервисы
npm run build # tsc -b && vite build -> frontend/dist (code-split, ~155KB gzip основной бандл)
npm run lint # ESLint
npm run format # Prettier --writeСобирается и разворачивается вместе с остальным стеком — отдельного запуска не требует:
docker compose up -d --buildfrontend/Dockerfile— multi-stage:node:20-alpine(npm ci && npm run build) →nginx:alpine, отдающийdist/со SPA-fallback (try_files ... /index.html).- Корневой
infra/nginx/nginx.confмаршрутизирует/на контейнерfrontend,/api/**и/actuator/**— наgateway,/health/<service>/**— напрямую на каждый backend-сервис (см. "State Management" выше). - Frontend-контейнер также содержит собственный
/apiи/health/*proxy кgateway/сервисам — работает самостоятельно, даже если открыт напрямую (http://localhost:5173), в обход корневого NGINX.
helpdesk-platform/
├── pom.xml # parent POM: dependencyManagement, plugins, Java 17
├── common/ # общая библиотека: Kafka-события, ApiResponse<T>, исключения, JwtVerifier
├── gateway/ # Spring Cloud Gateway
├── user-service/ # auth, users, RBAC
├── ticket-service/ # tickets, comments, attachments (MinIO)
├── notification-service/ # Kafka consumer -> email
├── search-service/ # индексация в Elasticsearch + поиск
├── audit-service/ # audit log
├── frontend/ # React 19 + TypeScript SPA (см. раздел Frontend)
│ ├── src/
│ ├── Dockerfile # multi-stage: npm build -> nginx:alpine
│ └── nginx.conf # SPA fallback + /api, /health/* proxy
├── infra/
│ ├── nginx/nginx.conf # корневой reverse-proxy: "/" -> frontend, "/api" -> gateway
│ └── monitoring/{prometheus,grafana}/...
├── docker-compose.yml
├── .github/workflows/ci.yml
└── docs/
├── DESIGN.md # design-документ: требования, архитектура, диаграммы, ADR
└── screenshots/ # скриншоты frontend (см. раздел Frontend)
Каждый сервис следует единой пакетной структуре (Hexagonal Architecture):
<service>/src/main/java/.../
domain/ # сущности/value objects, чистая бизнес-логика, без Spring
application/port/{in,out}/ # use-case интерфейсы и порты наружу
application/service/ # реализация use-case'ов
adapter/in/web/ # REST controllers, DTO, mapper
adapter/in/messaging/ # Kafka listeners (где применимо)
adapter/out/persistence/ # JPA entities, repositories, мапперы
adapter/out/messaging/ # Kafka producers
config/ # Security, Kafka, OpenAPI, MinIO/Redis config
53 unit/integration теста, 0 падений, во всех 8 модулях (JUnit 5 + Mockito + AssertJ),
измерение покрытия через JaCoCo (target/site/jacoco/ в каждом модуле после mvn verify).
Категории тестов: application-service (use-case логика на моках портов), controller-тесты
(@WebMvcTest + реальный SecurityConfig + MockMvc — проверяют реальную цепочку Spring
Security, а не только замоканные use case'ы), а также Testcontainers-интеграционные тесты
(реальные PostgreSQL/Kafka/Elasticsearch в контейнерах) и @EmbeddedKafka-тесты для
consumer-логики. См. известные ограничения
по поводу Testcontainers на этой машине.
./mvnw clean verify.github/workflows/ci.yml — пайплайн из 5 джобов на GitHub-раннерах (нативный Linux Docker,
поэтому все Testcontainers-тесты выполняются полностью):
- build-and-test —
mvn clean verify, публикация surefire/JaCoCo отчётов как артефактов - static-analysis — SpotBugs (информационно,
continue-on-error) - docker-build — сборка образов всех 6 сервисов
- deploy-local-smoke-test — поднимает весь стек, дожидается healthy Gateway, проверяет реальным HTTP-запросом через NGINX → Gateway → User Service
- docker-push — отключён по умолчанию (
if: false); включается добавлениемDOCKERHUB_USERNAME/DOCKERHUB_TOKENв секреты репозитория
Ниже — артефакты конкретной машины разработки (Windows + Docker Desktop), не дефекты кода; на GitHub Actions (нативный Linux) всё перечисленное работает без исключений:
- Testcontainers через
npipeна этом Docker Desktop не поднимает контейнеры (Could not find a valid Docker environment) — соответствующие integration-тесты (*IntegrationTestс@Testcontainers) написаны и включены в реактор, но исключены из локальногоmvn test. - Порты
5432/9200/5601/80были заняты другими локальными сервисами на машине разработки — сервисы вынесены на альтернативные хост-порты (5433/9201/5602,NGINX_PORT); внутри docker-сети все обращения идут по стандартным портам/именам контейнеров.
Не реализовано намеренно (см. docs/DESIGN.md, раздел «Импровизация») — рассматривалось как
избыточное для текущего масштаба или не давало достаточной ценности при заданных сроках:
- Outbox Pattern для продюсеров событий (устраняет проблему двойной записи БД + Kafka)
- OpenTelemetry + Jaeger — полноценная распределённая трассировка (сейчас — единый correlation-id через MDC/заголовки, без span-дерева)
- Resilience4j / Circuit Breaker на исходящих вызовах (сейчас между сервисами нет синхронных вызовов в горячем пути, поэтому не было явной необходимости)
- API-версионирование (
/api/v2/...) — задел есть (/api/v1/...), вторая версия не нужна - CQRS/SAGA — намеренно не введены: оркестрация текущих сценариев не требует их сложности
- Feature Flags
- Keycloak / OAuth2 вместо собственной JWT-реализации — рассмотрено, но для demo-масштаба собственная реализация давала больше контроля над учебной/демонстрационной ясностью кода





