Привет, я - Алмаз Мустакимов, ведущий разработчик одного из бизнес-центров в компании «БАРС Груп». Мы более года работаем над мобильным приложением, которое фактически позволяет получить любые услуги здравоохранения в режиме единого окна, без многочасовых квестов по поликлиникам. Цель проекта - создать платформу, которая содержит все сведения о пациенте и помогает ему записаться к врачу, вызвать скорую, скачать медицинские документы, воспользоваться услугами телемедицины и не только.
Наша компания развивает направление здравоохранения давно. Основной разработкой является медицинская информационная система, которая работает в закрытом контуре с большими данными и создает единую информационную среду для сотрудников медорганизаций.
Мы решили расширить количество пользователей, которые могут взаимодействовать с системой и получать медицинские услуги без посещения врача. Так возникла идея мобильного приложения для пациентов государственных и частных медицинских учреждений.
В проекте я пишу бэкенд мобильного приложения (BE Mapp) на PHP. В этой статье хочу раскрыть содержимое превью, примененные технологии и некоторые детали реализации. Надеюсь, опыт нашей команды поможет разработчикам, перед которыми стоят схожие задачи подключения большого количества внешних пользователей к закрытым информационным системам (ИС).
Ставка на Kafka
На первом этапе нужно определиться с архитектурой будущей ИС. Это может оказаться не такой простой задачей. Например, в нашем случае, если с концепцией и выполняемыми функциями понимание было, то связь с внешними системами вызывала множество вопросов.
Больше всего нас заботило, как оставаться отзывчивыми и доставлять данные до клиента, при этом не потерять производительность во внешних системах и не зависеть от них. Так мы пришли к Apache Kafka, но были не уверены, что сможем работать с этим инструментом в нашем стеке из PHP.
После экспериментов с разными Kafka-клиентами для php остановились на nmred/kafka-php и создали форк kafka-php, в котором смогли добиться стабильной работы.
Кроме того, в изначальной версии были проблемы с потерей пакетов размером больше 8 Мб, использованием портов брокера, отличных от дефолтного, и наличием устаревших зависимостей amphp/amp.
Успешно пофиксили, в итоге у нас есть kafka-php, который умеет публиковать (producer), подписываться (consumer) на сообщения и общаться с системами.
Мы приняли философию Spec-First (генерация кода стала возможна только на основе спецификации) и написали OpenApi-спецификацию на все топики Kafka. Да, Kafka этого не требует и работает с любыми данными, но наша цель структурировать и иметь полный контроль - для этого подойдет OpenApi.
Плюсы такого подхода:
возможность генерировать код;
ускоренный процесс разработки;
снижение количества ошибок;
единый документ для всех инструментов.
Все данные из Kafka мигрируют в PostgreSQL. Это отличная СУБД, которая не перестает развиваться.
Особенности обработки данных
Мы научились быстро доставлять данные из разных внешних систем до BE Mapp, которые обновляются в режиме реального времени по пользователям, прошедшим аутентификацию в ЕСИА и предоставившим права на просмотр личных данных. Идентифицированный пользователь получает ключ доступа. Остается обработать информацию из внешних систем и записать в базу данных (БД). Тут нужно выбрать подход к загрузке: из PHP либо на процедурном расширении языка SQL.
В первом случае у нас есть готовые модели данных, сгенерированные из спецификации, которые нужно передать выбранной ORM. В процессе могут возникать ошибки, грозящие потерей данных, или зависимые объекты, получаемые в разных топиках. Принимая решение работать из ORM, важно помнить об этом. Если вы решили не зависеть от внешних систем, это не должно стать для вас проблемой. Но когда у вас сложные структуры данных, то, возможно, это не ваш путь.
Мы выбрали второй вариант. Тут необходимо передать данные из Kafka клиента в БД, но в исходном виде, без обработки. В PostgreSQL это сделать очень просто: есть нативный тип json, jsonb, можно определять составные типы. Таким образом, мы сможем хранить информацию и дальнейшую обработку продолжить в БД. Это гарантирует сохранность данных, даже если возникнут ошибки в процессе работы с ними. Кроме того, мы можем отложить обработку, если еще не получили зависимые объекты.
Реализация:
cursor_queue cursor (p_topic text)
FOR select *
from kafka.queue
where topic = p_topic
and success
and pg_try_advisory_xact_lock(id)
order by created_at for update;
У нас в БД есть схема Kafka, где осуществлена логика работы с данными от внешних источников. Так, в таблице queue содержатся все данные, полученные из Kafka, с которыми мы еще работаем. Затем получаем курсор и приступаем к обработке данных, используем jsonb_populate_recordset.
При успешной загрузке удаляем связанную запись из таблицы:
delete from kafka.queue where current of cursor_queue;
Иначе пропускаем топик и вернемся к нему уже после получения новых данных, за это отвечает триггерная функция.
Далее можно приступить к реализации API. BE Mapp реализует REST API. Тут все также Spec-First, поэтому пишем спецификацию OpenApi. Подключив модуль swagger-ui, получаем удобный пользовательский интерфейс, с которым можно взаимодействовать и валидировать спецификацию.
Процесс обработки BE MAPP
Кратко описать можно так:
Авторизуем пользователя. Для этого интегрируемся с ЕСИА по протоколу OAuth2;
Сообщаем внешним информационным системам о новом пользователе по HTTP;
Слушаем Kafka и обрабатываем поток данных;
Пользователь получает свои медицинские документы, у нас есть REST API.
Клиент написан на Flutter. Этот инструмент позволяет создавать приложения для разных платформ. Умеет работать с OpenApi, есть реализация генерации кода из спецификации.
Итог
Нам удалось достигнуть поставленной цели и реализовать систему, которая сейчас в режиме препродакшн развернута у заказчиков. Мобильное приложение публикуется в сторах, к примеру на Android. Внешним системам, с которыми мы работаем, достаточно предоставлять данные, используя kafka-php, согласно нашей OpenApi-спецификации (в полном объеме или частично).