「象・死んだ魚・嘔吐」をやってみた振り返り

こんにちは。スクラムマスターの@sakebookです。

今回は「象・死んだ魚・嘔吐」をチームでやってみたのでその振り返りをします。

「象・死んだ魚・嘔吐」とは

振り返り手法の一つです。Airbnb Story 大胆なアイデアを生み、困難を乗り越え、超人気サービスをつくる方法(原題: The Airbnb Story)の中で紹介されていたようです。

翻訳されてなかなかキャッチーなネーミングになっています。

それぞれ次のようなことを意味します。

  • 凄く大きい、見えているけど、みんな見ないふりをしている課題・問題。表層化しているけど大きすぎてみようとしていない。これが何かをみんなで話していく。

死んだ魚

  • 放っておくと腐っていく。そういう問題。放置しておくとまずいことになる問題ってなんだろう?ということを話し合う。

嘔吐

  • 自分の胸の中に隠していて、吐き出せなかったこと。これをこの場で嘔吐する。

厳密な定義は記事によって異なるかもしれませんが、大枠どんなことを指しているのかイメージがつくと思います。

どうして取り組もうと思ったか

チームのレトロスペクティブ(振り返り)にはKeep Problem Try(以降KPT)を採用していました。

チームメンバーと1on1を定期的に行ったり、各種ミーティングに参加しているなかで、スクラムの価値基準の「公開(Openness)、尊敬(Respect)、勇気(Courage)」あたりをもっと強化できないか?と考えトライしてみることにしました。

どのようにして取り組んだか

いきなり振り返り手法としていつものKPTの代わりに行うのではなく、内容の紹介・導入からと段階を分けて行いました。

その過程で、いつもの振り返りとは別枠でやってみることになりました。

初めの紹介・導入の際には、個人を責めることにならないように、参考にした記事にもあった、レトロスペクティブのプライムディレクティブを共通認識として持つようにしました。

それは、「私たちが何を見つけたとしても、その時点で分かっているスキルや能力、利用可能なリソース、手元の状況を用いて、誰もが自分のできる最高の仕事をすることを、私たちは理解し、心から信じています。」ということです。

その後実際にやってみる時にも見える位置に置き続けました。

FigJamにボードを用意して、各自に事前に記入してもらう形にしました。

背景の画像はこちらの記事のものを使ってます。

開催当日までに各自で非同期で付箋形式で書き込んでもらうようにしました。

なので実際は

  • 朝会で周知
  • 非同期で書き込み
  • 皆で集まり話し合い

と段階を踏んでいます。

集まってから各自付箋に目を通してもらい、話したいと思ったものにスタンプをつけてもらいました。スタンプが多いものから取り上げて話をしていきました。話す際にはNotionを併用してメモを取りました。

モザイクかけるの難しかったので小さくしてキャプチャしました。

やってみてどうだったか

「ミーティング開始の集まりが悪い」「障害発生時の動きについて」など、普段の振り返りでは出ない内容が出ました。一部マイクロサービスのメンテコストが高いなど、メンバー間で同じような課題を持っているものもありました。

一つ一つの課題が大きなものが多く、TRYに繋げることが難しかったです。

ファシリテーションに不安を感じてましたが、何について話すかを決めたらいつもの振り返りと同じように進めることはできました。

スタンプで投票する形で、どんなことに関心が高いかも可視化されたのは興味深かったです。

普段の振り返りでは出ない内容が出たこと自体とても良いことだと思っていて、抱えているものを吐き出す練習にもなるしチームの課題を見える形にできるのでやって良かったと思いました。

スクラムの価値基準の「公開(Openness)、尊敬(Respect)、勇気(Courage)」あたりをもっと強化できないか?

というところから始めたのですが、正直なところ一度の実施だと強化できたと言う感じはしていないです。ですが、継続して行うことで訓練になって強化されていくと感じてます。

次回以降どうする

レトロスペクティブの選択肢の一つにできればと考えていたのですが、毎週やっても変化がなさそうに感じられたので月一か隔月とかでできればと考えています。

進め方は、次回以降いくつか改善しようと思っている点があります。

付箋は書いた人に読み上げてもらう

