前回、Delphiで作成したアルバムアプリをWindows 10に対応させ、ついでにマルチデバイス対応させてみましたが、実際にスマートフォンなどのモバイルデバイスでアルバム機能を使おうとすると、ローカル環境に写真を保存するだけでなく、クラウドなどのサーバー環境に保存するように作りこみたくなります。
Delphiおよびそのスイート製品RAD Studioでは、単一コードからWindows、Mac、iOS、Androidのマルチデバイスアプリを構築できることが目を引いていますが、その背後で重要となるサーバーへの接続や、モバイルクライアントに対応したサーバーアプリそのものを構築することもできることは忘れられがちです。
今回は、アルバムアプリを拡張してサーバー環境に写真を保存できるように、RAD Studioサーバーアプリ構築に挑戦してみます。
方法は2つ ― DataSnapとEMS
最近のサーバーアプリ(クラウドサービスなどを含む)は、REST/JSONなどのオープンなプロトコルを使って、特定のプラットフォームに限定されない接続性を用意する傾向にあります。RAD Studioでも、これに倣って、REST/JSONを標準とし、HTTP/HTTPSで接続可能なサーバーアプリを構築することができます。
DataSnap
RAD Studioには、2つの方法が用意されています。ひとつは、DataSnap。この機能は、元々はCOMをベースにした多層アプリケーション構築技術でしたが、COMを分離し、REST/JSONを使ったオープンな接続性を提供するようにリニューアルされたものです。
DataSnapは、多層アプリケーション構築のためのSDKと言っていいでしょう。DataSnapを用いれば、基本的なサーバーサービス機能をコンポーネントによって構築し、用意されたライブラリを使って、比較的簡単に多層アプリケーションを構築することができます。クライアントは、Delphi、C++によって構築されたものだけでなく、Javaや.NET、スクリプト言語などからも利用可能です。
EMS
モバイルアプリに対応するために、すぐに使える中間サーバー機能として用意されたのが、EMS(Enterprise Mobility Services)です。EMSは、カスタムREST APIをパッケージとして登録するアプリケーションサーバーの機能を提供します。
DataSnapのようにサーバー機能を、SDKを使って一から構築するのではなく、元々用意されたサーバーソフトウェアに作成したモジュールを載せていくスタイルなので、ログインなどのコネクション管理、データアクセスプール、セキュリティ、ログ分析など、あらかじめ用意された機能を活用してすばやくサーバーアプリを構築できます。
また、モバイルデバイスの識別や、それを活用した通知サービスの利用(Android端末の場合Googleのサービスを使うように自動的に振り分けるなど)もサポートしていて便利です。
EMSで単純な写真閲覧アプリを作ってみる
今回は、スマートフォンからサーバーに写真を登録できるようにするというのが主な目的なので、モバイルに対応したEMSを使ってみることにします。
はじめに、EMS向けアプリの作り方を理解するために、写真の一覧を表示する機能を実装してみようと思います。
これまでローカルに保存していた写真情報は、サーバーのデータベースに以下のような構造で保存してみました。
EMSでは、データベースにクエリーを発行し、その結果セットをJSONデータでクライアントに送信したり、更新差分をクライアントから受け取り、データベースに反映させるコードを記述できます。主要な機能は、コンポーネントによって提供されているので、クライアントアプリケーションの構築と同様に、マウスによるドラッグ&ドロップとプロパティの設定で済んでしまいます。
EMSパッケージを作る
EMSパッケージウィザード
RAD Studioで、「EMSパッケージ」を新規作成します。表示された「EMSパッケージウィザード」で「リソースを含むパッケージを作成する」を選びます。リソースは、EMSサーバーに追加するREST APIです。適切な名称を付けて、リソースを作成します。
リソースには、Get、GetItem、Post、PutItem、DeleteItemのエンドポイントを用意することができます。
EMSパッケージの設計画面
作成したEMSパッケージは以下のような設計画面です。
UIのないサーバーアプリでウィンドウのような画面が出てくるのは少し不思議ですが、ここには、ボタンやリストボックスなどのUI要素は置くことはできず、データアクセスやJSON処理などのロジックをカプセル化した非ビジュアルコンポーネントのみを配置できます。
非ビジュアルとはいえ、マウス操作で配置し、オブジェクトインスペクタでプロパティとイベントを設定するというやり方は共通です。コンポーネントが用意する機能をこのようなかたちで簡単に利用できるので、実装が楽になるわけです。
コンポーネントの配置
ここでは次のようにコンポーネントを配置します。
FDConnectionは、データベース接続を担当するコンポーネントです。EMSでも、データアクセスコンポーネントのFireDACが使え、主要なRDBMSへの接続をコンポーネントによって行えます。
FDQueryは、アルバムテーブルへのクエリーです。この結果セットをJSON形式でストリームに流し込むためのしくみを用意するのが、FDSchemaAdapterとFDStanStorageJSONLinkです。これらのコンポーネントを使うことで、コードを記述することなく、結果セットをJSON形式の文字列に変換できます。
エンドポイントの実装コード
次に、クライアントリクエストに応答するコードを記述します。今回は一覧を表示するだけなので、エンドポイントは、Getのみを実装します。コードは次のようになります。詳細な処理はすべてコンポーネントにまかせているので、わずか数行で済みます。
procedure; TMyAlbumResource1.Get(const; AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); var; oStr: TMemoryStream; begin; oStr := TMemoryStream.Create; try; // アルバム一覧をオープン FDQuery1.Open; // FDQueryに結び付けられたアダプタを使ってストリームにJSON形式で保存 FDSchemaAdapter1.SaveToStream(oStr, TFDStorageFormat.sfJSON); // 応答内容にストリームを設定 AResponse.Body.SetStream(oStr, 'application/json', True); except; oStr.Free; raise;; end;; end;
EMSパッケージの実行
EMS開発サーバー
完成したEMSパッケージは、すぐにEMS開発サーバーでテストすることができます。EMS開発サーバーを起動すると、次のようなログを表示するウィンドウが開きます。
ブラウザでJSONデータを表示
EMSリソースは、HTTPリクエストに応答しJSON形式のデータを返すので、ブラウザでも簡単にテストすることができます。
作成したリソースをブラウザで表示したのが、次の画面です。
EMSクライアントアプリケーションを作る
EMSクライアント用のコンポーネント
クライアントアプリの実装にも、コンポーネントを活用することができます。これまでと同様のUIに対し、以下のコンポーネントを配置することでEMSサーバーからデータセットを受け取ることができます。
EMSProviderは、EMSサーバーへの接続を定義します。ホストのURLやポートを定義することで、EMSサーバーへの接続を確立できます。EMSFireDACClientは、FireDACのデータ形式をEMSでハンドリングするためのクライアントモジュールです。この2つのコンポーネントだけで、EMSサーバーから結果セットを取得できます。
取得した結果セットは、FDMemTableを使ってメモリ上に展開します。このコンポーネントは、FDQueryなどと同じデータセットオブジェクトなので、データの閲覧や編集などを、通常のデータベースアプリケーションのように実装できます。ここでも、取得したデータをFDMemTableに展開するためにFDSchemaAdapter、FDTableAdapterなどのアダプターコンポーネントを使います。
コンポーネントをプロパティの設定によって関連付けるだけで、一切コードを記述することなく、EMSサーバーから取得したデータをデータセットオブジェクトに格納できます。
ビジュアル操作でデータをUIに結び付ける
必要なプロパティを設定したら、UIとデータセットを結びつけます。ここで活躍するのがVisual LiveBindingsです。ただ、データセットの各フィールドの定義は、設計時には取得できないので、適切なフィールドをあらかじめ定義し、静的にフィールドとUIを結びつけるようにします。
エンドポイントを呼び出すコード
以上でUIの定義は完了です。あとは、EMSサーバーからデータセットを取得するコードを記述します。今回は、ボタンを押してデータを取得するように、以下のコードを記述しておきます。
procedure; TMyAlbumResource1.Get(const; AContext: TEndpointContext; const ARequest: procedure TEMSAlbumAppForm.Button1Click(Sender: TObject); begin EMSFireDACClient1.GetData; end;
画像を単体でダウンロードする
高解像度画像をデータセットに含めない
この単純な一覧アプリは、うまく機能するようで、パフォーマンス上微妙な問題を含んでいます。現在のデータ構造では、高解像度の写真がテーブルに含まれていて、一覧の取得を行ったときにいっしょに結果セットとして渡ってきます。ローカルPCにデータがあったときにはあまり問題になりませんでしたが、リモート環境にあるデータを取得するとき、特に通信環境の限られたモバイルクライアントからではいろいろ問題が発生します。
そこで、一覧の画像はあくまでもサムネイルとし、高解像度の写真は表示を選択したときにはじめてダウンロードしてくるように変更してみたいと思います。
サーバー側で特定の写真データだけを取得するコードを書くのは、それほど複雑な作業ではありませんが、取得した写真データをどのようにクライアントに送信すればよいでしょうか。
RAD Studioには、豊富なJSONライブラリが用意されており、任意のオブジェクトをJSONストリームで送受信できます。今回は、写真データをオブジェクトとして定義して、これをJSONストリームに乗せてみましょう。
写真データのオブジェクトを定義
次のコードは、写真データオブジェクトの定義です。TPhotoItemは、写真のIDと写真データそのものを保持します。写真データは、TBytes形式、つまりバイナリ形式として定義しておくことがミソです。
type TPhotoItem = class(TObject) private // 写真アイテムのID FId: string; // 写真データそのもの FPhoto: TBytes; public // 写真イメージを設定 procedure SetPhoto(const Data: TBytes); // メモリストリームから写真イメージを設定 procedure SetPhotoData(const Data: TMemoryStream); property Id: string read FId write FId; property Photo: TBytes read FPhoto write SetPhoto; end; implementation // 写真イメージを設定 procedure TPhotoItem.SetPhoto(const Data: TBytes); begin FPhoto := Data; end; // メモリストリームから写真イメージを設定 procedure TPhotoItem.SetPhotoData(const Data: TMemoryStream); begin SetLength(FPhoto, Data.Size); Data.Position := 0; Data.Read(FPhoto[0], Data.Size); end;
高解像度画像をユーザーアクションでダウンロード
このようにオブジェクトを定義しておけば、次のコードで、EMSサーバーから取得したJSON文字列からオブジェクトを抽出し、高解像度の写真をImageControlなどに表示させることができます。
// JSON テキストから 写真データオブジェクトに変換 PhotoItem := TJson.JsonToObject<tphotoitem>(jsonText); // 写真データをイメージに設定 stream := TMemoryStream.Create; stream.Write(PhotoItem.Photo, length(PhotoItem.Photo)); imgPhoto.Bitmap.LoadFromStream(stream);
クライアントアプリでは、サムネイルをクリックしたときに、このコードを呼び出します。これにより、写真データオブジェクトを取得でき、フルスクリーンあるいは、フォーム上に表示することができます。
スマートフォンで撮影した写真をアップロードする
写真をアップロードするコード
これの反対の操作を行えば、スマートフォンで撮影した写真をEMSサーバーにアップロードできます。RAD Studioでは、スマートフォンのカメラを使って写真を撮影したり、ロールにある写真を選択したりといった操作を、「アクション」と呼ばれるあらかじめ定義されたコマンドによって簡単に実行できます。アクションは自分でも作成できるので、さまざまなメニューにコマンドを割り当てるときに便利です。
さて、取得した写真をサーバーにアップロードするコードは、次のようになります。ダウンロードのときに使ったのと同じオブジェクトに写真データを割り当て、JSON形式に設定します。
item := TPhotoItem.Create; stream := TMemoryStream.Create; try // 写真データを写真アイテムにセット imgPhoto.Bitmap.SaveToStream(stream); PhotoItem.SetPhotoData(stream); // 出来上がったオブジェクトを JSON テキストに変換 jsonText := TJson.ObjectToJsonString(item); // EMS エンドポイントを呼び出し BackendEndpoint1.ClearBody; BackendEndpoint1.AddBody(jsonText, TRESTContentType.ctAPPLICATION_JSON); BackendEndpoint1.Execute; finally FreeAndNil(item); FreeAndNil(stream); end;
まとめ
モバイルアプリでサーバーにアクセスするコードを書くのはなかなか大変です。RAD Studioには、今回紹介したEMSのほかにも、HTTPをはじめとした通信プロトコルをサポートしたコンポーネントが用意されています。
コンポーネントを使うとコードが少なくて済む。これは直接的な開発生産性向上のメリットですが、同時にコードが複雑にならないのでバグが少なくなる、また、将来にわたってメンテナンスがしやすくなるというメリットがあります。
また、データベースアクセスなどもコンポーネントによって抽象化されるため、どのデータベースを選択しても、同じ開発スタイル、同じコーディングで済むというのも効率的です。
サーバーアプリの構築は、UI設計と違ってイメージしづらい部分も多いと思いますので、最後にこの辺を解説したビデオをいくつか紹介しておこうと思います。
- Webセミナーシリーズ 「最新デバイス技術を活かすソフトウェア構築のキモ」(オンデマンド)
- デベロッパーキャンプ講演ビデオ「IoT時代のマルチデバイス開発環境」
- デベロッパーキャンプ講演ビデオ「DelphiによるEnterpriseアプリケーション開発 ~ 当社パッケージの内部構造、開発方法ご紹介」 - Part 1 | Part 2
また、EMSも試すことのできるDelphi(RAD StudioおよびC++Builder)のトライアル版は、こちらからダウンロードできます。