有名テック企業の技術ブログを、ひとつのフィードで。
フィード
32件
カミナシエンジニアの osuzu です。 「状態管理にどのライブラリを使うか」への違和感 Reactの状態管理の話になると、だいたいライブラリの比較から始まります。少し前なら Redux か Zustand か Jotai か、最近だと TanStack Query と React Hook Form を組み合わせれば残りはわずか、みたいな話が多い印象です。 ただ、読んでいてどこか議論がかみ合わない感じがずっとありました。 理由はたぶんシンプルで、その問いは手前にすべきモデリングを飛ばしているからだと考えてます。 ライブラリ起点・コンポーネント起点はなぜこじれるのか フロントの状態管理でやりがちな失敗に、両極端な二つがあります。「Reduxを採用したので全部storeに乗せる」や「Reactをシンプルに使うため全部useStateで」です。 Redux や useState が悪いということではありません。問題は、アプリのモデルを持たないまま道具の作法を機械的に当てはめ、状態の置き場所を道具の都合だけで決めてしまうことです。 ライブラリの構造やコンポーネントの構造に、本来モデルが担うべきまとまりを肩代わりさせている。自分も過去そうして沢山苦しみました。 ライブラリ起点:道具の都合が設計を歪める 特化したライブラリ TanStack Query や React Hook Form が普及して良くなった面はたしかにあります。たとえば TanStack Query を使うとサーバー状態の扱いは見違えるほど楽になりますし、多くのプロダクトで実際にうまくいっています。 ただこれがうまくいく理由は、必ずしもライブラリ自体の力だけではないように感じます。サーバーで誰かがすでにモデリングをして、フロントはそれを消費しているだけ、というケースがあります。APIのURLや返す JSON の形がそのままアプリのモデルの形になっているという状況です。 フルサイクルで開発している人にとっては、特に問題にならないと思います。サーバー側でモデルを考えるときも、フロント側で扱うときも、同じ概念を行き来していて、頭の中にモデルがあるからです。 問題は、フロントだけを担当している場合です。この構造は、静かにモデリングの機会を奪っていきます。「サーバーレスポンスのキャッシュ+フォームの値の合算=アプリの状態のすべて」という前提で書ける範囲では、欠けているものが表面化しません。複雑なフォーム、オフライン対応、書き込み時のコンフリクト解決、自動保存 — そういう話が出てきて、はじめて「自分たちはモデルを持っていなかった」と気づきます。 道具の作法に合わせてデータの形を決めると、アプリの構造ではなくライブラリの構造が前に出てきます。atom や queryKey や form field の単位でしか発想できなくなって、まとまりとは別の概念が一つの hooks に追いやられたり、画面ごとに似たような状態が重複したりします。 コンポーネント起点:useState の継ぎ足しで構造が消える 逆の極端が、useState を継ぎ足していくやり方です。 もちろん useState が悪いという話ではありません。むしろ useState はスコープを小さく保てる良い仕組みです。問題は、ドメイン上ひとまとまりであるはずの概念を、複数のコンポーネントの内部状態に分解してしまうことにあります。 分解しすぎると、コードのどこを見てもその概念そのものが姿を見せなくなります。属性は別々のフックの中に散っていて、それらを束ねた型もなければ、操作する関数もありません。アプリ全体としては明らかにその概念を扱っているのに、コードのどこにも名前が付いていない、という状態です。 モデルとして名前を付けるべきものに、名前を付けないまま分解してしまう。これがコンポーネント起点の落とし穴だと感じています。 コンポーネント思考の React は偉大過ぎたのだ… モデル起点という発想 ビジネス概念を定義する モデル起点とは、画面より先に「このアプリは何を扱っているのか」を言葉にする、ということです。 ユーザー、注文、編集中のドラフト。これらは、UIにどう描かれているかとは独立に意味を持つ概念です。コンポーネントの寿命やルーティングより長く生きることもあるし、サーバー側のデータモデルとも対応関係を持つケースも多いです。UIはその表現のひとつにすぎません。 モデルから始めると、自然と「このデータは誰のものか」「誰が変更するのか」「変更されたら何が起きるか」を考えることになります。コンポーネントの都合でもライブラリの都合でもなく、ドメインの都合でデータの形を決められます。 モデルか、UI状態か モデルとUI状態を分ける線を引こうとするとけっこう迷います。自分が使っている目安の一つはシンプルで、ビジネスの語彙で名前が付くかどうかです。 Order、Applicant、Cart。こうしたものはドメインの言葉で説明できます。エンジニアでない人に見せても通じる名前です。 一方で isModalOpen、hoveredIndex、focusedField はビジネスの語彙では説明できません。UIという特定の表現様式があってはじめて意味を持つ概念です。 同じ「選ばれている」でも、カートに入れた商品とリストで選択中の行は別物として扱ったほうがいいと思っています。 操作が「ドメインのコマンド」になるか もう一つ簡単なテストがあって、その状態を変更する操作をドメインの言葉で名付けられるかを見るやり方です。 placeOrder()、submitApplication()、addToCart()はドメインのコマンドとして読めます。openModal()、setHovered()、toggleDrawer() は画面の振る舞いの域を出ません。 モデルを表現する ここまでの考え方をコードに落とすとどうなるか、ひとつ具体的なドメインを置いて見ていきます。 題材は採用領域です。よくあるDDDの教材的な例ですが、採用選考(Screening)を集約のルートにして、応募者(Applicant)の情報と、面接(Interview)の連なりを内包する、という整理を考えてみます。画面ではこの選考を一件編集する想定です。 Screening ├ Applicant (name, email) ├ status (screening / hired / rejected) └ Interview[] (面接の連なり) 以下は React と Jotai を使った例です。Jotai 固有の API も出てきますが、やっていることはライブラリに依存しません。 モデルを型やスキーマで宣言する 最初にやるのは、モデルの型を宣言することです。状態管理の話というより、ドメインモデリングの話です。ツールは限りませんが一般例としてzodを使っています。 const screeningSchema = z.object({ screeningId: z.string(), applicant: z.object({ name: z.string(), email: z.string(), }), status: z.enum(["screening", "hired", "rejected"]), interviews: z.array(interviewSchema), }); export type Screening = z.infer<typeof screeningSchema>; 重要なのは「Screening というモデルが存在する」と先に宣言してしまうことのほうです。 このモデルをひとつの atom にします。 const screeningAtom = atom<Screening>(defaultScreening); これで Screening というモデルのインスタンスがアプリ内に存在することになります。ストアの中の slice ではなくて、モデルそのものへの参照、という捉え方です。 モデルとコンポーネントを接続する UI が必要とするのはモデル全体ではなく、その一部です。応募者の名前だけを表示するコンポーネント、面接の一覧、個別の面接、というふうに必要な粒度が違います。 コンポーネント起点で書くと分解が起こりがちですが、モデル起点で書くとモデル側を切らずに済みます。focusAtom でモデルの一部にフォーカスし、splitAtom で配列要素を独立した atom として扱う、というやり方です。 const interviewsAtom = focusAtom(screeningAtom, (o) => o.prop("interviews")); const applicantAtom = focusAtom(screeningAtom, (o) => o.prop("applicant")); const interviewsSplitAtom = splitAtom(interviewsAtom); この使い方は React 外のコミュニティで人気の Fine-grained Reactivity(Signals) に近く、コンポーネントの再レンダリングを最小化しつつ、モデルの一体性は壊さない、ということが両立します。 focusAtom も splitAtom も Jotai 固有の API ですが、やっているのは「モデルを切らずに一部だけを見る」ことだけです。考え方はライブラリに依らないので、Signals など別の道具でも実現できます。 ドメインコマンドを action atom として書く モデルの遷移はモデル側のロジックで完結させます。Jotai の write-only atom、いわゆる action atom はそのための場所として使えます。 export const addInterviewAtom = atom(null, (get, set) => { const current = get(interviewsAtom); if (current.length >= MAX_INTERVIEWS_PER_SCREENING) { throw new Error("これ以上面接を追加できません。"); } set(interviewsAtom, [...current, createEmptyInterview()]); }); ポイントは「面接を追加する」というドメインのコマンドが、コンポーネントの外に独立して存在していることです。件数の上限のようなドメインルールもここに住んでいて、UI 側には漏れません。並び替えや削除も同じ形で書けます。 コンポーネント側は useSetAtom(addInterviewAtom) で「面接追加」というコマンドを取り出して、押す。それだけになります。 すべてをモデルにしない ここまで Jotai を使った例で書きましたが、「すべての状態にモデルを置こう」と言いたいわけではありません。モデルが要らない場面でモデルを持ち出すと、今度は逆方向の道具の使いすぎになります。 補足すると、本記事の主軸は「モデルか、UI状態か」であって、「atom か、useState か」ではありません。Jotai の atom は <Provider> でストアをスコープできますし、コンポーネントの中で作って使えます。atomを使う=モデル化してグローバルに置く、ではないですし、useState を Context 通してグローバルなモデルのように扱うこともできます。 その上で私は、Jotai のようなライブラリを使うことでドメインロジックの表現や、Reactでモデルとコンポーネントを接続する場面で、パフォーマンス最適化がしやすいと考えています。 結局ある程度は道具側の知識も要るということですね笑 自分の中で、モデル相当のものは Jotai、それ以外のローカルなUI状態は useState、というふうに使い分けています。ここの線引きや選定はチームやプロジェクトの好みで変わって良いところだと思います。 UI状態はコンポーネントや hooks に閉じ込める モーダルの開閉、ホバー、フォーカス、入力中の未確定値、こういったものはコンポーネントや hooks の中で完結させたほうがシンプルです。前に書いた「ビジネスの語彙で名前が付かない」状態がだいたいここに該当します。 Prop drilling を避けたいというだけの理由で、UI状態をグローバル化したり、モデルとして扱ったりするのは避けます。 サーバーで完結するリソースは TanStack Query で サーバーが真実で、フロントは表示と編集だけ、編集も即時に PUT / PATCH で反映してよい、というケースもよくあります。ユーザー設定、ステータス一覧、要件がシンプルなフォームがこれにあたります。 こういう領域では、わざわざクライアントにモデルを持つ必要は薄いと思っています。サーバーから取ってきたデータをキャッシュとして扱い、変更があったら invalidate して再フェッチする、という TanStack Query 的なやり方がそのまま素直に当てはまります。 クライアント側にモデルを置く価値が生まれるのは、編集途中の状態をタブ閉じても保ちたい、複数の編集を集約として束ねたい、UXのためフロントで複雑なバリデーションや楽観的更新したい、といった性質がある場合です。 まとめ 設計順序:モデル → 置き場所 → ライブラリ 状態管理の設計には順序があると思っています。まずモデル、次に置き場所、最後にライブラリ、という順番です。 この順序を逆にすると道具に振り回されます。ライブラリから始めると道具の作法でデータが歪み、コンポーネントから始めると構造が消える。どちらも経験があります。 ※ ただし必ずしも設計から始める方が良いとは考えてません。フロントエンドだと実装と設計を行き来することでモデルがよくなるケースも多いと思います。 ライブラリの行き着く先は同じ問い 最終的に、Signals でも Jotai でも useState でも、良い状態管理に共通しているのは、その下にモデルがちゃんと定義されていることだと感じます。ライブラリの差より、モデルの有無のほうがずっと効きます。 「状態管理どうする?」と聞かれたときに、最初に返したい問いは「何を、どんなまとまりで持つべきか?」だと思っています。 AIとの距離感 最後にAI 全盛の今のスタンスです。 私は Jotai を使うときも使わないときもあります。それはツールで設計を考えたくないからです。 そのため設計フェーズでの Agent skill のような自動化にも重きを置いておらず、ド
こんにちは。カミナシで「カミナシ 設備保全」の開発を行っている澤木です。今回はフロントエンドのコンポーネントディレクトリの構成、特に「ネストを深くしないために何をやっているか」という話をご紹介したいと思います。 feature-basedなディレクトリ構成 まず前提として私たちのチームでは機能(feature)単位でディレクトリを切り、各featureの中をさらにcomponents / hooks / contexts / model / repositoryといった責務ごとのディレクトリに分けるスタイルを採用しています。現在のフロントエンドの実装では一般的な構成かと思います。 features/hoge/ ├── components/ ├── contexts/ ├── hooks/ ├── model/ ├── repository/ └── index.ts 今回はこの中で主にcomponents/ディレクトリに着目してどういった規約で実装を行なっているかについてお話しします。 なぜネストが深くなるのか 例としてfeatures/hoge/というfeatureを考えます。この中のcomponents/には一覧画面のHogeList、詳細画面のHogeDetail、フォーム画面のHogeFormなどhoge featureに関わるコンポーネントが置かれる形になります。今回は詳細画面のHogeDetailを例に見ていきます。 それぞれのコンポーネントには、そのコンポーネント内でしか使われないコンポーネントや hook、model などが存在します。例えばHogeDetailであればHeader・Footer・Contentといった子コンポーネントや、HogeDetail内でだけ使う hook(useHogeDetail)やロジック(logic.ts)など。これらはHogeDetailでしか使われないものなので、外部ではなくHogeDetailディレクトリの内部に置きたくなります(いわゆるcollocation)。 components/HogeDetail/ ├── HogeDetail.tsx ├── components/ │ ├── Header/ │ │ └── Header.tsx │ ├── Footer/ │ │ └── Footer.tsx │ └── Content/ │ └── Content.tsx ├── hooks/ │ └── useHogeDetail.ts └── model/ └── logic.ts このようにコンポーネントの階層が浅ければこれで問題ないのですが、子コンポーネントがさらに専用の子コンポーネントを持ち始めたりすると、どんどんネストが深くなっていきます。 components/HogeDetail/ ├── HogeDetail.tsx ├── components/ │ ├── Header/ │ │ └── Header.tsx │ ├── Footer/ │ │ └── Footer.tsx │ └── Content/ │ ├── Content.tsx │ ├── components/ │ │ └── Section/ │ │ ├── Section.tsx │ │ └── components/ │ │ └── SectionTitle/ │ │ └── SectionTitle.tsx │ └── hooks/ │ └── useContent.ts ├── hooks/ │ └── useHogeDetail.ts └── model/ └── logic.ts この構成はコンポーネント内の構造を統一的に保てるというメリットはあるのですが、同じ components/ や hooks/ といった責務ごとのディレクトリが各階層に繰り返し現れるうえ、階層を辿っていきながら目的のファイルを探すのが大変になります。特にSectionTitleのような小さな部品を作るたびに階層が深くなっていくと、どこに何があるのか見通しが悪くなっていきます。 同一構造をあきらめる そこで、各階層で components/ hooks/ model/ といったディレクトリを統一的に置く方針はあきらめて、コンポーネントの中身はフラットに並べてしまう方針にしました。 今回以下の二つの理由からあえて責務ごとにディレクトリを切る必要はないと考えました。 1つ目は、コンポーネント1つあたりの内部部品の数がそもそも多くならないこと。1つのコンポーネントの中の hook や model はせいぜい数個で、ディレクトリを切っても中身のファイル数は多くならないため、わざわざディレクトリを切らなくてもファイル数は見通せる範囲におさまります。 2つ目は、ファイル名の規約さえ揃っていれば、種類はディレクトリで分けなくても名前から判別できること。useXxx.ts であれば hook、Xxx.tsx であればコンポーネント、というように、hooks/ や model/ といったディレクトリに入っていなくても何のファイルなのかは伝わります。ディレクトリでの種類分けと命名規約は同じ情報を二重に持っているので、名前のほうを揃えればディレクトリは省略してよいと判断しました。 コンポーネントもフラットにしてみる 責務ごとのディレクトリを切らない方針に合わせてコンポーネント同士の入れ子もやめて、HogeDetail/ の中身も完全にフラットに並べてみました。そうすると以下のようになります。 components/HogeDetail/ ├── HogeDetail.tsx ├── Header.tsx ├── Footer.tsx ├── Content.tsx ├── useHogeDetail.ts └── logic.ts これで HogeDetail/ の中身は完全にフラットになりました。 ただこうした場合、コンポーネント同士の入れ子が消えてしまったことで、どれが内部部品でどれが外から呼び出される公開コンポーネントなのかがわからなくなってしまいます。HogeDetail.tsx は外から呼び出される公開コンポーネントですが、Header.tsx、Footer.tsx、useHogeDetail.ts、logic.ts は HogeDetail 内でしか使わない内部実装です。同じ階層にフラットに並べてしまうと、どれを外から呼んでよくて、どれが内部実装なのかが見た目で区別できなくなってしまいます。 ネストの代わりに命名規則で集約する そこで、ディレクトリで分けるのではなくファイル名で public/private を表現する方法を試してみました。コンポーネント内は以下のようなルールを決めています。 _ から始まるファイル・ディレクトリはprivate(外から参照されないもの)とする _ が付かないもののみpublic(外から参照されるもの)とする コンポーネントディレクトリの中は1階層しか掘らない 例えばHogeDetail/ の中はこのようになります。 components/HogeDetail/ ├── HogeDetail.tsx ├── _Header.tsx ├── _Footer.tsx ├── _Content.tsx ├── _useHogeDetail.ts └── _logic.ts componentsディレクトリ内はhooksやmodelといった種類で分けることもせずにpublicかprivateかのみをファイル名で区別して全てをフラットにおくようにしています。この結果、_Header という名前を見れば「コンポーネント内の内部部品」だと判別がつき、HogeDetail/ の中にあるので HogeDetail の部品だと判別できるようになりました。この方法であればディレクトリを作成しなくとも、ファイル名と置き場所で親子関係が表現できます。 このルールは HogeDetail/ の中だけでなくその外側でも同じように適用できます。features/hoge/components/ 直下にも _ 付きの private コンポーネントを置けますし、features/hoge/hooks/_useHoge.ts のように feature 全体で使う private hooks を置くこともできます。どの階層でも「_ 付きは private」の1つのルールで通せるので、レイヤーごとに違う規約を覚えなくて済みます。 privateなファイルを表現する方法はディレクトリを分ける・barrel export(index.ts)で公開範囲を絞るなど他にもありますが、それらはいずれもディレクトリを増やすアプローチで、今回避けたかったネストや階層構造が生まれてしまいます。 _ プレフィックスはファイル名だけで public/private を表現できるので、ディレクトリを増やさずに公開範囲を示せるのが利点です。一方で、ファイル一覧が _ で埋まって見た目が少しノイジーになる、規約を知らないと意図が伝わりにくいといったデメリットは存在します。後者に関しては後述の dependency-cruiser で機械的に守る形にして補っています。 コンポーネントが大きくなったら上の階層に切り出す 先ほどの命名規則での集約ではコンポーネント内の部品が増えていくと、コンポーネント内での依存関係も増えていき見通しが悪くなっていく可能性があります。そこで、コンポーネントが大きくなってきたら、「ネストさせるのではなく上の階層に切り出す」というルールも同時に運用しています。 最小は、HogeDetail/ の中に全部 _ 付きでフラットに並べた状態。 components/HogeDetail/ ├── HogeDetail.tsx ├── _Header.tsx ├── _Footer.tsx ├── _Content.tsx ├── _useHogeDetail.ts └── _logic.ts 例えばこの中で _Content が自身の子コンポーネント(Section)や専用 hook(useContent)を持つようになったとします。そうした時にHogeDetail/ の中でさらにディレクトリを作成するのではなく、components/ 直下に独立したディレクトリとして外に出します。 components/ ├── HogeDetail/ │ ├── HogeDetail.tsx │ ├── _Header.tsx │ ├── _Footer.tsx │ ├── _useHogeDetail.ts │ └── _logic.ts └── _Content/ ├── Content.tsx ├── _Section.tsx └── _useContent.ts _Content は private のままなので、HogeDetail.tsx 側の import は変更不要です。ここでも同じ「_ 付きは private」ルールが効くので、_Content/ の中でもまた _Section.tsx を private として扱えます。さらに Section が育ってきたら同じように _Section/ を切り出せばよく、同じパターンが入れ子で適用される形になります。 こうして切り出された _ 付きのコンポーネントが features/hoge/components/ 直下に増えてくると、HogeDetail まわりだけでなく HogeForm の private 部品も同じ階層に並ぶようになり、_Content や _Section がどちらのコンポーネントに属するものなのかが見分けづらくなってきます。 features/hoge/components/ ├── HogeDetail/ ├── HogeForm/ ├── _Content/ ← HogeDetail / HogeForm どちらの部品? ├── _Header/ ├── _Section/ └── ... そこで HogeDetail まわりや HogeForm まわりが大きくなってきたら、それぞれを sub-feature として別 feature に切り出します。features/ 全体ではこのように切り出していきます。 features/ ├── hoge/ ├── hoge-detail/ ├── hoge-form/ └── ... 最初は hoge/ だけ切っていたものを、詳細画面やフォーム画面が大きくなってきたタイミングで hoge-detail/ hoge-form/ を切り出す、というイメージです。<feature>-detail <feature>-form で命名を揃えることで、どの feature の詳細やフォームなのかもわかるようにしています。 切り出しの基準は数値などで厳密には決めていませんが、深くネストさせる代わりに上に出す、という選択肢が常にあることで実装時の迷いが減り、コンポーネントディレクトリが一定以上には深くならないように保たれるようになっています。 命名規則をツールで守る 命名規則は、規約としてはシンプルですがコード上で強制されているわけではないので、どこからでも_がついているファイルをimportできてしまいます。そこで静的解析で弾けるように dependency-cruiser を導入して、_ 付きのファイルは外から参照できないようにルールを設定しています。 まず、feature の外からは index.ts(barrel)経由でしかアクセスできないようにします。 { name: "features-barrel-only", from: { pathNot: "^app/features/" }, to: { path: "^app/features/[^/]+/.+", pathNot: "^app/features/[^/]+/index\\.ts$", }, }, from(参照元)が feature の外、to(参照先)が feature 内部の index.ts 以外のファイル、という組み合わせを禁止するルールです。これによって外部から feature の内部実装に直接 import することを防ぎます。 次に、その index.ts から _ 付きのファイルを再 export するのを禁止します。 { name: "no-underscore-in-barrel", from: { <sp
こんにちは、ソフトウェアエンジニアの渡邉(匠)です。「カミナシ 設備保全」の開発に携わっています。ゴールデンウィークが明けて1ヶ月ほどが経過し、休暇モードからやっと仕事モードに戻ってきました。 このプロダクトは開発開始から約2年が経ちました。バックエンドは長いあいだ presentation / domain / repository の3層で書いてきましたが、最近これにユースケース層を加えた4層へと再構成しました。 この記事では、なぜ最初から4層にしなかったのか、そしてなぜ今になって構成を取り直したのか、を書きます。 シンプルに始めた 当初のバックエンドは presentation / domain / repository の3層でした。守ろうとしていたのは「依存方向を内側に保つ」という一点です。層がいくつあるかは本質ではなく、依存の矢印がビジネスルールを置く domain 側を向き続けていれば構成として成立する。そう割り切っていました。 当時のドキュメントには、ユースケース層について「必要になるまで実装しない」と書いていました。実際に一度入れてみたこともあります。ですがそこで起きていたのは入出力の薄い変換だけ。処理の分岐もほとんどなく、repository を呼び出すだけの層でした。機能数が少なく扱う情報量も限られていて、presentation と分けるほどの責務がユースケース側に残らなかった。テストも書いてはみたものの存在意義が薄く、結局この層は外しました。ユースケース層を持たないことは、その時点での戦略的な判断でした。 少人数で開発を回すには、先に器を整えて維持コストを上げるより、必要な分だけ書くほうが現実的でした。SOLID、KISS、YAGNI といった原則を日々の意思決定の物差しにして、「いま要らないものは作らない」を素直に実行していました。 コードベースが育って、痛みになった しかし、その割り切りには期限がありました。使われ続けるソフトウェアは変化を強いられます。機能が増え、扱う概念が増え、コードベースは絶えず複雑さを積み増していきます。怠けたから起きるのではなく、生きているソフトウェアだからこそ避けられない現象です。 私たちのプロダクトでも、機能領域が広がるにつれて、1つのファイル・1つのハンドラに乗る情報量が増えていきました。やがて3層のシンプルさは、そのままでは立ち行かなくなります。症状は次の4つでした。 単一ファイルの肥大化。 最大のものは10を超えるハンドラを抱え、3000行近くまで膨らんでいました。1つのファイルに複数の責務が同居し、読み解くだけでコストがかかります。 責務の混在。 入出力の変換、複数の集約をまたぐビジネスロジック、関連エンティティの取得、トランザクション制御などが、同じ場所に並んでいました。 トランザクション境界の散らばり。 BeginTx/Commit/Rollback の3点セットが、書き込み系の80近いハンドラそれぞれに直接書かれていました。同じ定型がそれだけ重複し、書き方を統一する場所もありません。 テストの書きにくさ。 責務が同居して密結合だったうえ、repository も具象のまま直接依存しており、一部だけを切り離してモックを挟む余地がありませんでした。結果として domain 層以外の単体テストは書きにくく、振る舞いの担保はE2Eのシナリオテストに寄せていました。 どれも、機能が増える過程で少しずつ膨らんできたものです。当時の割り切りは、もう実態に合わなくなっていました。 痛みの出た責務を切り出す やること自体は一つです。痛みの出ている責務を取り出して、別の場所に置き直す。ただし結果としてコードベース全体に手を入れることになるので、いきなり本体には入れず、段取りを踏みました。 進め方 責務を切り出す作業は、コードベースの広い範囲に手を入れます。動いているものを大きく動かす以上、リスクは当然あります。そこで本体の作業に入る前に、まずE2Eのシナリオテストを刷新しました。大胆に動かしても壊れたらすぐ気づける状態を、先に用意したわけです(このシナリオテストの作り込みについては別の記事で触れています)。 その状態を土台に、設計の判断は人間が担い、機械的な変換・パターンの適用・繰り返しの多い作業はコーディングエージェントに任せました。一気に変えるのではなく課題ごとに議論を分け、集約ごとに小さくPRを刻んで、段階的に合意を重ねました。 その上で、切り出しは依存順に進めました。トランザクション境界 → インフラ層 → ユースケース層 の順に、後段が依存する受け皿を先に作り、最後に、それらを使う側として presentation を分解していきます。 トランザクション境界。 まず「トランザクションをどう管理するか」のメカニズムを決めました。domain にインターフェースを置き、その実装をインフラ側に用意します。 インフラ層。 次に、インフラを「相手にしているもの」の軸で整理しました。このとき、それまで具象のまま呼んでいた repository も、トランザクション境界と同じく domain にインターフェースを置いてインフラ側で実装する形に揃えています。 ユースケース層。 そして最後に、肥大化した presentation そのものを分解しました。受け皿となるトランザクション境界とインフラのインターフェースは先に揃っています。あとは presentation に同居していた責務——アプリケーションのフローや、書き込み系に散らばっていたトランザクション境界——を、そこから抜き出していくだけです。抜き出した責務の引き受け先として立ち上がったのが、ユースケース層でした。最初に一度入れて外した層が、今度は presentation から分解された責務を伴って戻ってきた、ということです。 整理した結果 痛みの出ていた4つの症状は、それぞれ次のように収まりました。 肥大化したハンドラは、ビジネスロジックが抜けたぶん薄くなりました。最大で3000行近くあったファイルも、責務が移った分だけ小さくなっています。 責務の混在は、ビジネスルールが domain へ、関連エンティティの解決や権限チェックがユースケース層へと分かれ、presentation には入出力の変換という本来の責務だけが残りました。 書き込み系のハンドラに散らばっていたトランザクション境界は、ユースケース層の1か所に集約されました。 インターフェース化した repository を抽象越しに差し替えられるようになり、テストも domain 以外の層に書けるようになっています。 痛みの出ていた責務が、それぞれの居場所に収まった状態です。 構成としては presentation / usecase / domain / infrastructure の4層に整理し、加えてこれらを束ねる組み立て層(エントリポイント・DI・ルーティング)を分けています。それぞれの責務を一言ずつ挙げます。 presentation:プロトコルの境界。入出力の変換だけを担い、ビジネス上の判断は持ちません。 usecase:アプリケーションのフロー。トランザクション境界、関連エンティティの解決、権限チェックがここに集まります。 domain:ビジネスルールとドメインモデル、そしてインフラ層が実装するインターフェース。依存性逆転の起点です。 infrastructure:domain が定義したインターフェースを実装する層。依存先の種類で分け、DBは repository、外部APIやクラウド・認証は gateway のように分類しています。 組み立て層:エントリポイント、DI、ルーティング。 層がひとつ増えただけに見えますが、先に層という器を用意したわけではありません。痛みの出ていた責務を順に切り出していった結果です。 判断はフェーズで変わる 最初にシンプルに始めた判断も、いま責務を切り出した判断も、その時々のフェーズに対する答えという点では同じです。むしろ、先回りで抽象化を仕込まず何も作り込まなかったからこそ、後から責務をいかようにも切り出せる選択肢の広さが残っていました。薄いレイヤーを抱え続けるより、痛んでから切り出すほうが筋が良かった。そう考えています。 今の4層も、おそらく終着点ではありません。別のフェーズが来たとき、あるいは別の道具が手に入ったとき、また取り直されるのだと思います。実のところ、今回もタイミングを狙って当てたわけではなく、痛みが溜まったところにリファクタリングの時間ができて、ようやく切り出せた、というのが正直なところです。それでも動けたのは、先に器を作り込まずシンプルに保っていたからでした。次のフェーズでも、そうありたいと思っています。 フェーズごとに変わったのは、3層か4層かという構成の判断でした。変わらなかったのは、その判断を貫く原則のほうです。必要になるまで作らないことと、必要になったら切り出すこと。一見逆を向くこの2つは、別々の判断ではなく、YAGNIという同じ原則の両面なのだと思います。
はじめに カミナシでエンジニアリングマネージャーをしてます、すずけん(@szk3)です。 唐突ですが、皆さん AWSのエミュレーター使ってますか? 自チームのプロダクトはS3、DynamoDB、STS、IAM あたりの AWS サービスに依存していて、ローカル開発やテストではこれらのエミュレーターを使っています。ただ、歴史的な背景からリポジトリには LocalStack、RustFS、Moto の 3 種が混在していて、用途ごとに考えることが地味に増えてしまった状態でした。 この記事では、その 3 種を Moto に統一した経緯と、検討した他の候補、そして移行から少し経った今でも次の選択肢を検討し続けているよ、という話をシェアします。 統合前の構成 ローカル開発とテスト環境では、目的ごとに違うエミュレーターを利用していました。 エミュレーター 用途 対象 AWS サービス LocalStack ローカル開発 (docker-compose) + E2E テスト S3, DynamoDB, STS, IAM RustFS シナリオテスト (testcontainers) S3 Moto 単体テスト (testcontainers) S3, STS 歴史的には、当初ローカル開発とE2EはLocalStackで進めていました。そこに、シナリオテストを軽くするために、RustFSを導入しました。また、単体テストでは、署名付きPOST URLのPOSTポリシーのテストを実装する際にRustFSでは対応できなかったため、そこにはMotoを採用しました。 こうして用途ごとに段階的に継ぎ足ししていった結果、気づいたらこの状態になっていました。 当時の一つ一つの判断は妥当だったとは思えるものの、3種になると流石にマインドシェアが分散すると感じたのと、LocalStackがコミュニティ版と商用版を統合することが発表されたのをきっかけに、統一を検討しはじめました。 なぜ Moto を選んだか 統一先の案として3つ検討しました。 LocalStackに統一 まずは、LocalStackです。ローカルと E2E で動いていて、もっとも実績がある選択肢。機能面でも対応サービスの幅と深さでも、エミュレーターとしては今も強力です。一方で2026年3月にコミュニティ版と商用版が統合され、利用にLOCALSTACK_AUTH_TOKENが必須化されるという告知が 2025年12月に出ました。アカウントベースの無料プランは個人・OSS 用途で引き続き利用できるものの、これまでの匿名で使える無償運用は終わる方向です。商用版に移行する費用対効果(コミュニティ版を商用 SaaS の開発で使い続けるのはライセンス的にも難しく、Base 以上のプランへの切り替えが前提になる)と、LocalStackが提供する多様な機能が今の利用状況で必要かどうか、という疑問が残ったため、別の選択肢に揃えるほうが自然と判断しました。 The Road Ahead for LocalStack: Upcoming Changes to the Delivery of Our AWS Cloud Emulators 専用エミュレーターを組み合わせる 次は、S3はRustFS、DynamoDBはAWS公式のDynamoDB Local、というように単機能のエミュレーターを束ねる案です。各エミュレーターがそのサービスに特化していて、本番挙動との一致度は一番期待できそうだと思いましたが、自分たちのテスト要件に必要なSTS/IAMをカバーするエミュレーターが見当たらず、POSTポリシーの発行にAssumeRoleが必要なので、ここが埋まらない以上、この案は不採用になりました。また各種エミュレーターを組み合わせるのであれば、当初の感じたマインドシェアが分散してしまう課題を払拭することはできません。 Moto に統一 docs.getmoto.org ひょっとしたらあまり馴染みがないかもしれませんが、かなり昔から存在するOSSです。Python 製でAWS SDK互換のサービス数は100超えています。既に自プロダクトの単体テストで動いていて、自チームのユースケース(S3、DynamoDB、STS、IAM)は検証済み。OSS として活発ですし、商用化フォークの動きも観測しておりません。一方で、MotoはPython製なのでRust製である RustFSと比べると遅くなりそうな予測をしていましたが、ローカル環境で実験したところ、テスト時間に致命的な影響は出ない見込みになりました。 最終的に Moto を選んだ決め手 主に3つあります。 OSSとしての継続性の実績。安定した更新が行われており、AWS SDKと歩調を合わせている 既に自チームで動作実績がある。単体テストで使っていたので移行のリスクを取りやすい 対応サービスの幅。将来 SES、SQS、EventBridge などを足す余地がある 上記に加え、シナリオテスト全体に占めるエミュレーター部分のテスト比率は意外に小さく、Moto が抱える Python 由来の遅さよりエミュレーター統一の効果のほうが大きいだろうと判断しました。 とはいえ、完璧ではない 署名付きPOST URLのPOSTポリシーの検証が緩い Motoは、署名付きPOST URLのContent-Type 条件を実際には検証しません。POSTポリシーの starts-with 条件は素通しです。本物のS3では image/ プレフィックス制限などをきちんと検証するので、ここに依存する挙動はテストで担保できないという問題があります。 なので、その緩さを許容する代わりに、POSTポリシーのJSON構造そのものを検証するテストを追加しました。 policyJSON, err := base64.StdEncoding.DecodeString(result.Policy()) require.NoError(t, err) assert.Contains(t, string(policyJSON), `"starts-with"`) assert.Contains(t, string(policyJSON), string(tc.contentType)) 実アップロード時の挙動は、開発環境以降で確認する前提で運用しています。 移行後に再選定する Motoに統一後は、当初抱えていた課題もスッキリし新しい課題も顕在化していないのですが、一方でAWS エミュレーターの選定は引き続き行っており、現時点ではいくつか新興 OSS が出てきており動向を追っています。 移行当時は、タイミング的に候補にあげられなかったのですが、最近だとflociとkumoが移行先の有力検討候補だと考えており、flociについてはMotoに移行が完了した後日にローカルで少し検証してみました。 floci floci.io flociは、Javaで実装されたパブリッククラウド(AWS, Azure, GCP)のエミュレーターで、AWSのエミュレートでは、現時点で52サービスに対応しています。また、起動 30ms、メモリ 13MB と軽いフットプリントも特徴的で、LocalStackのコミュニティ版統合をきっかけに作られたという経緯があります。 自チームのバックエンドのinfra/gateway系単体テスト(S3操作中心)でMotoと比較計測しました。macOS、Docker Desktop、-count=1 を 3 回計測、コンテナ起動込みです。 go test 中央値 real 中央値 速度比 Moto 2.285s 3.01s 1.00x floci 0.861s 1.57s 2.65x / 1.92x シナリオテスト全体(DB、Smocker(認証認可サービスのAPIモックとして利用)、AWS エミュレーターの 3 コンテナ起動込み、5 回計測の中央値)でも比較しました。 go test 中央値 real 中央値 速度比 Moto 69.77s 77.70s 1.00x floci 63.80s 72.31s 1.09x / 1.07x S3中心の単体テストでは 2 倍以上速いですが、シナリオテスト全体だとDBやSmockerのオーバーヘッドが支配的になって、差が 7〜9% 程度に落ち着き、速度的な優位性はそこまでという感じです。 互換性に関しては、検証した範囲で全てのテストを通過したので問題はありませんでした。 署名付きPOST URLのPOSTポリシーの content-length-range を Moto が素通しするのに対し、flociは忠実に検証するという挙動差があり、flociがAWSの振る舞いをエミュレートする際の精度の高さを感じました。 kumo github.com Go製のAWS エミュレーターで単一バイナリで動作し、現時点で76サービスに対応しています。移行を検討したタイミングでは S3 の署名付きURLのPOSTポリシー周りの実装がなかったので、画像アップロードのテストが実現できず、候補になりませんでした。ですが、個人的にはいま一番熱いAWSエミュレーターだと考えており、開発が活発なのでとても期待しています。 今の判断 次の乗り換えを検討するなら、現時点ではflociが魅力的に見えますが、まだ乗り換える判断には至っていません。理由は次の 2 点です。 シナリオテスト全体での速度差が 7〜9% 程度に留まる。ローカル単体テストは速くなりますが、CI の主時間を占めるシナリオテストでは効果が薄い。 floci 自体がまだ若い(1.5.x、活発に開発中)。来年も同じ配布形態で動いている保証は現時点では弱いですし、対応サービスもMotoやkumoと比べて少なく、最近 hectorvent/floci から floci/floci に組織が移動するなどの変更も観測されています。 hectorvent/floci - Docker Image しかし、Moto の緩さに暗黙に依存しているテストを厳密にできることは魅力的であり、継続して動向を追っていきたいと思っています。flociが業界での採用事例が増えてきた段階で、kumoを含めもう一度評価する予定です。 今、ゼロから選ぶならどうするか ここまで、自チームのケースをシェアしました。 では、今、新しいプロジェクトに使うAWSエミュレーターを選ぶならどう考えるか、ユースケース別に整理してみます。 まず、テストをエミュレーターで広めにカバーしたい場合。対応サービスの幅、OSS 継続性、運用実績でMotoが今の現実解になりえそうです。後発のエミュレーターと比較して対応サービスの幅は徐々に縮まってくるとは思うのですが、OSSとして継続してきた実績は選定する際の理由としては強いものになりそうです。 次に、機能の網羅性と本番挙動の一致を最優先にしたい場合。商用版のLocalStackが良いと思います。Snapshot 機能、CloudFormation 完全エミュレーション、IAM の細かい挙動など、Moto が追いついていない領域がいくつもありますし、商用版ならではのサポートも魅力です。チーム規模やプロダクトの段階によっては、ライセンスコストを払う価値が十分にあります。 性能を優先し、ローカル単体テストの速度を絶対値で短縮したい場合。flociの起動30msと軽量フットプリントは魅力的です。ただし若いプロジェクトなので、少しだけ様子を見てからでも遅くはないと考えます。 そもそもエミュレーターを使いたくない場合。テストやローカル開発からアクセスできるAWS環境が用意できれば最善かもしれません。ただ、AIコーディングエージェントを使って開発するのであれば情報漏洩・インフラ環境破壊など、ガードレール(権限制限、実行ログ、課金監視)の構築にはしっかりと投資する必要がありそうです。(想定外な費用も発生する可能性も否定できないですしね) これらを踏まえ、現時点での個人的な意見でいうと、まったく新しい環境に導入するなら、無料で使える、ある程度のサービスの網羅性、起動の速さ、パフォーマンス、構成されるライブラリ依存の少なさを優先したいので、kumoを第一候補、flociを第二候補に選ぶと思います。 kumoを第一候補としているのは、対応サービスの多さ、Go実装による依存解決のシンプルさとサプライチェーンの見通しの良さへの期待からで、flociは逆にAWS特化というよりマルチクラウドを見据えているので、ややリッチすぎるという観点と、kumo, Motoと比較して対応サービスの少なさから次点にしています(あくまでも個人の主観であり、選定に正解はないです) 振り返り AWSのエミュレーターとしてデファクトスタンダードといっても過言ではないであろうLocalStackは今も優れたプロダクトで、商用版を含めれば機能面・サポートの面では頭ひとつ抜けています。しかし、無償版のサポート方針が変わる告知が出たことで、自分たちのライブラリ依存戦略を見直す良いきっかけを与えてくれました。 自分たちのケースでは、候補のエミュレーターはどれも魅力的でしたが、最終的にはMotoに揃えることを選びました。すでに単体テストで動作実績があり、移行リスクが小さかったのが決め手です。実際にいまプロダクトで動いているものに揃えることを優先したのが、スムーズに移行できた一番大きな要因だと思います。 乗り換え自体のコストはAIのおかげで以前ほど高くなく、定期的に自分たちのスタックを見直しやすくなっています。flociやkumoのような新興OSSも今後出てくるはずです。Motoを選んだ時点で完結させず、いま取りうる選択肢の引き出しを増やす癖をつけておきたいですね。 エミュレーターの選定はサービス開発の本丸ではありません。ですが、ローカル開発体験とCI安定性に直結する地味に重要な基盤です。その基盤を、その時々で最適なものにし続けるのは、意外と投資効果があるのでは、と考えています。 Appendix GitHubのリンクはこちらですー GitHub - localstack/localstack: 💻 A fully functional local AWS cloud stack. Develop and test your cloud & Serverless apps offline · GitHub (public archive) GitHub - getmoto/moto: A library that allows you to easily mock out tests based on AWS infrastructure. · GitHub GitHub - floci-io/floci: Light, fluffy, and always free - The AWS
はじめに カミナシの共通ID管理基盤を開発しているmanaty(@manaty226)です。カミナシ ID管理基盤ではログストレージにCloudWatch Logsを使っていましたが、サービスの成長に伴いコストに悩んでいました。この記事では、私たちが実践したAmazon S3 Tables(以下、S3 Tables)を使ったログストレージの構築と移行、そのコスト最適化効果について書きます。 サービス成長に伴うCloudWatch Logsのコスト増大 Amazon CloudWatch Logs(以下、CloudWatch Logs)はAWSが提供するログの保管や分析のためのサービスです。多くの一般的な構成ではアプリケーションが出力するログをそのままCloudWatch Logsに保管し、何かあればCloudWatch Logs Insightsを利用してログをクエリしたり、特定のエラーログにもとづくアラート設定をCloudWatch Alermと連携して行っているかと思います。私たちのID管理基盤も同様に、ECSからログルーターを介してCloudWatch Logsへログを配信し、Amazon Managed Service for Grafana(以下Grafana)を使って日々の開発運用に役立てていました。 一方で、カミナシのサービスが成長するにつれて、全てのサービスの認証認可リクエストを捌くID管理基盤は、そのリクエスト数の増大や機能拡張に伴ってログコストも無視できないほど膨らんでいきました。週次で行うレビューにより不要なログを排除したりまとめることで地道に改善できた部分もありますが、そういった改善活動では大幅なコスト最適化は難しく、抜本的な対策を考え始めました。 コストが徐々に増加していく様子 通常運用するうえで発生するCloudWatch Logsのコストには大きく、ログの取り込み、保管、クエリの3つがあります。料金表からも分かるように、コストが膨らむ主要因はログの取り込みであるため、どれだけ保管期間などを最適化してもリクエスト数の増大に比例してログコストも大きくなっていくことが分かりました。 そこで、コストと運用の両面でバランスが取れそうなS3 Tablesへログを取り込み保管することを検討しました。 aws.amazon.com Amazon S3 Tablesへの移行 S3 Tablesとは S3 Tablesは、Apache Icebergというオープンなテーブルデータフォーマットを実装したAWSサービスです。下図に示すように、Icebergではデータの実体となるIceberg形式のファイルに対してメタデータを管理することで、Amazon AthenaやSparkといったクエリエンジンからテーブルデータとして読み書きすることができます。メタデータによってテーブル構造が抽象化されているため、パーティションやカラムなどのテーブルスキーマの変更に柔軟であったり、スナップショットして管理されているメタデータ変更履歴に基づき過去のデータを読み出せるタイムトラベル機能、他にも従来のデータレイクハウスで利用されてきたHiveフォーマットの課題を解決しています。詳細は公式ページや、「実践Apache Iceberg」や「Apache Iceberg活用入門」といった書籍を読むことをおすすめします。 CloudWatch Logsのようにスキーマの事前定義なくクエリ時に柔軟にデータを読み込むことはできないものの、S3 Tablesにより馴染みのあるSQL構文を使って検索できることや、S3の安価なストレージを利用することによるコストメリット、コンパクションのようなIcebergの日々の運用をマネージドサービスに委任できること、将来的なスキーマ進化によるログ構造の変化への追従性などを鑑みてS3 Tablesへ移行することにしました。 Apache Icebergのデータアーキテクチャ(公式ページ:https://iceberg.apache.org/spec/#overview) ログ配信パイプラインによる段階的な移行 事前にCloudWatch LogsからいくつかのログをサンプリングしてS3 Tablesに投入し検証を行ったものの、実際に運用の中でどこまで現実的に使えるか分からなかったので、以下のような手順で段階的に移行しました。 ECSから配信されたログをログルーターで受けた後、ログルーターからCloudWatch LogsとAmazon Data Firehose → S3 Tablesの両方にログを配信 監視ツールとして利用しているGrafanaのクエリ先をS3 Tablesに変更して日々の運用における使用感や課題を洗い出し S3 Tablesによる運用が問題ないと判断できた時点で、ログルーターの配信先をS3 Tablesへ変更(ただし、エラーログはアラートにも利用しているため引き続きCloudWatch Logsへ配信) これにより、仮にS3 Tablesが私たちの運用に利用することが難しいと判明しても、CloudWatch Logsに保管されるログは引き続き欠損することなくクエリできるので、安全に移行することができました。 移行の各ステップにおける構成概略図 S3 Tables移行後のコスト最適化効果 CloudWatch Logsへのエラーログ以外のログ配信を停止してから、すぐに大幅なコスト最適化効果が見られました。コストの主要因であったログの投入にかかるコストは8割以上削減され、クエリコストもCloudWatch InsightsからAthenaに変わったことで大幅に低減しました。 CloudWatch LogsからS3 Tablesへ移行したことによるコスト変化 S3 Tablesと周辺エコシステムにおける現在の技術制約 S3 Tablesのパーティション変更 S3 TablesのパーティションはAthenaから変更することができません。したがって、作成時に設定したパーティション設定から変更したい場合は、そのためにAWS Glueジョブを使ってSparkクエリを書いてパーティション変更を行ったり、その他IcebergクライアントからDDLを実行する必要があります。せっかくAthenaというマネージドクエリサービスがあるので、ここは改善してほしいと思っており今後に期待しています。 docs.aws.amazon.com repost.aws Intelligent Tieringの利用開始可能タイミング S3 TablesはCloudWatch Logsに比べるとストレージコストが低いものの、毎日ログが蓄積されていけばコストは日々増加します。S3 Tablesは2025年12月に発表されたIntelligent Tieringにより、規定の期間アクセスされていないファイルのストレージクラスを自動的に最適化される機能を持っています。適切なパーティションが存在すれば、例えばログについては多くの場合直近のログにアクセスパターンが偏るため、古いログのストレージクラスが変更されてさらなるコスト最適化効果が見込めます。 一方で、Intelligent Tieringはテーブル作成時しか設定できず、標準ストレージで作成されたテーブルを後から変更することはできません。殆どの場合はIntelligent Tieringで良いはずなので忘れずに設定しましょう。私たちは泣きながらテーブルを再作成しました。 aws.amazon.com Terraformによる構築時の注意事項 カミナシではAWSインフラ管理にTerraformを利用しています。hashicorp/terraform-provider-awsが提供するモジュールには大変お世話になっていますが、S3 Tables周辺のAPIパラメータはまだまだ対応していないものも少なくありません。例えば、前述したテーブル作成時のパーティション設定やIntelligent Tieringの設定についても、PRはオープンしているもののマージはされていません。以前私が出したAthenaのマネージドクエリリザルト対応のPRもマージまで3か月ほどかかったので、これらの機能を欲している皆様はPRに👍してメンテナにIcebergへの熱い思いを伝えましょう。 github.com github.com おわりに 今回はアプリケーションログストレージをCloudWatch LogsからS3 Tablesへ移行してコスト最適化した事例紹介をおこないました。CloudWatch Logsは非常に使いやすく始めやすいため、アプリケーション開発の初期には私も毎回必ずCloudWatch Logsを選択します。その一方で、アプリケーションの成長とともにログコストは肥大していきます。そんなときに、S3 Tablesをログストレージの選択肢のひとつとして考えてみてはどうでしょうか。 私は昨年Icebergの書籍を読んでから、そのメタデータアーキテクチャやコンパクション戦略を基に、IoTデータような小さなデータを逐次的に投入するユースケースから大量のデータをバッチ的に取り扱うユースケースまで幅広く対応しようとする仕様の面白さに感銘を受けています
「それ本当にMVP?」AI時代にプロダクトが巨大化する理由をふりかえる おはようございます。シャトレーゼに入ったときのいい匂いの根源がなにか気になっている daipresents です。あれ、ほんとなんの匂い? この1年、AIがとてつもないスピードで進化しており、活用するエンジニアもどんどん進化しているように感じています。 僕は「カミナシ 教育」と「カミナシ 従業員」の2プロダクトにかかわっているのですが、「小さく作ったつもりが、結果的に大きくなっちゃった」というケースが、立て続けでありました。 もしかしたら「AIの登場によって、MVPも巨大になってしまっているのではないか?」と感じたので、ふりかえってみようと思います。 AIの登場によってわかったこと カミナシ社内でも開発に置けるAI活用は進んでいます。これはデータにも現れていて、今年に入ってからはAIによるコード生成量は格段に増えています。 あきらかに増加しているAIのコード貢献 去年はまだ「試し試し」だった部分が、今年に入って「使うべき存在」になり、開発の流れも大きく変わっています。 特に「コードの量」は格段に増えており、開発に大きく影響を与えています。 MVP開発でおきたこと カミナシの開発では一般的に、小さく作ってどんどんリリースしながらプロダクトを育成してく形を取っています。 参考: カミナシと、質とスピード - MVP にこだわり、アジリティを獲得する 今回の事例でも同じくMVPを定義し、開発を進める形を取っていました。 しかし、成果物をレビューするときに「思った以上にいろいろできる」というフィードバックが多数でてきました。 エンジニアは、AIが開発に十分使えることがわかってきて、AIを使った開発を試していました。予想通り、AIは賢くなっており、たくさんのコードを生成してくれます。気がつけばスコープが広がっていました。 エンジニアとのふりかえりの場において、「もし、開発前にもどれるなら何をするか?」と問いかけディスカッションしましたが、以下の点がポイントとしてでてきました。 MVPを「ちゃんと」定義する 最初に決めたスコープを守る必要があります。 AIがいろいろ配慮して作ってくれるのはありがたいのですが、「何をやるべき」で「何をやらないべき」かを決めるのは「まだ」人間の役割です。 逆に「AIに全部作らせて、あとで削ればいいじゃん!」という考えもあります。 pic.twitter.com/YCmWPDlGOT— mrdoob (@mrdoob) 2026年4月5日 上記の図は、JavaScriptの3Dライブラリ「Three.js」の開発者である @mrdoob さんのポストです。ウォーターフォールやアジャイルのMVPの図はよくみかけるものですが、「AI」という項目が今回の現象をとても良く表しています。 この図のように、AIに作ってもらい、そこから削るのはどうでしょうか? エンジニアとも話しましたが、この作戦も難しく感じています。 まず、AIの出力の良し悪しを考えるためには、すべての出力を人間が読み込まなければなりません。機能が多くなると人間が理解するのに時間がかかってしまい、アジリティが落ちます。AIの認知能力と人間の認知能力では圧倒的に差があるため、人間がボトルネックになってしまいます。 さらに、機能が増えるということは、検証材料が増えるということです。検証したい項目が増えれば増えるほど、やっぱりアジリティは落ちます。 最後に、機能が存在すると心理的にも「なんか全部いりそうだな・・・」となりがちです。AIの労力とはいえ「作った時間や使ったトークンがもったいない」という心理が働き、捨てる勇気が持てず「使ってくれるはず」と思いこんでしまうかもしれません。 こういった「たくさん作ったときの難しさ」を考えると、「スコープを守る」という行為は、「タイムボックスを決める」に近しいのかもしれません。制約を設けないと、制約を超えてどんどん機能が作られます。タイムボックスという制約をつけることで、できる範囲がしぼられます。 まとめ AIが開発に与える影響は大きくなっています。特に「コード量」は確実に増加傾向にあります。 AIのそのまま任せてしまうと、開発も「大きく」なってしまう可能性があります。しかしながら、人間は大きなものを認知するのに時間がかかり、結果アジリティが落ちます。 そうなると、AI時代であれどアジャイル開発のMVPの考え方や、スプリント・イテレーションを使った漸進的な開発(インクリメンタルな開発)のほうが「まだ」今はいいのかもしれません。
2026年4月、「カミナシ 教育」開発チームのオフサイト。 エンジニア・プロダクトマネージャ(PM)・プロダクトデザイナー(PD)が3人ずつのチームに分かれ、「カミナシ 教育」のAI機能をいじりながら、ある特殊なケースを探していました。 機能のバグでしょうか?いえ、もっと厄介なものです。「出力結果はおかしいのに、評価指標は問題なしと判定してしまうケース」——AI機能を評価する仕組みそのものの盲点です。 これが私たちのチームで実施したAI評価改善ワークショップの一場面です。 AI評価改善ワークショップの様子 こんにちは、カミナシでAIエンジニアをしている井上です。カミナシではプロダクトのAI機能の検証や開発、プロダクト横断のAI機能運用の基盤作りなどに携わっています。現在は「カミナシ 教育」のAI機能の評価基盤構築に取り組んでおり、本記事でご紹介するワークショップもその一環です。 なぜ、わざわざチーム全員を巻き込んでこんなことをしているのか。AI機能の品質を継続的に維持・改善していくためには、人間の目や顧客の声に基づく定性評価だけでは限界があり、開発時やデプロイ前の自動化された定量評価の仕組みが欠かせません。しかし、評価指標そのものがプロダクトの本質的な価値や人間の感覚とズレていれば、スコアが高くても品質は低いままになってしまいます。 つまり、AI機能の改善と並行して、評価の仕組みそのものも継続的に改善していく必要があるのです。 LLM機能の精度改善の基本サイクル。機能を改善するループと、評価そのものを見直すループは表裏一体です。うまくいかないケースを評価が拾えていなければ、プロンプトより先に評価方法の見直しが必要になります。 そしてこの評価の仕組みを育てる営みは、AIエンジニアだけで完結させられるものではありません。何を良いアウトプットとするかの判断基準は、PMが見ているユーザーの期待値や、PDが描くプロダクトの方向性など、多様な視点を統合してこそ磨かれていきます。AIエンジニア1人の感覚よりも、チームが持つ集合知の方が、顧客の感覚により速く・より正確に近づける——これが私たちの考える、AI品質改善をチームで取り組む価値です。 だからこそ私たちは、評価指標と実際の品質の乖離が顕在化する前に先回りしてAI品質改善をチーム全員の取り組みにする文化づくりに着手しました。今回のワークショップは、その第一歩です。 文化として根付かせるために、ワークショップという形を選んだ チーム全員でAIの品質改善に取り組む——と言葉にするのは簡単ですが、実際にやってもらうとなると障壁があります。それは「AIの改善って、なんだか難しそう」という先入観です。 例えばLLMを活用した機能の改善なら、LangfuseのようなLLMOpsツールを使う必要がありますし、「データセット」「評価指標」「LLM-as-a-Judge」といった専門用語も登場します。こうした要素が実際の作業以上に難しい印象を与え、チームメンバーの心理的ハードルを必要以上に高くしていると思います。やってみると「案外自分たちでもできる」と感じられる部分が多いのですが、その感覚は外から眺めているだけでは伝わりません。 だからこそ、チーム全員が手を動かしてAI機能の品質改善を体験する場が必要だと考え、ワークショップを実施することにしました。 なお、本来は評価の改善とAI自体の改善(プロンプトや処理の改善)の両方を体験してもらいたかったのですが、時間の都合で今回は評価の改善にフォーカスしています。良い評価は良いAI機能を作るための前提条件であること、そしてAI機能自体の改善に比べて「何をやるのかイメージしにくく、1人だととっつきにくい」領域であることが理由です。ワークショップという"みんなで一緒にやる"形は、ハードルが高く感じられがちな評価改善の導入にこそ向いていると考えました。 ワークショップの設計 今回のワークショップは、LLMを活用した1つの機能を対象に、その評価の仕組みを改善していく形で設計しました。複数の機能を扱うと論点が散らばってしまうため、最初の取り組みとしては1機能に絞ってチーム全員で深く向き合える構成にしています。 チーム編成 開発チームをエンジニア・PM・PDが混在した3人ずつの小チームに分割しました。職種をあえて混ぜることで、評価の視点が一面的にならないようにしています。 当日の流れ 苦手なケースの収集(15分) 評価を実行して観察する(10分) 評価方法を改善する(30分) 共有と発表(20分) 1. 苦手なケースの収集(15分) 各チームで、プロダクト上のAI機能がうまく動かないケース(入力テキスト)を集めます。単にうまくいかないケースを探すのではなく、「評価をすり抜けそうなケース」を意図的に探すのがポイントです。既存の評価指標がどんな観点で動いているかを眺めながら、「この指標だと拾えなさそうな入力ってどんなものだろう?」とチームで仮説を立て、プロダクト上でさまざまな入力を試していきます。 実際の運用では、こうしたケースは既存顧客が日々使う中で自然に上がってきたり、新規顧客の新しい使い方で見つかったりするものですが、ここではそれを凝縮した形で体験してもらいました。 2. 評価を実行して観察する(10分) 集めたデータセットで評価を実行します。私たちのチームでは、PRに紐づいてGitHub Actions上で評価が自動実行され、結果がLangfuseに蓄積される仕組みになっています。評価指標は単一のスコアではなく、「文体に一貫性があるか」「読みやすい文章になっているか」など観点別に複数用意されているのが特徴です。 ここでの判断基準は以下のとおりです。 スコアが低いデータ → 問題なし(苦手なケースを定量評価が正しく検出できている) スコアが高いデータ → 問題あり(本来検出すべきケースを評価の仕組みがすり抜けている) 3. 評価方法を改善する(30分) 評価ですり抜けてしまったケースに対して、各チームで原因を議論し、評価の仕組みそのものに手を入れていきます。コードベースの評価指標の追加・修正、LLM-as-a-Judgeの新しい評価指標の追加、既存の評価プロンプトの修正といったアプローチから選んで改善します。 ここが一番議論が白熱するフェーズで、「このケースって結局何が問題なんだっけ?」という問い直しから、「お客さんの望む"精度"ってどんな指標で測れば良いんだっけ?」といったプロダクトの本質的な価値まで話が広がることもしばしばありました。 4. 共有と発表(20分) 最後に各チームが、見つけたエッジケース・評価の改善内容・議論の過程を発表します。発表を聞くうちにチーム横断で共通するAI機能の課題が浮かび上がってきたり、「こんな観点、自分たちでは思いつかなかった」という発見があったりしました。そして何より、評価の改善って、案外自分たちでもできるんだという手応えを持ってもらえたのが、私としては一番嬉しい瞬間でした。 発表の様子 やってみてわかったこと 最大の発見は、「評価指標がすり抜けてしまうケース」を自分の手で見つけることで、初めて定量評価の限界と重要性を腑に落ちる形で理解してもらえるということでした。「スコアが高くても品質が低いことがある」という事実を、自分が収集したデータで目の当たりにすることで、PMやPDも含めたチーム全員が評価改善の当事者になれました。また、職種をまたいだ議論の中で、AIエンジニアだけでは気づけなかった「ユーザー視点での違和感」が評価指標に反映されるようになったのも大きな収穫です。 このワークショップは一度きりで終わりにするつもりはありません。いくつかのAI機能について、また小チームを結成し、チーム全員が関わる形で評価改善とAI機能そのものの精度改善に継続的に取り組んでいく予定です。 AI機能の品質は、AIエンジニアだけが守るものではありません。開発チーム全員で育てていくものだと、私たちは考えています。同じような課題感を持つチームの参考になれば嬉しいです。 おわりに 最後に、カミナシでは現在AIエンジニアを募集しています。本記事の通り、カミナシのAIエンジニアは実装だけではなく、AIを道具として適切に扱うための活動を文化として開発チームに根付かせる役割も担っています。興味のある方はぜひ一度カジュアル面談でお話ししましょう! herp.careers
はじめに カミナシで新規プロダクトの開発をしているShimmy(@naoya7076)です。 現在、新規プロダクトをプロトタイプとして開発しており、顧客に提供しながらフィードバックを得ています。Claude Codeをはじめ、開発の全工程でAIを活用しており、開発アイテムは予定以上のスピードで実装できています。 「AIでコーディングが速くなった。ではその空いた時間で何をやるのか?」 この問いに対して、自分が新規プロダクト開発で実践してきたことを書きます。 「もっと作る」はアンチパターン AIで実装速度が上がると、「もっと作ろう」という方向に引っ張られがちです。しかし、むやみに作るものを増やすのはアンチパターンです。理由は2つあります。 コードの意図の希薄化 AIによってコードを爆速で書けるようになりましたが、それを続けていると人の理解を超えた「意図が希薄なコード」が増えてしまいます。プロトタイプは本番プロダクトの土台であると同時に、顧客のドメイン理解の記録でもあります。なぜそのコードが存在するのかを読み取れなければ、プロダクションに活かせるドメインを発見することや、フィードバックを本番コードに活かすことが難しくなり、その両方の機能を失ってしまいます。 翻訳記事「AIコーディングツールによって加速するコード生成に品質保証活動はどう立ち向かうか」 - ブロッコリーのブログ 作るコスト <<< 取り下げるコスト 作るのが簡単になった一方、リリースしたものを取り下げるコストは変わらず高いままです。B2Bではこれがより深刻で、ほとんど使われていない機能でも、一社でもその機能で業務を回していれば取り下げコストは大きくなります。特にカミナシのお客様は製造業の現場であり、会社ごとに業務フローの差異が大きく、プロダクトが業務に深く食い込むためこの非対称性はさらに大きいです。「何を作るか」の精度を上げないまま作る量を増やすと、メンテナンスコストだけが積み上がります。 AIで空いた時間は何に使うか AIで空いた時間は、新機能を増やすのではなく 何をどこまで作るかの精度 作ったものが「顧客に届くまで」の速度 を上げることに使うべきだと考えています。コーディングだけが速くなっても、顧客検証まで含めた開発フロー全体に詰まりがあれば、顧客に価値が届くスピードは変わりません。速さを価値のスループットに変えるには、ボトルネックをコーディングから段階的に遠いところへ広げ、顧客に価値が届くまでの全体として捉える必要があります。 「点として速い」だけでは意味がない。AI時代のプロダクト開発で本当に大事なこと【Bet X】|LayerX ボトルネックを追う 自分が実践してきたことは、次のステップの繰り返しです。 ボトルネックを見つける — コーディングが速くなった今、ボトルネックはコーディング以外にある スケールしないところに自ら入る — 入ることで初めて、何がボトルネックで何がスケールの余地なのかがわかる 得た知見をプロダクトに還元し、スケールできるところは技術で解決する ここからは、コーディングから始まり、徐々に遠いところへボトルネックを追っていった流れを話します。 コードレビューのボトルネックを解消する AIでコーディングが速くなった後、最初に直面したのはコードレビューのボトルネックでした。 Claude Codeで実装速度が上がった結果、コードのフィードバックやレビューが追いつかなくなりました。1人エンジニアの体制なので、レビュアーは自分自身とチームメンバーです。AIが書いたコードの品質担保が課題になっていました。 AIが生成したコードをレビューしてみると、指摘点が多く、レビュー自体に想定以上の時間がかかっていました。そこで、静的解析やAIレビューを導入し、コードレビューの負荷を下げました。設計ルールをdependency-cruiserなどで機械的に強制し、AIが生成したコードの品質をプロセスで担保する仕組みを作りました。詳しくは以下のブログに書いてます。 kaminashi-developer.hatenablog.jp 顧客理解のボトルネックを解消する コードレビューのボトルネック解消と合わせて、次に顧客理解のボトルネックに取り組みました。 私(エンジニア)やデザイナーが後からチームに入ったため、顧客やプロダクトについての知識がありませんでした。「なぜこの機能を作りたいのか」「顧客がどういう仕事をしているのか」がわからず、詰め切れていないところを都度他の人に聞く必要がある。顧客理解の薄さが、コミュニケーションコストと判断の詰まりとして現れていました。 そこで私やPM、デザイナーなど含めて顧客訪問やヒアリングに自ら行きました。多い時には週1〜2回ペースで訪問しました。 補足 カミナシには「現場ドリブン」というカルチャーがあり、プロダクト開発に関わる人も積極的にお客様の現場に行く土壌があります。昨年は会社全体で3900回訪問しました。 カミナシの現場訪問回数は3900回/年 エンジニアが現場に行く意味 「AIで二次情報はいくらでも取れるようになった今、差別化できるのは現場でしか取れない一次情報です。」 PMやセールスを通じた伝聞では、情報は「要望」や「課題」として整理された形で届きますが、大事なのはその裏にある背景です。なぜその機能が欲しいのか、どんな業務でどんな事情があって出てきた声なのか。綺麗に整理された要望から背景を組み立て直すのは難しいので、エンジニア自身が現場で「なぜ」を聞いてはじめて、複数の要望を抽象化したり、もっとシンプルに実現できると判断できるようになります。 実際に新規プロダクト開発チームでヒアリングに行ったことで、顧客ごとに異なるプロダクトの重要な値が、実は設定値の組み合わせで表現できるとわかりました。その定義を体系化し、ドメイン知識の勉強会を行ったことで、チーム全体の資産にできました 顧客検証に開発を従属させる 顧客理解が進んで開発が動き出すと、次に見えてきたのは顧客検証そのものがボトルネックだということでした。 たくさん作っても、お客様にヒアリングできるのは一部です。検証のタイミングや範囲には上限がある一方、開発はAIで速く動く。全体のスループットは顧客検証で決まるため、開発をそれより速く動かしても、仕掛かり品のように検証されない機能が溜まるだけです。そこで顧客検証に開発を従属させるため、次の2つを組み合わせました。 1. 作るものを、検証で聞くべきことに絞る 限られた検証機会は「正解がわからず、お客様に聞かないと判断できないもの」に集中させます。具体的にはプロダクトのコア機能や、お客様の業務課題を本当に解決できるかに関わる部分にフォーカスしました。逆に、設定画面のような一定のベストプラクティスがある部分は、検証も実装もせず、設定に必要なデータは自分が直接投入することで代替しました。 2. 作るタイミングを、検証のサイクルに合わせる チームで「次の検証で何を聞きたいか」を合意し 事前ヒアリング → 開発 → お客様に使ってもらう → フィードバック持ち帰り のサイクルに開発スケジュールを合わせました。余った開発リソースは他のボトルネック解消などにあてました。 この2つの判断により、開発と検証が同じリズムで回り、「作ったのに検証してない」機能を溜めず、本当に必要な機能だけを開発できる状態になりました。 顧客訪問をスケールさせる 検証サイクルが回り始めると、顧客訪問そのもののスケールしにくさが次の課題になりました。さらに人と人とのコミュニケーションや移動が発生するため、訪問に多く行く人の周辺業務の負荷も大きくなっていました。ここで次の2つに取り組みました。 一次情報をチームで共有 & 活用する 一次情報をチームで共有、活用するための基盤は、カミナシのUXリサーチャーが以前から整えてくれていました。Centouというサービスを使って顧客訪問で得たインサイトを蓄積・検索できる仕組みが、会社全体で運用されています。新規プロダクト開発チームにもリサーチャーが入ってくれていて、この基盤の上にインサイトを貯めていくことで、現場に行かなかった人もいつでも一次情報を取得できる状態が作れました。訪問自体がスケールしない以上、得た情報をスケールできる形で蓄積して活用することが、チームとして一次情報の解像度を上げることにつながっています。 centou.jp 周辺業務をAIで楽にする 日々の仕事が忙しい人こそAIを活用したいのに、仕事に追われて導入が進められていない。そんな状況がありました。エンジニアへの導入とは違った観点が必要で、具体的には以下の取り組みをしました。 AI活用の主導: チーム全員のAI活用状況を事前アンケートで把握した上で、ClaudeやConnectの導入、セキュリティ面を含む運用設計、既にAIを活用している人からのユースケース共有までをまとめて実施しました。結果としてエンジニア以外のロールへのAI活用展開につながりました。 Google Docs/Drive/Slides連携MCPの自作: 公式のGoogle MCPがうまく動作していなかったので、自作して社内展開しました。以前なら公式の修正を待つしかありませんでしたが、AIを使えば1日足らずで実装できたのはAI時代ならではの変化です。 一次情報の基盤を整えたこと、AIで周辺業務を軽くしたことで、スケールしない顧客訪問そのものに時間を使い、そこで得た情報をチーム全体で活用できる状態になりました。 補足 別チームのPMも似たようなことを試行錯誤しているようです。こちらもぜひ読んでください: プロダクト開発を加速させる ー ビルドトラップの向こう側へ|migi 商談獲得のボトルネックに飛び込む ここまでのボトルネックが軽減されて実際に使っていただいた数社から高い評価を得られると、次は「価値の確からしさを、より多くのお客様で検証する」フェーズに入ります。ただ、狙っているセグメントで十分な数のお客様に触れてもらうには、手前の「顧客獲得」自体が足りていませんでした。顧客に価値が届くまでの全体を視野に入れたとき、顧客獲得はその一番元にあるボトルネックです。コーディングから始まった動きが、ここまで遠いところに辿り着きました。 展示会に乗り込む 新規プロダクトの顧客獲得には既に、既存顧客へのアプローチ、マーケメール、ハウスリストへの架電などの施策が動いていました。それでも理想としている商談獲得数には届いていませんでした。追加の打ち手として、既存サービスが出展する展示会で新規サービスも一緒に紹介させてもらうことになり、私もここにコミットすべく乗り込みました。 展示会に行くのは、商談獲得のためだけではありません。ここも一次情報の現場です。どんなサービスが世の中にあり、顧客がどんな課題を抱えて何に興味があるのかを大量にインプットできる場でもあります。エンジニアなどサービス側の人間が顧客獲得に関わりたい場合、展示会は最も良い機会だと考えています。 ただ「エンジニアが来ました」だけでは意味がありません。1戦力として活躍するため、事前にマーケの人に話を聞き、社内の展示会ドキュメントを読み込み、疑問点を解消した上で当日に臨みました。 行ってみてわかったこと 結果として複数件の商談を獲得できました。それに加えて どういう課題を抱えている顧客がこのサービスに興味を持つのか どういう機能の話をするとお客様が「使ってみたい」となるのか を実感を持って理解できました。Salesからの「こういうことを言われているので、この機能が欲しい」という要望に対して背景から理解できるようになりました。 一方で、展示会ではカミナシのサービス全体での商談獲得という目標もあり、自分たちのプロダクトにマッチしない顧客には他のプロダクトの話をする必要があったり、かけた時間に対してプロダクトへのフィードバック量は顧客訪問ほどではありませんでした。展示会自体も技術でスケールさせづらく、エンジニアリングを活かすのも難しい領域です。 それでも行ったことで「ここはどの程度自分が入る価値があるのか」を判断できるようになりました。スケールしないところに自ら入ることの価値は、大きな成果が出たときだけにあるわけではありません。入った上で次のボトルネック選定の精度を上げられること自体が価値で、一度もやらずに選択肢から外すのは勿体ないです。 まとめ 目指していたのは冒頭で書いた2つでした。 何を作るかの精度を上げる 作ったものが顧客に届くまでの速度を上げる サイクルの順序は計画して決めたわけではなく、目の前のボトルネックを一つ減らすと次のボトルネックが自然と見えてくる、という繰り返しでした。 エンジニアの価値は、コードの外にも広がっている ヒアリングも商談も、エンジニア以外ができることです。しかしエンジニア自身がそこに入ったからこそ、顧客の課題やどんな機能がどんな顧客に響くかを実感として持てるようになり、その理解がプロダクトに還元されました。一次情報を持ったエンジニアが、他のロールと並んで意思決定に加わる。というところにエンジニアの価値が移りつつあると感じています。 作ることが簡単になった今、エンジニアの価値はコードの中だけでなく外にも広がっています。スケールしないことを自ら選んでボトルネックを見つけに行くことが、AI時代のエンジニアに求められるようになると考えています。 今後やりたいこと 人と人との情報共有は依然としてボトルネックになりやすい領域です。周辺業務や情報共有を技術でスケールさせましたが、まだ「人が情報を整理して共有する」構造は変わっていません。 今後取り組みたいのは、AIを軸にした情報設計です。Slack、顧客訪問記録、商談記録といった一次情報からAIがドキュメント(PRD等)を生成し、さらにそのドキュメントから元の一次情報にいつでも辿れる状態にする。情報の流れそのものを設計することで、情報共有のボトルネックを人の努力ではなく仕組みのレベルで解消したいと考えています。 参考書籍 ザ・ゴール ― 企
はじめに カミナシでエンジニアをしている Shimmy です。今は新規プロダクト開発をしています。 0→1の開発設計では「コードベースの持続可能性」と「短期的なデリバリー速度」の両方が重要です。そのバランスを取りながら、AIの力を最大限活かせるアーキテクチャを考えてきました。 その過程で分かった設計原則というのは、AIを活用する前から変わらないものでした。 この記事では、AIの力を引き出す設計と、その設計を決定論的に守らせる仕組みついて話します。 補足: TanStack Start(フルスタックReactフレームワーク)を利用しており、フロントエンドとバックエンドが同一コードベースにあります。 AIの力を引き出す設計の3つの条件 自分のプロダクトの設計原則は次の3つです。 関心の分離: 関心事ごとにファイルをまとめる。AIのコンテキストに載せやすく、並列開発でもコンフリクトしにくい 価値の高いテスト: テストは数より質。振る舞い(Input→Output)を検証する出力値ベーステストと純粋関数の組み合わせで、モック不要でリファクタリングに強いテストが書ける 依存方向の決定: 層ごとに「何に依存してよいか」が決まっていれば、AIは迷わない。さらに静的解析で強制できる 上記の条件は、AIのために特別に取り入れた考えではありません。今まで良い設計とされていたものが、結果的にAIとの協働でさらに力を発揮するようになりました。 ここからは、各条件を深掘りして、採用した設計パターンと具体的な構成を見ていきます。 関心の分離 AIとの相性 関連ファイルが1つのディレクトリに集約されていると、AIのコンテキストに載せやすくなります。「このディレクトリを読んで、こう修正して」で済みます。散らばっていると、AIは修正箇所を探し回ってコンテキストウィンドウを無駄に消費します。 並列開発でもコンフリクトしにくいです。git worktree で複数のAIエージェントを同時に走らせても、関心事が分かれていれば触るファイルが重ならず、安全にマージできます。 Feature-Firstの構成 結合は悪ではありません。結合の強さと距離のバランスを取ることが大切です。結合が強いなら距離を短くし、距離が長いなら結合を弱くする。この考え方を Feature-First の構成に落とし込んでいます。 features/ ├── 関心事A/ │ ├── domain/ # Functional Core: 純粋関数のみ │ ├── infrastructure/ # Imperative Shell: DBアクセスなどI/O │ ├── server/ # API層: domainとinfrastructureを組み立てるエンドポイント │ ├── components/ # 複数ページで再利用するUI │ ├── hooks/ # カスタムフック │ └── index.ts # Public API ├── 関心事B/ │ ├── domain/ │ ├── infrastructure/ │ ├── server/ │ └── index.ts └── ... Feature内部 → 高凝集: 同じ関心事に関するコード(ビジネスロジック、DB操作、APIエンドポイント)が1つのディレクトリにまとまっています。統合強度は高いが、距離が短いので問題にならないです。 Feature間 → 疎結合: 各featureは index.ts を通じてPublic APIだけを公開し、domain/やinfrastructure/の内部構造は隠蔽する。外部からは index.ts 経由でのみアクセスするので、統合強度はコントラクト結合に留まる。距離が長い分、結合を最小限に抑えています。 domain、infrastructure、serverがそれぞれ1つのFeature内にあります。関心事Aに関するコードはすべてこのディレクトリに集約されているので、AIに「この機能を修正して」とfeatureを渡せば、そのディレクトリで完結します。 参考: 『ソフトウェア設計の結合バランス』(Vlad Khononov) Public API境界 各featureの index.ts は、外部に公開するものだけをexportします。 // features/xxx/index.ts // 外部から使う必要があるものだけを公開 export { calculateSomething, type Quantity } from "./domain/calculations"; export { useSaveRecord } from "./hooks/use-save-record"; export { recordsQueryOptions } from "./queries"; // 外部から使わないものは公開しない // domain/の内部ヘルパー、infrastructure/のDB操作詳細 など 実装の詳細は外部に公開していません。外部からはこの index.ts 経由でのみアクセスできます。featureの内部をどれだけリファクタリングしても、このPublic APIのシグネチャが変わらなければ外部のコードは影響を受けません。 featureを横断するケース Feature-Firstで分離したときに、最も考えるべきなのは複数のfeatureにまたがるケースについてです。横断が必要なケースは2つのパターンで対応しています。 パターン1: shared/ — 共通のビジネスロジックが必要な場合 複数のfeatureが使う計算ロジックは shared/lib/ に純粋関数として切り出します。 src/ ├── features/ │ ├── 関心事A/ │ ├── 関心事B/ └── shared/ └── lib/date.ts # 複数featureが使う共通の純粋関数 shared/ は features/ に依存しません。依存の方向は常に features/ → shared/ の一方向です。あくまで共通の純粋関数を提供するだけの層であり、shared/ が特定のfeatureの内部を知ることはありません。 パターン2: routes/ - 1つのページで複数featureが必要な場合 複数のfeatureのデータを組み合わせて1つのページを作るケースはどうするのか。ここで routes/ 層が登場します。TanStack Startの routes/ は、サーバーサイドでのデータ取得とUI描画の両方を1つのページとしてまとめる層です。各ページのコンポーネントやページ固有のロジックを持ちます。 (Next.jsの app/ ディレクトリでも同様の構成が取れるはずです。) src/ ├── features/ │ ├── 関心事A/ │ │ └── index.ts │ ├── 関心事B/ │ │ └── index.ts └── routes/ └── xxx/ └── $id/ ├── index.tsx # ページコンポーネント ├── -lib/ # このページのロジック ├── -components/ # このページのUI └── -hooks/ # このページのフック 先ほど話したように、各featureは index.ts を通じてPublic APIだけを公開し、互いに直接依存しません。 routes層でそれらを組み合わせます。ページコンポーネントが各featureからデータを取得し、組み合わせてUIを描画します。 // routes/xxx/$id/index.tsx // 各featureが公開するデータ取得関数を使って並行取得 const [recordsA, recordsB, recordsC] = await Promise.all([ fetchFeatureA(id, date), fetchFeatureB(id, date), fetchFeatureC(id), ]); // 複数featureのデータを組み合わせてUIを描画 const rows = buildRowData(recordsA, recordsB, recordsC); return <DataGrid rows={rows} ... />; 各featureは互いの存在を知りません。どのfeatureを組み合わせるかを決めるのはroutes層(ページ)の責務です。この構造により、Feature-Firstの疎結合を維持したまま、横断的なページを柔軟に構築できています。 価値の高いテスト 価値の高い単体テストとは ここでは単体テストに絞って話します。テストは数より質が重要です。 「単体テストの考え方/使い方」では価値の高い単体テストの条件として4つの柱が挙げられています。 退行(リグレッション)に対する保護 リファクタリングへの耐性 迅速なフィードバック 保守しやすさ 退行保護、リファクタリング耐性、迅速なフィードバックの3つはトレードオフの関係にあり、同時に最大化できません。ただしリファクタリング耐性は「あるかないか」の二値なので、まずこれを確保した上で残りのバランスを取ります。リファクタリング耐性がないテスト(偽陽性が多いテスト)はテストへの信頼を損ない、やがて無視されるようになるからです。 参考: 『単体テストの考え方/使い方』(Vladimir Khorikov) 出力値ベーステスト リファクタリング耐性を確保するために関数の内部実装ではなく振る舞い(Input→Output)を検証するテストを行っています。これが出力値ベーステストです。関数にInputを入れて、返ってきたOutputを検証する。テストが検証するのは「関数が内部でどう動いているか」ではなく「どんな入力に対してどんな出力を返すか」です。内部実装に依存しないので、振る舞いが変わらない限りリファクタリングしてもテストは通り続けます。 domain層の純粋関数(補足: コードは抽象/単純化しています // features/xxx/domain/calculations.ts /** 進捗率を計算する純粋関数 */ const progressRate = ( actualTotal: number, targetQuantity: number, ): ProgressRate | null => { if (targetQuantity === 0) return parseProgressRate(0); const ratio = actualTotal / targetQuantity; const percentage = roundToOneDecimal(ratio * 100); return parseProgressRate(percentage); }; この関数に対する出力値ベーステスト: describe("progressRate", () => { it("実績と目標から進捗率を返す", () => { expect(progressRate(75, 100)).toBe(75.0); }); it("100%を超える場合も正しく計算する", () => { expect(progressRate(120, 100)).toBe(120.0); }); it("目標が0のとき0を返す", () => { expect(progressRate(50, 0)).toBe(0); }); <span class="synI
プロダクトマネージャやデザイナもAIでプルリクエストを作成できるプロセスを作ろう こんにちは。息子と『ドラベース』を読みはじめた daipresents です。トンボール投げたい! カミナシでは「カミナシ 教育」と「カミナシ 従業員」のマネージャを担当しております。 前回、月1回のオンサイトにおける取り組みを紹介させていただきました。 参考: エンジニアじゃない人でもAIを使えば開発貢献できるんじゃないの?イベントを開催してみた こちらについては、プロダクトマネージャやプロダクトデザイナの評価はとても高く、「もっとやりたい!」、「リリースしたい!」と、みんな開発に対する意気込みを表明してくれました そこで、翌月のオンサイトでは、今回はさらに一歩進んだ取り組みとして、「イベントでやるだけでなくて、開発フローに組み込めないか」を実験してみました。 しかし、実際にプロセスに組み込んでみようとなると、課題が出てきます。 オンサイトの様子 課題1: 動作確認環境をどうするか? AIの登場によって「作る」はとても簡単になりました。ひとつめの課題は「作ったものをどうやって確認するか」です。 僕のチームの場合、環境としてローカル環境や開発環境等を活用していますが、プロダクトマネージャやプロダクトデザイナはこれまでブラウザで開発環境や本番環境にアクセスするぐらいで十分でした。 しかし、自分が作ったものを動作確認するとなると、共有で利用している開発環境は使えませんし、本番環境に反映するのも難しい。 よって、今回はローカルでの動作確認ができるように、環境構築を進めました。 エンジニアが用意してくれた非エンジニア向けの開発環境手順書 必要なソフトウェアやGitの初期設定までを解説したドキュメントによって、環境構築がどんどん進みました。 また、プロダクトマネージャやプロダクトデザイナのやる気もすごく、忙しい間をぬって環境構築に時間を割いてくれたのも勝因のひとつだと感じています。 次のチャレンジとしては コマンド一撃で環境構築される仕組みを作る プルリクエストごとのプレビュー環境を用意する クラウド環境にログインしただけで開発できる・・・ といったアイデアもでてきています。 課題2: プルリクエストまでの開発お作法をどう教えるか? こちらに関しては、前のオフサイトでやったように、翌月のオフサイトで時間を取って、エンジニアとペア作業を行いながら、開発開始、修正、プルリクエスト作成までを学んでいただきました。 前回で開発の流れをだいたい掴めているので、プロダクトマネージャやプロダクトデザイナの理解度はとても高かったように思います。 ここで問題になったのは、非エンジニアが出したプルリクエストをどう処理するか?です。 通常の開発の流れだと、 修正者が修正の意図と、修正したコードをセットにしてPRを作成する 別のエンジニアがプルリクエストを確認して、フィードバックを行う 問題がなければプルリクエストはマージされる となりますが、非エンジニアに対して難しいフィードバックをすると・・・ プロダクトマネージャやプロダクトデザイナがプルリクエストを作成してエンジニアにレビュー依頼する エンジニアが「このループ、計算量が $O(n2)$ になっているので、$O(n)$ に落とせませんか?」みたいな小難しいフィードバックをしたとする プロダクトマネージャやプロダクトデザイナは、レビューのフィードバックをClaude Codeに投稿して対応する エンジニアがさらに小難しいフィードバックをする プロダクトマネージャやプロダクトデザイナは、レビューのフィードバックをClaude Codeに投稿して対応する ・・・ こうなってくると、フィードバックの反映に「人間を挟む意味とはなにか? 」を人類代表として考えこんでしまいます。 よって、現在は、 プルリクエストを作成するところまでをプロダクトマネージャやプロダクトデザイナの責務とする ただし、プロダクトマネージャやプロダクトデザイナ視点で必要なフィードバックはコメント等で行う プルリクエストのレビューからマージはエンジニアが担当する という形で実験運用しています。 プロダクトマネージャやプロダクトデザイナがコードベースに貢献する世界線の誕生 まずはオフサイトで作成したプルリクエストが翌週以降、ぞくぞくとリリースされました。 PRの一覧にプロダクトマネージャやプロダクトデザイナのアイコンが並び、どんどんApproveされていく。 その後も継続的にプルリクエストが投稿されるようになってきました。 今回の実験を通して感じたのは、軽微な修正はプロダクトマネージャやプロダクトデザイナ主体のプルリクエストで十分プロダクト改善できることです。 上記のように「ちょっと不便だな」と感じる部分は、やることリストの優先度が低くなりがちです。一発目のリリースに入れられなければ、二度と優先度が上がることなく、永遠の優先度低としてやることリストの下の方に残り続けがち。心理的にも「なんだかなぁ」という気持ちになります。 しかし、AIの登場で開発のハードルが低くなったことから、エンジニア以外が戦力としてコードベースを改善できる世界線が実現しました。 もしかしたら、カスタマーサポートやカスタマーサクセスが同じ動きをすると、CRE(Customer Reliability Engineer) に近い存在になるのかもしれません。 最後に、この企画を考えてくれたエンジニアからのコメントがこちら。 プロダクトマネージャやプロダクトデザイナに使ってほしいのは、こうした環境でプロトタイピングしたりデザインや企画の叩きを作って考えてもらうことです。 その後、プロダクトデザイナによる体験向上の提案がプルリクエスト上で出てきました。そのデザイナはローカルで実装をすませ、実際の動きを見せながらチームに説明。採用されたものはそのままリリースへと進み出しています。なによりもこの貢献が 「楽しい」 そうです。 AIとの付き合い方はまだまだ実験が必要そうですが、開発を「楽しい」と思ってくれる人が増えたのは、AIのおかげかもしれませんね。
AIと不安で眠れない夜。 あ〜〜〜〜〜今日もTwitterのタイムラインはAI、Claude、OpenClaw、エーアイ、Codex、Gemini、ハーネスの話題で持ち切りだわ。なんだよハーネスって。自意識過剰なホモサピエンスがAI様をコントロールできると考えているのか!?奴らの成長速度を考えたら、数年以内に制御できる範囲なんてとっくに飛び出して二足歩行でコンビニ行ってオハヨーのブリュレアイス買って食っとるわ。あれうますぎだろ。 あ〜〜〜〜〜わかってるよ。Twitter呼びは時代遅れだって?そのツッコミも飽きたわ!俺は死ぬまでTwitterって言うからいちいち気にしないでくれ! ジュニアやミドルエンジニアは不要?そうかもね!日々開発して身に染みてるよ。 と思ったらなに!エンジニア自体不要?SaaSも is dead?まだ俺は生きてんの!勘弁してよ〜〜〜〜〜〜〜。 漠然とした焦燥感と、描いていたキャリアプランが夢のまた夢となりそうな不安で、毎日押しつぶされそう。なんとなく寝れない。なんだったら日中もそのことがよぎる。 無駄に頭の稼働率が高い。生産的なわけではなく、鞭打って動かされてる感。俺の脳こそハーネスつけてレースさせられてんじゃね。誰か俺を、人々を助けてくれ〜〜〜〜。 オス!オラりくう。カミナシでソフトウェアエンジニアをやってる。23歳。実は大学を中退して就職したのでエンジニア歴は3年。石の上にもってヤツ。 AIと差別化するためにM-1グランプリに出たりしてる。笑いのセンスはまだギリ人間が勝ってるらしい。なんとか大学の研究でなんか証明されてた。 (左:かわりく。右:文章後半で登場する強いエンジニア) M1出たときの写真 一般的には経験年数3年以下はジュニアエンジニアに分類されるらしい。もちろん年数なんて本質的じゃない。転職活動ではミドルだと言い張って、都合が悪いときにはジュニアということにする。 オラと同世代は新卒か年数的にはまだジュニアな人が多いと思う。だから漠然と同じような不安とか悩みを抱えてんじゃないかと思って、今日はその悩みをみんなと共有したい。共感したい。慰め合いたい。傷を舐め合いたい。安心して!この文章は100%オーガニックライティングだから、SDGsへの配慮もバッチリだ。 徒労感 インザスカイ 今自分が身につけようとしてるあらゆる技術や知識が、明日には陳腐化してるんじゃないかと考えてしまう。AIの進化速度の前では、相対的に全ての営みが差別化にならなくてゼロになってしまうという不安。徒労感。タイパとか言いたいわけじゃなくて、バイブでできるなら俺の努力意味なくね?と思うのは自然な感情だと思う。 さらば自己効力感 最近は仕事でうまくいかない事があると、AIをうまく使いこなせていないせいなんじゃないか?という思考に陥る。それもある側面では正しいが、AIの応用ばかりに目がいって、そもそもの問題の本質に向き合う機会が減ってしまっている気がする。自分の頭を働かせるよりもAIに考えさせたほうが確度や効率が良いのでは?と思ってしまう。 ちょっとSNSを覗くと、バイブで完璧にサービスを開発して提供しています!みたいな極端な成功例が多くて、日々の仕事のインクリメンタルな成果が陳腐に見えてしまう。 AIは強いヤツを更に強くする。 AIは確かに強いヤツを更に強くする。べき乗的なバフをもたらす。 周りを見ると、元からめちゃくちゃ強かったエンジニアがAIによって半端なく強くなっている。とある知り合いはAIと共にOSSにコミットしまくり(AIスロップ的なやつじゃなくて、ちゃんとマージされてる)、CVEを取得しまくり、バグバウンティで稼いでる。かっけえ。 それを見て卑屈になるなど。 キャリアプラン?目標設定?そんなのわかんないよ!!! 今まで当たり前に思い描いていた、先輩の背中を追うんだと思っていた未来は知らぬ間に波にさらわれた... ど、どうなるんだ?俺は。俺達は。エンジニアは。そして人類は... 全てはLLMのモデルの進化が解決する。全ての問題と俺はその時が来るのを指を咥えて待っているだけなのでは...? 5年後にどうなりたいか?わかるわけないじゃん!5年後に何が残る? 目標設定?今はLLMモデルの進化そのものの影響がでかすぎて、適切な目標もよくわかんないよ! でも、そんな俺にも快眠できる夜があったのさ... 自分の門外の技術領域・言語(画像処理・C++)に触れる機会があった。 よ〜わからんなという感想で終わってしまいそうだったが、今の俺には最強のバディがいる。ヤツに質問しながら、時には図解してもらったり、呪術廻戦で例えてもらったり、完全オーダーメイドマンツーマンを施してもらった。 わかる...!!!着実にわかるようになっている...!!!を実感した。一昔前なら相当苦労しただろう。まずは言語の文法から押さえ、数冊のデカくて高い専門書を読み漁り、ようやく出発点に立てただろう。しかも忍耐がないと中途半端に手放すことになる。 ヤツはどこまでも寄り添って教えてくれる。結果的にめちゃくちゃ素早くキャッチアップし、新機能を開発できた。素早くアウトカムを出せた。 自分が今まで得意だと思っていた技術領域をAIと開発しているとき、無力感に襲われていたが、新しい領域に踏み出したとき、その可能性と純粋な知の喜びを思い出した。 また、最近は半端ない速度でプロトタイピングができるようになった。それこそバイブで。でも明らかにソフトウェアエンジニアだから出せる速度だと感じる。 技術の直感がないと、そもそも問題の切り分けが難しい。 要求や仕様の前提が複雑すぎるのか、技術的に難しすぎるのか。とりあえず作る。の前段階で整理ができる。結果としてまともに動く状態に達するまでが早い。 結果的に素早くフィードバックを得られるし、その回数を増やせるので、最終成果物のクオリティが高くなる。ニンマリホッコリした。 決意の朝に(かわりくの場合) 気づいた。俺は、AIを、自分の成長速度の鈍化の言い訳にしている!!!!! AIは本当にすごい。Bet AIすべきだ。ただ、そのことと自分の成長を諦めることとはなんの関係もない。 自分のフットワークの重さをAIのせいにして、頑張ることを諦めている。コンフォートゾーンに留まっている。 負けるな!負けるな!でも戦う相手はAIじゃない。自分自身だ。 そして23歳はまだ若い。というかあまりに若すぎる。いくら17,18のインフルエンサーが活躍していて卑屈になる夜があっても大丈夫。まだ若い。 だからサンクコストを恐れるな!今まで積み上げたもの?少なすぎる!たかが10年とかの積み上げを大事に守るな!これから生まれる無限の新しいチャンスに挑戦しよう。悲観的な未来を想像することには意味がない。AIといっしょに楽観的な未来を創造しようじゃないか。 宣伝 と、ここまでAIに対する不安を爆発させつつ、カミナシのエンジニアがどうやってAIと仲良くやってるかトークするイベントを開催しますので、ぜひご参加ください。AIすげぇ…怖い…と僕と一緒に怯えましょう。 https://kaminashi.connpass.com/event/387233/
「カミナシ レポート」の開発・運用をしている、AWS インフラが得意な Security Engineering の furuya です(属性過多)。妙に流行り物に乗っかるときがあるのですが、「超かぐや姫!」を見てきました。よかったです。それはさておき今回は「カミナシ レポート」の開発におけるセキュリティ向上施策のお話です。 カミナシでは開発チームに Security Engineer を派遣する取り組みがあります。 kaminashi-developer.hatenablog.jp 気がつけば、この記事の公開から1年が経過していました。ここでそれを振り返ってみたいと思います。 サービスにおけるコンテキストを把握する 派遣されるセキュリティエンジニアは基本的には最初の半年から1年程度は開発チームの1メンバーとして、ともに開発や運用を行います。ですので、障害対応やインシデント対応、お客様からの通常のお問い合わせなどのオンコール担当としても活動します。 この1年を通して「カミナシ レポート」の開発・運用に携わりました。私自身のキャリア的にコーディングから離れていたのでその点は AI の力も借りながらそこそこに、代わりに得意なインフラ・運用面で改善をしたり、オンコールに入ることでチームメンバーの負荷を軽減したりして貢献しました。およそ最初の半年くらいでチームには馴染めたと思っており、そこで得たコンテキストから残りの半年は重めのインフラ・セキュリティ改善に取り組むことが最優先であると判断し、チームとしても合意の上で対応を進めることができました。 施策のテスター その欠陥を修正することにより獲得したナレッジを他サービスチームに共有することができ、セキュリティ上の欠陥が埋め込まれるのを未然に防いだり改善することが狙いとしてあります。 欠陥の修正ではないのですが、全チーム対応しないといけないようなセキュリティ施策(AWS Security Hub CSPM の新しいコントロールへの対応など)をまず最初に「カミナシ レポート」の開発チームで実施し、対応するための手順や Terraform のコード、ハマりどころを公開して他のチームの工数を削減する、といった取り組みを行いました。サービスを運用する中での変更になるので特有のワークアラウンドが必要なこともあり、単純にマニュアル通りにいかないケースで役に立ったかと思います。 セキュリティナレッジの共有 また、セキュリティエンジニアが持っているナレッジや考え方などをサービスチーム内で共有することにより、エンジニアがセキュリティを意識しながら開発や運用を行えるようになることを目的としています。 私自身セキュリティを専門としたキャリアを歩んでいないのでナレッジの共有といっても手探りではあります。ただ、だからこそ「セキュリティどうしたらいいかわからない開発者」の気持ちにもなれます。やはり開発する身になってみると「セキュリティ、いつどこでどうしたらいいかわからない」というのが真っ先に思い浮かびます。 「いつどこで」がわからない、というのはセキュリティを気にするべきタイミング・ポイントに気付けなければ実施・対策しようがない、ということでもあります。何にせよまずは知識を得るところが大事なのではないか、と思っていたときにちょうど「開発者はどこまでセキュリティを気にすればいいのか」という問いをメンバーからもらいました。そのときの話をセキュリティエンジニアの西川さんがブログに書いてくれています。 kaminashi-developer.hatenablog.jp そういう気に掛けることができるかどうかというのがすごく大事で、そのような気付きがあったときに「一旦セキュリティエンジニアに聞いてみようかな」というマインドセットを持てるかどうかが重要だと思っています。 気付ける筋肉みたいなものがあると思っており、それはやはり知識と実践で養われていきます。気付けさえすれば自分で解決することもできますし、できなくてもセキュリティエンジニアに聞くというアクションがとれます。 前置きが長くなったのですがやっぱりまずは座学で知るところからが一番でしょう、ということで開発者が気にすべきセキュリティの勉強会を開催しています。 geminiに作ってもらったセキュリティ対策全体像 第1回は「設計で気付けるとよいポイント」ということで脅威モデリングのワークショップを、第2回は「普段の実装の中で気付けるポイント」として SAST(Static Application Security Testing。コードの脆弱性チェック)の紹介および運用に取り入れる方法について議論をしました。もっと自然にチームメンバーが「気付くことができる」ように、定期的に実施していきたいです。 想定外の効果 すべてのチームにセキュリティエンジニアが派遣できればよいのですが、人数も限られており実現はできていません。しかし、派遣されなかったチームにも心境の変化があったようです。具体的には、派遣されないならされないなりに自分たちでよりセキュリティを高めていくぞ、というオーナーシップの醸成に繋がったチームがありました。実際、(個人的に)悔しいながらそのチームが一番セキュリティが出来ていそうなので、副次的ではありますが他のチームにいい影響を与えることができたのではないかと思っています(ここから挽回していきたいです)。 これから この1年を通して開発・運用をしながらそのコンテキストを理解したうえでセキュリティ対策をしたり、セキュリティ文化の醸成に取り組んだりしてきました。まだやるべきことはたくさんあります。この1年でようやく土台が整ってきたところなので、ここから『「カミナシ レポート」の開発チームって息をするようにセキュリティできてるよね』と言われるようになりたいです。 「カミナシ レポート」はカミナシのフラッグシッププロダクトということもあり、昔から積み上がった課題が複数残っています。これらに対処しつつ、先程のSASTなどを「開発速度を落とさないように」運用にのせて一段上のセキュリティレベルを目指しつつ、チームメンバーがもっとセキュリティに「気付ける」ようにしていきたいと思っています。
こんにちは、ソフトウェアエンジニアの渡邉(匠)です。「カミナシ 設備保全」の開発に携わっています。 Claude CodeのSkills(以下スキル)を使い、約2週間で40,000行超のAPIシナリオテストを書き切りました。最初のスキルは粗削りでしたが、テストを量産する中で繰り返し改善した結果、後半は「スキル実行 → レビュー → マージ」のサイクルだけで回せるようになりました。 この記事では、スキルをどう設計し、どう育てたかを中心にお伝えします。 背景 APIの動作保証にシナリオテストツール runn を使っていました。 サービス成長に伴うAPIの増加により、当初のテスト構成では運用が回らなくなってきました。1ファイルあたりのステップ数が膨らみ、どこで何を検証しているのか判別が難しい状態に。テストの追加・修正が心理的にも技術的にもハードルの高い「辛い作業」へと変わっていました。 そこで、テストを以下の2種類に整理し直すことにしました。 API単位テスト: 1ファイル = 1API。正常系・異常系を網羅的にカバーする ユースケースシナリオ: 複数APIにまたがる業務フローのE2Eテスト 対象APIは数十個以上。正常系・異常系を含めると数百パターンに及ぶテストをClaude Codeで生成しました。 テスト生成の仕組み テスト種別ごとにスキルを用意しましたが、もっとも多用したAPI単位テストのフローを紹介します。 [Step 1] 情報収集 API定義やバリデーションロジックなど、関連コードを読み込む ↓ [Step 2] テストケース洗い出し(ホワイトボックス) コードからエラーハンドリングを網羅的に抽出し、 正常系・バリデーション・権限・存在チェックなどのケース一覧にまとめる ↓ [Step 3] ユーザー承認 テストケース一覧をMarkdownで提示し、過不足を確認してもらう ↓ [Step 4] サブエージェントに実装を委譲 API単位で分割して渡す テストファイル生成 → 実行 → エラー修正を自律的に繰り返す ↓ [Step 5] セルフレビュー チェックリスト(ファイル形式・テスト構造・命名規則)で品質確認 ここで重要なのはStep 4の委譲単位です。「サービス全体を一度に」ではなく「API単位で分割して」サブエージェントに渡すルールにしました。一度に渡すとコンテキストが膨れ、テストの品質が落ちるためです。 スキルを育てる スキルは最初から完成していたわけではなく、テストの量産を通じて段階的に改善していきました。 Phase 1: 基本構成を固める 最初の数APIで、テストの基本構造と命名規則を決めました。この時点のスキルはシンプルで、生成フローの大枠だけを定義したものです。 Phase 2: runnスキルの切り出し 複数サービスのテストを書いていく中で、Claudeが繰り返し同じrunnの記法ミスをするパターンが見えてきました。テスト生成スキルの中にrunn記法の注意点を書き足していたのですが、量が膨れてきたので、runnの記法ナレッジだけを独立スキルとして切り出しました。 Phase 3: サブエージェントの導入と構造の再設計 1セッションで情報収集から実装・実行まですべてをこなす構成にしていたところ、コンテキストが溢れて途中で止まるケースが頻発しました。 そこでスキルの役割を分離しました。 スキル本体: 情報収集 → テストケース設計 → ユーザー承認 サブエージェント: ファイル生成 → テスト実行 → エラー修正 それぞれが小さなコンテキストで動くため、テスト品質を維持しつつ、一度に生成できる量が増えました。 同時にスキルファイルの見通しが悪くなったため、以下の整理もおこないました。 テンプレートやチェックリストを参照ファイルに外出し スキル本体(SKILL.md)はフローの記述に集中 ぶつかった壁 runnの記法をClaudeが知らない runnはYAMLベースでテストを書くことができる柔軟なツールですが、独特の記法がLLMにとって馴染みが薄く、頻繁にミスが発生しました。 bindとincludeの実行順序 # ❌ NG: bindはincludeの後に評価されるので、faker値をincludeに渡せない steps: create: bind: name: '{{ faker.LetterN(10) }}' include: path: ./service/Create.yaml vars: name: '{{ name }}' # ← この時点でnameは未定義 # ⭕ OK: 別ステップで先にbindする steps: prepare: bind: name: '{{ faker.LetterN(10) }}' create: include: path: ./service/Create.yaml vars: name: '{{ name }}' # ← prepareで定義済み testフィールドのアサーション記法 runnの test: では expr-lang の記法が使えますが、Claudeはこれを知らず、存在しない関数を書いてしまうケースが多発しました。 # ❌ NG: .startsWith()や.endsWith()はexpr-langに存在しない test: | current.res.body.name.startsWith("テスト") && current.res.body.file.endsWith(".csv") # ⭕ OK: expr-langの関数を使う test: | hasPrefix(current.res.body.name, "テスト") && hasSuffix(current.res.body.file, ".csv") こうした「LLMが知らないrunn固有の記法」をNGパターン/OKパターンとしてrunnのスキルに蓄積していきました。 最大の壁: runnのエラー出力をClaudeが読めない これがもっとも苦労した課題です。 runnのテスト失敗時、assertionの条件式はツリー構造で展開されます。&& や || で条件を組むと、条件ごとに二分木のようにネストが深くなり、どの項目で失敗したかの判別が非常に難しくなります。 Condition: res.body.item.id != nil && res.body.item.name == "foo" && res.body.item.status == "active" && res.body.item.note == nil │ ├── ... && ... && ... == "active" => false │ ├── ... && ... == "foo" => true │ │ ├── res.body.item.id != nil => true │ │ └── res.body.item.name == "foo" => true │ └── res.body.item.status == "active" => false ← ここが原因 │ ├── res.body.item.status => "draft" │ └── "active" └── res.body.item.note == nil => [not evaluated] 人なら status == "active" が false だとわかります。しかしClaudeにとっては、ツリーの中から => false の葉を探す作業になります。実際のテストでは条件が6〜10個になることも多く、ネストはさらに深くなります。 結果として、Claudeがツリーを読み解けずに、テストを何度も再実行する悪循環に陥ることがありました。 解決策: assertionをステップに分離する テスト設計側で工夫しました。1つの test: に全条件を詰め込むのではなく、フィールドごとに独立したステップに分離します。 # ❌ Before: 1つのtestに全条件を&&で連結 test_1: test: | current.res.body.item.id != nil && current.res.body.item.name == "foo" && current.res.body.item.status == "active" && current.res.body.item.note == nil # ⭕ After: フィールドごとにverifyステップを分離 test_1: bind: test_1_res: current.res test: | current.res.status == 200 verify_1_id: desc: "test_1: id" test: test_1_res.body.item.id != nil verify_1_name: desc: "test_1: name" test: test_1_res.body.item.name == "foo" verify_1_status: desc: "test_1: status" test: test_1_res.body.item.status == "active" verify_1_note_nil: desc: "test_1: note == nil" test: test_1_res.body.item.note == nil この設計により、3つの問題が一度に解消しました。 エラー出力がシンプルになる: 1ステップ = 1条件なので、ツリーのネストが発生しない ステップ名で失敗箇所がわかる: verify_1_status ... FAIL と表示され、一目瞭然 Claudeが自律的に修正できる: 失敗したverifyステップ名から修正箇所を正確に特定できる 人がテストを手動で作成する場合、記述量が増えるため、あえてアサーションを分割しない選択をとることが多いと思います。しかし、LLMにテストを書かせることを考えると、記述の煩雑さ以上に修正とデバッグを自律的に実行できることに大きな価値が生まれました。 振り返
「カミナシ レポート」の開発・運用をしている furuya です。最近我が家では成長してきた子どもたちのことを考えて寝室含めて部屋の配置換えを検討しており、そのパズルに頭を悩ませています。それはさておき今回は「カミナシ レポート」の開発において AI Agent を主軸にした開発スタイルを取り入れたお話です。 背景 近年の AI Agent の進化は目覚ましいですね。日々情報がアップデートされる中、カミナシのエンジニアリング組織としてもこの流れについていかなければならない、ということで各チームいろんなことにトライしており、組織的にもそれが推奨されています。もちろん、前提として以前から GitHub Copilot や Claude Code などの Coding Agent を開発に取り入れています。そんな中で個人的に感じた課題と同時期に得た学びがカチッとはまる出来事がありました。 課題 以前の開発スタイルはこうでした。 1週間スプリントのスクラム PdM、デザイナー含めて PBI をリファインメントする ここでおおよその実装方針の合意やフロントエンド・バックエンドそれぞれでチケットを作成し、ストーリーポイントを見積もる ストーリーポイントをもとに1スプリントで捌くチケットを決め、おおよそ1チケット1担当者で分担して開発を進める フロントエンドは xx さん、バックエンドは yy さんといった具合 それぞれが自分の進め方(AI Agent の使い方含めて)で進める Copilot Instruction 等チームで共有すべきコンテキストは随時整備している 2025年12月頃、しばらく私が重めのインフラ構築をやっており久しぶりに開発に戻ってきました。今までの進め方自体に大きな問題はなかったのですが、あらためて1人で開発を進めているときに以下の課題・違和感を覚えました。 リファインメントの段階では見えていなかった詳細な要件が、実装フェーズに入ってから顕在化することがある Slack 上で非同期で PdM やデザイナーに確認するだけではありつつ、待ちやラリーが発生するため結局同期で話すことも Agent が出してきたコードをレビューしきれない 一度に多く出てくると目が滑るのと、個人的にはフロントエンドの引き出しが少ないのでセルフレビューに不安が残る Agent によってコード生成は早くなったが、レビュー依頼を出したあとアンコントローラブルになる・待ちが発生する スプリントゴールに紐づくアイテムの場合はもちろんみんな優先して見てくれるものの、それぞれタスクを持っているのもありいつ終わるかは明確ではない AI-DLC という考え方との出会い ちょうど同じ頃、AWS re:Invent 2025 への参加をきっかけに AI 周りの解像度が高くなった中、AI Builders Day というイベントで AI-DLC という手法に出会いました。 speakerdeck.com 詳細は上記 AWS 金森さんの資料などをご覧いただければと思うのですが、この話を聞いた瞬間に「チームでやってみたい!」と思いました。先程の課題は AI-DLC の考え方を適用することで解決できるのでは?と感じたからです。もちろんいきなりスクラムをやめるまでいくと混乱しそうなので、「AI Agent 主体で進める」「ペア・モブプロで進める」というあたりだけでも取り込むことで以下の効果が期待できるのではないか、と思いました。 AI による観点が加わることで要件のヌケモレに気づきやすくなる 要件を人間 + AI で決め、そのままドキュメント(requirements.md)に落とし込むため 熟練者のレビュー観点・引き出しを盗める みんなで AI Agent の出したコードをその場でレビューすることのメリット PR作成からマージOKのリードタイムが短くなる みんなでレビューするので、待たなくてよさそう チームに持ち込む AI-DLC の解像度はあまり高くないままではありましたが、上記の課題感およびこんな手法があってね、という話をチームに持っていき、やってみよう、ということになりました。チームとしてもメンバーの入れ替わりや長期離脱の予定があるなど変化の激しいタイミングであり、生産性を落とさないため & AI への投資ということで意見が一致しました。 やってみた 本来の AI-DLC のやり方、というよりはスクラムの形を保ったままとりあえずやってみるということで、どちらかというと仕様駆動開発として AWS の AI-DLC workflow を使う、というほうがイメージに近そうです。 github.com Agent には主に kiro(kiro-cli)を使っています。スタートアップ向けのクレジットをいただいたので、使わないと損だなと個人的に思っているからです。workflow 自体はいわばプロンプトでありどの Agent にも組み込めるため、 Claude Code でやってみるターンもあってもいいかもしれません。 まずはペア or モブで進め、スクラムの形は変えない、というところからスタートします。ここから少し長いですが、4スプリント分のゴール設定・結果・振り返りについて共有します。 1週目 ちょうど直近大きめの開発アイテムがあったため「リファインメント + ちょっと実装まで進んでいる」をゴールにしてトライしました。 結果として、リリース順序やデータベースの変更等も考慮して Phase を4つに分割し、それぞれの設計までできました。また Phase 1 の実装は PR の作成まで完了しました。以下振り返りです(以降同様のフォーマットです)。 Phase ごとにわけて「設計だけやる」はイマイチかも 設計を進めるには前の実装が終わっていることが前提で、かつ設計のみ進めた場合前の Phase の実装がまだないので Agent が混乱する 開発体験としてはポジティブ、workflow に則って設計開発進めること自体はよさそう PR の粒度や Design Doc の渡し方など改善ポイントが見えてきた このアイテムに関してはいったんこの進め方で最後までやってみる が、見積もりをしないのでいつ終わりそうか、みたいなのが言いづらくなる懸念も もちろんダメそうだったらすぐ止めるということも合意する 2週目 前週とは別のアイテムを対応する必要があり、そちらで引き続き workflow に則って実施しました。 設計 & 実装は1日で完了(フロントエンド、バックエンドともに)し、2日目にガッツリレビューをするという流れで難なく完了まで持っていけました。 1日ガツっと実装して次の日別れてレビューなどに回すのはアリかも やっぱりずっとやり続けるのはちょっとしんどい 真の AI-DLC を実践してるところは永久にペアプロしてるらしいけど… GitHub Copilot が PR でいっぱい指摘くれるので修正する時間や、次のアイテムの下準備の時間が必要 3週目 1週目で分割したアイテムの続きに取り組みました。また、新たなトライとして以下を検証しました。 Figma MCP サーバーを使ってフロントエンドの実装を楽にできるか検証する フロントエンドとバックエンドのリポジトリが別れているが、いい感じにまとめて設計実装できないか試行錯誤する 最終的にモノレポがいいのであればそれも視野に入れる トライ多めだったので実装は思ったところまでは進みませんでしたが、その分収穫がありました。 Figma MCP サーバーを使って実装してもらうとデザイナーが作ったものがそのまま反映できてよい(それはそう) Figma に情報がある場合はデザイナーと一緒にフロントエンド実装できるとよさそう なんならデザイナーがドライバーになって進めてもらった(実装できるデザイナー!) 設計をフロントエンド・バックエンド両方いっぺんにやると設計のターンが長くなりがち デザイナーと一緒にモブしたいのは、UXにまつわる要件の整理とフロントエンドの実装タイミングなのでうまいことできないか考える(待ち時間の削減) 4週目 引き続き Phase 3 の実装を進めます。この週は kiro の steering の整備をしました。steering は Claude Code でいうところの Claude.md のようなもので、チームで共有すべき永続化したい知識をまとめたドキュメントです。 あと一歩のところでゴール未達となりましたが「1週間でこれくらいまで進めることができる」というラインをまたひとつ得ることができました。 steering を整えることでセルフレビュー観点の質が向上した 開発側でも活用できるように引き続き育てていく 1ヶ月を振り返って やり方についてはポジティブ もちろん課題・伸びしろはいくつもあるのですが、それをよくしていけばなんだかよさそうである、というのがメンバーの総意です。 最初に狙っていた効果は? AI による観点が加わることで要件のヌケモレに気づきやすくなる これは実際救われたケースがあります。重複するのですが、UX観点ではやはり AI による視点 + デザイナーと一緒に作業することで効率よく体験を決定できました。 デキる人のレビュー観点・引き出しを盗める PR作成からマージOKのリードタイムが短くなる その場でのレビューも行うもののやはりガッツリレビューするのは落ち着いて次の日にやる、といったスタイルが今のところチームにあっていそうです。もちろん会話する中での気づきもあったりその場でコードやテストを Agent に直してもらったりすることもあるので、この点については中長期的に見ていくものになりそうです。 生産性あがったの? AI-DLC が狙うのは生産性を数倍に引き上げるというものですが、今回はその一部を取り入れてみた、というものになったのと今までのスタイルと単純な比較ができないので正直なところ不明です。ただ、チーム外から見た感覚だと「思ったより早く進んでいる」「ケイパビリティが上がったように見える」とのことで、ちゃんといい方向で効果が出ていそうです。おそらく以下のようなことだと推測しています。 明確に仕様駆動開発に寄せることで AI Agent の使い方を統一でき、効率があがった 今までだと、コンテキストとしてのコーディング規約などは整備していたものの、実際に実装するときは各自 Agent に要件のプロンプトを渡してどちらかというと Vibe Coding チックに進めていた ペア・モブでも捌くチケット量は変わっていなさそう(か、少し増えた) 1つめの要因のこともあり、2人で進めても今までの2人分くらいがきちんとできている = 人によるばらつきが減って安定して速度を出せていそう ペア・モブの時間だけで今までのチケットを捌けているので、それ以外の時間でやれることが増えた 逆にレビューDayはレビューと修正に集中できるので、時折やってくる他チームからのレビューなどにも応えやすくなった = ケイパビリティが上がったように見えていそう これから AI-DLC という考え方に出会い、一部ながらもチームに取り入れて1ヶ月取り組んでみました。実際どれくらい効果があったのか定量的には判断しがたいのですが、AI Agent を主体にした新しいスタイルを採用したことで安定して開発速度をキープできていそうであり、これはチームの状況が変わったとしても価値を提供し続けることができる基盤が整ったと言えそうです。 まだ課題・伸びしろはあるのでこれらを良くしていきたいですし、あるいはこれを突き詰めていけばひょっとすると「真の AI-DLC にしないとダメだ!」といってスクラムをやめる日が来るかもしれません。大事なのは振り返って改善することと、まだまだ AI に投資していくことだと思うので、自分たちのペースで取り組んでいきたいです。
はじめに 「カミナシ レポート」を開発しているかわりくです! 日本最大級のテックカンファレンス、Developers Summitに初参加してきました。 2日目のセッションの感想や持ち帰れそうなことをメモっております。 会場の雰囲気は、デデデデカイ!規模がデカい!今まで参加したどのカンファレンスよりも人の数と会場のキャパシティと、ブースの数が桁違い...!スタッフさんも多い...!ありがとうスタッフさん...! タダでサンドイッチもらってごめんなさい...!スタッフさんの分まで楽しみます! 興奮しながらの入場となりました。 (2026/2/19終了後、最速レポとして投稿されたものです。) beyond the code(デブサミ26のキャッチコピー) セッションレポート LLM利用率80%への道筋:ピクシブが実践した「People・Process・Technology」三位一体の変革とは? 登壇者: bash(ピクシブ) 川口 修平(GitLab) セッション詳細 ざっくりまとめ ピクシブさんが自社の開発者向けにLLMの利用を推進していく中で、組織にどんな変化が起きたか?という話。Technology、Process、Peopleの3つの要素が相互に変化を起こし続けるのがポイント。 (Technology)開発プロセスをツールチェーンで構築するのではなく、AIによる開発支援の強力なパイプラインを持つプラットフォーム(GitLab)を採用。ツールの選定やアップデート、チーム間の知見共有のコストをごっそり削減した。 (Technology)リリース前にセキュリティ診断を「一方通行で」やるんじゃなくて、GitLabの基盤で常にセキュリティ診断が回り続ける仕組み(DevSecOps)を作った。 (Process)開発プロセスに対する健康診断の仕組みでボトルネックを特定する。経営層もAI推進にコミットする。 (People)開発プロセスの全てを行うWhole Teamとしてチームを設計する。AIをチームメンバーと考えてチーム設計をする。 クローズアップ 「エンジニアリングとは、再現可能なプロセスを確立して継続することである。」ピクシブさんの社内Docに書かれた言葉らしい。AIによってエンジニアは不要になる・何を作るか?だけが重要になる。といった言説が増えたが、エンジニアの価値は再現性とそれを持続すること。そう考えると、AIが爆速でコードを書いてくれることと、エンジニアの本質的な価値はバッティングしないどころか、互いに補完しあう存在だと思った。 統合開発プラットフォーム(GitLab)に組織横断的なAIパイプラインを敷くことで、ツールや開発プロセスのサイロ化を防ぐ。一方で、コーディングツール(Claude CodeやCodexなど)やハードウェア(PC)といった手足の部分には一定の選択の自由度を残す。全てをトップダウンで強制しない、このバランス設計が良いと思った。 持ち帰り 再現性が高く、持続的な仕組みを作る。短期的な再現できない成果"だけ"を求めない。 自チームだと、QAフェーズをもっと開発チームが主体的に取り組むことができるはずなので、Whole Teamとして、QAフェーズにもオーナーシップを持って進められる仕組みをつくる。 意志を実装するアーキテクチャモダナイゼーション 登壇者: nwiizo セッション詳細資料 ざっくりまとめ 書籍からの引用と、登壇者の考えを交えながら、レガシーシステムをモダナイゼーションするためのhowとマインドセットの話。 実装はAIがこなせる時代になったが、組織や事業ドメインを織り込んだアーキテクチャ設計はできない。人が意志を持ってアーキテクチャを設計する。 AIが進化しても、組織の構造や人間の感情など、人の性質は変化しない。組織(人)と技術が相互作用しながら開発を進めるための技術がこれからも必要。 コンウェイの法則(システムを設計する組織は、そのコミュニケーション構造をそっくりまねた構造の設計を生み出してしまう)は避けられない。むしろ利用する。そのためにはドメイン境界をうまくモデリングすることが重要。 技術的負債は事業課題になる。変化に耐えられないシステムは競合に負ける。 クローズアップ ソシオテクニカル。俺たちはコーディングから逃げられたとしても人間からは逃げられない... モダナイゼーションは派手な技術選定より、地味な現状把握。説明しやすい嘘より、説明しにくい真実を話す。 技術負債は開発者体験やデリバリー速度が低下するだけじゃない...!プロダクトが...会社が終わる...! 持ち帰り 技術的負債について議論する時、技術的な課題に閉じずに、組織的な課題をセットで考えます。はい。やります。 人と向き合う時を増やす。 短期的で派手な成果よりも、地道な本質と向き合う。 2重リクエスト完全攻略HANDBOOK 登壇者: 三谷 昌平 セッション詳細資料 ざっくりまとめ フィンテック(2重リクエストが起きたら洒落にならない領域)の開発者が、Webサービスにおける2重リクエストの問題と対策をフロントからバックエンドまで段階的に解説してくれた。 まずフロントで発生頻度を下げる。submitボタンのdisabled化、PRGパターンでリロード対策。これはやらない理由がない。 バックエンドではDBのユニーク制約や状態遷移のバリデーションで、仮にリクエストが到達しても不整合なデータを作らせない。 さらに排他制御(悲観的ロック)やAPIレスポンスキャッシュで、並行リクエストやリトライにも対応する。 意図的なリクエストと事故を区別するために、idempotency-keyヘッダという選択肢もある。 クローズアップ 技術の概要だけでなく、実例が多く、ユースケースがイメージしやすかった。実例があると取り入れやすい。 2重リクエスト対策はめちゃくちゃ手段があって、それぞれのメリデリがあるので、パターンを知っておいて、適切に選択できる必要がある。人が意思を持って選ぶんだ! 持ち帰り 自分のプロダクトで2重リクエストが起きたら何が困るか、一度洗い出してみる。プロダクトの性質に合わせて優先度を整理して、なんらか解消してみます。やるゾス。 Claude Codeで実践するスペック駆動開発入門 登壇者:吉田 真吾(ジェネラティブエージェンツ/セクションナイン) セッション詳細 ざっくりまとめ 実践Claude Code入門の 著者によるスペック駆動開発(SDD)の話。仕様書を信頼できる唯一の情報源として、コード生成はAIが行うというアプローチとAI-DLCを比較しながら解説。 AIエージェントとは、目標に向けて環境と相互作用しながらタスクをこなす知能システム。ツールを実行し続けるループで目標を達成する。 SDD(Spec-Driven Development)では、spec(要件定義書・実装の設計・タスクリスト)を整備して、AIに実装させる。作業単位で永続的なドキュメントを作ってメンテしていく。 コーディングスタイルだけでなく、spec駆動のやり方自体もclaude.mdでポリシーとして定義しておく。 さらにAI-DLC(AI Development Life Cycle)という概念も紹介されていた。(ちょっと時間内だとSDDとAI-DLCの違いが理解しきれなかった...出直してきます) 簡素なSDDだと、laude.mdの肥大化によるコンテキスト消費、監査ログが残らない、ユーザーとのやり取りの詳細が不十分、といった課題もある。 クローズアップ 「Spec駆動開発は試してみるというフェーズを終えて、当たり前になりつつある」 持ち帰り AI-DLCを理解して、開発プロセスに取り入れます。 コードが物理世界を動かす興奮と責任 ~フィジカルAIの最前線でソフトウェアエンジニアは何と戦っているのか~ 登壇者:梅田 弾(ティアフォー)、山田 勲(T2) 司会:森崎 修司(名古屋大学) セッション詳細 ざっくりまとめ 自動運転業界がどのようにAI開発をしているのか、というパネルディスカッション。 自動運転といっても、センサーやハードだけじゃなく、ソフトウェアもクラウドもめちゃくちゃある。 ハードウェアの制約(計算資源、電源、センサーのタイムスタンプ同期...)がある中での最適化が必要。泥臭い。 あらゆるパーツが壊れても安全に動作する冗長なシステム設計が求められる。(人命かかりすぎてる。尊敬します。すごい。) 以前は技術導入すると後戻りできなかったが、今はシミュレーション環境が整ってきて、新しい技術もスピーディーに試して導入判断ができるようになった。 クローズアップ 「泥臭くやる」を登壇者が何度も強調していたのがカッコ良かった。最先端のAIも、データのタイムスタンプ合わせや電源の接続不良みたいな地味な問題と戦っている。華やかさの裏にある泥臭さ。 ソフトウェア開発を支えるためのソフトウェア。みたいなものをたくさん開発している。(データ不整合に気づく仕組みなど) 持ち帰り webシステムの開発でも「ソフトウェアのためのソフトウェア」(データ品質の自動検出、開発プロセスの自動化)という発想はもっと取り入れられるはず。開発を支える仕組みに投資する意識を持つ。 おわりに day2はサミットの熱量を最速で届けるレポートでしたが、day2,3の通しのレポートは日を改めて公開いたしますので、そちらもチェックお願いします! 弊チーム積極採用しております!来年はご一緒にデブサミいきましょう! herp.careers
コーポレートエンジニアの @sion_cojp です。 この記事では、Claude Code を使って SaaS セキュリティチェックを自動化した取り組みについて紹介します。 SaaSセキュリティチェックとは? 従業員が新しい SaaS を業務で利用したい場合、その SaaS がセキュリティ面で問題ないかを、コーポレートエンジニアが事前にチェックします。 チェック項目の一部を挙げると以下のような内容です。 公的認証資格を取得しているか(SOC など) MFA(多要素認証)/二段階認証に対応しているか 解約後にデータは完全に削除されるか 準拠法・管轄裁判所の確認 また近年では、SaaS に AI 機能が標準搭載されるケースも増えており、 学習への利用をオプトアウトできるか 入力データがモデル改善に使われるかどうか といったAI 特有の観点もチェック対象になっています。 これらを総合的に確認したうえで、業務利用可能かどうかをカミナシでは判断します。 Claude Code導入前のつらみ 従来は、以下のような情報を人力で収集・確認していました。 利用規約 プライバシーポリシー FAQ セキュリティ関連ページ その他チェック項目をジャッジするためのページ その結果、次のような課題がありました。 一定以上のリサーチスキルと知識が必要 項目をすべて埋めるまでに時間と労力を消耗 情報収集漏れにより、チェックが不十分になる可能性 多くの文書が英語で書かれている AI の進歩に伴い、新規 SaaS の登場や既存 SaaS の AI アドオンによる、チェック依頼の件数が急増 これらを改善するために、 Claude Code にリサーチとチェック項目・一次レポート生成を任せ、コーポレートエンジニアはレビューに専念する という運用に切り替えました。 Claude Codeとは? Claude Code は、Anthropic 社が開発した、ターミナル(CLI)上で動作する AI コーディングツールです。今回の用途で特に有用だった点は以下です。 Web Search 機能: 外部インターネットにアクセスし、公式ドキュメント等を自動検索 Custom Slash Command: Markdown でルールを定義すると、それに従って作業を実行できる Custom Slash Commandのサンプル 今回は /saas-check という Custom Slash Command を作成して利用しています。 .claude/commands/saas-check.md の一部は次のような内容です (※ 公開できない部分は ***** で伏せています。またチェック項目も省略しています)。 --- allowed-tools: Read, Glob, Grep, Bash, Write, WebSearch, WebFetch description: 利用規約、プライバシーポリシーなどのpdfファイルを解析し、要約と重要ポイントを抽出する --- # Your task 各SaaSやソフトウェアの利用規約、プライバシーポリシーなどのpdfファイルを解析し、以下の情報を抽出してください。 ファイル群は `saas-security-check/{SaaS or ソフトウェア名}/*.pdf` に格納されています。 ## 入力 ### ステップ1: 対象SaaSの選択 - `saas-security-check/` ディレクトリ内のフォルダ一覧を表示し、どのSaaS/ソフトウェアをチェックするか選択させる ### ステップ2: 必須ヒアリング項目(すべて確認すること) **重要: 以下の5項目すべてをユーザーに確認してください。AskUserQuestionツールを使用し、すべての項目を2回で呼び出しで質問してください。** 1. **情報種別**(必須・選択式) - 社外秘情報を含む - 公開情報のみ 2. **利用用途**(必須・自由入力) - ユーザーに直接入力させてください - 例: 「社内ミーティングの文字起こし」「顧客データの分析」など 3. **申請者のメールアドレス**(必須・自由入力) - ユーザーに直接入力させてください - 例: `example@kaminashi.jp` 4. **サービスURL**(必須・自由入力) - ユーザーに直接入力させてください - 例: `https://example.com` 5. **保存される情報**(任意・自由入力) - ユーザーに直接入力させてください - 未回答の場合は「未回答」と記録 ### ステップ3: 調査実行 - 指定されたSaaS/ソフトウェア内の全pdfを調査 - pdfファイル名を指定された場合: そのファイルのみ再調査 - 最後に、最終更新と命令履歴を更新 ## 出力先 `saas-security-check/{SaaS or ソフトウェア名}/result.md` マークダウンファイルを出力 ## 調査手順 1. **PDF読み込み**: ***** 2. **Web調査**: ***** 3. **会社・資本確認**: ***** 4. **出力生成**: ***** 5. **判断**: ***** ## 実装差分情報の活用 - **`saas-security-check/` ディレクトリ**: 各SaaSのチェック結果が格納されている - ファイル命名規則: `{SaaS or ソフトウェア名}.md` - 例: `Supabase.md`, `tl;dv.md` ### 注意事項 ***** ### 判断ガイドライン ***** ## 出力フォーマット ### マークの意味 | 記号 | 意味 | |-----|-------------------------| | ✅ | 機能があったり、規約に書いてある | | ⚠️️ | 機能があったり、規約に書いてあるが、注意が必要 | | 🚫️ | 機能がない、規約に書いてない | | ⬜️ | 未チェック | ### 理由のフォーマット - テーブルに記載する理由のフォーマットは下記となります \``` - {具体的な理由を記載してください} - > {ソースとなるファイル名} - > {ソースとなる文章} \``` - 理由内は、文章が読みやすいように `<br>` をうまく使ってフォーマットしてください。 `<br>` の理由はmarkdownレンダリング時に改行されるためです - 箇条書きの場合は、`・ ` で始めてください ### テンプレート \```markdown # {SaaS/ソフトウェア名}: セキュリティチェック結果 **最終更新**: {YYYY-MM-DD HH:MM} # 前提 - 本審査では、{SaaS/ソフトウェア名}の業務利用可否を審査する - カミナシ社内の利用者が本 SaaS にカミナシの下記情報を格納するユースケースを持つことを前提として、審査を実施する(セキュリティハンドブックの情報資産の分類を参照) - ⬜ 社外秘情報を含む - ⬜ 公開情報のみ # 申請情報 | 項目 | 内容 | |------|------| | 申請者 | {申請者のメールアドレス} | | サービスURL | {サービスのURL} | | 利用用途 | {利用用途} | | 保存される情報 | {保存される情報。任意項目のため、未回答の場合は「未回答」と記載} | # 審査結果 ## 結論 - {業務利用可 or 条件付きで利用可 or 業務利用不可} - {なぜ利用可能 or 不可能としたか記載する} ## 例外 - {「このデータは扱ってはいけない」など、例外があれば記載してください} ## 例外の背景 - {例外がある場合、理由を書いてください} ## 重要と思われる補記事項 - {AIのデータ取り扱いなど、リスクがありそうな点などがあれば記載してください} # 利用用途 - {このSaaS/ソフトウェアは一般的にどのような用途で利用するか記載してください} # マークの説明 - ✅: 機能があったり、規約に書いてある - ⚠️: 機能があったり、規約に書いてあるが、注意が必要 - 🚫: 機能がない、規約に書いてない - ⬜: 未チェック # 共通チェック項目 ### MUST | No | 項目 | マーク |理由 | |--- |--- |--- |--- | ### SHOULD | No | 項目 | マーク | 理由 | | --- | --- | --- | --- | # 特定の分野 ## AI ### MUST | No | 項目 | マーク | 理由 | | --- | --- | --- | --- | ### SHOULD | No | 項目 | マーク | 理由 | | --- | --- | --- | --- | ## 参照したpdfのパス一覧 {参照したpdfのパス一覧を箇条書きで書いてください。相対パスかつ、リンクを貼ってください} ## 参照したWebページ一覧 {Web調査で参照したURLを箇条書きで書いてください} ## 命令履歴 | 日時 | 命令内容 | |------|---------| | {YYYY-MM-DD HH:MM} | {初回調査} | \``` 実際の運用方法 関連ドキュメントの収集 Claude Code に十分なコンテキストを与えるためと、Web Searchの時間を減らすために、事前に以下のようなチェックに必要なドキュメントを収集し、ディレクトリに保存します。 利用規約 プライバシーポリシー セキュリティ関連ページ Custom Slash Commandを実行 Claude Code を起動し、Custom Slash Command を実行します。 今回は「カミナシ」の自社サービスをセキュリティチェックする例です。 claude /saas-check 実行後、AskUserQuestion によりチェック対象のSaaSなど「必須ヒアリング項目で定義したもの」のヒアリングをされるので、選択または入力します。 最後にSubmitを選ぶと、リサーチが開始されます。 Claude Codeのサマリ出力 調査が完了すると、Claude Code からサマリ結果が表示されます。 このサマリの良い点は以下です。 重要なポイントを簡潔にまとめてくれる 確認が必要な項目のレコメンド PDF 化が必要なサイトなど、実務目線のレコメンドを出してくれる <img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kaminashi-developer/20260206/20260206150724.png" wid
こんにちは、ソフトウェアエンジニアのいちび (@itiB_S144) です。 2026年1月28日 (水) に「カミナシ Tech Night #1 - AWS re:Invent 2025 Recap Special」を開催しました!カミナシのエンジニアチームとしては初めての外部の方も参加可能なイベントの開催で、準備段階からドキドキしていましたが、当日は大盛況で無事に終えることができました。 今回のテーマは AWS re:Invent 2025 の recap (復習) イベントということで、実際にカミナシから現地参加したエンジニア 5 名と豪華なゲストの方々がそれぞれの視点でセッションレポートをお届けしました。イベントには社外からも 13 名ほど参加いただき、会場は熱気に包まれていました! 本ブログではイベントのレポートをお伝えします。 カミナシ Tech Night とは カミナシのエンジニアを中心に業務や趣味で触れた技術についてわいわい発表するイベントを企画しています。今回は #1 としており、今後も継続して開催していく予定です。 イベント概要 日時: 2026年1月28日(水) 19:00〜21:00 (開場時間:18:45~) 会場: 株式会社カミナシオフィス 5F (神田駅徒歩5分) kaminashi.connpass.com 時間 内容 登壇者 18:45 - 開場・受付 19:00 - オープニング 19:05 - AWS 上での脅威と向き合うために 西川 彰 19:15 - みんなだいすき ALB、NLB の仕組みから最新機能まで総おさらい 古屋 啓介 19:25 - なんとなく知っていた Cache を学ぶ いちび 19:35 - 休憩 19:45 - re:Invent 2025を振り返る 【ゲスト】Noritaka Sekiyama 19:55 - カミナシの re:Invent 補助事情 Tori Hara 20:00 - やっぱり EC2 / JAM の勝ち方 [検索] 【ゲスト】星 北斗 20:10 - re:Invent Lv.500 セッションの歩き方 LLM の未知の未知を知る 高井 真人 20:20 - クロージング 20:25 - 懇親会 ゲスト紹介 re:Invent の現地でカミナシメンバーと交流のあった方々にもゲストとしてご登壇いただきました。 ご登壇くださりありがとうございました🙌 星 北斗 様 (@kani_b) 株式会社LayerX 執行役員 CISO 兼 バクラク事業部 VPoE 2024年1月に株式会社 LayerX に入社。コーポレートエンジニアリング室 室長、バクラク事業部 PlatformEngineering部 SRE グループマネージャーを務め、2025年10月より現職。クラウドとセキュリティと料理が好き。 Noritaka Sekiyama 様 (@moomindani) 通りすがりのData Professional。 セッションレポート AWS 上での脅威と向き合うために - カミナシ 西川 彰 (@nishikawaakira) トップバッターは西川さん。脅威モデリングについてのお話でした。 リスクマネジメントで用いられる脅威モデリングについてのアプローチ手法の変化について紹介してくださいました。 昨今は LLM の進歩もあり開発者がより脅威モデリングをしやすくなっています。LLM フレンドリーなドキュメントを書くことでより実りある脅威モデリングやリスク分析ができるものの、開発者がレビューするのも難しく LLM の扱い方を検討する必要が今後来そうと考えているとのことでした。 みんなだいすき ALB、NLB の仕組みから最新機能まで総おさらい - カミナシ 古屋 啓介 (@saramune) 古屋さんは「ALB/NLB の内部実装から最新機能まで」について学んだセッションを紹介してくださいました。ALB がどのように AWS の裏側で実現されているか、スケールするかなどを解説してくださいました。 ALB に新たに追加された新規の機能として「Target Optimizer」の紹介もあり、Target Optimizer を使うことで ALB がターゲットに流れる通信の同時実行数を制限することができるとのことです。 以下に古屋さんが Target Optimizer について書いたブログがあるので良ければ参考にしてみてください! dev.to なんとなく知っていたCacheを学ぶ - カミナシ いちび (@itiB_S144) 私自身も LT をさせていただきました。私の発表は「Cache me if you can, Valkey edition」という Valkey というメモリベースのデータベースについてのワークショップセッションをレポートしました。 自分のイメージとして持っているキャッシュは「画像を CloudFront でキャッシュすると読み込みが早い」「ログイン状態やカートを Redis に保存する」程度でしたがこのセッションでは書き込み時にキャッシュを作るWrite through や Write Behind などの実装パターンを知ることができました。 特にセッションの中で面白かったのは「生成 AI の結果をキャッシュする」という発想でした。 与えられたプロンプトをハッシュ化してキャッシュにヒットすれば返す、なければベクトル類似度検索で近いプロンプトの解答をキャッシュから返す。それでもヒットしなければようやく生成 AI に問い合わせるというキャッシュの活用テクを教わりました。 re:Invent を振り返る - ゲスト Noritaka Sekiyama 様 (@moomindani) ゲストの moomindani さんのセッションです。 re:Invent 2025 は AI Agent 祭りだったと一言でまとめつつ、そんなかでも moomindani さんの注目したアップデートをいくつかピックアップして紹介してくださいました。 Iceberg v3 のスペックが出てきて、いろんなサービスでサポートされた S3 Tables が進化して、テーブルレプリケーションなどができるようになりより便利に Iceberg テーブルを扱えるようになった SageMaker Unified Studio がかなり変わって、簡単に始められるようになった S3 Tables は 2024 の re:Invent で登場して以来、大幅にアップデートされていることをまとめて知れて非常にありがたかったです!現地で Iceberg のセッションにいくつか参加しましたが、どれも大盛況で盛り上がりを感じたので要注目のテクノロジーだと感じています。 カミナシの re:Invent 補助事情 - カミナシ 原トリ (@toricls) カミナシの CTO であるトリさんからはカミナシの re:Invent 参加補助制度について紹介していただきました。 カミナシでは「全額補助(普通の出張)、一部補助、業務時間扱い、有給を使って勝手に行く」など柔軟に会社から受けられる補助を選択でき、それぞれの補助ごとに出すべきアウトプットなどの成果が決められています。 柔軟に自分の re:Invent にかける思いに合わせて補助を決められるいい制度だなぁと感じています やっぱり EC2 / JAM の勝ち方 [検索] - ゲスト 星 北斗 様 (@kani_b) 2人目のゲストセッションでは LayerX の星さんに登壇していただきました。 星さんは世の中はサーバレスに染まっているがこだわりを持って EC2 のセッションを受けてきたとのこと。 昨今の EC2 はインスタンスの種類が豊富で様々な選択肢があります。適切に選ぶことでコスト効率がよくなることや Flex インスタンスなどの紹介をしていただきました。 また今回の re:Invent で AWS JAM(競技形式の技術演習)で優勝したとのことで星さん流、勝ち方のポイントを教えてくださり、さらに優勝景品を紹介していただけました。 教えていただいた勝ち方のポイントは私も勝ちたいのでここには書きません^^ re:Invent Lv.500 セッションの歩き方 LLM の未知の未知を知る - カミナシ 高井 真人 (@manaty0226) 最後はカミナシエンジニアのまなてぃさんの発表です。 re:Invent や re:Inforce, AWS Summit ではセッションごとに難易度別に Level がつけられています。Level 500 のセッションに参加しどんな紹介があったか、どんな雰囲気だったかを紹介していただけました。 詳細についてはブログでも記載してくださっているのでそちらを御覧ください! kaminashi-developer.hatenablog.jp 懇親会の様子 発表が終わったあとは懇親会として re:Invent の話しで盛り上がりつつ、技術の話しで盛り上がりました。 会場の案内をするごーとん(弊社マスコット) まとめ カミナシ Tech Night #1 大成功だったと思います!ご参加いただけた皆様、ありがとうございます。 初開催で緊張しましたが、登壇者の皆さんの熱い発表と参加者の皆さんの温かい雰囲気のおかげで、とても良いイベントになりました。re:Invent に行った人も行けなかった人も、最新のAWSの動向をキャッチアップできる良い機会になったのではないでしょうか。 また Tech Night を開催したいと思いますのでぜひお楽しみに! 来年の re:Invent 2026 に向けて、すでにカミナシメンバーは何人か航空券を予約済みとのこと。Slack には「re:Invent 2026」チャンネルが爆誕しています。 re:Invent 2026 でお会いしましょう!
こんにちは。青春ミュージックと言えば Judy and Mary の @daipresents です。好きな曲は『LOVER SOUL』です。 「カミナシ 教育」の開発チーム(サービスチームと呼んでいます)は、月に1回だけ東京神田オフィスに集まってオフサイトを行っています。そこでは、その月のふりかえりだけでなく、直接話したいことや、話したほうが意思決定がしやすいことをアジェンダにして、対話やディスカッションの時間を確保しています。 1月度のオフサイトでは、エンジニアと非エンジニア(プロダクトマネージャ、プロダクトデザイナー、リサーチャー)のペアを作り、「AIを使って非エンジニアに機能開発をやってもらおう」というイベントを開催しました。 会場の風景。神田オフィスの広場で開催しました。 このイベントの狙いは以下です。 プロダクトマネージャやプロダクトデザイナーが、プロダクトのちょっと気になった部分をシュッとPR作成&修正反映できるようにならないか実験したい AIを使った開発の可能性をサービスチーム全体で理解していきたい、当たり前にAI活用できる文化にしていきたい 今後開発するであろう機能をテーマにすることで、その解像度をチームで高めたい また、イベントのタイムテーブルは以下のようになりました。 14:20 ~ 14:25: ルール説明・チーム分け 14:25 ~ 15:20: 開発 15:20 ~ 15:35: 作ったものの紹介 15:35 ~ 15:40 : 投票・表彰 ルールは以下のようになっています。 エンジニアはコードを書かないこと プロンプトはプロダクトマネージャ、プロダクトデザイナーが考えること エンジニアは、ペアからの質問であれば何でも受け付けてOK 今回は時間が限られているので、エンジニアがプロンプト考えるの基本は禁止ですが、エラーやエディタ(Cloude Codeなど)の使い方で困っている場合は、助け舟を出す形にしています。 AIを使って理解度テスト機能を最強にしてみよう! 今回のテーマは「理解度テストの改善」です。理解度テストは、最近リリースされた機能で、今後、ユーザの声を聞きながら改善を進めようとしています。 ペアごとにアイデアを聞いてまわると、同じ機能であっても改善のアプローチはさまざまです。 テスト結果を元に弱点克服をうながせないかと考えたチーム テスト受講者の傾向などを使って次のテストに活かせないか考えたチーム テストを作る面倒くささを解決できないかと考えたチーム どのチームにも共通するのは、AIに命令した後に雑談する風景。ガリガリコーディングする形から、ぽりぽりお菓子を食べながら雑談が増える形になっていくのかもしれませんね。 AIの結果を待つ間は雑談タイム。各ペア質問したりアイデアを考えたりしていました。 おわりに 優勝は「テストを作る面倒くささを解決できないかと考えたチーム」でした。 今回のイベントの感想を集めてみると・・・ 普通に欲しいものを作れた! すぐにリリースできそう! Figma Makeより賢い! 私も使いたい! へー、こんなふうに開発しているんですねー こんなにちゃんとできるんだ! とポジティブな結果ばかりでした。 成果はプレゼンで発表。数時間のイベントですが動くモノをみると「おおー!」と歓声が上がります。 AIの活用という観点では・・・ 命令を3行だけ考えて、のこりの要件や仕様をAIに書かせるチームが多く、「これってエンジニアとデザイナで協業してやれるのでは?」という発見があった やることが大きすぎて、途中の軌道修正が間に合わず、時間切れになってしまった 「APIのエンドポイントはこれでいいですか?」のように、非エンジニアには難しい質問も届くため「わからん!」ってなるケースがあった と、AIの使い方や勘所の理解も深まったようです。 個人的に印象的だったのは、ほとんどのチームがAIに命令後に祈っている点。AIの結果は不確定要素が多く、各ペア命令を工夫して改善を試みますが、それでも祈りの時間は増えるようです。 今回イベントをやってみて好評だったので、来月いくつかのVoC(顧客の声)対応をこれでやれないか? という新しい企画にもつながりました。
コーポレートエンジニアの @sion_cojp です。 この記事では、ISMS関連の文書を Google Docs から GitHub に移行した理由と運用方法 について紹介します。 ISMSで管理する文書とは? ISMS(Information Security Management System)は、情報セキュリティを組織として継続的に管理・運用するための仕組みです。 ISO/IEC 27001(JIS Q 27001)は、そのISMSをどのように構築・運用・改善していくべきかを定めた規格になります。 例えば、規格では「付属書A 9.4.5 プログラムソースコードへのアクセス制御」が要求事項として定められており、これに対応する形で社内文書には「ソースコードのアクセスおよび管理方法」を記載します。 ISMS というと「文書が多い」「運用が大変」というイメージを持たれがちですが、本質は 文書を作ることではなく、運用を属人化させず、継続的に改善できる状態を作ることにあります。 私たちの組織では、その実現手段としてISMS 関連文書をGitHubに移行するという判断をしました。 なぜGitHubに移行したのか? 1. 議論の履歴が残らない問題 Google Docsでは「提案モード」でコメントしながら議論を進めていました。 しかし、提案を承認するとコメントが非表示になり、「いつ、どんな議論を経て修正されたのか」が追えなくなっていました。 さらにコメントの表示上限もあり、過去の議論を遡れない問題があります。 GitHubではPull Requestで修正を提案できるため、議論内容と変更履歴を同時に可視化できます。 2. 誰が起案・レビューしたのか分からない Google Docsの提案を承認すると、「誰が提案したか」「誰がレビューしたか」が履歴に残りません。 コーポレートガバナンスの観点からもこれは望ましくありません。 GitHubではPull Requestにより、起案者・レビュアー・承認者が明確に記録されます。 3. 改善提案が残しづらい Google DocsにはIssue機能がないため、「こう改善したら良いかも」という軽い提案を残す場所がありません。 結果として「それ前に議論したんだよね」ということがよく起きていました。 GitHubではIssueを使って、改善提案や議論の履歴を蓄積できます。 4. 文書のバージョン管理が難しい ISMS文書では「どんな改訂を行ったか」を明確に記録する必要があります。 しかしGoogle Docsでは、修正のたびにバージョン名を更新し、修正ハイライトを追記する必要がありました。 これは特に大規模修正時に手間がかかります。 GitHubならPull Requestとリリースタグで管理できるため、コピーを作る必要もなく、コミットログから修正内容を追跡可能です。 5. 会社の主要ドキュメントツールがNotion カミナシの主要なドキュメント管理ツールはNotionです。 Google DocsはNotion検索の対象外のため、情報が分断されてしまいます。 そこで、GitHubで管理したMarkdownをNotionに自動同期する仕組みを作りました。 これにより、統一されたドキュメント検索が可能になります。 GitHub移行と運用の流れ 既存文書をMarkdown化してpush Google DocsをMarkdownとしてエクスポートしましたが、一部が独自形式になっていたため、手作業で標準Markdownに整形。 整えた上で、GitHubにすべてpushしました。 GitHub ActionsでNotionへ自動同期 「GitHubのアカウントを持っていない社員も閲覧できるようにしたい」 「Notion上で全社的に検索できるようにしたい」 という要望を満たすため、MarkdownをNotionへ自動転記するOSSを開発しました。 github.com GitHub Actionsでは .github/workflows/deploy_ms_manual.yml に次のような設定を行っています。 (mainブランチにマージされたら、MSマニュアルをNotionへ同期する例) name: Update Notion page from Markdown on: push: branches: [ main ] paths: - 'docs/MSマニュアル.md' workflow_dispatch: permissions: contents: read jobs: DeleteAllBlocksMSManual: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install go-markdown-to-notion run: | curl -L https://github.com/sioncojp/go-markdown-to-notion/releases/download/v1.0.2/go-markdown-to-notion_Linux_x86_64.tar.gz -o go-markdown-to-notion.tar.gz tar -xzf go-markdown-to-notion.tar.gz chmod +x go-markdown-to-notion mkdir -p $HOME/.local/bin mv go-markdown-to-notion $HOME/.local/bin/ echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Delete All block in Notion page id: delete_blocks env: NOTION_API_TOKEN: ${{ secrets.NOTION_API_TOKEN }} # Notionのインテグレーショントークン NOTION_PAGE_ID: "xxxxxxxxxxx" # 上書き対象ブロックID run: go-markdown-to-notion delete-all-blocks --notion-page-or-block-id ${NOTION_PAGE_ID} UploadMSManual: needs: [DeleteAllBlocksMSManual] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install go-markdown-to-notion run: | curl -L https://github.com/sioncojp/go-markdown-to-notion/releases/download/v1.0.2/go-markdown-to-notion_Linux_x86_64.tar.gz -o go-markdown-to-notion.tar.gz tar -xzf go-markdown-to-notion.tar.gz chmod +x go-markdown-to-notion mkdir -p $HOME/.local/bin mv go-markdown-to-notion $HOME/.local/bin/ echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Insert Markdown to Notion page env: NOTION_API_TOKEN: ${{ secrets.NOTION_API_TOKEN }} # Notionのインテグレーショントークン NOTION_PAGE_ID: "xxxxxxxxxxx" # 上書き対象ブロックID MD_FILE: docs/MSマニュアル.md # 監視対象のMarkdownファイル run: go-markdown-to-notion upload --notion-page-or-block-id ${NOTION_PAGE_ID} --source-md-filepath ${MD_FILE} --is-add-table-of-contents Notionのメリットは「リッチな見た目に表示できること」なので、 h1, h2…タグは色をつけたり、--is-add-table-of-contents` オプションをつけると先頭に目次をつけれるなど、リッチ化できる実装もしてます。 Issueによる議論とタスク管理 やるべきことや議論したいことはすべてIssueで管理。 非同期に議論を進められるうえ、ISMS更新タスクをIssueテンプレート化することで、「何を」「いつまでに」行うかが明確になりました。 移行したことのその他のメリット 法令改訂時の差分が明確に見える 法令管理台帳という、会社を運営するために関係する法令をまとめた台帳があります。 e-Gov(https://laws.e-gov.go.jp/)から法令を取得し、GitHubで管理。Pull Requestで更新内容を差分表示できるようにしました。 変更箇所だけを確認できるので、どの条文が変わったか一目で把握できる<
「カミナシ レポート」の開発・運用をしている furuya です。今日は「サービスの健康診断」のお話です。 週次の健康状態チェック:運用定例 「カミナシ レポート」の開発チームでは週に1度、運用定例というものを開催しています。以前のブログ記事 より以下抜粋します。 また、そのダッシュボードを毎週見る会を始めました。一緒に運用にまつわるあれこれをまとめて確認する会ということで、運用定例(Service Review)と呼んでいます。ここでは他にインフラコストの確認やAWSなど利用する外部サービスからの通知の確認、障害・ヒヤリハットの周知などを行っています。 Datadog 上に構築したダッシュボードを1週間スパンで眺め、メトリクスが跳ねているところがないか、徐々に遅くなっているエンドポイントはないか、といった観点でチェックを行います。アラートにまでは派生しなかったものの異常値が見られたり、詳細な調査が必要になった場合は対応チケットを切り次スプリントにて調査を行います。 年に1回の人間ドック 今回はこちらのお話がメインです。週次スパンだと長期のトレンドや、今後どうなっていきそうなのか、といったところが見えないため年単位で見ていく機会を設けました。 目的 インフラメトリクスの現状を見て、これからの予測を考えてみる 今のインフラの状態を理解し、このままほっとくと1年後にはどうなっているのか、のイメージをチーム内で揃える 転じて直近立ちはだかりそうな壁を知る 大きく以下3つの観点で見ていきます。 観点1. パフォーマンス まずは Datadog ダッシュボードを1年スパンにして眺めてみます。普段は週次、長くても月次スパンでしか見ないので長期間の傾向を確認します。 とあるエンドポイントのレイテンシ たとえばこれはとあるエンドポイントのレイテンシのグラフですが、これを見ながら「この時期はxxxでちょっと悪化していた」「とある施策によりパフォーマンスがあがった」など1年を振り返りつつ、「今後の傾向はどうなりそうか?」「徐々に遅くなっている傾向があるので注視が必要」など未来へ向けての認識を揃えました。 観点2. キャパシティ こちらもメトリクスを1年スパンで眺めます。対象は主にコンピューティングリソースで「カミナシ レポート」はクラウドインフラストラクチャとして AWS 、コンピューティングは ECS(Fargate)、データベースは Aurora を利用しています。これらの CPU、メモリを見ていきます。 ここでも現状と1年間の傾向を見つつ「まだ余裕がありそうだから折を見てリソースを調整してみよう」「直近しばらくはインスタンスサイズを変えなくても大丈夫そう」といった認識合わせをしました。近く Reserved Instance や Savings Plans の購入を控えているので、変更があれば対処できるくらいの余裕をもって今回の人間ドックを実施しているのもミソです。 予測のために使った機能として、Datadog の Forecast があります。以下は単純な例(Aurora のストレージ利用量)ですが、ここからストレージ使用量を予測してそれによる利用料金を具体的な数値として算出できます。 Datadog Forecast 観点3. コスト みんなだいすきコストも見ていきます。ここでは特に、全体からみて割合が高いものや変動率が高いものに着目し、1年前と比べて / ビジネス指標的に今の金額は妥当かどうか確認します。 コスト削減の施策効果についても確認しました。直近でいうと、S3 の特定のバケット/オブジェクトに対して Glacier Instant Rerieval(年に数回しかアクセスしないが、必要なときには通常の S3 オブジェクトと同等の速度で取り出せる、長期保存向け低コストストレージクラス) への変更の設定を入れたのでその効果を見つつ、変更までの期間をチューニングするか否かの議論を行いました。ストレージタイプの変更自体にもコストがかかるためトータルの削減額としては大きくありませんでしたが、現時点のコストを少しでも下げつつ今後のコストの伸びを抑えるという意味では複利で効いてくる施策です。もともと変更の決定を行ったときは、ストレージクラスの変更にかかる総コストは2, 3ヶ月でペイできるという試算で始めたのですが、思ったよりも古いオブジェクトへのアクセスがあったためペイできるまでの期間が多少伸びそうです。コスト削減自体はできているので変更期間はそのままにして、また半年後くらいに効果を測定しようと考えています。 ポイント 今回の人間ドックで得られたことは2つあります。 1つめは、幸いなことに今回の人間ドックでは直近1年でぶつかりそうな大きなインフラ課題は見つからなかったことです。たとえばキャパシティ面だと DB のストレージが1年以内に枯渇するかもしれない、といったことがわかった場合は早めに対処することが必要です。そのため少なくとも「1年後どうなっていそうか?」という観点で過去の傾向から未来を予測することがダイジだと思っています。 2つめは、去年1年間のうち様々な場面で実装した施策が効いておりパフォーマンスが全体として1年前より改善していることがわかったことです。個々の施策による確認は週次の運用定例で確認していますが、やはり1年というスパンでみたときに「以前よりよくなっている」という実感を得られることは開発チームのモチベーションになります。 まとめ 自分たちが運用しているサービスの健康を保つためには普段のモニタリングに加えて、長期の目線で振り返りとこれから襲ってくるであろう事態に備える必要があります。我々人間の病気もそうですが、早期発見が大事です。人もサービスも定期的な健康診断をしていきましょう! カミナシでは開発はもちろん、このような活動を通してサービスに向き合いたいエンジニアの方を募集しています!また、宣伝になりますが1/28(水)に AWS re:Invent の re:Cap を開催します!豪華ゲストに登壇いただいたり、現地参加メンバーならではのトピックが聞けたりしますので是非ご参加ください。 kaminashi.connpass.com
新年明けましておめでとうございます、セキュリティエンジニアリングの西川です。年末から胃を壊しており、健康の大切さを改めて感じています。何事も当たり前ではないということですね。 さてさて、そんな話も関係しつつ AWS App Runner 使っていますか?カミナシでも使っています。お手軽にデプロイできるし、スケールも自動でしてくれるのでとても使い勝手が良いですよね。 という App Runner 全人類使うべきだよ!という話ではなく、App Runner を使う上での注意点をお伝えしたいと思います。 App Runner の特徴 本題に入る前に App Runner のことを知らない方のために App Runner の特徴をお伝えしたいと思います。App Runner は ECS 同様にコンテナをデプロイすることができます。さらに ECS はインフラの設定などを自分でする必要がありますが、App Runner はそこも自動でしてくれます。デプロイしたら即使えるというのが App Runner の特徴で、開発者が意識しなくてはいけないことは大幅に減るのが強みです。 App Runner を利用する上での注意点 ここで一つ問わせてください。 App Runner をお使いのみなさんは、もし、今ここで App Runner のアプリケーションに脆弱性があったとして攻撃を受けた場合、ログを見て追うことができますか? ぜひこの機会に今出力しているログでセキュリティインシデントを調査することが可能なのか確認してみてください。 現時点では App Runner はインフラを気にしなくて良いという強みが故に注意しなければならないことがあります。それは Amazon CloudFront や ALB のアクセスログ相当のものは自動では取得できないということです。アプリケーションで出力したログ以外は何も出力されません。もちろんエラーケースなどの場合、アプリケーションにてログを出力する実装は一般的にするでしょうが、正常系で必要なログを出力するようにしていますか?もしそうなっていないなら考えるチャンスです。 セキュリティインシデントの発生時にはアプリケーションのログとして通常 Web アクセスログとして出力されうるものを出力する必要があります。そのことは重要な事実でありますが、残念ながらマニュアルには記載されていません。 https://docs.aws.amazon.com/ja_jp/apprunner/latest/dg/security-monitoring.html ここに大きな落とし穴があります。ログが取られていなければもしアプリケーションに脆弱性があった場合、どこからどんな攻撃を受けたのかわからなくなってしまう可能性があります。 それでは、CloudFront を前段に挟めば AWS WAF も入れられるし、セキュリティも向上してログも CloudFront のログを見れば何かあった時に十分でしょ?と考えたあなた、半分正解です。 半分というのは何もしなければ App Runner 自体へのオリジンアクセスは可能な状態であり、CloudFront を経由しないリクエストを防ぐことができません。CloudFront や WAF に頼ってしまうと、オリジンに直接リクエストが送られた場合に攻撃を追うことができないという状況が発生します。では、どうしたら良いのかというと二つ方法があります。 アプリケーションのログとして出力する CloudFront のカスタムヘッダにシークレットな値を入れてそれをアプリケーション内で検証する という2点です。セキュリティグループで CloudFront の IPアドレス(マネージドプレフィックスリスト)を許可するという設定もありますが、それは攻撃者が用意した CloudFront でバイパスされてしまうということは過去の別の記事でも言及しています。 https://kaminashi-developer.hatenablog.jp/entry/2024/12/24/160000 アクセスログは CloudFront や ALB はたまた Apache や Nginx などを利用していた世代からすると当たり前に取得できていると思っていましたが、App Runner のようなサービスでは今までの当たり前は当たり前ではないということです。というところで健康の話の伏線を回収したいと思います。 セキュリティ担当者として App Runner を使っているかどうかに関わらずログ出力方針などを作成して、その通りに必要なログが出力されているかを監査する仕組みは必要なことです。もしそういった方針やそもそも監査できる状態にないということであれば、この機会に整備計画を立ててみてください。 何かあった時に「追えませんでした」、「おそらくこうでした」ではセキュリティ担当者として説明責任が果たせているとは言えません。これは私自身に言っている言葉でもあります。 開発者の方々には App Runner に限らず便利なサービスを使用して気持ちよく開発および運用を行なっていただき、我々セキュリティ担当者が説明責任を果たせる状態を目指したいですね。 というところで、このブログをしたためさせていただきました。 そのためにも使用しているサービスの特性もきちんと把握して利用しなければならないと感じます。難しいんですけどね。 最後に このブログが誰かの気づきや誰かの行動のきっかけになっていたら幸いです。 決して App Runner を咎めるために書いた記事ではないことをご承知おきください。App Runner は非常に優れたものです。きっとそこのあなたの役に立っていることでしょう。カミナシでも役に立っています。
新年あけましておめでとうございます!自分,なかなかテキストによるアウトプットが苦手なのですが,今年は頑張って克服していきたい所存です! 改めましてこんにちは.株式会社カミナシで EM をしております Keeth(@kuwahara_jsri)こと桑原です.昨年もカミナシ社のエンジニアリング本部で2日間,開発合宿を開催しましたので,今回はそのレポートとなります! 開催概要 日程:10月15日 12:30 〜 10月16日 15:00 場所:熱海 参加者:エンジニア23名 今回の開発合宿の目的は, AI Vibe Coding実践を通して,各エンジニアの開発生産性向上 エンジニアブログ執筆時間を設け本合宿含めたブログ執筆機会を与えることで,リーチしていなかった人材も含めた採用プレゼンス向上 としました. カミナシはマルチプロダクト戦略でサービス展開をしており,日々チームもプロダクトも変化していくことが求められます.そのためエンジニアの数も,デリバリー速度も,上げていく必要があります.そのためには,やはり AI Coding の活用が不可欠.そこで今回の合宿でガッツリ AI Vibe Coding に挑戦し,学んだノウハウを持って普段の業務での生産性を向上することをこの合宿で目指しました.また,生成 AI でアプリ開発ってどんなもんじゃい,を体験してみたかったのもあり,とても良い機会でした. 今回の会場 色々検討した結果,昨年と同じく宿泊施設として ハートピア熱海|熱海伊豆山温泉リゾートホテル さんに,合宿会場としては大会議室「秀峰」をお借りしました.料金・環境共に申し分なく,かつとても快適に過ごせた記憶も残っていることから,決定しました.ちなみに熱海駅から無料のシャトルバスも出ています.詳しくは 団体のお客様へ ページをご参照ください. ハートピア熱海さん今回も大変にお世話になりました.ありがとうございました! タイムテーブルとコンテンツ 時間 項目 1日目 11:30 会場 11:30 - 12:30 昼食など 12:30 - 13:00 チェックイン 13:00 - 17:00 AI Vibe Coding 自由時間 18:00 夕食 + 乾杯 20:00〜 夜のLT会, 終わり次第自由解散 2日目 7:00 - 8:30 朝食(バイキング) 自由時間 10:00 - 12:00 ブログ執筆タイム 12:00 昼食 13:00 - 14:00 成果発表 + 総評 14:00 - 15:00 片付け,チェックアウト,写真撮影,解散 準備 基本的に議事録も,旅のしおりも Notion にまとめています.余談ですが,旅のしおりを作るのは楽しいですね. また「やることのチェックリストがあるといいよね〜」という昨年の反省からチェックリストを作りましたが,やっぱりこれは大事ですね.準備の漏れがないかの確認に大いに役立ちました.参考に作ったリストを一部抜粋して載せておきます. Day1. 企画:AI Vibe Coding メインとなる Vibe Coding です.やったことはシンプルで,作るものと要件を提示し,チームで自由に設計・開発,時間内でできたものを発表していただきました.レギュレーションはこちらです. 人間が手を動かすことはNG 処理の途中で強制的に止める ただし,AI 用に設計書などのドキュメントを手動で作成は許容 チーム制 メンバーはバラバラ 4人 × 5 or 6チーム(参加者人数でよしなに調整) テーマは全チーム同じ 使用する生成 AI およびモデルに指定はない とはいえ,多くのチームが Claude Code の Sonnet 4.5 だったのではないか(未確認) 全プロンプトは後ほど公開する 総評として CTO が最優秀チームを独断で決定する テーマもチームごとに考えて,作っていただく案も検討したのですが「最後に評価をするのが難しくなること」「またチームごとの差分が見れるのが面白そう」ということで,全チーム同じテーマに設定しました.今回のテーマがこちら. vibe coding のルール ちなみに今回はより実践的(?)にしてみたく,残り1時間のタイミングでメテオフォールを投下しましたw メテオフォール 今回作るものの仕様については GitHub でリポジトリを1つ生やし,そこに requirements.md を作って記述.各チーム同じリポジトリに,チームごとのブランチを切って,そこにコミット・プッシュして頂く形にしました.これによりお互いのコードを見ることができたり,どういうドキュメントを作っていたのかなども見れます(そのチームがプッシュしていれば)ので,このあたりも学びになれば良いなと. さて,この企画をやるに当たって,運営側としてとても頭を悩ませたのが以下の2つ. テーマをどうするか(何を作るか) どこまで情報を渡すか ある意味で,運営の定例会では1番ここに頭と時間を使ったんじゃないかなと思います.単に必須要件のみを書くと,解釈が無限に広がりますし,当日の質問が大量に来る可能性がある,かと言って細かく書きすぎるとみんな同じようなものが作られる(多少の UI の違いくらい)になってしまいかねなく,ここのバランスが悩ましかったです. 以下,実際の開発中の写真たちです.チームごとに机の配置を変えたり,途中から模造紙と付箋で可視化しながら進めたりと,進め方にも特色が現れていました. 夕食,夜のLT会 お待ちかねの夕食タイム!宴会場を一部屋貸し切ってみんなでワイワイ飲みながら喋りつつ,美味しくいただきました.AI にコードを書かせたはずなのにしっかりお腹が空いたので,頭を使えている証拠でもありますね. お腹を満たしたところで個人的に楽しみにしていた 夜の LT 会 です.事前に社内で LT 登壇を公募し,今回は合計8本の LT が行われました.話はポッドキャストの状況,エンジニアがプロダクトディスカバリに参加する意味,AWS環境構築 RTA,野球を例にしたトランスフォーマーアーキテクチャ,中小企業診断士二次試験の勉強,3Dプリンタの布教,Bet Iceberg…などなど,多岐にわたる話が飛び交っていました.皆さんいろんなことに興味があるんだなと. ちなみに LT 会後に,別の店に飲みに行くメンバーもいました(笑) Day2. ポケカ部 カミナシには「ポケカ部」というポケモンカードを嗜む有志の団体が存在しておりまして,昨年同様に部員たちは各々のデッキを持ち寄り,朝からバトルに励んでおりました.ポケカ部の朝は早いようです. ポケカ部朝練 ブログ執筆タイム 昨年同様,各自が書こうと思っていたブログや note をこの機会に書いてしまおう!という企画です.昨年も好評な企画だった模様で,いつもと違う景色や場所だと筆が乗る人が多かったようだったので,今回もしっかり時間を確保しました.ちなみにブログではなく,登壇資料を作る方もいらっしゃいました.企画名は「ブログ執筆」ですが,アウトプットに繋がったのでOKです. 今回の会議室は窓が大きく景色も良かったのですが,展望ラウンジで書いているメンバーもちらほらいらっしゃいました.この場で1本ブログを書ききったメンバーもいたりと,各自だいぶ筆が進んだようで何よりです. 成果発表・総評 お昼ご飯も食べ,各チームが前日に作り上げたものを発表,レビューし合いました!各チーム色んな意味での色が出てて,思った以上に差分があり,また学びもありでとても有意義な時間になったなと感じます.個人的には,spec-workflow-mcp という,構造化された仕様駆動開発のための MCP サーバーを知れたのが収穫でした.こちらはリアルタイムダッシュボード機能もあり,ここまで便利な時代になってたのか!と驚きました. 優勝したチームは,今回作った承認フローの中で登場するデータやエンドユーザーの体験から見直し,承認するとはなにか?ということに時間をフルに使い,残り1〜2時間くらいで一気に作り上げていったようで,これにも驚かされました.後から他のチームにも聞きましたが,実際のコーディングは意外と2時間行かないくらいのチームが多かったようで,やはり作る前段階でどれだけ練られているのか,がポイントなんだなと改めて感じさせられました. <span it
カミナシエンジニアの osuzu です。 私は職能柄、Webフロントエンド技術の選定に関わる機会が多く、これまで React Server Component や Next.js に関する発信なども過去にしていました。 そうした事情から2025年12月の React や Next.jsのセキュリティ問題 に対し心痛めています。 私は現在もプロダクションでNext.jsを運用していますが、選定した事を後悔しているかというとそう単純な話でもありません。 そこであらためて、Next.jsをプロダクションで採用するポイント、何を意識した構成にしているか記載します。 今回の記事では以降、ReactはRSC(React Server Component)を含むものとし、Next.jsは記載のない限りApp Routerを指した話となります。 BFFとして使う まず私はプロダクション運用するプロダクトに対して、フルスタックフレームワークやバックエンドを兼ねる形でNext.jsを採用する事はありません。必ずBFFとして使います。ここであらためてBFFとして使う事について整理します。 今回は主にSaaSのような、認証認可が必要かつ、マルチプロダクト展開や他SaaS連携などで複数のAPIに依存しやすい構成を例に説明します。 BFFの構成について 私が推奨する「Next.jsをBFFとして使う」構成は、物理的にも論理的にもバックエンドAPIと明確に分離された状態を指します。 具体的には、Next.jsはデータベースへの接続情報を一切持たず、ビジネスロジックも記述しません。あくまで「UIのためのデータ整形」「APIアグリゲーション」「API認可プロキシ」に徹します。 「ブラウザの延長」と捉え、機密情報を保持しない Next.jsサーバー(Node.js/Edge Runtime)を「信頼できるサーバー(Trusted)」とは見なさず、「ブラウザが少し権限を持った延長線上の環境(Untrusted)」として扱います。 具体的には、「ブラウザのDevToolsで見えて困るロジックや変数は、Next.jsのサーバーサイドにも置かないという極端な意識で設計します。 そのため、DBのパスワードやAWSのシークレットキーを環境変数として渡すことはありません。これにより、万が一RSCの境界設定ミスでコードが流出したり、インジェクション攻撃を受けたとしても、攻撃者が手に入れられるのは「画面構築のためのロジック」だけであり、システム全体を掌握されるリスクを構造的に低減します。 BFFのインフラ権限を最小化し、トークンの検証責務をバックエンドAPIが持つこと BFFが実行されるコンテナや関数自体の権限(IAM Role等)は最小限に絞ります。 例えばAWS上で運用する場合、Next.jsの実行ロールに対して dynamodb:FullAccess は与えずに、 dynamodb:GetItem や dynamodb:PutItem など権限を最小限にします。 また、認証認可のフローにおいてもNext.jsはあくまでトークンの「運搬役」に徹します。例えばNext.js上でJWTを発行するといった構成は取りません。 カミナシが運用するプロダクトでは、バックエンドAPIはOAuthのクライアントでトークンイントロスペクションのリクエストするだけという構成を取っています。Next.jsはCookieからトークンを取り出しAPIへ転送するだけであり、バックエンドAPI側がそのトークンを検証するリクエスト結果を元に認可します。この徹底した責務の分離により、BFF層がセキュリティに対して権限やリスク集中することを防いでいます。 ※ すべてのバックエンドAPIが同じセッションストアを共有する構成などでは、BFFでトークンを取得せずcookieをproxyするだけで扱って良いかもしれません。 [追記] セッションストアへのアクセス権限を小さくする他にも以下のような対策があります。いずれもNext.jsなどBFF固有のものではありません。 コンテナをイミュータブルにする CookieにHttpOnly, Secure, SameSite属性を設定する Content Security Policyを設定する サーバーのアウトバウンド通信を制限する WAFを導入する ただし、いずれも攻撃を難しくしたり被害範囲の縮小に役立つことはありますが、RCE(遠隔コード実行)が存在する場合にセッション乗っ取りリスクをゼロにするものではありません。 そこまでしてNext.jsを使う必要があるか BFFは必要なのか ここで議論になるのが、「そこまで制約を課すならSPA + APIで良いのではないか?」という点です。SaaSやtoBのプロダクトではSPA(Client Side Rendering)にすべきという意見もよく目にします。 例えば「SSRからSPAにしたとしてSaaSのようなプロダクトではボトルネックは移動しない」「静的なファイルとして配信した方がインフラコストが安い、またバンドルしたファイルもユーザーのブラウザでキャッシュ可能」など… 私は上記も賛同しますし、前職でそう構築した経験もあります。 とはいえ残念ながら何事にもトレードオフがあるため、ここでは主にBFFを置くこと自体のメリットを記載します。 私の思うメリットは「認可の橋渡し(Token Handler)」と「APIアクセスの集約」にあります。 現代のバックエンド(特にマイクロサービス構成)は、ステートレスな Bearer Tokenを要求することが一般的です。SPA単体でこれを実現しようとすると、JSでトークンを扱う(XSSリスク)か、バックエンド側すべてにCookie認証を対応させる(実装コスト増・CORS問題)必要があります。 BFFを挟むことで、「ブラウザとはCookie(セッション)で通信し、バックエンドとはTokenで通信する」という構成が素直に取りやすいです。※もちろんToken Handlerのためにサイドカーや軽量プロキシを扱うことも可能ではあります。 APIアクセスの集約(APIアグリゲーション)に関してはコード的な問題だけでなく、APIが同一VPCのinternalなリクエストで通信出来たり、APIとBFFを同じリージョンにまとめたりとネットワークの効率が優れています。 Reactチームはブラウザから大量の遅いリクエストが飛んでしまう問題をBoomer Fetchingとして問題視しており、ご存知の方も多いと思います。 RSCはBFFとして優れてます BFFの中であえてNext.jsを選ぶ理由を挙げるとすると、RSCがもたらす開発体験とBFFとしての適性にあります。RSCを「宣言的なBFF」として捉えると非常に優秀です。 従来、BFF層でAPIを叩くためにはExpressを使ったりGraphQLを使うなどの詰め替え業が必要でした。しかしRSCであれば、コンポーネントツリーの中で直接非同期データを取得し、それをPropsとして流し込むだけで済みます。 データの取得とUIの構造がコロケーション(同居)され、かつ型安全にバックエンドAPIのレスポンスをクライアントコンポーネントへ渡せます。 またRSCによってReactを同期的に書くことができて、非同期で発生する難しさをいくつも解消してくれます。 なぜNext.jsじゃなくても良いか ここまでNext.jsを好意的に書いてきましたが、良くないと感じる部分は存在するのでフェアに記載していきます。 発明を捨てフルスタックフレームワークとしての道を失った Next.js はApp Router以降、フルスタックフレームワークとしての道を失ったと思っています。 Page RouterまではServerからClientへのPropsの境界が明確で(いわゆるgetServerSidePropsで境界が分かれてた)、貧者の選択としてNext.jsでサーバーを実装する選択も取り得ました。 しかし今回のCVE以降は特に、そうした構成を取る事は明確に難しくなります。 対抗も出現している たとえばTanstack Startというフレームワークでは、UIライブラリがReactに依存していなかったり(これはTanstack系は共通の思想ですね)、Reactに非依存な層を増やしたい人には嬉しいでしょう。 またサーバー/クライアントの境界を明確に区別しようという思想もあり、こうしたコンセプトもNext.jsより共感する人が多いと思います。 https://tanstack.com/start/latest/docs/framework/react/comparison#server-functions-vs-server-actions そもそも本当にReactなのかという話もある 上記で紹介したTanstack Startも、他に有力なReact Routerも将来的にRSCを取り入れていく方針ではあります。つまり今の上記フレームワークのシンプルさはRSCに対応してないゆえでもあり、今後どの程度混乱が生じるかは分かりません。 それならいっそ、React以外という道を考える人もいるかもしれません。 ただし、採用市場やフレームワーク自体の完成度を見るに難しい選択にはなると思いますが⋯ ただNext.jsを使う 今フロントエンドでフレームワークやライブラリを選定することは考慮すべきポイントが多くて、難しい時代だなと思います。 きっと色んな言葉が気になってしまうエンジニアも多いと思いますが、最後に私自身の考えを記載しておきます。 ドメインに時間を使う 私が近年大切にしてるテーマの一つに以下の言葉があります。 「どうでもいいことは流行に従い、重大なことは標準に従い、ドメインのことは自ら設計する」 #phperkaigi @koriym さんが突如キーノートみたいなトーク始めてる pic.twitter.com/G52wtZOsfV— uzulla (@uzulla) 2023年3月24日 私はカミナシという組織でSaaSを開発していますが、カミナシには「現場ドリブン」という、ドメインに時間を使おうという概念、そのままのバリューが存在します。 私も2025年だけでCS活動やプロダクトディスカバリや商談同席など現場に10件以上訪問して課題に向き合ってきましたが、エンジニアとしての時間をドメインを考えることに対して使っていきたいという想いが年々強まっています。 技術選定の正解探しに時間を溶かすのではなく、選んだ技術を正解にするためのオーナーシップと、何よりユーザーへの価値提供を大切にしていきたい所存です。
こんにちは、ソフトウェアエンジニアのいちび(@itiB_S144)です。 ラスベガスにて開催された AWS re:Invent 2025 に参加してきました。 このブログでは re:Invent 前にカミナシメンバー 4 人でロサンゼルスからラスベガスまで車旅をしたのでどんな旅をしたか紹介します! 来年の re:Invent 参加の際に参考にしていただけたら嬉しいです。 すべての始まりはここからでした。 2024 年の re:Invent が終わり、楽しみ尽くしたカミナシメンバーは来年の re:Invent に向けて飛行機チケットを予約したりしていたところ、この一言をきっかけに旅の計画が始まりました。 カミナシでは re:Invent に参加するエンジニアに対して柔軟な参加費用の補助があります。それぞれブログや勉強会など、社内外でのアウトプットを条件に自分にあった補助を選ぶことができます。今年は 4 名程度の枠があり、以下の 3 つの補助プランから選択が可能です。 全額補助 カンファレンスチケット代 or 交通費 + 宿泊代の補助 業務時間を使っての自費参加 この制度を活用して re:Invent にはカミナシから 5 人のエンジニアが参加しました。うち 4 人がロサンゼルスから車でラスベガスまで行く旅に参加しました。re:Invent のメインイベントは月曜日から始まります。そこで直前の土日をフル活用して時差ボケを直しつつ旅行を楽しもうという計画です。 旅程の紹介 日付 スケジュール 11/28(金) 東京夕方発の飛行機に乗ってロサンゼルスへ移動 --- 時差 --- 11/28(金) ロサンゼルス国際空港集合 レンタカーを借りて観光しながら移動 バーストーの宿に宿泊 11/29(土) バーストーからデスバレー国立公園へ移動 昼過ぎに到着、観光 デスバレー国立公園内の宿に宿泊 11/30(日) デスバレー国立公園の Mosaic Canyon をハイキング ラスベガスに向けて移動 15時くらいに到着、レンタカーを返却してチェックイン ポイントは金曜日の夜東京発の便を選んだことです。これによって金曜日の朝にロサンゼルスへ到着し、不思議なことに 2 泊 3 日を観光に使うことができます。 空港で現地集合としたため、メンバーによってはもう 1 日早くロサンゼルス入りしてアメリカを満喫している人もいました。 アメリカでは 17 時すぎには外が真っ暗になってしまうため、できるだけ観光の時間が取れるように移動時間を工夫しました。 旅の様子 11/28(金) ロサンゼルスからバーストーへ 11 時頃にロサンゼルス国際空港へ集合し、レンタカーを借りて出発しました。 レンタカー屋さんは空港からの無料シャトルバスで移動します。 無事に車を借りて出発したらまずは腹ごしらえです。 バーストーに向かう途中のメキシコ料理屋さん El Chilito Restaurant に入ってランチをしました。 金額感がよくわからず適当に「Super Nachos」なるものを頼んだらあまりの量にびっくりしました。さすがに $20 の食べ物はみんなでシェアして食べる量でした。どうりで 1 人 1 つ頼んでたら店員さんがニコニコしてるわけです。 道中の BEST BUY に立ち寄ってアメリカの電気屋を楽しみながらバーストーへ向かいます。 Switch 2 の在庫が残っていたり、Ray-ban Meta グラスを試せたり楽しみました。 砂漠を感じる風景 街と街の間が真っ暗なとこにアメリカを感じます 夜ご飯はバーストーの Barstow Burger でいただきました。THE ダイナーな雰囲気が最高です。 これこれ!!って感じのダイナー $30 で 4 つこのセットが付いてくるファミリーセットをみんなで買いました この日はバーストーの California Inn に泊まりました。いわゆるモーテルで、部屋に車を横付けできるタイプの宿です。アメリカっぽさを満喫できました。 11/29(土) バーストーからデスバレー国立公園へ 時差ボケしているメンバーも多いのでこの日の朝はゆっくりめに出発しました。 バーストーを出てデスバレーに向かう途中、Mac の OS で有名なモハベじゃーんとか盛り上がりながら車で進んでいきます。 途中の Baker でお昼休憩をしました。寄ったお店は Jersey Mike's Subs です。アメリカのサブウェイ的なサンドイッチ屋さんで、NFLの公式サンドウィッチスポンサーになっているチェーン店です。公式スポンサーの味は伊達じゃない、とっても美味しかったです。 ばりばりに盛ってじゃぶじゃぶにビネガーをかけてくれました 4人で2本でちょうど良かった もう少し車を走らせるとデスバレー国立公園に到着です。 デスバレー国立公園はとても広い公園で、砂漠地帯や岩山、塩湖など様々な地形が楽しめます。 www.nps.gov 道中で様々な観光スポットに立ち寄りながらビジターセンターへ向かい、チケットを購入します。 泥岩?でできた丘 海抜-855メートルの標識 このあたりに海抜0メートルの標識 塩湖 この日の宿はデスバレー国立公園のビジターセンターのとなりにある The Oasis at Death Valley です。温水とはいえ冬にも関わらず屋外のプールで泳いでいる方がいてすごいなーと... 11/30(日) デスバレー国立公園からラスベガスへ この日は早起きしてデスバレー国立公園内の Mosaic Canyon をハイキングしました。 往復でおよそ 1 時間半ほどのコースで水流に削られてできた岩の間を歩くことができます。 re:Invent では会場の移動でかなり歩くので、軽めのハイキングで体を慣らすのにちょうど良かったです。 <img src="htt