眠いしお腹すいたし(´・ω・`)

C#関連を主に書きます。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Durable Functionsを使用したサンプル -リクエスト結果をWebHookで返答するサービスへの対応-

今回はDurable Functionsを使用した少しだけ実践的なパターンを想定したサンプル実装を行ってみたいと思います。

前置き

前置き書いていたら長くなったので良い飛ばしてください。

みなさん、サービス間の連携にはどんな仕組みをよく使いますか?

一般的にはWeb APIが多いでしょうか?

今時的だとgPRCを使用したりする事もあるでしょう。

もしかしたら専用イーサネットワーク越しDATABASE LINKなどで RDBを使用して連携しているかも知れません。もしかしたらFTPサーバにcsvファイルをアップロードして連携するなんて事も2018年の今でも現在でも存在しているかも知れません。もしかしたらcsvファイルがshift-jisで書かれていたりeuc-jpで書かれていたり、どこかの企業が拡張した文字コードで書かれていたりするかも知れません。もしかしたらH○LFTなどの連携サービスを使用しているかも知れません。

Web APIでのサービス間連携は同期的であればとてもシンプルです。

1セッション内でリクエストとレスポンスが行われます。

しかし1処理に時間がかかる場合にはWeb APIによる連携は最善とは言えないかも知れません。

リクエストを受ける側は同期的に処理を実行するという制約がかけられる事によって実装難易度が上がってしまったりスケールアウトが難しくなってしまったりするかも知れません。

ロングランニングな処理に対する解決方法としては非同期なAPIを設計する場合があります。

リクエストを受け付けた時に処理IDを発行して返答し、リクエストを行なった側は処理IDを使用して結果取得APIをコールする事で結果を取得する形式です。

Durable FunctionsのOrchestratorは正にその振る舞いをします。

しかしこの形式はリクエストを送る側が結果取得APIをポーリングしなければならないという問題点があります。

そこで最終的に辿り着くパターンとしては処理が終わった時にリクエストを送信した側に何らかの方法で結果を通知する方法です。

良くある方式としてはリクエストを送信する側も結果通知リクエストを受け付けるエンドポイントを用意しておく所謂WebHook的な連携です。

今回は1リクエスト1フックなAPIへのリクエストをDurable Functionsの外部イベントを使用して処理するサンプルを作成してみたいと思います。

成果物

github.com

解説

docs.microsoft.com

人による操作または他の外部トリガーを処理するときに便利です

と書かれている通り外部トリガーというのが今回でいうWebHookであったりします。

まぁWebHookじゃなくてもFTPサーバに結果CSVをおいて(ry

WebHookOrchestrator

それではリクエストを行う側のOrchestrationを見てみましょう。

gist.github.com

まずはHookApiRequestActivityをコールしてAPIリクエストを行なって処理IDの取得をおこなっています。

gist.github.com

処理は単純です。

次にSaveRequestKeyActivityをコールして取得した処理IDを自身のInstanceIdとセットにしてStorageTableの保存しています。

gist.github.com

次にWaitForExternalEventを使用して外部から結果を発火されるのを待ちます。

最後にOrchestrateの結果として外部からの結果を返答します。

HookRecieve

次にサービスからWebHookを受け付けるFunctionを見てみます。

gist.github.com

WebHookによってリクエストされた情報の中からInstanceIdを取得してStorageTableに問い合わせを行い、リクエストを行なったOrchestratorのInstanceIdを取得。

そのInstanceIdでRaiseEventAsyncをコールします。

RaiseEventAsyncをコールするとWaitForExternalEventで待機していたOrchestratorが動き出してリクエストを行なったOrchestratorの処理が終了します。

終わりに

今回のサンプルであればリクエストを行う部分とWebHookを受ける部分を別々に処理したとしても問題がありません。

しかしAPIリクエストが処理の一部に過ぎず、その結果を使用してさらに複雑に処理を行なっていくようなパターンでは今回のようにDurable FunctionsのOrchestrationで実装していく方が楽になるかとおもいます。

また今回のパターンではWaitForExternalEventで待機した状態でWebHookが行われなかった場合、処理が中途半端になってしまします。

その場合のデザインパターンとしてScheduler Agent Supervisorパターンを用いるのも良いかも知れません。

その辺のサンプルはまた次回に。

それでは👋

Durable Functionsで学ぶクラウドデザインパターン -Pipes and Filters Pattern-

前回

tamafuyou.hatenablog.com

に引き続き、今回はPipes and Filters (パイプとフィルターのパターン)をDurable Functionsで実装してみます。

Durable Functionsのご紹介は

Durable Functions - Google 検索

この辺でお願いします。

Pipes and Filters

docs.microsoft.com

クラウドデザインパターンとして紹介されているのですが昔からある古典デザインパターンの一つですね。

著しく雑に表現すると、

モノシリックな大きいロジックは途中にボトルネックなロジックが含まれていても大きいロジックの単位でしか実行されないからリソースの無駄になりやすいよね。だからタスク単位に分割しよう。そうすれば再利用性も向上するし小さい単位での並列実行が可能になるからリソースの無駄を防げるかもね。しかも要件によってタスクの順序が入れ替わるようなケースでも対応できちゃうよね。しかもこのパターンにするとタスク毎に別々のコンピュータリソースで実行が可能だから障害に対する回復性も向上するよね。

って感じです。

C#のプログラム上でTask等の並列処理を使えば一つのコンピュータ上での並列実行は可能なんですけどクラウドで実行するんだったらもっとスケールを大きく行こう!! って事なんでしょうか。

今回の成果物

github.com

解説

とは言うものの、全然難しいことはしていません。 まずはActivityを1つのタスクと考えて実装します。

[FunctionName(nameof(TaskAActivity))]
        public static async Task<List<string>> TaskAActivity(
            [ActivityTrigger] List<string> results,
            TraceWriter logger
        )
        {
            logger.Info("Task A start");
            results.Add("TaskA Complete");
            return results;
        }

このようなアクティビティをA~Fまで作成しました。

次にタスクを繋ぎ合わて作成するロジックをOrchestratorと考えて実装します。

        [FunctionName(nameof(DataSource1Orchestrator))]
        public static async Task<List<string>> DataSource1Orchestrator(
            [OrchestrationTrigger] DurableOrchestrationContext context
            )
        {
            var input = context.GetInput<string>();
            var a = await context.CallActivityAsync<List<string>>(nameof(TaskAActivity), new List<string>() {input});
            var b = await context.CallActivityAsync<List<string>>(nameof(TaskBActivity), a);
            var c = await context.CallActivityAsync<List<string>>(nameof(TaskCActivity), b);
            var d = await context.CallActivityAsync<List<string>>(nameof(TaskDActivity), c);
            return d;
        }

        [FunctionName(nameof(DataSource2Orchestrator))]
        public static async Task<List<string>> DataSource2Orchestrator(
            [OrchestrationTrigger] DurableOrchestrationContext context
            )
        {
            var input = context.GetInput<string>();
            var a = await context.CallActivityAsync<List<string>>(nameof(TaskAActivity), new List<string>() { input });
            var b = await context.CallActivityAsync<List<string>>(nameof(TaskBActivity), a);
            var e = await context.CallActivityAsync<List<string>>(nameof(TaskEActivity), b);
            var f = await context.CallActivityAsync<List<string>>(nameof(TaskFActivity), e);
            return f;
        }

あとはHttpTriggerによる呼び出しを作成すれば終わりです。

呼び出し結果として

DataSource1であれば

{
    "runtimeStatus": "Completed",
    "input": "test",
    "output": [
        "test",
        "TaskA Complete",
        "TaskB Complete",
        "TaskC Complete",
        "TaskD Complete"
    ],
    "createdTime": "2018-01-22T14:54:31Z",
    "lastUpdatedTime": "2018-01-22T14:54:39Z"
}

DataSource2であれば

{
    "runtimeStatus": "Completed",
    "input": "kokoro",
    "output": [
        "kokoro",
        "TaskA Complete",
        "TaskB Complete",
        "TaskE Complete",
        "TaskF Complete"
    ],
    "createdTime": "2018-01-22T14:16:27Z",
    "lastUpdatedTime": "2018-01-22T14:16:31Z"
}

になります。

Pipes and Filtersのパターンって、そのままDurableFunctionsなんですよね。

特に難しいことはないです。

Orchestratorのコードって一見すると、そのコンピュータ上で非同期処理をしている様に見えるのですが実際にどのコンピュータで実行されているかは分からないんです。

例えばFunctionsをAppServiceプランのインスタンス x 10みたいな環境で実行したとすれば、その10台の中のどこかでActivityが実行される感じです。

コードの見た目以上にスケールの変動に容易に対応しちゃってるんですよね。

あと、例えば

Orchestratorのコードを

        [FunctionName(nameof(DataSource2Orchestrator))]
        public static async Task<List<string>> DataSource2Orchestrator(
            [OrchestrationTrigger] DurableOrchestrationContext context
            )
        {
            var input = context.GetInput<string>();
            var a = await context.CallActivityWithRetryAsync<List<string>>(nameof(TaskAActivity),new RetryOptions(TimeSpan.FromSeconds(30), 10),  new List<string>() { input });
            var b = await context.CallActivityWithRetryAsync<List<string>>(nameof(TaskBActivity), new RetryOptions(TimeSpan.FromSeconds(30), 10), a);
            var e = await context.CallActivityWithRetryAsync<List<string>>(nameof(TaskEActivity), new RetryOptions(TimeSpan.FromSeconds(30), 10), b);
            var f = await context.CallActivityWithRetryAsync<List<string>>(nameof(TaskFActivity), new RetryOptions(TimeSpan.FromSeconds(30), 10), e);
            return f;
        }

こう言う風にCallActivityWithRetryAsyncに置き換えるだけで簡単にAcitvity単位のRetryを設定することができます。

このリトライの容易さがDurable Functionsの良い点でもあり、どのように活かすかの腕の見せどころな様に思います。

要するにActivityの処理にてどの様な時にRetryを行ってどの様な時にRetryしないかを設計して実装すれば、あとはOrchestratorで如何様にもできますぜって感じなのです。

いやぁ、Durable Functionsって本当にいいものですねぇ

それではまた次回をお楽しみに、サヨナラ、サヨナラ、サヨナラ!

Durable Functionsで学ぶクラウドデザインパターン -Compensating Transaction Pattern-

先日、牛尾さん、Kanioさん主催の

www.meetup.com

に参加してきました。

Durable functionsの紹介だけではなくモブプログラミングもあり私が英語が駄目すぎることを除いてすごく楽しいセッションでした。

元々、非常に興味があるフレームワークだったのですが触ってはいなかったので、しばらくDurable functionsで遊んでみようという気になっている次第です。

今回はDurable Functionsを使ってマイクロサービスアーキテクチャなどでよく使われるCompensation Transaction Pattern(補正トランザクションパターン)を実装することで、補正トランザクションパターンを勉強してみようと思います。

## 今回の成果物

github.com

環境

Visual studio 2017 - 15.5.2

Azure Functions Cli - 1.0.7

Azure Storage Emulator 5.2

Nuget Package

https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.DurableTask/1.1.0-beta2

Compensation Transaction Pattern

この資料が一番わかりやすいと思います。

www.slideshare.net

著しく雑に表現すると

全然別々のサービス間でもトランザクション処理を行わなければならないケースはあるのだけど全てのサービスを通して高レベルなロールバック処理は難しいから、どこかで失敗したら各サービスで元に戻す上書きを行おう。

という感じです。

解説

OrchestratorとActivity

Durable Funcitonsでは処理の単位としてOrchestratorとActivityが存在します。

OrchestratorはOrchestration、つまりアーキテクチャを実装します。

Activityは処理を実装します。

なのでOrchestraorでI/Oを伴う処理をすることはアンチパターンです。

例えばRestAPIをコールして結果を取得する処理を行おうとした時には

Activity関数で実際に通信する部分を実装してOrchestratorでActivityを呼び出して結果を取得する処理を実装するようなイメージです。

ここを理解した上でコードの解説行います。

StartNew

[FunctionName("HttpStartSingle")]
        public static async Task<HttpResponseMessage> RunSingle(
            [HttpTrigger(AuthorizationLevel.Function, methods: "post",
                Route = "orchestrators/HttpStartSingle/{requestId}/{point}")] HttpRequestMessage req,
            [OrchestrationClient] DurableOrchestrationClient starter,
            string requestId,
            int point,
            TraceWriter log)
        {
            var instanceId = await starter.StartNewAsync("CompensatingTransactOrchestrator",
                new PointRequest() {RequestId = requestId, Point = point});

            return starter.CreateCheckStatusResponse(req, instanceId);
        }

この関数ではHttpRequestを受けた場合に「CompensatingTransactOrchestrator」というOrchestratorを起動させます。

そしてリクエストの結果として、そのオーケストレータの「インスタンスID」に伴う情報を返答しています。

Durable Functionsではオーケストレーションを開始すると、そのオーケストレーションのIDとしてInstanceIdが発行されます。

そのInstanceIdを使用してオーケストレーションの結果を追跡することが可能となります。

このリクエストの結果はこのようになります。

{
    "id": "f0d9f24a2cc94a3e8394e6ae00c30a59",
    "statusQueryGetUri": "http://localhost:7071/admin/extensions/DurableTaskExtension/instances/f0d9f24a2cc94a3e8394e6ae00c30a59?taskHub=DurableFunctionsHub&connection=Storage&code=e5aGhnpascgIhDdfTvQozANdKlML3FlayXzx1Pr6NqRyb7gSs2aIiQ==",
    "sendEventPostUri": "http://localhost:7071/admin/extensions/DurableTaskExtension/instances/f0d9f24a2cc94a3e8394e6ae00c30a59/raiseEvent/{eventName}?taskHub=DurableFunctionsHub&connection=Storage&code=e5aGhnpascgIhDdfTvQozANdKlML3FlayXzx1Pr6NqRyb7gSs2aIiQ==",
    "terminatePostUri": "http://localhost:7071/admin/extensions/DurableTaskExtension/instances/f0d9f24a2cc94a3e8394e6ae00c30a59/terminate?reason={text}&taskHub=DurableFunctionsHub&connection=Storage&code=e5aGhnpascgIhDdfTvQozANdKlML3FlayXzx1Pr6NqRyb7gSs2aIiQ=="
}

このJsonに書かれているUrlにアクセスするとオーケストレーションの結果を確認できたりオーケストレーションを途中で止めたりすることができます。

ステータス取得の結果はこんな感じになります。

{
    "runtimeStatus": "Completed",
    "input": {
        "$type": "CompensatingTransactionOrchestration.Functions+PointRequest, CompensatingTransactionOrchestration",
        "RequestId": "h",
        "Point": 100
    },
    "output": "repository1 Failure",
    "createdTime": "2018-01-21T04:06:13Z",
    "lastUpdatedTime": "2018-01-21T04:06:22Z"
}

Orchestrator and Activity

Compensating Transaction PatternなOrchestratorを見てみます。

        [FunctionName("CompensatingTransactOrchestrator")]
        public static async Task<string> CompensatingTransactOrchestrator(
            [OrchestrationTrigger] DurableOrchestrationContext context
        )
        {
            var pointRequest = context.GetInput<PointRequest>();
            var compensatingStack = new Stack<ValueTuple<string, RepositoryResult>>();

            try
            {


                var result1 = await context.CallActivityAsync<RepositoryResult>(nameof(Repository1Activity), pointRequest);
                if (result1.Succeed)
                {
                    compensatingStack.Push((nameof(Repository1CompensatingActivity), result1));
                }
                else
                {
                    return "repository1 Failure";
                }

                var result2 = await context.CallActivityAsync<RepositoryResult>(nameof(Repository2Activity), pointRequest);
                if (result2.Succeed)
                {
                    compensatingStack.Push((nameof(Repository2CompensatingActivity), result2));
                }
                else
                {
                    return "repository2 Failure";
                }

                var result3 = await context.CallActivityAsync<RepositoryResult>(nameof(Repository3Activity), pointRequest);
                if (result3.Succeed)
                {
                    compensatingStack.Push((nameof(Repository3CompensatingActivity), result3));
                }
                else
                {
                    return "repository3 Failure";
                }

                compensatingStack.Clear();
            }
            finally
            {
                while (compensatingStack.Any())
                {
                    var item = compensatingStack.Pop();
                    await context.CallActivityWithRetryAsync(item.Item1, GetRetryOprion(), item.Item2);
                }
            }

            return "Success";
        }

このFunctionはOrchestrationTriggerを使用しているのでStartNewされた時、もしくはCallSubOrchestrationされた時に実行されます。

読んでわかる通りCompensation Transactin Patternそのままです。

Repository1~3に対する処理を行うために各ActivityをCallしていきます。

しかし途中で失敗した場合には、実行が完了してしまっているRepositoryの補正用ActivityをCallして補正を行なっています。

各アクティビティの処理はこんな感じです。

        [FunctionName("Repository1Activity")]
        public static async Task<RepositoryResult> Repository1Activity(
            [ActivityTrigger] PointRequest context
            , [Table("table1")] CloudTable table
            )
        {
            var query =
                new TableQuery<PointTable>().Where(TableQuery.GenerateFilterCondition(nameof(PointTable.PartitionKey),
                    QueryComparisons.Equal, context.RequestId));

            if (!table.ExecuteQuery(query).Any())
            {
                var processId = Guid.NewGuid().ToString();
                var insertOperation =
                    TableOperation.Insert(new PointTable()
                    {
                        PartitionKey = context.RequestId,
                        RowKey = processId,
                        Point = context.Point
                    });

                var result = await table.ExecuteAsync(insertOperation);
                return new RepositoryResult()
                {
                    ProcessId = processId,
                    Request = context,
                    Succeed = true,
                };
            }

            return new RepositoryResult()
            {
                Request = context,
                Succeed = false
            };
        }

        [FunctionName("Repository1CompensatingActivity")]
        public static async Task Repository1CompensatingActivity(
            [ActivityTrigger] RepositoryResult repositoryResult
            , [Table("table1")] CloudTable table
            )
        {
            var query =
                    new TableQuery<PointTable>()
                        .Where(TableQuery.GenerateFilterCondition(nameof(PointTable.PartitionKey),
                            QueryComparisons.Equal,
                            repositoryResult.Request.RequestId))
                        .Where(TableQuery.GenerateFilterCondition(nameof(PointTable.RowKey), QueryComparisons.Equal,
                            repositoryResult.ProcessId))
                ;

            var targets = table.ExecuteQuery(query).ToArray();

            if (targets.Any())
            {
                foreach (var target in targets)
                {
                    var deleteOperation = TableOperation.Delete(target);
                    await table.ExecuteAsync(deleteOperation);
                }
            }
        }

ストレージテーブルに処理を書いて補正では書いた処理を消している感じです。

大事なこと

このOrchestratorと各Activityにブレークポイントをしかけて実行すると、とても面白い動作をしていることに気がつきます。

OrchestrationTriggerによって実行されているFunctionが何回も呼び出されるのです。

1回しかリクエストを行なっていないのに、です。

しかし各Activityは1度しか実行されません。

これがDurable FunctionsがDurableであるとされる部分です。

Orchestratorが何らかの理由で止まってしまったとしても再実行され再実行時にはすでに処理を行なっているCallActivityに関してはActivityの実際の呼び出しは行われずに結果だけ最初の実行と同じものが返されます。

常にOrchestratorFunctionは再実行されることが前提の設計となっているのです。

ここにOrchestratorでI/O処理を行なってはいけない理由にあります。

Orchestratorを実装する時にはI/OをOrchestration/Activityに限定しなければ、外部の情報の変化により結果が変化してしまうためハチャメチャな処理になってしまうのです。

終わりに

いかがでしたでしょうか?

Durable Functions 大変面白いです。

もちろん「Durable Funcitonsを使用することによるデメリットは無い」、なんてことは言えないのですが、それにしても大変面白いフレームワークであると言えると思います。

ヾ(´・∀・[.:゚+またね.: :]

おまけ

今回はHttpリクエストに対して非同期にオーケストレーションが実行されてますが

        [FunctionName("HttpStartWait")]
        public static async Task<HttpResponseMessage> RunWait(
            [HttpTrigger(AuthorizationLevel.Function, methods: "post",
                Route = "orchestrators/HttpStartWait/{requestId}/{point}")] HttpRequestMessage req,
            [OrchestrationClient] DurableOrchestrationClient starter,
            string requestId,
            int point,
            TraceWriter log)
        {
            var instanceId = await starter.StartNewAsync("CompensatingTransactOrchestrator",
                new PointRequest() { RequestId = requestId, Point = point });

            while (true)
            {
                var status = await starter.GetStatusAsync(instanceId);
                if (status?.RuntimeStatus > OrchestrationRuntimeStatus.Running)
                {
                    return new HttpResponseMessage(HttpStatusCode.OK)
                    {
                        Content = new StringContent(JsonConvert.SerializeObject(status))
                    };
                }
                await Task.Delay(TimeSpan.FromSeconds(2));
            }
        }

こんな感じにすれば一応リクエストに対して同期的に返答することも可能かです。

DroidKaigi2017アプリ for Xamarin.AndroidをXamarin.iOSに少し移植してみた。

この記事は Xamarin その1 Advent Calendar 2017 - Qiita の20日目の記事です。

はじめに

2017年は色々ありました。

転職してちょっとだけ都会に引っ越してみたりAzureを本格的に使い始めてみたり・・・それくらいかな?

今年も平常運転でクリスマスは1人なのでCivilization6でもやってるとお思います。

前提

今回の記事は

tamafuyou.hatenablog.com

この記事の続きになっているのですが

実は私の中ではこの記事のさらに前提があります。

Xamarin.Forms とコンソールアプリでモデル層を共通化してみた // Speaker Deck

今年3月に生まれて始めて登壇というものをさせていただいたのですが

この内容の実証実験と言いますか

この登壇資料を作っていた時に考えていた事を実装で組み込んでいます。

やってみた事

少し前にDroidKaigi2017アプリをXamarin.Androidに移植してみる、という挑戦を行いました。

これは単純にその当時で割と新し目の設計であるDroidKaigiアプリを学ぶ事でAndroidネイティブの知識を得ると同時にXamarin.Androidをやってみる事でXamarin Nativeというのはどういう特性があって何が良くて何が厳しいのか、自分の視点で考えてみようという目的がありました。

その実装を行うなかでどうせXamrinするなら所謂MVなんちゃらのM(Model)を3月の登壇内容を前提として設計を行ってみてXamarin.iOS版を作ってみるという方向に動きました。

そして今回、Xamarin.Androidとモデル層を共通化してXamarin.iOSを実装して見た形になります。

今回の取り組みに関する知識の前提

  • Androidネイティブ少しわかる
  • iOSネイティブはXamarin.Formsで使う範囲内くらいわかる
  • Swiftはちょっとだけわかる
  • Objective-Cは見たくない
  • C#/.NET Frameworkは少々わかる
  • Xamarin.Forms(Android/iOS)はそこそこ知ってる方だと思ってた時期がありました

今回の成果物

github.com

https://sleepyandhungry1984.tumblr.com/post/168746685717/droidkaigi2017アプリfor-xamarin-ios
sleepyandhungry1984.tumblr.com

謝罪

まだ余裕あるかなぁとかグダグダやってたら全然時間なくなってしまって執筆の時点で実装が不十分です。

年内はリファクタなどを行うかと思います。

解説

共通部分

DroidKaigi2017forXamarin/src/DroidKaigi2017.Service at master · yuka1984/DroidKaigi2017forXamarin · GitHub

モデル層の共有部分はSharedプロジェクトにて共有化を行なっています。

私はSharedプロジェクトをよく用います。

XamarinによるAndroid/iOS共通開発では..NET StandardLibrary(PCL)での共有プロジェクトにする意味はないだろうと考えています。 逆にStdLibとXamarin.Android/iOS間でのnugetパッケージでの不都合などが起きやすいと思います。

Serviceクラスとリポジトリクラスに分かれていて所謂クリーンアーキテクチャのような構成になっているかと思います。

Serviceクラスの特徴として流れの1方向性を意識した作りにあります。

DroidKaigi2017forXamarin/SessionServices.cs at master · yuka1984/DroidKaigi2017forXamarin · GitHub

公開プロパティはIObservableでありかつステートを持つことが可能であるReadOnlyReactivePropertyとReadOnlyReactiveCollectionを使用します。

そしてサービスに対するリクエストは戻り値のないasync/awaitでの実装でありデータの流れの1方向性となっています。

なぜ1方向性にするか、これは双方向性の繋がりの場合だと仕様変更・複数画面間での共有にて悲惨な問題が起きやすいという経験への対策になります。

またこのパターンの場合、View間の同期問題への対策を行うこともできます。

今回作成した動画の後半を見てもらえるとわかるのですが異なるページであってもデータの変更が伝搬するためView側で意識せずに状態の動機を行うことができます。

ただ、この実装にも問題はあり一つのサービスを多くの画面が共有するような状況で、かつ流れるデータが複雑な時にモデル層の変更のリスクが高くなってります。

今後はこの課題に対する解決策を考えていけたらと思っています。

Repositoryはただのデータの保存庫であり実装としてはCRUD的な作りとなっています。

Azure Mobile Appsのデータテーブルを使用しています。

これらのクラス類は前回のXamarin.Android編にて作成したままで変更はほぼありませんでした。(バグを直しただけ)

iOS部分

私はiOSネイティブ開発への理解度や知識が高くないため、今回作成したものはかなりメチャクチャではないかと思っています。 次こそはちゃんとiOS開発を学びたいと思います。

今回作成するにあたってViewModelを作った方が良いのか?とか考えていたのですが、単純に時間がなかったためViewControllerに直接サービスクラスを持って、そのままコードで操作するような作りとなりました。

storyboardを使いこなせる知識もなかったため、コードでゴリゴリ書いています。

感覚的にはXAMLです( ̄ー+ ̄) ニヤリ…

DroidKaigi2017forXamarin/FirstViewController.cs at master · yuka1984/DroidKaigi2017forXamarin · GitHub

こちらはセッション一覧画面にのコードになるのですが、今回はUICollectionViewを使い縦横スクロールのLayoutは

xyk.hatenablog.com

こちらを参考(C#移植)に作成しました。

DroidKaigi2017アプリのように13タイルや21タイルを組み込もうとするともう少し手を入れなければならないのですが、時間と技量と才能が足りなかったため今回は全て1*1タイルでの実装となりました。

この辺は特に難しいことはなくISessionServiceのSessionsプロパティを用いてCollectionを展開しSessionsの変更がおこなわれた場合UICollectionViewをReloadDataしている形になります。

またIMySessionServiceにてMySession情報が更新された時にもReloadDataをしています。

セルがロングタップされた場合にはIMySessionServiceにMySession情報の変更を依頼しています。

全ては1方向になっているので基本的には画面からのイベントはServiceへの依頼へと射影していきます。

画面は異なりますがMySession画面もほぼ同様の作りとなります。

MySession画面はUITableViewをベースにしています。

詳細画面に関しては

DroidKaigi2017forXamarin/DetailViewController.cs at master · yuka1984/DroidKaigi2017forXamarin · GitHub

これも特に難しくなくレイアウトしてセッション情報が流れてきたら更新する、という作りになっています。

おわりに

Xamarin.iOSを触ってみてやっぱりC#いいなぁという気持ちになりました。

もちろんSwiftやKotlinのような新しい言語の方が機能的に優れているのは間違い無いのですが

慣れた言語でバックエンドもフロントエンドも全部作れるって楽なんですよねぇ(´д`)〜з

