フォトシンス エンジニアブログ

株式会社Photosynth のテックブログです

Pull Requestテンプレートに寄せてOpenSpecを導入した話

こんにちは、@yellow-seedです。

システムの中心となっているモノレポに、仕様駆動開発フレームワークのOpenSpecを導入しました。

仕様駆動開発のツールには、良さそうだけれどチームに持ち込むのは大変そう、という印象がつきまといます。フル装備で入れようとすれば実際に大変です。ただ、導入の進め方を工夫することで、その大変さの大半は避けられました。

OpenSpec 導入前夜の課題

弊社は全社的にAI活用を推進していて、開発チームでもAIコーディングエージェントを日常的に使っています。AIを積極利用することについては全社的なコンセンサスが得られた状態からスタートしています。

一方で、どう指示するか、実装前にどこまで擦り合わせるかといった進め方が丸ごと個人任せになっていました。結果として次のような状態が生まれます。

  • 同じようなタスクでも、担当者によって成果物の品質が大きく揺れる
  • うまくいったやり取りが個人のチャット履歴の中に埋もれ、チームの再現可能な資産にならない
  • レビュアーから見ると、そのコードがAIとどんな合意のもとで書かれたのか追跡できない

揺れを収めるには、実装プロセスそのものに共通の型が必要です。個人のプロンプト力を鍛える方向で追いつく話ではありません。考えていたのはそこまでで、何を型にするのかは決まっていませんでした。

なぜOpenSpecだったのか

代替候補としては、GitHub製のSpec Kitと、AIに常時読ませる規約や設計方針をsteeringドキュメントとして自前で整備する案の2つ検討しました。

検討の末、OpenSpecを選びました。決め手は、公式ドキュメントに例示されているRapid Iteration Workflowを見つけたことです。ワークフロー定義のサンプルの1つで、本来4種類ある成果物のうち、提案を書くproposal.mdと実装チェックリストのtasks.md、この2種類だけでまわす最小構成になっています。

OpenSpecの標準成果物 役割 Rapid Iteration Workflow
proposal.md なぜやるか(背景・目的・影響範囲) 使う
specs/…/spec.md 何が変わるか(ADDED、MODIFIED、REMOVEDのデルタ要件) 使わない
design.md どう作るか(技術的な判断) 使わない
tasks.md 作業手順のチェックリスト 使う

フル装備の仕様駆動を丸ごと背負う必要はなく、まずこの2種類から始められる。それが分かって、導入の絵が具体的に浮かびました。

ここからは、OpenSpecに対して扱いやすいと感じた点を具体的に紹介していきます。

現在の仕様と変更提案をディレクトリで分けている

ディレクトリは、現在のシステム仕様を置くopenspec/specs/と、進行中の変更提案を置くopenspec/changes/に分かれています。1つの変更はchanges/配下の1フォルダに閉じていて、なぜやるかをproposal.md、何が変わるかをspecs、作業手順をtasks.mdが受け持ちます。

変更のspecはデルタ方式です。要件にADDED、MODIFIED、REMOVEDのマーカーを付けて変わる部分だけ書くため、既存システムの仕様を最初に全部書き起こす必要がありません。すでに動いているプロジェクトへ後付けするなら、ここが導入コストを直接左右します。

構造のチェックをCLIに任せられる

openspec validateは、変更ドキュメントの構造的な問題を機械的に検出します。進行中の変更を並べるopenspec list、中身を表示するopenspec showなど、CLIコマンドも一通り揃っています。

ドキュメントが揃っているか、次に何をすべきか。この判断をAIエージェントの読解力に委ねずに済みます。チェックの結果が毎回同じである以上、メンバーごとに使うツールやモデルが違っても、プロセスの品質は同じ基準で揃います。担当者によって成果物の品質が揺れるという最初の困りごとには、この仕組みがいちばん有効でした。

完了した変更を仕様へ畳み込む仕組みがある

変更の実装が終わったらopenspec archiveを実行します。すると変更フォルダのデルタ仕様がspecs/本体へマージされ、フォルダ自体は日付プレフィックス付きでchanges/archive/へ移動します。

完了のたびに現在の仕様が更新され、過去の意思決定は日付順に残ります。作業ドキュメントが散らかり続けません。なぜそう決めたのかの記録も、個人のチャット履歴からリポジトリの側へ移ります。仕様書とコードの乖離というドキュメント駆動につきまとう問題への回答も、最初からワークフローに組み込まれています。

すべてプレーンなMarkdownで、導入も撤退も軽い

成果物は全部ただのMarkdownです。専用のビューアも独自フォーマットもなく、リポジトリにコミットしてPRで普通にdiffレビューできます。仕様とコードが同じPRの同じ画面に並ぶので、そのコードがどんな合意のもとで書かれたのかをレビュアーがその場で追えます。やめたくなってもMarkdownの山が残るだけで、撤退コストもほぼありません。

工程を強制せず、柔軟さに重きを置いている

OpenSpecには、前工程を終えないと次へ進めないという関門がありません。公式が掲げるのもfluid not rigid、built for brownfieldです。Spec Kitとの比較記事でも、厳格な工程の関門を持つ重量級のSpec Kitに対して、OpenSpecは軽量で自由に反復できる点が対比されています。

Rapid Iteration Workflowのように、ワークフロー定義そのものをスキーマとしてカスタマイズし、成果物を減らすことも増やすこともできます。対応先はClaude Code、Cursor、Copilotなど30以上のAIツールで、スラッシュコマンド経由のため特定のIDEやモデルに縛られません。チームとしてはClaude Codeを推奨としていますが、CursorやGitHub Copilotも社内では利用可能なため、この柔軟性は相性が良いと判断しました。

段階的に入れるためにやったこと

いきなりフル装備で導入すれば、またルールが増えた、と受け取られます。抵抗をどこまで削れるかを最優先にして、次の順番で進めました。

事前準備としていつものPull Requestテンプレートと対応付ける

最初に手を付けたのは、OpenSpecの成果物と、チームにすでに定着していたPull Requestテンプレートの対応の整理です。

リポジトリには以下のようなテンプレートを導入しています。

## チケット
- [JIRA]()

## 概要
<!--
なぜやるのか。変更した目的を記載してください。(チケットで説明できない補足的な事項など)
- やったこと: 変更した内容を記載してください。
 - [ ] xxx
 - [ ] xxx
- やらなかったこと: レビューアーが指摘する時間を減らすため、このPRであえてしなかったことがあれば、その内容と理由を記載してください。なければこの項目を削除してください。
  - e.g) チケットにはxxxのタスクがあるがPRが肥大化するので、それは別PRで対応します
-->

## レビューして欲しい点・気になること
<!--なければこの項目を削除してください -->

## 動作確認手順
<!--
- 変更内容を確認した環境、方法を記載してください。

e.g.)
- 観点: 動作確認でなにを確認したかの観点を記載してください。
- 環境: ローカル or staging
- 方法: curlコマンドで確認した, Connect経由で確認した, etc.
- 手順: 確認した手順を記載してください。
  - 正常系
    - e.g) curlコマンドで確認した場合
      1. curl -X GET http://localhost:3000/api/v1/xxx
      2. レスポンスが200であることを確認する
      3. エビデンスを記載する
  - 異常系
    - e.g) curlコマンドで確認した場合
      1. curl -X GET http://localhost:3000/api/v1/xxx
      2. レスポンスが400であることを確認する
      3. エビデンスを記載する
