こんにちは、サイオステクノロジー技術部 武井です。シリーズ連載「多分わかりやすいサーバーレスアーキテクチャ入門」も今回で最後です。今回は、なんと現場ですぐ使える実践的なスキル(?)をご紹介します。
※全シリーズの記事一覧はこちら
- 多分わかりやすいサーバーレスアーキテクチャ入門その1 〜 Azure Functionsでハロワ!!〜
- 多分わかりやすいサーバーレスアーキテクチャ入門その2 〜サーバーレスアーキテクチャのメリデリ!!〜
- 今回はこちら → 多分わかりやすいサーバーレスアーキテクチャ入門その3 〜 「Azure Functions」を使って、クラウドネイティブなLINE風チャットアプリを作ろう!!
LINE風なチャットアプリを作ってみよう
第1回目では、ハロワを返す簡単なAzure Functionsを作成してみましたが、実際のアプリケーション開発はもちろんそんな単純なものではありません。アプリケーションに最低限必要なものとして、以下の要素を挙げることが出来ます。
- 認証
- 認可
- データの永続化
これらの要素を盛り込んだ「LINE風なチャットアプリ」を作ってみたいと思います。
外部仕様
チャットアプリの外部仕様は以下のとおりです。
まず、チャットアプリのURLにアクセスします。すると、Yahoo!の認証画面が表示されます。Yahoo!のIDとパスワードを入力します。
チャットの画面が表示されます。
画面下部にテキストボックスがあります。チャットに発言したい場合は、「メッセージ入れてね」の部分にメッセージを入れて「発言する」をクリックします。
「更新する」のボタンをクリックしない限り、相手の最新の発言が表示されません。
なんともレガシーな仕様ですみません。イマドキならWebSocketあたりでリアルタイムのチャットが出来るようにするんでしょうけど、今回はAzure Functionsの説明がメインなので、あまりチャットアプリの仕様を複雑にすると、本質から外れてしまうような気がしたので、こんな簡単な作りにさせて頂きました。
画面に表示されるアイコンはYahoo!のIDと紐づくアイコンを表示するようプログラム内でハードコーディングしてます。このあたり、実際は、別途ユーザー登録画面などがあり、プロフィール編集画面でアイコンを登録うんぬんという形になるのでしょうが、それも本質から外れると思いましたので、実装はしておりません。
ちなみこのアプリケーションの外部仕様は、私が一筆したためた記事「多分わかりやすいマイクロサービス入門 〜 マイクロサービスフレームワーク「Azure Service Fabric」でLINE風なチャットアプリを作ろう!! 〜 」(いいね!を忘れずにお願いします)で作成したアプリケーションと全く同じものでございます。
アーキテクチャ
チャットアプリのアーキテクチャをご説明致します。
認証は、Yahoo!のOpen ID Connect Providerの機能を利用します。Open ID Connectの詳細については、ブログ記事「多分わかりやすいOpenID Connect」(いいね!をお願いします)を参照して下さい。
画面のコンテンツについては、Angularによるシングルページアプリケーションとします。Angularによる静的コンテンツの格納先は、Azure App Serviceとしました。チャットのメッセージ取得や発言については、Angularで作成したクライアントアプリケーションから、Azure Functionsによって作成したRest APIをコールします。Angularについての詳細は、「JavaScriptフレームワーク「Angular」による掲示板システム構築 」(いいね!をしてくれると嬉しいな)をご参照下さい。
チャットのメッセージを取得したり、発言したりするAPIにはAzure Functionsを使います。
Yahoo!で認証して取得したIDトークンの検証は、AzureのAPI GatewayサービスであるAPI Managementを利用します。API Managementの詳細は、ブログ記事「AzureのAPI Gateway(API Management)を用いてOpenID Connect Providerより発行されたJWTを検証」(いいね!を・・・(´・ω・`))をご参照下さい。
データの永続化(チャットで発言したメッセージの格納)にはAzureが提供するNoSQLデータベース「CosmosDB」を使います。チャットアプリではAPIでやり取りするメッセージデータのフォーマットにJSONを用いておりますので、JOSNをそのまま登録できるCosmosDBと非常に相性がよいのです。
余談なのですが、Angularなどで作成された静的コンテンツ(jsとかHTMLとか)を格納するのは、やっぱりAzure Blob Storageかなと思いました。AWSでもS3とか使いますよね。でも、Azure Blob Storageはコンテナー配下にディレクトリが作成できませんでした。https://chat.example.com/assets/・・・というURLが実現出来ないのです。ファイル名をassets/auth.htmlというようにすれば、擬似的に実現できるとマイクロソフトのドキュメントに書いてあったのですが、仰せのとおりにすると、ファイル名がassets:auth.html(/が:(セミコロン))になってしまうんですよね(T_T)ワケ(´・ω・`)ワカ(´ε`;)リマ( ;∀;)セン(ノД`)なんだかんだしてもうまくいかなかったので、Azure App Serviceを立ち上げて、そこに静的コンテンツを格納しました。たったそれだけのためにAzure App Service立ち上げるのも重いなーと思ったのですが、解決方法わからなかったもので・・・。誰か教えて下さい(´・ω・`)
処理フロー
チャットアプリの処理フローについて説明します。
まず、チャットアプリのURLにアクセスします。静的コンテンツをホストしているAzure App Service内のコンテンツを取得することとなります。
Azure App Serviceから、Angularで作成された静的コンテンツ(チャットメッセージの一覧画面)を取得すると、チャットメッセージの一覧を取得するために、API Management経由でAzure Functionsで作成されたAPIにアクセスしようとします。しかし、未認証(=IDトークンを持っていない)ので、API Management側で401となり、Angularによって作成されたクライアントアプリケーション側で401をキャッチして、Yahoo!のAuthorizationエンドポイントへリダイレクトされます。
Yahoo!の認証画面が表示されるので、Yahoo!アカウントのIDとパスワードを入力します。
認証が完了すると、Implicitフローなので、クエリパラメータにIDトークンが付与されたコールバックURLにリダイレクトされます。
コールバックURLに記載されているJava Script内で、URLのクエリパラメターに付与されているIDトークンを取得し、クライアントPCのローカルストレージに保存します。そして、再びチャットアプリのURLにリダイレクトします。そのとき、HTTPリクエストのAuthorizationヘッダにIDトークンをセットします。
HTTPリクエストのAuthorizationヘッダに設定されたIDトークンをAPI Managementで検証します。検証を行うための鍵は、Yahoo!管理画面でアプリケーションを登録した際に取得したシークレットを、事前にAPI Managementに登録しておきます。
IDトークンの検証が完了したら、IDトークンのsubクレームに定義してあるYahoo!アカウントのユーザー識別子を、x-yahoo-uidヘッダに設定して、Azure FunctionsにHTTPリクエストを送信します。このとき、合わせて、Azure Functionsにアクセスするためのキーをcodeというクエリパラメータに付与します。
Azure Functions側でx-yahoo-uidを取得して、ユーザーの識別を行い、CosmosDBに保存されているチャットのメッセージ一覧を取得して、API Management経由でHTTPレスポンスを返します。
以降は、アクセスの都度、ローカルストレージに保存したIDトークンを取り出し、Authorizationヘッダに設定して、HTTPリクエストを行います。
Yahoo!のOpen ID Connect Providerの設定
では、早速(てさっきから何度も言ってますが)アプリケーションの開発をしてみましょう。まずは、Yahoo!のOpenID Connect Providerの設定から始めます。
以下のURLにアクセスしてください。
https://e.developer.yahoo.co.jp/dashboard/
「新しいアプリケーションを開発」をクリックします。
「サーバーサイド」の方をチェックします。
「アプリケーション名」に任意のアプリケーション名を入力します。あとはデフォルトのままでOKです。
「同意する」をチェックして「確認」をクリックします。
入力内容に問題ないことを確認して「登録」をクリックします。
「Client ID」「シークレット」をメモっておいてください。後で使います。
CosmosDBの設定
チャットで発言したメッセージを格納するCosmosDBの設定をします。まず、Azureポータルにログインして下さい。
Cosmos DBには、データベース、コレクション、ドキュメントというものがあり、それぞれのものを一般的なデータベースに当てはめると以下の通りとなります。
- データベース → データベース
- コレクション → テーブル
- ドキュメント → レコード
Azureのポータルを開いて、「すべてのサービス」をクリックして、「cosmos」と入力します。すると、サービスの一覧に「Azure Cosmos DB」が表示されますので、クリックします。
「+追加」をクリックします。
データベースを作成するための情報を入力します。
- ID:任意の名称(後の接続先ホスト名となります)
- API:SQL
後は必要に応じて、環境に適したものを入れて「作成」をクリックします。
しばらくすると、リソースグループ内にCosmosDBが出来上がります。クリックしましょう。
新しいデータベースとコレクションを作成します。「Data Explore」→「New Collection」の順にクリックします。
データベース名を「serverless-chat-db」、コレクション名を「messages」とします。その他の項目はとりあえず何も考えずに以下のように入力します。
これでとりあえずCosmosDBのセットアップは終わりです。
Azure Functionsの設定
次はあじゅーるふぁんくしょんずです。
Azureポータルにログインして、「リソースの作成」をクリックして、「function」と入力してフィルターすると「Function App」が出てきますので、クリックします。
「作成」をクリックします。
「アプリ名」は任意の名称、「サブスリプション」「リソースグループ」は環境に応じたものを、「Storage」はデフォルトで入っていたものそのまま、他は以下のように入力して、「作成」をクリックします。
しばらくして、リソースグループに移動すると、以下のようにAzure Functionsが出来上がっています。カミナリマークのアイコンをクリックして下さい。
「+」のアイコンをクリックして下さい。まずチャットのメッセージ一覧を取得するAPIを作成します。
「カスタム関数」をクリックします。
「HTTP trigger」の「Java Script」をクリックします。
「名前」の部分に「getMessageList」と入力し、その他の部分は以下の通りデフォルトのままで「作成」をクリックします。
CosmosDBからデータを取得出来るように設定します。「統合」→「+新しい入力」→「Azure Cosmos DB」→「選択」の順にクリックします。
「新規」をクリックします。
「Azure Cosmos DB アカウント」を選択して、先ほどCosmos DBを作成したサブスクリプションとデータベースアカウントを選択して、「選択」をクリックします。
データベース名及びコレクション名は、先程作成したCosmos DBのものを入力します。「SQL クエリ」は「SELECT * FROM c order by c.pubDate asc」と入力します。この設定をしますと、このAzure Functionsが呼ばれたときに、以下に定義したCosmos DBに対して「SELECT * FROM c order by c.pubDate asc」のクエリを発行した結果が、inputDocumentオブジェクトに入ってきますよと言うことを意味します。ということで、最後に「保存」をクリックします。
次に、「トリガー」の「HTTP(req)」をクリックします。「許可されているHTTPメソッド」をクリックして、「選択したHTTPメソッド」で「GET」のみをチェックして、「保存」をクリックします。リクエストをGETメソッドだけに絞ります。
「getMessageList」をクリックします。右側にコードが書いてあると思います。
以下のコードを入力して、「保存」をクリックします。
今度は、チャットのメッセージを保存するAPIを作成します。「+」のアイコンをクリックして下さい。
「HTTP trigger」の「Java Script」をクリックします。
「名前」の部分に「addMessage」と入力し、その他の部分は以下の通りデフォルトのままで「作成」をクリックします。
「addMessage」関数の「統合」→「トリガー」→「HTTP(req)」の順にクリックして、「許可されているHTTPメソッド」で「Selected methods」を選択します。「選択したHTTPメソッド」で「POST」のみをチェックして、「保存」をクリックします。リクエストをPOSTメソッドだけに絞ります。
次に、Azure FunctionsからDocument DBにデータを書き込めるようにします。「+新しい出力」→「Azure Cosmos DB」の順にクリックして、最後に「選択」をクリックします。
「新規」をクリックします。
「Azure Cosmos DB アカウント」を選択して、先ほどCosmos DBを作成したサブスクリプションとデータベースアカウントを選択して、「選択」をクリックします。
データベース名及びコレクション名は、先程作成したCosmos DBのものを入力します。他はそのままにして「保存」をクリックします。
「addMessage」をクリックします。右側にコードが書いてあると思います。
以下のコードを入力して、「保存」をクリックします。
以上で、Azure Functionsの設定はおしまいです。
API Managementの設定
Azureポータルにアクセスして、「すべてのサービス」をクリックして、下記のように「api」と入力すると、「API Managementサービス」が表示されますので、クリックします。
「追加」をクリックします。
「名前」「組織名」に任意の名称、「サブスリプション」「リソースグループ」「場所」は環境に応じた値を、「管理者のメールアドレス」はAPI Managementサービスの作成が完了したときに通知されるメールアドレスを、「価格レベル」は「開発者(いいえ SLA)」を選択して、「作成」をクリックします。しばらくするとAPI Managementのサービスが作成されます。
そして、これからこのAPI Gatewayの背後に配置するAPIを設定します。それが先程Azure Functionsで作成したものになるわけですが、その前にもう一つやることがあります。API ManagementにAPIを配置する前にAPIの定義を作成しなければなりません。これはOpen APIという仕様で定義されているのですが、このAPIがどのようなMethod(Post or Get or …)やどのようなパラメータを取るかを定義したものです。この定義を作成するために一旦、先程のAzure Functionsの画面に戻ります。以下の画面の右下の方にある「APIの定義」をクリックしてください。
関数(プレビュー)をクリックします。
「API定義テンプレートを生成する」→「保存」の順にクリックします。
では、API Managementの画面に戻りましょう。以下の画面で「API」をクリックします。
API Managementの背後に、先程作成したAzure Functionsを配置したいので「Function App」をクリックします。
「Browse」をクリックします。
「必要な設定の構成」をクリックします。
インポートしたいAzure Functionsを選択して、「選択」をクリックします。
「addMessage」「getMessageList」の2つがチェックが付いていることを確認した上で、「選択」をクリックします。
「Create」をクリックします。これでAzure Functionsが、API Managementのバックエンドとして配置されました。
これからは、細かいポリシーを制御していきます。「All APIs」の「Inbound Processing」の「</>」をクリックします。
以下のような画面が開きます。
先の画面で、以下のポリシーを入力して「Save」をクリックします。
最後にもう一つ手順があります。「af-chat」→「Settings」の順にクリックします。ここで、「Base URL」をメモしておきます。また「Products」に「Unlimited」を選択して、「Save」をクリックします。
次に「Test」のタブをクリックします。「Headers」のところに「Ocp-Apim-Subscription-Key」テキストボックスがあります。その隣のテキストボックスに目玉のマークみたいなものがあるのでそれをクリックすると、コードのようなものが表示されます。それを大事にメモしておきます。
Azure App Serviceの設定
次にAngularで作成したクライアントアプリケーションを配置するためのAzure App Serviceを設定します。Azureポータルにログインして、「すべてのサービス」をクリックし、検索テキストボックスに「app service」を入力すると以下のように表示されます。「App Service」をクリックします。
「追加」をクリックします。
「Web App」をクリックします。
「作成」をクリックします。
「アプリ名」は任意の名称、「サブスクリプション」「リソースグループ」は環境に応じたもの、「OS」は「Windows」、あとはデフォルトのままで「作成」をクリックします。
しばらくすると、リソースグループ内にApp Serviceが出来上がっております。地球のマークのアイコンをクリックして下さい。
「URL」を大切にメモして保存しておいて下さい。これは、後にこのApp Service内に保存する静的コンテンツにアクセスするためのURLになります。
クライアントアプリケーションをビルドします。ソースコードは以下のGitHubにありますので、Cloneして下さい。Angularで作成しております。もう何度言っているかわからなくなってしまいましたが、Angularについての詳細は、「JavaScriptフレームワーク「Angular」による掲示板システム構築 」(いいね!を忘れずに)をご参照下さい。
https://github.com/noriyukitakei/af-sample-chat
3箇所修正する箇所があります。src/app/chat.component.tsを開いて下さい。
まず上記コード11行目のLOGIN_URLです。こちらにはYahoo!のAuthorizationエンドポイントを以下のように設定します。
https://auth.login.yahoo.co.jp/yconnect/v2/authorization?client_id=[Yahoo!のアプリケーション登録時のクライアントID]&response_type=id_token&redirect_uri=[先程メモしたAzure App ServiceのURL]/assets/auth.html&scope=openid&state=hoge&nonce=hoge
※stateとnonceは適当です(´・ω・`)
[Yahoo!のアプリケーション登録時のクライアントID]は、「Yahoo!のOpen ID Connect Providerの設定」のところで、アプリケーションを登録したときに表示されたクライアントIDになります。 [先程メモしたAzure App ServiceのURL]は、先程大切にメモしたAzure App ServiceのURLになります。次に、12行目のAPIのURLになります。こちらは、API Managementの設定でメモしたBase URLの値を設定します。
最後に13行目ですが、API Managementの設定でメモしたOcp-Apim-Subscription-Keyを定義します。
さて、ではソースコードをビルドします。その前にビルド環境を構築しましょう。ビルドにはnode.jsが必要になります。
まず、node.jsのバージョンを管理するためのツールnodebrewをインストールします。
$ curl -L git.o/nodebrew | perl - setup $ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile
次に、node.jsをインストールします。
$ nodebrew install-binary stable
こうすると、最新の安定バージョンがインストールできます。インストールされたバージョンを確認してみます。
$ nodebrew ls v9.2.1
今は9.2.1がstableのようです。では、このバージョンのnode.jsを使いたいので、以下のように入力します。
$ nodebrew use v9.2.1
これでバージョン9.2.1が使えるようになりました。次にAngularで作成したアプリケーションの実行やビルドを行うためのコマンドをインストールします。
$ npm install -g @angular/cli
Cloneしたソースコードのトップディレクトリに移動して、以下のコマンドを実行します。
$ ng build
するとdistというディレクトが出来上がり、中に何やら色々なファイルが入っています。これがビルドの成果物になります。後で使いますので、この場所を覚えておいて下さい。
Azure App Serviceの画面に戻ります。「高度なツール」→「移動」の順にクリックします。
「Debug Console」→「PowerShell」の順にクリックします。
D:\home\site\wwwrootに移動して、先程ビルドしたdist配下に出力されたファイルを全て、以下のコンソールにドラッグアンドドロップします。
コールバックURLの定義
Yahoo!で認証したが完了したあとに遷移するコールバックURLを定義します。AuthorizationエンドポイントのURLを以下のように定義したと思います。
https://auth.login.yahoo.co.jp/yconnect/v2/authorization?client_id=[Yahoo!のアプリケーション登録時のクライアントID]&response_type=id_token&redirect_uri=[先程メモしたAzure App ServiceのURL]/assets/auth.html&scope=openid&state=hoge&nonce=hoge
このURLのredirect_uriに定義しているURLがコールバックURLなのですが、これはYahoo!のアプリケーション管理画面であらかじめ定義したものと一致していないと、リダイレクトミスマッチでエラーとなってしまいます。
ということで設定しましょう。
以下のURLにアクセスしてください。
https://e.developer.yahoo.co.jp/dashboard/
アプリケーション一覧の中から、先程作成したアプリケーションをクリックして下さい。
コールバックURLに、以下のように入力して、「更新」をクリックします。
[先程メモしたAzure App ServiceのURL]/assets/auth.htmlこれでチャットアプリケーションを動かす準備は完了です。
動かしてみる
では、Azure App ServiceのURLにアクセスしてみましょう。
Yahoo!の認証画面が表示されます。Yahoo!のIDとパスワードを入力します。
チャットの画面が表示されます。おおー(๑•̀ㅂ•́)و✧
最後に
いかがでしたでしょうか?本シリーズの連載は今回で終了になります。本記事が、サーバーレスアーキテクチャ導入の際の参考になれば幸いです。