(Durable Functions)「第22回 Azureもくもく会」に参加+LTした話

久しぶりに kingkino@マンダム (@kingkinoko) on Twitterさん主催のAzureもくもく会に参加しました。
で、LTもさせていただきました。

1. 何を もくもく+LT したの?

「Durable Functionsの基礎学習」とその発表でした。
(何かを作り上げた!みたいなものではないのだけれど、昔から技術のバックグラウンドを調べたりするの好きなので)

今年に入ってから、仕事では .NET/Azure を離れ、Java/AWS の世界に移っていましたが、ぽろぽろと趣味で.NET/Azureは やっていました。
で、先日のGlobal Azure Boot Camp 2018に参加してAzure界隈の方々とお話しさせていただいたら「Durable Functions」がホットだということで。
GW終わり辺りからドキュメントを読み、もくもく+LTをさせていただいたという感じです。

2. 発表資料

発表資料は↓↓↓です。

speakerdeck.com

なのですが、「学んだこと」「伝えたかった事」のメインはDemoをさせていただいた部分でした。
加えて、「内容がLT・プレゼン向きじゃない」+「私の説明が決して上手くない」ということで、伝わりにくかったかと思います。

ということでDemoした部分について、ブログらせて頂こうかと・・・

3. 何を学んだのか?何をDemoしたのか?

MS公式のDurable Functionsの資料を読んだ結果、以下のドキュメントがDurable Functionsが Durable である所以的な部分を指しているような気がしました。

docs.microsoft.com

しかし、
「ドキュメントに記述されているルールに従って実装すれば、いい感じにDurable Functionsは動くのだろうけど、何故上手く動くのかが腑に落ちない」
という感覚を持ち、その もやもや を払拭するための技術探索を行いました。
つまり、以下のような内容が、話としては理解するけれど、具体的にそうなる根拠となるバックエンドの知識が欲しい、と。。。

・オーケストレーター関数
 決定論的である必要がある。
 複数回実行されても結果が同じでなければならない。
  →現在日付の取得・GUIDのランダム生成の実行はNG。
  →DateTime.Now ではなく DurableOrchestrationContext.​Current​Utc​Date​Time を使う
・アクティビティ関数
 非決定論的操作が可能。
 ある責務を持ったドメイン ファンクションはここで定義。
  →つまり「DBアクセス」などの処理はアクティビティ関数に実装
・イベントソーシング(パターン)で実装されている。
・Azure Storage(キュー・テーブル)に実行履歴をチェックポイントする事で信頼性を得ている。
・awaitが呼び出されるとオーケストレーター関数をゼロから再実行する。

4. Demo内容

Durable Functionsがどのようなフローで実行されるのか?Azure Storageを使ってどのようにDurableに実行されるのか?(直訳すれば "丈夫に" "恒久的に"ですね)
ほぼVSテンプレが吐き出す以下のコードで検証します(Http TriggerによるDurable Functionsコード)。
カスタムしたのは 19 / 20行目 を追加したぐらいでしょうか。

