Azure FunctionsからCosmos DBに出力バインドする(2)~.csコンパイル編

1. はじめに

前回のエントリーに引き続きの投稿になります。
Azure Functions 出力バインドを利用し Cosmos DB にデータ出力を行います。

f:id:daigo-knowlbo:20170718235801p:plain

前回はAzureポータルのみでFunctionsの作成を行いました。
また、ソースコードの実装は .csx(C#スクリプト) をポータル上で編集しました。

今回は .csファイル で実装を行い、コンパイルアセンブリをAzure Functionsに発行することにします。
.csxでの実装に比べ、実行時コンパイル処理が省かれるので、起動時の動作が早くなります。

本エントリーのみでも理解できる内容として記述しますが、前回のエントリーと重複する部分は手短に記述します。ということで、前回のエントリーを見ていただけるとより理解しやすいかと思います。

ryuichi111std.hatenablog.com

2. 開発環境

コンパイル実装をするため、本エントリーでは以下の環境を利用します。

※ 2017/7/20現在、Stableな Visual Studio 2017 では 「Function Tools for Visual Studio 2017」がサポートされていません。

3. 前提条件

以下のAzure環境を前提条件とします。

3.1 Cosmos DB アカウント

以下のような 出力先 Cosmos DB アカウント を用意している前提とします。

f:id:daigo-knowlbo:20170720054830p:plain

API(データモデル)は「SQL (DocumentDB)」です。

3.2 Function App

前回エントリーで作成した以下の「Function App」を用意している前提とします。

f:id:daigo-knowlbo:20170720054951p:plain

「アプリ名」:RdExampleFunctionApp
「リソースグループ」:RdExampleFunctionApp
ホスティングプラン」:従量課金プラン
「場所」:西日本
「Storage」:新規作成 rdexamplefunctionapp

4. ② VS2017 Preview(Ver.15.3)を使ったcsコンパイルによる実装

VSを使用した実装に移りますが、「4.1 基本的なAzure Functionsプロジェクトの作成から発行まで」と、その上に「4.2 osmos DB出力バインドの追加からパブリッシュまで」の2段階で進めたいと思います。

4.1 プロジェクトの作成から基本パブリッシュまで

まず第1段階として、基本的なAzure Functionsプロジェクトの作成から発行までを行います。

(1) Visual Studio 2017 Preview(15.3)の起動

Visual Studio 2017 Preview(15.3)を起動します。
「Azure Function Tools for Visual Studio 2017」のインストールを済ませておいてください。

(2) Azure Functionsプロジェクトの作成

「ファイル → 新規作成 → プロジェクト」を選択します。
「新しいプロジェクト」ウィンドウで、テンプレートカテゴリーから「Cloud」を選択、プロジェクトテンプレートとして「Azure Functions」を選択します。
プロジェクト名は、ここでは「CosmosDbBindExampleFunction」としました。

f:id:daigo-knowlbo:20170719185150p:plain

以下のようなプロジェクトが作成されます。

f:id:daigo-knowlbo:20170719185207p:plain

(3) Functionソース(.cs)の追加

新規作成したプロジェクトには、まだ1つもFunction実装がありません。
Functionの実装ソース(.cs)を追加します。
ソリューションエクスプローラでプロジェクト名「CosmosDbBindExampleFunction」をマウス右ボタンクリック。表示されたメニューから「追加 → 新しい項目」を選択します。

f:id:daigo-knowlbo:20170719185448p:plain

表示された「新しい項目の追加」ウィンドウから「Azure Function」を選択し、名前を入力します。ここではデフォルトのまま「Function1.cs」としました。

f:id:daigo-knowlbo:20170719185550p:plain

「New Azure Function」ウィンドウが表示されます。

f:id:daigo-knowlbo:20170719190323p:plain

ここでは、設定内容は以下とします。

  • トリガーの種類: HttpTrigger
  • AccessRights: Anonymous(今回は簡易に匿名で利用できるようにします)
  • FunctionName: HttpTriggerCSharp

自動生成された Function1.cs は以下の通りです。

// 自動生成された Function1.cs 

using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;

namespace CosmosDbBindExampleFunction
{
  public static class Function1
  {
    [FunctionName("HttpTriggerCSharp")]
    public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
    {
      log.Info("C# HTTP trigger function processed a request.");

      // parse query parameter
      string name = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
        .Value;

      // Get request body
      dynamic data = await req.Content.ReadAsAsync<object>();

      // Set name to query string or body data
      name = name ?? data?.name;

      return name == null
        ? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
        : req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
    }
  }
}

(4) Nugetパッケージの更新

自動生成されたプロジェクトおよびfunction1.csは、2017/7/20現在の確認では、そのままではビルドが通りません。
Nugetパッケージの更新を行う必要があります。
ソリューションエクスプローラでプロジェクト名「CosmosDbBindExampleFunction」をマウス右ボタンクリック。表示されたメニューから「NuGetパッケージの管理」を選択します。

f:id:daigo-knowlbo:20170719190746p:plain

「更新プログラム」を選択、「プレリリースを含める」にチェック、「すべてのパッケージを選択」にチェックし、「更新」ボタンをクリックします。

f:id:daigo-knowlbo:20170719190950p:plain

(5) ビルド&発行

ソリューションエクスプローラでプロジェクト名「CosmosDbBindExampleFunction」をマウス右ボタンクリック。表示されたメニューから「発行」を選択します。

f:id:daigo-knowlbo:20170719193509p:plain

発行のUIが表示されるので「既存のものを選択」を選択して「発行」ボタンをクリックします(今回はAzureポータル上に既に RdExampleFunctionApp というFunction Appを作成済みの為)。

f:id:daigo-knowlbo:20170719193529p:plain

発行対象のFunction Appである「RdExampleFunctionApp」を選択して、「OK」ボタンをクリックします。

f:id:daigo-knowlbo:20170719193538p:plain

発行が完了したらAzureポータルでFunctionを確認します。
HttpTriggerCSharp関数が発行されて事を確認することができます。

f:id:daigo-knowlbo:20170719193555p:plain

4.2 Cosmos DB出力バインドの追加からパブリッシュまで

次に第2段階として、Cosmos DB出力バインドの追加を行います。

(1) DocumentDB Extensionの追加

Cosmos DBへの出力バインドを実装するために、以下のNuGetパッケージを追加します。

  • Microsoft.Azure.WebJobs.Extensions.DocumentDB

NuGetパッケージの管理画面から「Microsoft.Azure.WebJobs.Extensions.DocumentDB」を検索&選択して「インストール」を行います。

f:id:daigo-knowlbo:20170719192541p:plain

(2) Function1.csの実装を修正

Function実装を Cosmos DB出力バインド を行うように修正します。

// Cosmos DB出力バインド実装を加えた Function1.cs

using System.Linq;
using System.Net;
using System.Net.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;

namespace FunctionApp2
{
  public static class Function2
  {
    [FunctionName("HttpTriggerCSharp")]
    public static HttpResponseMessage Run(
      [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
      HttpRequestMessage req,
      [DocumentDB("CommunicationDB", "MessageCol",
            CreateIfNotExists = true,
            ConnectionStringSetting = "cosmosdb_DOCUMENTDB")]
      out object messageDocument,
      TraceWriter log)
    {
      string yourName = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "yourName", true) == 0)
        .Value;

      string message = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "message", true) == 0)
        .Value;

      // 出力パラメータmessageDocumentに設定された値がCosmos DBに出力される
      messageDocument = new
      {
        yourName = yourName,
        message = message
      };

      if (!string.IsNullOrEmpty(yourName) && !string.IsNullOrEmpty(message))
      {
        return req.CreateResponse(HttpStatusCode.OK);
      }
      else
      {
        return req.CreateResponse(HttpStatusCode.BadRequest);
      }
    }
  }
}

