Примеры реализации службы управления опросами

Примеры реализации службы управления опросами

1. Предпосылки проекта и выбор технологий

Сервис анкетирования является ключевым сервисом медицинской платформы, обрабатывающим результаты, которые пациенты сообщают сами (PRO, Patient-Reported Outcome), и отвечает за весь жизненный цикл анкеты от определения формата анкеты до отправки, сбора ответов и завершения. Оценка, статистика и обработка отчетов выделены в отдельный метрический сервис, а реальные отправки уведомлений, чатов, SMS/электронной почты поручены отдельному коммуникационному сервису, чтобы четко разделить ответственность и сосредоточиться на 'определении и сборе данных анкеты'.

Для обеспечения согласованности данных и масштабируемости в среде, интегрированной с множеством внутренних микросервисов и внешних устаревших систем медицинской информации (EMR), мы стратегически выбрали следующий технологический стек.

Java 21 & Spring Boot 3.5.13 : Мы обеспечили надежность сервиса на основе последней LTS среды выполнения и стабильной экосистемы Spring.

Spring Cloud 2025.0.0 (OpenFeign) : Синхронная связь между микросервисами обрабатывается декларативным образом, что повышает читаемость и поддерживаемость.

PostgreSQL & JPA/Hibernate & QueryDSL 5.0 : Сложные модели доменов анкеты управляются объектно-ориентированно, и реализован типобезопасный динамический запрос. Колонка JSON обрабатывается с помощью hypersistence utils для гибкого сохранения изменяемых свойств анкеты.

Apache Kafka : Асинхронная передача событий между сервисами снижает связанность и гарантирует конечную согласованность.

OAuth2 и Keycloak: Для интегрированной аутентификации (SSO) и авторизации в распределенной среде был применен отраслевой стандартный протокол OAuth2.

Redis · ShedLock · MapStruct · Jasypt: Для обеспечения эксплуатационной надежности были применены вспомогательные технологии, такие как кэширование, распределенная блокировка расписания, объектное сопоставление и шифрование конфигурации.

2. Применение ключевых технологий и проектирование архитектуры

2.1 Многомодульная и шестиугольная архитектура, ориентированная на домен

Чтобы защитить бизнес-логику от технических зависимостей, была принята многомодульная структура, сосредоточенная на домене. Это отражает концепцию шестиугольной (порто-адаптерной) архитектуры, где основная логика домена сосредоточена в доменном модуле, а персистенция базы данных (store-jpa) и внешняя интеграция (proxy) отделены в заменяемые адаптеры.

Сервис состоит из 9 модулей в зависимости от ответственности: domain (модель домена, порты), store-jpa (адаптер персистенции), proxy (внешняя интеграция, адаптер публикации событий), feature (варианты использования), facade (REST, прием событий), client/event (договоры и события, используемые другими сервисами), scheduler (пакетная обработка), boot (сборка, точка входа). Уровень использования зависит только от интерфейсов домена, а фактическая реализация вводится модулем boot (DIP), поэтому даже изменение целевых интеграций не требует изменений в основной логике.

2.2 Разделение ответственности команд (Command)/запросов (Query) на основе CQRS

CQRS с использованием паттерна был применен в соответствии с особенностями домена обследования, где требования к записи и чтению различаются. Изменения данных выполняются в нормализованной модели команд (таблицы cm_*), а запросы обрабатываются в денормализованной модели запросов (представления qm_*), оптимизированной для экрана. Обе модели синхронизируются через события, поддерживая итоговую согласованность.

Таким образом, сложные списки и запросы не нагружают транзакции команд, и модель запросов может быть свободно денормализована в соответствии с требованиями экрана. Отбор целевых команд постоянно выполняется на основе модели команд, гарантируя актуальность данных.

2.3 Изоляция и защита данных через многопользовательский доступ и мягкое удаление

Для среды, в которой несколько медицинских учреждений и клинические единицы делят одну платформу, общая базовая сущность была спроектирована так, чтобы идентификатор арендатора (учреждение/отделение/этап) автоматически вводился во все данные. Это позволяет изолировать данные на уровне арендатора без вмешательства в код приложения.

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

3. Основной опыт реализации функций и решения проблем

3.1 Безопасное управление версиями форм опроса (шаблон Master/Snapshot)

Формы опроса подвергаются изменениям в процессе эксплуатации, однако для отправленных форм, на которые пациенты уже ответили, существует строгая необходимость сохранить согласованность с формой на момент отправки. Это было решено с помощью паттерна 'Master/Snapshot'.

Редактирование (Master) : Автор свободно редактирует основную форму. Master содержит указатель на текущую активную снимок.

Публикация (Publish) : В момент публикации создается неизменяемый (immutable) Snapshot, который копирует содержимое Master. Все последующие отправленные опросы фиксируются на этом снимке.

