以下の内容はhttps://tech.basemachina.jp/より取得しました。


明日からできる、新機能開発でもAIエージェント活用を諦めないための工夫

こんにちは、taroです!

今回は、直近の新機能の開発でどのようにAIを活用したのかを紹介します。

といっても「設計から実装まで全てAIを使って爆速で開発できました!」という内容ではありません。

AIを試しつつも基本的には人間が設計して実装しつつ、AIを活用できた部分を場面別に紹介する内容です。

「お!これは良さそう!」と思うものがあれば、ぜひ明日の開発からお試しいただけると嬉しいです。

開発したのは管理画面SaaS「ベースマキナ」の「ビジュアルエディター」という機能で、 簡単に言えばフォームでポチポチ設定していくだけで典型的な管理画面を作成できる機能です。


www.youtube.com

about.basemachina.com

開発で使用しているAIツールは以下です。

  • ChatGPT
  • Claude
  • Claude Code
  • Cursor

ちなみにベースマキナでは業務または個人での開発で使用するAIツールの費用を会社負担で利用できる制度があります。

note.com

では本題に入っていきます。

設計

まずは機能の設計時点でのAI活用です。

競合の似た機能を調べるのが楽になった

同様の課題を解決するために競合がどんな機能を提供しているかを調べるのにChatGPTとClaudeを使用しました。

ベースマキナはローコード/ノーコードの管理画面開発SaaSという特性上、同じ目的でも機能の実現方法が競合各社で異なり、 これまで機能名などで似た機能を調査するのに時間がかかるという問題がありました。

それに対しAIの場合は自然言語で以下のような情報を伝えるだけで、同じ目的の機能を簡単に調査できます。

  • ベースマキナのサービス概要
  • ベースマキナのドキュメントのURL
  • 作ろうとしている機能の概要

さらに海外のサービスの英語のドキュメントを読み漁る手間が減ったのも嬉しい点だったりします。

プロトタイプが爆速で作れるようになった

ベースマキナでは実装に入る前にDesign Docを書いて社内でレビューし、それを元に開発をしています。

これまでは社内でレビューする際にFigmaやローカル環境で見た目だけ作ったものをモックとして使用していましたが、今回は実際にローカル環境で動作するプロトタイプをClaude Codeで作成しました。

モック作成の手順は以下の通りです。

  1. Design DocのMarkdownファイルをリポジトリに置く
  2. PlanモードでDesign Docを元に、実装のTODOリストを生成する
  3. TODOリストを順番に埋めるように指示し、プロトタイプを生成する

比較的規模の大きい機能だったこともあり結果的に数千行のコードを生成しましたが、自分自身では一切コードを修正することなく、Claudeとのやりとりのみでプロトタイプを作成できました。

「ユーザーがJavaScriptで書いて画面をカスタマイズできる」といった複雑な機能なども普通に動いているし、期間も一日程度で指示や成果物の確認以外の時間は別の作業を並列で進められるし、と最高の体験でした。

また爆速で作れる点以外にも以下のようなメリットがありました。

  • 実際に作って触れる分機能の解像度が高まりやすく、より良い体験や仕様の矛盾、実装観点での難しさなど「作ってから気づく」的な問題に早期に気づける
  • 実際に動くものがあるため、社内でのレビュー時に機能のイメージがすぐに伝わる
  • レビューで出たフィードバックをその場で反映できるため、持ち帰って修正後に再度共有という手間が減る

個人的には特により良い体験や仕様の矛盾、実装観点での難しさなど「作ってから気づく」的な問題に早期に気づけるメリットが大きいと感じています。

これまではモックやプロトタイプを作るのに時間がかかるため、複雑な機能だとやりづらかった「具体的な成果物から機能の体験や解決したい課題を逆説的に考える」設計の進め方が簡単にできるようになりました。

今回はある程度Design Docを用意した上でプロトタイプを作りましたが、今後はある程度機能のイメージができた段階でプロトタイプを作成し、プロトタイプを触って修正しながら詳細を詰めてDesign Docを作成していけばさらに設計が早くなるかなと思います。

ちなみに今回の開発からClaude Codeでプロトタイプを作ろうと思ったのは以下のツイートがきっかけでした。ありがとうございました。

実装

次に実装です。

React,TypeScriptのWebフロントエンドとGoのバックエンドの実装で使用しましたが、 機能の特性上ほとんどがWebフロントエンドの実装だったため、React,TypeScriptでの実装の話となります。

実装方法

基本的には以下の方法で実装していました。

  • Claude CodeのPlanモードで拡張思考で実装方針を立てて、その後に実装
  • PlanモードのモデルはOpus 4(最近はSonnet 4.5)
  • 実装のモデルはSonnet 4(最近はSonnet 4.5)
  • ユニットテストも生成しそれが通るまで実行させる
  • 生成されたコードはdifitでレビュー
  • StorybookをPlaywright MCPで操作し、ブラウザでのデバッグも行う
  • 積極的にSubagentsを作る

生成されたコードのレビュー

コードレビューはdifitを使うのが圧倒的に楽で、まだ試してない方はぜひ試してみてください。

github.com

コンテキストウィンドウの枯渇を防ぐために積極的にSubagentsを作る

以下はClaude Codeの公式ドキュメントに記載されているSubagentsの機能です。

  • 特定の目的と専門分野を持つ
  • メインの会話とは独立した独自のコンテキストウィンドウを使用する
  • 使用を許可された特定のツールで設定できる
  • その動作を導くカスタムシステムプロンプトを含む

docs.claude.com

この中で個人的に一番嬉しいポイントは「メインの会話とは独立した独自のコンテキストウィンドウを使用する」で、メインのコンテキストウィンドウの枯渇を防ぐために、Subagentsは積極的に作成しました。

Subagentsを作成したのは、例えば以下のような作業です。

  • Playwright MCPを使ったデバッグ
  • ライブラリのドキュメントの参照
  • コードレビュー

コードレビューに関しては、頻繁にレビューが必要になる分野ごとにAgentsを作っていました。

例えば、Reactのコードを書く際にuseEffectが多用されるのを防ぐための「Reactの公式ドキュメントを参照してコードレビューするエージェント」などです。

ちなみにSubagentsを作成しても呼び出されない場合、公式ドキュメントではsubagentのmdファイルのdescriptionで指示するのが推奨されています。

To encourage more proactive subagent use, include phrases like “use PROACTIVELY” or “MUST BE USED” in your description field.

docs.claude.com

ただそれでも呼び出されない場合があるのでCLAUDE.mdにも記載するようにしています。

基本は2並列で作業

基本的にはClaude Codeを2セッション起動し、上記の方法で2並列で動かしていました。

何度か2並列以上もチャレンジしましたが、自分自身が別の開発やコードレビューなどの別の作業をしていると、現状は2並列が安定してできるラインでした。

Hooksで音付きでデスクトップ通知していても、どうしても放置してしまうことがあるので、 Claude Codeのタスク状況などが可視化できるツールとかがあったら楽かなとも思いつつ、まだ良い解決方法が見つけられていないです。


以下は場面別にどんな感じにAIを活用できたかの話です。

既存のコードを参考にできる単純な実装

Claude Codeに任せていたタスクの中の大半は「既存のコードを参考にできる単純な実装」です。

例えば以下のような設定フォームであれば1つ手本となるフォームを実装し、あとは同様のフォームの実装をClaude Codeに任せていました。

実装するフォームの仕様と参考にするファイルの2つのみを指示するといった感じです。

既存のコードを真似するような実装は本当に精度が高く、ほぼほぼ生成したコードのみでPRを作成できました。

流し読みでレビューできるコードを生成させる

ただ単純な実装とはいえコード量が多くなるとミスも増える上、ちゃんとレビューしないとミスを見落としてPRを作成してしまうことが増えてしまいます。

そのため単純なフォームとはいえ以下のような単位で細かくタスクを分けて指示していました。

さらに参考にするコードはClaudeが真似しやすいように過剰なくらいにコードの構造が同じになるように揃えていました。

例えばファイルの分け方や命名はもちろん、変数・型の宣言やテストケースの順番、改行の有無までできる限り揃えて、コードの意味が分からなくてもパターンを認識できれば真似できるような状態です。

ここまで揃えると生成されるコードが自分が書くであろうコード(なんなら人間よりtypoなどのミスをしない分、自分よりも自分らしいコード)になり、流し読みでのレビューで十分になります。

逆に構造にブレがあるとただ真似をすればいい場面でClaudeが既存にはないコードを生成し、その分レビューで流し読みできないコードが増えてしまいます。

既存のコードを参考にできない複雑な実装

既存のコードをあまり参考にできない複雑な実装もとりあえず一度はClaude Codeに任せていました。

ほぼ使えないコードが生成されることも多いですが、うまくいったらラッキーですし、使えないコードが生成された場合も、その結果から、実装前に見えていなかった難しさに気付けたりします。

ちなみに複雑な実装の場合は、Plan時にClaudeから私に質問させるようにしていました。

ハマった時の調査

なぜか動かない、型エラーが直らない、テストが通らないみたいにハマった時は、一旦Claude Codeに修正を依頼するようにしていました。

特にハマった時にあるあるの「真偽値の条件が逆だった」みたいな単純なミスは本当に一瞬で見つけてくれるのでめっちゃ助かりました。

あとReactの無限レンダリングみたいなClaude Codeが不具合の動作を確認するのが難しい系のバグでも、ソースコードを読んでもらうだけで原因を見つけてくれたりして驚きました。

Playwrightを使ったe2eテスト

最後にPlaywrightを使ったe2eテストです。

ベースマキナでは本番と同等のテスト環境を使用して、Playwrightを使ったe2eテストを行っています。

今回の開発ではe2eテストの実装でもClaude Codeを使用しました。

実装方法

基本的には以下の方法で実装しました。

  • Claude Codeの使用方法は機能を作る時とだいたい同じ
  • 1つ目のテストは人間が実装し、2つ目以降はClaude Codeで生成する
  • Playwright MCPとAgentsを使用
  • テストのreporterはjsonを指定

当初は「試しに生成させてみるか」くらいの印象でしたが、ある程度テストの数が増えてくると精度が高まり、終盤はほぼ修正なしでPRにできるようなテストを生成できるようになりました。

ソースコードとサービスのドキュメントを参照できるようにする

Playwrightのテストやドキュメントはアプリケーションとは別のリポジトリで管理しているのですが、生成するテストの精度を高めるためにClaude Codeからソースコードとサービスのドキュメントを参照できるようにしていました。

特にテストシナリオを作る段階では参考情報がないと、テスト対象の画面にどんな要素があるかを全て指示するか、Playwrightを使って探索してもらう必要があり手間がかかります。

逆に参考情報を渡しておけば特に細かい指示をしなくても要素を把握し、例えばフォームのテストであれば全ての入力項目を洗い出した上でテストを生成してくれます。

またソースコードを渡しておくもう一つの利点として、テストの生成がうまくいかない時にソースコードを読んで原因を見つけてくれるようになります。

ちなみにドキュメントとソースコードの参照は、コンテキストウィンドウの枯渇を防ぐためにSubagentsを作成していました。

細かくutility関数を作ると良いテストが生成されやすい

page.getByXXXexpect()などを直接使用せず、openXXXPagefillYYYNameexpectZZZFieldValueのように細かくutility関数を切って使用したテストを参考に、テストを生成すると同様のテスト観点のテストが生成されやすかったです。

おそらくpage.getByXXXexpect()などを直接使用したコードの場合、コードだけだと「どんなシナリオなのか」「何を検証したいテストなのか」の情報が残りにくいためです。

例えばpage.getByRole("button", {name: "保存"}).click()というコードの場合、(前後のコードで多少推測がつくかもしれないですが)基本的には何かを保存していることしかわかりません。

対してsaveTableComponentForm()のような適切な名前のutility関数を使うとコードから読み取れる情報量が増え、Claude Codeが既存のテストを上手く真似してくれるようになります。

またutility関数に分けることで複数のテストで共通の構造が生まれるため、上述の「流し読みでレビューできるコードを生成させる」と同様に、レビューが楽になります。

その他

その他、細かいTipや便利だった点です。

git操作

この機能の開発以前からも使用していましたが、git操作はClaude Codeで行うことがだいぶ増えてきました。

例えばPRの作成は、普段1つのPRの修正が小さくだいたい1PRで1commitのため、メインブランチで実装後にclaude -p draftでPRを作ってで、branchを切るところからPRを作成するところまでClaude Codeに任せています。

またコンフリクトの解消やメインブランチへの追従もclaude -p {PRのURL}、コンフリクトを解消して or メインブランチに追従してでほぼほぼうまくいきます。

あとはgitの変更履歴を追って時系列順にまとめてもらうのも便利です。

リファクタリングなどでファイルが移動したりしていると、gitの変更履歴を追うのが面倒だったりするんですがファイル名と行数を指定するだけで、時系列順に変更内容とPRをまとめてくれます。

Claude Codeには画像を入力できる

使い初めの頃は気が付かなかったのですが、Claude Codeには画像を入力できます。

docs.claude.com

UIのデザインなどを画像を使って伝えたいときやaltテキストの生成などができて便利です。

おわりに

今回は「ベースマキナのビジュアルエディター」の開発でAIを活用できた部分を紹介しました。

冒頭で述べた通り、いきなり「設計から実装まで全てAIを使って爆速で開発できました!」とはいかず、AIを前提とした開発方法を人間が整備していく必要があると感じました。

今後も「これはAIでは難しいな…」と決めつけず「どうやったらAIでできるか」を考え柔軟に開発方法を対応させていきたいです。

BlacksmithでCIが速い・うまい・安い!

こんにちは、ほとけです。

