ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

Jamstackで動的サイトを作ってみる

こんにちは、サーバーサイドエンジニアの柿本です。

みなさんは Jamstack をご存知ですか??!!

先日知人が Jamstack について熱く語ってくれまして、私は「ナニソレオイシイノ?」と思って聞いていたのですが、彼があまりに熱く語るので「サゾカシオイシイニチガイナイ!」ということでいろいろ調べてみました。

「 SPA と SSR のいいとこ取り」や「動的サイトと静的サイトのいいとこ取り」といった魅力的な言葉が並ぶのですが、つまるところ、

サイトの中の静的な箇所は完全に静的ファイル化しておいて、動的な部分はAPIを叩いてよろしくやろうぜ!

ということのようです。

早速入門してみようとしたところ、 Jamstack の入門記事は主に ヘッドレス CMS や SSG (静的サイトジェネレータ)との繋ぎ込みなど 静的ファイル化部分 に主眼が置かれておりました。
動的によろしくやる方法を知りたかったので、自分でやってみることにします。

TL;DR

1クリックで Netlify にデプロイすることができます!

Deploy to Netlify

事前準備として FaunaDB のセットアップと Server secret の取得 が必要です。

デプロイ完了後に Netlify Identity の設定 が必要です。

目次

技術スタック

  • 静的サイトジェネレータ: Next.js
  • ソースコード管理: GitHub
  • ホスティング・CDN: Netlify
  • データベース: FaunaDB
  • ヘッドレスCMS: 利用なし(*動的部分をターゲットとしてるので使いません)

Jamstack の構成としては標準っぽい物を選定しました。

構想

動的サイトといえば ユーザーログインデータ保存が基本となるので、 『ユーザー登録制の掲示板サイトもどき』 を作ってみます。

ユーザーストーリー

  • ユーザーがログインできる
  • ユーザーが自分の名前でメッセージを投稿できる
  • メッセージ一覧を誰でもみれる

画面構成

/ フロントページ(メッセージ一覧)
└/ メッセージ投稿画面

非常にシンプルですね!

最終形態

f:id:unifa_tech:20200918003519g:plain
2人のユーザーによるログインから記事投稿まで

最終成果物

GitHub に公開 しました!
開発手順の詳細も README に記載してあるので、参考にしてみてください。

github.com

開発の流れ

  1. Next.js のセットアップ
  2. GitHub に push
  3. Netlify にデプロイ
  4. Netlify Identity の設定
  5. フロントコーディング
  6. FaunaDB の設定
  7. Netlify Functions コーディング

実装のポイント

フロントエンド

フロントエンドを構成するソースファイルは以下です。

dynamic-jamstack (App root)
├── pages
│   ├── index.js           // ホーム(メッセージ一覧)画面
│   └── new_message.js     // メッセージ投稿画面
├── components
│   ├── Message.js         // 1つのメッセージ表示
│   ├── MessageList.js     // メッセージを一覧で表示&データ取得処理
│   └── MessageForm.js     // メッセージ入力フォーム&データ送信
└── utils
    └── netlifyAuth.js     // Netlify Identityの認証処理

/pages/ 配下は Next.js により静的化されるページです。
デプロイ時に静的化されるため、ここに動的な処理(データ取得&描画)を記述しても想定どおりに動きません。

/components/ 配下は React コンポーネントです。
動的な処理をここに記述し、 pages のファイルで読み込むことで動的な挙動を実現できます。

Netlify Functions (API)

API のロジックは以下で構成されます。

dynamic-jamstack (App root)
└── functions
    ├── messages-create.js       // メッセージ保存
    └── messages-read-all.js     // メッセージ一覧取得

Netlify Functions の中身は AWS Lambda なので、書き方は同じです。

/messages-create はログイン済みのユーザーのみに制限する必要があり、クライアントと Functions のそれぞれで以下の対応が必要です。

クライアント - Header に Netlify Identity Widget から取得した token をセットします。

// components/MessageForm.js

  generateHeaders() {
    const headers = { "Content-Type": "application/json" }
    if (netlifyIdentity.currentUser()) {
      return netlifyIdentity.currentUser().jwt().then((token) => {
        return { ...headers, Authorization: `Bearer ${token}` }
      })
    }
    return Promise.resolve(headers)
  }

Functions - context.clientContext.user でユーザー情報を取得することができます。