上記実装のうち重要なポイントを2つ以下に記述します。

①メインの Run() メソッドの引数

前回の.csxファイル形式での実装では、引数のバインドをAzureポータルのGUI上で行いました。
AzureポータルGUI上での操作は、実際には function.json ファイルに反映されます。つまり、GUI操作はエディタとしての機能であり、本質的には function.json への記述で設定を行いました。
コンパイルベースのFunction定義では、メソッド引数への属性設定により同様の設定を行います(つまり、引数属性設定により入出力バインドの設定を行うことができます)。

  • 第1引数

    [HttpTrigger(AuthorizationLevel.Anonymous, “get”, “post”, Route = null)] HttpRequestMessage req

入力のHttpTrigger定義となります。匿名ユーザーのアクセスを許可し、GET / POST を受け入れます。ルーティング定義はnullとしています。

  • 第2引数

    [DocumentDB(“CommunicationDB”, “MessageCol”, CreateIfNotExists = true, ConnectionStringSetting = “cosmosdb_DOCUMENTDB”)] out object messageDocument

こちらがCosmos DB出力バインドの定義になります。
CommunicationDBデータベース、MessageColコレクションへの出力としています。
CreateIfNotExists=trueは、本Functionの実行時にCosmos DB側のデータベース・コレクションが存在しなかった場合に自動生成するかどうかの設定です。ここでは、trueなので存在しなかったら作成する意味となります。
ConnectionStringSetting 値は、出力先のCosmos DBへの接続文字列を表します。ただし、接続文字列自体ではなく、接続文字列を設定したアプリケーション設定キーとなります。
アプリケーション設定キーは、Azureポータルで設定・確認することができます。
RdExampleFunctionAppを選択し、「アプリケーション設定」をクリックします。

f:id:daigo-knowlbo:20170720060303p:plain

「アプリ設定」項目に「キーと値」の設定が行えるので、ここに「cosmosdb_DOCUMENTDB」キーを追加し、値としてCosmos DBアカウントへの接続文字列を追加します(前回の.csx形式によるブログエントリーを実施している場合、既に当該キーは追加されていると思います)。

f:id:daigo-knowlbo:20170720060347p:plain

②Cosmos DB ドキュメントの作成

出力バインド定義が行われた引数 messageDocument をnewで生成することで、作成するCosmos DB(DocumentDB)ドキュメントを指定することができます。

messageDocument = new
{
yourName = yourName,
message = message
};

(3) 発行

VSから発行を行います。
※ 既に前述で発行方法は説明したので、ここでは割愛します。

(4) テスト実行

今回発行したFunctionはHttpTriggerで起動します。
匿名ユーザーによるリクエストも許可している為、以下のURLによりキックすることができます。

https://rdexamplefunctionapp.azurewebsites.net/api/HttpTriggerCSharp?yourName=ryuichi.daigo&message=hello azure functions

ブラウザで上記URLをリクエストします。

AzureポータルからCosmos DBのデータエクスプローラを確認すると、以下のようにドキュメントが作成されたことを確認することができます。

f:id:daigo-knowlbo:20170720060710p:plain

5 まとめ

Azure Functionsは、超基本でいうとあくまで「Function(関数)」なのですが、今回紹介したBindを含め、やはり色々な機能を持っています。最近ではDurable Functionsなんてのも出てきましたし。
そんな進化し続けるAzure Functionsですが、Visual StudioのToolkitのリリースはやや遅めな印象を持っています。とはいえ、そろそろ正式版も出るんじゃないかなあ・・・とも思うので、そのあたりの開発インフラ事情が更新されたら、本エントリーも改めて修正していきたいと思います。