Репликация данных

Внимание

Документация репликации данных имеет статус DRAFT, так как могут быть произведены изменения в API и самом механизме репликации, в связи с чем, необходимо следить за изменениями в сервисе.

В этом разделе представлен материал описывающий механизм репликации и его программный интерфейс.

Основные понятия

Под репликацией данных в случае работы с MEDESK понимается односторонняя синхронизация данных: данные на сервере синхронизируются с БД клиента.

Этот механизм необходим при подключении новых клиентов в том случае, когда на клиентской стороне существуют какие-либо данные, которые клиент хотел бы видеть в системе. Например, в учреждении есть база данных пациентов или предприятий, с которыми оно работает. Естественно, чтобы продолжить продуктивно работать с сервером MEDESK, необходимо чтобы эти данные также были видны при работе в системе. Для этого и существует механизм репликации данных.

В таком механизме предусмотрено последовательное копирование данных в соответствии с описанными моделями и информации о предыдущих репликациях. Для более подробного разбора работы механизма репликации уточним некоторые понятия.

Модель, в терминах данной системы - это строго декларированный набор атрибутов, характеризующих определенный тип объекта или субъекта.

Например, модель пациента содержит такие поля общей информации как “Имя”, “Фамилия” и специальные поля, например, номер карты пациента, поля персональных данных и пр.

Сущность - экземпляр определенной модели, например, данные о пациенте, предприятии и пр.

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

  • Идентификатор репликации (repl.id) - идентификатор, уникальный в рамках сущностей одной модели необходимый для связи сущности имеющейся в, например, БД клиента и на сервере.

    Очевидно, уникальные идентификаторы сущностей в, например, БД клиента и сервера обычно не совпадают. Поэтому при репликации сущности необходимо указывать repl.id. То есть:

    1. Сервер не хранит идентификаторы, которыми сущность определяется в БД клиента.
    1. Клиент не хранит идентификаторы, которыми сущность определяется в БД сервера.

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

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

    <идентификатор предприятия в БД сервера>|<идентификатор сущности в БД клиента>

    Следует учитывать, что после записи в сущность на сервере repl.id - его нельзя изменить извне, как и не меняется идентификатор клиента-предприятия и (как предполагается) идентификатор сущности в БД клиента. После присвоения repl.id сущности он служит только в качестве средства связи между сущностью в БД клиента и сервера.

  • Дата изменения (repl.ts (ts от timestamp) ) - поле содержащее последнее время изменения данных сущности. Необходимо для снижения нагрузки на сервер, т.е. для того чтобы не копировать сущность когда ее время модификации совпадает в данных клиента и сервера.

  • Хеш данных сущности (repl.hash) - строка фиксированной длины представляющая собой хэшированные данные сущности. Необходима в случае, если в данных клиента не проставляется время изменения, т.е. при совпадении хеша в данных клиента и сервера сущность не копируется. (Про расчет хеша данных сущности см. 3 подраздел)

    repl.ts и repl.hash - изменяемые поля в сущности. В процессе репликации, если происходит копирование сущности (т.е. как следует из вышеописанного - данные сущности на сервере устарели), то эти поля необходимо обновить.

    Важно заметить, что в сущности обычно присутствует только одно из полей (хотя наличие и repl.ts, и repl.hash не является ошибкой), но одно из этих полей является обязательным.

    Проверка того, была ли модифицирована сущность начинается со сравнения repl.ts возвращаемой сервером с датой модификации сущности в БД клиента. Если сущность не предполагает наличие даты модификации, то сравнивается хеш данных сущностей на клиенте и сервере. Таким образом проверка по repl.ts приоритетнее проверки по repl.hash, поэтому если в сущности присутствуют оба поля, то проверка по repl.hash игнорируется. ( В действительности клиент решает, какое поле более для него приоритетное, тем не менее рекомендуется использовать только одно из двух и отдавать приоритет сравнению по repl.ts.)

  • Общая информация о репликации (repl.ref) - текст, содержащий данные, необходимые клиенту для сопоставления реплицированной сущности с данными в, например, картотеке или БД клиента. То есть человекочитаемая информация, например, о номере карты пациента в картотеке, таблице в БД из которой были реплицированы данные и пр., т.е. любые дополнительные данные необходимые человеку при работе с реплицированной сущностью. Данное поле включено т.к. все вышеописанные поля используются исключительно в API и неинформативны для пользователя. Данное поле не имеет какой-либо смысловой нагрузки в терминах API и не является обязательным при репликации.

