himanago

C#とかAzureとかMS系の技術メモ中心に書きたいです。最近はLINE関連技術やVUIも多め。

Micronaut for Azure Functions 試してみた(2020年版)

Micronaut now supports Microsoft Azure Functions !!

去年、こんなLTをしました。

www.slideshare.net

当時はまだ Azure Functions がサポートされておらず、Web App として動かしてみたよって話だったんですが、ついに Azure Functions に正式対応されたので、動かすところまでやってみました。

プロジェクト作成

なんと、Web 上で自分好みの雛形プロジェクトを作ってダウンロードできます。これめっちゃいいです。

micronaut.io

こんなかんじで構成を選択して、最初のプロジェクトを生成することができます。

f:id:himanago:20200803122348p:plain
Azure Functions 用に生成

Azure Functions 用の雛形の作り方は、

  • Application Type を「Serverless Function」
  • Java Version を「8」
  • +FEATURES で「azure-function」を追加

でいけます。

そのほかはお好みで。今回は Groovy 縛りで行きます(Groovy / Gradle / Spock)。

PREVIEW」を押すと中のコードも確認できます。

f:id:himanago:20200803122513p:plain
プレビュー

「GENERATE PROJECT」を押すと zip が落ちてきます。

最初のとっかかりとしては非常にわかりやすく、とてもいいですね。

開いてみる

適当なフォルダに zip の中身を解凍して開いてみます。

今回は IntelliJ IDEA Ultimate を使ってみます。フォルダを開いたら起動の設定。

右上の「Add Configuration...」から f:id:himanago:20200803194210p:plain

「Templates」→ 「Gradle」で以下のように設定し、ダイアログ右上の「Create configuration」→「OK」 f:id:himanago:20200803195142p:plain

右上の▶︎ボタンから実行できるようになったのでここをクリックでローカル実行します。 f:id:himanago:20200803194448p:plain

なお、IDE を使わない場合は、Gradle コマンドを使って実行します(雛形の README にも実行手順が書いてあります)。

ところが、このまま実行すると「Cannot package functions due to error: Azure Functions entry point not found, plugin will exit.」というエラーが出ました。

なんで??と思ったんですが、エラー内容の通りで関数のエントリーポイントがないんですね。アノテーションで関数名を指定する、あれです。

こちらのドキュメントの記載を参考に、@FunctionName('echo') をメソッドに追加します。

Micronaut Azure

さらに、String req のバインドもうまくいかないようだったので、同じくドキュメントと同じ形式に修正。

ついでに

  • ログ出力の行も Groovy っぽくなかったので修正
  • return も消しつつ(Groovy では不要)body の中身をそのまま返すよう修正

しました。

Before:

package dev.himanago;
import com.microsoft.azure.functions.annotation.*
import com.microsoft.azure.functions.*
import io.micronaut.azure.function.AzureFunction

/**
 * Azure Functions with HTTP Trigger.
 */
class Function extends AzureFunction {
    String echo(
        @HttpTrigger(name = "req", methods = HttpMethod.POST, authLevel = AuthorizationLevel.ANONYMOUS)
        String req,
        ExecutionContext context) {
        context?.getLogger()?.info("Executing Function: ${getClass().getName()}");
        return String.format(req)
    }
}

After:

package dev.himanago;
import com.microsoft.azure.functions.annotation.*
import com.microsoft.azure.functions.*
import io.micronaut.azure.function.AzureFunction

/**
 * Azure Functions with HTTP Trigger.
 */
class Function extends AzureFunction {
    @FunctionName('echo')
    String echo(
        @HttpTrigger(name = "req", methods = HttpMethod.POST, authLevel = AuthorizationLevel.ANONYMOUS)
        HttpRequestMessage<Optional<String>> request,
        ExecutionContext context) {
        context?.logger?.info "Executing Function: ${this.class.name}"
        request.body.get()
    }
}

これで無事ローカル実行できました。

f:id:himanago:20200804230751p:plain
Postmanでの実行結果

<補足追記>

ローカル環境に Azure Functions Core Tools、Gradle を入れておく必要があります。
また、JDK は 8 じゃないと正しく動かないので、java -version で 1.8 が出るようにしておく必要があります。

Azure にデプロイ

今度は Azure にデプロイして動かしてみましょう。

デプロイの設定は、build.gradle に書きます。最初から書いてあるので、これを自分の環境に合わせて書き換えれば OK です。

azurefunctions {
    resourceGroup = 'java-functions-group'
    appName = 'demo'
    pricingTier = 'Consumption'
    region = 'westus'
    runtime {
      os = 'windows'
    }
    localDebug = "transport=dt_socket,server=y,suspend=n,address=5005"
}

Terminal から az loginaz account set -s <subscription id> を実行して、gradle azureFunctionsDeploy すればデプロイできます(gradle コマンドは IDE で実行しても OK)。

Function App のリソースが Azure 上にない場合は作成してくれます。

できあがった Function App をポータルから開いてテストしてみると、ちゃんと実行できました!

f:id:himanago:20200805001827p:plain
Azureポータルでの実行結果

ちょっと苦戦しましたがとりあえずいけそうです。

今後いろいろ試してみて、よさそうなら実戦投入も視野に入れていこうかなと思います!

YouTube 公開されたイベント「de:code 2020」の開催期間中に観たセッションの感想

はじめに

パーソナルスポンサーとして参加した de:code 2020、開催期間が終了しましたが全セッションが YouTube で公開されました。

www.youtube.com

資料も公式のセッションリストから落とせます(終了間際にまとめて落としたのに!)。

セッション リスト | de:code (decode) 2020 | 開発者をはじめとする IT に携わるすべてのエンジニアのためのイベント

いつも Twitter で実況したりはするもののイベント参加の感想とかは書いてなかったんですが、せっかくぜんぶ YouTube に上がったということで、せっかくなので開催期間中に観たものだけ、感想メモを放流しようかなと思いまとめてみました。

動画も埋め込んでますんでよろしければ観てみてください。

Apps and Infrastructure

