メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

Vue 2 の EOL まで 2 ヶ月ですが進捗どうですか?~Vue Fes Japan 2023 ゴールドスポンサーとして登壇します~

フロントエンドの小林和弘です。

遂にオフラインでの開催となった Vue Fes Japan 2023 が今月末に迫っています。
2019 年は台風、2020 年、2021 年は新型コロナウィルス感染症による開催見送り、去年は感染予防のためオンライン開催となっていました。
2018 年以来、実に 5 年ぶりのオフラインカンファレンスです(めでたい)🎉🎉🎉

vuefes.jp

メドピアは例年に引き続き Vue Fes Japan 2023 にゴールドスポンサーとして協賛しています。
(去年に引き続き、個人スポンサーもさせてもらいました)

その他にセッションルームネーミングライツスポンサー、スペシャルランチスポンサーとしても協賛しています。
タイムテーブル上の 4 つのトラックのうちのひとつが「メドピアトラック」という名前になっていて、さらにランチタイムでは「メドピアトラック」にてスペシャルランチセッションのお時間をいただいています。 そこでは Vue3 の migration についてのお話をする予定です。

セッションの概要

メドピアではいくつもの医療系サービスを開発・運用しています。そのほとんどのサービスで Vue を使わせてもらっています。

Vue は Single File Component で双方向データバインディングができるため Multi Page Application で部分的にリッチな UI が簡単につくれたり、Nuxt を使って Single Page Application が構築できたりする非常に便利なフレームワークだと思っています。

その Vue ですが Vue2 が 2023 年 12 月 31 日に LTS が終了して EOL を迎えてしまいます。Nuxt2 も同日に EOL になります。
年始にも Vue の開発者の Evan You がブログのポストで EOL についてリマインドのアナウンスを行っていました。今年は Vue2 / Nuxt2 の migration に追われたプロジェクトがいくつもあると思います。

Vue Fes Japan 2023 のランチセッションでは、メドピアで利用している Vue2 / Nuxt2 の migration をどのようにして行っているのか紹介したいと思います。
migration 中のプロジェクト、migration を考えているプロジェクトの一助になれば幸いです。

vuefes.jp

ご興味がある方は、Vue Fes Japan 2023 のランチセッションに足を運んでみてください。

ブース出展について

会場ではブースも出展します。

ブースに足を運んでいただくと、お酒との相性をセルフチェックできる「アルコールパッチテスト」をお試しいただけます。アフターパーティーで飲酒される方はぜひ試してみてください🍻
「アルコールパッチテスト」をお試しいただいて、X(旧 Twitter)で結果投票に参加いただくと大きなオリジナルバッグがもらえます 👜

アルコールパッチテストをするとオリジナルバッグがもらえます

また、メドピアのノベルティグッズも配布する予定です。
LINE スタンプにもなっているメドピア公式キャラクター「メドベア」がプリントされたかわいいノベルティグッズです。ご興味がある方は会場でゲットしてみてください。

メドベアのノベルティグッズと現場猫の VPoT

アフターイベントについて

Vue Fes Japan 2023 の 2 週間後、2023 年 11 月 7 日(火)にメドピア株式会社、MNTSQ 株式会社、及び株式会社 hacomono で Vue Fes Japan 2023 After Meetup を開催します。
Vue.js 日本ユーザーグループ代表の kazupon さんを特別ゲストとして迎え、セッションやパネルディスカッションを行います。

Vue Fes Japan 2023 と同じく、アフターイベントもオフライン開催を予定しています。
会場はメドピアのオフィスで、最寄り駅は東銀座駅になっています。

medpeer.connpass.com

こちらのイベントもご興味がある方はぜひ参加してみてください。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

A/BテストツールとしてCloudWatch Evidentlyを導入してみた

サーバーサイドエンジニアの熊木(@k_kumaki_)です。

先日、私が担当しているヤクメドにA/BテストツールとしてAWSのサービスであるCloudWatch Evidentlyを導入したので、その経緯や内容についてまとめます。

目次

CloudWatch Evidentlyとは

AWS上でA/Bテストやフィーチャーフラグの管理ができるサービスです。 比較的新しいサービスのため、馴染みのない方も多いかもしれません。

aws.amazon.com

導入に至った経緯

私が開発しているサービスであるヤクメドでは、UX向上のためA/Bテストを実施しています。

そのA/Bテストツールとして、Google Optimizeを使用していましたが、この度2023年9月30日をもってサポートが終了することが発表されました。そのため、A/Bテストの代替案を探す必要がありました。

導入理由

一番の理由は低価格であることです。 CloudWatch EvidentlyではEvidentlyイベントとEvidently分析ユニットに基づいて課金が発生する仕組みになっています。 これをもとに料金を試算すると、他のサービスに比べてかなり低額でA/Bテストを実施できるためCloudWatch Evidentlyを採用することとしました。

docs.aws.amazon.com

導入方法

構成図

1. プロジェクトの作成

ここではプロジェクト名と説明を設定するのみです。 また、データストアとしてS3かCloudWatch Logsが選択できるので、今回はS3を選択しました。 発生したイベントをデータストアに保存して、他のサービスと組み合わせることでより柔軟な分析が可能になります。

ヤクメドではTerraformでAWSリソースの管理をしており、以下のコードで作成しました。

resource "aws_evidently_project" "evidently" {
  name        = "${local.prefix}-evidently"
  description = "${local.prefix}-evidently"

  data_delivery {
    s3_destination {
      bucket = aws_s3_bucket.evidently_logs.id
      prefix = "${local.prefix}-evidently"
    }
  }

  tags = {
    Name = "${local.prefix}-evidently"
  }

  depends_on = [aws_s3_bucket_policy.evidently_logs]
}

2. 機能の追加

次に作成したプロジェクトに機能を追加します。 どういった分岐をするか、ユーザートラフィックを何%ずつ割り振るかなどの設定を行います。

3. アプリ側の対応

ヤクメドではバックエンドはRailsで書かれているため、CloudWatch EvidentlyのSDKを導入しました。

docs.aws.amazon.com

どこからでも使用できるA/Bテスト用のモジュールを作成し、以下のようなリクエストをCloudWatch Evidentlyに送信します。

evidently_client.evaluate_feature(
  {
    entity_id: entity_id, # ユーザーを特定する一意のID
    feature: feature, # 設定した機能名
    project: project, # 設定したプロジェクト名
  }
)

