Глава 12. Создание мобильных бэкендов

В мире мобильной разработки распространена практика, когда существуют выделенные разработчики и команды, отвечающие исключительно за создание мобильных фронтендов (собственно приложений, работающих на устройстве) и бэкендов (веб-сервисов для доступа к данным и размещения части бизнес-логики). По этой причине мы также хотим примерить шляпу бэкенд-разработчика и использовать Delphi для создания современных, масштабируемых, безопасных, отказоустойчивых REST API веб-сервисов с полным доступом к базе данных.

В этой главе будут рассмотрены следующие вопросы:

Цель этой главы — помочь вам понять различные варианты технологий бэкенда в Delphi и получить практические знания о том, как создавать веб-сервисы. Существует еще одна технология веб-сервисов в Delphi, называемая RAD Server, которая будет темой следующей главы.

Технические требования

Как обычно, все библиотеки, рассмотренные в этой главе, поставляются в составе Delphi, и большинство из них доступны уже много релизов. Однако поддержка DataSnap доступна только в Enterprise-версии продукта, а не в Professional и Community Editions.

Исходный код демонстрационных проектов этой главы можно найти на GitHub по следующей ссылке: GitHub-репозиторий книги.

Delphi и многоуровневые архитектуры

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

Зачем вам создавать мобильные бэкенд-сервисы? Многоуровневые архитектуры имеют множество преимуществ. Но что именно представляет собой многоуровневое решение? Это архитектура, в которой клиентские приложения взаимодействуют с серверными приложениями для доступа к базовым ресурсам. В отличие от клиент-серверного приложения, в котором пользовательский интерфейс (UI), бизнес-логика и доступ к базе данных объединены (даже если они, возможно, логически разделены на разные модули, как мы делали для приложения todo в главе 10), в многоуровневом сценарии, где доступ к базе данных находится на сервере, вместе с ядром бизнес-логики, клиентское приложение имеет только UI и ограниченную бизнес-логику, необходимую для поддержки лучшего UI.

Представление клиент-серверной и многоуровневой архитектур приложений
Рисунок 12.1: Представление клиент-серверной и многоуровневой архитектур приложений

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

В многоуровневых архитектурах фактическое серверное приложение является лишь одним из многих элементов общей картины. Очень важно, как развертывается серверное приложение. Если масштабируемость и высокая доступность сервисов являются нашими основными задачами, то нам следует подумать об использовании технологий балансировки нагрузки. В зависимости от количества входящих HTTP-запросов инфраструктура автоматического масштабирования может запускать и останавливать экземпляры этого образа виртуальной машины, адаптируясь к текущей нагрузке.

В таких архитектурах выгодно не поддерживать состояние взаимодействия с клиентским приложением на серверном уровне, поскольку разные запросы, поступающие от одного и того же клиентского приложения, могут приниматься разными экземплярами сервера. Это одна из причин, почему использование протокола HTTP так распространено для веб-сервисов. Другим техническим элементом, который следует учитывать для улучшения масштабируемости и лучших вариантов развертывания в облаке, является использование Linux в качестве целевой платформы, что Delphi поддерживает только в Enterprise Edition.

Работа на низком уровне с WebBroker

Самым простым и универсальным фреймворком для разработки веб-серверов в Delphi является WebBroker. Это базовая технология для многих специализированных типов веб-сервисов, которые можно создавать с помощью Delphi, таких как SOAP XML веб-сервисы, DataSnap и RAD Server. Если вы создаете новое приложение веб-сервера с помощью мастера New WebBroker App, вы можете реализовать произвольную функциональность HTTP-сервера. В нашем случае это будет простой веб-сервис, который предоставит REST API доступ к новой версии приложения Todo.

Вот шаги для начала создания нашего первого серверного приложения WebBroker:

  1. Нажмите File | New | Other в IDE и дважды щелкните на мастере Web Server Application в категории Web.
  2. На первой странице мастера, озаглавленной Platform, у вас есть возможность указать целевые платформы. Windows обязательна, и вы можете добавить Linux, если хотите. Я рекомендую включить его, если ваша версия Delphi поддерживает это.
  3. На второй странице мастера, озаглавленной WebBroker Project type, нам нужно решить, как наш проект будет интегрироваться с веб-сервером. WebBroker предлагает четыре варианта:
    • Apache dynamic link module — это для интеграции с веб-сервером Apache на Windows или Linux
    • Standalone console application — это самодостаточный исполняемый файл на базе технологии веб-сервера Indy без GUI (и по этой причине он лучше подходит для целевой платформы Linux)
    • Standalone GUI application — это также самодостаточный исполняемый файл на базе технологии веб-сервера Indy, но на этот раз с главной формой (это может быть удобнее для отладки на Windows)
    • ISAPI dynamic link library (DLL) — это для интеграции с Microsoft IIS на Windows (этот последний вариант недоступен, если вы выбрали Linux на первой странице)

Генерация автономного проекта