付箋の内容だけみてスタンプを押す流れだと、書いた人の意図が正しく伝わらないのでは?という意見がありました。

集まって、皆に書いたものを読み上げてもらってからスタンプを押すようにしようと思っています。

似た内容をまとめる

タイムキーピングに不安があったので、進めることを優先して多いものから順に取り上げていったのですが、似た内容があったものはせっかくFigJamを使っているのでまとめられるといいと思いました。

一つのことに話しすぎない

こちらもよくある話ですが、普段話さない内容だったので集中しすぎました。FigJamだとタイマーもあるので次回は積極的に使っていきます。

無理に解決しない

上がった課題は大きいものは多いので、解決まで持っていこうと考えてしまうと無理矢理になってしまったり、話しきれなかったやるせなさが残ってしまいます。なので解決まで持っていくのを目標にせず、現状を知ることを目標にすることで気持ちを楽にさせたいと考えています。

最後に

次回もやってみてチームの定点観測に使っていければと思っています。

課題は勝手には無くならないので、見えたものに対してスクラムマスターとしてチームや組織に働きかけていきたいと思います。

これで本当にうまくいくかはわからないですが、やってみようと思ってるチームの参考になれば幸いです。

参考

zenn.dev

qiita.com

no-kill-switch.ghost.io

生成AIでニュースアプリの精度改善を競う社内コンペを開催しました

JX通信社CTOの小笠原(@yamitzky)です。4月19日に「AIコンペティション」を社内勉強会として開催したので、その取り組みを紹介します。

開催の目的

JX通信社では、AIで世界中のリスク情報を解析する「FASTALERT」という製品を開発しています。ただ、普段からエンジニアの全員がAI開発に取り組んでいるわけではなく、フロントエンドやバックエンドなど、AI以外のコンポーネントを開発しているエンジニアも多くいます。

数年前まではDeep Learningなどの高度なAIを活用した製品開発は敷居の高いものでしたが、 近年ではAutoMLやAmazon SageMakerのようなマネージドなAI開発の仕組みや、生成AIの台頭など、AIを使ったサービス開発の難易度は日に日に下がっています

そこで今回の勉強会では 「普段AI開発に関わってない人」をターゲットに、AIの分野や、AIを使ったプロダクト開発を身近に感じてもらうこと を目的として、AIのコンペティションを開催しました。

テーマ

なるべく実務に近いもの...ということで、弊社のニュースアプリ「NewsDigest」のタブ機能の改善をテーマに選定しました。NewsDigestには話題ごとのニュースを見れる機能(タブ機能)があります。しかし、「社会」「エンタメ」などをニュースをタブに分類するモデル自体は、やや古いものでした。

NewsDigestのスクリーンショット。各メディアから配信されている記事を、話題(タブ)ごとに閲覧できます

今回のコンペは「ニュース記事を解析し、タブを分類するモデルを作る」というテーマで開催し、タブ分類の正解率を競いました。

タイトルには「生成AI」と書きましたが、 手法自体は生成AI(プロンプト)でも、古典的な機械学習的手法でも、ノーコードでも、ルールベースでもエクセルでも何でもOK としています。今回の記事では「モデル」という表現に統一します。

事前準備

コンペは2時間の限られた時間で開催したため、勉強会の事前準備として、以下のものを用意しています。*1

  • 学習用データ・・・後述するサンプルコードでは利用していませんが、パラメータの学習を要するAI用に一応用意しました
  • 評価用データ・・・各自が作ったモデルの精度をポータルサイトで確認するためのサンプルデータ
  • ポータルサイト・・・NewsDigest風のUIで定性的な精度チェックをしたり、精度を定量的に確認するためのWebサイト
  • Pythonのサンプルコード

「実サービスでの運用」という点に近づけるため、次のような工夫しました。

  • あえて評価用データの正解ラベル等は開示しない。実際には自分で正解データを作らないといけないことも多いため
  • ポータルサイトでの定性的な確認をしやすいようにした。定量データが実際サービスで求められる品質や体験と乖離することもあるため

ポータルサイト