レスポンスとして、以下のような形式で返ってきます。 今回は機能でブール値を設定したためvalueとしてbool_value が返却されており、これをもとにアプリ内で分岐をさせることができます。

#<struct Aws::CloudWatchEvidently::Types::EvaluateFeatureResponse
 details="{}",
 reason="DEFAULT",
 value=#<struct Aws::CloudWatchEvidently::Types::VariableValue::BoolValue bool_value=true, double_value=nil, long_value=nil, string_value=nil, unknown=nil>,
 variation="LP">

よかった点

自分で細かな定義が可能

  • アプリ側で設定している情報に対して細かく制御が可能
    • 例えば、特定の地域のユーザーやChromeを使用しているユーザーなど細かく分けられます

参考: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Evidently-segments.html

導入が簡単

  • この記事にも書いた通り、元々AWSを使っているサービスであればかなり簡単に導入することが可能です

終わりに

今回は、シンプルな構成での導入をご紹介しました。

CloudWatch Evidentlyを導入してみてまだ数週間ですが、Google Optimizeと遜色なくA/Bテストを行うことができています。 この記事が、A/Bテストツールでお悩み中の方の参考になれば幸いです。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

Kaigi on Rails 2023に@lni_Tが登壇します & メドピアが協賛します #kaigionrails

皆様こんにちは、メドピアのサーバーサイドエンジニアの草分( @lni_T )です。

この度、2023/10/27(金)-28(土)の2日間で開催される「Kaigi on Rails 2023」に登壇させていただくこととなりました!
タイトルは「Turbolinksアレルギー患者に捧げるTurbo & Stimulusでの時短実装術」となります。

kaigionrails.org

スケジュールは、
Day1 16:50 〜 17:05 / Room B
を予定しています。 ぜひセッションにお越しくださいませ。

9月には大阪ruby会議03松江Ruby会議10 にて登壇させていただきましたが、それに続いて今月は東京での登壇となります。
先月のセッションも聞いていただいた方は、今月もぜひよろしくお願いします!

協賛について

メドピアはGold SponsorsとしてKaigi on Rails 2023に協賛しております!

kaigionrails.org

登壇内容について

セッションでは以下の内容をお伝えします。

皆様、Railsのフロントエンドツール「Hotwire」は使っていますか?
Rails 7からはデフォルトで採用されており、「Turbo」や「Stimulus」といったライブラリが利用できます。

ですが、利用者があまり多くなかった「Turbolinks」のイメージに影響されて、利用を避けている方は居ませんか?

このトークでは、実際のバックエンドリプレイス案件において、
Turbo & Stimulusを採用することで開発工数を大幅に削減できた事例、および実装方法についてご紹介します。

Hotwireは、JavaScriptをあまり書かずにモダンなWebアプリケーションを実現するフレームワークで、JSONではなくHTMLをやりとりすることが特徴です。
RailsのController/Viewのみでは手間がかかる画面の実装において、Hotwireは大きな効力を発揮してくれます。

セッションでは、そんなHotwireの活用例やノウハウを皆様に紹介します。

見どころは?

公式ページにも記載した通り、Hotwireを実際の開発案件での活用事例をお話しします。

背景となったメドピアの事業はこちらです。

medpeer.co.jp

「事業譲受」をおこなったため、システムもそのまま移行したかのように見えますが、実はバックエンド側については、ほぼ全てをRailsアプリでリプレイスしています。

限られた時間でリプレイスを進めるにあたり、画面実装だけに時間を割いている余裕はありません。
そんな中でも、Hotwireの「少ない労力でフロントエンドが実装できる」利点を活かすことで、無事にサービスをリリースすることができました。

今回のセッションでは、そんな【鉄火場での活用事例】をお伝えできればと考えています。

おわりに

Kaigi on Railsとしては初のオフライン/オンライン同時開催とのことで、どのような会になるのか非常に楽しみですね。
チケット購入がまだの方はこちらよりお求めください。 kaigionrails.doorkeeper.jp

それでは皆様、お会いできることを楽しみにしております!


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

【Nuxt 3移行】ユニットテストをNuxt 2から移行し、実行速度が4倍速くなった話

こんにちは。フロントエンドエンジニアの相澤 ( @ttt3pu ) です。

みなさま、Nuxt 2 から Nuxt 3 へのアップグレードは順調でしょうか。

メドピアでは、2023年末のVue 2のEOLへ向けて、
各プロダクトで積極的にNuxt 3へのアップグレードを進めています。

現在私の担当しているプロダクトでは、マイグレーション作業自体はほぼ完了しており、
残すはQAテストなどを行うのみと言う段階で、本番リリースまであと一歩というところまで進んでおります! 🎉

マイグレーションの事例も徐々に増え始めてきて、Nuxt 3のリリース当初よりも段々と移行はしやすくなってきましたが、
個人的に結構大変だったのがユニットテストのマイグレーション作業でした。

当記事では、マイグレーションに当たっての Tips と、ついでに Vitest を導入したことにより、
実行速度が 約 10分 -> 約2分30秒 ( 約 4倍 ! ) まで跳ね上がった話をご紹介します。

目次

対象プロジェクトのユニットテスト周りのライブラリ構成

まず、今回マイグレーション作業を行なったプロジェクトは、以下のようなライブラリ構成で構築されていました。

移行後は、以下のようなライブラリ構成となりました。

Jest は廃止し、 Vitest へ移行しています。

vue-test-utilsについては、Vue 3対応しているのがv2以降となるため、バージョンが上がっています。

今回肝となったのが「nuxt-vitest」の導入でした。
Nuxt 3 + Vitest の実行環境を用意してくれるライブラリです。
詳しい内容については後述させていただきます。

移行完了までのステップ

プロダクトの実装状況によって多少順番が変わってくるとは思いますが、
テストが全て通るまでに、以下のステップで作業を行いました。

  1. Jest から Vitest へ移行
  2. nuxt-vitestを導入
  3. vue-test-utils の v1 から v2 へのマイグレーション作業を実施
  4. 落ちているテストごとの個別修正
  5. Vitest の設定を調整し速度改善

ここから先は、それぞれのステップで行なった作業について、詳しくご紹介させていただきます。

Jest から Vitest への移行

一番最初のステップとして、nuxt-vitestを導入する前準備として、JestからVitestへの移行を行いました。