【A01】React Native で Windows アプリ開発 ~React Native for Windows~

www.youtube.com

React はまったく触ってこなかったけど、React Native 自体がけっこうよさそうに思えました。

仮に iOS/Android/Win の 3 プラットフォーム対応のアプリを同時開発したい、となった場合にどれだけ快適かが気になりました(Xamarin と比べて)。

環境整えてやってみようと思います。

【A02】Azure Bot Services を使って Teams bot を開発する

www.youtube.com

Teams Bot 開発を始めるのに必要なものすべてが詰まってるセッション。30分でスライド95枚!

Bot Services は(変化が激しい印象なので)使うのをわりと避けてきてたけど、使ってみてもいいのかな…という気になってきました。

App Service Editor でオンラインで完結できるの知らなかった。ハンズオンとかには便利そう(まあ実戦では使わないかな??)。

にしても、フォローアップ用の記事のボリュームもすさまじい。。手厚いです。

【A03】Azure トラブルシューティング道場 ~どこかがおかしくなりました~

www.youtube.com

普遍的な考え方の話。インシデントのための準備の重要性や、その方法について。

思えば Monitor まわり、細かい話をきちんと勉強していなかった(使うときにドキュメント見るくらいだった)ので、やっぱり Docs ちゃんと読まないとだめですね。

なんとなくでやってるといざというときに困るので、改めて Learn と Docs 読んで見直そうと思います。

【A04】Azure ならこうする、こうできる! ~ AWS 技術者向け Azure サービス解説 de:code 2020 Edition ~

www.youtube.com

そのまま AWS 技術者向けということだったのですが、そのまま、AWS 技術者の方に観ていただきたい。

AWS 使ってる人で Azure わからないんだよね…っていう人に紹介してみてもいいかなと思いました。

自分は Azure 技術者なので…「AWS ならこうする、こうできる! ~ Azure 技術者向け AWS サービス解説 ~」が観たい(笑)

【A05】Azure インフラ 最新アップデート!!

www.youtube.com

インフラまわりについて、細かいアップデートも触れられてとてもありがたいセッションでした。以下気になったところ↓

  • Routing Prefernce、インターネット経由だと通信コスト安くなるっていうメリットがあるのね…たしかに。
  • ARM Template の What-if分析(デプロイすると何が起きるかプレビューできる機能)は便利そう。
  • Blobのアップデートは便利なの多いので早く GA してほしいところ、、

おすすめドキュメントの、Learn のコンテンツはぜひチェックしておきたいと思いました。

【A07】2020 年の最新 Xamarin 概要

www.youtube.com

Xamarin.Forms の最新情報。収録時期の関係か、.NET MAUI の名前は出てこないです。

XAML ではなく C# で画面が書けるようになる話が紹介されましたが、XAML のほうが見やすく感じる。実際はどうなんだろう。単に慣れの問題?

C# で書ける場合、partial class で UI とロジックを分けるというやり方を紹介いただけたのがよかった。これは意識しておきたい。

【A10】Build 2020 最新情報 〜 Azure & Visual Studio & .NET

www.youtube.com

Build 発表内容をざっくり紹介するセッション。1h と長めでデモ多め。とても楽しかったです。

Blazor WebAssembly のデバッグ方法について、ブラウザ・Visual Studio 両方でのやり方の紹介があったのが特によかったので、ここはあとで実際に動かしてみたいと思いました。

【A11】Azure 10 周年の節目に見直したい、Azure インフラのちょっと大事な話

www.youtube.com

とてもよかった。特に前半の Azure の歴史の話が非常に興味深かった。。

Azure 好きとしては、こういう話があるとテンション上がりますね。

中身のところでいうと、PaaS を利用する場合でもインフラのことに目を向けるのはやっぱり重要なのだなと改めて感じられてよかったです。

【A14】「あつまれ フロントエンドエンジニア」 Azure Static Web Apps がやってきた

www.youtube.com

Static Web Apps。とてもいいですよね。パーソナルスポンサーで提供したハンズオンでも使いました。

セッションで紹介があったように、PR のレビュー時に実際のデプロイされたサイトを確認できるのはすごく便利。

はやく実戦でも使いたいのですが、業務では Azure DevOps(Repos)を使っているので、そっちへの対応が(しばらく)なさそうなら、GitHub の利用も検討しないとなのかもですね。。。

Data and AI

【D15】Azure Cosmos DB - Build 2020 アップデート

www.youtube.com

Cosmos DB のアップデートまとめ。

目玉機能の紹介がたくさんあるなか、C# ノートブックのデモがありました。このあたりはあまり追えていないので助かりました。使い慣れてる .NET SDK ベースで触れるのはうれしいかも。

D51】ハンズオンで学ぶ AI ~ Bot Framework Composer + QnA Maker / Custom Vision + IoT Edge

www.youtube.com

Composer が便利そう。

ひととおりの作り方が紹介されているので、観るだけでイメージできるのはとてもありがたかったです。

ハンズオン資料が公開されているため、それを見て実践できるというのもうれしい。あとでやります。

Mixed Reality and IoT

【X08&X09】『RE:BEL ROBOTICA レベルロボチカ』の世界と現代をミックス! MR で変わるライフスタイルとワークスタイル [episode 1] ストーリー編 / [episode 2] 技術解説編

www.youtube.com

www.youtube.com

まず、ただのセッション録画ではなく動画コンテンツとして完成されてるのがびっくり。コンテンツの作り方の観点でもとても興味深かったです。

デモの撮影方法も、Teams を組み合わせてうまく遠隔で動画作成する…という使い方。このコロナ禍、さまざまな工夫の中で新しいものが生み出されているんですよね。

【X06】そのロジック、IoT Edge で動きます - Azure IoT Edge 開発 Deep Dive

www.youtube.com

デモではちょちょいとやってるかんじだけど、IoT Edge はやっぱり壁にはぶちあたるものらしい。

実際モジュール作成は難しい。ちょっとやったことあるけど、すごいハマる。。