Gemini 1.5 Pro にお願いをして、ポータルサイトを作りました(Next.js/bun/Tailwind CSS/Cloud Runを利用)。解析結果のCSVをアップロードすると、タブに分類された記事を定性的に確認したり、精度チェックをしたりすることができます。このポータルサイトを見て、プロンプトやパラメータのチューニング結果のうまく行った/行ってないが確認できるようになっています。

ポータルサイトの実際のUI。右上にスコアが出ます。画像内のニュースは全て架空のものです。

サンプルコード

スムーズに体験してもらうために、サンプルコードを用意しました*2。あえてプロンプトを雑に作ったり、軽量な(精度が高くない)モデルを使ったりしています。実際、text-bisonのモデルは指示を無視して「ゲーム」や「生活」など、存在しないタブを誕生させ、スコアを下げてしまいます。プロンプトエンジニアリングの苦しみを味わってもらいました!

import vertexai
from vertexai.language_models import TextGenerationModel

# LLMのライブラリ初期化
vertexai.init()
parameters = {
    "candidate_count": 1,
    "max_output_tokens": 1024,
    "temperature": 0.9,
    "top_p": 1,
}
model = TextGenerationModel.from_pretrained("text-bison")

# 指示を自由にカスタマイズ
instruction = """あなたはニュース記事を判定し、カテゴリーを予測するAIです。記事のタイトルを受け取ったら、その記事のカテゴリーを推定してください。
カテゴリーは社会、政治、経済、国際、エンタメ、スポーツ、テクノロジーのどれかです。
記事のタイトルは、改行して複数渡されることがあります。その場合は、改行してそれぞれの判定結果を出力してください"""

# train.csvを元に、具体例をいくつか書いてあげる
examples = """input: 東北道でトレーラーとトラックの追突事故 2人の救出活動つづく 群馬・館林市
北海道 広尾町長選は新人の田中靖章氏が初当選

output: 社会
政治
"""


# 10記事ずつまとめて処理し、AIに予測させる
for i in range(0, len(titles), 10):
    input_text = "\n".join(titles[i:i+10])
    response = model.predict(
        prompt=f"""{instruction}

    {examples}

    input: {input_text}
    output: 
    """,
        **parameters,
    )
    print(response.text)

結果

当初のサンプルコードのモデルだと65.9%の正解率でしたが、優勝者は81.62%でした 🎉

優勝賞品のAmazonギフト券(AIによって生成された画像です)

優勝者コメントです。

タブ分類の工夫がスコアという形で可視化されて面白かったです!
時間があればプロンプト自体のチューニングもいろいろ試してみたかったです

【主に工夫した点】
生成AIの賢さによって精度も変わるだろうと思い、ChatGPT4-turboを使ってみることにしました。
予期しないカテゴリ(タブ)が返ってくる事を防ぐためFunction callingのレスポンスをEnum型で定義したことで安定して分類出来たように思います。

他にも「実際にAIを使う時の雰囲気掴めて良かった」といったコメントもあり、開催してよかったです。

今回は社内勉強会でのコンペ開催を通じて、AI開発の導入を体験していただきましたが、より良いプロダクト開発に役立てていけると良いなと思っています。

*1:コードやデータの公開は行っていませんが、もし同じような社内コンペを開催したい方が教えてください

*2:記載のものはCSVの読み書きなどを省略し、抜粋しています

リモートワークでもできる、気軽に始める勉強会のすすめ

スクラムマスターの@sakebookです。今回は「リモートワークでもできる、気軽に始める勉強会のすすめ」です。

「勉強会」というと、想像するものが人によって異なるので、事前にどんなものかを書いておくと

「直接の業務ではないが何かのテーマについて一緒に学んでいる会」

のことを勉強会と、この記事では定義します。

そんなのは勉強会じゃない!という人は適宜読み替えて、こういうことをやったよ気になったらやってみてねくらいの温度感で読んでください。

この日は動画再生が上手くいかなかった

リモートワークにより開催機会の減少

コロナ禍以前からリモートワークを取り入れていた弊社ですが、コロナ禍に伴い、地方へ移住した人や地方勤務にて採用したメンバーも増えています。そのため、なかなかオフサイトでのイベント開催が難しくなっていました。

勉強会も同様で、開催頻度が減少していました。