Чтобы начать разработку, давайте сгенерируем консольный сервер WebBroker:

  1. Пока что давайте выберем опцию Standalone application console. Позже мы добавим второй проект Apache в группу проектов.
  2. Если мы выбираем автономную опцию, на последней странице мастера, озаглавленной Port Number, мы можем выбрать протокол связи, HTTP или HTTPS, и порт для использования, по умолчанию это 8080. Также есть кнопка для проверки, доступен ли порт в данный момент.
Страница Port Number мастера Web Server Application
Рисунок 12.2: Страница Port Number мастера Web Server Application
  1. Нажмите на кнопку Test Port, чтобы убедиться, что выбранный порт не используется, и нажмите Finish для генерации проекта сервера WebBroker.
Примечание: Сервер по умолчанию, сгенерированный этим мастером, настроен на использование порта 8080 для HTTP-связи, что является распространенным альтернативным портом вместо порта по умолчанию, который равен 80. Только один сервер может слушать порт 8080 одновременно, поэтому вам может потребоваться изменить этот порт на 8081 или любое другое значение и соответствующим образом обновить вызов клиента. Если вы запустите второй сервер на том же порту, вы получите сообщение об ошибке в момент, когда сервер открывает порт, которое говорит о том, что он уже используется другим сервером на том же компьютере.

Автономный сервер WebBroker

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

  1. Создайте новую папку для проектов. Внутри этой папки создайте три подкаталога: apache, standalone и shared.
  2. Нажмите на кнопку Save All в Delphi. Сохраните модуль ServerConst1 в папку standalone, WebModuleUnit1 в папку shared, и сохраните проект как ToDoWebBrokerStandaloneHTTP, убедившись, что он также сохранен в папке standalone.
  3. Запустите проект веб-сервера на Windows. Вы должны увидеть консольное окно с приглашением ввести одну из опций. Введите start для запуска веб-сервера. Если вы увидите сообщение от брандмауэра Windows, нажмите кнопку Allow или Allow Access, чтобы позволить клиентским приложениям взаимодействовать с сервером.
  4. Если теперь вы откроете свой веб-браузер и введете localhost:8080 или 127.0.0.1:8080 в адресной строке, вы должны увидеть веб-страницу по умолчанию нашего сервера.
Вывод по умолчанию от пустого приложения WebBroker
Рисунок 12.3: Вывод по умолчанию от пустого приложения WebBroker

Вы можете ввести exit в консоли веб-сервера, чтобы остановить проект веб-сервера.

Не очень удобно вводить start после запуска веб-сервера. Давайте сделаем небольшое изменение в главном файле программы, чтобы сервер запускался автоматически.

Выберите View Source из меню Project в IDE Delphi. Прокрутите к концу файла. В процедуре RunServer найдите следующую строку кода в цикле while: StartServer(LServer)

Теперь скопируйте строку и вставьте ее после строки с присваиванием свойства LServer.DefaultPort и непосредственно перед оператором while True do. Таким образом, нам не придется вводить команду start вручную, так как сервер будет запускаться автоматически.

Главный файл программы консольного приложения веб-сервера сложнее, чем обычный файл проекта Delphi, но вам редко приходится его трогать. Автономный веб-сервер Indy создаст веб-модуль, используя предоставленную ссылку на класс, и направит на него входящий URL для обработки.

Реальный код нашего приложения веб-сервера инкапсулирован внутри WebModuleClass, который реализован в модуле WebModuleUnit1. Веб-модуль — это специализированная форма модуля данных. Вы можете разместить различные невизуальные компоненты на этом модуле данных для реализации логики сервера.

Основная структура приложения WebBroker построена вокруг коллекции действий, которые соответствуют различным путям URL в приложении веб-сервера. Эти действия хранятся в свойстве коллекции Actions веб-модуля.

Веб-действие DefaultHandler и его свойства в Object Inspector
Рисунок 12.4: Веб-действие DefaultHandler и его свойства в Object Inspector

Мастер сгенерировал одно действие с включенным свойством Default, что делает его действием, которое вызывается в ответ на любой HTTP-запрос, поступающий на сервер.

В обработчике события OnAction элемента веб-действия мы можем ввести код, который будет выполняться при поступлении HTTP-запроса. Это событие имеет параметры Request и Response, предоставляющие нам информацию о входящем HTTP-запросе веб-браузера и позволяющие нам определить данные и метаданные HTTP-ответа, который мы хотим отправить обратно в веб-браузер.

Ответ может быть чем угодно; это могут быть текстовые данные, такие как HTML или JSON, а также двоичные данные, такие как JPEG или Zip-файл. В сценарии по умолчанию содержимое, которое отправляется обратно, — это HTML. Мы можем изменить обработчик по умолчанию, чтобы он возвращал информацию о том, что это наш сервис ToDo REST API.

Вот как выглядит измененный код; он возвращает немного статической HTML-разметки:

procedure TWebModule1.WebModule1DefaultHandlerAction(
  Sender: TObject; Request: TWebRequest; 
  Response: TWebResponse; var Handled: Boolean);