ベースマキナは、GitHub ActionsのランナーをBlacksmithに置き換えました。結論から言うと、Blacksmithは心の底からお勧めできるサービスです。ほとんどノーコストでCIの速度向上・コストカットが実現できました。

Blacksmithとは

Blacksmithは「GitHub Actionsの代替ランナーを提供する」というかなりピンポイントな課題を解決するサービスです。セールスポイントはその価格と実行速度です。GitHubの公式と同一のコア・メモリー数のランナーが半額、しかもハードウェア性能は公称で2倍、キャッシュのダウンロード速度は4倍です。つまり、同レベルのランナーに置き換えるだけでコストカット・速度向上が見込めるわけです。

そんな夢のような話があるのかと思うかもしれませんが、真実です。そして、畳みかけるようですが、驚くべきことに公式ランナーからの移行のステップはほぼこれだけです。

  • Blacksmithにサインアップ
  • GitHub ActionsのWorkflowファイルのruns-onをBlacksmithのものに変更(BlacksmithのWebコンソールに移行ツールも用意されています)

論より証拠ということで、この後は実際の統計情報をお見せします。

実行速度の比較

実際にGitHubの公式ランナーとBlacksmithのランナーでの実行速度を比較してみます。 これはベースマキナフロントエンドのCIの統計情報を抜粋してきたものです1

ジョブ ランナー 平均実行時間 平均キュー時間 失敗率2
unit test GitHub Actions 2vcpu 3m 20s 3s 6%
unit test Blacksmith 4vcpu 1m 1s 14s 3%
lint GitHub Actions 2vcpu 1m 38s 3s 8%
lint Blacksmith 2vcpu 1m 7s 14s 7%

もともとはどのジョブもGitHub Actionsデフォルトの2vcpuのランナーを使っていましたが、Blacksmithへの移行に際して、律速となるunit testのジョブを4vcpuのランナーに変更しました。同一スペックのランナーで半額になるのだから、倍のスペックのランナーにしても単価は変わらないのです。

結果としては、unit testはランナーの価格は据え置きで2倍以上速くなっているので、トータルのコストは半分以下になっています。また、同スペックのランナーを使っているlintではunit testほどではないにしても高速化しており、かつランナーの価格が半分なので、こちらもトータルのコストは半分以下となります。

両者ともキュー時間は増えていますが、トータルで言えばほぼ無視できる数字でしょう。

ここまで本当に都合のいい話しかしていないのですが、公平を期して言えば、バックエンドでは1点トラブルに遭遇しました。結合テストがflakyになり、結構な頻度で無変更なコードのテストが失敗するようになってしまったのです。ただし、確証は無いものの、今のところは「我々のテストがもともと不安定であり、高速化したことでそれがより顕著になったのではないか」という仮説を持っています。テスト実行の並列数を調整したところひとまずは落ち着きを見せました。そのせいで本来期待できるレベルの速度向上を享受できていないためそこは今後の課題になりますが、もちろんコストカットにはなっております。

まとめ

このように、GitHub ActionsのランナーをBlacksmithに置き換えるだけで爆速爆安のCI環境が手に入りました。 個人的には、Y Combinatorの投資先がAI系サービスで占められているようなAI全盛時代に、ピンポイントだが技術者が日々直面している課題を、まっすぐな技術的アプローチで解決するこうしたサービスは最高にかっこいいと思っており、正直に申し上げますとブチ上がりました。 皆様もぜひぜひお試しください。

宣伝

ベースマキナは、2025年10月18日に開催されるHono Conference 2025にゴールドスポンサーとして協賛しており、当日はブースも出展しています。社員一同、皆様とお会いできることを楽しみにしております。協賛の背景については弊社代表timakinがエントリーを書いておりますので、こちらも合わせお読みいただけると幸いです。

ベースマキナはHono Conference 2025にゴールドスポンサーとして協賛・出展します! - ベースマキナ エンジニアブログ


  1. ジョブ名は説明のために実際のものから変更してあります
  2. 失敗率は単にBlacksmithのハードウェア由来の不安定な挙動などがないことを示すためのもので、公式ランナーと比べて下がっていることに意味はありません。

ベースマキナはHono Conference 2025にゴールドスポンサーとして協賛・出展します!

こんにちは!社内管理画面開発・DX/AXプラットフォーム、ベースマキナ代表取締役社長をしております、timakinです。

弊社はこの度2025年10月18日(土)に開催される「Hono Conference 2025」に、ゴールドスポンサーとして協賛します。

当日はイベントブースも出展するため、是非お越しいただければと思います。

Hono Conference 2025とは

以下、公式のconnpassページからの抜粋になります。

Webフレームワーク「Hono」をテーマにしたカンファレンスです。Honoコントリビュータ、ユーザによるトーク、座談会などが開催されます。

イベント名に冠している通り、Webフレームワーク「Hono」をきっかけにして開催されるカンファレンスです。

HonoはCloudflareでSenior Developer Advocateをされているyusukebeさんが開発し、現在進行系でさかんに機能追加が行われています。

実はベースマキナでも、Honoの恩恵を受けているコードがあります。

どこで使っているのか、気になる方は是非当日のスポンサーブースにお立ち寄りください!

開催日時・会場

開催日時は2025年10月18日の12:30~20:30(開場12:00、懇親会18:30〜20:30)です。

会場はお台場のdocomo R&D OPEN LAB ODAIBAです。

という駅近の立地です。お台場のイベント会場はなかなかお邪魔することがないため、私も当日行くのが楽しみです。

今回弊社がスポンサーする理由

今回のHono Conference 2025にスポンサーをさせて頂いた理由は、Hono Conferenceというイベントに対して「登壇トピックも参加者側のエンジニアの技術知見も、どちらも面白いものが集まるイベントに違いない!」という期待があったからです。

開発者向けプラットフォームである私達ベースマキナは、Honoの活用をはじめとして日々新規技術のトレンドにキャッチアップしていく必要があります。

connpassのタイムテーブルを見れば一目瞭然な通り、Honoというフレームワークに留まった技術単体での深堀りよりかは、最新の開発のリアルワールドでいかにHonoが活用されているかを知れる場となっていそうです。実運用されている技術ネタにこそ一番価値がありますからね。

そうした内容に関心があるエンジニアの方々と出会い、サービスへのフィードバックや社内の管理画面・DX/AXの現場でのご経験についてお話を伺うまたとない機会ではないかと考えております。

また、Cloudflareの最新のリリースを追っているとAI関連のアップデートも盛んであり、カンファレンスでは現場発ないしユーザー向け機能でのAI活用のお話も登場するだろうと考えておりました。

ベースマキナもドキュメントにお客様社内でのAI活用を見越したユースケースを掲載しはじめたこともあり、このあたりのお話も登壇トピックに含まれたら面白そうだろう、と考えた面もございます。

そんな期待を寄せていたところ登壇のタイムテーブルとスポンサー企業様それぞれが発表され、それを見て「間違いはなかった!」と既に心が浮き立っております。

蓋を開けて見たら「見たことあります!使ってます!」と声を上げたくなるような企業様ばかり、日々ニュースのフィードで流れてくる内容では見れないネタばかりだなと感じており、一参加者として楽しみです!

ご一緒できるのが楽しみなスポンサー企業様方

終わりに

繰り返しにはなりますが、当日お越しになる方は是非ベースマキナのスポンサーブースにお立ち寄りください!

サービスのお話を一方的にするような形ではなく、今使っている技術や社内管理画面の開発の本音を話し合える機会となればと思います。

サービスの方にご興味が湧いた方は、是非こちらからお目通しいただければと思います!

about.basemachina.com

llms-full.txtで始めるAIサポートチャット

こんにちは!syumaiです。

今回は、ベースマキナ ドキュメントへの llms-full.txt の追加(リリースノート)に合わせて、その活用方法についてお伝えさせていただければと思います。

なお、今回ベースマキナに llms-full.txt を追加した主な目的は、お客さまご自身で、NotebookLMなどを使ったAIサポートチャット環境をご利用いただけるようにすることです。詳細については後ほど説明させていただきます。

llms.txt とは?

llms-full.txt の話をする前に、 llms.txt の話をする必要があります。(深くは踏み込みません)

重要なのは、これらがいずれもLLMのための文書である点です。

一般的なドキュメントサイトは、人間が読みやすいように書かれています。 しかしながら、人間が読みやすいようにするためのマークアップには、LLMにとっては不要な情報が多く含まれています。 例えば、文字色の情報は、文書そのものの意味には影響しないことが多いでしょう。

一方で、見出しや太文字は、文書の構造や言葉の持つ意味の強さを示すので、LLMも知っておいた方がよい情報といえます。 また、文書間のリンクなども、LLMが必要な知識を集めるのに使われうる情報でしょう。

最近では、これらを考慮した上で、元のドキュメントが持つ意味をなるべく損なわず、LLMに必要な情報に絞った、効率的に扱えるドキュメントが必要とされています。

そこで、LLM向けのドキュメントの形式として提案されたのが、llms.txtです。 llms.txt は、人間にとっても読みやすい、Markdown形式をとっています。そのうえで、機械的にもパース可能な文書として形式が定められています。

llms.txt は、サイトの概要や、サイトに含まれるページへのリンク一覧を含みます。 サイトマップと似ているように聞こえますが、 sitemap.xml がサイトの全てのページをリストアップする一方で、 llms.txt は、LLMにとって有用と考えられるページへのリンクに絞って提供するべき、とされている点で大きく異なります。

詳しくは、azukiazusaさんの以下のブログをご覧ください。

azukiazusa.dev

ここまで llms.txt の説明を行いましたが、実際現実として提供されている llms.txt には、上記提案に沿った内容になっていないものも多いです。 llms.txt は標準化されていませんし、「Markdownによって記述された、サイト内のページへのリンク集」というふんわりとした共通認識によって提供されているのが実態でしょう。

llms-full.txtとは?

先ほど紹介したazukiazusaさんのブログによると、 llms.txt の当初の提案には llms-full.txt も含まれていたようです。 しかしながら、現在の https://llmstxt.org には llms-full.txt への言及はありません。どこかしらのタイミングで提案から外れたのでしょう。*1

ということで、 llms.txt よりも更にフワッとした存在である llms-full.txt ですが、これは何かというと、サイト全体の情報を1つに収めた文書ファイルです。なかなかインパクトがありますね。

*1:もしくは、初めから提案に含まれていなかった可能性もありますが、筆者は正確には確認できていません: https://x.com/__syumai/status/1945689589471133992

続きを読む

開発期間2週間!新機能の叩き台をAIエージェント駆動で爆速開発した話

はじめに

こんにちは。ベースマキナ代表取締役社長をしております、高橋(timakin)です。

皆様、AI活用してますか?この数ヶ月から1年くらいの間で、AIを使ったプログラミングの進化といったら本当に目覚ましいものがありますね。

自分もソフトウェアエンジニアとしての経験をしたうえで代表をしているので心が踊る技術を何度か見てきましたが、10年単位で見てもここまでのパラダイムシフトはなかったのではと思います。

この変化に取り残されないよう、ベースマキナでもエンジニアチーム全体でのCursor導入、Clineの積極的活用等を実施しています*1

ClineでClaudeを使う場合は会社で全エンジニアメンバー用のAPIキーを発行して使ってもらっています。

さて、そんなベースマキナですが、この記事では弊社のプロダクトの新機能開発をAIエージェント駆動で行った際の試行錯誤・学びをご紹介します。

なんで今こんなことをしたのか?

このタイミングで既存プロダクトの新機能をAIエージェント駆動で作成した動機は2つあります。

AIエージェント駆動開発の現在地を知りたかった

弊社が提供するベースマキナは、社内管理画面を作る開発基盤です。主にソフトウェアエンジニアの皆さまが作成した画面を、カスタマーサポート・マーケター・セールス等の方が使用される形でご利用頂いています。

ベースマキナに限らず、この世のサービスの中でソフトウェアエンジニアの開発工程を圧縮するツールや、開発をソフトウェアエンジニア以外に開放するツールはことごとくAIとの共存方法を考えなければなりません。

そんな背景もあり、サービスの方向性に責任を持つ立場としてAIエージェント駆動でどこまでのものが作れるのか、人が介在する価値はあるのかの現在地を知っておきたい、と考えました。

既存プロダクトの機能開発に寄与するかを知りたかった

もう1つの動機は、細かいチューニング系タスクではない、仕様の複雑さが高い機能開発にどこまでAIエージェント駆動で寄与できるのかを知りたかったことです。

この世には新しい技術によって生み出され、メンテされることのない悲しき(?)TODOアプリとブロック崩しゲームが山程あります。

新技術による成果物の品質を検証する際に、仕様面で一定程度考えることが多く、かつそれが既知のものであればリトマス紙としての役割を果たしてくれるのは確かでしょう。

しかし、私達を含めて世の中の大半の人はすでにプロダクトを作っており、リリースさもなくば死の世界で生きているので、そこから目を背けることはできません。

どこかで既存のプロダクトにどれだけ新技術が寄与するかを検証しなければなりません。何もない状態から作り上げるのではなく、すでにある資産を応用できるかを身銭を切ってやってみるか!と決意したのがこの記事の主題です。

あと本音をもう少し言えば、「これからは上流工程の価値が全てで、サービス立ち上げにエンジニア不要」とか「明確な仕様定義をした後の細かいタスクか新規事業しか応用できない」みたいな両極端な議論は大体空中戦で無意味だと思っています。

重みある議論は経験と責任意識に裏付けされるものですから、自分が今後議論で正しくポジションを取れるよう、経験を積んでおくべきだと判断したのも理由の1つです。

前提

誰がどんな立場・文脈で検証したのかを書いておかなければ齟齬が生まれてしまうので、少しだけ前提情報を書かせていただきます。

使用者のプロフィール