それではまた〜ヾ(´д`)ノシ

Azure FunctionsでCircuit Breaker付きなService Bus トリガーを作ってみる

あいさつ

もうすぐ年末ですがAdventカレンダーの進捗はいかがでしょうか?

私は全くです(ノ゚Д゚)八(゚Д゚ )ノイエーイ

そしてここでお知らせがございます。

クリスマス中止のお知らせ

2017年12月24,25日に開催予定のクリスマスは、隣国の情勢の悪化に伴い中止となりました。

本決定により、クリスマスイブも中止になります。

中止、ならびに本告知が遅れたことにつきまして、楽しみにしておられた方々、及び関係者各位には謹んでお詫び申し上げます。

はじめに

みなさん、Azure Functionsは使ってますか?

ビバ!サーバーレス!! 素晴らしきかなサーバーレス!! 

サーバレスで作成すマイクロサービスアーキテクチャでAIを利用すればOK(∩´∀`)∩

なんてことは全く思っているわけではないんですがFunctions自体は好んで使ったりしてます。

Functionsではキュートリガーであったりブロブトリガーなどはよく使うかと思います。

キュートリガー系の設計を設計して行った時に以下のような課題にぶつかったりする事があります。

  • トリガーで動作するFunctionの中で外部サービスへアクセスを行なっている。
  • 外部サービスが障害により停止した時にもFunctionは実行されてしまう。
  • FunctionはExceptionが発生するのでキューはいずれポインズンキュー or 配信不能キューに移動する
  • 失敗する事が確定しているのに無駄にリソースが消費されてしまう