begin
  Response.Content := '<html>' +
    '<head><title>To-Do REST API</title></head>' +
    '<body>Delphi "To-Do List" REST API</body>' +
    '</html>';
end;

Это просто заполнитель, поэтому простого HTML-текста пока достаточно.

Примечание: Начиная с Delphi 12, вы можете воспользоваться поддержкой многострочных строковых литералов, чтобы записать HTML как одну строку, а не серию строк, соединенных знаком +. Это делает HTML-код гораздо более читаемым.

Добавление поддержки Apache

Прежде чем начать реализацию нашего веб-сервиса, давайте добавим второй проект, модуль Apache:

  1. Щелкните правой кнопкой мыши на узле группы проектов в Project Manager и выберите опцию Add New Project.
  2. Снова дважды щелкните на иконке New Web Server Application.
  3. На первой странице мастера выберите опцию добавления поддержки Linux.
  4. На второй странице убедитесь, что выбрали Apache dynamic link module в качестве типа проекта.
  5. На последней странице мастера вы можете указать версию Apache, которую хотите поддерживать; как правило, вы захотите оставить значения по умолчанию.
  6. Нажмите на кнопку Finish. Проект Apache сгенерирован.

Теперь нам нужно аккуратно его сохранить. Нажмите на Save All и сохраните модуль веб-модуля как WebModuleUnit2 в папке apache и проект как ToDoWebBrokerApache в той же папке apache. Сохраните группу проектов как ToDoWebBrokerGrp.

Теперь мы хотим, чтобы оба проекта использовали один и тот же веб-модуль. Щелкните на WebModuleUnit1 в окне Project Manager и перетащите его на узел проекта Apache. Диалог подтверждения спросит вас, хотите ли вы добавить выбранный модуль в проект. Подтвердите этот запрос.

Теперь щелкните правой кнопкой мыши на WebModuleUnit2 в Project Manager и выберите опцию Remove from project.

Теперь оба проекта используют один и тот же код веб-модуля. Щелкните правой кнопкой мыши на узле Project Group и выберите Build All. В любое время мы можем собрать наш веб-сервер как автономное приложение для тестирования и как модуль Apache для развертывания.

Оба проекта WebBroker в ToDoWebBrokerGrp используют один и тот же веб-модуль
Рисунок 12.5: Оба проекта WebBroker в ToDoWebBrokerGrp используют один и тот же веб-модуль

Добавление клиентского приложения

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

  1. Создайте новую папку restclient в ранее созданной папке, которая уже содержит три другие папки для обоих серверных проектов.
  2. Скопируйте все файлы из проекта ToDoListSQLite из главы 10 в только что созданную папку.
  3. Щелкните правой кнопкой мыши на узле группы проектов в Project Manager и выберите опцию Add Existing Project. Добавьте проект ToDoListSQLite в группу.
  4. Сохраните проект как ToDoListRESTClient.
  5. Сохраните модуль главной формы как uFormToDoRESTClient и измените свойство Name формы на FormToDoRESTClient.
  6. Переместите модули uToDoTypes, uToDoUtils и uDMToDo в папку shared. Добавьте их в оба серверных проекта.
  7. Добавьте модули uToDoTypes и uToDoUtils в клиентский проект.
  8. Теперь щелкните правой кнопкой мыши на проекте и выберите Build All. Все три проекта должны успешно скомпилироваться.

Построение веб-модуля

Существуют различные подходы к архитектуре веб-сервиса, предоставляющего REST API. В нашем случае мы хотим предоставить операции CRUDL для базовых данных ToDo. В нашем примере давайте переключимся на WebModule1 и добавим еще пять элементов веб-действий в коллекцию Actions веб-модуля, которые будут соответствовать базовым операциям в наших данных ToDo. Это операции, которые наш REST-сервер будет предоставлять, используя разные URL.

После добавления новых действий переименуйте их в ActToDoCreate, ActToDoRead, ActToDoUpdate, ActToDoDelete и ActToDoList.

Элементы действий Web Module в Structure View
Рисунок 12.6: Элементы действий Web Module в "Structure View"

В свойстве PathInfo действий введите /ToDo/Create, /ToDo/Read, /ToDo/Update, /ToDo/Delete и /ToDo/List соответственно.

Первым шагом для реализации их обработчиков событий OnAction является предоставление доступа к базовому интерфейсу IToDoData, предоставляемому модулем данных uDMToDo, который, в свою очередь, использует SQLite для хранения данных. Добавьте модуль uToDoTypes в секцию uses интерфейсной части модуля данных, а uDMToDo и uToDoUtils в секцию uses реализации.

Теперь объявите приватное поле FToDoData типа IToDoData в объявлении класса веб-модуля и приватную функцию GetToDoData, которая будет возвращать ссылку на интерфейс, необходимый в реализации всех обработчиков событий элементов веб-действий. Вот код функции:

function TWebModule1.GetToDoData: IToDoData;
begin
  if FToDoData = nil then
    FToDoData := TDMToDo.Create(nil);
  Result := FToDoData;