-->

## Ref
<!--関連するPR、参考記事など、レビュワーがレビューするにあたっての補足情報。なければこの項目を削除してください -->

このテンプレートと OpenSpecの作成するドキュメントは、以下のような対応関係があると判断しました。

  • PRテンプレートの「背景・目的」 ⇄ proposal.md(なぜやるか・外部契約)
  • PRテンプレートの「作業内容チェックリスト」 ⇄ tasks.md(作業手順・タスク)

この整理があると、説明の中身が変わります。新しい概念を覚えてください、ではなく、いつもPRに書いているあの欄を実装の前に書くようになるだけです、と言えます。導入時の概念説明のコストが目に見えて下がりました。

同じタイミングで、開発補助ツールのバージョンを管理するmiseも入れました。アプリケーション本体とは独立したCLIツールの置き場所と配布方法を先に決めておいたので、OpenSpec CLIの導入はmise install一発で済むようにしました。

第1段階は公式サンプルをそのまま導入することから

最初のスキーマは、Rapid Iteration Workflowの例をほぼそのまま持ち込みました。独自カスタマイズに時間をかけず、proposal.mdとtasks.mdがPRテンプレートと対応する、その体験づくりだけに集中しています。

この段階ではspecs/への仕様の蓄積をあえて止め、軽量運用に振りました。

コマンドの構成には手を入れていません。openspec initはプロファイル設定に従ってコマンドを生成し、デフォルトのcoreプロファイルで入るのはpropose、explore、apply、archiveの4つです。Claude Codeなら/opsx:proposeのようなスラッシュコマンドと、その実体となるskillが組で置かれます。初期状態が、実装前に提案を書いて合意するという一点にほぼ絞られていたので、削る作業が要りませんでした。

生成物はリポジトリにコミット済みです。メンバー側はmise installでCLIを入れるだけで、各自がopenspec initを叩いて構成がばらつく余地は最初からありません。

第2段階で specs/ を解禁し、追加のコマンドも足す

軽量運用が回り始めた1か月後、デルタ仕様をspecs/へ蓄積する選択肢を開きました。全変更に強制はしていません。外部仕様が変わる変更など、蓄積する価値があるときに使える状態にしただけです。

解禁したあとも、specs/はあまり厚くなりませんでした。これまでにアーカイブした22件の変更のうち、仕様を積んだのは2件、design.mdまで書いたのは1件です。

アーカイブを振り返って気づいたのは、その大半が既存実装のリファクタリングと、溜まったデータの整理だったことです。外部から見える振る舞いが変わる変更は、そもそもそれほど出ていません。specs/の薄さは運用の不徹底ではなく、自分たちが普段どんな変更をしているかの反映でした。全件を強制にしていたら、この偏りは毎回きちんと仕様を書く手間の側に隠れて、見えないままだったかもしれません。

チームが手応えを感じ始めた段階で、coreには入っていない検証コマンド(openspec verify)を足しました。成果物と実装の整合性の確認まで含めて、フローを厳密にした形です。現在はここにいます。厳密さは最初に課すものではなく、手応えが出てから足す、という方針がAIを伴うツールの導入とは相性がいいと感じました。

用意中のドキュメント肥大化対策

運用を続けるとchanges/配下のフォルダは増え続けます。GitHub Actionsのcron起動で、完了済みの変更を定期的に自動アーカイブする仕組みを準備中です。openspec archiveが標準機能としてあるので、定期実行に乗せるだけで済みます。

導入してみての所感

実装前にproposal.mdで合意してから書き始めるため、AIとの往復回数が減りました。書き上がったものを見てそうじゃないとやり直す、あの手戻りが起きにくくなっています。

proposal.mdとtasks.mdが固まり切った状態で実装に入ると、AIが自走できる期間が明らかに伸びました。途中で人が割り込んで軌道修正する回数が減り、チェックリストを順に消化していく様子を見守るだけの時間が長くなっています。減った往復回数が、トークン消費のコストにも表れてきてくれることを期待しています。

いちばん大きな変化は、開発プロセスの型が固まったことです。まず提案を書き、合意してから実装する。この共通言語がチームにでき、AIの使い方の個人差をプロセスの側が吸収するようになりました。

誰におすすめできるか

導入して4か月、いちばん相性が良いのは、意思決定の記録をリポジトリの中に置きたいチームです。

ADRを運用しようとすると、たいていは採番ルールとテンプレート、置き場所を自前で決めるところから始まります。OpenSpecのchanges/archive/は、そのすべてを最初から持っています。日付プレフィックスが採番になり、proposal.mdが決定理由の書式になり、アーカイブ先が置き場所になる。しかも実装の直前に書くものなので、決定を後追いで思い出しながら書き起こす手間もかかりません。ADRのために別のツールを立てるくらいなら、このCLIを使ってしまうほうが早いです。

ほかに向いているのは、既存プロジェクトへ後付けしたいチームです。デルタ仕様のおかげで、現行仕様を全部書き起こす作業が要りません。メンバーごとに使うAIツールが違うチームにも向きます。成果物がMarkdownなので、誰がどのツールから書いても同じものが残ります。

逆に、最初から工程を厳格に縛りたいなら、Spec Kitのような重量級のほうが素直です。OpenSpecは工程を強制しないので、書かずに実装してしまうのを止める仕組みは持っていません。そこはチーム側の合意で支える前提になります。

今後の展望

次は実装プロセスの外側へ広げたいと考えています。AI活用のボトルネックは、実装より前後の工程に残っています。

  • リリースする際のビジネス部門への説明やQAエンジニアへのインプットでは、外部的な振る舞いのBeforeとAfterを定期的に説明します。proposal.mdはその外部契約を書く場所なので、リリース説明資料の一次ソースとして使えそうです
  • テスト管理ツールのQaseを、開発者による動作デモの記録蓄積に使い始めました。proposal.mdに書かれた外部契約からQase APIへテストケースを自動生成する連携(あるいは、Qaseのテストケースからproposal.mdの一部を生成する連携)フローも、現実味を帯びてきています
  • 節目ごとに人がAIの成果物へフィードバックを返したい場面があります。レビューを差し込むためのツールであるcrit.mdをフローに組み込む案は、まだ検討中です

まとめ

OpenSpec単体を新しいプロセスとして提案すると、説明のハードルが上がります。PRテンプレートのように先に定着している型と対応付けて出すのが、いちばん通しやすい入り方でした。仕様ドキュメントが溜まって大変になりそうという不安も、openspec archiveの定期実行で片が付くので、導入を止める理由にはなりません。proposal.mdがPR説明、リリース説明、テストケースといった最終成果物へ連続していくと示せると、実装者以外にも価値が伝わります。

正直なところ、ここまで抵抗なく回るとは思っていませんでした。導入前に困っていた品質の揺れは、プロセスの側でかなり吸収できています。この型を実装の外側まで伸ばせるかは、これからです。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。

career.photosynth.co.jp

Akerunにご興味のある方はこちらから

akerun.com

「課題があることに慣れてしまう文化」からの脱却。属人化を解消し、全員が自律的に動けるチームへ

はじめに

はじめまして、Akerun IoTプラットフォーム開発部でWebエンジニアをしている島田です。