// functions/messages-create.js

  const user = context.clientContext && context.clientContext.user;
  if (!user) {
    return callback(null, {
      statusCode: 401,
      body: JSON.stringify({ "error": "You must be signed in to call this function" })
    })
  }

考察

今回は "Jamstack構成で動的な処理を実装する" を目的として Next.js をベースに

  • 動的な処理を React コンポーネントで記述
  • 静的ファイル化される箇所からコンポーネントを呼び出し

として実現しました。

わかったこと

  • Jamstack 構成で動的サイトは実現可能
  • ユーザー認証は Netlify Identity で実現可能
  • ローカル(静的ファイル内)でも API 内でもユーザー情報を取得可能
  • データ保存はサーバーレスのデータストアにより実現可能

課題

1) サイトによる向き不向きがありそう

いわゆる SNS のような動的な処理がメインのサイトの場合、静的ファイル化できる部分が限られるためあまり恩恵を受けられないかもしれません。

メリットを最大化するには静的化する箇所を適切に切り出す必要がありそうです。

例えば弊社サービスのルクミーフォトの販売画面(購入する写真を保護者が選択する画面)などは、販売写真の設定がされたタイミングで静的ファイル化すればハイパフォーマンスのメリットがありそうです。

そのためには incremental build が適切に実行されるようにする、などの複雑な設定も必要になるかと思います。

2) pages 配下は静的ファイル化される、と意識してコーディングする必要がある

Next.js の仕組み上当たり前のことなのですが、ローカルの開発サーバー上でホットビルドをしているとついつい忘れてしまいます。

これは慣れの問題だとは思いますが、 Netlify にデプロイしてから「あれれ?」となることが何度かありました。(笑)

まとめ

Jamstack で動的サイトを作ることが可能なことは分かりましたが、感想としては「ちょっと無理やり感が否めない」といったところです。

とはいえ Netlify などの CDN 経由で静的ファイルを配信できることのメリットは大きいので、サイトの向き不向きは選びますが、活用していければ良いなと思いました。


ユニファでは保育をハックするエンジニアを募集中です。 一緒にスマート保育園を実現しましょう!

www.wantedly.com

Appendix

FaunaDBの設定

https://fauna.com/ からアクセスし、 Signup します。 Netlify のアカウントでも Signup できます。

任意の名前で Database を作成し、以下を設定します。

  • Collection - Name: messages
  • Index - Name: all_messages

SECURITY メニューから key を作成し、 Server secret を取得します。
Server secret は2度と表示されないので、無くさないように保管します。

Netlify Identityの設定

Netlify の dashboard の Site 一覧から対象の Site を選択します。

Identity > Enable Identity の順にクリックしてアドオンを有効化します。

Setting and usage > Registration > External providers で追加したい認証プロバイダーを選択します。

f:id:unifa_tech:20200918025325p:plain
Netlify Identity の External providers

TensorFlow Profilerを用いた学習時間の短縮

こんにちは。R&D改めデータエンジニアリングチームの宮崎です。 普段はTensorFlowを使って、Deep Learningを学習させて画像認識モデルを作ったりしています。

Deep Learningの悩みの一つとして、学習時間が数時間から数日に及び、非常に長いという点が挙げられます。 作業効率やマシンの費用を考えると、少しでも学習時間を短くしたいです。 そこで今回はTensorFlow Profilerを用いてDeep Learningの学習時間を短縮してみたいと思います。

TensorFlow Profilerとは

TensorFlow ProfilerはTensorFlowの計算時に、処理毎の所要時間などパフォーマンス情報を収集し、TensorBoard上で可視化してくれるツールです。 TensorFlow v2.2.0やv2.3.0で機能が強化され、より詳細な分析ができるようになっています。

続きを読む

コンテキストスイッチから思ったこと

こんにちは、プロダクトエンジニアリング部のちょうです。最近ミーティングでコンテキストスイッチというワードが出て、これについて思うことがあり、すこし話したいと思います。

ここのコンテキストスイッチはプログラムのコンテキストスイッチではなく、毎日の仕事や勉強の中、あることをやって、その後また別のことをやる、つまり仕事の切り替えということです。仕事の切り替えがいいとか悪いのかというと、はっきり言えません。仕事の切り替えができないとそもそも仕事ができないというイメージになり、ものすごく切り替えると効率が落ちるに違いありません。