end;

Мы будем часто использовать эту функцию для реализации различных серверных операций.

Создание нового элемента ToDo

Теперь дважды щелкните на событии OnAction элемента веб-действия ActToDoCreate и введите следующий код, чтобы добавить новый элемент ToDo в базовое хранилище базы данных:

procedure TWebModule1.WebModule1ActToDoCreateAction(
  Sender: TObject; Request: TWebRequest; 
  Response: TWebResponse; var Handled: Boolean);
var
  AToDo: TToDo;
begin
  AToDo.Title := Request.QueryFields.Values['title'];
  AToDo.Category := Request.QueryFields.Values['category'];
  var Id := GetToDoData.ToDoCreate(AToDo);
  Response.Content := Id.ToString;
end;

Теперь, когда вы можете сохранить код, убедитесь, что тестовый проект автономного веб-сервера активен в Project Manager, и запустите его. Теперь вы можете протестировать его с клиентом, имея в виду, что конкретный URL конечной точки следующий:

http://127.0.0.1:8080/ToDo/Create

Вы можете использовать утилиту REST Debugger, доступную в Delphi. В этом случае введите предыдущий URL и на вкладке Parameter добавьте параметры title и category с некоторыми тестовыми значениями. Нажмите на кнопку Send Request. В разделе Response вы должны увидеть ID только что добавленного элемента ToDo.

Альтернативой является использование веб-браузера, в этом случае добавляя параметры непосредственно в URL, используя поле запроса, как в следующем примере:

http://localhost:8080/todo/create?title=milk&category=food

Опять же, результатом будет номер ID нового элемента. Теперь мы можем аналогичным образом реализовать остальные элементы веб-действий.

Чтение данных и другие операции

В действии ActToDoRead мы собираемся извлечь параметр id из запроса, вызвать базовый метод модуля данных и вернуть элемент ToDo, закодированный как JSON (и мы устанавливаем свойство ContentType ответа в application/json, чтобы отправить эту информацию о кодировке обратно клиенту):

procedure TWebModule1.WebModule1ActToDoReadAction(
  Sender: TObject; Request: TWebRequest; 
  Response: TWebResponse; var Handled: Boolean);
var
  AToDo: TToDo;
begin
  var Id := Request.QueryFields.Values['id'].ToInteger;
  if GetToDoData.ToDoRead(Id, AToDo) then
    Response.Content := ToDoToStr(AToDo)
  else
    Response.Content := 'Failed';
  Response.ContentType := 'application/json';
end;

Основная часть работы — это преобразование одиночной записи TToDo в представление JSON, операция, которая выполняется в функции ToDoToStr, которую мы добавили в модуль uToDoUtils:

function ToDoToStr(AToDo: TToDo): string;
var
  ASw: TStringWriter;
  AJtw: TJsonTextWriter;
begin
  ASw := TStringWriter.Create;
  AJtw := TJsonTextWriter.Create(ASw);
  try
    AJtw.WriteStartObject;
    WriteItem(AToDo, AJtw);
    AJtw.WriteEndObject;
    Result := ASw.ToString;
  finally
    AJtw.Free;
    ASw.Free;
  end;
end;

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

Обновленное веб-действие аналогично. Мы извлекаем три значения параметров (Id, Title и Category) и передаем их базовому методу ToDoUpdate модуля данных. То же самое делается для операции удаления, которая требует только значения Id записи.

Последняя операция возвращает список всех элементов to do. Она не ожидает никаких параметров, так как ей нужно получить данные из базы данных и преобразовать их в JSON, используя утилитную функцию ToDosToStr:

GetToDoData.ToDoList(AList);
Response.Content := ToDosToStr(AList);

Наши простые REST API на базе технологии WebBroker готовы. Щелкните правой кнопкой мыши на группе проектов в Project Manager и выберите Build All, чтобы собрать тестовую и развертываемую версии нашего приложения веб-сервера.

Клиент для REST API

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

  1. Запустите автономную версию проекта веб-сервера и дважды щелкните на проекте ToDoListRESTClient в Project Manager, чтобы активировать его.
  2. Удалите модуль данных uDMToDo из проекта и добавьте новый модуль данных.
  3. Измените свойство Name модуля данных на DMToDoWebBrokREST и сохраните его модуль как uDMToDoWebBrokREST.
  4. Добавьте модуль uToDoTypes в секцию uses интерфейсной части модуля данных и модуль uToDoUtils в его часть реализации.
  5. Добавьте интерфейс IToDoData в объявление класса модуля данных. Скопируйте сигнатуры процедур из объявления интерфейса в публичную секцию объявления класса модуля данных и нажмите Ctrl+Shift+C для вызова завершения класса.
  6. В классе главной формы клиентского приложения измените имя модуля в секции uses в части реализации, чтобы использовать новый модуль данных, и соответственно измените реализацию метода GetToDoData:
uses
  uDMToDoWebBrokREST;