業界歴10年です。会社の代表をしてはいますがソフトウェアエンジニアとしての経験はあります。GitHubこちら

人並みに業務経験 + OSSの自作やコントリビューションの経験はありますが、立場が変わったので積極的にはコードを書いていません。

ただ仕様議論はし続けていますし、もともとのプロダクトのコードベースは一定程度触っていたので、今回いきなりノータッチの基盤に手を入れたわけではありませんでした。

AIの成果物をレビューしながら進められる程度ではある、という前提だけご認識いただければ良いかと思います。

使用したツール・モデル

  • Cursor
  • Cline(Roo Code)
  • Claude 3.7 Sonnet

なお、ポテンシャルを限界まで引き出したかったのでAuto Approveモードは積極的に使いました。

実施タイミング

2025年3月中旬です。

スケジュール

約2週間(営業日換算10日)です。

密にコミットできたのは1週間程度で、それ以外は流石に他業務と並行でした。

会社のAI活用状況

冒頭に書いた通りCursor, Cline等は導入済みです。Devinも経験済み。

開発以外だとo1 ProやDeep Researchには必要に応じてお世話になっています。

今回のゴール

概要

ローコード開発基盤のベースマキナにノーコードでのUIビルダー機能を追加するというのが今回のゴールです。

今までのベースマキナにはビジネスロジックを登録したら、引数や結果の形式に対応した画面が自動で出来あがる”アクション”と、

ベースマキナの機能を呼び出せるJSXを描画できるビューという2つの画面作成機能がありました。

これらの中間を担う機能の提供によって、カスタマイズ性は担保しつつ一定の型に沿って設定をショートカットできるようにし、お客様の画面構築の負荷を減らして応用幅を広げることを狙いとしています。

  • 動的なコンポーネント配置
  • 画面表示の権限制御
  • テーブルならページネーションや行操作・メニュー配置、フォームなら送信後の挙動も含めた設定など、種別ごとの詳細設定
  • タブ構成などのレイアウト機能

といった、今までよりもより自然に管理画面としてお使い頂ける機能をご提供予定です。コンポーネントの種類は正式リリース後に充実させてまいります。

youtu.be

変更範囲

Webフロントエンド(React / TypeScript)です。

バックエンド側に変更を加えず、クライアント側で完結する設計を事前にしておきました。こうすることで多少無茶な変更があってもリリースブロッカーを生まず、画面を隔離しながらPoCを作れると判断しました。

求める品質

PoCも兼ねつつα版リリース一歩手前まで行ける、というのが要求水準です。

そのままリリースするとお客様も自分たちも混乱してしまうので手直しは前提にしています。

難所となる機能や概念が4,5個あったのでその骨子を作り上げるところができれば御の字、UIや状態管理の書き味は後で相当チューニングする想定でした。

また、検証段階を抜けたあとにエンジニアチームに引き継ぐので、AIに指示を出している私自身が動作原理を説明できる状態を維持しつつ開発を行いました。

事前準備

clinerulesの作成

まず、 .clinerules を作成しておきます。

  • プロジェクトの概要
  • サービス上で登場する概念
  • ディレクトリ構成
  • TypeScriptの記法

などを個別にドキュメント化して、それを .clinerules に結合します。ここではmizchiさんのdenoのスクリプトを参考にしました。

github.com

初期のプロンプトの微調整

機能の仕様をNotionでまとめていましたが、これをClineに渡すプロンプトにする前に若干実装方針のたたきを決めた上で最終文言を作ることにしました。

ChatGPT 4.5のDeep Researchに機能概要を渡したうえで、類似機能を持つ他のサービスをリサーチしてもらって迷っている点(今回は状態管理や配置したコンポーネントに持たせる設定など)の方針整理を行いました。

その後、o1 Proに上記方針と元のNotionのテキストを貼り付けたものを渡して、Clineに入力するプロンプトを生成してもらいました。

開発

開発時にはいくつかのステップがありました。具体的には大まかに以下の4つです。

  1. 初期のたたき台作成
  2. 初期段階レビュー
  3. リファクタ + 機能追加
  4. PoCレビュー + 引き継ぎ前相談事項決め

それぞれのステップの詳細を以下に記載します。

初期のたたき台作成

まず事前にNotionに機能の概要や体験設計、画面構成などをまとめていました。

  • 機能の目的
  • 利用者想定
  • 体験設計(機能を利用する際の遷移フローなどをテキストでまとめたもの)
  • 画面の構成
  • 配置するコンポーネントの仕様
  • 自分たちが考えた実装方針

文量は8000字程度です。

初期段階レビュー

PoCとして実際に画面を作成して動くものができました。ここまでが1週間程度です。

チーム内でPoCの初期段階にレビューを挟むことにしました。当初実現したい要件の8割程度が実現できていました。

ここからはもう1週間くらい時間を取ってα版として成立するのに必要な機能を盛り込んだり、引き継ぎ用にリファクタすることにしました。

具体的にはコンポーネントを配置して動的にデータを表示するだけではなく、設定の中でも複雑なイベントハンドラーの実装やデバッグ機能を追加してみました。

リファクタ + 機能追加

ここから2週目です。時間を取ってリファクタと機能追加をすることになりました。

リファクタ

Cline + Claudeで生成したコードは仕様への準拠という意味では大変良い成果物を作り上げてくれました。

しかし、1コンポーネントの中に子要素をレンダリングする関数を大量に含めてしまい、ファイル分割がされなかったり、複雑なロジックが共通化されないなどの問題がありました。

なので、

  • 重複実装の排除
  • ファイル分割

の2つの観点でリファクタを行いました。

リファクタはこのタイミングで挟んで良かったと思っていて、ファイルやコンポーネント1つにロジックが集中していると、たまたまその内容を読まずに新たなコンテキストを切って実装が始まったとき、

重複実装、正確には同じアウトプットを微妙に違うロジックで再実装したものをいとも簡単に量産してしまっていました。

なので、Clineの成果物を安定化させるためにもリファクタを挟まざるを得ませんでした。

機能追加

初期段階ではコンポーネントの描画・配置変更のPoCに集中していたので、

などを行い、「これがあれば管理画面として実運用に耐えうる」機能を一通り揃えるところまで走ってみました。

表向きはそこそこ上手くいっており、α版の事前準備としては十分なものができたと思います。

PoCレビュー + 引き継ぎ前相談事項決め

機能追加をしたうえで、PoCのレビューを行いました。

Clineの力で追加修正量が増えたことで、チーム内で現状どこまで何が作られているのかを説明するのが難しかったのは事実ですが、到達できる地点が予想以上だった点はチーム内で驚きがありました。

このタイミングでは、UI面は主題ではなく概念レベルで見直しが必要な箇所をみんなで議論して、イベントの情報のもたせ方や初期要件の見直しを行いました。

それが完了した段階でエンジニアチームに引き継ぎ、私の手を離れました。ここまでが2週間です。

成果

機能

PoCを超えてα版として成立させるまで機能追加ができました。

先程も動画を貼りましたが、できたのはこちらです。※画面は開発中のものです。正式版リリースの際にはブラッシュアップしてお届けします。

PoC成果物

画面左側でコンポーネントを動的に追加・配置換えし、真ん中でプレビュー、右側でコンポーネントごとの詳細設定を管理する画面が出来上がりました。

最低要件を満たす機能ができた意味では大満足、一方で仕様変更の過程で大量に不要になった実装が残っていたので、お掃除に+1日かかりました。

また、致命的なのが状態管理の粗さ、具体的には地獄みたいな量のuseEffectを入れ込むところで、これをお掃除する仕事をチームにお願いしてしまってそれは本当に申し訳ない気持ちになっています。

金銭的コスト

大体 $500です。いいんじゃないでしょうか。

振り返り

良かったこと

型定義から作ったこと

さきほど貼った画面の真ん中に表示されているのが構築した画面ですが、この構成情報はJSONとして管理されています。

実装に入る前に、まず全体の機能要件からこの型定義を決める指示を出したのが重要でした。

なぜなら、個々のコンポーネントの見た目・仕様は動的ですが、画面のレイアウト情報の保存形式などは変化が少ないですし、

そこを堅く作っておかないと後々”何が動作保証されていれば成果物を承認してよいか”を人間が判断できなくなります。

なので、初期段階で変化を小さくしたい箇所を決め、その仕様をArchitectモードやo1 Proに交互に投げて堅牢にしておいたのは後々も功を奏しましたし、

おかげで無邪気にUI変更をしても型定義が変わってないから最低限の仕様が担保されていると判断でき、思い切った開発ができました。

画面構成が当初想定から良い意味で大幅に変わった

画面構成は自分が伝えたものから大幅に変わりました。

実は前述のビュー機能はレンダリングする際にiframeを使っているのですが、iframe内のコンポーネントドラッグアンドドロップで配置できるようにしたいと考えていました。

とはいえ、配置変更のメッセージをmain windowからiframeに伝えるフローなどを考えると、コンポーネント配置だけでも相当に複雑な実装になるのは目に見えていました。

そこで、iframe内の内容は純粋にレンダリングの責務に集中し、配置変更はmain window側で設定することにしました。

その方針変更もClineのArchitectモードで案だしからやってもらいました。

結果、先程のスクリーンショットの左側が配置変更のメニューで、上下に配置変更したりすることになったのですが、認知負荷が低く意外と使い勝手が良い形に収まりました。

微妙だったこと

Auto Approveモードに徐々に慎重になっていった

Auto Approveモードは、Clineが生成した仕様案・コードをそのまま自動承認して書き続けてくれるモードです。

ガンガン使ってみたいと思ってやってみたのですが、設計レビューを踏まえず一気にコードを書かせるところまで自走させるのは2日目あたりで限界が来ました。

設計レビューさえ丁寧に挟んでおけば大体の実装はうまくいきますが、流石に要件定義だけでモノが作れる時代はまだきていませんね。初日うまくいっただけでも儲けものでしょう。

また、既存実装の使い方を丁寧に説明するのが重要だと上で書きましたが、型エラーが生じたりCline自体のエラーが出ると、実装ミスを大胆に丸々再実装という形で解決しようとし始めました。

この問題が頻発したため、CodeのモードでのAuto Approveは機能の修正粒度が小さいときとArchitectモードのときだけにして、複雑度の高いコード変更は見守るコストを払う意思決定をしました。

Claude 3.7 SonnetのThinkingモードだと途中から過剰だった

Claude 3.7 SonnetのThinkingの方は仕様が不明瞭なときは行間を読んでくれて良かったのですが、初期段階を抜けて仕様がクリアになったあとだと、実装を過剰に修正してしまうことが多くて通常のモデルを使う様になりました。

既存ロジックの説明資料をかき集める労力が増えた

ベースマキナには、もともと開発基盤として型に応じたフォーム入力欄を生成したり、お客様が入力したJavaScriptを実行する基盤があります。

後者は以下のスライドからご確認いただけます。

speakerdeck.com

イベントの処理にこうしたロジックを一部使わないと表現できない箇所があるのですが、clinerulesに全ての情報を載せられるわけでもないので都度言葉で説明していました。

ここからは余談ですが、解決方法で自分が思いつくのは開発のナレッジをMCPサーバー化して参照できるようにすることです。

例として、AIエージェント開発フレームワークmastraだとドキュメント用のMCPサーバーがあります。

mastra.ai

ドキュメントを動的に呼び出すだけだと微妙かもしれませんが、彼らの場合は開発ドキュメントに加えてブログ記事など、他のリソースも合わせて参照している点が面白いポイントです。

AIに入力する情報をテキストで用意するだけではなく、既存リソースをMCPとして参照させる、というのも面白いかもしれません。

AIエージェントを使った開発でオススメしたいこと

以下は私達の今回の開発に限らず、AIエージェントを使った開発でオススメしたいスタンスやTipsをまとめます。

まずはトップギア

作業を始めるときはAuto ApproveモードをONにして極力エージェントの自律性を重んじてみましょう。

設計レビューを挟まずにコードを生成させるとろくなことにはなりませんが、ArchitectモードからCodeモードへの切り変えをオフにして、それ以外は自走を許すようにしていました。

Architectモードでのレビューが最重要

何度か書きましたが、設計レビューは丁寧に挟みましょう。ここで「要件定義が大事」と書いてないのが重要で、要件はエージェントが出してきた設計を踏まえて見直すこともありました。

最初から完璧な要件定義ができることも、設計ができることもありませんが、実装直前で目を光らせるのは絶対に欠かせないことがわかりました。

堅い仕様、ゆるい仕様

先ほど画面の構成情報をスキーマで定義して、変更の柔軟性を局所的に抑えている話を書きました。

実装面で好き勝手して良い部分とそうでない部分を明文化しておいて、何をトライアンドエラーして良いのかを事前にチームですり合わせしてからAIに指示を出すと良さそうです。

リファクタを挟み込む

継続的に1つの機能の追加修正にAIエージェントを使ってみて気づきましたが、リファクタリングのステップを集中的に入れることをおすすめします。

コンテキストを切り替えていくと、過去実装と重複してるかを丁寧にチェックしてから実装するのにも限界が生じます。

アーキテクチャレベルでリファクタする必要は全然ないのですが、ファイル分割や共通化できる実装がないかを人の目でチェックし、リファクタリングをエージェントに依頼するのを習慣化すべきです。

もしくは、Clineのカスタムのモードでリファクタリング専用のモードを作ってみてもいいかもしれませんね。

おわりに

やや定性的な情報で恐縮ですが、AIエージェントは既存サービスへの機能追加にも威力があることがわかりました。

上流工程で全て完結することもないですし、細かい修正だけしか任せられないわけでもありません。設計内容が妥当かをレビューし、AIが生成した内容の質を定期検診みたくチェックしなければ回らないイメージが湧きました。