開発メンバーから見て、ミーティングが多くなると、丸一時間、丸二時間の開発時間が取れなくて、「あれ、ミーティング前に俺何をやったっけ」ということもあり、進捗があまり進まないこともあります。とはいえ、ミーティングの時間はみんな空いている(ように見える)時間帯から決めたから、簡単に変えられないです。一つ解決策はあまり要らないミーティングをキャンセルし、任意参加なら時間があるかどうかで決めるのです。残りは自分で仕事をどこまで効率化できるのか。

一番簡単なのか、予定してた時間内で完了するのです。つまりコンテキストスイッチがないということです。仕事を30分内、1時間内で完了できる細かいタスクに分けるのも効果的です。

次は、コンテキストスイッチの問題というと、前回仕事の内容はすぐに思い出せないということです。プログラムのコンテキストスイッチでしたら、切り替える前にいまやっている仕事の内容をどこかに保存します。そのように、次の仕事に入る前に、現在の仕事を保存します。具体的には

  • Macbookでdesktopを活用して、仕事AのDesktopと仕事BのDesktopを分けるようにしましょう。
  • IDEにはチケット単位やブランチ単位で開いたファイルなどを覚えることができます。またはチケットに何をやっているかをコメントするのも良いでしょう。
  • もっと一般的なことはメモなどに書いても問題ないでしょう。
  • 特別に、現在やっている仕事まだ終わってないという気持ちでブラウザのタブを開きすぎるのはよくあると思いますが、あえてOneTabでタブをまとめたり、タブのリンクをメモなど(つまり現在の仕事と関係ないタブをすべて閉じる)に保存するのをおすすめです。あとでタブを探すのが大変のと複数の仕事のタブが混ぜるとお互い邪魔になるからです。

もう一つ効率的に仕事する方法は同じ種類の仕事を連続やるのです。同じ種類だとコンテキストが似ていて、スイッチしても影響は少ないです。具体的にはパソコンの前にやる仕事をまとめやったり、同じプロジェクトの仕事を連続やったりするのです。

いかがでしょうか。すこし自分の仕事の効率どうやって上げるのを分かるようになりましたか。最後に、本記事はいくつの方法を紹介したが、「ミーティングの合間」に頑張るより、連続で影響されない時間があれば一番いいということを忘れないでください。

Amazon ECS+FargateでRailsを動かす際の最適なパラメーターを考えてみる

こんにちは、最近野菜が高いのでもやしばかり食べているWebエンジニアの本間です。 そろそろレタスが食べたい...。

さて、ここ1、2年、ユニファではAmazon ECS+AWS Fargateを使用して、Railsアプリケーションを本番運用することが増えてきました。 stagingでテストしたDockerイメージがそのまま本番で使えて安心だったり、オートスケールが簡単だったりとメリットが多く、大変便利だと感じています。

ただ、そのような環境を構築する中で、vCPU数やメモリ量、およびPumaの並行性に関するパラメーターをどうしようか毎回悩んでいたため、この辺で自分の中で整理しておこうと思います。

続きを読む

マルチAWSアカウント環境での構築をTerraformで行う際に待機する

おはこんばんちは

ユニファのインフラみてます、すずきです。

8月に入ってから猛暑と湿度で部屋から一歩も出たくない日々が続きますね。 涼しくなれ!

さて、Terraformでステータスが更新されるまで情報が取得できない、変更できないリソースなどは、Create後すぐに別のリソースで呼び出そうとするとエラーになることがあります。 ちょっと違うけど、ACMって検証が終わるまでELBやCloudFrontで呼び出せないので Resource: aws_acm_certificate_validation を利用して待機しますよね。

そういったことが起こりうるリソースで同じことをやってみようというものです。

続きを読む

緊急事態宣言下でのシステム運用振り返り

みなさんこんにちは。

ユニファでサーバーサイドエンジニアをしております田渕です。

新型コロナウイルスによる突然の生活の激変が起こってから、早くも半年近くが過ぎようとしています。 (居住している国、地域などにより、体感している期間は多少異なると思いますが。。。) 弊社でもこれまでに何度か、新型コロナウイルスにまつわる投稿をこのブログの中でしてきています。 経験したことのない状況の中、色々と試行錯誤をしながら進めてきたわけですが、今回は主としてここまでの「システム運用」について振り返ってみたいと思います。

前提

これを読んでくださっている方はおおよそご存知かと思いますが、ユニファは保育園、幼稚園、こども園様向けのプロダクト/サービスを提供しています。 したがって弊社では、新型コロナウイルスにおける緊急事態宣言のもとにおいても、平時と変わらない(あるいはそれ以上の)レベルでの安定したシステム稼働が必要でした。