Jestとは互換性が高いので、導入はほとんど苦になりませんでした。

ざっくりとまとめると、以下の作業を行うだけで移行が完了しました。

  • jest.config.ts の各設定を vitest.cofig.ts へ移行する
  • jest メソッドを vi へ置換する
    • 例: jest.fn -> vi.fnjest.spyOn-> vi.spyOn など
  • npm scripts等にCLIの記述があれば変更する

詳細については、公式の Migration Guide をご参照ください!

vitest.dev

nuxt-vitest の導入

続いて nuxt-vitest を導入します。

nuxt-vitest は、Nuxt の開発コアメンバーである Daniel さんによって開発されている、Nuxt 3 + Vitest の実行環境を用意してくれるライブラリです。

github.com

導入することより、自分で実装しようとするとかなり複雑になってしまうような設定を、ある程度自動で行なってくれるようになります。
コアメンバーが開発しているということもあり、メンテナンスもNuxt本体のアップグレードに合わせて頻繁に行われているので安心です。

導入方法もとても簡単です。
nuxt-vitest をインストール後、 nuxt.config.tsvitest.config.ts にそれぞれ読み込ませたら完了です!

※ バージョンによって導入方法が変わる可能性があるため、詳細は公式の README をご参照ください。

github.com

// nuxt.config.ts の設定例
export default defineNuxtConfig({
  // ...
  modules: [
    'nuxt-vitest'
  ]
})

// vitest.config.ts の設定例
import { defineVitestConfig } from 'nuxt-vitest/config'

export default defineVitestConfig({
  test: {
    environment: 'nuxt',
  },
});

実はこの時点でこのライブラリの恩恵をかなり受けることができており、
vitest.config.ts 内の記述をかなりスッキリさせることができています。

この時点で nuxt-vitest なしで実行しようとした場合、
useNuxtApp や useRoute 等の、 #importsから使用するメソッド類 の import 周りだったり・・・
composables と component のAuto Import 周りだったり・・・
等の問題が出てきてコケてしまいます。

この問題を手動で直すのはかなり難易度が高いため、自動で補完してくれるのはかなり助かりました。

また、 nuxt-vitest には、環境構築用の module だけでなく、
mockNuxtImport 等のユニットテスト用のヘルパーも用意されています。

これらの詳しい使い方については後述させていただきます。

vue-test-utils のマイグレーション作業を実施

ひとまずこの時点で vitest コマンドが正常に実行できる状態にはなるはずなので、
ここから先はspecファイル内の記述の修正を行なっていきます。

公式で Migration Guide が用意されているため、これに沿って実施を行なっていきます。

test-utils.vuejs.org

変更内容としてはそこまで難しくはないのですが、変更量はどうしても多くなってしまう感じでした。

大きいところだと stubsplugins 等のオプションを global の中に入れる必要が出てきたこと等でしょうか。
これが地味に結構大変でした・・。

// before
const wrapper = mount(Component, {
  stubs: {
    ...
  },
})

// after
const wrapper = mount(Component, {
  global: {
    stubs: {
      ...
    },
  },
})

落ちているテストごとの個別修正

この時点である程度テストは通るようになったと思います。
ここから先はテストごとに個別修正をしていきます。

ここから先はプロダクトによって臨機応変に対応を行う必要がありそうですが、
個人的に大変だった部分をいくつかピックアップして記載させていただきます。

Composition API 内で setData メソッドを使用している箇所の修正

vue-test-utils で用意されている、 data の値を変更するメソッドとして setData というものがあります。

test-utils.vuejs.org

このメソッドは v1 の時はComposition API を使用しているコンポーネントでも動いていてくれたのですが、 v2 からはうまく動作がしなくなってしまいました。

そのため、以下のような形で代用することで対応しました。

// before
const wrapper = mount(Component);
await wrapper.setData({ count: 1 });  
expect(wrapper.html()).toContain('Count: 1')

// after
const wrapper = mount(Component);
wrapper.vm.count = 1;
await flushPromises();
expect(wrapper.html()).toContain('Count: 1')

useRoute などの #imports から使用するメソッドのモック化

色々やり方はあると思いますが、
私たちのプロダクトでは、useRoute のモック化を Nuxt 2 環境では以下のような形で行なっていました。

// hoge.vue
<script>
const useRoute();
...
</script>

// hoge.spec.ts
const wrapper = mount(Component, {
  mocks: {
    $route: {
        path: '/hoge',
    },
  },
}) 
...

Nuxt 3 ではこの方法ではモック化ができなくなるため、修正を行う必要があります。

ここで nuxt-vitest に用意されているヘルパーメソッドが活躍します。
mockNuxtImport を使用することによって、以下のような記述で mock 化を行うことができます。

import { mockNuxtImport } from 'nuxt-vitest/utils'

mockNuxtImport('useRoute', () => {
  return () => {
    return {
      path: '/hoge',
    },
  }
})

ここも nuxt-vitest を使わなかったら更にひと工夫がいるであろう箇所のため、かなりの助かりポイントでした。

useRoute 以外のメソッドに関しても、同様の方法で対応を行うことができます。

Vitest の設定を調整し速度改善

なんとかテストが全て通るようになりましたが、
ここでせっかく Jest から Vitest に移行したのにあまり速度が変わっていないことに気づきます。

詳しく調べてみると --single-thread と言うオプションがあることがわかり、
このオプションを有効にしたところ、
冒頭に記載した通り速度が 10分 -> 2分30秒 ( 約 4倍 ! ) にまで跳ね上がりました!

vitest.dev

# コマンドの実行例
yarn vitest --single-thread --coverage

このオプションは名前の通り、シングルスレッドでテストを実行させるオプションで、
ざっくりとまとめると、

  • 無効にした場合 -> テストごとに別々の環境を作成した上で実行される
  • 有効にした場合 -> 同一の環境でテストが実行されるため、初期化を繰り返すコストを回避できる

という挙動を実現してくれるようです。

今回対応を行なったプロダクトは弊社内では規模が大きめのもので、 テストファイル総数 178件、テスト総数 854件 と実行される数も多いです。
そんな中、しかもcoverage付きでこの速度というのはなかなか感動しました・・・ ✨

ただし、公式の Docs に記載されている通り、実行する環境によっては、このオプションを使用した場合うまくいかない場合もあるようなので、そこに関しては注意が必要そうです。

WARNING