「ねばってねばって壁をぶち壊す」のが必要ということなので、サンプルや資料をみて参考にしたい。

※「このセッションの使い方」が見る→ドキュメントを読む→もう一度見る だったので、YouTube で公開されて本当に助かった。。

全体感想

興味があるところから優先して観たので Apps and Infrastructure のトラックばっかりになっちゃいましたが、刺激を受けるセッションばかりでとても面白かったです。

オンデマンドセッションだけなので、いつでも好きなときに観れるということでしたが、そのためにどうしても優先度が下がってしまい、期間終了間際までなかなかスケジュールを空けられなくなりました。。

結局、がしっとリアルタイムでやる日が設けられたイベントじゃないと、自分はなかなか観ようって思えなかった&時間を作れなかったです。。(個人の感想)

あと、一体感というか、同時性がないぶん盛り上がりに欠けたような…気もしました。観ながら Twitter で実況とかしないですし、ツイートはあっても「これから見ます」とか終わった後の感想とか冷静なものばかり。

セッション中の「おーっ」みたいな感想の共有はできないので、熱気みたいなものは感じられずちょっとさみしい感じもしました(これはセッションを担当くださった方々がいちばんに感じていることかなと思いますが…)。

ふだんの仕事では社内研修の運営や講師をやっていて、研修とかでオンラインでの双方向性や盛り上がりを考えます。なので、こういう課題がどうにかならないかな?と思ったんですが、たとえば、

  • オンデマンドの動画の横にツイートボタンを用意
  • ツイート本文にハッシュタグと再生中の動画の秒数が入った状態で投稿される
  • 秒数の入ったツイートが動画再生にあわせて同期的に表示される

みたいな仕組みがあると、観るタイミングは違ってもその時々の熱さを共有できないかな??とか思ったりしました。Togetter にもまとめやすい?

でもどうだろう、そもそも投稿してくれるかな。。それにわざわざ Twitter でやる必要もなくて、サイト内にコメント機能があればそれでいいのかも。

なんにせよ、オンラインイベントは動画として残りやすく、あとからの公開も容易なのでいいですね。

パーソナルスポンサー提供のコード/ツールたちもぜひ活用いただければと思います。(前のブログにも書きました↓)

himanago.hatenablog.com

de:code 2020 に Microsoft MVP パーソナルスポンサーについて(Azure のハンズオンを提供しました+気になったもの)

(すっごくいまさらなのですが…)

6 月 17 日 (水) ~ 7 月 17 日 (金) で開催された日本マイクロソフト主催のオンラインイベント「de:code 2020」の MVP パーソナルスポンサーとして、ハンズオン資料の提供をさせていただきました。

スポンサー | de:code (decode) 2020 | 開発者をはじめとする IT に携わるすべてのエンジニアのためのイベント

提供したもの

今回提供させていただいたのは、「Azure サーバーレステクノロジー体験ハンズオン ~イベント駆動でつなげよう~」というタイトルで、 個人的に好きで業務でも使用している Azure のサーバーレス系サービスを組み合わせて簡易勤怠管理アプリを構築するハンズオンです。

github.com

チャットツール(LINE or Teams)から、いつからいつまで働いたかを記録でき、それが Web ページにリアルタイムで反映されます。これがあればテレワーク中の時間管理が楽になるかも?といったものです。

LINE or Teams → Functions → Cosmos DB → Functions → SignalR Service → Static Web Apps リアルタイム更新のしくみを VS Code とその拡張機能を利用して開発します。

企画段階では Web ページ部分を Blob Storage の 静的 Web アプリホストの機能で作る予定だったのですが、発表されたばかりの Static Web Apps が便利すぎたので、こっちにシフト。結果的に言語は JavaScript に統一することに。

f:id:himanago:20200725113940p:plain

イベント駆動でつながっていく様子を手軽に体験できるように…というねらいのハンズオンですが、今後もこういうハンズオンをバリエーション増やしながらどんどん作っていけたら…と思っています。

ほかのパーソナルスポンサー提供のツールについて

ほかにもたくさんのツールやサンプルコードが MVP パーソナルスポンサーから公開されています。

自分は「Instance selector for Azure App Service Kudu (Google Chrome Extension)」が特にお気に入りで、業務で活用させていただいています。

AzureKuduInstanceSelector/README_ja.md at master · pnopjp/AzureKuduInstanceSelector · GitHub

ほかにもハンズオン資料では、

Surface Duo を先取り。Dual Screen 対応アプリを作ってみよう!
GitHub - TomohiroSuzuki128/XFSurfaceDuoSample2020

Bot Framework Composer + QnA Maker で作る Q&A チャットボット ハンズオン
GitHub - seosoft/BfCompQnaBot: Bot Framework Composer + QnA Maker で作る Q&A チャットボット ハンズオン

あたりが気になっているので、近日中にやってみる予定!

まとめ

de:code 2020 の開催期間は終わりましたが、セッション資料と動画が引き続き公開されています。

www.microsoft.com

もちろん、パーソナルスポンサー提供のツール&サンプルコードも公開がなくなるわけではありません!

以下の記事にすべてまとめていただいていますので、ぜひたくさんの方に見ていただけたら…と思います。

kogelog.com

de:code 2020 の期間中に楽しみきれなかった分も、引き続き時間を見つけて活用していきたいと思います。