みんなで動画を見るみたいな体験をまたしたいと思った

今年の1月、会社の制度を利用してRegional Scrum Gathering Tokyo 2024(以降RSGT)というイベントにオフラインで参加しました。その際、改めて「オフラインで集まるっていいなー」と感じました。

そのような経緯もあり、「会議室でみんなで動画を見る」みたいな体験で、勉強会を再び開催したいと思っていました。

RSGTの参加者には発表の動画を早い段階で共有されました。社内共有の用途で利用可能だったので、これを題材にイベント動画視聴会という形で勉強会を開けないか?と考えました。

初めは参加の敷居を下げたいと思い、特定の言語や領域に依らないものでできればと考えていました。

社内にいくつかあるチームがスクラムを採用していたので、まさにRSGTの動画は題材にもぴったりだと思いました。

バーチャルオフィスで一緒に見る

弊社ではバーチャルオフィスとしてGatherというサービスを採用しています。

リモートワークが当たり前になった中でZoomの常時接続やTandemというサービスの検証などもしていたのですが、今はGatherを採用しています。

意識せずに働いていることを可視化できたり、ふらっと他の人やチームに絡んだりできる点などがメリットです。

Gatherでの勉強会開催では、参加者を増やしたり、集まってる感を出すために、次のような工夫をしました

なにやらイベントやってるぞという感を出した

開催していることを社内で可視化したいと思い、社内のイベントの告知用カレンダーに勝手に登録して存在を周知させました。

そして実際にGatherで開催するときには、目立つ位置で行うことで何かやってるぞという感じを出しました。

皆で一緒に見てる感じを出した

オフラインで勉強会を開催するときも、スクリーンに投影してみんなで見ると思いますが、同じような体験にするために同期再生で行うようにしました。

Gatherには、バーチャルオフィス内にテレビを設置し、動画を再生する機能があります。また、決められた時刻に自動再生を始める機能もあります。

埋め込みビデオの設定

事前に何をやるかのイメージを持ちやすくした

勉強会に興味を持ってくれた人に説明するために、次のような内容をテンプレとして周知するようにしました。

  • スライド(あれば)
  • 説明文
  • 再生時間
  • 関連リンクなど

RSGTの動画を題材にしたときには、ConfEngineのproposalを添えてます。

反響があった

動画の視聴が終わったら、感想戦で勝手にワイワイ......できるかと思ったらそうはならなかったです。なので2回目以降の開催からは、司会を用意して進めるようにしました。

イベント自体は参加者から好評で、第2回第3回と案内をしたときに社内で自主的に展開してくれる動きもありました。

自身のチームに展開してくれる様子

さらに何度かやっていくと、「この動画をイベント動画視聴会のテーマに共有できないか?」と参加者から提案をもらえるなど、一方通行ではないコラボレーションができていて良いと感じています。

提案される様子

まだ課題もある

継続はできそうですが、まだまだ課題もあります。

  • 司会を用意はしているが、なかなか話を回したりするのが難しい
    • この辺りは回数こなしたり他社の知見やプラクティスを知りたい
  • ワイワイしたいけど、動画再生中に喋ると動画の音声が聞こえにくくなる
    • オフラインの会話と違い、Gather上では人の声と動画の音声が混ざってしまう
  • GatherではYouTubeの時間指定再生が対応していない
    • やり方間違ってるだけ?解決方法知ってる人いたら教えてください
  • ふらっと参加しやすいようにしているが、人が増えすぎると感想戦というほど話せない

サステナブルな勉強会

運営というほどのことはやっておらず、題材を探してGatherでセッティングするだけなので準備もほぼ不要です。最悪人が集まらなければ自身が学ぶ回になるだけです。複数人で見た方が自分だけだと気づかない視点を得たり、補足の情報を追加で得たりできるので、テーマに興味があればお互いWin-Winになると考えています。

似たようなことやってみたいけど足踏みしてるとか、気になったという人はまずやってみるといいと思います。

KiotaでOpenAPIの定義からGoのクライアントを生成してみる

こんにちは。 kimihiro_nです。

Microsoftから「Kiota」というOpenAPIの定義からクライアントコードを生成するツールが公開されていたのでちょっと触ってみました。