また、今回手応えある結果が得られたのは”堅い仕様”と呼ぶように、揺るがない機能や設計の範囲を決めておけたことです。

過去全てのアプリケーションフレームワークやノーコード・ローコードが生んできた価値も、共通化できる仕様は型を決めておき、それ以外の柔軟な開発に集中できるようにすることだったはずです。今回の開発ではそれと近い構造を感じました。

今後も全てのシステム開発を自前でやるよりも、AIエージェントが触れる余地を残しつつフレームワークで揺るがない仕様を守ってあげる価値はあるのだと思いますし、

ソフトウェアエンジニアが触るツールもAIで調整可能な形式やエンドポイントが用意されているものになると思います。

そしてベースマキナもそうしたサービスでありたいなと新鮮な気持ちになる体験でした。

あと余談ですが、めちゃくちゃ脳が疲れるので糖分摂取量が増えました。AIは駆動し、人類は肥える。残念ですね。

この記事の内容でなにかご質問・ご意見があれば是非私のアカウントにご連絡ください!

今のベースマキナのサービスや、この機能が正式リリースされたときに使ってみたいという方も是非ランディングページからお問い合わせいただければ幸いです。

*1:なおDevinも導入しましたが、Cline等の伴走型のエージェントと比べると費用対効果が相対的に低いので、一旦止めています。

「JSConf.jp おかわり Node学園46時限目」でエンジニア2名が発表しました

こんにちは、yebis0942です。

2/28に開催されたJSConf.jp おかわり Node学園46時限目に、ベースマキナのエンジニア2名(Motoki Shakagori, yebis0942)が登壇する機会をいただきました。JavaScript界隈のホットなトピックが集まるイベントで発表できたことは、とても貴重な経験となりました。

アーカイブはこちらで視聴できます。

www.youtube.com

発表一覧はこちらです。(弊社のエンジニアは名前に𝘽を付けています)

タイトル 発表者
[LT] PEPCはパーミッションモデルをどのように変えるのか ken7253さん
Cloudflare Workers in Production Motoki Shakagoriさん 𝘽
「The Clean Architecture」がWebフロントエンドでしっくりこないのは何故か - 制約から考えるアーキテクチャとテスト t-wadaさん
Playwrightでブラウザを拡張する yebis0942 𝘽
Webフレームワークとともに利用するWeb Components spring-rainingさん
フロントエンドで日時処理と戦うために 2025 ver. Ryusei Sajikiさん

ベースマキナのエンジニアの発表内容を簡単にご紹介します。

Cloudflare Workers in Production by Motoki Shakagori

発表資料: Cloudflare Workers in Production

Shakagoriさんが前職でCloudflare Workersをプロダクトに採用した事例の紹介です。

ベースマキナでも一部の社内ツールにCloudflare Workersを採用していますが1、本発表を通して、Wranglerをコアとした総合的な開発体験のよさやロックインの少なさなどをあらためて実感しました。

p.19に掲載されている、DevToolとシームレスに連携してバックエンドのコードをデバッグするデモなどは個人的にはかなり熱い(flareだけに…)ところだと思っています。

Playwrightでブラウザを拡張する by yebis0942

docs.google.com

以前このテックブログでも取り上げたPlaywrightでライブプレビューツールを実装する - ベースマキナ エンジニアブログに書き切れなかった内容や、その後Playwrightに追加された機能の紹介を盛り込んだ発表です。

E2Eテストツールとして広く利用されているPlaywrightですが、より一般的なブラウザ自動化ツールとしての側面も持っています。この発表が、使い慣れたツールの新しい一面を発見する機会となりましたら幸いです。

むすび & 次回登壇予告

「JSConf.jp おかわり Node学園46時限目」のベースマキナのエンジニアの発表内容をご紹介しました。関連技術の深掘りからフロントエンドアーキテクチャの俯瞰的な考察まで、いまのJavaScriptの面白さが詰まった発表だったと思います。このような場で登壇する機会をいただいたことを光栄に思います。ありがとうございました。

また、3/18(火)開催のNEWT Tech Talk vol.15 【LayerX/ベースマキナ/令和トラベル】サービスの成長に合わせたフロントエンドの進化にもベースマキナからtaroさんが登壇します。こちらもお楽しみに!

reiwatravel.connpass.com

Google CloudのHTTPのヘルスチェックにリダイレクトを返すと何が起きるのか

こんにちは、yebis0942です。体重計を購入してヘルスチェックを始めました。最近の体重計は計測結果をBluetoothWi-Fiクラウドに保存できて便利ですね。

このほどベースマキナでもGoogle Cloud上で運用するサービスのヘルスチェック設定を見直しましたので、それにまつわるTipsをご紹介します。

tl;dr

  • Load Balancingではリダイレクトは失敗と判定される
  • Cloud Runではリダイレクトは成功と判定される

ヘルスチェックとは

サービス運用におけるヘルスチェックとは、サーバーが正常に動作しているか検証する仕組みを指します。正常に動作していないと判定されたサーバーは、ロードバランサーから切り離されたり、再起動されたりすることになります。

Load Balancingのヘルスチェック

Load Balancingは、ユーザーから受け取ったリクエストを複数のインスタンスに振り分けて負荷分散をするコンポーネントです。ヘルスチェックに成功したインスタンスだけにトラフィックを振り分けます。HTTP系のプロトコルとしてはHTTP, HTTPS, HTTP/2をサポートしていますが、いずれの場合にも200 OK以外のHTTPステータスコードは失敗と判定します。

HTTP、HTTPS、HTTP/2 のヘルスチェックでは、ヘルスチェックのタイムアウト前に HTTP 200 (OK) レスポンス コードを受信する必要があります。301 や 302 など、リダイレクト レスポンス コードを含む他のすべての HTTP レスポンス コードは異常と見なされます。

ヘルスチェックの概要  |  Load Balancing  |  Google Cloud

Cloud Runのヘルスチェック

Cloud Runは、Dockerコンテナを動作させるマネージドサービスです。ヘルスチェックに失敗したコンテナは自動的に再起動されます。HTTP系のプロトコルとしてはHTTP/1のみサポートされていますが、成功条件は以下のようになっています。

200~400 のレスポンスはどれも成功であり、他のレスポンスは失敗を意味します。

コンテナのヘルスチェックを構成する(サービス)  |  Cloud Run Documentation  |  Google Cloud

より正確には、HTTPステータスコードが200以上、400未満であれば成功と判定されます。

Load Balancingと違い、301 Moved Permanentlyなどのリダイレクトは無条件に成功となります。リダイレクト先を見ることはありません。

意図せずリダイレクトが発生してしまうケース

あえてヘルスチェックの設定にリダイレクトが発生するURLを使うことはなさそうですが、意図せずそのような状況が発生してしまうことはあります。たとえば、Goのhttp.ServeMuxTrailing-slash redirectionによって、URLの末尾の/の有無によって自動的にリダイレクトが行われるようなケースです。*1

ややわざとらしい例ですが、以下のように、/health/という末尾に/のあるルーティングを設定したhttp.ServeMuxのHTTPサーバーを考えてみます。

package main

import (
    "net/http"
)

func main() {
    mux := http.NewServeMux()

    // /health/ にルーティングを設定する
    mux.HandleFunc("/health/", func(w http.ResponseWriter, r *http.Request) {
        // 常にunhealthy
        w.WriteHeader(http.StatusInternalServerError)
    })

    http.ListenAndServe(":8080", mux)
}

このHTTPサーバーの/health/にアクセスすると意図通り500 Internal Server Errorが返されますが、/healthにアクセスすると301 Moved Permanentlyが返され、/health/にリダイレクトされます。

先に説明したヘルスチェックの動作と組み合わせると、正常に動いているはずなのにヘルスチェックに失敗しつづけたり、正常に動かなくなってもヘルスチェックに成功しつづけるといった問題が起こりうることが分かります。

Webブラウザではリダイレクトは自動的に解決されてしまうので、検証方法によっては気づきづらいのではないでしょうか。検証環境で実際にヘルスチェックを失敗させてみて挙動を確認しておくのが安全そうです。

まとめ

Google CloudのサービスごとのHTTPのヘルスチェックの挙動の違いと、それが問題になりうるケースをご紹介しました。みなさまの健やかなサービス開発の一助になれば幸いです。

*1:http.Handleおよびhttp.HandleFuncではtrailing slashによる自動リダイレクトは発生しません。

監査ログの出力スキーマをProtocol Buffersで定義する

こんにちは、yebis0942です。今回はベースマキナの監査ログ機能の実装改善の取り組みをご紹介します。

簡単にまとめると、プロダクトの機能が充実するにつれて監査ログの仕様管理が複雑化し、メンテナンスコストが増加するという課題がありました。その解決策として、Protocol Buffersを採用し、スキーマベースで出力内容を定義しました。結果として、開発効率とメンテナンス性が向上しました。

以下で詳細についてご説明します。なお、本ブログ内で紹介するコード例は説明のため一部簡略化しています。

ベースマキナの監査ログ

ベースマキナは「安全なオペレーションに必要な管理画面を数分で」を掲げるローコードSaaSです。

私たちは管理画面に必要とされる機能をまとめて提供しており、その一つに「監査ログ機能」があります。管理画面で何らかの操作が行なわれたり、管理画面自体の設定が変更された際に、「いつ」「誰が」「どんな操作をしたか」をログに残して、後から管理者が証跡を確認できる仕組みです。

監査ログ出力フォームの様子

なお、お客様のAPIへの呼び出しやDBへの書き込み時に送信する情報には、ベースマキナ側では保持できない機密情報が含まれている可能性があるため、監査ログの機能では保存を行いません。代わりにお客様自身のS3やGCSに値を保存する監査ログストリーミング機能をご利用いただけます。

スプレッドシート管理の時代

これまで、監査ログの出力項目はスプレッドシートで管理していました。こちらがその一部です。

「監査ログ出力項目」スプレッドシートの様子

スペースの都合で全体を掲載することはできませんが、出力タイミング(監査ログの出力対象となる操作)は約50種類、共通出力項目は5種類(操作の種別によらず出力する共通の項目)、個別出力項目(操作の種別ごとに出し分ける項目)は約20種類となっています。

また、個別出力項目の内容もスプレッドシートで管理していました。

「監査ログ出力項目」スプレッドシートの個別出力形式の様子

このスプレッドシートの内容を仕様として参照し、実装やユーザー向けドキュメントを更新します。

出力タイミングや個別出力項目は機能追加のたびに増加し、スプレッドシートはすくすくと育っていき、次第にメンテナンスや実装・ドキュメントへの反映が難しくなってきました。

スキーマ定義言語の選定

この問題を解決するため、監査ログの出力項目をスキーマ定義言語で定義することにしました。

サーバーサイドの実装言語であるGoの構造体などをそのまま使うことも検討しましたが、ユーザー向けドキュメントを自動生成したいという狙いがあったため、スキーマ定義言語を採用したほうがコストが少ないと判断しました。

主な選定基準は以下の3点です。

  • 書きやすさ・読みやすさ
  • 監査ログの出力形式の定義に適した仕様
    • ネストしたオブジェクトで、型定義を使い回したい
  • エコシステムの充実度(コード生成や他フォーマットへの変換が容易であること)

この選定基準に沿っていくつかのスキーマ定義言語を検討し、最終的にProtocol Buffersを採用することにしました。エコシステムが発達していて、コード生成や他フォーマットへの変換が容易であり、社内での利用実績もあったことが理由です。

Protocol Buffers導入後の開発の流れ

1. 出力タイミングに対応するmessageの定義

監査ログは、出力タイミングに応じて異なる項目を出力します。そのため、Protocol Buffersのmessage(Goの構造体と相互に変換可能なデータ型)を使い、出力タイミングごとの出力項目をスキーマとして定義しました。例えば、レビュー依頼を承認した際のログは以下のように記述します。

// レビュー依頼を承認する操作のmessage
message ApproveReviewRequestOperation {
  Action action = 1;
  ReviewRequest review_request = 2;
}

// 個別出力項目(アクション)
message Action {
  string id = 1;
  string name = 2;
}

// 個別出力項目(レビュー依頼)
message ReviewRequest {
  string id = 1;
}

2. protocを使ったコード生成

Protocol Buffersの標準ツールprotocを使用して、以下を自動生成しています。

  • messageに対応するGo言語の構造体(公式に提供されているprotoc-gen-goプラグインを使用)
  • messageを監査ログ用のロガーに出力するためのメソッド(社内で開発したプラグインを使用)

社内で開発したプラグインでは、Goのコード生成にprotogenを利用しました。protoc-gen-goもこのヘルパーを利用しています。

たとえば、protoc-gen-goの以下のコードを見てみましょう。(protoc-gen-go/internal_gengo/main.goより引用)

// var g *protogen.GeneratedFile
g.P("func (x *", m.GoIdent, ") String() string {")
g.P("return ", protoimplPackage.Ident("X"), ".MessageStringOf(x)")
g.P("}")
g.P()

このコードからは以下のようなコードが生成されます。

func (x *Action) String() string {
    return protoimpl.X.MessageStringOf(x)
}

g.P()は一見するとただのfmt.Sprintf()のように見えますが、実は、引数として渡したprotoimplPackageで参照しているパッケージのimport文がソースコードの先頭に自動で挿入されるという高度な仕組みも持っています。

3. messageに対応する構造体に値を詰めるコンストラクタを定義する

DBやHTTPリクエストから取得したデータを構造体に詰め替えるコンストラクタを実装します。このコンストラクタはスキーマからは自動生成できないため、手作業で実装しています。

先にご紹介したレビュー依頼の承認操作の監査ログのコンストラクタは以下のようになっています。

