7 ноября 2014 г.

mORMot

Сегодня я хочу рассказать о mORMot. Надеюсь, эта статья заинтересует вас и подтолкнет попробовать библиотеку.

mORMot — это набор библиотек для разработки программного обеспечения на основе сервис-ориентированной архитектуры (распределенных, слабо связанных заменяемых компонентов, со стандартизированными интерфейсами и протоколами, в данном случае на основе REST). Все библиотеки доступны в виде исходного кода и распространяются по лицензии MPL, GPL или LGPL (на выбор).

Набор библиотек содержит большое количество полезных компонентов: сетевого взаимодействия, доступа к БД, шифрования, сжатия данных, создания PDF, выполнения JavaScript, ORM и многое другое. Все компоненты минимально связаны друг с другом — можно использовать только те, которые нужны. Например, для создания PDF нужно добавить в свой проект всего три файла: SynPDF.pas, SynCommons.pas и SynLZ.pas.

Разработка ведется в основном одним человеком — Арно Буше (Arnaud Bouchez). Желающие помогают ему с кодом разных компонентов. Например, код выполнения JavaScript с помощью движка Spider Monkey написал Павел Машляковский. Я написал код прозрачной аутентификации пользователя в домене Active Directory. Все-таки, открытый исходный код и совместная разработка — это здорово!

В одном из своих приложений я использовал практически весь набор библиотек. С приложением активно работают несколько десятков пользователей, в базе данных SQLite около 20 Гб данных. За время разработки и эксплуатации приложения библиотека на деле подтвердила свое качество и стабильность. Ошибки конечно бывают, но оперативно исправляются.

Именно быстрая (буквально в течение пары дней) и адекватная реакция автора на баги и недоработки является серьезным преимуществом библиотеки. А если что-то не получается, всегда можно спросить совета на форуме. Сравните с тем, как я пытался решить проблему с багом в компиляторе Delphi XE2. В результате, всего через год ошибку починили, в версии XE4, которую нужно купить, чтобы компилятор заработал правильно.

Библиотека имеет хорошую документацию, основной документ «Software Architecture Design» содержит 1500 страниц (в формате PDF). Читать их все на первых порах необязательно, но очень желательно.

Пример использования


Приведу краткий пример для быстрого знакомства с mORMot.

Первым делом следует определить модель данных, с которой будет работать наше приложение. Для этого мы объявляем классы-наследники TSQLRecord. Модель данных мы поместим в отдельный модуль SampleData.pas, общий для клиента и сервера. В нашей модели будет один класс — TSQLSampleRecord. Поля класса из секции published будут участвовать в сериализации, то есть передаваться между клиентом и сервером, сохраняться в базе данных, использоваться ObjectToJSON/JSONToObject и в других местах.
TSQLSampleRecord = class(TSQLRecord)
private
    FQuestion: String;
    FName: String;
    FTime: TModTime;
published
    property Time: TModTime read FTime write FTime;
    property Name: String read FName write FName;
    property Question: String read fQuestion write FQuestion;
end;
У каждого наследника TSQLRecord есть поле ID, это первичный ключ в базе данных.

Для нашего класса TSQLSampleRecord в базе данных будет создана таблица SampleRecord (при вызове TSQLRestServer.CreateMissingTables):
CREATE TABLE SampleRecord (
    ID INTEGER NOT NULL PRIMARY KEY,
    Time INTEGER,
    Name TEXT, 
    Question TEXT
);
Разные классы-наследники TSQLRecord могут сохраняться в разных базах данных.

Далее мы создаем модель — экземпляр TSQLModel, в которой будут собраны вместе наследники TSQLRecord. В модели нужно указывать только классы, сохраняемые в базе данных. Каждый наследник TSQLRecord, добавленный в модель TSQLModel, будет представлен таблицей в базе данных, и сервер будет предоставлять удаленный доступ к нему.
function CreateSampleModel: TSQLModel;
begin
    Result := TSQLModel.Create([TSQLSampleRecord]);
end;
Создаем экземпляр сервера. Сервер будет сохранять и получать классы-наследники TSQLRecord в базе данных.
FDatabase := TSQLRestServerDB.Create(FModel,
    ChangeFileExt(ParamStr(0), '.db3'));
FDatabase.CreateMissingTables();
Теперь мы можем сохранять в базе данных экземпляры TSQLSampleRecord:
var
    LSampleRecord: TSQLSampleRecord;
begin
    LSampleRecord:= TSQLSampleRecord.Create();
    try
        LSampleRecord.Name := NameEdit.Text;
        LSampleRecord.Question := QuestionMemo.Text;
        if Database.Add(LSampleRecord, True) = 0 then
            raise Exception.Create('Error adding the data');
    finally
        LSampleRecord.Free();
    end;
end;
И получать экземпляры TSQLSampleRecord из базы данных:
var
    LSampleRecord: TSQLSampleRecord;