LINE Bot を Azure Functions (C#) で作る際のオウム返しテンプレ

概要

以前 JavaScript + Azure Functions で作る LINE Bot のテンプレについて書きましたが、C# のもなかったじゃん…ということで書いていきます。

himanago.hatenablog.com

いつも横着していたロギングの部分をちゃんと DI で書いてみたので、今後はこれをテンプレとして使っていきたいと思います(よさそうなら VS のテンプレートにもしていけたら)。

プロジェクトテンプレート

Azure Functions v3 (.NET Core) を使用します。

f:id:himanago:20200602022245p:plain

必要な NuGet パッケージ

LineDC.Messaging (v1.2.0)

Messaging APIC# SDK。最新版の機能が含まれているのはこのパッケージです。
もともとあった Line.Messaging は更新が止まり、こちらに移行しているので注意が必要です。

Microsoft.Azure.Functions.Extensions (v1.0.0)

Azure Functions で DI を使うために必要なパッケージです。

このほか、パッケージとしては Microsoft.NET.Sdk.Functions (サンプルでは v3.0.7) が入っている状態です。

作成するファイル

C# のコードで作成するのは 4 つ。

  • LineBotApp.cs
  • Startup.cs
  • WebhookEndpoint.cs
  • Configurations/LineBotSettings.cs

LineBotApp.cs

WebhookApplication を継承した、LINE Bot のメイン処理を司るクラスです。

OnMessageAsync 等のイベントハンドラーをオーバーライドして処理を定義しておくことで、Webhook 時に受け取ったメッセージに応じて呼び出してくれます。
今回は、オウム返し(Echo Bot)なのでメッセージを受け取ったらそのまま返すだけの処理が書いてあります(メッセージの種類で switch)。

もう一つのポイントはコンストラクタです。
DI によって受け取る 3 つの引数を持ち、基底クラスの WebhookApplicationILineMessagingClient とチャネルシークレット(LineBotSettings オブジェクトに含まれる)を渡しながら、自身で使用するロガーを loggerFactory.CreateLogger から生成しています(このクラスは WebhookEndpoint という関数内で使われるので、その関数名を指定しています)。

using LineBotTemplate.Configurations;
using LineDC.Messaging;
using LineDC.Messaging.Webhooks;
using LineDC.Messaging.Webhooks.Events;
using LineDC.Messaging.Webhooks.Messages;
using Microsoft.Azure.WebJobs.Logging;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace LineBotTemplate
{
    public class LineBotApp : WebhookApplication
    {
        private ILogger Logger { get; }

        public LineBotApp(ILineMessagingClient lineMessagingClient, LineBotSettings settings, ILoggerFactory loggerFactory)
            : base(lineMessagingClient, settings.ChannelSecret)
        {
            Logger = loggerFactory.CreateLogger(LogCategories.CreateFunctionUserCategory(nameof(WebhookEndpoint)));
        }

        protected override async Task OnMessageAsync(MessageEvent ev)
        {
            Logger?.LogTrace($"OnMessageAsync => Type: {ev.Source.Type}, Id: {ev.Source.Id}");
            switch (ev.Message)
            {
                case TextEventMessage textMessage:
                    await Client.ReplyMessageAsync(ev.ReplyToken, textMessage.Text);
                    break;
                case MediaEventMessage mediaMessage:
                    await Client.ReplyMessageAsync(ev.ReplyToken, $"contentProvider: {mediaMessage.ContentProvider}");
                    break;
                case FileEventMessage fileMessage:
                    await Client.ReplyMessageAsync(ev.ReplyToken, $"filename: {fileMessage.FileName}");
                    break;
                case LocationEventMessage locationMessage:
                    await Client.ReplyMessageAsync(ev.ReplyToken, $"{locationMessage.Title}({locationMessage.Latitude}, {locationMessage.Longitude})");
                    break;
                case StickerEventMessage stickerMessage:
                    await Client.ReplyMessageAsync(ev.ReplyToken, $"sticker id: {stickerMessage.PackageId}-{stickerMessage.StickerId}");
                    break;
            }
        }
    }
}

Configurations/LineBotSettings.cs

LINE Bot の設定に関するクラス。

namespace LineBotTemplate.Configurations
{
    public class LineBotSettings
    {
        public string ChannelSecret { get; set; }
        public string ChannelAccessToken { get; set; }
    }
}

LineBotSettings:ChannelSecretLineBotSettings:ChannelAccessTokenといったキー名で Function App のアプリケーション設定(Azure での実行時)や local.settings.json (ローカル実行時)に追加しておくと、次に載せる Startup.cs でこのクラスのプロパティの値として使うことができます。

f:id:himanago:20200602023813p:plain

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "LineBotSettings:ChannelAccessToken": "dummy",
    "LineBotSettings:ChannelSecret": "dummy"
  }
}

どちらの値も、LINE Developers で発行・確認できる値を入れておきます。

Startup.cs

DI コンテナへの登録を行う Startup クラスです。

最初に ConfigurationBuilder を使って local.settings.json環境変数(アプリケーション設定)から値を読み取り、LineBotSettings に入れて、そのあとに LineBotSettings のオブジェクトである settingsLineMessagingClientLineBotAppAddSingleton しています。

using LineBotTemplate.Configurations;
using LineDC.Messaging;
using LineDC.Messaging.Webhooks;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(LineBotTemplate.Startup))]
namespace LineBotTemplate
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var config = new ConfigurationBuilder()
                .AddJsonFile("local.settings.json", true)
                .AddEnvironmentVariables()
                .Build();

            var settings = config.GetSection(nameof(LineBotSettings)).Get<LineBotSettings>();

            builder.Services
                .AddSingleton(settings)
                .AddSingleton<ILineMessagingClient>(_ => LineMessagingClient.Create(settings.ChannelAccessToken))
                .AddSingleton<IWebhookApplication, LineBotApp>();
        }
    }
}

LineBotApp は Webhook 応答で使用するものなのでこのサンプルでも DI で受け取って使用しています。
ILineMessagingClient はサンプルでは直接使用していませんが、何かの関数で Push したりするのに使うために、DI で受け取れるようにしておくと便利です。

ほかに必要なサービス(HttpClient や、CosmosClient)があればここで追加します。
また、DI されるインスタンスの生成方法は、DI コンテナに登録するクラスごとに必ず確認しましょう(よく間違えるので気を付けたい)。

メソッド 内容
AddTransient インジェクションごとに生成
AddScoped リクエストごとに生成
AddSingleton 一度生成されたものを使いまわす

WebhookEndpoint.cs

Azure Functions のプロジェクトテンプレートから作成したときは Function1.cs という名前で作られますが、リネームして中身を書き換えます。

DI するので static を外してコンストラクタで IWebhookApplication を受け取るようにしておきます。Startup で設定した通り、LineBotAppインスタンスが受け取れます。