みなさん、以下のような課題を感じたことはありませんか?

  • Sentryのエラー対応では「対応できる人」が固定化し、その人(ベテラン勢)にばかり負荷が集中している。
  • 対応できない人は決してサボっているわけではなく、単に「何をすればいいかわからない」だけなのに、それが誰にも共有されないまま放置されている。
  • 「技術課題に工数の2割を充てる」ルールはあるのに、いざ時間が来ると「何から手をつけるか」で毎回悩むことになる。
  • リリース後の確認作業が、特定の人しか読み解けない「秘伝のタレコマンド」(下記に記載)に依存している。
tail -n 10000 /path/to/production.log \
| grep -v 'INFO' \
| grep -v "can't modify frozen String" \
| grep -v 'Grape parameter error' \
| grep -v 'バリデーションに失敗しました'
# ...以下、既知のWARN/ERRORを除外する条件がさらに続く

リリース後は、こんなコマンドをコピペしてログを確認するのが「お作法」になっていました。 このコマンドは、「このログは無視して良いもの」を省いていった結果です。

裏を返すと、大量の既知ログに埋もれて本当に見るべきエラーを見落とすリスクをはらんでいたということです。

もしこれらと同じような悩みに心当たりがあるなら、この記事が何かのヒントになるかもしれません。

機能不全なMTG

まず、これまでの開発チームにおけるナレッジ共有の足跡を、MTGの設計とともに振り返りたいと思います。

週次定例(1時間):チームの情報共有を目的としたMTG。

ここではSentry当番が、その週に上がったエラーを共有するだけになっていました。 チームとして対応方針や優先度を決めることはありませんでした。 ログの読み方もエラーの追い方も「知っている人だけが知っている」属人的なノウハウのままで、チームに展開されず当番個人の中に閉じていました。

週次定例にはこれ以外にもアジェンダがあったため、チームの技術力を向上させる場としては十分に機能していませんでした。

MTGの見直し

その後、組織再編を機に開発フローを整備するとともにMTGを見直し、以下のような場を新設しました。

Sentryレビュー(30分):Sentryレビューは、「チームメンバーがエラーに対してプロアクティブに動けるようになること」を目的に始めました。

最初は発生したエラーの対応方針と優先度を決める場でしたが、運用を重ねるうちに、サーバーログの確認、RDS Performance InsightsでのSlow Queryの確認、New Relicでのパフォーマンス確認まで扱うようになりました。

単なるエラー共有ではなく、調査の観点や判断基準をチームでそろえる場へと広がっていきました。

Issue検討会(30分):技術的な課題をGitHub Issueとして起票し、優先度と対応方針を決める場。メンバーは日々感じた課題を自由にIssue化できます。

当初は様々な観点のIssueが上がるため、その優先度を決めることにチームとして悩みました。 そのため、なんらかの指標・方針を決めることにしました。MoSCoW分析やISO 25010の「ソフトウェア製品の品質特性と副特性」などを参考に独自の評価項目を考えました。

このMTGでは、各Issueについてメンバー全員が6軸それぞれに1〜3のレベル評価をつけます。 重み × レベルの合計 を個人スコアとし、その平均をIssueの優先度スコアとしています。(下記に記載) そのスコアをもとに優先度を判断していました。評価が割れたIssueは、その場で意見をすり合わせる運用です。

Issueの評価項目を簡易化した表です。本当はもっと細かく説明を記載しています。

No. 項目名 重み 説明(要約) レベル3 レベル2 レベル1
1 運用・保守性 9 運用・監視作業への影響や負荷軽減度 多大な工数発生・障害対応の遅延 時々追加の工数が発生 長期的な改善の余地あり
2 リスク(安定性・セキュリティ等) 10 障害・データ損失等のリスクへの直結度 システム停止・重大な漏洩に直結 頻繁なエラー・軽微な脆弱性 直接的な影響は限定的
3 開発効率への影響 6 開発速度やバグ対応への影響度 改修が著しく困難・頻繁なバグ 作業に時間がかかる・時々バグ 直接的な影響は限定的
4 影響範囲・利用頻度 4 システム全体での影響範囲や利用頻度 コア機能・関与者が非常に多い 重要な機能・一定の関与者 限定的な機能・関与者が少ない
5 改修コスト(解消に必要な労力) 3 解消に必要な開発リソース(時間・人数) 短期間(数時間〜数日)・少人数 中期間(数日〜数週間)・数名 長期間(数週間〜数ヶ月)・大人数
6 開発生産性向上 5 チーム全体の生産性向上度合い 劇的な改善・明確な生産性向上 ある程度の効率化・プラス影響 局所的な改善に留まる

※ No.5のみ『解消が容易なほど高レベル』としています。同じ効果なら低コストなものから着手したいためです。

本当の課題

MTGを再設計する中で気づいたのは、技術的な課題そのものよりも、「課題があることに慣れてしまい、放置してしまう文化」こそが本当に向き合うべき課題だったということです。 Sentryのエラーにすぐ反応できる人とそうでない人の差は、能力の差ではありませんでした。

単に「調べ方を学ぶ機会」があったかどうかの差でしかなかったのです。機会がない人は、いつまでも「できない人」の立場から抜け出せません。

ここで、私たちはMTGの目的を以下のように定めました。

目指すのは「障害やエラーを少なくするチーム」ではなく、「自ら課題に気づき、改善へ動けるチーム」になること。

エラーをゼロに近づけること自体はゴールではなく、あくまで結果です。本当のゴールは、チームが自己組織化し、誰かの指示や善意を待たずに課題を見つけて動けるようになることでした。

機能するチームにするためのMTG

Tech Quality Review(以下、TQR)というMTGを設計しました。 隔週45分間で運用しています。30分+30分だった2つのMTGを統合したのに45分に短縮できたのには理由があります。

実際に運用してみると、Sentryレビューで見ていたログやパフォーマンスの話とIssue検討会の技術課題は、しばしば同じ根っこの問題を別々に議論していただけでした。 統合したことで重複が消え、むしろ効率が上がりました。アジェンダは次の流れに統一しています。

  1. ログの確認
  2. Slow Queryの確認
  3. Sentryのピックアップ
  4. 技術課題(Issue)の優先度と対応方針の決定
  5. 最後に、1〜4で挙がった内容のタスク化・優先順位付けと、スプリントバックログへの追加

この一連の流れを一つのMTGでやり切ることで、「気づき→調査→意思決定→バックログ化」までが途切れません。 TQRの目的も、次のように再定義しました。

  • 技術課題の優先度と対応方針を決定し、スプリントバックログに載せる
  • チームメンバーがエラー発生に対してプロアクティブになれるように、「調査手法」「原因特定のステップ」「対応の決め方」「優先度の決め方」を全員が学べる場にする

Issue検討会で使っていた6軸評価もそのまま引き継ぎました。個人の感覚のズレを数値として可視化し、「なぜその評価をつけたのか」を議論するきっかけに活用しています。これによって、声の大きい人の意見ではなく、チームとして納得感のある優先度づけができるようになっています。

成果

ログレベルの出力定義を決め、不要なログを抑制・削除することで、WARN/ERRORログを削減しました。「秘伝のタレコマンド」で除外していた既知ログの多くは、そもそも出力を止められるものでした。その結果、

  • リリース後の確認は、CloudWatchのダッシュボードでログの増減を眺めるだけで済むくらいスマートになりました。
  • ログが見やすくなったことで、メンテナンス後のインシデントにも素早く気づくことができました。