function TFormToDoRESTClient.GetToDoData: IToDoData;
begin
  if DMToDoWebBrokREST = nil then
    DMToDoWebBrokREST := TDMToDoWebBrokREST.Create(Application);
  Result := DMToDoWebBrokREST;
end;

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

Переключитесь обратно на модуль данных uDMToDoWebBrokREST и разместите на нем компоненты, необходимые для доступа к REST API. Вам нужны компоненты TRESTClient, TRESTResponse и пять компонентов TRESTRequest.

Переименуйте компоненты модуля данных соответственно в RClientToDo, RRespToDo, RReqToDoCreate, RReqToDoRead, RReqToDoUpdate, RReqToDoDelete и RReqToDoList.

Компоненты REST-клиента на модуле данных
Рисунок 12.7: Компоненты REST-клиента на модуле данных

Установите свойство Response пяти компонентов запроса на тот же объект ответа, RRespToDo.

В компоненте REST-клиента введите 127.0.0.1:8080 в свойство BaseURL. Это для тестирования. В окончательной версии клиентского приложения это значение должно быть обновлено до фактического доменного имени или IP-адреса сервера, на котором работает наше приложение веб-сервера REST API.

В свойстве Resource компонента RReqToDoList введите значение ToDo/List. Теперь убедитесь, что тестовое приложение веб-сервера запущено. Если это так, щелкните правой кнопкой мыши на компоненте RReqToDoList и выберите Execute из его контекстного меню. Вы должны увидеть отображение диалоговой формы с сообщением Response: 200 - OK.

Нам нужно настроить свойства оставшихся компонентов REST-запроса. В компоненте RReqToDoCreate введите ToDo/Create в качестве свойства Resource. Добавьте два параметра в свойство Params и установите их свойства Name в title и category. Сделайте то же самое для всех остальных компонентов.

Параметры различных компонентов TRESTRequest в модуле данных клиентского приложения
Рисунок 12.8: Параметры различных компонентов TRESTRequest в модуле данных клиентского приложения

Последняя часть работы — это написание методов модуля данных. Давайте начнем с простого, метода ToDoCreate. Нам просто нужно присвоить значения параметра в компоненте RReqToDoCreate, вызвать его метод Execute и вернуть результат из свойства Content компонента REST-ответа. Вот код функции:

function TDMToDoWebBrokREST.ToDoCreate(AValue: TToDo): Integer;
begin
  RReqToDoCreate.Params[0].Value := AValue.Title;
  RReqToDoCreate.Params[1].Value := AValue.Category;
  RReqToDoCreate.Execute;
  Result := RRespToDo.Content.ToInteger;
end;

Операции удаления и обновления аналогичны.

Реализация метода ToDoRead, наоборот, требует преобразования JSON-строки, полученной от сервера, в запись TToDo. Чтобы сохранить код чистым, это делается с использованием функции StrToToDo, добавленной в модуль uToDoUtils:

function StrToToDo(S: string): TToDo;
begin
  var ASr := TStringReader.Create(S);
  try
    var AJtr := TJsonTextReader.Create(ASr);
    try
      while AJtr.Read do
        if AJtr.TokenType = TJsonToken.StartObject then
        begin
          Result.Id := StrToInt(ReadStr(AJtr));
          Result.Title := ReadStr(AJtr);
          Result.Category := ReadStr(AJtr);
        end;
    finally
      AJtr.Free;
    end;
  finally
    ASr.Free;
  end;
end;

Используя эту функцию, мы теперь можем реализовать метод ToDoRead относительно простым способом:

function TDMToDoWebBrokREST.ToDoRead(
  Id: integer; out AValue: TToDo): Boolean;
begin
  RReqToDoRead.Params[0].Value := Id.ToString;
  RReqToDoRead.Execute;
  Result := RRespToDo.Content <> 'Failed';
  if Result then
    AValue := StrToToDo(RRespToDo.Content);
end;

Последний метод для реализации — это ToDoList. Здесь мы используем утилитный метод StrToToDos, аналогичный тому, который мы только что видели, для преобразования представления JSON списка и заполнения списка, переданного в параметре AList, полученными элементами:

procedure TDMToDoWebBrokREST.ToDoList(AList: TToDos);
begin
  AList.Clear;
  RReqToDoList.Execute;
  StrToToDos(RRespToDo.Content, AList);
end;

Вот и все. Нажмите на Save all и запустите клиентское приложение REST. Оно должно просто работать! Наше модифицированное клиентское приложение будет вызывать REST-сервер, построенный с помощью WebBroker, который, в свою очередь, использует локальную базу данных.

Фреймворк WebBroker является базовой технологией для других типов приложений веб-серверов, таких как приложение SOAP веб-сервера или различные типы проектов серверов DataSnap, размещенные в веб-сервере. Это самая простая форма HTTP-сервера, которую вы можете построить с помощью Delphi. Итак, давайте теперь перейдем к одному из этих высокоуровневых серверных фреймворков, DataSnap.

Сделай сам с DataSnap