この様なケースに対応するクラウドなサービスパターンはサーキットブレーカーパターンでしょう。

github.com

www.slideshare.net

Azure Functionsでサーキットブレークを行おうとすると、おそらく二つの方法が思いつきます。

  1. Application Insights等でFunctionの実行状況を監視してエラーが閾値を超えた場合にFunction App自体を停止する。
  2. そういう風な仕組みを持ったトリガーを自作する。

今回は前回の記事

tamafuyou.hatenablog.com

を応用して、サーキットブレークするトリガーの作成してみたいと思います。

なお、今回サーキットブレークを厳密に実装しているわけではなくなんちゃってサーキットブレーカーである事をはじめに宣言しておきます。

本題

今回の成果物

github.com

SErviceBus・・・・・

解説

実装検討

まず最初に検討した実装方法は「SIngletonAttribute的な感覚でCircutiBreakerパターンを行うことはできないだろうか」ということだったのですが、SingletonAttribute自体WebJobsのコアに食い込んで実装されているため同様な実装を行うためにはかなりのコストがかかってメンドくさいのでボツ。

じゃあトリガー系でよく使われるのはAzure Storage QueueのトリガーとServiceBusのキュートリガーなので「継承していい感じにできないかな」と考えたんですがほとんどの実装がinternalとなっているため無理(・ω・ノ)ノ!