Even though this option will force tests to run one after another, this option is different from Jest's --runInBand. Vitest uses workers not only for running tests in parallel, but also to provide isolation. By disabling this option, your tests will run sequentially, but in the same global context, so you must provide isolation yourself.

This might cause all sorts of issues, if you are relying on global state (frontend frameworks usually do) or your code relies on environment to be defined separately for each test. But can be a speed boost for your tests (up to 3 times faster), that don't necessarily rely on global state or can easily bypass that.

最後に

Nuxt 3 本体のマイグレーションについては事例が徐々に出てきてはいるものの、テスト周りはまだ情報が少なくなかなか大変でした。
この記事がみなさまの Nuxt 3 アップグレードの助けになれば幸いです!


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

Raspberry Pi + ChatGPT で会話ロボットを作った

会話の間が長く、スムーズでなくてすいません💦

長期休みに入る子供対策 R&Dの一環で子供達と会話してくれる AI ロボットを作りました。

github.com

会話の例

日々このような会話が繰り広げられる様になりました。

例1

  • 🤖「こんにちは。今日はどんなサッカー練習をしましたか?」
  • 👦「今日はダイレクトシュートの練習をしました」
  • 🤖「それは良いですね。ダイレクトシュートは難しいですよね。どんなダイレクトシュートの練習ですか?」
  • 👦「インサイドキックです」
  • 🤖「サッカーの試合で使われるキックの7割がインサイドキックと言われていますね。試合でもインサイドキックをしますか?」

例2

  • 🤖「こんにちは。どんなサッカー選手が好きですか?」
  • 👦「ドリブルやパスが上手な選手です。」
  • 🤖「どの選手が好きですか?」
  • 👦「モドリッチです。」

などなど、普段親子での会話では出ない様な回答が引き出せて面白いです。

ロボットには敬語使うんだなというのも興味深いです。

システム概要図

*1

大まかな処理の流れ

  1. モーションセンサー検知
  2. OpenAI に会話開始テキストを送信し、レスポンスを音声変換しスピーカーから再生
  3. 以下繰り返し
    1. ビープ音再生
    2. RPi 録音開始
    3. 子供しゃべる
    4. 録音内容を音声ファイルに保存
    5. 音声ファイルを Cloud Speech to Text API でテキスト変換*2
    6. テキストを OpenAI に送信しレスポンスを音声変換しスピーカーから再生

工夫点

  • 会話のきっかけはロボットから
    • 「話しかけられた」方が子供は嬉しいと思ったので 👦
  • 子供の話すターンの終了は無言3秒経過したら
    • 「最初は録音開始から10秒以内に話す」ルールにしたが、話したいことが言えなかった
    • pyaudio の無言時間計測できる仕様があったのでそれを利用した
  • 子供が話して良いタイミングにはビープ音を鳴らす
    • 話して良い合図がないとロボットとの会話は難しかった
    • ビープ音と一緒に話している間は LED を光らせる様に視覚的にも会話期間を認識させる
  • 会話は返答無視で自動終了
    • ある程度遊んだらロボットの返事待たずどこかへいってしまう為
    • 返答を 5秒無視で会話終了
      • 再度モーションセンサーが検知したら会話開始となる
  • 日本語・英語どちらでも話せる様にした
    • 私個人の英語学習の為に英会話もさせたかった

ChatGPT にお願いする会話の制約

英語・日本語で制約を指定しています。

先の動画では以下のような指示をしています。

ja:
  system_content: >-
    #指示:
    以下の制約の下でチャットしてください。

    #制約条件:
    私は日本のサッカーが大好きな小学生2年生です。
    会話のトピックはどんなサッカー選手が好きか、とか、今日はどんなサッカーの練習をしたとか、をお願いします。
    回答は極力短くお願いします。

使ったもの

  • RPi 4B
    • OS: Raspbian GNU/Linux 10 (buster)
  • OpenAI API
  • Cloud Speech to Text API
  • モーションセンサー
  • スピーカー
  • マイク

諸々購入しましたが、弊社エンジニアは実質 0 円です😄
弊社のテックサポート制度で購入をサポートしている為です 🎉

ブレッドボードの設定

SunFounder で各種モジュールの設定方法があったので参考にしました。

https://docs.sunfounder.com/projects/kepler-kit/en/latest/

難しかったこと

3歳の舌足らずなところはうまく聞き取ってもらえなかったです。 でも会話が継続されているのでこれはこれで良いのかもしれない😅

また、こちらの話終わりを「無言になってから3秒経過したら」としましたが、ノイズを拾ってしまい3秒以上かかることがありました。

マイクの性能にも依存するのかもしれませんが、スマートホームのようなスムーズな会話を実現するには録音ライブラリの微調整と機材の向上は必要かと感じました。*3

もしくは、視覚的にローディング待ち状態がわかる仕掛けがあると良いかなと思いました。

子供達の反応

  • 長男:
    • もっと自分に合ってるサッカーの練習方法を教えて欲しい⚽️
    • これいつまで話せばいいの?
  • 次男:
    • ロボットになりたい🤖

カスタマーからストイックな要求も飛び出し、改良の余地がありました。
会話を続ける限り続けられてしまうので質問攻めの苦痛もあった様です。*4
次男のサイボーグ化は家族会議します。

まとめ

シンプルにモジュールと API を繋ぎ合わせて Raspberry Pi + ChatGPT で会話ロボットを作ってみました。

今後、さらに会話した感が増すように唇が動いたり、表情筋を再現できるような仕組みを作りたいと思っています。(怖い)

以上
参考になれば幸いです。


是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

*1:シーケンス図は https://github.com/kenzo0107/raspi-talk#system-overview

*2:音声のテキスト変換について、julius: 日本語音声を認識できるものの、辞書の作成や各種ライブラリのインストールが必要で手間でした。 pocketsphinx はインストールの難易度が高く、解決しなかったので見送りました。最終的に音声ファイルからテキストに変換する際の精度と導入の簡易さから Cloud Speech to Text を選択しました。他良い選択があれば是非ともご教示ください🙇‍♂️

*3:動画では途中早送りしてます

*4:数ターンで会話終了する仕組みを入れても良さそう

お財布に優しいCI改善小ネタ集

こんにちは。サーバーサイドエンジニアの三村(@t_mimura)です。