WebBroker хорош для базовой функциональности HTTP-сервера, но чем сложнее система, которую вы хотите построить, тем больше вы смотрите на полные многоуровневые фреймворки Delphi, такие как DataSnap (рассматривается в этом разделе) или RAD Server (рассматривается в следующей главе). Они предоставляют гораздо больше высокоуровневой функциональности, чем вы можете найти в WebBroker. Однако обратите внимание, что высокоуровневые многоуровневые решения доступны только в версиях Enterprise и Architect Delphi, а не в Professional и Community Editions.

Фреймворк DataSnap является частью Delphi с ранних дней и эволюционировал с течением времени. Delphi 3 представила технологию MIDAS, чтобы упростить создание клиент-серверных приложений баз данных. В Delphi 6 эта технология была переименована в DataSnap, а в Delphi 2009 она была полностью переписана. В этой новой архитектуре удаленные методы, публикуемые сервером DataSnap, выглядели как хранимые процедуры базы данных, которые типичная система реляционных баз данных SQL предоставляет. Фреймворк доступа к базе данных dbExpress был повторно использован, и специальный драйвер DBX был построен для обеспечения связи между клиентами и серверами DataSnap. Модель DBX все еще присутствует в текущей реализации DataSnap, но была добавлена более новая REST-ориентированная модель. В DBX вы могли использовать TCP/IP или HTTP для связи между клиентом и сервером. В REST-ориентированной архитектуре DataSnap единственным выбором является использование HTTP или HTTPS.

Когда вы открываете диалог New Items в Delphi, есть три разных мастера в категории DataSnap Server для создания новых серверных проектов:

Построение сервера DataSnap

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

  1. Выберите мастер DataSnap WebBroker Application и нажмите кнопку OK.
  2. На первой странице мастера отметьте опцию добавления поддержки Linux.
  3. На третьей вкладке мы можем выбрать, какие функции будут добавлены в новый проект. Выберите все и нажмите Next.
  4. На следующей вкладке мы можем выбрать базовый класс для класса серверных методов. Для простых проектов, где мы не планируем использовать невизуальные компоненты в реализации сервера, мы могли бы выбрать TComponent. В большинстве случаев лучше выбрать TDataModule.
  5. На последней вкладке нам нужно указать папку для файлов проекта. Имя папки должно быть допустимым идентификатором для проектов Delphi, потому что последняя часть пути будет использоваться мастером для генерации имени проекта.

Поскольку мы выбрали TDataModule в качестве класса-предка серверных методов, нам представлен диалог для подтверждения того, что мы хотим включить фреймворк VCL. Нажмите на кнопку OK.

Мастер сгенерирует для нас полный проект. Нажмите на Save All и примите имена по умолчанию для всех новых модулей.

Сердцем приложения сервера DataSnap является компонент DSServer1, который вы можете найти в ServerContainerUnit1. Он имеет свойство AutoStart, установленное в True, так что в момент запуска приложения веб-сервер начинает ожидать входящих запросов от клиентов.

Компоненты DataSnap на модуле данных ServerContainerUnit1
Рисунок 12.9: Компоненты DataSnap на модуле данных ServerContainerUnit1

ServerMethodsUnit1 содержит фактические API сервера. Все публичные и published методы, объявленные в этом классе, доступны для вызова клиентами. Компонент DSServerClass1 в ServerContainerUnit1 соединяет компонент DSServer1 с классом серверных методов. Он имеет свойство Server, указывающее на DSServer1, а также событие OnGetClass, которое сообщает компонентам DSServer1, каков тип класса сервера.

Обратите внимание, что в следующем коде мы присваиваем ссылку на класс (сам класс), а не экземпляр, объект этого класса:

procedure TServerContainer3.DSServerClass1GetClass(
  DSServerClass: TDSServerClass; 
  var PersistentClass: TPersistentClass);
begin
  PersistentClass := TServerMethods3;
end;

Реализация функциональности сервера DataSnap

В архитектуре DataSnap программист не пишет код для создания экземпляра класса серверных методов внутри серверного приложения. Свойство LifeCycle компонента DSServerClass1 управляет жизненным циклом класса сервера. Это свойство может иметь три разных значения:

Серверы DataSnap обычно не зависят от хостинга. Это означает, что серверы DataSnap могут быть реализованы как веб-сервер WebBroker, консольное приложение, приложение форм VCL или FireMonkey, или как служба Windows.

Мастер сервера сгенерировал класс TServerMethods3 в ServerMethodsUnit1. Он имеет всего два примера серверных методов: EchoString и ReverseString. Они принимают строковый параметр и возвращают строку. Вы можете добавить другие публичные методы в этот класс, и они автоматически станут доступны клиентам. Обратите внимание, что директива компилятора METHODINFO обеспечивает генерацию полной информации RTTI о методах, принадлежащих классу серверных методов, что необходимо фреймворку DataSnap.

Это полный исходный код класса:

type
  {$METHODINFO ON}
  TServerMethods3 = class(TDataModule)
  private
    { Private declarations }
  public
    { Public declarations }
    function EchoString(Value: string): string;
    function ReverseString(Value: string): string;
  end;
  {$METHODINFO OFF}