関数の中の処理としては、受け取ったメッセージをもとに RunAsync を呼ぶだけで OK です(勝手に OnMessageAsync 等は実行してくれる)。

using LineDC.Messaging.Webhooks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Threading.Tasks;

namespace LineBotTemplate
{
    public class WebhookEndpoint
    {
        private readonly IWebhookApplication _app;

        public WebhookEndpoint(IWebhookApplication app)
        {
            _app = app;
        }

        [FunctionName(nameof(WebhookEndpoint))]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
            ILogger log)
        {
            try
            {
                var body = await new StreamReader(req.Body).ReadToEndAsync();
                var xLineSignature = req.Headers["x-line-signature"];

                log.LogTrace($"RequestBody: {body}");
                await _app.RunAsync(xLineSignature, body);
            }
            catch (Exception ex)
            {
                log.LogError(ex.Message);
                log.LogError(ex.StackTrace);
            }

            return new OkResult();
        }
    }
}

まとめ

あらためて、Messaging API の新しい C# SDK 便利です。

Functions でも、DI で使うサービスを整理しておくと拡張もしやすいですね。

サンプルリポジトリ

上記のコードは以下のリポジトリに置いてあります。 github.com

Azureサーバーレス&LINE API フル活用のシステム事例紹介!というタイトルで登壇しました&しゃべりきれなかった部分を補足します

はじめに

5/28(木)に、「クラウドが得意なLINE API Expert集合!サーバレス×LINEでアプリ開発してみたLT」というイベントでお話ししました。

linedevelopercommunity.connpass.com

時間 15 分で Azure サーバーレス × LINE API のおもしろさを、実例を交えてお伝えするという内容でしたが、時間も短く話したりない部分もあったので、補足込みで記事にします。

概要

「Azureサーバーレス&LINE APIフル活用のシステム事例紹介!」というタイトルでお話ししました。

ちょっと盛りすぎなタイトルですが、実際に業務で開発しているシステムの中で Azure サーバーレステクノロジーを活かしている機能がいくつかあったので、(ちょっとデフォルメして)紹介するという内容です。

Azure のサーバーレス

Azure の中でサーバーレスというと、たくさんのサービスが該当しますが、今回はこの 3 つを中心にまとめました。

f:id:himanago:20200530030545p:plain

サーバーレスとは「サーバー管理を意識する必要がない」という意味で、イベント駆動で動き、動いた分だけの課金体系という特徴を持ちます。Azure でこの特徴を持つ代表格は Azure Functions です。

Azure Functions

Azure Functions はサーバーレスなコード実行環境で、関数コードをデプロイ(または Web のポータル上で直接記述)するとすぐにイベント駆動で動作する関数が手に入ります。

さまざまな言語に対応し、デフォルトでも C# / JavaScript (TypeScript) / F# / Java / PowerShell / Python という多くの言語がサポートされているほか、(現在プレビューですが)カスタムハンドラーという仕組みを使えば、任意の言語で開発したプログラムを裏に置いて使うことができるので、事実上どんな言語でも Azure Functions を利用することができます。

Azure Functions でサポートされている言語 | Microsoft Docs

Azure Functions のカスタム ハンドラー (プレビュー) | Microsoft Docs

そのほか、豊富なトリガーと入出力バインディングが魅力的です。

HTTPトリガーや BLOB トリガーなど、イベントに応じて起動する設定がいろいろとできます。LINE API と組み合わせて使用することの多い HTTP トリガーも、Azure Functions 単体で Webhook をさばくことができるのもお手軽で使いやすい点です。

それと同時に、DB からの読み取り(入力)や DB への書き込み(出力)などの入出力バインディングを簡単に設定することができ、関数を使ってさまざまなデータのやりとりを行うことができます。

サーバーレスなコード実行環境としての Azure Functions は、通常「従量課金プラン」と呼ばれ、使われていない間は停止しています。そこから関数を呼び出して動作が始まるまでに時間がかかってしまうというコールドスタートの問題があるため、LINE Bot のリプライなど、即返信する必要のあるものでは向かないことがあります。

これに対する対策としては、常時稼働するインスタンスを用いる「App Service プラン」や、イベントに応じたスケールに対応する従量課金プラン同様のサーバーレスらしさを持ちながらコールドスタートのない「Premium プラン」を使うこともできます。料金はそれなりにかかってしまいますが、ユースケースによっては絶大な威力を発揮します。

Azure Cosmos DB

Azure における NoSQL の DB である Cosmos DB は、惑星規模のアプリケーションにも対応する大規模向けな DB として登場しましたが、さまざまな機能追加、特に今年登場した Free Tier (無料枠)の登場で小規模サービスや個人開発でも使いやすくなりました。

API (DB の形式)も選ぶことができ、とても使いやすいサービスです(自分は SQLを使ってデータ検索できるドキュメント型の「コア (SQL)」をよく使います)。

サーバーレスへの対応も進んでいて、2020年5月に「Serverless pricing」が発表されています。

Azure Cosmos DB で任意のサイズまたはスケールのアプリを構築 | Azure のブログと更新プログラム | Microsoft Azure

Azure SignalR Service

こちらはクライアントをリアルタイム更新する WebSocket のためのサービスです。出力バインディングを使うことで Functions とも簡単に統合できます。

WebSocket は、Web ページなどの更新をクライアント側の再取得操作に頼らずに、サーバー側のアクションを起点としてクライアント側の画面を更新できる技術です。もともと ASP.NET に SignalR という WebSocket 対応の機能があり、それの Azure 版といったところでしょうか。

Functions と組み合わせて使えば、簡単にサーバーレスでリアルタイムにクライアントに更新を通知できる機能を実現できます。

Azureサーバーレスフル活用のシステム事例 with LINE API

今回紹介したのは、以下の特徴をもったシステムです。