Гарантия согласованности ответов : Ответы пациентов всегда ссылаются на снимок на момент отправки, поэтому, даже если форма изменится позже, значение предыдущих ответов не будет нарушено.

Была также реализована возможность предварительного просмотра для проверки перед публикацией, а также поток, в котором редакционные версии объединяются с Master, что позволяет одновременно достигать бесшовной доработки форм и целостности данных в процессе эксплуатации.

image1.png

3.2 Жизненный цикл отправки, назначения и сбора ответов на опросы

Опрос был смоделирован по этапам 'назначение → задача → состояние'. Задачи создаются на основе назначения (кто кому и с каким графиком отправляет), а прогресс выполнения задачи (не начато/временное сохранение/отправлено/завершено и т.д.) управляется отдельным контейнером состояния.

Ответы нормализованы в зависимости от типа вопроса: для открытых вопросов хранятся как значения, для закрытых — как наборы выбираемых вариантов, чтобы последующая оценка и статистика обрабатывались последовательно. Также завершение опроса/принудительное завершение/истечение срока были явно разделены в юз-кейсе, а планировщик автоматически выполняет завершение/истечение/окончание связи, чтобы снизить операционную нагрузку.

image2.png

3.3 Интеграция с внешними унаследованными медицинскими информационными системами

Мы интегрировались с унаследованной EMR существующих медицинских учреждений, однако между микросервисами не используются физические внешние ключи (FK), а только логические ссылки на основе ID. Интеграция с внешними системами осуществляется через отдельный адаптер-сервис с использованием REST (Feign), чтобы изменения во внешних системах не могли напрямую повлиять на основную доменную область, создавая уровень защиты от коррупции (ACL).

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

4. Оптимизация инфраструктуры и операций

4.1 Устранение зависимости между сервисами через Apache Kafka

Когда в домене происходит значимое событие (публикация формата, отправка опроса, завершение ответа, завершение кейса и т.д.), оно публикуется как доменное событие. Службы оценки и статистики, а также уведомлений и чата подписываются только на необходимые события, выполняя свои обязательства, что устраняет зависимость от прямых вызовов между сервисами, и сбой в одном не передается на другой.

С другой стороны, события изменения информации пользователя/организации подписываются основной службой, обновляя модель поиска, поддерживая тем самым результирующую согласованность с мастер-данными. События для преобразования в стандартный формат обмена медицинской информацией (FHIR) также публикуются, обеспечивая расширяемость внешних интеграций.

image3.png

4.2 Инфраструктура для стабильности операций (Redis · ShedLock · Outbox)

Кэширование Redis: Кэшируя часто запрашиваемые общие данные (с применением TTL), мы повысили производительность запросов и снизили нагрузку на базу данных.

ShedLock : В многопользовательской среде мы гарантировали уникальность задач с помощью распределенной блокировки, чтобы планировщик, такой как завершение и отправка, не выполнялся несколько раз.

Шаблон Outbox : Используя шаблон Outbox общих библиотек, мы реализовали надежную публикацию без потерь событий, объединив изменения данных и публикацию событий в одной транзакции.

4.3 Безопасность и аутентификация

Внешние точки доступа защищены сервером ресурсов OAuth2, что позволяет только проверенным токенам (JWT), а контроль доступа на основе ролей разделяет права доступа для пациентов, медицинского персонала, операторов и администраторов. Внутренние вызовы между сервисами осуществляются через токены сервиса в формате client_credentials, которые автоматически внедряются интерсептором Feign, что позволяет отделить бизнес-логику от кода аутентификации. Чувствительные значения конфигурации, такие как информация о доступе к базе данных, хранятся в зашифрованном виде (Jasypt), а учетные данные и внутренние конечные точки внедряются только как переменные окружения, чтобы не подвергать их раскрытию в исходном коде.

4.4 Стратегия распределения модулей на основе контрактов

Коммуникационные контракты (интерфейсы Feign) и схемы событий, которые другие сервисы могут подписаться, разделены на клиентский/событийный модули и развернуты в внутреннем хранилище артефактов. Сервисы-абоненты используют этот артефакт в качестве зависимости, что делает изменения API видимыми на этапе компиляции и повышает стабильность интеграции.

5. Заключение и результаты

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

Целостность данных : Шаблон Master/Snapshot сохраняет согласованность прошлых ответов, даже если формат изменяется.

Устранение связности : Мы снизили связанность между сервисами и достигли изоляции отказов с помощью асинхронной связи на основе событий Kafka.

Обеспечение масштабируемости : С помощью гексагональной многомодульной структуры и многопользовательского проектирования мы создали основу для гибкого реагирования на увеличение числа новых интеграционных объектов или учреждений без изменения основной логики.

Опыт, полученный в этом проекте, основанной на проектировании с акцентом на домены, CQRS и архитектуре на основе событий, станет ключевым активом для проектирования и построения различных систем, работающих с комплексными доменами в будущем.

conley

Site footer