どちらの方が再実装が楽かなぁとコードを見てみた結果、ServiceBusの方が実装シンプルだったので、今回はServiceBusのキュートリガーを作成してみることにしました。

ServiceBusTrigger

azure-webjobs-sdk/src/Microsoft.Azure.WebJobs.ServiceBus at dev · Azure/azure-webjobs-sdk · GitHub

この辺がMicrosoftで作られているServiceBusTriggerのコードです。

現状のdevブランチはVersion3.0系の開発が行われています。 3.0系は.net standardに対応するためにServiceBusTriggerで使われているServiceBusClientのパッケージが

www.nuget.org

となっています。

version2.0系は

www.nuget.org

こちらのパッケージを使用しています。

で、今回どっちを使おうかなぁって考えたのですが、Microsoft.Azure.ServiceBusの方は今まで使った事がなかったので、今回はこちらで実装してみることにしました。

解説

ServiceBusTriggerの実装を見てみるとやっていることは実にシンプルでMessageReceiverでメッセージ受信を行なってメッセージを受信したらFunctionを実行して終わったらComplete or Abandonする。という事をしています。

ちなみにStorageQueueトリガーの場合にはループ処理にてキューの取得を行なってキューが存在していたらFunctionを実行する様な処理をしています。

Triggerの作り方とかは前回の記事で解説していますので今回は実装のキモであるIListenerの実装部分のみ解説します。