func NewApproveReviewRequestOperation(
    action *model.Action,
    reviewRequest *model.ReviewRequest,
) *ApproveReviewRequestOperation {
    return &ApproveReviewRequestOperation{
        Action:        NewAction(action),
        ReviewRequest: NewReviewRequest(reviewRequest),
    }
}

4. 出力処理を実装する

あとは上記で作成した構造体を以下のように監査ログ用のロガーに渡すだけです。出力用のコードはステップ2で自動生成されているため、引数として渡すだけでスキーマに沿った項目が出力されます。

logger.Audit(
    ctx,
    loggerfield.NewApproveReviewRequestOperation(
        action,
        reviewRequest,
    ),
)

スキーマの導入の効果

スキーマから構造体などを自動生成することで、監査ログの出力項目の仕様との一貫性を保証しやすくなりました。また、スキーマ定義言語を導入したことで、監査ログの仕様の議論もスムーズに進められるようになりました。

最後に

Protocol Buffersを利用することで、監査ログの出力処理のメンテナンス性が向上しました。ベースマキナでは、これからも信頼できる管理画面基盤を提供するために改善を続けてまいります。

技術カンファレンスでスポンサーブースを出すことになったらやること

こんにちは!株式会社ベースマキナで代表を務めておりますtimakinです!

今回の記事では、以前当ブログでも告知した通りTSKaigi Kansai 2024にブース出展した経験を踏まえて、今後ブース出展をされる皆様への一助になればと思い準備や当日の手引きをまとめます。

なお、FindyさんのDevRelのまっきーさんが書いてくださった以下の記事も非常に参考になるので、是非ご覧ください。

note.com

はじめに

技術カンファレンスのスポンサーでブース出展が決まった方、おめでとうございます!

ブースで当日参加者の方にお会いするのが楽しみだと思いますが、現実問題たくさんの準備が必要です。

まずは出展の目的整理から始めましょう。

出展の目的整理

ブース出展をする目的は皆様異なると思いますが、概ね以下のどちらかあるいは両方を目的にしたブランディングだと思います。

  • 採用広報
  • 営業広報

極端にどちらか一方だけということは無いと思いますが、どちらの目的に重きを置くか決めておくことは大切です。

目的が曖昧なままでも実施はできますが、ビラやロールアップバナーに記載される宣伝用のURLやメッセージの内容を考えるときに詰まってしまう恐れがあります。

6:4くらいの微妙なバランスでも、何に比重に置くかはメンバーと一緒に決めておきましょう。

運営メンバー

できれば準備に3名、当日は4名いるとベストです。

準備の役割分担

  • 方針ぎめ・スケジュール管理
  • デザインコンテンツ制作
  • 発注等の作業

で、それぞれPdM、デザイナー、総務のご担当者がいると良いです。

ただ、技術カンファレンスの場合はエンジニアの方のアイデアを活かしたコンテンツがあるのが望ましいので、当然エンジニアの方による協力が必要となります。

当日の役割分担

  • 設営
  • 参加者の方との交流

の2種類があり、大体

  • 設営は2名
  • 参加者の方との交流をする当日メンバーは4名

くらいがベストです。 設営は究極1名でもOKですが、背が高い展示物があるときやテーブルクロスを引くときにバランスをとるのが難しくて手間取ります。

参加者の方と交流するメンバーが4名いると嬉しいのは、

  • カンファレンス特有のスタンプラリー等の対応
  • アンケートの実施
  • プロダクトのデモや名刺交換
  • ほか、楽しい世間話

などの作業を余力をもって対応するためです。

3名でもそこそこ対応できますが、誰かが登壇で出払っているときついので4名がベストです。

出展準備

ここまでは準備以前の前提情報のまとめでしたが、ここからは出展準備の内容をまとめます。

スケジュール確認

なにはともあれ、まずはスケジュールを確認しましょう。

開催日の前に当日の配布物を郵送したり、その内容を手前のタイミングで運営の方にお伝えする作業が発生します。

ある程度ガントチャートを引いて時系列で管理しないと、締め切りが五月雨に発生して混乱してしまいますので、注意が必要です。

冒頭のファインディさまの記事にもございますが、欲を言えば2か月前から準備はしたいです。今回はそれよりちょっとタイトだったので、若干うめき声を上げながら作業してなんとか間に合いました。

宿泊施設・交通手段の手配

意外と忘れがちなのが、宿泊施設と交通手段の手配です。

オフィスの近くで開催される場合は不要かもしれませんが、地方開催や関東・関西を行き来するケースだと早め(少なくとも1か月前に)手配しておくことをおすすめします。

今回のTSKaigi Kansai 2024は京都のみやこメッセで開催されましたが、秋の京都の宿泊施設はあっさり埋まり、価格が高騰します。これ自体は想像に難くないと思いますが、実際直前に価格を見てみると海外旅行かと思う値の上がり方をしていました。

キックオフMTGの実施

やっていくぞ!という気合を入れ、用意が必要なものやノベルティの内容などを仮ぎめするキックオフMTGを実施しましょう。

めっちゃ大事です。特にノベルティをこの段階で決めておくのをおすすめします。

アジェンダの例は以下のとおりで、所要時間30分くらいでしょう。

  • スポンサーブース出展の目的の確認
  • 用意しなきゃいけないものの確認と、備品・制作物以外の役割分担ぎめ
  • ノベルティ仮決め
  • ブースコンテンツ仮決め

ノベルティの決め方

私達は参加者の方視点で以下のような基準でノベルティを選定しました。

  • たくさんあっても困らないもの
  • 帰宅して見直せるもの
  • かさばらないもの

また上記の視点に比べると重要度は下がりますが、出展側の視点で「在庫になっても困らない」という基準も重要です。

上記を踏まえて今回は「マイクロファイバークロス(メガネ拭き)」とステッカーとなりました。

クロスなんてなんぼあってもええですからね。

制作物・備品の整理

ブースに向けて細々としたもの、大きいもの問わず、様々な制作物・備品の準備が必要です。

弊社は今回が初出展でしたが、他の会社様が錚々たるお名前だったため、見劣りしないように必要なものは揃えました。

デザイン制作物

デザインを制作して作成したものは以下のとおりです。

  • ロールアップバナー
  • テーブルクロス
  • ノベルティ
  • A4ビラ
  • LPでの採用告知用画像
  • A1パネル
  • 名刺
  • 名札
  • 企業ブース用の服
  • スライドテンプレート(スポンサーセッションがある場合)

付け加えるなら、A2パネルで機能解説などの技術的コンテンツを用意しておくと、もっと面白かったかもと反省しております。

備品購入

Amazonやモノタロウ、楽天などで購入できるものはそちらで購入しましょう。

具体的には以下のとおりです。

  • テーブルクロスおさえ
  • デモ用モバイルディスプレイ
  • パネル用イーゼル
  • 名札用ネックストラップ
  • ポストイット
  • 筆記用具

イーゼルは自立して軽すぎなければOKです。

テーブルクロスおさえ名札用ネックストラップは想像以上のコスパでした。

また、可能であれば名札用ネックストラップに、名札と合わせてNFCカードを入れましょう。

例えばXのリンクをカードに設定しておけば、お名刺を持ち合わせていらっしゃらない方とでも、SNSで相互フォローになれますので。

またこれは自分たちの反省点でもあるのですが、デモ用モバイルディスプレイはできればタッチパネル式のもの、持ち運び可能なもののほうがよいでしょう。

据え置きのディスプレイと立ちながらスライドなどを見せられるiPadなどがあると嬉しいですね。

デザイン作業

先ほど「デザイン制作物」にまとめたものを作成します。なお、これらのほとんどはAdobe Illustratorで作成します。

Figmaでいけるのは画像をpngで提出する告知用画像くらいで、業者発注が必要なものはすべてイラレでの制作が前提です。

なぜなら、カラー設定や印刷時の余白があらかじめ設けてあるテンプレートが大体イラレのaiファイルで配布されているからです。

加えて注意点として、カラー設定はsRGBではなくCMYKが前提となるので、コーポレートカラーの色味が想定とズレてしまわないように、場合によっては微調整しなければなりません。

また、印刷前には文字のアウトライン化や画像の埋め込み作業が必要です。アウトライン化は1回適用すると戻せないので、清書済みのファイルと発注用ファイルを分けておくことをおすすめします。

発注用ファイルをわける

コンテンツ発注

デザイン制作物は発注が必要です。 今回は制作物を以下のような業者様に発注しました。

基本的にラクスルさまで色々まかなえるのですが、一部納期が短いものや費用、ロット数の制限が気になるものは別の業者様を選んでいます。

ユニクロさまで服が発注できたのはかなり体験がよかったです。品質に信頼がありますからね。

ですが、freeeさんのようにオリジナルデザインで手の込んだものを作りたい場合は別の業者様のほうがいいかもしれません。

www.wantedly.com

他にオススメの業者様

今回ご依頼できなかったですが、以下の業者様もオススメです。

  • 秋葉原製作所
    • 普段は同人誌の印刷等を請け負われていますが、持ち込みでの大量印刷が可能です。
      • キャノン砲みたいな印刷機があります。
    • ビラもそうですが、名刺サイズ以外の厚紙であれば対応可能で、アクセアさまやキンコーズさまでもNGだった厚紙が印刷できました。
      • ちょっと気合の入った社内イベントなどで、ぜひ!
  • プリントパック
    • パネルの印刷時にsRGBでの印刷を受け入れてくださいます。
    • 写真が中心になるようなパネルは色味が大事なので、お願いするのをおすすめします。
  • 株式会社羽車
    • 紙製品の中でも箱や封筒、名刺などの厚めの用紙と印刷を扱っています。
      • 時間があればここで名刺を作りたかった
      • 表参道に店舗がございますので、ぜひサンプルを見に行ってください!

発送作業

前日などの指定日に到着日を指定して、備品や制作物の発送を行います。

ヤマト運輸さまでの発送はネット上から指定できますが、佐川急便さまでの発送はお電話での対応が必要なのと、厳密な時間指定が難しいものもあるので注意しましょう。

ブログやSNSでの宣伝

ブログやSNSで事前に告知して、当日お越し頂けるように促しましょう。

今回私達も、以下のブログ記事で告知してSNSでも何度か登壇予定をシェアしました。

tech.basemachina.jp

予習打ち合わせ

数日前や前日に、当日ブースにお越しいただいた方への対応の流れやスケジュールを運営チームで確認しましょう。

何も想定がないと緊張で堅くなってしまうので、話しはじめと締め方だけでも認識合わせをしておきましょう。

当日

設営

朝来たらブースの設営を行います。大体20分くらいで完了します。

段ボールから梱包物を開けたり、レイアウト調整をするのを加味すると30分あれば安心です。

このとき、カッターを持ってくると楽に済みます。(※今回は隣のブースにいらっしゃったナレッジワークさまにカッターをお借りしました。誠にありがとうございました!)

なお、返送用に段ボールをとっておけるものはそのまま控室などに置いておくと良いでしょう。

参加者の方との交流

事前の打ち合わせ通りに参加者の方との交流を行います。

一部のノベルティグッズや、当日の会場でのスタンプラリーのシールなどからお渡しすると、会話のきっかけが生まれやすいです。

じっとこちらを見てくださっている方がいたら、あまり受け身にならずせっかく来てくださったお礼を言いつつ積極的にお話ししましょう。

なお、セッションの聴講に向かう途中で会話してくださった方には、長時間お話しすることで聴講に差し障りがあるとご迷惑なので、タイミングを見計らって移動を促してさしあげるとよいでしょう。

ブースではダラッとした姿勢で座ったりはせずに、せめて崩れてしまったレイアウトを直したりしておきましょう。

撤収

撤収作業はスピーディかつあまり音を立てないように行いましょう。

最初の設営よりも実はこちらのほうが時間がかかります。

返送用の段ボールにものを詰め、次に使えるノベルティなどは丁寧に包みましょう。

なお、今回は私が初ブースで経験が浅く、返送用段ボールを手配するのを忘れてしまいました。

翌朝ヤマト運輸さまの営業所に直接持ち込んだのですが、ロールアップバナーが重すぎて死ぬかと思いました。絶対に段ボールは手配しましょう。

まとめ

以上がブース出展の準備や当日の手引きでした。

カンファレンスの準備についてインターネット上でブログ記事などを探してみたのですが、網羅性の高い情報が意外と多くなかったため、自分たちの振り返りも兼ねつつ、今後ブース出展をされる方の一助になればと思いまとめてみました。

また、当日お越しいただいた方、アンケートにご協力いただいた方、TSKaigi Kansai 2024の運営の皆様、そして素晴らしい発表をされたスピーカーの皆様、本当にありがとうございました!

弊社一同、参加者のみなさまや、すでに弊社サービスをご利用いただいているお客様など、様々な方にお会いできて嬉しかったです。ぜひほかのカンファレンスでもお会いしましょう!

おまけ

宣伝です!ベースマキナでは積極的にエンジニアの方を募集しております。

当日ブースでお話しできなかった方も、オンラインでお話しさせていただけますので、ぜひご連絡ください!

私の個人のXが一番早く返信できると思います。よろしくお願いいたします。

https://x.com/__timakin__

また、管理画面の作成に困っているよという方もぜひ以下のサービスのお問い合わせフォームからご連絡ください。

トライアル環境や料金プラン等、詳細なご説明をすぐにご連絡いたします。

そもそもサービス利用だけでは解決しないくらい複雑な業務や古い管理画面がある。。。ということであれば、仕様を紐解いたりするところから伴走いたしますので、ぜひそちらもご相談ください。

about.basemachina.com

ベースマキナはTSKaigi Kansai 2024にスポンサー&登壇します!

こんにちは!ベースマキナ代表取締役社長をしております、timakinです。

2024年11月16日(土)に開催される「TSKaigi Kansai 2024」に、ベースマキナはゴールドスポンサーとして協賛します。