これ以外にもデータベースのID枯渇問題など、クリティカルな問題を事前に検知することができました。

また、障害発生時に自分で動けるメンバーが明確に増えました。調査手順が属人化した秘伝ではなく、TQRという場を通じてチーム全体の共有知識になったからです。 特定の「できる人」への負荷集中が緩和され、フラストレーションも和らぎました。優先度を全員で判断する文化が根づいてきています。

まとめ

技術的な課題そのものを解決するより、「課題があることに慣れて放置していた文化」を変えるほうがずっと大変でした。

「なんとなく回っているから、このままでいいか」という空気は、誰か一人の努力では変えられません。能動的に改善できる"仕組み"を作ること自体が最大のチャレンジでした。気合いや意識づけではなく、45分・隔週・固定アジェンダという「仕組み」に落とし込んだことが、結果的に文化を変える近道だったと感じています。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 career.photosynth.co.jp

Akerunにご興味のある方はこちらから akerun.com

面倒くさいを潰し続けたら、開発がループになった話

こんにちは。Esperna です。

組み込みファームウェアの開発をしています。この記事は、AIに開発を投げて、自分は別の仕事をする。そんな働き方にたどり着くまでの話です。想定読者は「AIコーディングを日々の開発にもっと組み込みたい」と思っているエンジニア全般で、組み込みの前提知識は要りません。

面倒なことを一つずつ潰していったら、私の開発は最終的に二段のループになりました。開発そのものを回すループと、そのループを回す道具つまりスキル自体を改善するループです。そして、それぞれのループには必ず制約を付けています。このループと制約という形が、この記事を通して言いたいことです。

改善のきっかけ

きっかけは、ごく個人的な「嫌だ」でした。

commit して push する。しばらくしてCIが落ちる。直す。また push する。この往復が、とにかく時間の無駄で嫌だったのです。

自分のセッション履歴を振り返ってみると、失敗の大半は派手なバグではありませんでした。多かったのは、フォーマット崩れ(clang-format)と静的解析(cppcheck)の細かい指摘です。軽い。軽い内容なのにパイプラインは失敗する。「Format On Save なり、これくらいは push する前に片付けておきたい」と思わされる類のものでした。

であれば、push する前に全部終わらせてしまえばいい。そう考えて、手を動かし始めました。

push まわりを先に片付ける

最初にやったのは、push 前の工程です。

まず、push 前チェックを一気通貫にしました。実装してテストを生成し、静的解析とビルドとテストを回す修正ループを通して、最後にフォーマットをかける。ここまでをひとつにまとめました。ポイントは2つあります。

  • 修正ループは最大3サイクルで止める。直しては壊し、を延々と繰り返してAIが暴走しないための制約です。3回試して直らないなら、それは人間が見るべきサインだと割り切りました。
  • テストは実装直後、同じコンテキストで生成する。実装の意図がまだコンテキストに残っているうちに書くほうが、意図した挙動を正確に写し取れるからです。コードの挙動をその場で固定する万力としての価値が高い。ただし同じコンテキストは、実装に引きずられてバイアスがかかる裏返しでもあります。そのバイアスのない検査は、あえて次のレビュー工程に分けて持たせています(このあと触れます)。

次に手を付けたのは、レビューです。ここで一つ学びがありました。自分の実装コンテキストのままレビューさせると、レビューが甘くなるのです。書いた本人も、相棒のAIも、どうしても自分の実装に引きずられます。

そこで、レビューは隔離した別エージェントに見せることにしました。しかも、

  • コード差分そのものへのレビュー
  • 変更ファイル全体へのレビュー
  • MR(説明文の文脈を含む)へのレビュー

の3系統を別々のエージェントに並列で担当させ、結果を統合したうえで、もう一体の「Redチーム」役エージェントに全体整合の観点から深掘りさせています。各エージェントは自分の担当範囲しか見ないので一発で浅くなりがちですが、観点を分けて重ねることで、人間が一人で見るより漏れにくくなりました。

todesking さんが書いているように、大事なのは「AIが書いたかどうか」ではなく「検品と責任を果たしたか」だと思います。だから、書く工程はAIに任せても、検品の工程は別エージェントと私とで二重に持つようにしています。

あわせて commit 後には、TODO や「仮」「暫定」、命名規約から外れた snake_case といったやり残しをスキャンさせています。

最後に、push の先も自動化しました。MRを作る、チケットを更新する。この手作業が手間に見合わないと感じたからです。成果物のMRは、説明欄とコメントの二重に記録させています。同じ処理を何度流しても結果が壊れない、いわゆる冪等になるよう作ってあるので、安心して任せられます。

これで、実装からレビュー、後工程までが一つのループになりました。

ボトルネックはコンテキスト整理へ移った

ところが、全部自動化して気づいたことがあります。ボトルネックは消えませんでした。移動しただけでした。

push 周りが楽になった結果、今度は「AIに渡すコンテキストを整理する」工程に時間を取られるようになったのです。

たとえば、こんなプロンプトをその都度手で書いていました。

再起動コマンドが送られてきたら再起動したい。そのために決めるべきことを洗い出して設計を詰めたい。少なくともコマンドのパケットは決める必要がある。再起動中に再起動要求が来たらどう扱うか(受け付けなくてよいと思う)も整理したい。過去製品ではどんなケースで再起動していたか、現行の設計資料に再起動への言及があるかも確認したい。最終的に、作るべき設計ドキュメントとブランチをチケットの説明欄に書いてほしい。使えるところは並列で進めて。

実際はもっと具体的に書くのですが、毎回、書く中身は違うのに、書いている「型」はいつも同じでした。要求を整理し、過去を調べ、現状を確認し、成果物を定義する。この骨格を手で書き直し続けているのが、どうにもDRY原則違反な感じがして嫌だったのです。

そこで、この「型」自体をスキルに切り出していきました。

一つ目は要求整理スキルです。「何を作るか」より「なぜ作るか」を掘ることに振り切りました。何を・なぜ・どこまでという観点で曖昧さを自分で判定し、足りないところだけ私に質問してきます。逆に「どう作るか」は聞いてきません。そこはAIの仕事だからです。

二つ目は実装計画スキルです。設計ドキュメントが複数に散っていて参照が面倒だったので、計画をチケットに書き出して参照させるようにしました。ここでは設計と現状のギャップを、file:line 付きの表で出させています。実装済みの行まで明示させるのがコツです。

実装計画でもう一つ大事なのが、分割です。大きいまま出すと、いわゆる巨大MRになってレビューしづらい。だから分割します。

ただ分割には、「どのMRを土台(main)にして、どれを積み上げる(stacked)か」という関係設計が要ります。ここを間違えると、レビューでの修正が別のMRに反映されない、といった抜け漏れが起きます。だから、修正やマージの順序まで計画に含めます。

そしてここにも制約です。計画には「触るファイル数の概算」を書いておき、実装ループ側は実際の変更がその概算の約2倍に膨らんだら、いったん止めてスコープを問い直します。さらに、計画そのものを工数・依存・リスクなどの観点から攻撃する「自己批判役」のエージェントを1体挟み、出した計画を一度疑わせています。

自動化して変わったこと