主に保険薬局と患者さまを繋ぐ「かかりつけ薬局」化支援アプリ kakariのサーバーサイド開発(Ruby on Rails)を担当しています。

今回はRailsシステムのCI時間をコスト追加なしで半減した話をします。

目次

前提

対象プロジェクト

まず、今回対象となるRailsシステムの規模感について簡単にご紹介します。

kakariは2019年にリリースされ、今現在も活発に開発が進んでいるプロジェクトです。 大まかに以下のような構成となっています。

  • バックエンド Ruby on Rails
    • フロントエンドとモバイルアプリ向けのAPIを提供
    • 一部システムではwebpackを利用しVue.jsが組み込まれているRails Viewも提供
  • フロントエンド Vue.js
    • Rails Viewに組み込まれている
  • モバイルアプリ Kotlin, Swift
    • ※ 今回のスコープ外

今回はこれらのうち「Ruby on Railsとそれに組み込まれているVue.js」を管理しているGit リポジトリのCIを改善した話となります。

$ rails stats
+----------------------+--------+--------+---------+---------+-----+-------+
| Name                 |  Lines |    LOC | Classes | Methods | M/C | LOC/M |
+----------------------+--------+--------+---------+---------+-----+-------+
| Controllers          |  25114 |  18962 |     700 |    2768 |   3 |     4 |
| Helpers              |    336 |    240 |       1 |      42 |  42 |     3 |
| Jobs                 |      6 |      3 |       1 |       0 |   0 |     0 |
| Models               |  68048 |  46214 |    1537 |    5532 |   3 |     6 |
| Mailers              |   1128 |    871 |      60 |     117 |   1 |     5 |
| Views                |    182 |    172 |       0 |       0 |   0 |     0 |
| Libraries            |   1473 |   1140 |      16 |      77 |   4 |    12 |
| Controller specs     |    459 |    389 |       0 |       2 |   0 |   192 |
| Decorator specs      |    135 |    111 |       0 |       0 |   0 |     0 |
| Haml_lint specs      |     39 |     26 |       0 |       0 |   0 |     0 |
| Helper specs         |    320 |    240 |       0 |       1 |   0 |   238 |
| Lib specs            |   1126 |    936 |       0 |       2 |   0 |   466 |
| Mailer specs         |   6797 |   6046 |       0 |       3 |   0 |  2013 |
| Model specs          |  77120 |  61488 |       3 |     140 |  46 |   437 |
| Push_notifier specs  |   2547 |   2173 |       0 |       6 |   0 |   360 |
| Request specs        |  54132 |  45976 |       0 |     407 |   0 |   110 |
| Serializer specs     |   1820 |   1509 |       0 |       0 |   0 |     0 |
| System specs         |  28542 |  22830 |       0 |      20 |   0 |  1139 |
| Task specs           |    203 |    169 |       0 |       0 |   0 |     0 |
| Validator specs      |   1355 |    957 |      20 |       6 |   0 |   157 |
| Worker specs         |   7776 |   6388 |       0 |      25 |   0 |   253 |
+----------------------+--------+--------+---------+---------+-----+-------+
| Total                | 278658 | 216840 |    2338 |    9148 |   3 |    21 |
+----------------------+--------+--------+---------+---------+-----+-------+
  Code LOC: 67602     Test LOC: 149238     Code to Test Ratio: 1:2.2

CIの状況