今回のブログでは、セッション内容やスポンサーブースをご紹介します!

TSKaigi Kansai 2024とは

以下、公式ページからの抜粋になります。

TSKaigi Kansai 2024は、2024年5月に東京で開催されたTSKaigi 2024から派生した初の地域型イベントです。

https://kansai.tskaigi.org/

TSKaigi Kansai 2024の派生元イベントのTSKaigi 2024は、東京の中野で開催されました。オンラインも含めると参加者2000人越えの大盛況の会で、事前に発表されたプロポーザル審査員の方のお名前を見て、なんだか楽しみだ!となった方も多かったのではないでしょうか。

前回採択された方のインタビューをブログに掲載されていて、これまでプロポーザルを提出したことがない方に対して門戸を開く工夫をされている点でも、すごく丁寧に準備されたイベントだなと感じました。

開催日時・会場

開催日時は2024年11月16日の10:30~18:00(開場10:00/懇親会~20:00)です。

会場は京都の「みやこめっせ」です。京セラ美術館のすぐ近くですね。京都駅からですと、電車よりバスの方が早いみたいです。

今回弊社がスポンサーする理由

弊社がスポンサーブースを置くのは、今回のTSKaigi Kansai 2024が初です。なぜこのタイミングでスポンサーを決めたのか、その理由を書かせていただきます。

ベースマキナは汎用の管理画面を作るサービスとして、画面作成のコアからサーバーサイドでお客様のJavaScriptのコードを実行する基盤など、サービスのあらゆるところでTypeScriptの恩恵を受けています。

自分たちのサービスの支えになっている技術に間接的にでもお返しをできればと思い、今回のスポンサーを決めました。

何より私自身もカンファレンス運営に携わることが度々あったのですが、自分が会社を作ったときにスポンサーとして参加するのは1つの目標だったので、今回の機会をとても嬉しく思っています。

一方で、弊社のサービスを自分たちがまだ出会っていない方々に知っていただきたいという思いもございます。

弊社では少し前からテックブログを始めたり、リリースのお知らせを通じてサービスのことをオンラインで知っていただく機会が少しずつ増えています。

とはいえ既にSNS上などで知り合いだったり、サービスのことをご存知の方が多い中で、これまで接点のなかったエンジニアの方に新たに弊社を知っていただく機会が少ないのが現状です。

そこで、まだ弊社のことをご存じない方にもベースマキナというサービス・会社を知っていただくためにスポンサーを行いたいと考えておりました。

サービス特性上、技術的に面白いネタは多々あるので今後も技術イベントでのスポンサー活動を積極的に行っていきたいと考えております。

また、リアルイベントでのスポンサー活動はお客さまの生の声を伺える機会でもあります。ブースでお話する中で弊社のことを知って頂くだけではなく、開発組織での管理画面開発のご課題も是非伺えればと思いますので、お気軽にブースに立ち寄って頂ければ幸いです!

ベースマキナのエンジニアが登壇するセッション

ベースマキナからは公募による採択で「30分セッション」「LT枠」、そして「スポンサーセッション」の3枠で登壇の機会をいただきました。

採択されたセッションの内容は以下の通りです。

セッション名 登壇枠 名前
ローコードSaaSのUXを向上させるためのTypeScript 30分セッション taro
初めてDefinitelyTypedにPRを出した話 LT枠 syumai

こちらに加えてyebis0942がスポンサーセッションで登壇します。

弊社は汎用の管理画面開発基盤のサービスという特性上、技術的な工夫が求められる中でTypeScriptの恩恵を多々受けています。

当日は具体的にTypeScriptを活用しているポイントやノウハウをお伝えする予定ですので、ぜひお越しください!

スポンサーブースについて

スポンサーブースも出展いたします。

ベースマキナがスポンサーブースを出展するのはこれが初めてのため拙い部分もあるかと思いますが、鋭意準備中です。

ブースではベースマキナを普段開発しているエンジニアが直接対応いたします。

登壇と直接関係のない技術的なお話や、今すでに運用されている管理画面のご課題の相談も大歓迎ですので、ぜひお越しください。

参加者の方に配布予定の各社様の資料・グッズに、ベースマキナのノベルティグッズも同封される予定ですので、お楽しみに!

終わりに

来月半ばで意外とあっという間にイベント当日が来てしまいそうです。当日直接お話ししたり、オンラインで登壇をご視聴いただいた方は是非感想や疑問点をツイートしていただければ幸いです。

当日以降も技術ネタの雑談から弊社のことを知ってみたいと思った方、お気軽に登壇予定のメンバーや私のアカウントにDM等でご連絡いただければ幸いです。

それでは京都でお会いしましょう!

Playwrightでライブプレビューツールを実装する

こんにちは、yebis0942です。

先日、@basemachina/bm-view-previewというツールをnpmで公開しました。ベースマキナのビュー機能ソースコードをローカル環境で編集しながら、Next.jsのnpm run devのようにライブプレビューで動作を確認できるツールです。

ライブプレビューはPlaywrightによってコントロールされたChromiumで表示しています。PlaywrightをE2Eテストや自動化以外の用途で使うのは珍しい事例かと思いますので、その背景と実装の裏側についてご紹介します。

ビュー機能とは

ベースマキナではビュー機能というReactベースの管理画面作成機能を提供しています。ベースマキナのデフォルトの画面では不十分な場合に、Reactによって柔軟に画面を実装することができます。専用の関数を実行することで、ベースマキナのアクション実行やジョブ実行などの機能を呼び出すことができます。

編集画面では、以下のようにコードエディタとプレビューエリアを用意しています。コードエディタにReactのコードを入力すると、プレビューエリアが更新されます。

ビュー機能の編集画面

ローカル環境で開発したい

ビュー機能でまとまった画面を開発する事例が増えるにつれて、ローカル環境で開発を行いたいというご要望をいただくようになりました。

具体的には

  • 使い慣れたエディタでコードを書きたい
  • tsc(TypeScript)のようなトランスパイラーや、Webpackのようなバンドラーを使いたい

といったご要望です。

この記事では詳細は割愛しますが、ビュー機能ではレンダリング時にクライアントサイドJavaScriptソースコードのトランスパイルやモジュール解決を行っており、ベースマキナのアクションなどの呼び出しに関してはサーバーサイドの機能に大きく依存しています。

このような背景から、ローカル環境のみで完結するプレビュー機能を提供することは現実的ではありませんでした。

bm-view-previewの内部構成

今回は、Playwrightを組み込んだNode.jsアプリケーションを提供することで課題の解決を試みました。

bm-view-previewはnpmでパッケージとして配布されているNode.jsアプリケーションです。bm-view-previewコマンドを起動すると、Playwrightを通してChromiumを立ち上げ、basemachina.comのビューの新規作成ページを開きます。そして、ローカル環境のソースファイルの更新を監視して、ビューの新規作成ページ内のコードエディタに随時その内容を流し込んでいきます。

このような構成にすることで、既存の実装を利用する形でローカル環境でのライブプレビューを実現しています。

ライブラリとしてのPlaywright

Playwrightには、E2Eテストなどの機能を提供する@playwright/testパッケージのほかに、ブラウザの操作などの基本的な機能を提供するplaywrightパッケージがあります。

以下のようにplaywrightライブラリを直接使うことで、ブラウザを起動して操作することができます。

// 操作対象のブラウザとしてchromium, firefox, webkitがimportできる。今回はchromiumを使う。
import { chromium } from "playwright";

(async () => {
  // ブラウザを起動する
  const browser = await chromium.launch();

  // タブを開く
  const page = await browser.newPage();

  // 開いたタブでhttp://example.com/にアクセスする
  await page.goto("http://example.com/");
})();

ブラウザを人の手で操作できるようにする

これでブラウザを動かすことができましたが、Playwrightはデフォルトでは自動テストやオートメーションに使われることを想定した設定となっています。そのままでは人の手でブラウザを操作するには不便なので、設定をいくつか変更します。

ウィンドウを表示する