f:id:himanago:20200530030601p:plain

  • 自動応答(自然言語処理によりユーザーの意図を読み取り、数往復の対話フローで自動で処理)
  • 手動応答(運営担当者のマニュアル返信による個別トーク

の 2 つの応答モードを持ち、それをユーザーごとに切り替えることができます。

応答モード

応答モードといえば、LINE Bot / LINE 公式アカウントの標準でもサポートされています。

f:id:himanago:20200530030627p:plain

ユーザー(Bot の友だち)とチャットをするか、Bot による自動応答をさせるか選べる機能ですが、この機能では Bot / 公式アカウント単位での 切り替えしかできず、ある一人のユーザーと「チャットモード」で会話したい、と思って切り替えると、その他のユーザーが自動返信による Bot の機能が使えなくなってしまいます。

この弱点を克服するため、今回のシステムではユーザーごとの応答モードの切り替えを実装しています。

リアルタイムチャット

まず先に、マニュアル返信による手動応答(個別トーク)の実現方法を説明します。

運営・管理側には以下のような Web 画面を用意します。

f:id:himanago:20200530030656p:plain

この画面でユーザーとチャットを行いますが、チャットなので画面はリアルタイムに更新されてほしいところ。SignalR の出番です。

さらに、組み合わせるのは Cosmos DB の Change Feed と呼ばれる機能です。この機能は、Cosmos DB へのデータの変更を検知し、それを順番に次のイベントに伝えてくれる機能です。

以下の図のように、Change Feed は Cosmos DB トリガーとして Functions を起動するイベントとしてつながり、その Functions に出力バインドされた SignalR Service がクライアントにいあるタイムで更新を通知します。

f:id:himanago:20200530030719p:plain

今回のシステムでいえば、Bot からのメッセージが Cosmos DB に格納されると、Change Feed により Functions が動き、SignalR で Web サイトをリアルタイム更新します。

f:id:himanago:20200530030731p:plain

また、Web 画面から LINE へも同じ Change Feed / Cosmos DB トリガーを共通で利用しており、起動した関数から Messaging API の Push API でメッセージを送信します(Reply API ではなく Push API なのは、返信時にはリプライトークンの有効期限が切れてしまっているため)。

f:id:himanago:20200530030744p:plain

Web → LINE では、個別の担当者からのメッセージということがわかりやすくなるよう、Messaging API の icon / nickname switch API を使い、担当者のアイコンと名前を表示するようにしています。

ニュース: アイコンおよび表示名が変更できるようになりました | LINE Developers

自動応答部分

自動応答部分は自作のAzure Functions + C# の LINE Botフレームワークを使っています。bot-expressBot Framework のように会話コンテキストを記憶したうえでの対話フローを通してユーザーの課題解決を行う機能を「スキル」という単位で追加していける仕組みを作りました。

f:id:himanago:20200530030824p:plain

Azure には Bot Framework が動作するマネージドな Bot Service というのがあるのですが、今回のシステムでは Functions ベースで組みたかったので、「スキル」単位で対話フローの処理を開発していける簡易版のフレームワークを自作した、という経緯です。

ちなみに、実装がちょっと気に食わない部分があるので、個人として OSS で作り直そうかなと思っていたりします(時期など予定は全く決まっていませんが)。

Messaging API と LIFF の連携

対話フローの中で LIFF を併用したいことは多くあると思います。

チャットボットでは、通常対話を通して Bot 側に必要な情報を与えますが、チャットのやりとりなのでたくさんの種類の情報をインプットするのは一苦労です。

その点、通常の Web 画面のフォームなら、一気に入力欄を埋めて送信するだけで済むので、チャットボットにおいてもフォーム入力の併用が求められる場面は多くなります。

LINE Bot においては、LINE 内 に埋め込むことのできる Web アプリである LIFF(LINE Front-end Framework)を組み合わせることになりますが、チャットボット部分の Messaging API と LIFF をあわせて使う際、タイミングや情報の連携ために工夫が必要なときがあります。

たとえば、Messaging API で対話をしている途中で LIFF を使った入力を挟み、同じ対話フローでその LIFF の入力内容を使った対話を続けていく、というような場面です。

ユーザーID (Messaging API と LIFF では、同一プロバイダ内であれば同じ LINE ユーザー ID が振られます)をキーにして DB に登録したりしていけばよいのですが、ここを、Durable Functions を使って簡略化するアイディアを紹介します。

Durable Functions とは、「スターター(クライアント)」「オーケストレーター」「アクティビティ」といった関数を組み合わせ、サーバーレスでありながらステートフルな処理を実現する、Azure Functions の拡張機能です。

f:id:himanago:20200530030922p:plain

今回使うのは Durable Functions で利用できる「外部イベントの待機」機能です。

オーケストレーター関数は、外部イベントを待機して処理を中断することができます(関数としては実行されたままになるわけではなく、何度も再実行されてイベントの発生を監視している)。その間に、別の関数からイベントを発生させると中断していたオーケストレーターが再開します。

これを、Messaging API と LIFF の情報連携・待ち合わせに利用します。

f:id:himanago:20200530031239p:plain

  1. ユーザーから Bot に対して話しかけ、対話フローを開始
  2. HTTPトリガーの関数が起動。オーケストレーターをLINEユーザーIDをインスタンスIDとして起動し、イベントを待機
  3. 同じHTTPトリガーの関数からユーザーへLIFFアプリへのリンクを返信
  4. ユーザーはLIFFのフォームにデータを入力して送信(別のHTTPトリガーの関数にPOST)
  5. DBへの登録処理などを行い、イベントを発生させる(LINEユーザーIDとイベント名を指定)
  6. 同一LINEユーザーIDで待機していたオーケストレーターがイベントの発生を検知し、処理が再開される
  7. 再開したオーケストレーターがアクティビティ関数を呼び、LIFFの入力を前提にした処理を実行

ポイントはオーケストレーターの「インスタンスID」で、これに LINE ユーザー ID を使うことで、Messaging API と LIFF という異なる API 間での連携を容易にしている点です。

同一ユーザーであれば、LINE API では同じユーザー ID 文字列が振られているため、Durable Functions との相性がとてもいいのです。

もちろん、Durable Functions を使わなくても Messaging API と LIFF の連携は実装はできるので、無理して Durable Functions を使う必要も実はないと思います。実運用するうえでは扱いがちょっと難しいですし、関数を再実行ありきで設計するのも難しいです(何度も再実行される関数なので、意図しない再実行による二重処理などを防ぐため、冪等性を意識した関数設計が大切です。オーケストレーターは必須で、アクティビティはできれば。)。

また、CI/CD などをする場合にも、オーケストレーション実行中のデプロイには気を配る必要があります(以下のドキュメントが参考になります)。

Durable Functions のためのゼロダウンタイムのデプロイ | Microsoft Docs

なお、Durable Functions は現時点で C# / JavaScript / F# のみの対応ですが、Python のサポートがもう少しで登場しそう…?な予感です(以下参照)。

GitHub - Azure/azure-functions-durable-python: Python library for using the Durable Functions bindings.

(追記)
でました。
Durable Functions で Python がサポート対象に | Azure の更新情報 | Microsoft Azure

ユーザーごとの応答モード

ユーザーごとに応答モードを切り替える部分にも、Durable Functions を使っています。2.0 で追加された新機能、エンティティ関数(Durable Entities)です。

f:id:himanago:20200530031429p:plain

ここでも、エンティティ ID に LINE ユーザー ID を用いることで応答モードを使いやすくしています(Messaging API からでも LIFF からでも応答モードを参照・更新できる)。

セッションでは触れられませんでしたが、エンティティ関数はただ状態を持つだけではなく、それをもとにした操作も簡単に定義できるところが魅力です。

.NET での持続エンティティに関する開発者ガイド - Azure Functions | Microsoft Docs

応答モードのような単純なフラグ値なら DB に書いても別にいいのでは?と思うかもしれませんが、その状態をもとにした操作を定義したい場合など、エンティティ関数で書くと驚くほどシンプルに済むことがあります。

先に述べたように注意点も多く使い過ぎは禁物だと思いますが、Durable Functions はコードのみでステートフルな処理を実現できる点が非常に強力です。

その他

運営・管理側のチャット画面や LIFF など、静的 Web サイトを使っている部分がありますが、Azure の場合は大きく 3 つの実現方法があります。

  • Blob Storage:静的サイトホスティング機能を利用
  • Web Apps:Node.js アプリとして配備
  • Static Web Apps:2020年5月の Build で発表された新サービス。プレビューですが GitHub から簡単に静的サイトがホストできる&Functions の統合も可能

Static Web Apps が非常に強力で、今後かなり使われるようになるのではと思っています。

Azure Static Web Apps – App service | Microsoft Azure

ちなみに、今回のシステムでは通常の Web App として動かしています。

まとめ

Azure Functionsを軸にイベント駆動でサービスが簡単につながる

Cosmos DB → Functions → SignalR を例にお話ししましたが、イベント駆動がサーバーレスの醍醐味。

簡単な設定をするだけでサービスがつながって画面表示に反映されるのは感動ものです。

ステートフルな Durable Functions は LINE API と相性抜群

Durable Functions を使った機能を 2 つ紹介しましたが、LINE ユーザー ID が全 LINE API で共通であるという強みがとても活かしやすく、相性がいいです。

この特徴をうまく使えば、Azure × LINE でもっとおもしろいサービスを作ることができるのでは…と思っています。

Azure サーバーレス × LINE API はいいぞ

Azure サーバーレスと LINE API の相性はよいですし、サーバーレスも LINE API もどちらも手軽に始められるよさがあります。

小規模・個人でも始めやすく、大規模にも対応できるものなので、いろいろな人に本当におすすめです。

Azure を使うと、今回紹介したような機能を用いることで、新しいことができる可能性がたくさんあり、特におすすめです。ぜひ、触ってみてほしいなと思います。

スライドはこちら

www.slideshare.net

感想など

コロナでばたばたしててすっかり忘れてましたが、そういえば LINE API Expert になってから初登壇。

オンライン登壇も初だったのですが、オンラインのみの登壇は難しいですね。。反応がないのでわかりにくく、アドリブもしにくい。

開発者/非開発者の比率もわかっておらず、話しはじめてから準備段階でオーディエンスが意識できなかったのを思い出し、話しながら戸惑ってしまいました。アドリブを効かせていいかんじに乗り切る…ができないなと思ったので、話すことはきちんと事前に用意したほうがいいなと思いました。

おまけ

なんと、今回紹介したシステムを、Hiro さん (@mofumofu_dance) がすぐさま真似して自動応答と手動応答の Bot を作ってくれました!しかも、Power Platform で!

Power Apps で LINE とやりとりしてる…感動。

Twitter にも書いたけど、当日中にすぐ形にしてもらえるなんて、本当にうれしすぎます。。。ありがとうございます。。

その他、セッション中もたくさん Twitter で反応いただきました。

クラウドが得意なLINE API Expert集合!サーバレス×LINEでアプリ開発してみたLT まとめ #linedc - Togetter

Azure と LINE API の魅力が少しでも伝わっていたらうれしいです。ありがとうございました!

Azure Functions の HTTP トリガーで、キーが間違っていないのに 401 エラーとなってしまうケース

前置き

Azure Functions の HTTP トリガーの関数では、API キーを関数 URL のクエリストリング(code)に入れて実行しますね(承認レベルが匿名の場合は不要)。

https://example.azurewebsites.net/api/Function1?code=xxxxxxxx

この API キーを間違えたり、渡さなかったりすると 401 エラーが出るわけですが、今回はこのキーを間違えていないはずなのに 401 エラーが出てしまうケースを紹介します。

401 エラーになるコード

以下のような関数のコードを見てください。

承認レベルは Function で、関数ごとに異なる キーを渡して実行します。

これをデプロイして Function2 を実行すると、なんと正しい API キーを渡しても 401 エラーになります

public static class SampleFunctions
{
    [FunctionName(nameof(Function1))]
    public static IActionResult Function1(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = "{name}")] HttpRequest req,
        string name)
    {
        return new OkObjectResult($"Function1: {name}");
    }

    [FunctionName(nameof(Function2))]
    public static IActionResult Function2(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req)
    {
        return new OkObjectResult("Function2: OK");
    }
}




