Привет, я - Алмаз Мустакимов, ведущий разработчик одного из бизнес-центров в компании «БАРС Груп». Мы более года работаем над мобильным приложением, которое фактически позволяет получить любые услуги здравоохранения в режиме единого окна, без многочасовых квестов по поликлиникам. Цель проекта - создать платформу, которая содержит все сведения о пациенте и помогает ему записаться к врачу, вызвать скорую, скачать медицинские документы, воспользоваться услугами телемедицины и не только.

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

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

В проекте я пишу бэкенд мобильного приложения (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-спецификации (в полном объеме или частично).

Комментарии (0)