API репликации

На сервере не предусмотрено отдельных маршрутов для реплицирования, все сущности реплицируются клиентом посредством запросов patch и post на сущность. Клиент может только получить информацию о предыдущих репликациях используя следующий запрос:

POST /repl

Получить данные о предыдущих репликациях.

Запрос:

POST /repl HTTP/1.1
Host: api.medesk.md
Cache-Control: no-cache
Content-Type: application/json

{
  "enterprise": [
    '540d5833da9d816b7ee1c771|10000064',
    '540d5833da9d816b7ee1c771|10000074',
    '540d5833da9d816b7ee1c771|10000053'
  ],
  "patient": [
    '540d5833da9d816b7ee1c771|80000433',
    '540d5833da9d816b7ee1c771|80000555'
  ],
  "insurance": [
    '540d5833da9d816b7ee1c771|00296666',
    '540d5833da9d816b7ee1c771|00125543'
  ],
  "role": [
    '540d5833da9d816b7ee1c771|50000001',
    '540d5833da9d816b7ee1c771|50000002'
  ]
}

enterprise и пр. - все поля json представляют собой названия моделей реплицируемых сущностей. (На данный момент для репликации доступны 4 вышеуказанных в json)

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

Ответ:

HTTP/1.1 200 OK
Content-Type: application/json

{
    "enterprise": [{
        "id": "540dc07f42199b1a85da1871",
        "acl": {
            "general": 4,
            "system": 4,
            "subscription": 0,
            "configuration": 4,
            "finance": 0,
            "memberships": 0,
            "replication": 4,
            "acl": 0
        },
        "repl": {
            "id": "540d5833da9d816b7ee1c771|10000064",
            "hash": "d41d8cd98f00b204e9800998ecf8427e"
        }
    }, {
        "id": "540d5833da9d816b7ee1c771",
        "acl": {
            "general": 8,
            "system": 2,
            "subscription": 2,
            "configuration": 8,
            "finance": 2,
            "memberships": 8,
            "replication": 8,
            "acl": 0
        },
        "repl": {
            "hash": "d41d8cd98f00b204e9800998ecf8427e",
            "id": "540d5833da9d816b7ee1c771|10000074",
            "ts": "2013-12-31T20:00:00.000Z"
        }
    }],
    "patient": [{
        "id": "540dca24196da2aa85cc46cf",
        "acl": {
            "general": 4,
            "details": 4,
            "medical": 4,
            "replication": 8,
            "acl": 0
        },
        "repl": {
            "id": "540d5833da9d816b7ee1c771|80000433",
            "ts": "2014-04-15T13:38:51.000Z",
            "hash": "d41d8cd98f00b204e9800998ecf8427e"
        }
    }],
    "insurance": [{
        "id": "540ecc8c637f4b4e8ff7d656",
        "acl": {
            "general": 4,
            "details": 0,
            "configuration": 0,
            "self": 0,
            "replication": 4,
            "acl": 0
        },
        "repl": {
            "id": "540d5833da9d816b7ee1c771|00296666",
            "hash": "d41d8cd98f00b204e9800998ecf8427e"
        }
    }],
    "role": [{
        "id": "540eeba3af24059d930ecb44",
        "repl": {
            "hash": "d41d8cd98f00b204e9800998ecf8427e",
            "id": "540d5833da9d816b7ee1c771|50000001"
        }
    }]
}

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

  • id - идентификатор сущности на сервере, в данном случае необходим для возожности обновления сущности запросом patch
  • acl - права доступа к сущности (см. Общие положения -> Безопасность и контроль доступа -> Access Control List, покрытия и уровни доступа) Некоторые сущности могут не содержать acl, это не является ошибкой и значит только то, что предоставлен доступ к редактированию и добавлению этих сущностей.
  • repl - информация о предыдущей репликации (см. выше). Как можно заметить, в ответе может не содержаться repl.ts.