どんな状況だったのか

勤務体制

何度かこちらのブログでも紹介している通り、ユニファのエンジニアは元々リモートワークが可能な勤務形態でした。 これまではリモートワークと言えばエンジニアがメインでしたが、緊急事態宣言に伴い、全社的に可能な限りリモートワークを行う体制に移行していました。

緊急事態宣言下における当社の業務に関するお知らせ

tech.unifa-e.com

エンジニアのリモートワーク状況については、下記の記事を参考にしてください。

tech.unifa-e.com

システムの利用状況

以前、弊社エンジニアの島田が、緊急事態宣言下での保育施設利用状況について、本ブログにてまとめてくれたことがありました。

tech.unifa-e.com

このブログの中でも書かれている通り、3月~5月は園児さんの欠席率が大きく上がり、保育園の稼働率も例年の同時期と比べると大きく下がっていました。

何が難しかったのか

ここまで聞くと、システム負荷が低いだけだし、そんなに問題はないのでは、……とお考えになるかと思います。 が、実は裏側では、日々バタバタしていました。

難しくしていた原因その1:いつもと違う利用のされ方

平常時と異なる状況なので、いつもとは少々異なる負荷の傾向が現れていました。 具体的な例では、先に紹介した保育施設の利用状況のデータをまとめたブログ中でもご紹介した「欠席コメント欄への記入率違い」などが挙げられます。

f:id:unifa_tech:20200603160824p:plain
[再掲]緊急事態宣言前後でのコメント記入率

緊急事態宣言が発令された2020年4月7日以降で、明らかに利用の仕方が変わっていることが分かると思います。 この様な事例が、幾つか見受けられました。

システム保守をしているサーバーサイドエンジニアならば多くの方がシステムの利用状況、負荷状況を日常的に気にされていることと思います。 通常、システム利用の予測というのは、これまでの利用データ、負荷状況、利用者数の増加予測などを元として行っていきます。 しかしながら、新型コロナウイルスに対しての様々な新しい対応が日々、各所で行われる中では、これまでの利用データがあまり参考にならず、かつ今後の利用者数の予測を行うことは非常に難しいことでした。 基本的にシステムというのは、ある一定の想定条件下で正常に動くように各所のパラメータが調整されているので、その条件を外れれば正常に動かない箇所も出てきます。 事前に考えられる事態に対して対策はしていましたが、やはり事前には予測していなかった状況に直面しました。 それらのうちの幾つかの要素を原因としたトラブルに見舞われる中、地道にデータを分析し、暫定対応、恒久対応を行っていくことで事態の収束を行いました。

難しくしていた原因その2:全社的なリモートワーク

エンジニアのリモートワークはこれまでも行って来ていましたが、全社的に大多数の社員がリモートワークになるという状況は初めてのことでした。 どの会社でも同じだと思いますが、トラブルの発生の折には社内の関連部署への連絡が必須となります。 平時であれば誰かしら出社していたので、速報を口頭で伝えるということもしていたのですが、全員がリモートなのでSlackでの連絡が全てのきっかけになります。 元々エンジニアにリモート勤務が認められていた環境でしたので、Slackでの各所との話し合いは定着していたのですが、「口頭で速報を伝える」という手段を封じられるという状況はありませんでした。 もちろん、一番初めのきっかけ以降はリモートの会議体制に移行し、話し合いを行えますが、第一報はSlackです。 このことは、意外に緊急時に於いて、情報の速報性を妨げました。 というのも基本的にSlackは送ったからと言って必ずしもその場で読んでもらえるものでもないからです。

対策を考えてみる

緊急事態宣言が今後再び発令されるかは分かりませんが、少なくとも暫くの間はリモートでの勤務を継続することにはなるでしょう。 となれば、上記の困ったことを放っておく訳にはいきません。

いつもと違う利用状況に対して

必要となるのは、下記の要素だと考えています。

  • 問題が発生した際の検知の早さ
  • 影響の最小化に向けた取り組み
  • 適切な暫定対応を行うための早期の決断

このうち、「問題が発生した際の検知の早さ」に関しては、ログやシステム監視の検知条件の見直しなどを実施し、平時と異なる利用があった場合に検知が可能となるように改善を行いました。

