Привет, я - Алмаз Мустакимов, ведущий разработчик одного из бизнес-центров в компании «БАРС Груп». Мы более года работаем над мобильным приложением, которое фактически позволяет получить любые услуги здравоохранения в режиме единого окна, без многочасовых квестов по поликлиникам. Цель проекта - создать платформу, которая содержит все сведения о пациенте и помогает ему записаться к врачу, вызвать скорую, скачать медицинские документы, воспользоваться услугами телемедицины и не только. Наша компания развивает направление здравоохранения давно. Основной разработкой является медицинская информационная система, которая работает в закрытом контуре с большими данными и создает единую информационную среду для сотрудников медорганизаций. Мы решили расширить количество пользователей, которые могут взаимодействовать с системой и получать медицинские услуги без посещения врача. Так возникла идея мобильного приложения для пациентов государственных и частных медицинских учреждений. В проекте я пишу бэкенд мобильного приложения (BE Mapp) на PHP. В этой статье хочу раскрыть содержимое превью, примененные технологии и некоторые детали реализации. Надеюсь, опыт нашей команды поможет разработчикам, перед которыми стоят схожие задачи подключения большого количества внешних пользователей к закрытым информационным системам (ИС).   На первом этапе нужно определиться с архитектурой будущей ИС. Это может оказаться не такой простой задачей. Например, в нашем случае, если с концепцией и выполняемыми функциями понимание было, то связь с внешними системами вызывала множество вопросов. Больше всего нас заботило, как оставаться отзывчивыми и доставлять данные до клиента, при этом не потерять производительность во внешних системах и не зависеть от них. Так мы пришли к 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, получаем удобный пользовательский интерфейс, с которым можно взаимодействовать и валидировать спецификацию. Кратко описать можно так: Авторизуем пользователя. Для этого интегрируемся с ЕСИА по протоколу OAuth2; Сообщаем внешним информационным системам о новом пользователе по HTTP; Слушаем  Kafka и обрабатываем поток данных; Пользователь получает свои медицинские документы, у нас есть REST API. Клиент написан на Flutter. Этот инструмент позволяет создавать приложения для разных платформ. Умеет работать с  OpenApi, есть реализация генерации кода из спецификации.  Нам удалось достигнуть поставленной цели и реализовать систему, которая сейчас в режиме препродакшн развернута у заказчиков. Мобильное приложение публикуется в сторах, к примеру на Android. Внешним системам, с которыми мы работаем, достаточно предоставлять данные, используя kafka-php, согласно нашей OpenApi-спецификации (в полном объеме или частично).   Разработчик