さて、なぜだかわかりますか???




解説

ポイントは各関数メソッドの Route = の部分です。

Route でパラメータを渡せる

Route では、関数を呼び出す URL の 「https://example.azurewebsites.net/api/」以降のパスを指定することができます。

そして、{ } でパラメータも渡すことができ、このパラメータの値は同じ名前の別の引数としてメソッド内部で利用可能です。

したがって Function1 のこの部分、

public static IActionResult Function1(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = "{name}")] HttpRequest req,
        string name)

関数 URL としては

https://example.azurewebsites.net/api/JohnDoe?code=xxxxxxxx

のようになり、好きな文字列を渡して関数内で使うことができるようになります。

Route = null

ところが、Function2 では、Route = null となっています。

Routenull の場合は、URL のパスに関数名が入ります。つまり、こんな URL になります。

https://example.azurewebsites.net/api/Function2?code=XXXXXXXX



もうおわかりですね

そう、

  • Function1 の指定 Route = {name}
  • Function2 の指定 Route = null

が、URL として完全にかぶってしまっているのです。

そして、定義として先にくる Function1 が優先され、「https://example.azurewebsites.net/api/{何かしらの文字列}」という Function1 の URL に当てはまるhttps://example.azurewebsites.net/api/Function2」という Function2 の URL も、Function1 の呼び出しだと思われてしまうため、Function2 のキーを渡しても Function1 としては間違ったキーとなるので、401 エラーになってしまう、ということになります。


