Глава 12. Создание мобильных бэкендов
В мире мобильной разработки распространена практика, когда существуют выделенные разработчики и команды, отвечающие исключительно за создание мобильных фронтендов (собственно приложений, работающих на устройстве) и бэкендов (веб-сервисов для доступа к данным и размещения части бизнес-логики). По этой причине мы также хотим примерить шляпу бэкенд-разработчика и использовать Delphi для создания современных, масштабируемых, безопасных, отказоустойчивых REST API веб-сервисов с полным доступом к базе данных.
В этой главе будут рассмотрены следующие вопросы:
- Delphi и многоуровневые архитектуры
- Работа на низком уровне с WebBroker
- Сделай сам с DataSnap
Цель этой главы — помочь вам понять различные варианты технологий бэкенда в Delphi и получить практические знания о том, как создавать веб-сервисы. Существует еще одна технология веб-сервисов в Delphi, называемая RAD Server, которая будет темой следующей главы.
Технические требования
Как обычно, все библиотеки, рассмотренные в этой главе, поставляются в составе Delphi, и большинство из них доступны уже много релизов. Однако поддержка DataSnap доступна только в Enterprise-версии продукта, а не в Professional и Community Editions.
Исходный код демонстрационных проектов этой главы можно найти на GitHub по следующей ссылке: GitHub-репозиторий книги.
Delphi и многоуровневые архитектуры
В предыдущей главе мы видели, что можно использовать веб-сервисы и размещенные в облаке веб-API для создания информационной системы, где приложения, работающие на разных устройствах, могут подключаться к одному и тому же хранилищу данных. Вы можете не только использовать Delphi для интеграции с существующими веб-сервисами, но и создавать свои собственные.
Зачем вам создавать мобильные бэкенд-сервисы? Многоуровневые архитектуры имеют множество преимуществ. Но что именно представляет собой многоуровневое решение? Это архитектура, в которой клиентские приложения взаимодействуют с серверными приложениями для доступа к базовым ресурсам. В отличие от клиент-серверного приложения, в котором пользовательский интерфейс (UI), бизнес-логика и доступ к базе данных объединены (даже если они, возможно, логически разделены на разные модули, как мы делали для приложения todo в главе 10), в многоуровневом сценарии, где доступ к базе данных находится на сервере, вместе с ядром бизнес-логики, клиентское приложение имеет только UI и ограниченную бизнес-логику, необходимую для поддержки лучшего UI.
Дополнительные уровни делают архитектуру приложения более сложной, но они приносят такие преимущества, как улучшенная масштабируемость и безопасность. Они также предоставляют возможность создавать несколько клиентов (например, мобильных, настольных и даже веб-клиентов) с единым общим бэкендом. Многоуровневый подход упрощает управление изменениями, поскольку клиентские приложения не привязаны к базовым сервисам и взаимодействуют с ними через уровень абстракции, предоставляемый серверными API.
В многоуровневых архитектурах фактическое серверное приложение является лишь одним из многих элементов общей картины. Очень важно, как развертывается серверное приложение. Если масштабируемость и высокая доступность сервисов являются нашими основными задачами, то нам следует подумать об использовании технологий балансировки нагрузки. В зависимости от количества входящих HTTP-запросов инфраструктура автоматического масштабирования может запускать и останавливать экземпляры этого образа виртуальной машины, адаптируясь к текущей нагрузке.
В таких архитектурах выгодно не поддерживать состояние взаимодействия с клиентским приложением на серверном уровне, поскольку разные запросы, поступающие от одного и того же клиентского приложения, могут приниматься разными экземплярами сервера. Это одна из причин, почему использование протокола HTTP так распространено для веб-сервисов. Другим техническим элементом, который следует учитывать для улучшения масштабируемости и лучших вариантов развертывания в облаке, является использование Linux в качестве целевой платформы, что Delphi поддерживает только в Enterprise Edition.
Работа на низком уровне с WebBroker
Самым простым и универсальным фреймворком для разработки веб-серверов в Delphi является WebBroker. Это базовая технология для многих специализированных типов веб-сервисов, которые можно создавать с помощью Delphi, таких как SOAP XML веб-сервисы, DataSnap и RAD Server. Если вы создаете новое приложение веб-сервера с помощью мастера New WebBroker App, вы можете реализовать произвольную функциональность HTTP-сервера. В нашем случае это будет простой веб-сервис, который предоставит REST API доступ к новой версии приложения Todo.
Вот шаги для начала создания нашего первого серверного приложения WebBroker:
- Нажмите File | New | Other в IDE и дважды щелкните на мастере Web Server Application в категории Web.
- На первой странице мастера, озаглавленной Platform, у вас есть возможность указать целевые платформы. Windows обязательна, и вы можете добавить Linux, если хотите. Я рекомендую включить его, если ваша версия Delphi поддерживает это.
- На второй странице мастера, озаглавленной 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:
- Пока что давайте выберем опцию Standalone application console. Позже мы добавим второй проект Apache в группу проектов.
- Если мы выбираем автономную опцию, на последней странице мастера, озаглавленной Port Number, мы можем выбрать протокол связи, HTTP или HTTPS, и порт для использования, по умолчанию это 8080. Также есть кнопка для проверки, доступен ли порт в данный момент.
- Нажмите на кнопку Test Port, чтобы убедиться, что выбранный порт не используется, и нажмите Finish для генерации проекта сервера WebBroker.
Автономный сервер WebBroker
Теперь, когда у нас есть проект, сгенерированный мастером, давайте убедимся, что у нас есть хорошая структура папок для правильной организации наших двух проектов WebBroker, Standalone и Apache, прежде чем мы продолжим и протестируем проект:
- Создайте новую папку для проектов. Внутри этой папки создайте три подкаталога:
apache,standaloneиshared. - Нажмите на кнопку Save All в Delphi. Сохраните модуль
ServerConst1в папкуstandalone,WebModuleUnit1в папкуshared, и сохраните проект какToDoWebBrokerStandaloneHTTP, убедившись, что он также сохранен в папкеstandalone. - Запустите проект веб-сервера на Windows. Вы должны увидеть консольное окно с приглашением ввести одну из опций. Введите
startдля запуска веб-сервера. Если вы увидите сообщение от брандмауэра Windows, нажмите кнопку Allow или Allow Access, чтобы позволить клиентским приложениям взаимодействовать с сервером. - Если теперь вы откроете свой веб-браузер и введете
localhost:8080или127.0.0.1:8080в адресной строке, вы должны увидеть веб-страницу по умолчанию нашего сервера.
Вы можете ввести 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 веб-модуля.
Мастер сгенерировал одно действие с включенным свойством 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-текста пока достаточно.
+. Это делает HTML-код гораздо более читаемым.
Добавление поддержки Apache
Прежде чем начать реализацию нашего веб-сервиса, давайте добавим второй проект, модуль Apache:
- Щелкните правой кнопкой мыши на узле группы проектов в Project Manager и выберите опцию Add New Project.
- Снова дважды щелкните на иконке New Web Server Application.
- На первой странице мастера выберите опцию добавления поддержки Linux.
- На второй странице убедитесь, что выбрали Apache dynamic link module в качестве типа проекта.
- На последней странице мастера вы можете указать версию Apache, которую хотите поддерживать; как правило, вы захотите оставить значения по умолчанию.
- Нажмите на кнопку Finish. Проект Apache сгенерирован.
Теперь нам нужно аккуратно его сохранить. Нажмите на Save All и сохраните модуль веб-модуля как WebModuleUnit2 в папке apache и проект как ToDoWebBrokerApache в той же папке apache. Сохраните группу проектов как ToDoWebBrokerGrp.
Теперь мы хотим, чтобы оба проекта использовали один и тот же веб-модуль. Щелкните на WebModuleUnit1 в окне Project Manager и перетащите его на узел проекта Apache. Диалог подтверждения спросит вас, хотите ли вы добавить выбранный модуль в проект. Подтвердите этот запрос.
Теперь щелкните правой кнопкой мыши на WebModuleUnit2 в Project Manager и выберите опцию Remove from project.
Теперь оба проекта используют один и тот же код веб-модуля. Щелкните правой кнопкой мыши на узле Project Group и выберите Build All. В любое время мы можем собрать наш веб-сервер как автономное приложение для тестирования и как модуль Apache для развертывания.
Добавление клиентского приложения
Давайте добавим проект клиентского приложения в группу, выполнив следующие шаги:
- Создайте новую папку
restclientв ранее созданной папке, которая уже содержит три другие папки для обоих серверных проектов. - Скопируйте все файлы из проекта
ToDoListSQLiteиз главы 10 в только что созданную папку. - Щелкните правой кнопкой мыши на узле группы проектов в Project Manager и выберите опцию Add Existing Project. Добавьте проект
ToDoListSQLiteв группу. - Сохраните проект как
ToDoListRESTClient. - Сохраните модуль главной формы как
uFormToDoRESTClientи измените свойствоNameформы наFormToDoRESTClient. - Переместите модули
uToDoTypes,uToDoUtilsиuDMToDoв папкуshared. Добавьте их в оба серверных проекта. - Добавьте модули
uToDoTypesиuToDoUtilsв клиентский проект. - Теперь щелкните правой кнопкой мыши на проекте и выберите Build All. Все три проекта должны успешно скомпилироваться.
Построение веб-модуля
Существуют различные подходы к архитектуре веб-сервиса, предоставляющего REST API. В нашем случае мы хотим предоставить операции CRUDL для базовых данных ToDo. В нашем примере давайте переключимся на WebModule1 и добавим еще пять элементов веб-действий в коллекцию Actions веб-модуля, которые будут соответствовать базовым операциям в наших данных ToDo. Это операции, которые наш REST-сервер будет предоставлять, используя разные URL.
После добавления новых действий переименуйте их в ActToDoCreate, ActToDoRead, ActToDoUpdate, ActToDoDelete и ActToDoList.
В свойстве 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, которые мы только что построили. Вы можете сделать это, выполнив следующие шаги:
- Запустите автономную версию проекта веб-сервера и дважды щелкните на проекте
ToDoListRESTClientв Project Manager, чтобы активировать его. - Удалите модуль данных
uDMToDoиз проекта и добавьте новый модуль данных. - Измените свойство
Nameмодуля данных наDMToDoWebBrokRESTи сохраните его модуль какuDMToDoWebBrokREST. - Добавьте модуль
uToDoTypesв секциюusesинтерфейсной части модуля данных и модульuToDoUtilsв его часть реализации. - Добавьте интерфейс
IToDoDataв объявление класса модуля данных. Скопируйте сигнатуры процедур из объявления интерфейса в публичную секцию объявления класса модуля данных и нажмите Ctrl+Shift+C для вызова завершения класса. - В классе главной формы клиентского приложения измените имя модуля в секции
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.
Установите свойство 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. Сделайте то же самое для всех остальных компонентов.
Последняя часть работы — это написание методов модуля данных. Давайте начнем с простого, метода 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 REST Application генерирует проект веб-сервера DataSnap, включая веб-страницы и файлы JavaScript для вызова серверных методов из браузера
- Мастер DataSnap Server поддерживает создание серверов DataSnap старого стиля на базе DBX, которые не рекомендуются
- Мастер DataSnap WebBroker Application генерирует приложение сервера DataSnap, размещенное в веб-сервере WebBroker
Построение сервера DataSnap
В контексте создания бэкендов для мобильных приложений последний выбор кажется наиболее полезным. Давайте попробуем и построим простые проекты сервера и клиента, чтобы понять, каковы различные строительные блоки этого фреймворка. Вы можете выполнить следующие шаги:
- Выберите мастер DataSnap WebBroker Application и нажмите кнопку OK.
- На первой странице мастера отметьте опцию добавления поддержки Linux.
- На третьей вкладке мы можем выбрать, какие функции будут добавлены в новый проект. Выберите все и нажмите Next.
- На следующей вкладке мы можем выбрать базовый класс для класса серверных методов. Для простых проектов, где мы не планируем использовать невизуальные компоненты в реализации сервера, мы могли бы выбрать
TComponent. В большинстве случаев лучше выбратьTDataModule. - На последней вкладке нам нужно указать папку для файлов проекта. Имя папки должно быть допустимым идентификатором для проектов Delphi, потому что последняя часть пути будет использоваться мастером для генерации имени проекта.
Поскольку мы выбрали TDataModule в качестве класса-предка серверных методов, нам представлен диалог для подтверждения того, что мы хотим включить фреймворк VCL. Нажмите на кнопку OK.
Мастер сгенерирует для нас полный проект. Нажмите на Save All и примите имена по умолчанию для всех новых модулей.
Сердцем приложения сервера DataSnap является компонент DSServer1, который вы можете найти в ServerContainerUnit1. Он имеет свойство AutoStart, установленное в True, так что в момент запуска приложения веб-сервер начинает ожидать входящих запросов от клиентов.
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 управляет жизненным циклом класса сервера. Это свойство может иметь три разных значения:
- По умолчанию оно установлено в Session, что означает, что для каждого подключенного клиента в серверном приложении есть один экземпляр класса серверных методов. Когда клиент подключается, он создается, а когда клиент отключается, он уничтожается.
- Если это свойство установлено в Server, то есть только один экземпляр класса сервера для всех подключенных клиентов. В этом случае реализация серверного метода должна быть потокобезопасной, потому что его методы могут вызываться из разных потоков одновременно.
- Наиболее масштабируемым вариантом для свойства
LifeCycleявляется Invocation. В этом сценарии класс серверных методов создается только на время вызова серверного метода. Он не поддерживает состояние между вызовами серверных методов.
Серверы 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. Если генератор прокси не поддерживает определенные типы параметров, он просто игнорирует данный серверный метод, и прокси для него не генерируется.
Давайте теперь построим клиентское приложение:
- Генератору прокси потребуется доступ к работающему серверу. Нажмите на кнопку Run, чтобы запустить сервер DataSnap.
- Введите
startв консольном окне и держите сервер работающим. Вы можете по желанию ввести URL сервера в веб-браузере, чтобы убедиться, что он слушает порт 8080, как указано в мастере.
- Щелкните правой кнопкой мыши на узле группы проектов в Project Manager и выберите Add New Project из контекстного меню. Создайте новое пустое мульти-устройственное приложение Delphi.
- Сохраните модуль главной формы как
uFormDSClientи проект какDataSnapClient. Измените свойствоNameглавной формы наFormDSClient. - Теперь мы будем использовать мастер для генерации клиентского кода DataSnap. В категории DataSnap Server в диалоге New Items дважды щелкните на иконке DataSnap REST Client Module.
- На первом экране мастера выберите Remote Server в качестве расположения сервера. В конце концов, наш мобильный клиент DataSnap будет обращаться к удаленному серверу.
- На втором экране мастера мы можем указать тип проекта сервера DataSnap. Прямо сейчас мы строим клиентское приложение против автономного сервера WebBroker, но в производстве мы, скорее всего, будем использовать сервер DataSnap, построенный как модуль Apache и развернутый на машине Linux. Здесь нормально указать опцию Do not know.
- На последнем экране нам нужно ввести URL (или Host Name) сервера и проверить, может ли быть установлено соединение. Если мастер не может подключиться к серверу, он не сможет сгенерировать клиентский код DataSnap. Введите
127.0.0.1в качестве имени хоста и8080в качестве номера порта. Нажмите на кнопку Test Connection, чтобы убедиться, что мы можем подключиться к серверу, и нажмите на кнопку Finish.
Мастер сгенерировал два новых модуля и добавил их в клиентский проект: ClientClassesUnit1 и ClientModuleUnit1. Нажмите на кнопку Save All, чтобы сохранить новые модули.
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 из его контекстного меню, чтобы убедиться, что сервер все еще работает.
Вторая опция, Generate DataSnap client classes, очень полезна, когда мы хотим перегенерировать клиентские классы с помощью генератора прокси. Это нужно делать каждый раз, когда мы добавляем или изменяем объявления серверных методов.
Мастер добавил приватное поле FServerMethods3Client в класс клиентского модуля и добавил публичный метод GetServerMethods3Client, который создает экземпляр TServerMethods3Client, передавая компонент REST-соединения в его конструктор, а затем возвращает готовый к использованию класс.
В коде клиентского приложения, если мы хотим вызвать любой метод удаленного сервера DataSnap, мы можем просто вызвать соответствующий метод на классе TServerMethods3Client, возвращенном из GetServerMethods3Client, и наш вызов будет перенаправлен на удаленный сервер для выполнения, и результат, полученный от сервера, будет возвращен. Если удаленный метод выбрасывает исключение, то оно перехватывается на сервере и повторно выбрасывается на стороне клиента.
Давайте завершим клиентское приложение:
- Перейдите на главную форму клиентского приложения и добавьте модуль
ClientModuleUnit1в его секциюusesв части реализации модуля. - Разместите на форме элементы управления
TButtonиTEdit. - Измените свойство
Nameкнопки наBtnReverseи ееTextна Reverse. Переименуйте edit вEdtTestи в его свойствеTextвведите любую строку, например, Delphi. - Дважды щелкните на кнопке и введите всего одну строку кода, которая возьмет текст из edit, отправит его в метод DataSnap
ReverseStringи отобразит перевернутую версию исходной строки в edit:
uses
ClientModuleUnit1;
procedure TFormDSClient.BtnReverseClick(Sender: TObject);
begin
EdtTest.Text :=
ClientModule1.ServerMethods3Client.ReverseString(EdtTest.Text);
end;
Сохраните клиентское приложение и запустите его. Если вы нажмете на кнопку, вы должны увидеть исходный текст из edit перевернутым.
Если вы хотите развернуть это приложение на мобильной платформе, для тестирования локально в вашей среде, вам нужно будет заменить значение свойства 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 на эту новую технологию, несколькими различными способами.