Azure ServiceFabric - Web Apiを実装してみる

Service Fabricについては下記のMSDNのページを参照するとよい
 [Sevice Fabricの概要](https://docs.microsoft.com/ja-jp/azure/service-fabric/service-fabric-overview)
 [開発環境を準備する](https://docs.microsoft.com/ja-jp/azure/service-fabric/service-fabric-get-started)
 [最初の Azure Service Fabric アプリケーションを作成する](https://docs.microsoft.com/ja-jp/azure/service-fabric/service-fabric-create-your-first-application-in-visual-studio)
 [ローカル クラスターでアプリケーションのデプロイおよびアップグレードを開始する](https://docs.microsoft.com/ja-jp/azure/service-fabric/service-fabric-get-started-with-a-local-cluster)
 [Azure ポータルを使用して Azure で Service Fabric クラスターを作成する](https://docs.microsoft.com/ja-jp/azure/service-fabric/service-fabric-cluster-creation-via-portal)

# 環境
- Visual Studio 2017
- Azure SDK 2.9.6
- Microsoft Azure Service Fabric SDK 2.6.204
- Service FabricのHost OSはWindows Server、言語はC#で作成。

*Host OSをLinuxにしたり、C#以外の言語でも開発自体は可能

# 仕様
## API仕様
下記のテーブルを参照し、ユーザIDからをJson形式で名前を返すAPIを作成する
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/3c89026a-d79c-5b46-064d-5ef19f5d93e0.png)

## 構成
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/63c5b85f-2f26-d1a7-84fb-1d51f594d0ad.png)

・Actorが定期的にDBからデータを取得し、Stareful Serviceのデータを更新
・Web APIにアクセスの際はStateful Serviceをコールし、Stateful Serviceで永続化されたデータを返す




# 実装
## Service Fabricのソリューションを作成
ソリューションの新規作成からService Fabricを選択する
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/e578cb26-187c-3d56-4344-1d1f114d06c7.png)

最初に作成するサービスのタイプを選択。(今回はStateful Serviceから作成するためステートフルサービスを選択)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/291e813e-3709-6b2f-720b-002b2abdf89e.png)


上記でステートフルサービスを選択した場合、Service Fabric、ステートフルサービスの2つのプロジェクトが作成される
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/9357c31c-0f6d-b04b-43da-9daa69a3b08f.png)

## Sateful Serviceの実装
### Interfaceプロジェクト作成
Interfaece用のプロジェクトを新規で追加する。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/7c774051-731a-22e1-8788-60758a2e16f3.png)


別のサービスとのデータをやり取りするためのModelを作成  

```C#:User.cs
public class User
{
    /// <summary>
    /// ID
    /// </summary>
    public int Id { get; set; }
    /// <summary>
    /// FirstName
    /// </summary>
    public string FirstName { get; set; }
    /// <summary>
    /// LastName
    /// </summary>
    public string LastName { get; set; }
}
```

### データ更新処理
データ更新用のInterfaceを作成  

```C#:IUpdateHogeService.cs
public interface IUpdateHogeService : IService
{
    /// <summary>
    /// Hoge ServiceのUserデータを更新
    /// </summary>
    /// <param name="users"></param>
    /// <returns></returns>
    Task Update(IEnumerable<User> users);
}
```

StatefulServiceに実装する(追記:2017/06/13データ更新がされないバグのため修正)

```C#:HogeStatefulService.cs
private static readonly string _dictionaryName = "MyDictionary";

public async Task Update(IEnumerable<User> users)
{
    var myDictionary = await this.StateManager.GetOrAddAsync<IReliableDictionary<int, User>>(_dictionaryName);

    using (ITransaction transaction = this.StateManager.CreateTransaction())
    {
        foreach (User user in users)
        {
            await myDictionary.AddOrUpdateAsync(transaction, user.Id, user, (key, value) => user);
        }
        await transaction.CommitAsync();
    }
}
```

### データ取得処理
データ取得用のInterfaceを作成

```C#:IGetHogeService.cs
public interface IGetHogeService : IService
{
    /// <summary>
    /// ユーザリストを取得する
    /// </summary>
    /// <returns></returns>
    Task<User> GetUser(int id);
}
```

StatefulServiceに実装する

```C#:HogeStatefulService.cs
public async Task<User> GetUser(int id)
{
    var myDictionary = await this.StateManager.GetOrAddAsync<IReliableDictionary<int, User>>(_dictionaryName);
    using (ITransaction transaction = this.StateManager.CreateTransaction())
    {
        return (await myDictionary.TryGetValueAsync(transaction, id)).Value;
    }
}
```

### リスナーの実装
CreateServiceReplicaListenersに外部からのリクエストリスナーを追加

```C#:HogeStatefulService.cs
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
    return new List<ServiceReplicaListener>()
    {
        new ServiceReplicaListener(
                    (context) =>
                        this.CreateServiceRemotingListener(context))
    };
}
```

## Actorの実装

### プロジェクトの追加
Service Fabricのプロジェクトのコンテキストメニューから追加⇒Service Fabricサービスの新規作成...を選択する
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/5fa32206-f575-6e93-e58e-d7b2aec55022.png)

アクターサービスを選択
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/97cbf363-5ce8-911d-04ea-0a317a99b5b8.png)

ActorとInterfaceのプロジェクトが追加される
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/deb5f34d-8a9b-fd87-a48b-50703b2cc8b5.png)

### データ更新処理
データ更新用のInterfaceを作成

```C#:IHogeActor.cs
public interface IHogeActor : IActor
{
    /// <summary>
    /// データ更新を行う
    /// </summary>
    /// <returns></returns>
    Task Update();
}
```