SErviceBusTriggerWithCircuitBreaker/ServiceBusQueueListener.cs at master · yuka1984/SErviceBusTriggerWithCircuitBreaker · GitHub

とは言ったものの、解説するほど大したことはしていなくて

Functionの実行結果が成功でなかった場合、ErrorCountをインクリメントして閾値をオーバーした場合に一旦受信を停止します。

停止した際にオープン状態時間経過後に再度受信を開始します。

エラーが一定時間経過間に発生しなくなった場合にはエラー状態をリセットして正常に復帰します。

サーキットブレーカーと書いていますがハーフオープンなどもなく違う感じなのですがブレーカーが落ちるっていうところでサーキットブレークとさせてください(´・ω・`)

動作画面は非常に地味なため載せません。(´・ω・)(´-ω-)) ペコリなさい

こんな感じで自作したトリガーを用いてデザインパターンを使って課題を解決するっていう事ができます。

いやぁAzure Functionsって本当に良いものですね、それではヾ(´д`)ノシ

Azure Functionsのカスタムトリガーの作り方を調べてみた

ちょっと前にカスタムの出力バインディングの作り方

tamafuyou.hatenablog.com

を調べたのですが、今回は独自なカスタムのトリガーの作成方法を調べて見ました。

今回の成果物はこちら