learn.microsoft.com

Kiota の特徴

JSON、YAMLで書かれたOpenAPIのAPI仕様から、APIを呼び出すためのクライアント部分を自動生成してくれるツールです。 特徴としてはGoやPythonなど様々な言語への書き出しに対応していて、似たようなインターフェースで扱える点になります。 API仕様を一度共通の内部的なモデルに変換し、そこから各言語のクライアントを生成する面白いアプローチを取っています。

似たようなツールだとOpenAPI Generatorという有名なものがありますが、 ツール自体が巨大になってきてしまっているのと、CLIからだとGo言語でクライアントのみのコードを生成する方法が分からなかった(サーバー側のコードも一緒に生成されてしまう)ため、クライアントに特化したKiotaを試してみることにしました。

Kiotaを使ってみる

インストール

learn.microsoft.com

インストール手順はこちら。 コミュニティ作成によるものですがbrewによるインストールもできます。

OpenAPI の用意

クライアントを作るOpenAPI仕様を用意します。

swagger: "2.0"
basePath: /api/v1
definitions:
  main.ErrResponse:
    properties:
      detail:
        type: string
    type: object
  main.Post:
    properties:
      content:
        type: string
      id:
        type: integer
      title:
        type: string
    required:
    - content
    - title
    type: object
  main.PostListResponse:
    properties:
      posts:
        items:
          $ref: '#/definitions/main.Post'
        type: array
    type: object
info:
  contact: {}
  title: ExampleWebAPI
  version: "1.0"
paths:
  /posts:
    get:
      consumes:
      - application/json
      description: get posts
      parameters:
      - description: limit
        in: query
        name: limit
        type: integer
      - description: offset
        in: query
        name: offset
        type: integer
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            $ref: '#/definitions/main.PostListResponse'
        "500":
          description: internal server error
          schema:
            $ref: '#/definitions/main.ErrResponse'
      summary: Get List of Posts
      tags:
      - posts
    post:
      consumes:
      - application/json
      description: add post
      parameters:
      - description: Post
        in: body
        name: post
        required: true
        schema:
          $ref: '#/definitions/main.Post'
      produces:
      - application/json
      responses:
        "201":
          description: created
          schema:
            $ref: '#/definitions/main.Post'
        "400":
          description: invalid params
          schema:
            $ref: '#/definitions/main.ErrResponse'
        "500":
          description: internal server error
          schema:
            $ref: '#/definitions/main.ErrResponse'
      summary: Create new post
      tags:
      - posts

投稿の一覧取得と投稿が出来る簡単なAPIです。 諸事情(後述)でOpenAPI 3.xではなくてOpenAPI 2.0の仕様に合わせて作っています。 2.0の方はSwaggerとも呼ばれてます。

Goクライアントの生成

こちらからKiotaのGoクライアントを生成してみます。

kiota generate -l go -d ./swagger.yaml -o ./client -n ${レポジトリ名}/client

-l オプションでGo言語を、-d オプションでOpenAPI(Swagger)ファイルの置き場を、-o で出力先ディレクトリを、 そして -n で生成されるクライアントのフルパッケージ名を指定します。 フルパッケージ名は生成されたコードのimportが正しく動くために必要で、go mod init したときのレポジトリ名 + 出力時のディレクトリ(/client)を指定します。

生成されたファイル

実行するとこのようなファイルが生成されます。 これらのファイルは触らなくて大丈夫ですがimportのエラーが出ている場合は-nオプションに渡している値が適切かどうかを確認してみてください。

Goクライアントを使ってみる

まずはAPIクライアントを初期化から。

package main

...
import "github.com/microsoft/kiota-abstractions-go/authentication"
import "github.com/microsoft/kiota-http-go"
import ${レポジトリ名}/client

func main()
    ctx := context.Background()
    authProvider := authentication.AnonymousAuthenticationProvider{}
    adapter, err := http.NewNetHttpRequestAdapter(&authProvider)
    if err != nil {
        log.Fatalf("Error creating request adapter: %v\n", err)
    }
        // サーバーのURLをセット
    adapter.SetBaseUrl("http://localhost:8080/api/v1")
    apiClient := client.NewApiClient(adapter)