Таким образом, для тех сущностей, для которых в ответе получили json с соответствующим repl.id репликация, если необходима повторная, осуществляется посредством запросов patch (обновление) на конкретные сущности используя id этих сущностей.

Для тех сущностей, для которых в ответе не существует соответствующего их repl.id объекта json, репликация осуществляется запросом post (создание).

ВАЖНО: размер json в запросе на репликацию не должен превышать 100 кбайт. По возможности для получения информации по репликации по всем сущностям стоит разбить один запрос на несколько, например, по моделям, т.е. запрос по предприятиям, запрос по пациентам и пр.

По запросам patch и post см. раздел API по соответствующим сущностям.

Вычисление хеша данных сущности

Для расчета repl.hash необходимо закодировать данные сущности, представленные строкой, по алгоритму MD5 для получения строки фиксированной длины (32 символа - шестнадцатеричных цифр.)

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

Например для полисов строка может выглядеть так: #<дата создания>#<действителен с>#<действителен до>#<серия>#<номер>#<выдан>#

Пример последовательности реплицирования данных

Общие положения.

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

После создания пользователя, прежде чем начать процесс репликации необходимо авторизоваться под этим пользователем (см. API -> Авторизация пользователя). После авторизации необходимо передавать заголовок Authorization в каждом запросе к серверу, связанному с репликацией, так как и получение данных по репликации, и изменение и создание сущностей требуют авторизации пользователя.

Далее, в первую очередь необходимо получить данные по репликации, сделав запрос POST /repl, в том случае, если ранее происходила репликация. Если данные реплицируются в первый раз - этот запрос можно не делать.

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

Первая репликация.

Далее, разберем следующий пример.

Предположим, что есть некоторое медицинское учреждение МедКлиника, которое имеет необходимость синхронизировать данные внутренней БД с сервером MEDESK.

Пусть до текущего момента времени МедКлиника регистрировалась в сервисе и но не реплицировала данные на сервер. То есть, допустим, известно следующее:

  • Создан пользователь, под авторизацией которого происходит репликация: replicator.
  • Известно id учреждения на сервере. Допустим, для простоты, что id равен medClinicId. (Такой id рассматривается только для примера, идентификаторы сущностей на сервере тоже являются строками но имеют другой формат.)

И необходимо реплицировать следующее:

  • Данные о самом учреждении.
  • Данные о должностях.
  • Данные о пациентах.
  • Данные о полисах.
  • Данные о предприятиях.

Соответственно так как ранее не происходило репликации данных, последовательность действий будет следующая:

  1. Авторизация под пользователем replicator (см. API -> Авторизация пользователя).
  2. PATCH запрос на сущность учреждения МедКлиника с данными, которые необходимо добавить/обновить.
  3. POST запросы на все сущности, которые необходимо реплицировать.

По запросам PATCH и POST см. раздел API.

Важно:

  • При запросах POST и PATCH необходимо указывать в json дополнительно раздел repl:

    "repl": {
      "id"  : "<repl.id>",
      "ts"  : "<repl.ts>",
      "hash": "<repl.hash>",
      "ref" : "<repl.ref>"
    }
    
  • При запросе POST поле repl.id обязательно.

  • При запросе PATCH поле repl.id должно отсутствовать.

  • При любом из двух запросов одно из полей repl.ts или repl.hash обязательно.

Разберем более подробно на примере репликации пациента:

Допустим, существует пациент, для которого сформированный json выглядит следующим образом (см. API):

{
  "general": {
    "fname": "Иванов",
    "gender": "male",
    "lname": "Иван",
    "mname": "Иванович",
    "timezone": "Europe/Moscow"
  },
  "personalDocuments": {
    "ru": {
      "inn": "123123123123",
      "snils": "123 444444444",
      "pension": "32132132132",
      "passport": {
        "series": "0804",
        "number": "012123",
        "issuedAt": "2014-01-01"
      }
    }
  }
}

Известно ID предприятия - medClinicId, и, допустим, что идентификатор пациента в БД МедКлиники равен 001122. Теперь можно получить уникальный repl.id для этого пациента:

"repl": {
  "id": "medClinicId-001122"
}

, составив его из ID организации, ID сущности в БД организации, и, на усмотрение, дополнительной обезличенной информации, используя какой-либо разделительный знак, например “-” или “|”.

Далее рассмотрим 2 случая:

Cлучай 1. БД МедКлиники имеет поле, которое отображает дату модификации сущности (данных пациента в данном случае).

В этом случае необходимо и достаточно добавить только поле repl.ts, значение которого должно содержать последнюю дату изменения сущности, т.е. если данные вышеописанного пациента были последний раз изменены 01.01.2014 то полный json будет выглядеть так:

{
  "general": {
    "fname": "Иванов",
    "gender": "male",
    "lname": "Иван",
    "mname": "Иванович",
    "timezone": "Europe/Moscow"
  },
  "personalDocuments": {
    "ru": {
      "inn": "123123123123",
      "snils": "123 444444444",
      "pension": "32132132132",
      "passport": {
        "series": "0804",
        "number": "012123",
        "issuedAt": "2014-01-01"
      }
    }
  },
  "repl": {
    "id": "medClinicId-001122",
    "ts": "2014-01-01"
  }
}

(Формат даты см. ISO 8601)

Случай 2. Неизвестно когда последний раз изменялась какая-либо сущность.

В этом случае необходимо добавить поле repl.hash. Как было описано ранее, в repl.hash должен быть записан MD5 хеш со строки, содержащей все данные, которые в дальнейшем могут быть изменены. Как будет сформирована эта строка решает клиентская сторона. На сервере хранится только сам хеш и никаких действий с ним не происходит. Клиентская сторона сопоставляет хеш для проверки того была ли изменена сущность.

Для примера, допустим, что нас интересует, чтобы на сервере всегда были актуальны следующие данные:

  • Фамилия
  • ИНН
  • СНИЛС
  • Номер пенсионного страхового свидетельства
  • Серия паспорта
  • Номер паспорта
  • Кем выдан пасспорт
  • Когда выдан пасспорт

Заметим, что поле ``personalDocuments.passport.issuedBy``(“Кем выдан”, см. API) отсутствует. В таком случае желательно не убирать из строки эти поля, а заполнять или ключевым словом или символом, обозначающим пустое поле.

Теперь, для примера, в качестве разделителя возьмем символ ‘#’ и в ‘null’ для обозначения пустого поля. Тогда строка для хеширования будет выглядеть следующим образом:

#Иванов#123123123123#123 444444444#32132132132#0804#012123#null#2014-01-01#

, и хеш, соответственно:

1621c4411daf29cbe79cac7a8f7ad7d2

т.е. в этом случае к json добавится

"repl": {
  "id"  : "medClinicId-001122"
  "hash": "1621c4411daf29cbe79cac7a8f7ad7d2"
}

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

{
  "general": {
    "fname": "Иванов",
    "gender": "male",
    "lname": "Иван",
    "mname": "Иванович",
    "timezone": "Europe/Moscow"
  },
  "personalDocuments": {
    "ru": {
      "inn": "123123123123",
      "snils": "123 444444444",
      "pension": "32132132132",
      "passport": {
        "series": "0804",
        "number": "012123",
        "issuedAt": "2014-01-01"
      }
    }
  },
  "repl": {
    "id": "medClinicId-001122",
    "hash": "1621c4411daf29cbe79cac7a8f7ad7d2",
    "ref": "Картотека 2-123"
  }
}

Таким образом, в соответствии с API формируются запросы для всех сущностей, к которым присоединяются соответствующие поля раздела repl.

Последующие репликации.

Подход к формированию запросов и полей repl последующие репликации аналогичен подходу в случае с первой репликацией, кроме следующих аспектов:

  1. В первую очередь необходимо сделать запрос POST на /repl в соответствии с вышеописанным 2. API репликации.
  2. На основе полученного ответа от сервера определить какие сущности существуют, какие необходимо создать, и какие из тех что существуют необходимо обновить. В соответствии с этим сформировать серию запросов POST и PATCH с обновленным разделом repl.
  3. В запросах PATCH на сущности, в которых уже записаны данные репликации, repl.id должен отсутствовать, в то время как остальные поля из repl возможно обновить.

ВАЖНО: Необходимо соблюдать правила репликации, всегда и корректно заполняя и сверяя repl.ts и repl.hash, чтобы не реплицировать заново сущности, которые не обновлялись, чтобы не создавать дополнительную нагрузку на сервер.