「影響の最小化に向けた取り組み」については今回利用した暫定対策の手順等をまとめ、次回の類似事象発生時には即座に適用できるようにしました。 また、問題の原因分析、システムの状況がより分かりやすくなるように、検知ツール類の設定の見直しも行っています。 が、これに関しては実際は発生した事象ごとに必要な情報も異なってくるため、実際のところは発生ベースでの対応となってしまいます。

残る「 適切な暫定対応を行うための早期の決断」については、次項で詳細を記載する体制の整備の中で承認ルートの見直しを行っています。

リモート主体勤務時のトラブル対応体制の整備

トラブルが発生した際、「どこのチャンネルで」「どうやって」一報を流すのかについて、改めて整備と認識合わせを行いました。 これまでも基本的に一報を流すことはSlack上で行ってきましたが、自身のチーム内や直接的な関係者しかいないチャンネルを利用していることもあるなど、その方法が統一されていませんでした。 今回のことを機に

  • 一報を出す場所、メンション先、報告内容
  • システム利用者への連絡方法
  • トラブルによる緊急リリースが必要となった場合の承認ルート

の再整備を実施しています。

まとめ

今回は、今年2月以降で経験したことについて、備忘の意味も込めて記載してみました。 全く整備や準備が出来ていなかったという訳ではなかったのですが、非常時に於いてはほんの少し詰めの足りなかった部分が綻びとなり、顕著に形となって現れてくるものなのだなと感じています。 当たり前のことしか書かれていないのですが、当たり前のことが出来てないと非常時に困るんだ!ということを痛感した期間でもありました。 幸い、「もっとここが出来ていたらな。」という内容なので、今後も続くこの状況でもより安定してサービス提供を行えるように試行錯誤を繰り返して行こうと思っています。

自分たち自身も平常時と環境が異なる中、平常時以上のサービスレベルを保つと言うことはそれなりに苦労もありますが、一方で利用者の方から見て「動いていないと困るサービス」になれているのだ、と言うことが実感できた期間でもありました。 そのことは純粋に、有難いことだなと感じています。

ユニファでは現在も積極的に仲間を募集しています! ご興味のある方は、お気軽にお声がけください。

unifa-e.com

SORACOM API を Sandbox 環境でテストする

皆様こんにちは、ユニファの赤沼です。

弊社では「ルクミー午睡チェック」というプロダクトで SORACOM Air Sim を使用しています。通常はセルラーモデルの iPad mini での通信用途に使っていて、API の活用はまだまだ十分にはできていないのですが、最近 Ruby のスクリプトから API を利用する機会があり、Sandbox 環境でのテストも行ったので書いてみます。

SORACOM のサービスは API が充実

まず改めてのおさらいですが、 SORACOM のサービスは IoT 用途ということもあり、 API が充実しています。サービスの数もかなり増えており、コンソールも使いやすいのですが、API でも大半のことはできるようになっているのではないでしょうか。ドキュメントもしっかり提供されています。

dev.soracom.io

全ての API についてリファレンスも提供されています。リクエスト時のパラメータの内容やレスポンスの各項目についてはもう少し説明があると良いなとは思いますが、実際に認証と各APIの実行までリファレンスページから行うことができるようになっているので、実装に組み込む前に実際のレスポンスなどが確認しやすくなっています。

dev.soracom.io

テスト用の Sandbox 環境

実際に動かして試せる API Reference はとても便利なのですが、一つ気を付けないといけないのは、 API Reference のページから実行した内容は、実際の環境に対して実行されるということです。情報取得系の API であれば問題ないのですが、何か変更を加えたり、Sim の解約等まで行えてしまいますので、軽い気持ちで試したりするとあとで大変なことになります。また、実装に組み込む際にも開発段階ではテストが必要になると思いますが、そういう時のために SORACOM には Sandbox 環境が用意されています。Sandbox 環境で実行した内容は本番環境には影響しませんので、解約処理等の危険な処理も試すことができます。

dev.soracom.io

本番アカウントも必要

Sandbox環境は本番環境とは別環境ではありますが、本番環境にもアカウントを持っている必要はあります。そのため本番環境のコンソールから SAM(SORACOM Access Management)ユーザを作って認証キーを生成しておく必要があります。SAMユーザがいて認証キーさえあれば良いので、権限は何も与えなくてもOKです。間違って本番環境に対して実行してしまっても影響がないように、テスト用に何も権限のない SAMユーザを作っておくのが良いかと思います。