API認証が必要なケースに対応するためのauthProviderを生成し、そこから通信用のadapterを作っています。 今回は認証不要のAPIを叩くのでAnonymousAuthenticationProviderを利用しました。 adapterはAPI通信部分を吸収する部分で、ここで独自のHTTP通信クライアントを組み込んだりも出来ます。 OpenAPIにサーバーのエンドポイントの記載がない場合はこのようにadapterで指定します。 adapterからクライアントを生成したら準備完了です。

limit := int32(3)
params := posts.PostsRequestBuilderGetQueryParameters{
    Limit: &limit,
}
// リクエストを送信
result, err := apiClient.Posts().Get(ctx, &posts.PostsRequestBuilderGetRequestConfiguration{
    QueryParameters: &params,
})

リクエストを組み立ててAPIを呼び出す部分のコードはこのような形です。 APIごとに専用の構造体が生成されているのでそれを利用して組み立てる形になります。 パラメータが未指定の場合と区別するためすべてポインタ型で渡すようなインターフェースになっています。 数値から直接ポインタを取ることはできないので、一度代入が必要な点は少し手間になります。 lo.ToPtr みたいな関数を利用してもいいかもしれません。

result, err := apiClient.Posts().Get(/* 省略 */)

if err != nil {
    // OpenAPIで定義されているエラーは errors.As でキャスト出来る
    var errResp *models.ErrResponse
    if errors.As(err, &errResp) {
        fmt.Printf("request error: %s\n", *errResp.GetDetail())
        return
    }
    fmt.Printf("Error getting inference result: %+v\n", err)
    return
}
// Post の一覧を出力
for _, post := range result.GetPosts() {
    fmt.Printf("id: %d, title: %s, content: %s\n", *post.GetId(), *post.GetTitle(), *post.GetContent())
}

/* 実行結果例
id: 1, title: First Post, content: This is first test post.
id: 2, title: Second Post, content: This is second test post.
id: 3, title: Third Post, content: This is third test post.
*/

リクエストを行うと、パースされたレスポンスとエラーにアクセスすることが出来ます。 エラーはOpenAPIに定義されていれば専用のモデルが生成されるので、キャストすることでエラーオブジェクトにもアクセスすることが可能です。 定義されていないエラーの場合は汎用的なエラーが返ってきます。 正常時のレスポンスもパースされた状態で入っておりGetter経由で好きに取り出すことが出来ます。 JSONのパースなどを自分で記述しなくていいのはコード生成の大きなメリットです。

// リクエストデータの作成
requestData := models.NewPost()
title := "Hello world"
requestData.SetTitle(&title)
content := "This is a test from kiota apiClient."

// リクエストに追加
requestData.SetContent(&content)

// リクエストを送信
result, err := apiClient.Posts().Post(ctx, requestData, &posts.PostsRequestBuilderPostRequestConfiguration{
    Headers: header,
    Options: []abstractions.RequestOption{
        kiotaHttp.NewCompressionOptions(false),  // リクエストBodyのgzipをオフに
    },
})
// 作成した Post の ID を出力
fmt.Printf("saved id: %d\n", *result.GetId())

/* 実行結果例
saved id: 4
*/

POSTを使って送信する例はこちら。 こちらも専用のモデルが定義されているのでSetterを利用して値をセットしていく形になります。

試してみたときのはまりどころとしては、デフォルトだとリクエストが圧縮されて送られてしまうことでした。 サーバー側でリクエストが弾かれてしまい、原因を探っていったところBodyをgzipして送信していることが分かりました。 リクエスト時のオプションとしてCompressionをfalseにセットしてあげることで Content-Encoding: gzip に未対応なサーバーでも適切にリクエストを送ることが出来ます。

OpenAPIの生成もコードファーストでやってみる

OpenAPI 3.xではなくOpenAPI 2.0の形式でYAMLを生成していた部分の答え合わせなのですが、今回OpenAPIの生成もGoのコードから作ってみることにしました。

