soracom-sdk-go ベータ版でいろいろ試す

ソラコムの小熊です。

10/26 に札幌で行われたクラスメソッドさん主催のイベント re:Growth 2015 にて、[soracom-sdk-go](https://github.com/soracom/soracom-sdk-go) のベータ版を Github 上で公開して開発中であることを発表させて頂きました。

[soracom-sdk-go](https://github.com/soracom/soracom-sdk-go) は鋭意開発中ですが、先日発表されたばかりのメタデータサービスなどにも早速対応しました！

ここでは soracom-sdk-go の簡単な使い方を紹介したいと思います。


# soracom-sdk-go の基本的な使い方

まず、なにはともあれ `github.com/soracom/soracom-sdk-go` を `go get`して `import` します。

認証を行って API を呼び出すには APIClient を使用します。（通常はこちら）

SIM を搭載しているデバイスなどからメタデータサービスを呼び出すには MetadataClient を使用します。

## APIClient

APIClient は以下のようにしてインスタンスを生成し、それを使いまわします。

```go
ac := soracom.NewAPIClient(nil)
```

引数として APIClient の生成オプションを指定することも可能ですが、主にテスト用なので一般の方は単に nil を指定してください。

APIClient のインスタンスができたら、まずは Auth() を呼び出して認証を行います。
この Auth() は、一度呼び出したら API トークンの有効期限が切れるまでは呼び出す必要がありません。

```go
err := ac.Auth("me@example.com", "SuperSecretP@ssw0rd")
```

Auth() の引数にはメールアドレスとパスワードを指定します。
認証に失敗すると err が nil 以外の値になります。

ここまでで準備完了です。
あとは好きな API を呼び出しましょう。


## MetadataClient

MetadataClient のほうは準備がより簡単です。

```go
mc := soracom.NewMetadataClient(nil)
```

こちらも APIClient と同様に、引数として生成オプションを指定することも可能ですが主にテスト用なので一般の方は単に nil を指定してください。

メタデータサービスの利用には認証は不要です。
ただし SORACOM Air の SIM を搭載したデバイス等からしか実行できません。


# APIClient を用いて呼び出す API

SORACOM API と APIClient の関数の対応は以下のとおりです。

## Subscriber 関連の API

| Method / Path | 関数名 |
|:------|:-------|
| GET /v1/subscribers | ListSubscribers() |
| POST /v1/subscribers/{imsi}/register | RegisterSubscriber() |
| GET /v1/subscribers/{imsi} | GetSubscriber() |
| POST /v1/subscribers/{imsi}/update_speed_class | UpdateSubscriberSpeedClass() |
| POST /v1/subscribers/{imsi}/activate | ActivateSubscriber() |
| POST /v1/subscribers/{imsi}/deactivate | DeactivateSubscriber() |
| POST /v1/subscribers/{imsi}/terminate | TerminateSubscriber() |
| POST /v1/subscribers/{imsi}/enable_termination | EnableSubscriberTermination() |
| POST /v1/subscribers/{imsi}/disable_termination | DisableSubscriberTermination() |
| POST /v1/subscribers/{imsi}/set_expiry_time | UpdateSubscriberExpiryTime() |
| POST /v1/subscribers/{imsi}/unset_expiry_time | DeleteSubscriberExpiryTime() |
| POST /v1/subscribers/{imsi}/set_group | SetSubscriberGroup() |
| POST /v1/subscribers/{imsi}/unset_group | UnsetSubscriberGroup() |
| PUT /v1/subscribers/{imsi}/tags | PutSubscriberTags() |
| DELETE /v1/subscribers/{imsi}/tags/{tag_name} | DeleteSubscriberTag() |

ここでは例として ListSubscribers() を呼び出してみましょう。

```go
subscribers, _, err := ac.ListSubscribers(nil)
```

ListSubscribers() の引数には検索オプションなどを指定することができます。
nil を指定すると、保有するすべての Subscriber (SIM) の情報を取得します。

ListSubscribers() は 3 つの値を返します。
最初は Subscriber のスライスです。検索オプションを指定した場合は条件に適合した Subscriber のみ返されます。
2番めは LastEvaluatedKeys で、これは Subscriber の数が多くてページネーションなどをするときに使います。オプションに Limit を指定すると一度に返却される Subscriber の数が制限され、ページネーションすることができます。ここで返ってきた値をオプションの LastEvaluatedKey に指定します。
3つめの戻り値は error です。

## Stats 関連の API

| Method / Path | 関数名 |
|:------|:-------|
| GET /stats/air/subscribers/{imsi} | GetAirStats() |
| GET /stats/beam/subscribers/{imsi} | GetBeamStats() |
| POST /stats/air/operators/{operator_id}/export | ExportAirStats() |
| POST /stats/beam/operators/{operator_id}/export | ExportBeamStats() |

先ほどの ListSubscribers() で取得した Subscriber のうち先頭のもの (subscribers[0]) に対して、Air の stats（統計情報）を取得してみましょう。

```go
now := time.Now()
from := now.AddDate(0, 0, -1)
airStats, err := ac.GetAirStats(subscribers[0].Imsi, from, now, soracom.StatsPeriodMinutes)
```

このようなコードで、1日前から現時点までの分精度（実際には約5分単位）の統計情報を取得できます。


## Group 関連の API

| Method / Path | 関数名 |
|:------|:-------|
| GET /groups | ListGroups() |
| POST /groups | CreateGroup()<br />CreateGroupWithName() |
| DELETE /groups/{group_id} | DeleteGroup() |
| GET /groups/{group_id} | GetGroup() |
| GET /groups/{group_id}/subscribers | ListSubscribersInGroup() |
| PUT /groups/{group_id}/configuration/{namespace} | UpdateGroupConfigurations()<br />UpdateAirConfig()<br />UpdateBeamTCPConfig() |
| DELETE /groups/{group_id}/configuration/{namespace}/{name} | DeleteGroupConfiguration() |
| PUT /groups/{group_id}/tags | UpdateGroupTags() |
| DELETE /groups/{group_id}/tags/{tag_name} | DeleteGroupTag() |

`POST /groups` に対応する関数が `CreateGroup()` と `CreateGroupWithName()` の 2 つ用意されています。
また、`PUT /groups/{group_id}/configuration/{namespace}` に対応する関数も3つ用意されています。

いずれも、最初の関数が汎用的なもので、残りは特定の状況でより簡単に使えるようにという意図で用意されている関数です。

たとえば `CreateGroup()` と `CreateGroupWithName()` を見てみましょう。

`POST /groups` API としてはグループ作成時にいくつでもタグを指定できます。
とはいえ、グループを作成するときにいきなりたくさんのタグを指定することはあまりないかもしれません。
また、グループの作成時には最低でも名前くらいは指定すると思いますがグループの名前は実はタグで実現されています。（キー名："name"）

ということで、もっともありえそうな「名前付きのグループを作成する」という処理に特化したものが CreateGroupWithName() であり、汎用バージョンの関数である CreateGroup() でも同じことはできますが以下のように CreateGroupWithName() のほうが少し簡単になります。

```go
// CreateGroupWithName()
g1, err := ac.CreateGroupWithName("group name")

// CreateGroup()
g2, err := ac.CreateGroup(soracom.Tags{"name": "group name"})
```

## EventHandler 関連の API

| Method / Path | 関数名 |
|:------|:-------|
| GET /event_handlers | ListEventHandlers() |
| POST /event_handlers | CreateEventHandler() |
| GET /event_handlers/subscribers/{imsi} | ListEventHandlersForSubscriber() |
| DELETE /event_handlers/{handler_id} | DeleteEventHandler() |
| GET /event_handlers/{handler_id} | GetEventHandler() |
| PUT /event_handlers/{handler_id} | PutEventHandler() |

ここでは例としてやはり先ほどの ListSubscribers() で取得した Subscriber のうち先頭のもの (subscribers[0]) に対して、EventHandler を作成してみましょう。

```go
imsi := subscribers[0].Imsi
o := &soracom.CreateEventHandlerOptions{
    TargetImsi:  &imsi,
    Status:      "active",
    Name:        "Test Event handler Name",
    Description: "Test Event Handler Description",
    RuleConfig: soracom.RuleConfig{
        Type: soracom.EventHandlerRuleTypeDailyTraffic,
        Properties: soracom.Properties{
            "inactiveTimeoutDateConst":  "BEGINNING_OF_NEXT_MONTH",
            "limitTotalTrafficMegaByte": "1000",
        },
    },
    ActionConfigList: []soracom.ActionConfig{
        soracom.ActionConfig{
            Type: soracom.EventHandlerActionTypeChangeSpeedClass,
            Properties: soracom.Properties{
                "speedClass":             "s1.minimum",
                "executionDateTimeConst": "IMMEDIATELY",
            },
        },
    },
}
err = ac.CreateEventHandler(o)
```

## Operator 関連の API

| Method / Path | 関数名 |
|:------|:-------|
| POST /operators/{operator_id}/token | GenerateAPIToken() |
| POST /operators/{operator_id}/password | UpdatePassword() |
| POST /operators/{operator_id}/support/token | GetSupportToken() |
| POST /operators | CreateOperator() |
| POST /operators/verify | VerifyOperator() |
| GET /operators/{operator_id} | GetOperator() |

Operator 関連の API は GenerateAPIToken() 以外は滅多なことでは使わないと思います。

GenerateAPIToken() は新しい API Token を取得します。APIClient は最初の Auth() で取得した API Token を用いて API の呼び出しを行っていますが、その API Token には有効期限があります。その API Token の有効期限内に、GenerateAPIToken() を呼び出して得られた新しい API Token を APIClient にセットすることで、最初の API Token の有効期限を超えて処理を継続することができます。（再度メールアドレスとパスワードで Auth() する必要を減らすことができます）

```go
newToken, err := ac.GenerateToken(0)
```

引数は有効期限の秒数で、0 を指定するとデフォルト値（24時間）が使われます。
最大値は48時間（48 * 60 * 60）で、負の値は指定できません。（指定しても API がエラーを返します）


# MetadataClient を用いて呼び出す API

メタデータ API (http://metadata.soracom.io/v1/) と MetadataClient の関数との関係は以下のとおりです。

| Method / Path | 関数名 |
|:------|:-------|
| GET /v1/subscriber | GetSubscriber() |
| POST /v1/subscriber/update_speed_class | UpdateSpeedClass() |
| POST /v1/subscriber/enable_termination | EnableTermination() |
| POST /v1/subscriber/disable_termination | DisableTermination() |
| POST /v1/subscriber/set_expiry_time | UpdateExpiryTime() |
| POST /v1/subscriber/unset_expiry_time | DeleteExpiryTime() |
| POST /v1/subscriber/set_group | SetGroup() |
| POST /v1/subscriber/unset_group | UnsetGroup() |
| PUT /v1/subscriber/tags | PutTags() |
| DELETE /v1/subscriber/tags/{tag_name} | DeleteTag() |
| GET /v1/userdata | GetUserdata() |

MetadataClient を用いて、認証なしで呼び出す API を試してみましょう。

```go
mc := soracom.NewMetadataClient(nil)
sub, err := mc.GetSubscriber()
```

クライアントを生成してすぐに（認証不要で）自分自身の Subscriber の情報が取得できます。

SORACOM Air で通信しているデバイス上で実行するか、SORACOM Air で通信しているデバイス経由で（テザリングなどで）実行してください。

Deactivate() / Terminate() は危険なので関数を用意していません。
（自分で自分の通信を止めたり解約してしまうとそれ以降通信できなくなり、自分で自分の状態を元に戻したりできなくなるため）
どうしても自分で自分を止めたり解約したいという方がいれば実装するかもしれません。。。

また、Deactivate() できないので Activate() する必要もないため用意していません。


# まとめ

いかがだったでしょうか？
駆け足でしたが soracom-sdk-go の簡単なご紹介をしました。

Go の SDK なので、これを使って書かれたアプリはサーバーサイドではもちろんのこと、Linux/Windows/Mac 問わずクライアント PC 上でもバイナリ一つで動作しますし、ARM Linux 向けにビルドすれば Android 上でも動作します。

soracom-sdk-go は外部コンポーネントに依存していないのでビルドも簡単です。

ぜひいろいろ試してみてください。