Sandbox 環境での準備: オペレータ作成

Sandbox環境にはオペレータは用意されておらず、またGUIもないので、まずはオペレータを下記APIで作成する必要があります。

sandboxInitializeOperator API https://dev.soracom.io/jp/docs/api_sandbox/#!/Operator/sandboxInitializeOperator

Sandbox専用の API もリファレンスページから実行できますが、今回は Ruby で下記のようなコードを書いて実行しました。 init メソッドの中で実際に API にリクエストを投げています。ちなみに使用するメールアドレスは過去に使ったものと重複するとエラーになりますので、作成時には新たなメールアドレスを使用する必要があります。これを実行すると Operator ID, API Key, API Token が取得できますので、以降の API 実行にはこれらを使用します。

#! /usr/bin/env ruby
require 'httpclient'
require 'json'
require './errors'

class SoracomSandbox
  attr_reader :api_base_url

  def initialize
    @api_base_url = 'https://api-sandbox.soracom.io/v1'
    @client = HTTPClient.new
  end

  def post(url:, body: {} , header: {})
    res = @client.post(url, body: body, header: header)
    check_status(res)

    JSON.parse(res.body)
  end

  def check_status(res)
    return if HTTP::Status::successful? res.status

    res_json = JSON.parse(res.body)
    msg = "CODE: #{res_json['code']} MSG: #{res_json['message']}"
    raise SoracomApiError, msg
  end

  def init(email: nil, password: nil, auth_key_id: nil, auth_key: nil)
    request_body = {
      email:                 email,
      password:              password,
      authKeyId:             auth_key_id,
      authKey:               auth_key,
      registerPaymentMethod: 'true'
    }.to_json
    res_json = post(url: "#{@api_base_url}/sandbox/init", body: request_body,  header: { 'Content-Type' => 'application/json' })

    @operator_id = res_json['operatorId']
    @api_key     = res_json['apiKey']
    @token       = res_json['token']

    @request_header = {
      'Content-Type'      => 'application/json',
      'X-Soracom-API-Key' => @api_key,
      'X-Soracom-Token'   => @token
    }

    return @operator_id, @api_key, @token
  end
end

Sandbox環境での準備: Simデータ作成

続いてテストに使用する Sim のデータを作成します。実際に Sim を契約しなくてもいくらでも Sim を用意できるのが Sandbox 環境の良いところです。 Simデータを用意するには、架空のSimを作成してからそれを登録するという手順を踏みます。架空のSim作成には Sandbox 用の API が用意されています。

Sim作成: sandboxCreateSubscriber https://dev.soracom.io/jp/docs/api_sandbox/#!/Subscriber/sandboxCreateSubscriber

Simの登録は本番環境と同様の API で行います。

Sim登録: registerSubscriber https://dev.soracom.io/jp/docs/api/#!/Subscriber/registerSubscriber

先ほどのクラスに下記のようなメソッドを追加して実行します。

  def create_sims(sim_count)
    sims = []
    sim_count.times do
      res = @client.post("#{@api_base_url}/sandbox/subscribers/create", header: @request_header)
      res_json = JSON.parse(res.body)
      sims << {
        imsi: res_json['imsi'],
        msisdn: res_json['msisdn'],
        registration_secret: res_json['registrationSecret']
      }
    end

    sims.each do |sim|
      request_body = { registrationSecret: sim.fetch(:registration_secret) }.to_json
      post(url: "#{@api_base_url}/subscribers/#{sim.fetch(:imsi)}/register", body: request_body, header: @request_header)
    end
  end

実際にはさらにオペレータ作成とSimデータの作成をラップするコードを用意して、それを毎回 Sandbox 利用時に実行しました。

#! /usr/bin/env ruby
require 'optparse'
require './soracom_sandbox'

opt = OptionParser.new

params = {}

opt.on('-e EMAIL')              {|v| params[:email]       = v }
opt.on('-i AUTH_KEY_ID')        {|v| params[:auth_key_id] = v }
opt.on('-k AUTH_KEY')           {|v| params[:auth_key]    = v }
opt.on('-n NUM_OF_SUBSCRIBERS') {|v| params[:num]         = v.to_i }

opt.parse!(ARGV)

sandbox = SoracomSandbox.new
operator_id, api_key, token = sandbox.init(
  email:       params[:email],
  password:    'superStrongP@ssw0rd',
  auth_key_id: params[:auth_key_id],
  auth_key:    params[:auth_key]
)