В целом, в сервере DataSnap может быть больше классов серверных методов. Каждый класс потребует своего собственного компонента TDSServerClass, указывающего на DSServer1, возвращающего в своем событии OnGetClass тип данных (класс) класса серверного метода.

Построение клиента DataSnap

Подобно построению клиентов для XML SOAP веб-сервисов, должен быть сгенерирован модуль доступа к клиенту, который имитирует функциональность, доступную на сервере. В случае SOAP веб-сервисов отправной точкой для генерации клиентских прокси является документ WSDL, который обычно генерируется автоматически из работающего сервиса. Разработка клиента DataSnap аналогична.

Генератору прокси DataSnap необходим доступ к работающему серверу. Он опрашивает сервер и генерирует клиентский класс DataSnap, который предоставляет те же методы, что доступны на сервере. Существуют генераторы клиентских прокси для нескольких языков программирования, включая Object Pascal, C++, PHP, JavaScript, C#, Java и Objective C. Однако обратите внимание, что за пределами Object Pascal и C++, генераторы для других языков не поддерживались и в настоящее время устарели.

На клиенте вам просто нужно создать экземпляр этого клиентского класса DataSnap, передав компонент соединения DataSnap в конструктор. Фреймворк DataSnap использует RTTI для обеспечения функциональности сериализации для многих простых и сложных типов данных Delphi. Следовательно, нам не нужно вручную преобразовывать типы параметров и результатов из их нативного представления Delphi в строки и JSON. Это делается за нас, но выбор типов параметров важен в DataSnap. Если генератор прокси не поддерживает определенные типы параметров, он просто игнорирует данный серверный метод, и прокси для него не генерируется.

Давайте теперь построим клиентское приложение:

  1. Генератору прокси потребуется доступ к работающему серверу. Нажмите на кнопку Run, чтобы запустить сервер DataSnap.
  2. Введите start в консольном окне и держите сервер работающим. Вы можете по желанию ввести URL сервера в веб-браузере, чтобы убедиться, что он слушает порт 8080, как указано в мастере.
Работающий сервер DataSnap WebBroker и браузер, подключенный к нему
Рисунок 12.10: Работающий сервер DataSnap WebBroker и браузер, подключенный к нему
  1. Щелкните правой кнопкой мыши на узле группы проектов в Project Manager и выберите Add New Project из контекстного меню. Создайте новое пустое мульти-устройственное приложение Delphi.
  2. Сохраните модуль главной формы как uFormDSClient и проект как DataSnapClient. Измените свойство Name главной формы на FormDSClient.
  3. Теперь мы будем использовать мастер для генерации клиентского кода DataSnap. В категории DataSnap Server в диалоге New Items дважды щелкните на иконке DataSnap REST Client Module.
  4. На первом экране мастера выберите Remote Server в качестве расположения сервера. В конце концов, наш мобильный клиент DataSnap будет обращаться к удаленному серверу.
  5. На втором экране мастера мы можем указать тип проекта сервера DataSnap. Прямо сейчас мы строим клиентское приложение против автономного сервера WebBroker, но в производстве мы, скорее всего, будем использовать сервер DataSnap, построенный как модуль Apache и развернутый на машине Linux. Здесь нормально указать опцию Do not know.
  6. На последнем экране нам нужно ввести URL (или Host Name) сервера и проверить, может ли быть установлено соединение. Если мастер не может подключиться к серверу, он не сможет сгенерировать клиентский код DataSnap. Введите 127.0.0.1 в качестве имени хоста и 8080 в качестве номера порта. Нажмите на кнопку Test Connection, чтобы убедиться, что мы можем подключиться к серверу, и нажмите на кнопку Finish.

Мастер сгенерировал два новых модуля и добавил их в клиентский проект: ClientClassesUnit1 и ClientModuleUnit1. Нажмите на кнопку Save All, чтобы сохранить новые модули.

Проекты сервера и клиента DataSnap в Project Manager
Рисунок 12.11: Проекты сервера и клиента DataSnap в Project Manager

ClientClassesUnit1 — это фактический прокси-код DataSnap. Вы найдете там сгенерированный класс TServerMethods3Client. Он имеет то же имя, что и класс серверных методов, но с добавленным словом Client. Он наследуется от класса TDSAdminRestClient и имеет те же объявления методов, что и сервер. Это полный код класса:

type
  TServerMethods3Client = class(TDSAdminRestClient)
  private
    FEchoStringCommand: TDSRestCommand;
    FReverseStringCommand: TDSRestCommand;
  public
    constructor Create(ARestConnection: TDSRestConnection); overload;
    constructor Create(ARestConnection: TDSRestConnection; 
      AInstanceOwner: Boolean); overload;
    destructor Destroy; override;
    function EchoString(Value: string; 
      const ARequestFilter: string = ''): string;
    function ReverseString(Value: string; 
      const ARequestFilter: string = ''): string;
  end;