Python だとFastAPIのようなフレームワークで簡単に出力できますが、Goだとフレームワークと一体でOpenAPIを生成してくれるものは見当たりませんでした。 OpenAPIを用意してサーバー側のコードを生成する「スキーマファースト」なやり方であれば、OpenAPI Generatorをはじめとして複数候補がありますが、 OpenAPIを手で書くのが辛い、ファイル分割の対応具合がツールによってまちまちなどの理由で「コードファースト」なやり方を模索してました。

github.com

最終的にたどり着いたのがSwagというツールでした。 こちらはAPIサーバーにコメントの形でOpenAPIに必要な情報を埋め込むことでOpenAPIのスキーマを生成してくれるツールです。 gin, echo, fiberなど主要なWebフレームワークにも対応しています。

type Post struct {
    ID      int    `json:"id" binding:"-"`
    Title   string `json:"title" binding:"required"`
    Content string `json:"content" binding:"required"`
}


// AddPost
// @Summary Create new post
// @Schemes
// @Description add post
// @Tags posts
// @Accept json
// @Produce json
// @Param post body Post true "Post"
// @Success 201 {object} Post "created"
// @Failure 400 {object} ErrResponse "invalid params"
// @Failure 500 {object} ErrResponse "internal server error"
// @Router /posts [post]
func (s *Server) AddPost(g *gin.Context) {
    post := Post{}
    if err := g.ShouldBindJSON(&post); err != nil {
        g.JSON(http.StatusBadRequest, ErrResponse{Detail: err.Error()})
        return
    }
    if err := s.postRepository.AddPost(g, &post); err != nil {
        g.JSON(http.StatusInternalServerError, ErrResponse{Detail: "internal server error"})
        return
    }
    g.JSON(http.StatusCreated, post)
}

このような形で各ハンドラにコメントを付与し、swagコマンドを叩く事でOpenAPIのスキーマを生成できます。 (上記はginの例になります。) Postなどの構造体名をコメントに入れておくとOpenAPIに反映してくれるのでスキーマの更新漏れがなくて便利です。

欠点としては生成されるOpenAPIが2.0相当になってしまう点です。 3.0が公開されたのが2017年なので、OpenAPIまわりのツール対応状況を考えると3.xに移行しておきたいところです。 v2という形でSwagのOpen API 3.x対応が進められていますが現時点ではリリースされていません。 RCまでは来ているようなのでそのうち公開されると思いますが。

まとめ

SwagとKiotaを利用することで、システム間の連携を扱いやすくすることが出来ました。 OpenAPIを利用してスキーマを管理しつつ、プログラムの上ではOpenAPIを意識せず扱えるので改修やメンテナンスも行いやすくなりそうです。

弊社の場合PythonとGoをよく利用するので、「PythonのFastAPIでOpenAPIを生成してKiotaのGoでクライアント生成」、「SwagでOpenAPIを生成してKiotaのPythonクライアントを生成」みたいな言語を跨いでの連携もしやすそうです。 似たようなシステム連携をするのに「Protocol Buffers」も選択肢に上がると思いますが、REST APIでの資産が既にある場合、現状のコードベースを生かしつつSwagなどでOpenAPI化し、自動生成されたクライアントで連携を堅固にしていくのも実用的かと思いました。

今回解説用に作成したサンプルコードはこちら github.com


The Go gopher was designed by Renée French.

APIクライアント「Insomnia」で始める、チーム開発効率化

JX通信社の CTO の小笠原(@yamitzky)です。本日は、最近社内で検証している API クライアントの「Insomnia」や、Insomnia を活用したチームでの API 開発の効率化についてご紹介します。

Insomnia とは

Insomnia は、オープンソースの API クライアントです。API 通信を GUI で直感的に検証・保存できる、というのが最も基本的な機能です。似たようなツールだと Postman などが有名だと思います。

insomnia.rest

Insomnia は一般的な REST API だけでなく、GraphQL や gRPC の API にも対応したツールです。JX通信社では、NewsDigest や FASTALERT などのサービスで GraphQL を活用しているため、GraphQL にネイティブ対応しているのは非常に便利です。

Insomniaのスクリーンショット。各リクエストを保存しておくことができる。GraphQLの対応や、jq的な絞り込みもできる

また、後述のように Insomnia にはチーム開発のための機能(コラボレーション機能)が備わっており、保存したリクエストのリストを他の人と共有することもできます。