Playwrightはデフォルトではウィンドウを非表示となっています。launchメソッドのオプションに headless: false を渡すことでウィンドウを表示させることができます。

 import { chromium } from "playwright";

 (async () => {
-  const browser = await chromium.launch();
+  const browser = await chromium.launch({
+    headless: false,
+  });
   const page = await browser.newPage();
   await page.goto("http://example.com/");

ウィンドウのリサイズに対応する

Playwrightはデフォルトではviewport(ページの描画領域)のサイズが1280x720に固定されています。そのため、ウィンドウを広げても描画領域は広がらず、以下のように空白の領域が現れます。

ウィンドウを広げても表示領域は広がらず、画面右と画面下に空白が生じる

これは自動テストの実行結果を安定させるための意図的な挙動です*1

とはいえユーザーがウィンドウを操作する場合には不便なので、以下のように設定して無効にしておきます。

 (async () => {
   const browser = await chromium.launch({
     headless: false,
+    viewport: null,
   });
   const page = await browser.newPage();
   await page.goto("http://example.com/");

ダイアログを自動で閉じない

Playwrightはデフォルトではダイアログ(window.alert, window.confirm, window.promptによって開かれるネイティブのダイアログ)をすべて自動で閉じます。ダイアログが開いたままではページの操作がブロックされ、テストが進められなくなってしまうためです。

ですが、ユーザーがウィンドウを操作する場合にはこのような挙動は不要です。以下のように、dialogイベントに対して何もしないハンドラを明示的に指定して無効にしておきます。

page.on('dialog', () => {});

できなかったこと

アプリケーションモードでの起動

ChromiumGoogle Chromeではchromium --app=https://example.comのように起動オプションを指定することでアプリケーションモードとなり、URLバーやタブ一覧が隠された状態で起動します。

プレビュー画面だけを表示するのにちょうど良さそうな機能ですが、Playwrightではこの機能は無効化されており、使うことができません。

自動テスト

bm-view-preview自体のE2Eテストを実装することができていません。PlaywrightによってコントロールされているChromiumを、さらに別のテストコードから制御して動作を検証するための適切な手法が見つけられていないためです。

そのため、現在はリリース前の動作確認を手動で行っています。

まとめ

Playwrightによるライブプレビューツールの実装についてご紹介しました。

このようにライブラリとしてのPlaywrightを利用することで、通常のウェブブラウザだけでは実現できない機能を比較的容易に追加することができます。また、Webアプリケーション本体に変更を加える必要がないという点も、場合によっては大きなメリットになりうると感じました。

一方で、Playwrightの制御下のブラウザはアプリケーションとして利用することが想定されていないため、いくつかの課題があることも分かりました。

ビュー機能では、今回ご紹介したライブプレビュー機能と「コード取得設定」を合わせて使うことで、ベースマキナの管理画面の構築機能を活用しながら、通常のフロントエンド開発に近い開発体験で画面を作り込むことができるようになっています。今後とも、利用体験の改善の裏側をご紹介できればと思っています。

*1:It makes the execution of the tests non-deterministic. Playwright - viewport

Cloudflare WorkersのCron Triggersでリリース当番通知botを作った話

こんにちは、syumaiです!

ベースマキナでは、現在Cloudflare WorkersのCron Triggersを活用したリリース当番通知botを社内で運用しています。このbotは、リリース対象日の朝にリリース担当メンバーにメンションを行います。実装はTypeScriptで行われています。

今回の記事では、

  • なぜリリース当番通知botを作ることにしたのか
  • なぜCloudflare Workersを使ったのか
  • Cloudflare Workersによる定期実行Workerの実装例

などについて紹介させていただきます。

なぜリリース当番通知botを作ることにしたのか

もともと、ベースマキナでは、リリース担当のメンバーを特に決めていませんでした。リリース担当が決まっていないと、自然と「直近機能開発を行ったメンバー」がリリースを自主的に行うようになります。すると、タスクの持ち具合によって、設計主体のメンバーはリリース作業の頻度が低く、開発主体のメンバーはリリース作業の頻度が高くなり、不均等な状態が生まれていました。

リリース作業の頻度が不均等になると、

  • リリースそのものにかかる作業時間
  • 作業工程に対する理解度

についても同様に均等性が失われてしまうという問題があります。リリース作業は極力自動化されているとはいえ、リリース前に確認しないといけないことが色々ありますし、作業中の待ち時間が発生することもあります。また、リリースの作業工程に対する理解度が低いと、よくリリース作業を行っているメンバーが欠席した場合や、緊急時のHotfix対応が必要になった場合に作業がうまく行えないリスクがあります。

こうした問題を防ぐために、「リリース当番を輪番制で自動的に割り当て、チームメンバー全員が確認しやすい形で通知する」というのが今回実現したいことでした。

なぜCloudflare Workersを使ったのか

Cloudflare Workersは、実は「定期的にJavaScriptを実行する基盤」として非常に手軽です。

本来、Cloudflare WorkersはCDNエッジでJavaScriptの実行を行い、クライアントからのリクエストや、オリジンサーバーからのレスポンスに対して加工を行なう、リバースプロキシ的な用途が主体となっています。 しかしながら、ここ近年、リバースプロキシ的な用途だけに限らず、Cloudflare Workers単体でアプリケーションとして利用するのに十分な機能が提供されています。 例えば、Cloudflare WorkersからJavaScript SDKで簡単に呼び出せるWorkers KV (キーバリューストア)、D1 (リレーショナルデータベース)、R2 (オブジェクトストレージ)などがあります。

いまや、Cloudflare Workersは単にJavaScript製アプリケーションの実行環境としてみなすことができ、リバースプロキシ的な使い方はあらゆる用途の中の一つでしかないと考えるのが適切でしょう。

今回は、Cloudflare WorkersのCron Triggersという機能をWorkerの定期実行に利用しました。

Cron Triggersとは

Cloudflare Workersは、Triggersと呼ばれる機構によって起動されます。 設定できるTriggersの種類の一覧は、Cloudflareのダッシュボード内にあるWorkerの設定ページの、「Triggers」タブから確認できます。 Cloudflare Workersを起動するための最も一般的な方法はWorkerのURLに対するHTTPリクエストですが、これもTriggerのひとつとして見ることができます。そして、HTTPリクエストなしでWorkerを起動する方法もあります。 例えば、メールの受信をTriggerとしてWorkerを起動するEmail Triggersがあります。 こうしたHTTPリクエスト以外の方法で起動されるWorkerは、HTTPによるエンドポイントを完全に無効化して使うこともできます。

Cron Triggersは、cronスケジュールを使ってWorkerを定期実行するためのTriggerです。 無料プランでは全部で5つまで、有料プランでは250個までと、作成数に限りはありますが、追加料金無しで気軽に使うことのできる機能となっています。

Cloudflare Workersによる定期実行Workerの実装例

実装したのは以下の機能です。

  • Slack通知
  • 当番の割り当て
  • 土日・祝日と、その前日のSlack通知の停止機能

完成形のコードは以下のリポジトリにて公開しています。

github.com

Slack通知

jsx-slackを使っています。jsx-slackを使うと、Slackのメッセージを表現するBlockをJSXで簡単に書くことができます。 本来、SlackのメッセージのBlockはJSONで表現する必要があり、直接JSONを書くか、何かしらのライブラリを導入した上で(JSXではなく)コードで組み立てる形になります。 これをJSXによって代替することにより、大幅に学習コストを下げられていると感じます。メッセージの構造が視覚的にわかりやすいので、レビューもしやすいです。

export const postReminder = async (webHookUrl: string, members: string) => {
  const parsedMembers = parseMemberSlackIds(members);
  const blocks = (
    <Blocks>
      <Section>
        <a href={`@${getTodayMemberSlackAccountId(parsedMembers)}`} />
        <br />
        本日のリリース確認作業をしましょう!
      </Section>
    </Blocks>
  );
  return await postBlocksToSlack(webHookUrl, blocks);
};

https://github.com/basemachina/release-reminder-worker/blob/85044854e6ba795b6b738fd9cef83be56b254433/src/reminder.tsx#L24-L36

実際に送信されるメッセージの表示例

メッセージの送信処理は、単にWebHook URLにPOSTでJSON化したBlockを送っているだけです。 Cloudflare Workersからは、(当然ですが)このようにfetchを呼び出して外部にリクエストを送ることが簡単にできます。 ややこしいのがメッセージのBlockを組み立てる箇所だけだったので、Slack通知関連でjsx-slack以外のライブラリは導入せずに済みました。

export const postBlocksToSlack = async (webHookUrl: string, blocks: any) => {
  return await fetch(webHookUrl, {
    body: JSON.stringify({ blocks }),
    method: "POST",
    headers: { "Content-Type": "application/json" },
  });
};

https://github.com/basemachina/release-reminder-worker/blob/85044854e6ba795b6b738fd9cef83be56b254433/src/slack.ts#L1-L7

当番の割り当て

当番の割り当ては、単にメンバーのリストを日で割って行っています。今は均等に回せていますが、メンバー数が7の倍数になると曜日が固定になってしまうので、何かしらの調整方法を考えます…! メンバーのリストは、名前:SlackIDの組でCloudflare Workersの環境変数に持つようにしています。これはハードコードしてしまってもよかったのですが、公開リポジトリに切り替えやすくしたかったためこのようにしています。 実際の運用としては、以下のような設定内容をwrangler.tomlに直接commitしています。

[vars]
# `名前:Slack ID`の形式で改行区切りで指定する
MEMBER_SLACK_IDS = """
example1:UXXXXXXXXXX
example2:UXXXXXXXXXX
"""

https://github.com/basemachina/release-reminder-worker/blob/85044854e6ba795b6b738fd9cef83be56b254433/wrangler.toml#L13-L18

土日・祝日と、その前日のSlack通知の停止機能

土日・祝日と、その前日は原則リリース作業を行わないため、通知を行っていません。

まず、wrangler.tomlに設定するCron Triggersのスケジュール自体を、金・土・日を除外した内容にします。

[triggers]
crons = ["0 1 * * MON-THU"] # 日本時間の月〜木の朝10時に実行

https://github.com/basemachina/release-reminder-worker/blob/3eba148ddc2bccfa84259c544c821519b3eee46f/wrangler.toml#L7-L11

この上で、祝日を考慮した除外処理を追加します。 日本の祝日情報を配布しているholiday_jpと言う便利なライブラリがあったので、今回はこちらを利用させていただきました。

export const isHolidayOrBeforeHoliday = () => {
  const today = new Date();
  const tomorrow = getTomorrow();
  return holiday.isHoliday(today) || holiday.isHoliday(tomorrow);
};

https://github.com/basemachina/release-reminder-worker/blob/1c74af5b0865574b1aa565e675e88014d363de2d/src/holiday.ts#L10-L14

あとは、Cron Triggersによって起動されるscheduledハンドラーに、この除外処理によるフィルタを行った上で送信処理を実装するだけです。

const postMessages = async (webHookUrl: string, members: string) => {
  if (isHolidayOrBeforeHoliday()) {
    return;
  }
  await postReminder(webHookUrl, members);
};

export default {
  scheduled: async (_, env) => {
    await postMessages(env.SLACK_WEBHOOK_URL, env.MEMBER_SLACK_IDS);
  },
} satisfies ExportedHandler<Env>;

開発・運用してみた感想

開発自体は、元々Cloudflare Workersを使ったことがあったのですが、やはり楽でした。

まず、Cloudflare Workersの開発・デプロイに使うWranglerは、TypeScriptのトランスパイル、成果物のバンドルを巻き取ってくれるので、Webpackの設定などを特に書く必要がなく非常に手軽でした。

また、Cron Triggersのデバッグも、開発サーバーをwrangler dev --test-scheduledのフラグ付きで起動すれば、Cronスケジュールを待たずに済む、デバッグ用のエンドポイントを生やしてくれるので便利でした。(curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*" で呼べます)

特に不具合もなく、運用もつつがなく回っています。日次でメンションされたメンバーが担当者となることでリリース機会が均等化されましたし、「あとでリリースしないと」という意識を持たなくても自然とリリースが行われるようになったので、心理的負荷が減ったと感じています。

おわりに

Cloudflare Workersは、思った以上に手軽に導入できる、あらゆる用途に利用可能なJavaScriptアプリケーションの実行環境です。 今回のように運用オペレーションの改善にも活用することができます。 JavaScriptを定期的に実行したいと思った時に、ぜひ選択肢として検討してみてください。

少々個人的な宣伝ですが、Software Design 2024年8月号にて、Cloudflare WorkersのCron Triggersについてのより詳しい解説を執筆しました。もし興味があれば、ぜひこちらもご覧ください。

gihyo.jp

TypeScript/JavaScriptの不要なコードを削除するツール「Knip」の紹介

こんにちは、taroです!

今回は、ベースマキナのTypeScriptのプロジェクトで不要なコードの検知・削除で使用しているKnipについて紹介します。

Knip とは

Knipは、TypeScript/JavaScriptのコードベースの不要なコードを検出するCLIツールです。

以下が検出できる不要なコードの例です。

  • package.jsondependencies/devDependenciesの中で使われていないpackage
  • exportされているがどこからもimportされていない変数、関数、型など
  • 使用していないファイル

その他、検出できる内容の一覧はこちらで確認できます。

またExperimentalな機能(2024年7月現在)として不要なコードの自動削除も可能です。

ちなみにTypeScript/JavaScriptの不要なコードの検出するツールではts-pruneも知られていますが、ts-pruneは2023年12月にPublic archiveされており、READMEでもKnipの使用が案内されています。

🚨 ts-prune is going into maintanence mode

Please use knip which carries on the spirit.

https://github.com/nadeesha/ts-prune?tab=readme-ov-file#-ts-prune-is-going-into-maintanence-mode

Knipの使い方

Knipの使い方はとてもシンプルです。

対象のプロジェクトにインストールして、ルートディレクトリで実行すると不要なコードを検出できます。

# インストール
npm install -D knip typescript @types/node

# 実行
npx knip

# 出力例
Unused files (1)
company.ts
Unused devDependencies (1)
@testing-library/jest-dom  package.json
Unlisted dependencies (1)
jest-watch-select-projects  jest.config.ts
Unused exports (1)
userIds  unknown  user.ts:1:14
Unused exported types (1)
User  type  user.ts:2:13

Knipは"zero config"を目指しているツールであり、

  • 各種linterやtest runner、ビルドツールの設定で、import文を使わず暗黙的に読み込むpackageがある
  • monorepoで複数のpackage.jsonがある

といった場合でも、ほとんど追加の設定なしで使用できます。

個人的にこういった開発の補助的なツールは、設定が複雑だと導入後にエラーが発生した場合に、対処が放置されがちなのでzero configな点はとてもありがたいです。

ではKnipはどのようにして不要なコードの検出をzero configで実現しているのでしょうか?

Knipが不要なコードを検出する仕組み

Knipが不要なコードを検出する仕組みを理解する上で、重要な概念が以下の2つです。

エントリーファイル

エントリーファイルは、不要なコードを検出する際の起点となるファイルです。

デフォルトではindex.tsmain.tsxなどがエントリーファイルです。*1

Knipはエントリーファイルから順番にimportされているファイルを解析していき、使用しているpackageやexportされている変数、関数、型などを検出します。

ファイルの解析後、使用しているpackageをpackage.jsondependencies/devDependenciesと比較して、差分がある場合は

  • unused(使用していない)
  • unlisted(使用しているがdependencies/devDependenciesにない)

dependencyとして出力します。

Unused devDependencies (1)
dayjs  package.json
Unlisted dependencies (1)
date-fns  src/utils/date.ts

しかしindex.tsmain.tsxを解析するだけでは検出できないパターンがあります。

そこで登場するのがプラグインです。

プラグイン

プラグインは各ライブラリ(各種linterやtest runner、ビルドツールなど)で使用しているファイルから不要なコードを検出する機能です。

プラグインは主に以下の3つを行います。

  • 各ライブラリに応じたプラグインの有効化
  • 各ライブラリに応じたエントリーファイルの追加
  • 各ライブラリの設定ファイル内で使用しているpackageの検出

それぞれJestのプラグインを例に説明します。

各ライブラリに応じたプラグインの有効化

まずは各ライブラリに応じたプラグインの有効化です。

プラグインの有効化に追加の設定は不要です。

Jestのプラグインでは、package.jsondependencies/devDependenciesjestがあれば自動で有効化されます。*2

各ライブラリに応じたエントリーファイルの追加

次にエントリーファイルの追加です。

Jestのプラグインでは、**/__tests__/**/*.[jt]s?(x)**/?(*.)+(spec|test).[jt]s?(x)がエントリーファイルに追加され、テストファイルのみで使用しているpackageやexportされている変数、関数、型なども検出できるようになります。

各ライブラリの設定ファイル内で使用しているpackageの検出

最後に設定ファイル内で使用しているpackageの検出です。

Jestのプラグインでは、以下の設定ファイルを解析して使用しているpackageを検出します。

  • jest.config.{js,ts,mjs,cjs,json}
  • package.jsonjestの値

例えば、以下の設定ファイルがある場合、Knipはjest-watch-select-projectsを使用しているpackageとして検出します。

// jest.config.ts

export default {
  watchPlugins: ["jest-watch-select-projects"],
};

以上のようにKnipでは各種ライブラリを使用している場合でも、プラグインを使用してzero configで不要なコードを検出できるようになっています。

プラグインは多数用意されており、以下が一例です。

プラグインの一覧はこちらで確認できます。

プラグインでpackageが正しく検出されない場合の対処方法

Knipはzero configとはいえ、追加設定が必要な場合もあります。

ベースマキナで導入した際は、プラグインに関連したpackageが

  • 使用しているのにunusedなdependencyとして検出されてしまう
  • 使用していないのにunlistedなdependencyとして検出されてしまう

のように、正しく検出されない場合があったので、最後にその対処方法を紹介します。

正しく検出されないpackageを無視する前に検出できない原因を考える

この場合最初に思いつく方法は、Knipのプラグインの誤検出として正しく検出されないpackageをignoreDependenciesに追加する設定です。

// knip.ts

export default {
  ignoreDependencies: ["jest-watch-select-projects"],
};

私も当初はこの方法を取ることが多かったのですが、よくよく調べてみると誤検出ではなく、そもそもプラグインが有効化されてなかったり、ファイルが解析されていないことがほとんどでした。

そのため現在は、無視する前に一度そのpackageが検出できない原因を以下の流れで考えるようにしています。

プラグインが有効化されているか確認する

まずはそのライブラリのプラグインが有効化されているか確認します。

Knipでは--debugオプションをつけると、実行結果に有効化されたプラグインが表示されます。

npx knip --debug

# 出力例
# ...
[.] Enabled plugins
[ 'ESLint', 'Jest' ]
# ...

プラグインが有効化されていない場合

もし有効化されていない場合は、原因を調査してみます。

プラグインが有効化される条件は、ドキュメントの各プラグインのページの"Enabled"に記載されています。

例えばJestのプラグインが有効化される条件は以下のように記載されています。

This plugin is enabled when there’s a match in dependencies or devDependencies in package.json:

  • jest

https://knip.dev/reference/plugins/jest#enabled

またKnipはTypeScriptで実装されているためソースコードからも比較的簡単にプラグインが有効化される条件を確認できます。

https://github.com/webpro-nl/knip/tree/main/packages/knip/src/plugins

プラグインPlugin interfaceを満たすオブジェクトをexportしており、その中のisEnabledメソッドで有効化されるかどうかを判定しています。

例えば以下がJestのプラグインisEnabledメソッドです。

// https://github.com/webpro-nl/knip/blob/main/packages/knip/src/plugins/jest/index.ts#L11-L14

const enablers = ["jest"];

const isEnabled: IsPluginEnabled = ({ dependencies, manifest }) =>
  hasDependency(dependencies, enablers) ||
  Boolean(manifest.name?.startsWith("jest-presets"));

基本的にプラグインに対応したライブラリがpackage.jsondependencies/devDependenciesに含まれているかどうかが、プラグインが有効化される条件ですが、プラグインによってはそれ以外の条件が含まれている場合もあるので、一度ドキュメントやソースコードを確認するのがおすすめです。

プラグインが有効化されているが正しく検出されない場合

次はプラグインが有効化されているが正しく検出されない場合です。

Knipが不要なコードを検出する仕組み」で述べたように、Knipは主に以下の2箇所からpackageを検出しています。*3

そのため、次はプラグインが追加するエントリーファイルとプラグインに対応したライブラリの設定ファイルを確認してみます。

ドキュメントの各プラグインのページの"Default configuration"の

に記載されています。

例えばJestのプラグインでは以下のように記載されています。

This configuration is added automatically if the plugin is enabled:

{
  "jest": {
    "config": ["jest.config.{js,ts,mjs,cjs,json}", "package.json"],
    "entry": ["**/**tests**/**/_.[jt]s?(x)", "**/?(_.)+(spec|test).[jt]s?(x)"]
  }
}

https://knip.dev/reference/plugins/jest#default-configuration

有効化の条件と同様に、エントリーファイルと設定ファイルも該当する実装箇所を確認してみます。

エントリーファイルと設定ファイルは、Pluginentryconfigに対応しています。

以下がJestのプラグインentryconfigです。

// https://github.com/webpro-nl/knip/blob/main/packages/knip/src/plugins/jest/index.ts#L16-L18

const entry = ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"];
const config = ["jest.config.{js,ts,mjs,cjs,json}", "package.json"];

entryconfigを確認して、正しく検出されないpackageを使用しているファイルが含まれていない場合は、Knipの設定ファイルに追加します。

// knip.ts

export default {
  jest: {
    entry: ["**/__custom_tests__/**/*.ts"],
    config: ["jest.config.ts"],
  },
};

entryconfigを追加する場合は、デフォルトの値は上書きされるためご注意ください。

どうしても正しく検出されない場合はKnipにコントリビュートするチャンスかも?

もしどうしても正しく検出されない場合は、Knipにコントリビュートするチャンスかもしれません。

ベースマキナのリポジトリに導入した際には、以下の修正を行いました。

  • 不要コードの自動修正でignoreignoreDependenciesなどの設定が適用されないバグの修正

    github.com

  • GraphQL-Codegenプラグインで、正しく検出できるのが@graphql-codegen/で始まるpackageのみだったので、その他のpackageでも検出できるように修正

    github.com

  • GraphQL-Codegenプラグインで、GraphQL Configの設定ファイルのサポート

    github.com

  • webpackのプラグインで、webpackの設定ファイルのoneOfで使用しているpackageのサポート

    github.com

Knipは実装がTypeScriptで、またプラグインのみであればとてもシンプルなので、比較的理解がしやすいと感じました。

作者のLarsさんもとても親切でPull Requestを送るたびに、Tweetにも反応をもらえてとても嬉しかったです!

おわりに

今回は、TypeScript/JavaScriptの不要なコードを検出するツール「Knip」について紹介しました。

Knipはzero configを目指しているツールで、簡単に導入できるのでよかったらぜひ使ってみてください。

*1:正確には{index,main,cli}.{js,cjs,mjs,jsx,ts,cts,mts,tsx}、src/{index,main,cli}.{js,cjs,mjs,jsx,ts,cts,mts,tsx}、package.jsonのmain,bin,exportsのファイル、package.jsonのscriptsで指定されているファイルがエントリーファイルとなります。https://knip.dev/explanations/entry-files

*2:正確にはpackage.jsonのnameがjest-presetsで始まる場合にも有効化されます。https://github.com/webpro-nl/knip/blob/main/packages/knip/src/plugins/jest/index.ts#L13-L14

*3:正確にはpackage.jsonのscriptsからも検出しています。https://knip.dev/explanations/entry-files#scripts-in-packagejson

Goで0秒待つとどうなるか

こんにちは。yebis0942です。GoとTypeScriptを書いています。夏祭りのおみくじで「待ち人来る」を引いたので、最近のちょっとした待ち事例についてご紹介します。

Goでタイムアウト時間を指定する関数を呼び出したとき、待機時間を0秒にすると何が起きるのか?という点が社内のレビューで少し話題になりました。

気になって調べてみたところ、同じ0秒のタイムアウト処理でも、内部の実装によって振る舞いが異なるケースがあることが分かりました。

よく見るタイムアウト処理

Go言語では、一定時間だけあるchannelを待つというタイムアウト処理は以下のように time.After() を使って書くことができます。

func timeAfter(c chan int, duration time.Duration) {
    select {
    case <-time.After(duration): // durationの経過後に値が入るchannelを返す
        fmt.Println("Timeout")
    case <-c:
        fmt.Println("OK")
    }
}

context.WithTimeout() を使って、次のように書き換えることもできます。

func contextWithTimeout(c chan int, duration time.Duration) {
    ctx, _ := context.WithTimeout(context.Background(), duration)

    select {
    case <-ctx.Done(): // durationの経過後に値が入るchannelを返す
        fmt.Println("Timeout")
    case <-c:
        fmt.Println("OK")
    }
}

ちなみにtime.After()context.WithTimeout()も、内部的にはtime.NewTimer()を使ってタイマー処理を実現しています。

振る舞いの違い

外側も内側もよく似たコードですが、durationを0秒にして、値を送信済みのchannel c を渡してみると、振る舞いが異なることが分かります。

timeAfter() はほぼ常にOK を表示しますが、contextWithTimeout()OKTimeout を半々の確率で表示します。

どちらも内部では time.NewTimer() を使っていて、同じようにselectで待ち受けているのに、いったいなぜ…?

理由

この振る舞いの違いは、それぞれのchannelからの受信がブロックされるかどうかの違いによります。

たとえばバッファなしのchannelの場合、channelからの受信がブロックされるとは以下のような状況を指します。

c := make(chan int)
go func() { c <- 1 }()
<-c // 別のgoroutineから値を送信しているのでブロックされない
<-c // 受信できる値がないのでブロックされる

time.After() は待機時間が0秒であってもGoの内部のタイマーに登録して待ち受け処理を行なう

待機時間として0以下の値が指定された場合には、以下のように発火予定時刻を現在時刻に丸めてからタイマーに登録します。

func when(d Duration) int64 {
    if d <= 0 {
        return runtimeNano() // 注: 現在時刻をナノ秒で返す関数
    }
    // 略
}

time/sleep.go#L29-L31

そのため、タイマーが発火する前にselectの評価が開始されると、time.After()のchannelからの受信はまだブロックされたままです。

context.WithTimeout() は待機時間が0秒以下であれば即座に自身をキャンセルする

一方で、context.WithTimeout() は待機時間が0秒以下であればタイマーを登録しません。

以下のように、即座に自身をキャンセルします。

// 注: 実際の処理はWithDeadlineCause()に集約されている
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {
    // 略
    dur := time.Until(d)
    if dur <= 0 {
        c.cancel(true, DeadlineExceeded, cause) // deadline has already passed
        return c, func() { c.cancel(false, Canceled, nil) }
    }
    // 略
}

context.go#L630-L634

そのため、contextWithTimeoutではctx.Done()のchannelからの受信はブロックされていないことが保証されます。

select はブロックされていないchannelをランダムに選択する

では、なぜselectで上に置かれているchannel ctx.Done()ではなくchannel cが選択されることがあるのでしょうか?

The Go Programming Language Specification - Select statementsでは、selectのchannelの選び方を次のように説明しています。

If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.

参考訳: 1つ以上の通信が進行可能であれば、偏りのない擬似ランダムな選択方法によって、進行可能な通信を1つ選ぶ。

これにより、<-ctx.Done()<-cが均等に選択されることになります。

むすび

Go言語のタイムアウト処理のコーナーケースについてご紹介しました。

ちなみにGo 1.23ではtimerのchannelの仕様が改善されており、Reset()Stop()が安心して利用できるようになっています。8月中盤のリリースを楽しみに待ちたいと思います。

ベースマキナのエンジニアブログを始めます

こんにちは、syumaiです!

このたびベースマキナのエンジニアブログを始めることになりました!

本記事では、エンジニアブログを始めることになった経緯や、これからどういった内容について書いていくのかなどについて簡単に紹介します!

ベースマキナとは

今回は、一本目の記事ということで、簡単にサービスの紹介もさせてください!

ベースマキナは、一言で言うと管理画面のSaaSです。特に社内向けの管理画面をメインの用途としたプロダクトです。

社内向けの管理画面は専任のチームが存在しないことも多く、

  • ライブラリの更新、新機能追加のメンテナンスが滞る
  • 権限管理の実装を十分に行えない

といった課題を抱えている場合があります。

ベースマキナでは、任意のデータベースやAPI (REST API / gRPCなど) への接続を行ったうえで、

  • 処理実行用のフォームの生成
  • 入力値へのバリデーション
  • 権限管理、承認フローの追加

といったことが組み込みの機能で実現できます。

管理画面を開発するにあたって特に面倒な機能を一通り揃えているので、管理画面の維持コストの削減にご活用いただけます。

詳しくは、サービスの紹介サイトをご覧ください。

about.basemachina.com

それでは、ここから本題に入っていきます。

ベースマキナ エンジニアブログの目的

ブログ開設の目的は、端的に言うと広報です!

ベースマキナはリリースから2年以上が経過し、非常にありがたいことに、ご利用いただいているお客さまの数が順調に増えています。

その結果として数多くのご要望をいただいており、サービスを利用したいユースケースの幅もどんどん広がっています。自分たちでは想像もつかなかった提案をいただくこともあり、まさにお客さまと並走しながらサービスを開発している実感があります。

しかしながら、いただいたご要望や新しいユースケースに対応するスピードにはまだ満足できていません。 開発のスピードを上げていくには、当然のことですが組織の規模を拡大していく必要があります。 そこで最近ぶつかっているのが、サービス認知度の壁です。

YOUTRUSTさんの募集機能を使って社長のtimakinがエンジニアの方を対象に面談の募集を行っていましたが、面談に来てくださった方のほとんどが、もともとベースマキナのサービスを知っていて来たと言うより、YOUTRUST上で偶然募集を見付けていらしたようでした。

採用に関しても、新規のお客さまによるサービスの導入に関しても、まずは私たちのことを知っていただかないと始まらないということを痛感しています。

本ブログは、継続的にベースマキナの開発メンバーが発信を行うことで、ベースマキナのことを知っていただく機会を増やすことを目的としています。

ブログのコンテンツ

コンテンツとしては、

  • 新機能の技術的な背景
  • 意思決定の裏側
  • 開発に関する小ネタ

など、あまりテーマを限定しすぎず書いていこうと思っています。

ベースマキナでは、BackendにGo (gqlgen)、FrontendにNext.jsを採用しているので、それらに関連した記事が多くなると思います。

また、継続することを第一目標としているので、小さめの記事の割合が高くなりそうです。大きな機能のリリースに際しては、実装に大きく踏み込んだ紹介も行いたいです。

始めることになった経緯

(やや個人的な話ですが)最近オフラインのカンファレンスに行く機会が増え、会場で出会った方とお話しする機会が何度かあったのですが、「ベースマキナ」のサービス名を十分に認知いただけているとは言えないと感じました。 この状況をそのままにしておくことは出来ないということで、打ち手を考えないといけないと社内で相談を行った結果、会社のメンバーでブログを書いていくことになりました。

また、Go Conference 2024の懇親会でお話ししたcatatsuyさんからいただいたアドバイスや、tenntenn Conference 2024での「自社の魅力を候補者に知ってもらう方法」についてのパネルディスカッション (tenntennさん、赤川さん、パウリさんが登壇) も強い後押しとなりました。継続的な発信が組織を強化するということを、きちんと認識する機会をいただけました。パネルディスカッションについてはアーカイブも残っているので、気になる方はぜひご覧ください!

なぜはてなブログを選んだのか?

会社の開発チームの顔として技術記事を投稿する場を考えた時、ヘッダーなど、自分たちのサイトとしてカスタマイズしたページを表示できる点は重要だと思いました。

技術記事を投稿するプラットフォームに組織のアカウントを作成して記事を書いていくのも、執筆体験や、サービス内での露出の面で有利な点が多々存在すると思います。しかしながら、あくまでそのプラットフォーム自体に目が行ってしまい、「どの組織によって書かれた記事なのか」という意識はやや薄くなるという感覚を個人的には持っています。(個人差があると思います)

はてなブログはその点、細かいデザインのカスタマイズが出来ますし、独自ドメインを利用することもできます。 はてなさんは、企業の技術ブログを特集するページも用意されていたり、技術ブログが読まれやすくなるよう支援する取り組みを行っています。 ブログ記事をGitHubで管理・レビューできるようにするためのボイラープレートも用意してくださっていて、執筆体験も他のプラットフォームとほぼ遜色ないと感じています。

これらを踏まえて、今回ははてなブログさんを採用することに決めました。

最後に

今回は第1回目の記事ということで、サービスの紹介に始まり、エンジニアブログを始めるに至った経緯や目的について書かせていただきました。

これから、(無理のない範囲で)できる限り多くの記事を出していきたいと思っているので、もし応援していただけるという方はブログの購読をぜひお願いします!




以上の内容はhttps://tech.basemachina.jp/より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14