begin
    LSampleRecord := TSQLSampleRecord.Create(Database,
        'Name=?', [NameEdit.Text]);
    try
        if LSampleRecord.ID = 0 then
            QuestionMemo.Text := 'Not found'
        else
            QuestionMemo.Text := LSampleRecord.Question;
    finally
        LSampleRecord.Free();
    end;
end;
Немного изменив код, мы можем перейти к клиент-серверной архитектуре на основе REST. Модель данных будет общая для клиента и для сервера. На сервер мы добавим создание экземпляра TSQLHttpServer, а на клиенте вместо TSQLRestServerDB будем использовать TSQLHttpClient.

Организовываем доступ к серверу по протоколу HTTP:
FServer := TSQLHttpServer.Create('80', [FDatabase], 
    '+', HTTP_DEFAULT_MODE);
Подключение клиента к серверу:
FDatabase := TSQLHttpClient.Create('localhost', '80', FModel);
FDatabase.SetUser('User', 'synopse');
Здесь описана только очень небольшая часть возможностей mORMot, только чтобы заинтересовать читателя. Подробней лучше почитать в документации, посмотреть примеры.

Полный код примеров, части из которых были показаны выше:
01 - In Memory ORM,
02 - Embedded SQLite3 ORM,
04 - HTTP Client-Server.

Быстрая сборка примеров:
  1. Скачайте архив текущей версии исходного кода с GitHub — файл mORMot-master.zip. Распакуйте архив в любое удобное место.
  2. Скачайте файл sqlite3obj.7z и распакуйте его в папку mORMot-master, получившуюся на предыдущем шаге.
  3. Откройте нужный файл проекта в Delphi (например, mORMot-master\SQLite3\Samples\02 - Embedded SQLite3 ORM\Project02.dpr) и в свойствах проекта добавьте в Search path пути ..\..\;..\..\..\, должно получиться, как на рисунке ниже.
  4. Для запуска примеров «13 - StandAlone JSON SQL server» и «17 - TClientDataset use» нужна база данных test.db3. Она создается при запуске тестов, файл проекта mORMot-master\SQLite3\TestSQL3.dpr.



В следующей статье я планирую описать код аутентификации пользователя в домене Active Directory, написанный мной для mORMot.

12 комментариев:

  1. Для изучения примеров нужна база test.db3.
    Помогите плиз её найти.

    ОтветитьУдалить
    Ответы
    1. БД должна создаться автоматически при первом запуске сервера.

      Удалить
    2. Покажите тогда код, где она создаётся... Я не нашел!

      Удалить
    3. Сейчас понял, о чем вы.
      База test.db3 создается при запуске тестов, проект
      mORMot-master\SQLite3\TestSQL3.dpr.

      Удалить
  2. Хорошая статья, давно интересно было прочитать, но времени мало очень, пиши ещё, спасибо

    ОтветитьУдалить
  3. Классная библиотека. Нагрузил парой тысяч корпоративных клиентов http/rest. Работает как часы.

    ОтветитьУдалить
  4. Да, библиотека хорошая, но вопросов тьма...
    Готов финансировать небольшой проект на mORMot, может кому интересно будет...откликнитесь.

    ОтветитьУдалить
  5. Мил человек, подскажи, как сделать каскадное удаление? Уже всю голову сломал, документацию перерыл, но не хочет TRecordReferenceToBeDeleted нормально отрабатываться.
    Задача - есть дерево, составленное из всяких TSQLRecord, нужно, чтобы при удалении узла убивались все потомки.

    ОтветитьУдалить
    Ответы
    1. Так, разобрался с TRecordReferenceToBeDeleted, и нашёл ТАКИЕ грабли..

      function TSQLRecord.RecordReference(Model: TSQLModel): TRecordReference;
      begin
      if (self=nil) or (fID<=0) then
      result := 0 else begin
      result := Model.GetTableIndexExisting(PSQLRecordClass(Self)^);
      if result>63 then // TRecordReference handle up to 64=1 shl 6 tables
      result := 0 else
      inc(result,fID shl 6);
      end;
      end;

      Вот так, по-простому, в первых 6 битах хранит номер!!! таблицы в модели, а в остальных - ID. И что получается ? А ничего хорошего:
      1) Количество таблиц для всяких связок не должно превышать 63.
      2) Если, не дай бог, поменяли модель, то кирдык всем связкам. Удобно, что говорить

      И как выворачиваться, непонятно :(

      Удалить
    2. 1. 63-х таблиц обычно достаточно.
      2. Да, в данном случае нельзя менять порядок таблиц в модели, т.е. можно только добавлять новые.
      Вообще, в mORMot можно делать быстро и просто, а можно сложнее и гибче.
      Я обычно использую Client-Server services via interfaces. Там можно реализовать логику любой сложности. И взаимодействие клиента и сервера более наглядно.

      Удалить
    3. Но ведь в случае интерфейсов есть проблема - каскадное удаление, ссылочная целостность и создание таблиц нужно делать ручками на сервере?
      Кстати, я извратился построить дерево по 200+ таблиц с каскадным удалением на чистом mORMot-е. Если нужно, обращайтесь.

      Удалить