kakariでは主にCircleCIを利用しており、以下のようなJobを実行しています。

  • Ruby on Railsのテスト(RSpec
    • 各種モデル群の単体テスト
    • APIのRequest Spec
    • Rails Viewを提供しているエンドポイントに対してのSystem Spec
  • JavaScriptのテスト(Jest
    • JavaScriptで完結する各種Vue.js Componentの単体テストなど
  • 静的解析

他にも細かなJobが幾つかありますが、いずれも1分前後の小さなJobのため今回は割愛します。 また、Playwrightを活用したE2Eテストもありますが、これに関しても実行環境・タイミングなどが特殊なため今回の対象外とします。

改善結果

細かな改善内容はさておき、具体的な改善結果をお見せします。

改善前のCI Workflow 14分18秒

改善後のCI Workflow 6分35秒

「14分」から「6分半」への大幅な改善を達成することができました 🎉

また、 「お金💰の力で並列数を増加」のような対応はとらずに一定改善できたため副次的効果ですがコストの削減にも繋がりました。

改善内容

前提知識: CIのキャッシュ機能

具体的な改善内容紹介の前に、ここで「CIのキャッシュ機能」について前提知識として理解しておきましょう。

CircleCI公式ドキュメントが丁寧のためそちらをご参照ください。 circleci.com

例示されている通り、yarn packageやruby gemsなどのライブラリのインストール処理にてキャッシュを活用し、各Job内でのインストール時間を短縮することが典型的な活用事例かと思います。

また、今回はCircleCIを利用しましたがGitHub ActionGit Lab CI/CDなど大抵のCIサービスには同等の仕組みがあるため適宜読み替えてください。

それでは、改善内容を紹介します。

webpack buildのキャッシュを活用

出落ちになりますが、本改善がかなり大きなものとなります。

前述した通り、本Railsシステムには一部Vue.jsが組み込まれているエンドポイント(画面)が存在します。 そのエンドポイントに対してのSystem Specを動作させるにはwebpackのbuildが必要な構成となっています。

このwebpack buildの実行時間が毎回5分弱ほどでした。

この時間を短縮するためにwebpackのpersitent cachingという仕組みを活用しました。 これは「キャッシュ」という名から推測できる通りビルド結果をキャッシュし、ビルド時間を短縮してくれる仕組みです。

改善効果

「5分弱」から「1分弱」(4~5分)の時間短縮

※ キャッシュが適用される場合

対応方法

以下のようにwebpackの設定を記述してキャッシュ情報をファイル出力し、そのファイルをCircleCIのキャッシュ対象に含めるだけです。

  // webpack.config.js
  cache: {
    type: 'filesystem',
    cacheLocation: path.resolve(__dirname, 'tmp/cache/webpack'),
  },
  snapshot: {
    buildDependencies: { hash: true },
    module: { hash: true },
    resolve: { hash: true },
    resolvebuildDependencies: { hash: true },

ポイントはキャッシュデータ有効性の判定方法を「hash 方式」としている点です。 キャッシュの有効性判定の方法は以下の二種類が選べます。

  • hash(ファイルコンテンツハッシュ) 方式
    • ファイル内容のハッシュ値が同一かどうかで判定
    • 公式原文 Compare content hashes to determine invalidation (more expensive than timestamp, but changes less often).
  • timestamp(ファイルタイムスタンプ)方式 デフォルト
    • ファイルのタイムスタンプ(更新日時)が同一かどうかで判定
    • 公式原文 Compare timestamps to determine invalidation.

CIでは毎回git cloneする都合上ファイルタイムスタンプが更新されます。 そのため、timestamp方式を利用するとキャッシュされているスナップショットが不正と判定されてしまいキャッシュを活用することができなくなってしまいます。

参考資料

RuboCopのキャッシュを活用

RuboCopにも同様のキャッシュの仕組みがあります。

docs.rubocop.org

キャッシュの有効性判定は以下の通りで、「Ruby・RuboCopのバージョン、Cop設定が変わらない限り同一ファイル内容に対してのCop結果は不変」ということから、Cop結果をキャッシュとして記録し次回実行時にキャッシュされているCop結果を流用する仕組みです。

Cache Validity
Later runs will be able to retrieve this information and present the stored information instead of inspecting the file again. This will be done if the cache for the file is still valid, which it is if there are no changes in:

・the contents of the inspected file
・RuboCop configuration for the file
・the options given to rubocop, with some exceptions that have no bearing on which offenses are reported
・the Ruby version used to invoke rubocop
・version of the rubocop program (or to be precise, anything in the source code of the invoked rubocop program)

改善効果

「2分」から「10秒弱」(2分)の時間短縮

※ キャッシュが適用される場合

対応方法

実はRuboCopはデフォルトでキャッシュファイルを生成します。 生成されるキャッシュファイルをCircleCIのキャッシュ対象に含めるだけです。

以下のように --cache-root オプションでキャッシュファイルの生成先を指定することができます。

bundle exec rubocop --cache-root tmp/cache/rubocop

参考資料

ESLintのキャッシュを活用

そろそろキャッシュにも慣れてきた頃でしょうか。
冗長な説明となるため簡単にご紹介します。

eslint.org

改善効果

「30秒前後」から「3秒前後」(30秒)の時間短縮

※ キャッシュが適用される場合

対応方法

eslint app/bundles/javascripts spec/javascripts --ext js,vue,ts \
  --cache \ # キャッシュを有効化
  --cache-location tmp/cache/eslint/ \ # キャッシュファイル生成先を指定
  --cache-strategy content # キャッシュの有効性判定を「ファイル内容方式」とする(理由は上記webpack buildと同様)

参考資料

Jestのキャッシュを活用

キャッシュはもう飽きてきましたね。
これが最後のキャッシュ対応です。

jestjs.io

改善効果

「3分前後」から「2分前後」(1分)の時間短縮

※ キャッシュが適用される場合

対応方法

cacheDirectory を指定し、生成されたキャッシュファイルをCircleCIのキャッシュ対象に含めるだけです。

# jest.config.js
module.exports = {
  cacheDirectory: 'tmp/cache/jest',
};

参考資料

RSpec Jobをテスト特性ごとに分割

RSpec実行Jobを「webpack buildが必要なSystem Spec rspec_with_assets」と「それ以外(Model Specなど) rspec_without_assets」の二つに分割し、「時間がかかるWebPack Buildを待たずに rspec_without_assets を開始」することで全体の総時間を短縮する試みです。
こちらは同チームのサーバーサイドエンジニアの谷(@yuyat137)が対応しました。

RSpecを分割実行しJob依存関係を最適化(before)

RSpecを分割実行しJob依存関係を最適化(after)

改善効果

これまでの改善とは異なり特定のJobが改善されるようなものではないため改善効果の計測が難しいのですが、最大(webpack buildのキャッシュが適用されない場合)は4,5分の時間短縮が期待できます。
また、後述するリソースクラスと並列数の最適化にも繋がります。

対応方法

特別な工夫はなく、単にRSpec実行Jobを分割するだけです。 Jobを分割する分メンテナンスコストは増えるため、期待できる改善効果とのトレードオフで採用・不採用を検討すると良いかと思います。

CircleCIのリソースクラスと並列数の最適化

まず前提知識として、CircleCIには特定のJobを並列稼働させる仕組みがあります。 例えば以下はRSpec実行Jobを8並列で稼働している様子です。

CircleCIにてRSpecの実行Jobを8並列で稼働している様子

このように並列数を上げることでCIの総時間を短縮することができます。 ただし、CircleCIは「サーバー稼働時間の合計 ≒ コスト」となるため、並列数を上げるとその分コストが増えます。(※ Job実行のための事前処理が並列数分実行されるため)

circleci.com

もう一つの前提知識として、CircleCIでは各種Jobのリソースクラス(サーバーのスペックのようなものと解釈してください)を調整することが可能です。 例えばCPUやメモリをあまり消費しないようなJobの場合は小さめの small なんかを指定するとコスト削減に繋がります。

今回の改善は「並列数」と「リソースクラス」の両方を調整し、最適化しました。

端的に言うと「RSpec実行Jobのリソースクラスを下げて並列数を上げた」となります。 実際の最適化内容は以下です。

※ 消費クレジット= ここではCircleCI上のコストの単位と解釈してください。 (参照: https://circleci.com/docs/ja/credits/

最適化前

  • 「Medium(CPU=2, Mem=4, 消費クレジット=10/min)」を8並列

最適化後

前述の「RSpec Jobをテスト特性ごとに分割」が完了していたため、それらのJobの特性に応じてリソースクラスと並列数を最適化。

  • 重ためのSystem Spec系実行Job
    • 「Medium(CPU=2, Mem=4, 消費クレジット=10/min)」を4並列
  • テストケースが多いSystem Spec以外実行Job
    • 「Small(CPU=1, Mem=2, 消費クレジット=5/min)」を8並列

最適化前後で消費クレジットの変化なし(共に80/min)

改善効果

「10分30秒」から「7分弱」(4分弱)の時間短縮

細かなテストが多いRSpec群に対しては「スペック抑えめで並列数を増やす」というアプローチが良さそうでした。

採用しなかった・見送った改善候補

HAML-Lint, Fasterer, Brakemanのキャッシュを活用

RuboCopやESLint同様に静的解析のJobは極力キャッシュを活用したかったのですが、ツール側が機能提供していなかったので断念しました。
OSSコントリビュートチャンスですね。

Stylelintのキャッシュを活用

Stylelintにもキャッシュの仕組みがあるためこれを活用することができたのですが、元々のJob実行時間が10秒未満と短かったため今回は見送りました。

bootsnapを活用

Bootsnapは各種ファイルパス情報などをキャッシュすることで、Rubyアプリケーションの起動を高速化するGemです。
詳細は公式ドキュメントなどを参照ください。

github.com

Bootsnapが生成するファイルをCircleCIのキャッシュ対象に含めるだけでJob内のコマンド実行が高速化する可能性があります。 Bootsnapは巨大なアプリケーションほど効果を発揮しますが、kakariはせいぜい小〜中規模程度なため「Bootsnapで受けられる恩恵 < Jobの複雑さ」と判断し今回は見送りました。

Jestの並列実行化

kakariではJestをシリアル実行していたので、並列で稼働する(デフォルト)の設定に変更してみましたが、逆にCI時間が延びたため今回は見送りました。 JestのCI時間はそこまで支配的でなかったため、深く調べてはいませんが恐らくCPU/メモリリソースが十分に割り当てられていなかったことが原因かと思われます。 jestjs.io

まとめ

以上が最近kakariプロジェクトで取り組んだCI改善の小ネタ集でした。

繰り返しになりますが、追加コストなし でCIの実行時間を「14分」から「6分半」に短縮することができました 🎉

いずれも裏技のような改善ではなく、公式ドキュメントに記載されているような正攻法ばかりでした。
「重たいテストを一つ一つ修正する」ことも大事ですが、まずは落ち着いて公式ドキュメントを読み込むことも大事ですね。

本記事がきっかけで世界で動いているCI時間が少しでも短縮することを願っています。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

Terraform Provider を自作し SendGrid リソースを管理した話

SRE の田中 @kenzo0107 です。
Terraform Provider kenzo0107/terraform-provider-sendgrid を作成し、SendGrid のリソースを terraform で管理する様にした話です。

https://registry.terraform.io/providers/kenzo0107/sendgrid

まず弊社の SendGrid リソース管理の Before/After をご覧ください。

弊社の SendGrid リソースの管理方法 Before

SendGrid コンソール上で手動でリソースの作成・更新・削除していました。

特にチームメイトは以下運用で工数が肥大化していました。

  • 定期的な棚卸し
  • メールアドレスを元にプロジェクト担当者(or 退職者)かをチェック

弊社の SendGrid リソースの管理方法 After - Terraform 管理

コード管理することで以下メリットがありました。

  • 運用工数削減
    • メンバーがどのプロジェクトに所属しているかコード上で確認できる
    • 運用フロー改善*1
    • 変更理由が commit history に記録される
  • 設定の知見が伝えやすい
    • 「あのプロジェクトと同じ設定にしたい!」をコードのコピペでできる

何故自作したか?

まず既に SendGrid の Terraform Provider あるんじゃないの?と思い探しました。

世の中には SendGrid 用 Terraform Provider が沢山あるではないですか?!

利用頻度の高いリソースをサポートしているかを軸に探してみました*2が、以下のことがわかりました。

  • チームメイトをサポートしているのは以下のみ
    • SyedK1987
      • テストをしていない
      • チームメイトの管理は問題なし
      • API Key リソースをサポートしていない
    • octoenergy
      • テストが少ない
      • チームメイトリソースを定義し terraform apply 後、terraform plan 実行するとエラーになる*3

上記踏まえて以下理由から新たに SendGrid 用の Terraform Provider を自作することとしました。

  • テストの少ないプロジェクトへの関与が大変そう 💦
  • Terraform Provider 制作の知見を得たい ✨
    • 今後 SendGrid 以外にも terraform 管理を推進したい場合がありそう
    • Terraform のこと、もっと知りたい 💕

Terraform Provider を作成する為にまずやったこと

Terraform Custom Provider 作成のチュートリアル で学ぶ 📝

  • Terraform Plugin Framework を使ってHashiCupsという架空のコーヒーショップアプリケーションの API に対して Provider を作っていくチュートリアル
  • 2時間以内で終わる!*4
  • Golang をインストールしておく

手順通り進めればできるのでやり遂げられる気になれます!

SendGrid API の利用方法 確認 📝

  • 管理したいリソースに対する API を実行しレスポンスを確認し検証*5

上記をまず実施した上で以下ステップに進みました。

Terraform Custom Provider 作成する際の Plugin の選定

Provider を作成するには Hashicorp 社が提供する Plugin を利用します。 Plugin は以下2種類あります。

  1. SDK
  2. Framework (後発)

公式ドキュメント で推奨されている Framework を採用しました。

We recommend using the framework to develop new providers because it offers significant advantages as compared to Terraform Plugin SDKv2.

Golang 製 API Client ライブラリの選定

Terraform Provider が Golang 製なので API Client も Golang 製にする必要があります。

sendgrid/sendgrid-go が公式のライブラリですが、
以下理由の為、回避しました。

  • API 実行時に毎回 API Key を渡す必要があり処理が冗長
  • API 実行時に必要なキーやパス、メソッド、レスポンスのパースは利用者が実装する必要がある
  • Terraform Provider の API Client として利用する場合、Provider 側で API Client について上記を実装・テストが必要になる

例) チームメイト招待

        apiKey := os.Getenv("SENDGRID_API_KEY")
        host := "https://api.sendgrid.com"
        request := sendgrid.GetRequest(apiKey, "/v3/teammates", host)
        request.Method = "POST"
        request.Body = []byte(`{
  "email": "teammate1@example.com",
  "scopes": [
    "user.profile.read",
    "user.profile.update"
  ],
  "is_admin": false
}`)
        response, err := sendgrid.API(request)
        if err != nil {
                log.Println(err)
        } else {
                fmt.Println(response.StatusCode)
                fmt.Println(response.Body)
                fmt.Println(response.Headers)
        }

上記解消の為、 SendGrid ライブラリを自作し採用することとしました。*6

github.com

自作することで公式と比較し以下メリットがありました。

  • API は関数名で把握できパスを意識する必要がなく、リクエストに必要なキーは引数から確認できる
  • API のレスポンスのパースはライブラリ側で実施しており、 利用者は意識する必要がない
  • ライブラリのテストをライブラリ側で完結できる*7

例) 自作 SendGrid ライブラリの チームメイト招待

    c := sendgrid.New(apiKey)
    u, err := c.InviteTeammate(context.TODO(), &sendgrid.InputInviteTeammate{
        Email:   "engineer-boshuchu@example.com",
        IsAdmin: false,
        Scopes: []string{
            "user.profile.read",
            "user.profile.update",
        },
    })
    if err != nil {
        return err
    }
    log.Printf("invite user: %#v\n", u)

他 SendGrid Provider はどの API Client ライブラリを使ってる?

Terraform Provider 管理リポジトリ内で sendgrid/sendgrid-go を利用し API の処理をサポートするケースが多かったです。

この場合、Terraform Provider 管理リポジトリ内でライブラリのテストを実施する必要があります。
そして、ほぼテストしてなかったです 😢

Terraform Provider 作成でわかったこと

開発関連

  • まずチュートリアルを終え、以下を学ぶことが大前提
    • 開発環境を整備する方法
    • Provider リソースの定義の仕方
    • スキーマの定義の仕方*8
    • API Client の設定方法
    • data, resource リソースの作り方
    • terraform init, plan, apply でリソースを管理する方法

チュートリアルでも利用しますが、
Terraform Plugin Framework のクイックスタート用のリポジトリがあるので、そこにテストやドキュメント生成、Terraform Registry へのリリース等の GitHub Actions が用意されています。

テスト関連

SendGrid API 関連

  • リソース作成時と取得時でレスポンスが異なる
    • API Key は作成時のみキー値が取得できるが、Read する API がない
      terraform import 時は API Key のキー値が tfstate に保存されない*9
  • SendGrid API はサーバエラーが時折発生する
    • terraform plan/apply 実行時にサーバエラー (502) で正常終了できない場合がある
    • GitHub Actions 上でテスト実行時に too many requests (429) が発生することが多い*10
  • チームメイトの権限は招待後に SendGrid 側が自動で付与する場合がある*11
    • 付与する権限により権限を更新できない場合がある

自作 SendGrid Terraform を運用してみて得た知見・問題

アカウント毎の管理方法

  • 親アカウントは親アカウント用の terraform で管理*12
  • 子アカウント(サブユーザ)は各種プロジェクト毎の terraform で管理

弊社では子アカウントはプロジェクトに紐づくのでプロジェクトの terraform に寄せることでプロジェクト毎のライフサイクルで管理できる様にしています。

provider で指定するもの

各ワークスペースで環境変数として以下設定しています。

  • 親アカウント
    • SENDGRID_API_KEY
  • 子アカウント(サブユーザ)
    • SENDGRID_API_KEY
    • SENDGRID_SUBUSER

親アカウント・子アカウントのワークスペースで利用する SendGrid API Key は親アカウントで発行した API Key を指定します。

上記について Terraform Cloud を管理する Terraform Provider を利用し Terraform で管理されています。

秘匿情報の扱いについて

これはあくまで回避策として実施したことです。
別途解決策があればご指南いただきたいです🙇‍♂️

  • 秘匿情報は data "aws_kms_secrets" を利用し暗号化し管理
    • 開発者がアクセスできる KMS 鍵を用意し暗号化・復号できる様にした
    • 利用用途: サブユーザ作成時に必須のパスワードの暗号化

SendGrid は秘匿情報を暗号化する仕組みがないので、 terraform-provider-aws の力を借りました。*13

まとめ

Terraform Provider - SendGridSendGrid Go ライブラリを自作し SendGrid の管理工数が削減され、Terraform・SendGrid への知見が高まりました。

これからサポートできるリソースを増やし、より管理しやすくしていきたいと思います。

また、個人 OSS を社内導入時にレビューいただいた皆様に感謝です。

以上 参考になれば幸いです。


是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

*1:以前はチケットを作成し、そのチケットを上長が承認した上で Sendgrid コンソール上でメンバー追加等をしていましたが、 GitHub の Pull Request で上長が Approve したら OK という簡易な運用になりました。

*2:各種 SendGrid Provider の対応状況 2023-08-31 時点

チームメイト サブユーザ API Key Sender Authentication GitHub
download count
at 20230831 14:00
GitHub
archived ?
GitHub
star count
anna-money x o o o 16108 no 4
daco-tech x o o o 848 no 0
davidji99 x x o x 21,191 no 0
indentinc x o o x 1765 no 0
Meuko x o o o 740 no 0
nanasess x x x x 23 no 0
octoenergy o o o o 1,093 no 0
phogolabs x x x x 2275 no 0
SpotOnInc x o o o 106 no 0
SyedK1987 o o x o 144 no 0
taharah x o o x 3711 no 2
tatsuo48 x x x x 26,232 no 2
theaox x o o x 21,198 no 0
Trois-Six x o o o 287,579 yes 13
yinzara x o o x 1,488 no 0

*3:チームメイトとして招待後、保留中のチームメイトとなり、チームメイト取得 API で取得できないことを考慮できていない。

*4:チュートリアルのセクション毎に掛かるおよその時間が記載されているのでその合計を取りました。また自身が試して2時間以内には終わりました。

*5:ドキュメントと実際の挙動が異なる API がいくつかあった

  • 例: https://docs.sendgrid.com/api-reference/domain-authentication/add-an-ip-to-an-authenticated-domain の response の dns キーの値
  • 例: https://docs.sendgrid.com/api-reference/domain-authentication/remove-an-ip-from-an-authenticated-domain の response の dns キーの値

    *6:以前 https://github.com/kenzo0107/backlog を再利用したので然程手間ではなかったです。エラー時のハンドリングが異なる程度でした。

    *7:Provider 側でライブラリのテストを実装する必要がない

    *8:スキーマ関連は一番苦労したのでここは知見を溜めて別途執筆したいと思います 📝

    *9:ドキュメントに記載

    *10:調査中。MacOS ローカルで実施した場合は然程発生しない

    *11:明確な仕様がドキュメント上に見当たらなかったので、意図せぬ動作を避けるべく ignore_changes = [scopes] で変更無視する運用にし、権限だけはコンソール上で変更する運用にしている

    *12:親アカウントが複数あるのは、請求を分けたい意図です

    *13:SendGrid のワークスペースであるのに AWS の credentials が必要なのは問題であると判断し、Vault の様な秘匿情報の管理の仕組みを別途用意する必要がある旨、社内の GitHub Issue に登録してます。