Второй модуль, ClientModuleUnit1, — это модуль данных с уже добавленным компонентом DSRESTConnection1. В его свойствах Host и Port есть значения, введенные на последней странице мастера. Щелкните правой кнопкой мыши на компоненте соединения и выберите Test Connection из его контекстного меню, чтобы убедиться, что сервер все еще работает.

Опция контекстного меню Test Connection для компонента TDSRESTConnection
Рисунок 12.12: Опция контекстного меню Test Connection для компонента TDSRESTConnection

Вторая опция, Generate DataSnap client classes, очень полезна, когда мы хотим перегенерировать клиентские классы с помощью генератора прокси. Это нужно делать каждый раз, когда мы добавляем или изменяем объявления серверных методов.

Мастер добавил приватное поле FServerMethods3Client в класс клиентского модуля и добавил публичный метод GetServerMethods3Client, который создает экземпляр TServerMethods3Client, передавая компонент REST-соединения в его конструктор, а затем возвращает готовый к использованию класс.

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

Давайте завершим клиентское приложение:

  1. Перейдите на главную форму клиентского приложения и добавьте модуль ClientModuleUnit1 в его секцию uses в части реализации модуля.
  2. Разместите на форме элементы управления TButton и TEdit.
  3. Измените свойство Name кнопки на BtnReverse и ее Text на Reverse. Переименуйте edit в EdtTest и в его свойстве Text введите любую строку, например, Delphi.
  4. Дважды щелкните на кнопке и введите всего одну строку кода, которая возьмет текст из edit, отправит его в метод DataSnap ReverseString и отобразит перевернутую версию исходной строки в edit:
uses
  ClientModuleUnit1;

procedure TFormDSClient.BtnReverseClick(Sender: TObject);
begin
  EdtTest.Text := 
    ClientModule1.ServerMethods3Client.ReverseString(EdtTest.Text);
end;

Сохраните клиентское приложение и запустите его. Если вы нажмете на кнопку, вы должны увидеть исходный текст из edit перевернутым.

Клиентское приложение DataSnap, работающее на Windows
Рисунок 12.13: Клиентское приложение DataSnap, работающее на Windows

Если вы хотите развернуть это приложение на мобильной платформе, для тестирования локально в вашей среде, вам нужно будет заменить значение свойства Host компонента DSRestConnection1 в модуле ClientModule1, чтобы оно имело IP-адрес компьютера, на котором вы запускаете сервер DataSnap. Также, если вы хотите запустить клиент на Android, и вы используете HTTP на своем ПК (а не HTTPS), вам нужно будет запросить исключение из правила платформы использовать только HTTPS, добавив следующую строку в файл AndroidManifest.template.xml в папке вашего проекта Delphi:

android:usesCleartextTraffic="true"

DataSnap — это очень мощный и богатый фреймворк. Это очень хороший выбор для реализации систем с относительно небольшим количеством одновременных клиентов. Он также поддерживает аутентификацию и авторизацию, где доступ к определенным классам или методам сервера может быть ограничен для определенной группы пользователей. Также есть поддержка фильтров связи, где вы можете предоставить пользовательские модификации сырого потока байтов, которые обмениваются между клиентами и серверами. Из коробки доступны фильтры сжатия и шифрования, но можно реализовать пользовательский фильтр. Другой интересной особенностью DataSnap является функциональность обратного вызова. Серверы могут уведомлять выбранные или все подключенные клиентские приложения, отправляя информацию о том, что что-то интересное произошло на сервере, в каналы обратного вызова.

Также существует фреймворк FireDAC JSON Reflection, который упрощает создание клиент-серверных приложений баз данных. Данные из нескольких наборов данных FireDAC на сервере могут быть объединены в один объект и отправлены клиентам для загрузки в локальные компоненты TFDMemTable для обработки. Все изменения, внесенные в данные на клиенте, могут быть отправлены за одну операцию на сервер для обновления базовой базы данных.

Резюме

В этой главе мы рассмотрели различные варианты создания мобильных бэкендов с помощью Delphi. Есть много мастеров, которые помогут вам построить все виды серверных приложений, включая простые HTTP-серверы WebBroker, SOAP XML веб-сервисы и DataSnap.

Архитектура DataSnap была велика, когда она была построена, но она никогда не предназначалась для того, чтобы быть бэкендом для современных REST-стилевых API. Фактически, она началась в мире COM и была расширена, чтобы охватить HTTP.

Если вы хотите полностью принять модель REST, создавая stateless и масштабируемую архитектуру, с большим количеством готовых к использованию сервисов из коробки, вы можете захотеть перейти к новой архитектуре, которую Embarcadero добавила в Delphi, которая называется RAD Server. Это подробно рассматривается в следующей главе. RAD Server предлагает лучшее и наиболее обширное решение в качестве бэкенда для ваших мобильных приложений.

Следующая глава проведет вас через создание веб-сервиса RAD Server и соответствующего клиентского приложения, мигрируя нашу демонстрацию ToDo на эту новую технологию, несколькими различными способами.