ちなみに

もし Function1Function2 の名前が逆だったら、つまり

  • Function1 の指定 Route = null
  • Function2 の指定 Route = {name}

という定義であれば、/Function1 という呼び出しは定義が先の Function1 と扱ってくれるため、どちらもエラーにならず 200 が返ってきます。

しかし、この場合でも Function2 の name に「Function1」という文字列を渡そうとしたら……?

恐ろしいですね。

そんなことが起きないように

ということで、Route を指定する場合は必ず固定のパスを含んだかたちで指定するようにしましょう。

ドキュメントのサンプルを見てもきっちりここは指定されているはずです。

[FunctionName(nameof(Function1))]
public static IActionResult Function1(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "Function1/{name}")] HttpRequest req,
    string name)
{
    return new OkObjectResult($"Function1: {name}");
}

固定のパスを指定しなくても、そしてたとえ URL がかぶっていたとしてもデプロイや起動時にエラーになってくれないので、注意が必要です。

認証プロキシ環境下で JavaScript 開発をする際の設定

以前 Qiita に書いた記事のアップデート版1

https://qiita.com/himarin269/items/adda54fc5dde3c908dba

毎回ググって設定しているので、備忘録としてまとめておきます。
※パスワード変更をしたらその都度設定しなおしが必要です(パスワードの定期変更強制やめてほしい)。

設定のしかた

Node.js

npmできるようにするための設定。以下のコマンドを実行します。

npm config set proxy http://<ユーザーID>:<パスワード>@<認証プロキシホスト名>:<ポート番号>
npm config set https-proxy http://<ユーザーID>:<パスワード>@<認証プロキシホスト名>:<ポート番号>

Git

Gitも使えないと困るのであわせて設定。

git config --global http.proxy http://<ユーザーID>:<パスワード>@<認証プロキシホスト名>:<ポート番号>
git config --global https.proxy http://<ユーザーID>:<パスワード>@<認証プロキシホスト名>:<ポート番号>

ほぼ同じですね。

ハマりポイント

認証プロキシのパスワードに%が含まれているとうまくいきません。 %の部分を%25にしましょう。

Fiddler 併用の場合

Fiddler を使って認証プロキシを通過できるようにして使う場合は、もっと楽です。

まず以下の記事を参考に Fiddler で認証プロキシを越えられるようにします。
認証プロキシ非対応アプリケーションを、認証プロキシ環境下で動かす方法 - Qiita

※上記記事では Fiddler の種類や ScriptEditor について触れられていますが、現在は以下のページから本体を落とすだけで済みます。
Download Fiddler Web Debugging Tool for Free by Telerik

認証プロキシを越える設定をした Fiddler を起動した状態なら、Fiddler を経由する通信が認証プロキシを越えるようになるので、npm も Git も以下のように Fiddler のポート(デフォルト8888)を指定してあげればOK。

npm config set proxy http://127.0.0.1:8888
npm config set https-proxy http://127.0.0.1:8888

git config --global http.proxy http://127.0.0.1:8888
git config --global https.proxy http://127.0.0.1:8888

パスワード変更した際も Fiddler の設定を1か所変えるだけで済むので、断然楽です。

設定の消し方

設定をしていると認証プロキシ環境外で動かないので、環境が変わったら設定を消しましょう2。 これで消せます。

npm config rm proxy
npm config rm https-proxy

git config --global --unset http.proxy
git config --global --unset https.proxy

消せるんですが、これを実行しても Git がうまくいかなかった…。

git config --global --listgit config --local --list もやって設定がないのを確認したのに、なぜか動きとしては古いプロキシ設定を見ちゃってる動きでした(なんでなのかは不明)。

しかたないので、

git config --global http.proxy ""
git config --global https.proxy ""

で空の設定をして上書きしました。

2020/5/11 追記

うまくいかなかったのは Windows 環境変数HTTP_PROXY, HTTPS_PROXY が設定されてたからでした。これを消したら、空文字設定なしでいけました。

まとめ

認証プロキシとパスワード定期変更強制は悪。

とはいえ、そういった環境の中でも快適に過ごすために、設定を Fiddler で一本化しておくと管理含めて楽ですね。


  1. Qiita はもう使わないことにしてるので、気が向いたときにアップデートしながらこっちに移行してくるようにします

  2. 在宅勤務になって認証プロキシ環境じゃなくなった