01: using System;
02: using System.Collections.Generic;
03: using System.Net.Http;
04: using System.Threading.Tasks;
05: using Microsoft.Azure.WebJobs;
06: using Microsoft.Azure.WebJobs.Extensions.Http;
07: using Microsoft.Azure.WebJobs.Host;
08: 
09: namespace FunctionApp2
10: {
11:   public static class Function2
12:   {
13:     [FunctionName("Function2")]
14:     public static async Task<List<string>> RunOrchestrator(
15:       [OrchestrationTrigger] DurableOrchestrationContext context)
16:     {
17:       var outputs = new List<string>();
18: 
19:       var currentUtc = context.CurrentUtcDateTime;
20:       var current = DateTime.Now; // ※本当はオーケストレーター関数で実行しちゃいけないやつ
21: 
22:       outputs.Add(await context.CallActivityAsync<string>("Function2_Hello", "Tokyo"));
23: 
24:       outputs.Add(await context.CallActivityAsync<string>("Function2_Hello", "Seattle"));
25: 
26:       outputs.Add(await context.CallActivityAsync<string>("Function2_Hello", "London"));
27: 
28:       return outputs;
29:     }
30: 
31:     [FunctionName("Function2_Hello")]
32:     public static string SayHello([ActivityTrigger] string name, TraceWriter log)
33:     {
34:       log.Info($"Saying hello to {name}.");
35:       return $"Hello {name}!";
36:     }
37: 
38:     [FunctionName("Function2_HttpStart")]
39:     public static async Task<HttpResponseMessage> HttpStart(
40:       [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]HttpRequestMessage req,
41:       [OrchestrationClient]DurableOrchestrationClient starter,
42:       TraceWriter log)
43:     {
44:       // Function input comes from the request content.
45:       string instanceId = await starter.StartNewAsync("Function2", null);
46: 
47:       log.Info($"Started orchestration with ID = '{instanceId}'.");
48: 
49:       return starter.CreateCheckStatusResponse(req, instanceId);
50:     }
51:   }

ブレークポイントは張りまくります。

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

テストではローカルAzure Storage(Emu)を使いますが、中身を完全に空にしておきます。

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

4.1. 実行

(1) デバッグ実行

プロジェクトをデバッグ実行します。
以下のような感じでローカルでAzure Functionsが実行されます。

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

ここで、Azure Storage ExplorerでStorageの確認を行います。

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

「Blob Contaners」「Queues」「Tables」に色々作成されています(中身は空)。

※上記Storage項目のそれぞれの説明はここらへんに詳細が書かれています。構成により調整が可能。

(2) HTTPトリガーをキック

HTTPトリガーの受け口である「http://localhost:7071/api/Function2_HttpStart」をポストマンでキックします。

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

当然、FunctionsのHTTPトリガーである「HttpStart()」が呼び出されます。

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

「続行(F5)」を行います。
すると、コードの実装の通り「StartNewAsync("Function2")」でFunction2オーケストレーター関数が呼び出されます。
ということで、以下の画面のように Function2オーケストレーター関数 でブレークします。

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

再び「続行(F5)」して「outputs.Add(await context.CallActivityAsync("Function2_Hello", "Tokyo"));」まで飛んだ状態が以下です。

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

日付取得結果は以下の通りでした。

context.CurrentUtcDateTimeの値 → 2018/5/12 14:30:29
DateTime.Nowの値 → 2018/5/12 23:30:46

今度は「ステップオーバー(F10)」します。
CallActivityAsync()により Function2_Hello が呼び出され、以下の場所でブレークします。

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

この状態でAzure Storage Explorerにて「DurableFunctionsHubHistoryテーブル」を確認すると以下のようレコードが作成されています。

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

※要するに「Function2オーケストレーター関数→Function2_Helloアクティビティ関数の呼び出しの履歴の保存」がAzure Storageに対して行われたということです。

で、「続行(F5)」しちゃいます。
結果、ブレークするのはココ↓↓↓↓↓。

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

そう、次のアクティビティ関数呼び出しの個所ではなく、オーケストレーター関数の頭のブレークに飛びます。

また、「続行(F5)」しちゃいましょう。
こんな感じになりますね↓↓↓。

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

context.CurrentUtcDateTimeの値 → 2018/5/12 14:30:29
DateTime.Nowの値 → 2018/5/12 23:32:31

DateTime.Nowは実行した時間そのものが取得されているのに対し、context.CurrentUtcDateTime値は「初回実行時と同じ値」が取得されています!
何度実行しても(何度再生されても)結果が同じ、つまり「決定論的動作」をしています。オーケストレーター関数の条件を満たしている!

次に「ステップオーバー(F10)」すると、先程1度実行済みの「outputs.Add(await context.CallActivityAsync("Function2_Hello", "Tokyo"));」が実行されるはず・・・
では「ステップオーバー(F10)」してみます。
Function2_Helloアクティビティ関数は呼び出されず、次のアクティビティ関数呼び出し箇所「outputs.Add(await context.CallActivityAsync("Function2_Hello", "Seattle"));」に移りました。

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

さらに「ステップオーバー(F10)」してみます。
今度は、Function2_Helloアクティビティ関数が呼び出されました(引数nameはもちろんSeattle)。

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

ここで再びAzure Storage Explorerを見てみましょう。
レコードが増えましたね。Function2_Helloアクティビティが2回呼ばれた記録が行われています。

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

また、Result列に「Hello Tokyo!」と、1つ目のアクティビティ関数の処理結果が保存されています。

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

ここでちょっといたずら的にTokyoをOsakaにUpdateしてしまいます。

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

再び「続行(F5)」しちゃいましょう。
想像通り、オーケストレーター関数のトップに帰ってきます。

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

F5を連打し、Durable Functionsの処理をすべて終了させます。

結果として処理の履歴はDurableFunctionsHubHistoryテーブルに以下のように出力されました。

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

DurableFunctionsHubInstancesテーブルを見ると、処理結果が保存されています。

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

ポストマンでstatusQueryGetUriをリクエストしてみると・・・

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

先程いたずらでAzure Storageデータを変更した「Osaka」の文字が。
つまり、ロジックをメモリ上で実行して終わりではなく、Azure Storageに処理状態を永続化していることが理解できました。

5. まとめ

長々と分かりにくい検証を行いましたが以下のことが明確に理解できたのではないかと思います。

  • Durable Functionsは、アクティビティ関数の実行履歴を細かくAzure Storageに保存している。
  • Durable Functionsは、オーケストレーター関数で定義されたアクティビティ関数を高い信頼性で実行するためにawaitのタイミングでAzure Storageに状態を保存し、自らをリプレイしてすべてのアクティビティ関数をワークフローとして実行している。

Durable Functionsの実装については以下のgithubで公開されているので、更にソースレベルで探索ができるのではないかと思います。

github.com

また、勉強会等でもkingkino@マンダム (@kingkinoko) on Twitterさんや(「🍖・ω・)「🍺 (@yu_ka1984) on Twitterさん などDurable Functionsマスターがいらっしゃるので、色々伺えるのではないかと思います。

Syncfusion SfCalendarを使う(Xamarin Forms)

1. はじめに

Xamarin Formsで、Syncfusion SfCalendarを使って以下のようなサンプル実装を行いました。

f:id:daigo-knowlbo:20180328013715p:plain:w300
f:id:daigo-knowlbo:20180328020349p:plain:w300

  • 月カレンダー形式で予定を表示する
  • 日をクリックすると、対象日のスケジュール詳細(件名)が表示される
  • スワイプもしくはボタンクリックで前後の月に移動できる
  • Prismを使ってMVVMアーキテクチャとする

2. 実装手順

2.1. VS 2017でプロジェクト作成

Visual Studio 2017でPrism Blank App(Xamarin.Forms)プロジェクトを作成。
プロジェクト名は「UseSfCalendarWithPrism」としました。

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

2.2. Nugetパッケージ管理でSyncfusion SfCalendarを追加

XamarinNugetパッケージ管理で「Syncfusion.Xamarin.SfCalendar」をインストール。

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

2.3. 実装

で、実装は以下に置きました。。。。

github.com

以下にサンプル実装のポイントを・・・

ポイント1 カレンダーの月変更イベントをViewModelにバインド

カレンダーの月変更イベントは SfCalendar.MonthChanged です。
MVVMとしているのでイベントをViewMode(MainPageViewModel)で受け取りたいです。
その為、Prismの「EventToCommandBehavior」を利用して、イベントをView→ViewModelにコマンドとして伝播させています。

[MainPage.xaml]
<ContentPage ...省略
                        xmlns:b="clr-namespace:Prism.Behaviors;assembly=Prism.Forms" />
<SfCal:SfCalendar x:Name="calendar" 
                  ShowInlineEvents="True"
                  MinDate="{Binding MinDate}"
                  MaxDate="{Binding MaxDate}"
                  BlackoutDates="{Binding BlackoutDates}"
                  DataSource="{Binding CalendarEventCollection}"
                  SelectionMode="{Binding SelectionMode, Mode=OneWay,Converter={StaticResource SelectionModeConverter}}">
    <SfCal:SfCalendar.Behaviors>
        <b:EventToCommandBehavior EventName="MonthChanged" 
                              Command="{Binding MonthChangedCommand}"
                              EventArgsParameterPath="args.CurrentValue" />
    </SfCal:SfCalendar.Behaviors>
</SfCal:SfCalendar>
[MainPageViewModel.cs]

public class MainPageViewModel : ViewModelBase
{
  ...省略
  public ICommand MonthChangedCommand => new Command<DateTime>((currentDate) =>
  {
    this.UpdateEvents(currentDate);
  });
}

ポイント2 カレンダーにバインドするイベントコレクション作成

SfCalendarに表示するイベントは「SfCalendar.DataSourceプロパティ」に設定しますが、PrismでViewModelにバインドしているので「MainPageViewModel.CalendarEventCollectionプロパティ」にデータバインドしています。
カレンダーの月変更イベントに対して当該月+前後一週間のイベントをバインドデータに設定しています。

[MainPageViewModel.cs]
  public class MainPageViewModel : ViewModelBase
  {
    
    /// <summary>
    /// イベントを更新します。
    /// </summary>
    /// <remarks>
    /// 今月のイベントを表示するために、対象月+前後一週間のイベントをコレクションに設定します。
    /// 1ヶ月のカレンダーの前後に 前月・次月 の日付が表示されるため、前後1週間のイベントを設定します。
    /// </remarks>
    /// <param name="calendarDate"></param>
    private void UpdateEvents(DateTime calendarDate)
    {
      // バインド対象のthis.CalendarEventCollectionを直接、繰り返しAdd()するとパフォーマンスが著しく落ちるのでテンポラリにデータコレクションを用意して差し替える
      CalendarEventCollection newCalendarEventCollection = new CalendarEventCollection();

      DateTime dt = new DateTime(calendarDate.Year, calendarDate.Month, 1);
      int thisMonthLastDay = dt.AddMonths(1).AddDays(-1).Day;
      for (int i = -7; i < thisMonthLastDay+7; i++)
      { // イベントはサンプルなので適当に2日に1回散歩と仕事、毎日のランチを設定
        DateTime eventDt = dt.AddDays(i);

        if (eventDt.Day % 2 == 1)
        {
          //this.calendarEventCollection.Add(
          newCalendarEventCollection.Add(
          new CalendarInlineEvent()
          {
            Subject = $"{eventDt.Day}日 散歩",
            StartTime = eventDt.AddHours(10),
            EndTime = eventDt.AddHours(11),
            Color = Color.Green
          });
        }
        //this.calendarEventCollection.Add(
        newCalendarEventCollection.Add(
          new CalendarInlineEvent()
          {
            Subject = $"{eventDt.Day}日 ランチ",
            StartTime = eventDt.AddHours(12),
            EndTime = eventDt.AddHours(13),
            Color = Color.Orange
          });
        if(eventDt.Day % 2 == 0)
        {
          //this.calendarEventCollection.Add(
          newCalendarEventCollection.Add(
            new CalendarInlineEvent()
            {
              Subject = $"{eventDt.Day}日 仕事",
              StartTime = eventDt.AddHours(10),
              EndTime = eventDt.AddHours(19),
              Color = Color.Blue
            });
        }
      }
      this.CalendarEventCollection.Clear();
      this.CalendarEventCollection = newCalendarEventCollection;
    }
  }

つまずいた点

2018/3/28現在版 Syncfusion SfCalendar(android)に不具合がありました。
イベントを設定しているのに画面上で、対象日をクリックすると「No Appointments」と表示されてしまう不具合でした。
twitterでつぶやいたところSyncfusionの方よりリプライを頂き、即座にパッチアッセンブリを頂き不具合の修正を確認できました。
アップデートとしては2018年3月末の「2018 Vol 1 SP1」で対応されるとのことです。

SfCalendarへの要望

表示形式が「YearViewとMonthView」の2つなのですが、Weeklyとかandroidのカレンダーみたいな形式とか、色々なバリエーションがあればもっと嬉しいなぁ、と思いました。

※今日のブログ、雑ですね。。。まあ、動くソースをgithubに置いたので、「おかしいぞ!分からんぞ!」と思った方はコメント頂ければと思いますm( )m

Xamarin FormsでCarouselViewControlを使う

1. はじめに

Xamarin Formsで以下のようなUIを作りたくて、Carouselコントロールについて あたふた したのでブログにメモっておきます。

f:id:daigo-knowlbo:20180322003924p:plain:w300 f:id:daigo-knowlbo:20180322004001p:plain:w300 f:id:daigo-knowlbo:20180322004009p:plain:w300

2. いくつかのCarousel実装

久しぶりにXamarin Formsアプリ作り始めたのですが、そんな自分にとってはカルーセルがカオスに見えました。。。
どれ 使えばいいの?と・・・
ググったら公式のと非公式のと色々出てきまして、今回は「alexrainman/CarouselView」を使用しました。
一応公式の2つと合わせて以下の3つについて書いておこうと思います。

  • Xamarin.Forms..CarouselPage
  • Xamarin.Forms.CarouselView
  • alexrainman/CarouselView

2.1. Xamarin.Forms.CarouselPage

Xamarin Formsの標準クラスです。
名前からも分かるようにページクラスです。
クラス図でいうと以下のような感じ。なので、ページ内の1要素として配置することは出来ないですね。

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

2.2. Xamarin.Forms.CarouselView

公式から出された奴ですね。
なんか一時期、CarouselPageはdeprecatedしてCarouselViewに移行する的な話を聞いた気がするのですが、Nugetしようとしたら、2016/7/28のプレリリースで止まっている。。。これはタヒってるのかな。。。

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

2.3. alexrainman/CarouselView

と、上記公式の2つが使えそうにないので、ググったら良さげなものがありました。

「alexrainman/CarouselView(https://github.com/alexrainman/CarouselView)」

こちらは以下のようにXamarin.Forms.Viewクラスの派生クラスとして実装されているのでPage内の1要素として利用することができます。
※「alexrainman/CarouselView」の実際に配置するコントロールクラス名は CarouselViewControlクラス になります。

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

3. 実装

では実装します。
(超簡単なサンプルだけど、alexrainman/CarouselViewを使った説明は日本語に少なげだったので、書いておこうと思いました。)
あと、一応 Prism の上で実装します。

3.1. ソリューション・プロジェクトの作成

Visual Studio 2017を起動します。
メニュー「ファイル→新規作成→プロジェクト」を選択。
ここでは、以下の設定で作成します。

プロジェクトテンプレート:「Prism→Xamarin.Forms→Prism Blank App(Xamarin.Forms)」
名前:UseAlexCarouselViewApp

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

ターゲットは android / iOS としました。

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

3.2. Nugetパッケージの追加

UseAlexCarouselViewAppプロジェクト(Formsの共通実装のプロジェクト)のNugetパッケージマネージャを表示して、CarouselView.FormsPluginを検索し、インストールします(これが alexrainman/CarouselView になります)。

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

3.3. モデルクラスの追加

せっかくPrismを使用しているので、ページ情報を表すモデルクラスを追加します。
UseAlexCarouselViewApp/Models/PageInfo.cs を追加します。

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

// UseAlexCarouselViewApp\Models\PageInfo.cs

using Xamarin.Forms;

namespace UseAlexCarouselViewApp.Models
{
  public class PageInfo
  {
    public string Name { get; set; }
    public Color ForeColor { get; set; }
    public Color BackColor { get; set; }
  }
}

3.4. ViewModelにPageInfoコレクションを追加

デフォルトで作られたMainPageViewModelクラスにPageInfoコレクションプロパティを追加します。
後でPageに配置するCarouselViewControlのページにバインドするプロパティになります。

// UseAlexCarouselViewApp\ViewModels\MainPageViewModel.cs

using System.Collections.ObjectModel;
using Prism.Navigation;
using Xamarin.Forms;
using BlankApp6.ViewModels;

namespace UseAlexCarouselViewApp.ViewModels
{
  public class MainPageViewModel : ViewModelBase
  {
    // CarouselViewControlにバインドするページ情報コレクション
    public ObservableCollection<PageInfo> CarouselPageInfo { get; set; }

    public MainPageViewModel(INavigationService navigationService) 
      : base (navigationService)
    {
      Title = "Main Page";

      // コレクション初期化
      this.CarouselPageInfo = new ObservableCollection<PageInfo>
      {
        new PageInfo
        {
          Name = "Page1",
          ForeColor=Color.Yellow,
          BackColor= Color.Red
        },
        new PageInfo
        {
          Name = "Page2",
          ForeColor=Color.Black,
          BackColor= Color.Yellow
        },
        new PageInfo
        {
          Name = "Page3",
          ForeColor=Color.Gold,
          BackColor= Color.Green
        }
      };
    }
  }
}

3.5. PageにCarouselViewControlを追加

MainPage.xamlにCarouselViewControlを配置して、MainViewModel.PageInfoをデータバインディングします。

UseAlexCarouselViewApp\Views\MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:cv="clr-namespace:CarouselView.FormsPlugin.Abstractions;assembly=CarouselView.FormsPlugin.Abstractions"
       x:Class="UseAlexCarouselViewApp.Views.MainPage"
       Title="{Binding Title}">

  <StackLayout>
    <Label Text="Hello" />
    <Editor BackgroundColor="Yellow" />

    <cv:CarouselViewControl
      VerticalOptions="FillAndExpand"
      HorizontalOptions="FillAndExpand"
      Position="0"
      ShowIndicators="True"
      ShowArrows="True"
      ItemsSource="{Binding CarouselPageInfo}">
      <cv:CarouselViewControl.ItemTemplate>
        <DataTemplate>
          <StackLayout VerticalOptions="FillAndExpand"
                 HorizontalOptions="FillAndExpand"
                 BackgroundColor="{Binding BackColor}">
            <Label Text="{Binding Name}" TextColor="{Binding ForeColor}"/>
          </StackLayout>
        </DataTemplate>
      </cv:CarouselViewControl.ItemTemplate>
    </cv:CarouselViewControl>
  </StackLayout>

</ContentPage>

ポイントは以下の通り。

  • コントロールを使うのでページにnamespaceを追加する
    以下により cvタグプレフィックス で CarouselViewControl が使えるようになります。
xmlns:cv="clr-namespace:CarouselView.FormsPlugin.Abstractions;assembly=CarouselView.FormsPlugin.Abstractions"
  • CarouselViewControlを配置
    親要素(ContentPage)で名前空間定義したタグプレフィックcvを使ってコントロールを配置定義しています。
    ItemsSourceとかDataTemplate定義は普通のデータバインド対応コントロールと同じテーストです。
<cv:CarouselViewControl
...
  • CarouselViewControlはView派生クラス
    CarouselViewControlはView派生クラスなのでStackLayoutの子要素としてLabelやEditorと並べて配置可能です。

3.6. ViewRendererの初期化を追加

最後に重要な「ViewRendererの初期化」処理とiOS/androidそれぞれのプロジェクトに追加します。

androidは、UseAlexCarouselViewApp.Android\MainActivity.csに「CarouselViewRenderer.Init();」を追記します。

// UseAlexCarouselViewApp.Android\UseAlexCarouselViewApp.Android\MainActivity.cs
...
using CarouselView.FormsPlugin.Android;  // ←これも追記
...
namespace UseAlexCarouselViewApp.Droid
{
  ...
  public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
  {
    ...
    global::Xamarin.Forms.Forms.Init(this, bundle);
    CarouselViewRenderer.Init();  // ←追記
    ...
  }
}

iOSは、UseAlexCarouselViewApp.iOS\AppDelegate.csに「CarouselViewRenderer.Init();」を追記します。

// UseAlexCarouselViewApp.OS\UseAlexCarouselViewApp.iOS\AppDelegate.cs
...
using CarouselView.FormsPlugin.iOS;  // ←これも追記
...
namespace UseAlexCarouselViewApp.iOS
{
  ...
  public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
  {
    ...
    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
      global::Xamarin.Forms.Forms.Init();
      CarouselViewRenderer.Init();  // ←追記
      ..
    }
  }
}

※ViewRenderer
ViewRendererは「Formsとネイティブコントロール要素との繋ぎ役」みたいな役割を持っています。
Forms上でのXXコントロールandroidでは○○コントロールとして表示し、iOSでは△△コントロールとして表示する。また、各ネイティブで発生したアクション・イベントをFormsコントロールのイベントに結び付けるような、そんな役割を持っています。

4. ソースはココ

サンプル実装は↓↓↓↓↓に置いときました。

github.com

db tech showcase 2017(1日目)に参加した話

今回の投稿は、いつもの、技術を論じるブログではなく読み物です^^;

2017/9/5、db tech showcase 2017に参加しました。

私はDatabaseを軸として打ち出しているイベント(セミナー)に参加したのは実は初めてだったりします(「私はDB屋では無い」のです)。
秋葉原UDXでがっつり3日間開催されました(されています)が、これ、参加費用無料!なんですよね^^
ミニ展示スペースがあった(&プロダクト宣伝的セッションもあった)ので、スポンサー費用(+インサイトテクノロジー様の持ち出し?)なんかで運用されてるのかな?・・・なんて金勘定の頭が働いてしまうは私が穢れたエンジニアだからでしょうか^^;

初日の本日は2コマ目から参加させていただきました。

では、以下に感想をポロポロと・・・

1. 「A12:Keynote Speech これからの'‘本命技術’‘はこう見つける!~ポスト・リレーショナルデータベース時代を読み解くコツ~」

まずは、キーノート2として、ウルシステムズ株式会社 漆原 様 / 楽天株式会社 森 様によるトークセッションでした。
特定の技術に深く踏み込むものではなく、DBを中心として業界全体を俯瞰した形でのトーク、また、楽天技術研究所というテクノロジーの集まる現場における、技術との付き合い方(まとめすぎかな?)的な内容のトークが展開されました。

個人的に最も感じたのは、「お二方の下で働くエンジニアは幸せだろうな」ということです。
ビジネスを推進する要素は「ビジネス的視点を起点とするもの」「技術的視点を起点とするもの」の2つがあると私は信じています。
前者が絶対であり、技術を語るものは「ビジネスを理解しない ただのオタ」と判断される組織も日本には多々あるように感じています。でも、それが真であるケースも多々あるか・・・。

でも、やはり、最終的にテクノロジーによりサービスは顧客に対して提供されており、同時にビジネス的観点も重要であると思っています。
ビジネスマンとエンジニア、互いが、互いを否定しあうのではなく、理解しあって、「顧客に対する価値を提供できた」時が最も大きな力を発揮するような気がしているのです。

2. 「A13:MySQL MySQLを割と一人で300台管理する技術」

すみません、私はDB界隈の人間ではないので「@yoku0825さん」を存じ上げていなかったのですが、セッションは何気に楽しめました。
完全にDBエンジニアセッションでした。私はMySQLの面倒を見る仕事をしたことがなかったので、心に沁みる「おーー」という気持ちにはならなかったのですが、実運用する上で、ここ気を付けようよ、というノウハウが詰まっていて、「是非このパワポスライド欲しいわー」って感じのセッションでした。
あとGMOグループってよく耳にする企業なので、「へー、こんな感じなんだなぁ。MySQLを軸にしてるんだぁ」といった感覚を覚えました(全グループ企業の状況なんかは、もっと詳細を聞かないと分からないと思いますが・・・)

3. 「A17 データベース・ビフォーアフター ~インメモリーによる超高速化の世界~」

再び ウルシステムズ株式会社 様のセッションです。
漆原社長は、プレゼン上手&惹き付け上手 ですね^^
いえ、まあそれを除外しても、このセッションは面白かった気がしました。
apache geode」を使ったインメモリグリッドデータベースによるパフォーマンス改善事例の紹介、でした。

冒頭の「私はDB屋では無い」宣言の通り、従来のRDB文化をぶち壊すアーキテクチャが私の好物です^^
なので「インメモリグリッドデータベース」で、旧来のRDBシステムのパフォーマンスボトルネックを非RDBでぶっちぎる事例はすごくワクワクしました。
セッションのお話を聞いた限り、apache geodeは結構自動的に水平スケーリング&分散(って言っていいのかな)してくれるそうなので、これはちょっと自分でもリサーチしてみたい気になりました。

4. まとめ

上記以外のセッションも参加させていただいたのですが、特に印象的なセッションのみ感想を書かせていただきました。
あー、でも「E15 分散グラフデータベース - DataStax Enterprise Graph 森下 雄貴 様」も自分好みでしたよ。
でも、グラフデータベースはAzure Cosmos DBを学ぶと同時に学習していたので、心に刺さるレベルとまではいかなかったかも・・
まあ、私は「RDB駆逐してやるぜ!」という意気込みの「アプリケーションエンジニア」として参加させていただきましたが、十分に楽しめた気がしています。
(過激表現、ご勘弁を・・・)
まあ、なんだかんだ言いながら・・・
アプリケーションエンジニアもデータベースエンジニアも、進化や改善を求め歩みを止め無いことが最も素晴らしいことではないかと思っています!!

Dictionary to Objectのマッパーを使う - Slapper.AutoMapper

本日、会社でDictionaryオブジェクトからObjectへのオートマッパーを行う方法知りませんかー?と聞かれまして・・・
決め打ちの固定プロパティへのマッピングであれば、AutoMapperのMapFromで出来るかなぁ・・・と思ったのですが・・・
要件としてはプロパティ名を決め打ちしない動的マッピングなのです。

ということでググったら、StackOverflow経由で Slapper.Automapper なるものを見つけましたので、ここにメモしておきます。
といっても結構前からgithubに上げられていたライブラリになります。

Slapper.AutoMapper

これです!

github.com

普通にVSからNugetで取得することができます。

Slapper!!!なんかかっこいいですね。そのままの動きをします。

何をするもの?

まあ、つまり「Dictionaryオブジェクトから任意のオブジェクトへのマッパー」を可能にします。

以下の リスト1 のオブジェクトを、一発で リスト2 のオブジェクトにマッピングします。

// リスト1
var src = new Dictionary<string, object>();
src.Add("IntProp1", 10);
src.Add("IntProp2", 20);
src.Add("IntProp3", 30);
src.Add("IntProp4", 40);
src.Add("IntProp5", 50);
src.Add("StringProp1", "10");
src.Add("StringProp2", "20");
src.Add("StringProp3", "30");
src.Add("StringProp4", "40");
src.Add("StringProp5", "50");
// リスト2
public class TestClass
{
  public int IntProp1 { get; set; }
  public int IntProp2 { get; set; }
  public int IntProp3 { get; set; }
  public int IntProp4 { get; set; }
  public int IntProp5 { get; set; }

  public string StringProp1 { get; set; }
  public string StringProp2 { get; set; }
  public string StringProp3 { get; set; }
  public string StringProp4 { get; set; }
  public string StringProp5 { get; set; }
}

使い方

以下のリスト3が使い方のサンプルです。

// リスト3
namespace ConsoleApp1 {
  class Program {
    static void Main(string[] args) {
      // データソースを作成
      Dictionary<string, object> src = new Dictionary<string, object>();
      src.Add("IntProp1", 10);
      src.Add("IntProp2", 20);
      src.Add("IntProp3", 30);
      src.Add("IntProp4", 40);
      src.Add("IntProp5", 50);
      src.Add("IntProp6", 60);  // マップ先に存在しないプロパティは無視される
      src.Add("StringProp1", "10");
      src.Add("StringProp2", "20");
      src.Add("StringProp3", "30");
      src.Add("StringProp4", "40");
      src.Add("StringProp5", "50");
      
      
      // 一発でマッピング!!
      var desc = Slapper.AutoMapper.Map<TestClass>(src);
    }
  }
  
  // マップ先のオブジェクト
  public class TestClass
  {
      public int IntProp1 { get; set; }
      public int IntProp2 { get; set; }
      public int IntProp3 { get; set; }
      public int IntProp4 { get; set; }
      public int IntProp5 { get; set; }

      public string StringProp1 { get; set; }
      public string StringProp2 { get; set; }
      public string StringProp3 { get; set; }
      public string StringProp4 { get; set; }
      public string StringProp5 { get; set; }

      public string NothingText { get; set; } // ソースにないプロパティはnull(number系の型なら 0 )
  }
}

ちなみに AutoMapper と名が付きますが、いわゆる本家AutoMapperは全く内部的にも使っていません。
githubで公開されているので、ソースを見ればわかりますが、シンプルなReflectionによるMapper解決を行っています。

※最近ブログをさぼっていたのでウォーミングアップ的ブログ投稿でした。
でも、何気に要求的にはよくありそうな話なので、本投稿がGoogleで引っかかってどなたかのお役に立てれば^^です!

C#7.1のAsync Mainがお気に入り~Visual Studio 2017 Update 3(15.3)

Visual Studio 2017 Update3(15.3)が 2017/8/14 にリリースされました。

www.visualstudio.com

.NET Core 2.0サポートだったり、コンパイルベースのAzure Functionsサポートのような今までPreview版を利用しなければならなかった機能が正式版で使えるようになりました。

1. C# 7.1サポート

で、その中に「C# 7.1言語機能の追加」ってものもありました。

C# 7.1機能として以下のような機能が使えるようになりました。

  • async Main methods
  • pattern-matching with generics
  • “default” literals
  • inferred tuple names

2. async Main methodのサポート

個人的には「async Main methods」が何気にお気に入りです。

普段の開発作業においてサンプル実装を試すのに、コンソールアプリプロジェクトを利用することが結構あります。
この時、従来はMain()メソッドにasyncを付けることができなかったので、以下のようなことをする必要がありました。

↓↓↓こんなやり方や・・・

// リスト1

// C# 7.0までは・・・。
// Main()以外に非同期版メソッドを用意して呼び出し
static void Main(string[] args)
{
  MainAsync().GetAwaiter().GetResult();
}

private static async Task MainAsync()
{
  string html = await new HttpClient().GetStringAsync("http://www.yahoo.co.jp");
  Console.WriteLine( html );
}

↓↓↓こんなやり方など・・・

// リスト2

// C# 7.0までは・・・。
// 試したいメソッドに .Result をつける(実際の使い方と異なる形になる)
static void Main(string[] args)
{
  string html = await new HttpClient().GetStringAsync("http://www.yahoo.co.jp").Result;
  Console.WriteLine( html );
}

C# 7.1では以下のような実装が可能になりました。

// リスト3

// C# 7.1版
// Mainにasyncが付けられる!!
static async Task Main(string[] args)
{
  string html = await new HttpClient().GetStringAsync("http://www.yahoo.co.jp");
  Console.WriteLine( html );
}

Main()メソッドにasyncが付けられるようになったことで、直接的に実装を記述することができるようになりました。

3. 利用する C# のバージョン

利用する C# のバージョンはプロジェクトのプロパティで設定することができます。
デフォルトでは「最新のメジャーバージョン」が既定になっています。つまり VS2017 Update3(15.3) で作成したプロジェクトは、そのまま C# 7.1 が利用可能です。
プロジェクトで明示的に利用する C#バージョン を設定する方法は以下の通りです。

(1) プロジェクトプロパティの表示

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

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

(2) C#バージョンの選択

表示されたプロパティ ペインから「ビルド」を選択し、「詳細設定」ボタンをクリックします。

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

表示された「ビルドの詳細設定」ウィンドウで、「言語バージョン」を選択します。

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

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

Azure FunctionsでCosmos DBを入力バインドする

以前「Azure FunctionsからCosmos DBに出力バインドする」という記事を書きましたので、それと対になる形で入力バインドについても簡単にまとめておこうと思います。

ryuichi111std.hatenablog.com

今回 実装することは以下の通りです。

  • HttpTriggerを持つAzure Functionsを作成
  • Cosmos DBドキュメントを入力バインドで受け取る
  • Function内の処理で、受け取ったCosmos DBドキュメントに変更を加える

1 開発環境

利用する環境は、以下の通りです。

2 前提条件

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

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

  • APIモデルは「DocumentDB」
  • データベース名「CompanyDB」、コレクション名「EmployeeCollection」を作成済
  • ドキュメントが1件登録済み(id=“0000000001")
-- 登録済みドキュメント --
{
  "id": "0000000001",
  "firstname": "ryuichi",
  "lastname": "daigo",
  "_rid": "5T9QALZ5bQABAAAAAAAAAA==",
  "_self": "dbs/5T9QAA==/colls/5T9QALZ5bQA=/docs/5T9QALZ5bQABAAAAAAAAAA==/",
  "_etag": "\"0300aeae-0000-0000-0000-599081c20000\"",
  "_attachments": "attachments/",
  "_ts": 1502642619
}

3 実装

Visual Studio 2017 Preview(15.3)を起動します。

(1) プロジェクト作成

メニュー「ファイル → 新規作成 → プロジェクト」を選択します。

「新しいプロジェクト」ウィンドウが表示されます。
以下の入力を行います。

  • プロジェクトテンプレート:「Visual C# → Cloud → Azure Functions」
  • 名前:適当に・・・ここではデフォルトの FunctionApp1 にしました

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

設定用JSONのみの空っぽのソリューションが出来上がります。

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

(2) Functionsコードの追加

ソリューションエクスプローラで FunctionApp1 をマウス右ボタンクリック。
表示されたメニューから「追加 → 新しい項目」を選択します。

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

「新しい項目の追加」ウィンドウが表示されるので、「Azure Function」アイテムを追加します。
ここでは名前をデフォルトの「Function1.cs」としました。

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

「New Templates - Function1」というファンクションのトリガーを選択するウィンドウが表示されます。

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

「HttpTriggerWithParameters」を選択します。
また、「AccessRights」を「Anonumouse」としました。これにより、トリガーとなるHTTPリクエストを行う際に認証なしとなります(テストの利便性の為ここではそうしていますが、誰でもHTTPトリガーをキックできてしまうということなので、ご利用には気を付けてください)。
「FunctionName」はデフォルトの「HttpTriggerWithParametersCSharp」とします。

自動生成された Function1.cs 配下の通りです(リスト1)。

//リスト1 自動生成された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 FunctionApp1
{
  public static class Function1
  {
    [FunctionName("HttpTriggerWithParametersCSharp")]

    public static HttpResponseMessage Run(
      [HttpTrigger(
        AuthorizationLevel.Anonymous, 
        "get",
        "post", 
        Route = "HttpTriggerCSharp/name/{name}")]
      HttpRequestMessage req, 
      string name, 
      TraceWriter log)
    {
      log.Info("C# HTTP trigger function processed a request.");

      // Fetching the name from the path parameter in the request URL
      return req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
    }
  }
}

Run()メソッドがFunctionのエントリーポイントです。
第1引数「HttpRequestMessage req」に「[HttpTrigger]属性」が付与されています。HttpTrigger属性のパラメータで以下の設定が宣言されています。

(3) CosmosDB入力バインド実装の追加

まず、CosmosDB入力バインド用エクステンションを NuGetパッケージ より追加します。

ソリューションエクスプローラで FunctionApp1 をマウス右ボタンクリック。
表示されたメニューから「NuGet パッケージの管理」を選択します。

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

「NuGet パッケージの管理」画面が表示されたら、「参照」タブを選択し「Microsoft.Azure.WebJobs.Extensions.DocumentDB」を検索します。

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

Microsoft.Azure.WebJobs.Extensions.DocumentDB」を選択して、インストールボタンをクリックします。

では、 Function1.cs を開き、以下のように編集します。

// リスト2 CosmosDB入力バインド処理を追加した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 FunctionApp1
{
  public static class Function1
  {
    [FunctionName("HttpTriggerWithParametersCSharp")]

    public static HttpResponseMessage Run(
      [HttpTrigger(
        AuthorizationLevel.Anonymous, 
        "get",
        "post", 
        Route = "HttpTriggerCSharp/name/{name}")]
      HttpRequestMessage req,
      [DocumentDB(
        "CompanyDB", 
        "EmployeeCollection",
        ConnectionStringSetting = "cosmosdb_DOCUMENTDB", 
        Id = "{name}")]
      dynamic document,
      string name, 
      TraceWriter log)
    {
      log.Info("C# HTTP trigger function processed a request.");

      // 入力ドキュメントをログ出力
      log.Info("input document info");
      log.Info(" firstname:" + document.firstname);
      log.Info(" lastname:" + document.lastname);

      // 入力ドキュメントに変更を加える
      document.age = 25;
      document.Salary = 15000000;

      // Fetching the name from the path parameter in the request URL
      return req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
    }
  }
}

Function1.csへの修正ポイントは以下の通りです。

  • 引数「document」の追加
    HttpTrigger引数に続けて「document引数」を追加しています。これが CosmosDB入力バインドパラメータになります。
    パラメータには「DocumentDB属性」を付与しています。
    入力バインドの情報として「データベース名」「コレクション名」を指定しています。
    「ConnectionStringSetting」は、Cosmos DBへの接続文字列情報です。接続文字列自体ではなく、接続文字列を設定した「構成情報キー」を指定します(設定方法は後述)。
    「Id」は、入力バインドするCosmos DBドキュメントの ID を表します。ここでは「{name}」としていますが、これはHttpTriggerのパラメータ「{name}」に該当します。「nameパラメータ値 = ドキュメントID」と、意味的に少し変ですが、自動生成コードの修正を最小限にしています。

  • documentのログ出力
    document引数には ID に該当するドキュメント オブジェクトが自動的にバインド設定されて呼び出されます。
    document.firstname / document.lastname をログ出力することで、内容を確認できるようにしています。

  • documentへのプロパティ値追加
    以下のようにdocumentに対して変更を加えています。
    入力バインドしたドキュメントへの変更は、Functionの正常終了後に Cosmos DBドキュメント に反映されます(データ更新が行われます)。

// 入力ドキュメントに変更を加える
document.age = 25;
document.Salary = 15000000;

(4) 公開 & Azureポータルでの構成

では、Azure上に公開します。

ソリューションエクスプローラで FunctionApp1 をマウス右ボタンクリック。
表示されたメニューから「公開」を選択します。

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

「新規作成」を選択して「発行」ボタンをクリックします(既にAzure FunctionsをAzure上に作成済みの場合には、「既存のものを選択」を利用します)。

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

Functionの名前等は適当に・・・ここでは Function Appの名前を InputBindExample として、「リソースグループ」「App Service プラン」「ストレージアカウント」は、それぞれ新規作成しました。

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

「公開」が成功した後の、Azureポータル画面が以下となります。
「Azure Functions(App Service)」「App Service Plan」「ストレージアカウント」の3つのリソースが作成されました。

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

Azure Functions「InputBindExample」を選択します。
「アプリケーション設定」をクリックして、Cosmos DBへの接続文字列項目を追加します。

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

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

(5) 実行

では、Functionを実行してみます。
まず、Azureポータルから実行します。

Function「HttpTriggerWithParametersCSharp」を選択します。
そして右側のテストタブを表示し、パラメータ「name」に「0000000001」を設定します。これは「入力パラメータname = ドキュメントID」として実装しているためです。
実行ボタンをクリックします。

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

出力されたログが以下の通りです。
ID = 0000000001 のドキュメント内容 firstname / lastname が出力されていることを確認することができます。つまり、確かにCosmos DBドキュメントが入力バインドパラメータとして引き渡されました。

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

次にAzureポータルで、Cosmos DBの「データエクスプローラ」を表示します。
age / salary 項目が追加されていることが確認できます。

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

匿名認証OK、HTTP GET許可としている為、ブラウザで「Https://inputbindexample.azurewebsites.net/api/HttpTriggerCSharp/name/0000000001」をアクセスする事でもFunctionをキックすることができます。

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

4 まとめ

ということで、Azure FunctionsへのCosmos DB入力バインドでした。
入力バインドといっても、Functionsの処理内でCosmos DBドキュメントに対して変更を加えることもできました。

Azure Functionsへの入出力バインドはCosmos DB以外にも「Blob storage」だったり、「Queue storage」だったりと、たくさんのバリエーションがあります。これらのバインドを利用すると、従来型の定型的ロジックの記述を省略することができます。
今回のCosmos DBバインドの例では、Cosmos DBへの接続処理の記述、取得・更新処理 の記述といったものが不要となりました。
つまり、煩雑なロジックの記述が Azure上のプラットフォームインフラ に吸収されていることにより、開発者はビジネスロジックの記述に注力することが可能になります。

本投稿では、ローカル実行やデバッグ等、色々端折っておりますが、Azure FunctionsへのCosmos DB入力バインドの一例として参考になればと思います。