Информационное сопровождение. Инструкция по настройке взаимодействия МИС с сервисами ТФ ОМС ЯО. Система обмена сообщениями между участниками информационных систем построена на базе платформы RabbitMQ, обеспечивающей высокую производительность и надежность промышленного уровня, имеющей обширный набор компонентов по взаимодействию с ней. (Подробней см. http://rabbitmq.com (описание платформы) и http://www.rabbitmq.com/devtools.html (описание клиентов и инструментов разработчика). Терминология RabbitMQ — платформа, реализующая систему обмена сообщениями между компонентами программной системы (Message Oriented Middleware) на основе стандарта AMQP (Advanced Message Queuing Protocol). МИС – медицинская информационная система МО – медицинская организация ЯО, участвующая в процессе информационного взаимодействия посредствам одного или нескольких участников информационного взаимодействия. Участник информационного взаимодействия (УИВ) – компонент информационной системы МО, представленный отдельной МИС. Описание принципов взаимодействия RabbitMQ реализует обменники (exchanges) и очереди (queues). Взаимодействие осуществляется через обменник с именем TFOMS.Exchange, путем отправки сообщений в очередь ТФ ОМС ЯО с именем TFOMS.Queue.7ADB0C1E-D488-4C2B-97FF-52D003FC47BD, и приемом сообщений из очереди, соответствующего УИВ с именем TFOMS.Queue.{client_id}. Где client_id глобальный уникальный идентификатор (GUID) УИВ. client_id назначается ТФ ОМС ЯО, каждому УИВ согласно предоставленной из МО информации. МО 1 RabbitMQ ТФ ОМС МИС1 (УИВ) УИВ МИС 2 (УИВ) МО 2 Каждый УИВ имеет свой уникальный client_id МИС 1 (УИВ) Рисунок 1. Общая схема информационного взаимодействия УИВ 1 (МО 1) ТФ ОМС УИВ 2 (МО 2) Публикация сообщения в очередь ТФ ОМС Обработка входящего сообщения Результат приема в очередь УИВ 1 Передача сообщения в МО 2 в очередь УИВ 2 Доставка сообщения из МО 1 в МО 2 Обработка Результат приема в очередь ТФ ОМС Сообщение из ТФ ОМС в МО 1 в очередь УИВ 1 Обработка сообщения Результат приема сообщения в очередь ТФ ОМС Получение информации из ТФ ОМС Рисунок 2. Концептуальная схема передачи и обработки сообщений Соединение с RabbitMQ Сервер RabbitMQ размещается в ТФ ОМС. Доступ к нему возможен только внутри защищенной сети VipNET. Для определения IP адреса сервера RabbitMQ в сети VipNET каждому УИВ необходимо взять виртуальный IP адрес узла 76 (ЯрТФОМС) SP TNO. Программная реализация Для взаимодействия с RabbitMQ необходимо реализовать два процесса: 1. Процесс публикации в очередь ТФ ОМС 2. Процесс приема сообщений из очереди УИВ Каждое отправляемое и принимаемое сообщение должно иметь заголовок сообщения и его тело. В заголовке сообщения передается информация следующего содержания: 1. protocol - тип протокола передаваемого в теле сообщения содержимого. (См. Порядок обмена данными между учреждениями здравоохранения Ярославской области) 2. client_id – идентификатор УИВ отправляющего сообщения 3. package_id – идентификатор сообщения. В теле сообщения в формате XML передается само сообщение согласно порядка обмена данными между учреждениями здравоохранения Ярославской области. Пример реализации Все примеры написаны с использованием клиента под платформу .Net. Создание подключения private ConnectionFactory GetFactory() { ConnectionFactory factory = new ConnectionFactory(); factory.UserName = "tf_account"; factory.Password = "tf_account"; factory.VirtualHost = "/"; factory.Protocol = Protocols.DefaultProtocol; factory.HostName = "11.0.0.99"; //Виртуальный IP узла 76 (ЯрТФОМС) SP TNO factory.Port = AmqpTcpEndpoint.UseDefaultPort; return factory; } Отправка сообщения private void SendMessage( string messageBody ) { // Имя обменника string exchangeName = "TFOMS.Exchange"; // Имя очереди ТФ ОМС string queueName = "TFOMS.Queue.7ADB0C1E-D488-4C2B-97FF-52D003FC47BD"; // Ключ маршрутизации. Всегда пустая строка string routingKey = ""; using( IConnection conn = GetFactory().CreateConnection() ) { using( IModel channel = conn.CreateModel() ) { // Настройка обменника и очереди приемника channel.ExchangeDelete( exchangeName ); channel.ExchangeDeclare( exchangeName, ExchangeType.Direct, true, false, null ); channel.QueueDeclare( queueName, false, false, false, null ); channel.QueueBind( queueName, exchangeName, routingKey, null ); // Публикация сообщения // Получение тела сообщения byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes( messageBody ); // Построитель сообщения var builder = new RabbitMQ.Client.Content.BytesMessageBuilder( channel ); // Настройка заголовка сообщения builder.Headers["protocol"] = "A01"; builder.Headers["client_id"] = "E20AD2CF-2936-4329-9AF4-56B2FDAB0F3D"; builder.Headers["package_id"] = Guid.NewGuid().ToString(); // Тело сообщения builder.WriteBytes( messageBodyBytes ); // Публикация сообщения channel.BasicPublish( _exchangeName, _routingKey, ( IBasicProperties )builder.GetContentHeader(), builder.GetContentBody() ); // Завершения сеанса (закрытие подключения) channel.Close( 200, "Goodbye" ); } } } Прием сообщения из очереди УИВ Возможны два подхода к процессу получения. 1. Периодически опрашивать «свою» очередь 2. Через создание получателя Рассмотрим пример на основе создания получателя работающего в отдельном потоке. void _subscribeWorker_DoWork( object sender, DoWorkEventArgs eventArgs ) { var worker = sender as BackgroundWorker; if( worker == null ) { return; } using( IConnection connection = GetFactory().CreateConnection() ) { using( IModel channel = connection.CreateModel() ) { QueueingBasicConsumer consumer = new QueueingBasicConsumer( channel ); //Подключаем "свою" очередь String consumerTag = channel.BasicConsume( "TFOMS.Queue.E20AD2CF-2936-4329-9AF4-56B2FDAB0F3D", false, consumer ); while( true ) { if( worker.CancellationPending ) { break; } try { RabbitMQ.Client.Events.BasicDeliverEventArgs e; // Ожидаю получения сообщения if( consumer.Queue.Dequeue( 1000, out e ) ) { IBasicProperties props = e.BasicProperties; byte[] body = e.Body; // Обработка входящего сообщения Dispatcher.BeginInvoke( new Action<string>( SetSubscribedMessageText ), System.Text.Encoding.UTF8.GetString( body ) ); channel.BasicAck( e.DeliveryTag, false ); } } catch( RabbitMQ.Client.Exceptions.OperationInterruptedException ex ) { break; } } channel.Close( 200, "Goodbye" ); } } }