Actorに実装

```C#:HogeActor.cs
public async Task Update()
{
    ServiceProxyFactory serviceProxyFactory = new ServiceProxyFactory();
    IUpdateHogeService service =
                serviceProxyFactory.CreateServiceProxy<IUpdateHogeService>(new Uri(@"fabric:/Hoge/HogeStatefulService"), new ServicePartitionKey(0));

    using (HogeDbContext context = new HogeDbContext())
    {
        var data = await context.Users.Select(u => new HogeStatefulService.Interface.Models.User()
        {
            Id = u.Id,
            FirstName = u.FirstName,
            LastName = u.LastName
        }).ToListAsync();

        await service.Update(data);
    }
}
```

※Azure ServiceのエミュレータからLocalDBに接続する場合は下記を参照
[Azure Service Fabric - エミュレーターからLocalDBへの接続](http://qiita.com/msomini/items/89db413b6374d2f7979d)

### Alarmの設定
先ほど作成したUpdateメソッドを定期的に実行して、DBからデータを取得するため、
Alarmを実装する。
定期的に処理を実行する機能としてAlarmの他にTimerがある。
AlarmとTimerの違いは下記を参照。
[アクターのタイマーとアラーム](https://docs.microsoft.com/ja-jp/azure/service-fabric/service-fabric-reliable-actors-timers-reminders)

OnactivateAsyncメソッドにAlarmの起動処理を実装
RegisterReminderAsyncは引数の順番にAlarm名、Alarmに関連付けられた状態、実行遅延、実行間隔となる
(2017/08/30追記 原因不明だが、アップデート時に前回起動のreminderが終了されない場合があるので、終了処理を追記)

```C#:HogeActor.cs
ActorEventSource.Current.ActorMessage(this, "Actor activated.");
    try
    {
        var registerReminder = GetReminder(RequestLimitDataActorName);
        await UnregisterReminderAsync(registerReminder);
    }
    catch (ReminderNotFoundException) 
    {
        // 処理なし 
    }
    var reminder = await this.RegisterReminderAsync(
           "Hoge Alarm",
           null,
           TimeSpan.FromMilliseconds(0),
           TimeSpan.FromMinutes(1)
        );
```

IRemindable Interfaceを継承し、Alarmから呼ばれる処理をReceiveReminderAsyncメソッドに実装する。

```C#:HogeActor.cs
public async Task ReceiveReminderAsync(string reminderName, byte[] state, TimeSpan dueTime, TimeSpan period)
{
    if (reminderName == "Hoge Alarm")
    {
        await Update();
    }
}
```

(2017/06/17追記) Alarmの終了処理を記述(終了処理の記述がないと、更新のデプロイ時に失敗するので必ず終了処理を記述)

```C#:HogeActor.cs
protected override async Task OnDeactivateAsync()
{
    var register = GetReminder("Hoge Alarm");
    await UnregisterReminderAsync(register);

    await base.OnDeactivateAsync();
}
```

ActorのAlarmやタイマーの起動はOnActiveAsyncメソッドで起動する。
OnActiveAsyncはActor内のいずれかのメソッドがコールされたタイミングで実行されるため、
最低1回はメソッドをコールする必要がある。
下記を参考にActorServieを独自のものにすることで、起動時にメソッドをコールし、自動的に
AlarmやTimerを起動することができる。

[Azure Service Fabric - ActorのTimerやAlermを自動で起動](http://qiita.com/msomini/items/99a85c13151cee5342fd)

## Web APIの実装
### プロジェクトの追加
Service Fabricのプロジェクトのコンテキストメニューから追加⇒Service Fabricサービスの新規作成...を選択する
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/5fa32206-f575-6e93-e58e-d7b2aec55022.png)

ステートレスWebApiを選択
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/83e82260-e5f0-f138-13a8-9db78990e2c1.png)


WebApiのプロジェクトが追加される
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/7740106e-86fd-0255-fe5d-4cda9a3edbcc.png)

### Web Apiの実装

適当なControllerを作成し、Getメソッドを実装

```C#
public async Task<HttpResponseMessage> Get(int id)
{
    ServiceProxyFactory serviceProxyFactory = new ServiceProxyFactory();
    IGetHogeService service =
                serviceProxyFactory.CreateServiceProxy<IGetHogeService>(new Uri(@"fabric:/Hoge/HogeStatefulService"), new ServicePartitionKey(0));

    HogeStatefulService.Interface.Models.User result = await service.GetUser(id);


    return this.Request.CreateResponse(
            HttpStatusCode.OK,
            new { Result = result });
}
```

## エミュレーターで実行してみる

Visal Studioの構成マネージャーを開きすべてx64になっていることを確認。
AnyCpuの場合はx64に設定(存在しない場合はAnyCpuをベースにx64を作成)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/1289e190-85e2-58c8-2fb3-30f75baa034d.png)

構成マネージャーですべてx64にしVisual Studioの開始ボタンを押下すると、エミュレーターへの
デプロイが開始される

Service Fabric Explorerを確認し、各サービスでエラーがないことを確認
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/447b0cc6-d247-896d-f0cf-03358c92daba.png)

ブラウザやRest ClientからApiをたたいてみる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/109278/00ea443a-67a9-0443-e385-ee56d4bbfa48.png)

APIの実行結果が確認できる。

# その他
StateMangerのReliableDictionaryはレコードの容量が大きくなるとパフォーマンスが悪くなるので注意。
MSDNでは80kByte未満が推奨。
テスト周りではまりポイントが多いので、次はテストについて書きます。