github.com

今回参考にしたサイトはこちら

GitHub - Azure/azure-webjobs-sdk: Azure WebJobs SDK

それでは早速

今回はSlackのReal Time Messaging API

api.slack.com

を用いてSlackでメッセージを書き込んだらファンクションが実行されるトリガーの作成を行いました。

Slackでこういう事をする場合にはOutgoing WebHooksを使うのが普通ですが今回はトリガーの学習なのでRTMを使ってみました。

RTMはWebSocketを使用したリアルタイムAPIなのですが、これまで実装するとなるとちょっとめんどくさいので今回はSlackConnectorというライブラリを使用しました。

www.nuget.org

この辺を見てもらえればわかるかと思います。

それでは解説ヾ(`・ω・´)ノ

作るべきクラス

作るべきクラスは以下

  • IExtensionConfigProviderの実装クラス
  • ITriggerBindingProviderの実装クラス
  • ITriggerBindingの実装クラス
  • IListenerの実装クラス
  • パラメータ用のArributeクラス

になります。

Attributeクラス

実装はSlackMessageTriggerAttributeクラスです。

SampleProjects/SlackMessageTriggerAttribute.cs at master · yuka1984/SampleProjects · GitHub

本当にシンプルで十分です。パラメータに付けるためのAttributeでプロパティにAccessTokenを持たせています。このAccessTokenをキーに環境変数から値を引っ張って来てRTMへの接続に使用します。

IExtensionConfigProviderの実装クラス

実装はSlackMessageExtentionConfigクラスです。

SampleProjects/SlackMessageExtentionConfig.cs at master · yuka1984/SampleProjects · GitHub

このインターフェースの実装クラスのInitialize関数はFuncgionsのホストがスタートした時に呼び出されます。この関数内でITriggerBindingProviderを登録してあげる事でホストがファクションのパラメータバインディングを行う時に登録したProviderのTryCreateAsyncが呼び出される様になります。

ITriggerBindingProviderの実装クラス

実装クラスはSlackMessageTriggerAttributeBindingProviderです。

SampleProjects/SlackMessageTriggerAttributeBindingProvider.cs at master · yuka1984/SampleProjects · GitHub

先ほど説明した様にホストが関数を捜査してパラメータをバインディングする際にTryCreateAsync関数が呼び出されます。

gist.github.com

ですのでパラメータのAttributeが正しければITriggerBindingのインタンスを返してあげて関係ないAttributeであればnullを返してあげればOKです。

ITriggerBindingの実装クラス

実装はSlackMessageTriggerBindingクラスです。

SampleProjects/SlackMessageTriggerBinding.cs at master · yuka1984/SampleProjects · GitHub

このクラスの主な仕事はリスナークラスのインタンス生成とリスナーによってファンクションの実行が指示された際にリスナーからのデータを パラメータにバインドする事です。

gist.github.com

CreateListnerAsync関数は単純です。IListnerの実装を返してあげれば良いです。

gist.github.com

IListenerの実装クラス

実装クラスはSlackMessageListenerです。

SampleProjects/SlackMessageListener.cs at master · yuka1984/SampleProjects · GitHub

IListnerはStartAsync, StopAsync, Cancel, Disposeを実装する必要があります。

今回は単純に何も考えずStartAsyncでRTMへのコネクションを行いStopAsyncで切断を行なっています。

そして引数で受け取ったITriggeredFunctionExecuterのTryExecuteAsync関数をSlackメッセージを受信した際に実行しています。

TryExexuteAsync関数を実行するとTriggerBindingのBindAsync関数を経由してFunctionが実行される流れになります。

例えばServiceBusTriggerなどはTryExecuteAsync後に実行結果に応じて関数の実行が失敗していたらAbandonAsync、成功していたらCompleteAsyncを実行しています。なので関数実行後にキューが完了していたりするわけです。

実際にサンプルを実行すると

f:id:tamafuyou:20171029153028p:plain

f:id:tamafuyou:20171029153047p:plain

こんな感じで実行されたりします。

なお、今回の実装はあくまでもサンプルですので色々と抜けています。

インスタンスがスケールすると1つのメッセージで複数回ファンクションが実行されてしまいます。

ですのでこの感じの実装を使用する際にはSIngletonAttributeをリスナーモードで使用するか、それと同等の実装を行う必要があります。

Azure Functionsでトリガーが作れると夢が広がりますね

(ノ゚Д゚)八(゚Д゚ )ノイエーイ

ではでは

Azure Functions の SingletonAttributeとModeプロパティ

今日職場でSingletonAttributeのModeプロパティに関する話が出てたので検証してみました。

ソースコードはこちら

github.com

gist.github.com

TriggerTest1はSingleton(Mode=Function)

TriggerTest2はSingleton(Mode=Listner)

TriggerTest3はSingletonなし

動作環境はAppServiceプラン(S1 インスタンス数を10に固定)です。

まずはTest3のSingletonなしの結果から

f:id:tamafuyou:20171029151501p:plain

同じ時間に並列実行されています。またPartitionKeyもバラバラです。PartitionKeyにはstatic Lazyで作成されたGuidを設定していますのでGuidの違いは実行インスタンスの違いを表しています。要するにSingletonを付けないキュートリガー関数は複数のインスタンスでそれぞれ並列処理が行われる感じになります。今回の場合は10インスタンスなので10インスタンス * デフォルトサイズ16 なので、最大同時実行数は160になります。

続いてTest1の結果はこんな感じ

f:id:tamafuyou:20171029151547p:plain

Timeを見てみるとおおよそ秒毎に処理が実行されていますので、まさにシングル動作です。またPartitionKeyをみると結構バラバラです。

SingletonをFunctionモードで使用すると複数インスタンスであっても同時実行数1が保証される感じになります。

次にTest2の結果

f:id:tamafuyou:20171029151646p:plain

ingletonを使っているのに同じ時間帯にFunctionが実行されています。しかしPartitionKeyに注目してください。全て一緒です。

SingletonをListnerモードで使用すると単一のインスタンスでの実行が保証されます。今回の場合にはバッチサイズはデフォルトなので最大同時実行数は16になります。

ではListnerモードはどのように有効活用すると良いか? という事ですが、私自身、これだ!!っていう決定的な活用法は思いついていないのですが

qiita.com

こちらで書いたような同時実行の細かな制御を行うのに良いのかなぁとか思っています。