sandbox.create_sims(params[:num])

puts "#{operator_id},#{api_key},#{token}"

テストしたい API を実行する

ここまでで Sandbox 環境の準備ができましたので、あとは本番と同様の API を Sandbox 環境向けに実行すればテストをすることができます。 例えば Sim 情報の取得は下記のようなメソッドを書いて実行します。

  def subscribers(status: [], limit: 100)
    parameter = "limit=#{limit}"
    if not status.empty?
      parameter += "&"
      parameter += URI.encode_www_form(status_filter: "#{status.join('|')}")
    end

    get(url: "#{@api_base_url}/subscribers?#{parameter}", header: @request_header)
  end

これで事前準備で登録された Sim の情報が分かりますので、 Activate なども実行してみることができます。

  def activate_subscribers(imsis: [])
    subscribers = []
    imsis.each do |imsi|
      url = "#{@api_base_url}/subscribers/#{imsi}/activate"
      subscribers << post(url: url, header: @request_header)
    end

    subscribers
  end

こちらもラップするコードを書きました。 API_KEY と TOKEN はオペレータ作成時に取得したものを使います。

#! /usr/bin/env ruby
require 'csv'
require 'optparse'
require './soracom'

opt = OptionParser.new

params = {
  api_key:    nil,
  token:      nil,
  file:       nil,
  production: false
}

opt.on('-f SUBSCRIBERS_CSV_FILE') {|v| params[:file] = v }
opt.on('-a API_KEY')   {|v| params[:api_key]    = v }
opt.on('-t TOKEN')     {|v| params[:token]      = v }
opt.on('--production') {|v| params[:production] = true }

opt.parse!(ARGV)

soracom = Soracom.new(production: params[:production])
soracom.set_request_header(api_key: params[:api_key], token: params[:token])

subscribers = CSV.table(params[:file], { converters: nil })
imsis = subscribers[:imsi]

puts soracom.activate_subscribers(imsis: imsis).to_json

activate_subscribers メソッドや subscribers メソッドは前述の SoracomSandbox とは別の、本番と同様の API をまとめたクラスに実装しましたので参考までに抜粋して載せておきます。

#! /usr/bin/env ruby
require 'httpclient'
require 'json'
require 'uri'
require './errors'

API_BASE_URL         = 'https://api.soracom.io/v1'
API_BASE_URL_SANDBOX = 'https://api-sandbox.soracom.io/v1'

class Soracom
  def initialize(
    production:  false
  )
    @production   = production
    @client       = HTTPClient.new
    @api_base_url = production ? API_BASE_URL : API_BASE_URL_SANDBOX
  end

  def production?
    @production
  end

  def set_request_header(api_key:, token:)
    @request_header = {
      'Content-Type'      => 'application/json',
      'X-Soracom-API-Key' => api_key,
      'X-Soracom-Token'   => token
    }
  end

  def post(url:, body: {} , header: {})
    res = @client.post(url, body: body, header: header)
    check_status(res)

    JSON.parse(res.body)
  end

  def get(url:, header:)
    res = @client.get(url, header: header)
    check_status(res)

    JSON.parse(res.body)
  end

  def check_status(res)
    return if HTTP::Status::successful? res.status

    res_json = JSON.parse(res.body)
    msg = "CODE: #{res_json['code']} MSG: #{res_json['message']}"
    raise SoracomApiError, msg
  end

  def subscribers(status: [], limit: 100)
    parameter = "limit=#{limit}"
    if not status.empty?
      parameter += "&"
      parameter += URI.encode_www_form(status_filter: "#{status.join('|')}")
    end

    get(url: "#{@api_base_url}/subscribers?#{parameter}", header: @request_header)
  end

  def activate_subscribers(imsis: [])
    subscribers = []
    imsis.each do |imsi|
      url = "#{@api_base_url}/subscribers/#{imsi}/activate"
      subscribers << post(url: url, header: @request_header)
    end

    subscribers
  end
end

まとめ

SORACOM のサービスの魅力は単に Sim での通信というだけでなく、関連する様々なサービスや API を活用することで、よりコスト効率を良くしたり、プロダクト提供までの実装コストの削減や運用時の柔軟性をあげることができるというところかと思います。 API を活用していくためにも、 Sandbox 環境でしっかりテストして安全に利用したいものです。

また弊社では複数ポジションを募集中ですので、興味ある方は是非ご連絡ください! unifa-e.com