要求整理、実装計画、実装。この3つが揃うと、仕事の仕方が変わりました。

[要求整理] → [実装計画] → [実装(テスト・実機spec生成まで)]
   なぜ作るか   何を・どう分けて   ループで自動的に回る

以前は、AIとずっと壁打ちを繰り返して、ペアプログラミングをしているような感覚でした。もちろん今も壁打ちや議論はします。でも、一度コンテキストを作ってしまえば、実装計画から先はほぼ任せきりです。しかも可能な限り、複数のトピックを同時にAIへ投げて並列で走らせています。

ただし、同時に走らせる重めのタスクは1つだけと決めています。残りは、スキルの小さな改善や重複排除、軽いリファクタリング、テスト追加、ドキュメント拡充といった、大きな判断を要さない軽めのタスクに絞る。

似たことは Peter Steinberger さんも書いていて、RAG やサブエージェントのような凝った仕組みより「ただ対話しろ(Just talk to it)」だと言い切っています。変更の影響範囲(blast radius)を見ながら複数を並列で回す感覚も近いなと思いました。

手を動かす時間は減り、代わりに指示を出す時間が増えました。投げた作業はループとして設計してあるので、単体テストと別エージェントのレビューが通るまで、勝手に進み続けます。ここでも形は同じ、ループと制約です。

体感で言うと、残業が1時間は減った気がします。私は早く家に帰る、寝る。その間もAIは働いている。これはなかなかいい状態です。

そして今、実機テストもループに入れようとしています。いわゆる HW in the Loop です。組み込みなので、最後は実物のデバイスで動かさないと安心できません。今のところは、

  • テストの spec を自動生成する
  • スクリプトで JLink を起動する
  • 人間がデバイスに ICカードをかざす(あるいはシェルコマンドを叩く)
  • そのログをAIが判定する

という形です。AIが司令塔で、人間がアクチュエータ、という役割分担になっています。

AIが「次はこの操作をして」と指示し、人間が物理的な手足としてカードをかざす。役割が逆転していて、最初は少し変な感じがしました。 もちろんこの辺りは Arduino を始めとしたマイコン、ラズベリーパイなどでモーターを回せばよい、という話はあるのですが、それでもまだボトルネックは残ります。試作機の台数に限りがあり、自動チェック専任の機械には仕立てられていません。ただ、フェーズが進めば、CIの回帰テストを「実機で正しく動くこと」まで含む形に持っていきます。

もう1つのループ

開発をループにする話は、ここまでです。あとになって、もう1つループがあることに気づきました。 それは道具(スキル)そのものを改善するループです。

たとえば、他の人の書いた記事を読んで、ためになるなと思ったらそこで得た知見を自分のスキルに取り込む。これも専用のスキル(記事取り込みスキル)にしてあります。さらに、日々の作業で得た学びを、スキル自身が「改善候補」として溜めていく仕組みを入れています。 ただ、AIが暴走して勝手に自分のルールを書き換えていくのも嫌なので、

  • 改善候補は、一定回数以上・一定期間以上観測されて初めて昇格する(すぐ上げたいと思ったときは、その場で更新します)
  • しばらく使われなければ降格する
  • そして何より、書き換えは必ず人間がチェックする。これで、触ってはいけない凍結ルールを絶対に書き換えさせないようにしています

ここでも結局、形は同じでした。ループと制約です。

おわりに

振り返ると、私がやってきたのは「面倒くさいを一つずつ潰す」だけでした。その結果として、開発のループと、そのループを育てるループという二段構えに行き着きました。

ここで一番伝えたいのは、自動化に終わりはないということです。一つボトルネックを潰すと、それは消えてなくなるのではなく、別の場所へ移動します。push がボトルネックなら push を潰す。すると今度はコンテキスト整理がボトルネックになる。だから次はそれを潰す。この繰り返しです。

そして、この移動には続きがありました。個人の開発フローを潰しきると、ボトルネックはついに自分自身、つまりAIのアウトプットを判断する人間に移ってきたのです。一度、重めのタスクを3つ同時に走らせてみたら、Claude Code is waiting for you! という催促の声があちこちで同時に鳴り、脳内のコンテキストスイッチが追いつかずに音を上げました。今は、同時に走らせる重めのタスクは1つだけと決めています。

ここから先は、もう一人では潰しきれない気がしています。AIが量産する時代に律速になるのは、結局「検品と判断」です。そしてそれを一人で抱えるほど、詰まる。だとすれば私の次のボトルネックは、検品と責任を、個人で抱えるのではなくチームでどう分担し、共有していくか、という設計なのだと思います。

だからこそ、道具はループと制約で設計し、道具自体も育て続ける。そして人間は、空いた時間でひとつ上のレイヤー、つまり何をどこまで作るかという要求と境界の設計へ移っていく。そうやって人の手を離れても回る範囲が広がっていくのは、素直に良いことだと思います。開発の対象が、プログラムや製品そのものから、それらを生み出す環境作りにシフトしてきた感があります。

次に潰すべきボトルネックは、たぶんもう、コードの中にはあまり残っていないのかもしれません。

参考にした記事

  • Peter Steinberger「Just talk to it」 — https://steipete.me/posts/just-talk-to-it 凝った仕組みより対話を、という実践。複数を並列で回す感覚や blast radius の考え方が、自分のやり方と重なっていて心強かった一本です。
  • todesking「ちょっとしたアイデアをAIで長文記事にして公開するのをやめろ」 — https://zenn.dev/todesking/articles/fba4cc93baf0a9 「AIが書いたかどうかではなく、検品と責任を果たしたか」。本文で書いた制約という発想を補強してくれました(……この記事もそれなりの長さになってしまいましたが、検品と責任は果たしたつもりです)。

株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 photosynth.co.jp

Akerunにご興味のある方はこちらから akerun.com

RubyKaigi 2026 参加レポート Part 3

こんにちは、市川です。

この度、RubyKaigi2026に参加してきました。RubyKaigiに限らず、カンファレンスへの参加は初めてでした。

普段はWebエンジニアとしてRailsを触ってはいるものの、正直Ruby本体の中身とか仕組みをあまりちゃんと知っていたわけではなかったので、「自分が行って大丈夫かな…」と若干不安を持ちながらの参加でした。

ただ、ふたを開けてみると、話についていけない部分もあったんですが、それも含めていい刺激を受けることができました。

今回は参加レポートということで印象に残ったセッションについて振り返ります。

Day1

When Can You Skip a Test? Tracking Test Impact

変更の影響を受けないテストをスキップしたいという趣旨の話。Coverage、TracePoint、C拡張の3つを比較してC拡張に落ち着いたとのこと。「15分のテストが9秒になった」という数字は衝撃でした。

AIでの開発でテスト実行回数が増えていて、CIの待ち時間が地味に効いてくる場面が多いので、こういう「そもそも全部走らせなくていいよね」という発想は、とても勉強になりました。

Day2

Surviving Black Friday: 329 billion requests with Falcon!

ShopifyがBlack Fridayを乗り越えた話。週末で3,290億リクエストというよく分からない量のリクエストを完走させたそうです。Black Friday直前まで本番由来のバグを潰していた、という話が印象に残ってます。

今後の展望でRack v3へのバージョンアップとあって、自社でもUnicornを使っていることによって、Rack v3へバージョンアップができていないので、規模は全然違うけど同じような悩みを抱えていたんだなと思いました。