Insomnia を導入するモチベーション

Insomnia の最も基本的な使い方は API クライアントとしての使い方です。これだけであれば、GraphQL Playground や、Swagger UI などでも事足りるかと思います。

今回、Insomnia を導入することで、次のような課題を解決できないかと考えました。

1. API 通信の様々なユースケースの記録

同じ通信エンドポイント(URL)に対して「◯◯でフィルターする場合」「◯◯でソートする場合」「不正なデータを入れた場合」といった複数のユースケース(利用想定)が紐づくことがありますが、これらを網羅的に保存できるようにしたいです。

2. スキーマ定義がされていない社内 API のドキュメント

一部、昔開発した API サーバーなどでは、GraphQL や OpenAPI でのスキーマ定義がされていないものが残ってます。これらの API の使い方のドキュメントについても、統一した場所に保存しておきたいです。

3. 成果物の共有

スプリントレビューのような成果報告会で営業側のメンバーに GUI で API を叩いてもらう場合や、普段 API の開発に入っていないエンジニアメンバーにさっと共有したい場合があります。

4. 認証方法が複雑なプロジェクトのデバッグ

Google Cloud の Cloud Run で開発した認証付き API だと、Bearer Token を都度 CLI で生成して設定する必要があります。このような認証が設定されていても、容易にデバッグできることが望ましいです。

これらの「保存」と「共有」の課題を解決するために、今回 Insomnia の導入を検討しました。特に、本稿で紹介するような基本機能だけであれば、Insomnia の共有機能は無料で使えるのが嬉しいポイントです(料金プラン)。

Insomnia の共有機能

Insomnia では、右上の「+Share」ボタンを押すことで、他のメンバーに共有することができます。

その際にデフォルトで設定されているのが E2EE(エンドツーエンド暗号化) です。利用者が管理する鍵を元に、保存したリクエスト内容は暗号化されます。逆に、パスワードを忘れるとアクセスできなくなってしまうため気をつけてください*1

共有されたプロジェクトは、 他のユーザーと共同編集ができます。好みが分かれるところですが、Notion や Google Docs のような同期的な共同編集ではなく、Git のような概念 (commit / push / pull) による共同編集がベースとなっています。プロジェクトの環境変数(例:API の URL) を共有することもできます。

有償プランでは、実際に Git のリポジトリと紐づけたりもできるようです。

その他の便利機能

OpenAPI

Insomnia は、OpenAPI のエディターとしての機能も備わっています。バリデーションしてくれたり、定義を元に Insomnia のリクエストを一括生成→ curl や通信プログラムとしてコピーできるようにしたりもできるので非常に便利です。

テスト

あまり検証していませんが、API 通信のテストケースも保存しておくことができます。システム障害発生時の初動調査やリリース前の一括動作チェックなどにも使えそうです。

変数埋め込み

Insomnia は環境変数の概念があり、これを DRY に埋め込むことができます。API のドメインや API キー等を設定し、本番/開発環境を切り替えるのにも便利です。環境変数の情報も他チームメンバーと共有することができます。また、逆に共有しない「Private enviromnent」を設定することもできます。

このような環境ごと設定値の埋め込みだけではなく、プロンプト(リクエスト実行時に発火するフォームダイアログ)の結果を埋め込む機能や、動的に生成した UUID やタイムスタンプ、OS 情報などを埋め込む「Template tag」という機能もあります。

外部プラグイン

実は、Template Tag の機能は、サードパーティーや独自に開発したプラグインによって、拡張することができます。これらのプラグインはPlugin Hubに公開されています。

例えば、Cloud Run の API の認証には cloud run auth を使えば自動的に認証情報を埋め込むことができます。プラグインのソースコードは公開されているので、参考にして自作もできますね。

まとめ

本稿では、Insomnia というツールと、検証している共有機能などの紹介をしました。無料で始めることができる、というのは、スタートアップ企業にとっては嬉しいのではないでしょうか?

他にみなさんが使っているツールあれば、ぜひコメントなどで教えていただけると嬉しいです。良いお年を!

*1:JX通信社ではパスワード管理ツールを使っています