The future of Ruby documentation

RDocを刷新していくという話。ドキュメントツールの裏でこんなに大変なことになっていたのかという素朴な驚きがありました。Prismベースに移行してコード量がほぼ半減(22,000行→12,000行)、パースも30%速くなったとのこと。

あと2008年からデザインがそのままだったテーマが刷新されたという話で、20年もののプロダクトをアップデートし続ける営みが地味にすごいなと思いました。

Day3

Autoresearching Ruby Performance with LLMs

LLMで性能改善を自動化する試みの話。発端は「autoresearchでLiquidを50%高速化した」とPRを公開したことだそうです。ところが、不具合があり実際はほとんどのPRがマージされなかったとのこと。一方で、他の方がautoresearchベースで出したPRはちゃんと取り込まれていて、「やり方によっては機能する」ことが示されていました。

「自分が読めて直せるコードにだけautoresearchを使う」という結論は、AIを使ってコーディングしていくうえで大事なところだと思いました。

Ruby Releases Ruby

Rubyのリリース作業の自動化の話。Ruby本体のリリースが裏でどう動いているかなんて考えたこともなかったのでとても新鮮でした。

セキュリティリリースが「5〜6人で深夜5〜6時間」から「1人1時間以内」になったとのことで、ここまで効率化できるんだと素直に驚きました。リリース頻度も年9回から14〜15回に増えたとのことで、自動化の効果がしっかり数字として表れていて印象的でした。

おわりに

普段は素のRubyをあまり意識することはなく、Railsの上で書いている自分にとっては、登場する話題がどれも新鮮で、聞いているだけでも刺激を受けることができました。 何より、Rubyコミュニティの熱量、盛り上がりを肌で感じられた3日間でした。

来年もぜひ行きたいですね。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 photosynth.co.jp Akerunにご興味のある方はこちらから akerun.com

RubyKaigi 2026 参加レポート Part 2

こんにちは、@yellow-seedです。

この度、RubyKaigi 2026に参加してきました。Python Conferenceには過去に参加経験があるのですが、RubyKaigiは初参加です。

RubyKaigiは言語カンファレンスの中でも特に規模が大きいと事前に聞いていましたが、実際に参加して、たしかにその通りだと肌で感じられました。また、他のカンファレンスは東京で済ませたりすることも多く、日本の各地で実施するご当地イベント感も特色かなと思います。ラッキーピエロを今回知ることができたのが個人的には大きな収穫でした。

今回は初参加レポートとして、Dayごとに印象に残ったセッションを簡単に紹介したいと思います。

Day-4

Day-4 (2026-04-17) に足を怪我したので、あわや松葉杖での参加になるかと思いましたが、幸いにも回復が早く無事に自分の足で参加することができました。あやうく松葉杖で飛行機に乗るところでした。

Day1

Day1はやはりKeynoteである The Journey of Box Building が初っ端から素晴らしかったです。Boxという命名の仕掛けなど、プレゼンとしての完成度の高さが印象に残りました。

「Box」はRuby 4.0に実験的に導入された機能で、Rubyのグローバル空間をプロセス内で分離する仕組みです。

Ruby VM (C実装)についてはまったくの素人なのですが、コントロールフレームスタック(VMの内部構造)とそれにまつわる課題の整理が、発表の技術的な内容の中心でした。

前回、エンジニアブログのネタとして、モジュラモノリスに関する記事を書いたこともあり、Ruby::Box自体はRuby 4.0発表当時から関心を持っていました。今回さらに背景となるC言語の実装とその課題についても聞くことができてよかったです。

後追いで、Featureのリクエストのチケット(#19744 や #21311)も見つけたので、色々と読んでいきたいと思います。

アプリケーションやライブラリやmonkey patchを隔離された空間に分離するというアイデアは、モジュラモノリスの文脈だったり、カナリアリリースの文脈だったり、様々な文脈で応用できそうだなと感じています。同日のLT(joker1007さんの「Do Ruby::Box dream of Modular Monolith?」)でも言及がありましたが、実際のRailsサーバへの適用においてはまだまだ課題もありそうに見えています。今後の発展に期待したいと思います。

昨年モジュラモノリスへの方針検討のためにあれこれ調べ回った身としては、導入のあかつきにはpackwerkと組み合わせるのがやはり筋が良さそうというのが個人的な所感にはなります。

Day2

RubyKaigiは、日英の言語でセッションが行われます。

英語セッションで印象に残ったのはDay2のSurviving Black Friday: 329 billion requests with Falcon!でした。

FalconはFiberを活用した非同期I/OベースのWebサーバーで、単一プロセスで数千の並行リクエストを効率よく処理できる設計になっています。Shopifyでは本番投入前に段階的なスケールテストを繰り返し、ボトルネックを一つずつ潰していったとのことでした。

ピークで85 million RPMというとんでもない規模のリクエストをFalconでいかに効率よくさばくのか、当日までにどんな準備をしているのか、という趣旨の発表でした。

ShopifyクラスのRuby界のフロントランナーでもかなり直前まで泥臭くトラブルシューティングをしている様子が伝わってきて、非常にリアルな発表であると同時に、親近感も湧きました。Falconの切り戻し構成がUnicornというのも、Shopifyでもいまだに一定現役なのかと同じく親近感を覚えました。

発表でも言及されていた、UnicornだとRack v3系にアップデートできないという課題はまさに悩みどころで、自社でもプロダクション環境でPitchforkを導入するか、Pumaを導入するかは今後のテーマのひとつです。そういうテーマで書かれた記事が今後でるかもしれません。

Day3

Day3は、Ruby Releases Rubyが印象に残りました。

労働集約的だったRubyのリリース作業がRubyを使っていかに自動化されているのか、という趣旨の発表でした。

具体的な成果として印象的だったのは、リリース作業が「5〜6人で5〜6時間の深夜作業」から「1人で1時間以内」に短縮されたという点です。「タグを打つ」「アナウンスを出す」以外は全て自動化されており、リポジトリディスパッチによる連鎖実行でパッケージ生成からDockerイメージ、Webサイト更新まで一気に走るとのことでした。年間リリース回数も9回から14〜15回へと増え、自動化の恩恵が数字にも表れています。

別にRubyで書く必要のないRubyスクリプトが社内にもあり、個人的には他の言語で書いた方がよいのでは? と感じることもあるのですが、Rubyという言語のリリースにおいてはむしろなんでもRubyで書くことで検証する機会を増やす(Rubyのドッグフーディング)というのは、腑に落ちました。

どのような目的をもって、そのプロセスを設計するのか、というのは正解がない問いであり、少なくとも自分は言語のリリースにおいて、いわゆるデプロイメントのベストプラクティスを遵守するよりも、極力全てのスクリプトをRubyで書き切るアプローチに理があると感じています。

おわりに

普段はRailsを中心にRubyを触っているので、RailsではないRubyの視点や機能に着目する良い機会となりました。紹介したセッションではないですがWASM, Ractor, PicoRubyなどなど。特にPicoRubyはIoTサービスの企業に所属している身としては触っていきたいと感じました。Matzも言っていましたが名前が可愛い。

PRK Firmware は自作キーボードのファームウェアをRubyで書けるプロジェクトなので気になりますね。Integration of PRK Firmware and R2P2 でRubyKaigiのセッションのトピックでもあったようですが、こちらは他講演の方に出ていて聴きそびれてしまいました。Funicular: A Browser App Framework Powered by PicoRuby.WASM もPicoRubyをwasmで動かすというテーマで、PicoRubyの初学者的にはなかなか気になる題材に後から感じます。こちらも別講演の方に参加していたので聴けずじまいでした。後から振り返ると、聴ければよかったです。こういうのはキリがないですね。

そしてどうやら社内に、Raspberry Pi Picoがあるらしいという情報が出てきました。電子工作サークルが社内にあるので、いずれかのタイミングで実機でのお試しもできる機会がありそうです。

初参加の反省としては、初日は会場にトートバッグ一つで参加したのですが、想像以上にブースでノベルティをもらったので、素直にリュックを持っていけばと思いました。(2日目以降はリュックを使いました。) あとは、なかなか聴きながらメモを取るのは難しいですね。記事にしようとすると、自分の手元のメモ程度ではいかに役に立たないかを痛感しました。(そして、RubyKaigiのアーカイブ動画の公開が遅いということも。。。)

次回の宮崎もぜひ行きたいですね。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。

career.photosynth.co.jp

Akerunにご興味のある方はこちらから

akerun.com

RubyKaigi 2026 参加レポート

こんにちは、 AkiAbe's My Page - Qiita です。

RubyKaigi2026 函館 に参加してきました!!
人事の方にも協力してもらって、フォトシンスはゴールドスポンサーとして協賛をしました。 こういうテック系イベントにスポンサーしたのは、僕が入ってからは多分初めてです。

Sponsors - RubyKaigi 2026

僕自身、Ruby は 17 年ほど前に windows で動くスクリプトを作ったりメンテしたりで利用してました。
たぶん Ruby1.9 くらいだったと思う。
昔は CICD みたいなの充実してなくて、自前でビルドしてテストするようなツールを作ってました。

Rails はチュートリアルをなぞったことがある。程度の知識。
ほぼ初心者みたいなもんなので、セッションについていけるかとても不安でした。

出てみた感想としては、「すぐに使えるTips紹介」みたいなセッションはほとんどなくて、低レイヤーの話も多い。
自分が選んでいたセッションが組み込み寄りだったせいもあるけど、Ruby と同じくらい C の説明がされていてめちゃくちゃ意外でした。
ruby コミッターすごい。

ということで、時系列で Day1 まで振り返ります。


Day -1

大きな地震が、三陸沖でありました。 避難指示もでている?開催できるの?と少し不安になりました。

news.yahoo.co.jp

Day 0

前日の地震とは関係なく、朝から飛行機のトラブルがありました。 X で #rubykaigi のハッシュタグ追っていると、諦めて新幹線移動に切り替えたりしている方もいるようでした。 我々は午後の飛行機が飛ぶのを信じて待ちました。 結果、20 分遅れで移動できたのでよかった! (朝の便は 3時間遅れとかだった。)

news.yahoo.co.jp

とりあえず移動はできて、夕方くらいに現地着!
空港にも広告が!すごい。

弊社メンバーはみんな初参加。ドキドキワクワクしながら懇親会をして終了。

Day 1

会場へ

電車すごい人がいっぱい。
そして電車にも広告が、、、!

現地エントランスにある看板、かっこいい。

スポンサーロゴもみつけました!うれしい。

各社さん、ステッカーとかノベルティを置いてます。
我々もステッカーと簡単なパンフレットをおきました。
急遽、マーケの方にも手伝ってもらって作成してもらいました。感謝🙏

さあ、ここからセッション中心に楽しみます!!
自分がみた Day1 セッションとめちゃくちゃ雑な感想などなど。

Ruby::Box — アプリやクラスを隔離する

tagomoris さんのセッション。

Ruby::Box という Ruby4.0 からの新機能の話。 アプリケーション・ライブラリ・クラス・メソッドをそれぞれ隔離できる仕組み。 アプリごとにテストモックを作るときなんかに使えそう。 なんとなく、自分が昔にやった Linux のデバドラ開発を思い出した。

テストを効率よく絞るには?

anmarchenko さんのセッション。

カバレッジ網羅率だけ追うのではなく、コードの構造に着目して最適なライブラリを選ぶ、というアプローチ。 それによって「有効なテストだけに絞る」ことができ、テスト実行時間が短くなったとのこと。 英語セッションで正直難しかったけど、考え方は面白かった。

test-unit で並列化

tikkss さんのセッション。

並列処理を綺麗に作るというよりも、「空いている CPU をうまく活用できるライブラリを作った」という印象。 みんな低レイヤー触っていてすごい。

Z80 を mruby で動かす

yujiyokoo さんのセッション。

セガ・マスターシステムを mruby で動かしてるっぽい。なんか楽しそう。

PicoRuby で OS を作る

kishima さんのセッション。

PicoRuby で OS を作っているという話。 tick のカウントがうまくできなかったり、割り込み制御がまだ完全じゃなかったりと、リアルな進捗感があってよかった。 バス競合で落ちる課題があるとか、完全に組み込みやん。

CRuby のコードの読み方

youchan さんのセッション。

C 言語をあまり知らない人向けに、CRuby のコードをどう読むかという話。 「わかりにくかったら後ろからコードを読め」「戻り値がどのようにセットされているかから読むと読みやすい」という tips はその通り。 今の時代なら AI と一緒に読めばだいたい理解できると思うけど、こういう読み解き方の作法を知っておくのは良いなと思った。

ハードウェア上で md ファイルを使ってプレゼン

harukasan さんのセッション。

このデバイス上の md ファイルで発表しているというのがつよい。 pico ruby、mruby が多い中、CRuby でベアメタルしてる人もいるっぽい。 ハードウェア関連のセッションがこんなにあるとは思わなかったので、嬉しかった。

公式パーティには参加できず、、、

公式パーティ、、行きたかったけどチケット取れずで出れなかった。。残念無念。
このあたり、ちゃんと手配してマネジメントしてくれる人が必要ですね。反省。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。

career.photosynth.co.jp

Akerunにご興味のある方はこちらから akerun.com

モジュラモノリスへのリアーキテクチャに向けた準備

この記事はAkerun - Qiita Advent Calendar 2025 -の12日目の記事です。

こんにちは、@yellow-seedです。 アドベントカレンダーに書くのは二度目になります。

フォトシンスのサービスのうち、大半を担うのはモノリスなRailsアプリケーションサーバーです。 サービスの規模が大きくなるにつれて、モノリスなアプリケーションの保守性や拡張性が低下してきました。 所属するチームは、このプラットフォームの改善のためのリアーキテクチャを推進するのがミッションのひとつです。 今回はモジュラモノリスへのリアーキテクチャに向けた準備を通じて、チームの2025年の活動を紹介したいと思います。

はじめに

2025年は本格的な着手というよりは、実行に向けた準備の期間となりました。実際に直面した課題ではなく、どのような方針で進めていくのかを決定したのか、その過程を紹介します。

2026年以降の実際の推進を通じてこの当初方針にも修正が入る可能性は十分にありえます。

モノリスサーバーを安全に分解するにあたって、『ソフトウェアアーキテクチャ・ハードパーツ』1の第I部に書かれているアプローチを参考にしています。今回は書籍でとられていたアプローチをどのように実際のプロジェクトに適用したかを紹介します。

※ ディレクトリ構造などは記事内の説明をわかりやすくするために一部省略・編集しています。

なぜやるのか

運用開始当初のアーキテクチャの想定を超えたサービス利用をつづけたことによるビジネス上の課題が顕在化してきたためです。

運用開始当初のアーキテクチャの想定を超えた例として、以下のような事例がありました。

  • 当初のコア機能であった、入退室管理以外の機能を提供している
  • オフィス以外のビジネスドメイン(住宅向けサービス)でも利用されている
  • サービス開始当初から新規機種が追加されている

これらの事例は

  • 耐障害性とスパイクリクエストに対する弾力性の低下
  • バグの混入リスクの増大
  • 新機能、バグ修正のデリバリー速度の鈍化
  • データベース負荷の増大

として、ビジネス上のリスクをもたらしています。

どう取り組もうと考えたのか

端的には

  1. 『ソフトウェアアーキテクチャ・ハードパーツ』の第I部のアプローチによるコンポーネントの特定
  2. Packwerkを用いたコンポーネントベースのディレクトリ構造への整理

の2段階で進めることにしました。順番に説明します。

1.コンポーネントの特定

分解の方針

歴史あるモノリスの分解戦略は

の2種類に分けられます。

予期せぬ機能への不具合の前例と現状の開発チームごとの責任範囲の曖昧さを鑑みて、いきなり戦術的フォークをして、サービスベースのアーキテクチャに移行するのはリスクが高いと判断しました。書籍の例と同様に、まずはコンポーネント分解によるアプローチを選んだということになります。

コンポーネントの候補の洗い出し

分解戦略をまず決めたので、今度は分解するコンポーネントは最終的にどのようなカテゴリに分類すべきかを決めます。

まずは、チームのメンバーで分担してAPIベースでリクエストの処理を追うことにしました。それぞれの処理内で、どのデータリソースにアクセス、操作しているかを調べて、全APIについて、以下のような調査表を作成しました。

APIパス (Path Pattern) ネームスペース 調査担当 コンポーネント名の案
/app/login (POST) app 担当者 XX login.mail
/app/login/mfa (POST) app 担当者 XX login.mfa
/app/login/sso (GET) app 担当者 XX login.mfa
/app/logout (DELETE) app 担当者 XX logout
/akc/login/recovery (POST) akc 担当者 YY login.recovery_code
/akc/user_password/reset (PUT) akc 担当者 YY user.password
/akc/user_password/update (POST) akc 担当者 YY user.password
/akc/logout (DELETE) akc 担当者 YY logout

コンポーネントの特定

コンポーネント名の案を洗い出した後、最終的にはコンポーネント名のルートレベル(上記の例でいう.のつかない属性名)が10程度にまとまるように、統廃合を行いました。上記の例で言うと login, logoutのコンポーネント名はauth、userはaccountとして集約されることになりました。

2.ディレクトリ構造を整理する

コンポーネント名の整理方針が決まったところで、コンポーネントを実際の実装に落とし込むために必要な要素を決定します。

がポイントになります。

これらの決定についてはGitHub Discussionsを利用して、Architecture Decision Record (ADR)として記録していて、いつ誰がどのようなトレードオフ条件を理解したうえで、この方針を決めたのかが追跡しやすいようにしてあります。

どの技術を使うのか?Packwerkにする

Packwerk自体はすでにさまざまなRailsアプリケーションを開発する企業テックブログで紹介されている2gemになるので、ここでは割愛します。

モジュラモノリスのアーキテクチャにおける適応度関数としての重要な役割を果たします。

Rails標準ではない学習コストのデメリットを理解しつつも、以下のメリットがコンポーネント分割の目的にかなうと判断しました。

  • あくまでLintのgemでしかないので、導入自体が容易で既存動作に影響を及ぼさない。
  • package_todo.yml で警告無視もできるため、段階的な導入ができる。
  • CIに統合してドメイン規約違反をシステムとして検知できる。属人性を下げられる。

Packwerkのアーキテクチャのゴール像をあてはめる

分解したコンポーネントを実際のディレクトリ構成に落とし込みます。Packwerkに変更した際の実現予定のディレクトリ構造を対応させました。

将来的にクライアントごとにも分割することも想定していることから、コントローラーは従来通りのディレクトリ構造を維持しつつ、Packwerkのpacksディレクトリ以下にコンポーネントごとの責務を整理する方針としました。以下のようなイメージ構造を想定しています。

app/
  assets/
  controllers/
    app_controller.rb ※モバイルアプリAPI
    app/xxx_controller.rb 
    ...
    akc_controller.rb ※web管理システムAPI
    akc/xxx_controller.rb
    ...
  jobs/ ※基底クラスのみ
    application_job.rb
  mailers/ ※基底クラスのみ
    application_mailer.rb
  models
  policies/
  serializers/
  validators
packs/
  account/
    app/
      public/
        account/
          public_account_interaction
      interactions/
      jobs/
      models/
    lib/
      tasks/
  ...

その他の実装ルールの決定

Serviceクラス、Formオブジェクトのインターフェースがバラバラなままその時々の開発者による増築が続けられている状況だったため、今後はActiveInteractionのgem3でそういった責務のクラスを実装していく方針、実装ルールも合わせて決めました。

現時点で予想される今後の困難さ

本来Packwerkを利用する場合は、ControllerもPackwerkのpackageディレクトリ以下に配置することが推奨されています4が、前述の通りそれを採用しないことにしました。

これは、Railsの標準的な水平アーキテクチャとドメイン中心の垂直アーキテクチャの折衷案を採用することになります。ディレクトリ構造を通じた説明の仕方をすると、以下のようになります。

Railsの典型的な水平アーキテクチャは端的に以下です。

# Horizontal(典型的なRails構成)
app/
  controllers/  # すべてのコントローラー
  models/       # すべてのモデル
  serializers/  # すべてのビュー

一方で、ドメイン中心の場合は以下のようなディレクトリ構造になります。

# Vertical(ドメイン中心でPackwerkの標準構成)
app/
  domains/
    users/
      user_controller.rb
      user.rb
    devices/
      device_controller.rb
      device.rb

これらを踏まえると、採用しようとしている方針が両方の特徴を持っていることがわかります。

# 今回の方針は上記の折衷案(Vertial&Horizontal)
app/
  assets/
  controllers/
    app_controller.rb ※モバイルアプリAPI
    app/xxx_controller.rb 
    ...
    akc_controller.rb ※web管理システムAPI
    akc/xxx_controller.rb
    ...
  models
  policies/
  serializers/
  validators
packs/
  account/
    app/
      public/
        account/
          public_account_interaction
      interactions/
      jobs/
      models/
    lib/
      tasks/
  ...

本来の用途をあえて活かしきらない方針で実際の実装を進めていく過程では、いろいろと悩む場面は出てくるのだろうと予想しています。

まとめ

モノリスアーキテクチャのモジュラモノリスへの移行推進以外にも、ログデータの蓄積によるデータ肥大化の解決に向けた取り組みなど、サービスの継続にともなう種々の課題が山積み状態となっています。

これらの課題の解消のための取り組みを引き続き進めていきたいと思います。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 career.photosynth.co.jp

Akerunにご興味のある方はこちらから akerun.com