TECH PLAY

エス・エム・エス

エス・エム・エス の技術ブログ

259

はじめに こんにちは、介護/障害福祉事業者向け経営支援「カイポケ」の介護レセチームでエンジニアをしている沖口です。 チームで管理しているテーブルに長年の運用によりデータ量が相当数まで増えてきたものがあり、idカラムの型をINT UNSIGNEDからBIGINTに変更する必要がありました。 先日、その対応をAurora MySQLの BlueGreenDeployment (以降では省略してBlueGreenDeploymentと記載)を用いて短時間のメンテナンスで実現できたため、その事例を紹介します。 背景 カイポケは2011年にリニューアルを行い、現在のカイポケとしての運用を開始しました。 当時作成された一部のテーブルでは2011年から現在までデータの蓄積が続いており、今回対応を行った利用者の請求明細に関するテーブルは、INT UNSIGNEDの上限である約42億レコードに近々到達することがわかっていました。 レコード数がこの上限に達してしまった場合、利用者請求に関連する業務上極めて重要な機能が停止します。 これを未然に防ぐため、大規模なレコードが存在するテーブルの型変更について、実現方法の検討を行うことから始めました。 実現方法の検討 まずidカラムの型変更を行う方法について、生成AIのDeepResearchにより取りうる選択肢を調査しました。 直接ALTER TABLEを実行する オンラインスキーマ変更ツール gh-ost (GitHub's Online Schema Transmogrifier/Translator/Transformer/Transfigurator) を利用する オンラインスキーマ変更ツール pt-online-schema-change を利用する BlueGreenDeployment によるデプロイを行う 選択肢と特徴について把握した後は、実際に私たちのテーブルに適用する場合の制約と照らし合わせてよりベストな方法を検討します。 そこからは、DeepResearchの調査文章を鵜呑みにするのではなく、DeepResearchが根拠資料として出力した公式資料を確認しながら詰めていきました。 以下では、それぞれの選択肢についての判断結果と、その理由を書いていきます。 (見送り) 直接ALTER TABLEを実行する MySQLはテーブルの型変更を行う場合、オンラインDDLによる顧客影響のない形でスキーマ変更を行うことはできません。 *1 もし本番環境でDDLを開始した場合はDDLが完了するまでの間 SHARED_UPGRADABLE ロックが取得されます。 ユーザーの視点に立つと、利用者請求の変更に関する操作を行うと処理が完了せず、タイムアウトすることになります。 それを防ぐためにはメンテナンスの時間をとってリリースを行うことになりますが、その妥当性を検討しておかなければなりません。 私たちのメンテナンスモード下でのリリースには以下の制約があります。 介護事業者は日中は常に重要な業務を行っています。ログアウトが発生するリリースは利用率が下がった19時以降に行う必要があります。 介護事業者の中には19時から24時近くまで業務を行っている方もいます。メンテナンスを行う場合はより短い時間で行う必要があります。 私たちのチームも継続可能な開発体制の一環として、休日深夜のリリースは極力避けるべきという考えがあります。 これらの制約を基に、この手法については「リリース作業が2、3時間程度で完了するか否か」を1つの判断基準として調査することにしました。 そこで、試しに本番環境のAurora MySQLインスタンスをコピーした疑似環境を用意し、実際に流したいDDLを実行しました。 結果として実行時間は7時間弱となり、判断基準とした時間より上回ったため、この選択肢は除外することになりました。 (見送り) gh-ost gh-ostはGitHubが提供する、オンラインでスキーマ変更を行うことができるツールです。(ツール自体の説明は割愛します) オンラインでスキーマ変更を行うことができるのであればメンテナンス時間も不要となり、サービスを止めることなく型の変更ができそうです。 しかし、実行するための 制約 を確認したところ、対象テーブルに外部キー制約がある場合はサポート外との記載があり、今回のケースとマッチしなかったため見送ることとなりました。 (見送り) pt-online-schema-change Perconaが提供する、MySQLのスキーマ変更をオンラインで実行するためのツールです。(こちらも、ツール自体の説明は割愛します) pt-online-schema-changeには、私たちに関係する部分としては以下の特徴があります。 トリガー作成に伴うデータコピー負荷の増加 負荷が大きいとツールが判断した場合の処理停止と再開 上記は注意事項ではありますが、これらによってpt-online-schema-changeの使用が不可能であるとの判断はしませんでした。 一方、懸念点としては以下がありました。 トリガー作成による負荷検証を検討・実施する必要がある。 pt-online-schema-changeによるリリースを開始した後、リリース完了となる時刻が不明であり体制を作りづらく、深夜待機に発展する可能性もある。 pt-online-schema-changeによるリリース実施経験がなく、リリース中断時に元の状況にロールバックする方法の検討/再検討の可能性がある。 上記の懸念点がBlueGreenDeploymentの場合は抑えられると判断し、結果としてpt-online-schema-changeは見送ることにしました。 (採用) BlueGreenDeployment BlueGreenDeploymentで対処する場合、以下の流れで作業することになります。 本番で稼働しているBlue環境を元にGreen環境を作成 Blue環境へのデータ変更は、binlogを用いてGreen環境に継続的にレプリケーションされる Green環境に対して変更を実施 Blue環境とGreen環境を入れ替えることで変更の適用完了 もし、BlueGreenDeploymentの制約が今回のテーブル変更に影響しない場合には、以下が言えます。 本番環境に影響しない形でリリースとは分離してDDLを事前実行できる Green環境へのDDL実行時間中には、本番環境への影響がないため体制を張る必要がない 予定より長時間となった場合もDDL実行日とリリース日の間にバッファをもうけておくことでリリースに影響を出さずに済む リリース可否の判断が明確。リリース可能と判断した後はBlue環境とGreen環境を入れ替えるだけであり問題が発生しにくい Green環境へのDDLが成功した場合はリリース可能 失敗した場合はリリース不可 メンテナンスはBlue環境とGreen環境を入れ替えるタイミングのみ必要であり、1時間もかからず完了できる見込み こういった変更の確実性の高さとリリースまでの予測の立てやすさから、この手法を最優先で検討することにしました。 BlueGreenDeploymentの調査 まずはBlueGreenDeploymentを用いる場合の制約について公式ドキュメントを確認します。 公式ドキュメントに記載されている内容の中で見落としがないよう、全て読みました。 ここでユースケースとしてスキーマ変更についての記載があると一気に実現の可能性が高まるのですが、公式ドキュメントではパッチや設定値変更についてのユースケースが勧められている程度でスキーマ変更については記載がありませんでした。 Amazon RDS ブルー/グリーンデプロイを使用する利点 Amazon RDS ブルー/グリーンデプロイを使用すると、セキュリティパッチを最新の状態に保ち、データベースのパフォーマンスを向上させ、短い予測可能なダウンタイムで新しいデータベース機能を導入できます。ブルー/グリーンデプロイでは、エンジンのメジャーバージョンまたはマイナーバージョンのアップグレードなど、データベース更新のリスクとダウンタイムが軽減されます。 しかし、 ブルー/グリーンデプロイの一般的なベストプラクティス には、制約事項の形で以下の文言がありました。 ブルー/グリーンデプロイを使用してスキーマの変更を実装する場合は、レプリケーション互換の変更のみを行ってください。 例えば、ブルーデプロイからグリーンデプロイへのレプリケーションを中断することなく、テーブルの最後に新しい列を追加することができます。ただし、列名の変更やテーブル名の変更などのスキーマの変更は、グリーンデプロイへのレプリケーションを中断させます。 レプリケーションと互換性のある変更の詳細については、MySQLドキュメントの「ソースとレプリカのテーブル定義が異なるレプリケーション」とPostgreSQL論理レプリケーションドキュメントの「制限」を参照してください。 この制約は裏を返せば「その変更がレプリケーション互換であればスキーマ変更に利用可能である」と解釈できます。 MySQLのドキュメントを見てみましょう。 17.5.1.9.2 データ型が異なるカラムのレプリケーション ソース上の対応するカラムと同じテーブルのレプリカコピーは、同じデータ型であることが理想的です。ただし、特定の条件が満たされているかぎり、これは必ずしも厳密には強制されません。 通常、特定のデータ型のカラムから、同じサイズまたは幅 (該当する場合) またはそれ以上の同じ型の別のカラムにレプリケートできます。 サポートされる変換.違うけれども似ているデータ型の間でサポートされる変換を次のリストに示します。 整数型TINYINT、SMALLINT、MEDIUMINT、INT、およびBIGINTのいずれかの間。 これには、これらの型の符号付きおよび符号なしバージョンの間の変換が含まれます。 不可逆変換は、ソース値をターゲットカラムで許可される最大値 (または最小値) に切り捨てることで行われます。符号なし型から符号付き型への変換時に非可逆変換を保証するには、ターゲットカラムがソースカラムの値の範囲を収容できる十分な大きさである必要があります。たとえば、TINYINT UNSIGNEDは、非不可逆にSMALLINTに降格できますが、TINYINTにはできません。 上記の内容から、カラム型のINT UNSIGNEDからBIGINTへの昇格はレプリケーション互換であることがわかったため、サポート対象と判断しました。 Amazon Aurora のブルー/グリーンデプロイの制限と考慮事項 にも今回のスキーマ変更に影響する制約はなかったため、ここまでの調査およびメリットの高さからBlueGreenDeploymentの採用を決めました。 本番環境でのリリース スケジュール BlueGreenDeploymentの本番環境適用とメンテナンス(本番リリース)は数日空けて計画しました。 このスケジュールでは、以下を考慮しています。 DDL実行時間が事前計測よりも長くかかる可能性への備え DDLが実行された後、Blue環境で実行されたクエリがGreen環境に後追いで適用されるレプリケーション処理時間への考慮 不測の事態が発生した場合のリカバリー時間 特に不測の事態への備えについては、BlueGreenDeploymentによるDDL実行について公開されている事例が少ないことから、何かエッジケースを踏む可能性があったので設定しました。 後続で書きますが、不測の事態が実際に発生しており、時間ギリギリでリカバリーに成功したためバッファを設けておいてよかったです。 実行時間 本番での実測値は以下の通りで、無事1時間のメンテナンスでリリースを完了することができました。 Aurora MySQL DBインスタンス: db.r6i.16xlarge 変更対象テーブルのレコード数: 2,574,439,006レコード DDLの実行時間 9時間30分 メンテナンス時のBlueとGreenの入れ替え作業 10分ほど(※正確な記録を取り忘れました) メンテナンス全体 1時間 以降は、本番環境で起きた不測の事態について書いていきます。 本番環境での不測の事態: データ不整合が発生しDDL実行後のレプリケーションが失敗する 起きたこと Green環境を作成し、Green環境へDDLの実行を行って無事完了したのですが、DDLの実行が完了した後に行われるレプリケーションでエラーが発生していました。 エラーメッセージ 2025-11-18T01:14:43.909892Z 7858 [ERROR] [MY-010584] [Repl] Replica SQL for channel '': Worker 1 failed executing transaction 'ANONYMOUS' at source log , end_log_pos 54575012; Could not execute Delete_rows event on table {テーブル名}; Can't find record in {テーブル名}, Error_code: 1032; handler error HA_ERR_KEY_NOT_FOUND; the event's source log mysql-bin-changelog.038641, end_log_pos 54575012, Error_code: MY-001032 これはBlue環境で実行されたdeleteクエリをGreen環境に適用しようとした際に、delete対象のレコードが無いぞと言っています。 エラーが発生している削除対象の該当行が存在するか、Green環境へSELECTクエリを実行したのですが、しっかりと存在するので不可解なエラーメッセージで困惑です😇 原因 以下はレプリケーションに利用されるbinlogから、DELETEに失敗したlogを抜粋したものです。 ### DELETE FROM `{テーブル名}` ### WHERE ### @1=-824762424 (3470204872) /* INT meta=0 nullable=0 is_null=0 */ ### @2=NULL /* DATE meta=0 nullable=1 is_null=1 */ ### @3=NULL /* TIME(0) meta=0 nullable=1 is_null=1 */ ### @4=NULL /* TIME(0) meta=0 nullable=1 is_null=1 */ ### @5='05' /* STRING(8) meta=65032 nullable=1 is_null=0 */ 「@X」の次にあるのは実際に入る値で、たとえば @5 、つまり5番目のカラムに入るのは '05' であることがわかります。 このうち、 @1 は今回型変更を行ったカラムです。Blue環境ではINT UNSIGNED、Green環境ではBIGINTというように型が異なります。 @1 の値として書かれている -824762424 (3470204872) は他と表記が異なりますが、以下のように読み取ることができます。 -824762424 : signed解釈の値 (3470204872) : unsigned解釈の値 INT UNSIGNEDのカラムに対してSQL上で使われるのはunsigned解釈の値なので delete from {テーブル名} where id = 3470204872 はHITします。 一方でGreen側はこのbinlogを用いてレプリケーションを実行していく際、 どうやら -824762424 が値として扱われるよう解釈してしまっているようでした。 つまり、Blue側は delete from {テーブル名} where id = 3470204872 を行っていましたが、情報を伝搬されたGreen側は delete from {テーブル名} where id = -824762424 を行っていました。しかしそのような行は存在しないため、レプリケーションエラーが発生していたのです。 調査の過程 今回は一度の実行が長時間となるDDLを用いたリリースであるということから、探索的に成功するまで実験を繰り返すような対処法の探し方はできず、限られた時間で確実性の高い対処法を探し出し、一度の成功でリカバリーをする必要がありました。 そのため、仮説を持って確実性の高い証拠・裏どりを探しに行く動きをとっています。 (仮説1)binlogを確認する前、「行は存在するのに削除対象が見つけられない」という状況から Green環境の型変換後のid: 1 (BIGINT) binlogレプリケーションにおけるクエリの履歴である delete from {テーブル名} where id = 1 (INT UNSIGNED) ともに値は 1 ではありますが、内部的に別物となり一致しない状態になっているのではないか?という仮説を持ちました。 (仮説2)仮説1を念頭にbinlogを確認したところ、id列に -824762424 と (3470204872) の2値が併記されており、本来扱うべきである 3470204872 ではなく -824762424 がwhere句で使われているのでは、と初期の仮説1から別仮説に一歩進展しました。 裏付け 仮説2をドキュメントで確認できれば確度が上がると考え、レプリケーション時の値の解釈についてMySQLの公式ドキュメントを再確認しました。 すると、 ソースとレプリカで異なるテーブル定義を使用したレプリケーション にて、以下のオプション記載を見つけました。 ALL_SIGNED 昇格される整数型を符号付き値として扱います (デフォルト動作)。 ALL_UNSIGNED 昇格される整数型を符号なし値として扱います。 整数型が昇格されるときに、符号ありか符号なしかは保持されません。デフォルトでは、レプリカはこのような値をすべて符号付きとして扱います。 これは、今回変更したカラムの値はデフォルトでは符号付き値 (-824762424) として扱われることを示しています。また、 ALL_UNSIGNED を指定することで符号なし値 (3470204872) として扱われるように設定できるともあります。 今回の場合はまさにこれが対処法となりそうで、「これだ!」と仮説が確信に変わりました。 ドキュメントの該当部分は事前に読んでいたのですが、このような事態につながる記載であるとは気付けませんでした。 さらなる裏付け id列がsigned解釈の値であるという場合、delete / updateをしようとすれば対象の行が見つからないエラーとなりますが、insertの場合はエラーなくマイナスの値でinsertされた行があるはずです。 実際に確認したところ、Green環境にはマイナス値のid列が多数作成されており、確証を得ることができました。 リカバリー リカバリーのためには ALL_UNSIGNED を付与したGreen環境で再度レプリケーションを実行する必要があり、以下のように全工程のやり直しを行いました。 Green環境の削除 Green環境の再作成 ALL_UNSIGNED パラメータの追加 DDLの再実行 幸いエラーが発生した当日に原因と対処法の特定ができたため、リリース実行日に間に合わせることができました。 終わりに 今回はBlueGreenDeploymentを活用したカラム型の変更と、その際に生じた予期せぬレプリケーションエラーについて記載しました。 予期せぬ事態は1件発生してしまいましたが、 顧客影響を出さず、実行時間を気にせずDDLを本番実行できる Green環境の型変更が正常に完了できればメンテナンスは不安なくリリースができる 短時間のメンテナンスにより介護事業者がアプリを利用できない時間を短時間としつつ、リリース関係者も無理なくリリースができる というBlueGreenDeploymentのメリットを活かしたリリースが行えたと思います。 この記事がこれからスキーマ変更を行う方の役に立つと嬉しいです。 読んでいただきありがとうございました。 *1 : オンラインDDL操作に関する公式ドキュメント: https://dev.mysql.com/doc/refman/8.0/ja/innodb-online-ddl-operations.html
アバター
はじめに こんにちは!カイポケコネクトの開発推進チームでエンジニアをしている @_kimuson です。 私たちのチームでは、開発体験の向上や今後の拡張に備えて大規模なフロントエンドアプリケーションのマイクロフロントエンド化を進めています。 アプリ分割については下記の記事で紹介していますので、よろしければ合わせてご参照ください。 アプリ分割の一環としてpnpm workspaceを使ったモノレポ構成を採用しているのですが、internal packageにおけるpeerDependenciesの扱いが課題になりました。 この記事では、pnpm workspaceにおけるpeerDependenciesの問題とその解決策について整理してみます。同じような構成で開発している方の参考になれば幸いです。 pnpm workspace と peerDependencies、何が問題なのか まずは問題の背景から説明します。 pnpm workspaceでは workspace:* プロトコルを使うことで、ローカルパッケージ間の依存をシンボリックリンクで解決してくれます。これがとても便利で、パッケージ内のファイルを編集すると即座に反映されますし、watchプロセスも不要なので開発環境が重くなりません。 典型的な構成はこんな感じです: // apps/main/package.json { " dependencies ": { " react ": " ^18.0.0 ", " shared-ui ": " workspace:* " } } // packages/shared-ui/package.json { " peerDependencies ": { " react ": " ^18.0.0 " } } apps/main が shared-ui に依存していて、 shared-ui はReactをpeerDependenciesとして宣言しています。ごく普通の構成ですね。 シンボリックリンクが引き起こす問題 UI Library等ではよくある普通の構造だと思いますが、workspaceでのinternalパッケージでは致命的な問題があります。 シンボリックリンクでパッケージを参照すると、Node.jsのモジュール解決の仕組み上、それぞれのパッケージが 異なるライブラリの実態を参照してしまう 、ということです。 シンボリックリンクにより shared-ui を参照していますが、それぞれが異なるreactインスタンスを参照してしまいます。 Node.jsはモジュールを解決するとき、呼び出し元のファイルから見て近いディレクトリから順番に node_modules を探していきます(参考: Node.js Documentation - Loading from node_modules folders )。 shared-ui のソースコードから import React from 'react' すると、まず packages/shared-ui/node_modules/react を見つけてしまうわけです。 peerDependenciesは本来「利用側と同じインスタンスを使ってね」という意図で宣言するものですが、シンボリックリンクだと期待通りの解決になりません。 実際に問題が起きるケース とはいえ、すべてのpeerDependenciesで問題が起きるわけではありません。問題が顕在化するのは、基本的にパッケージが グローバルな状態を持つ 場合です。 たとえば、わかりやすい例として単純なカウンターパッケージを考えてみます: // counter.ts let count = 0 ; export const addCount = () => { count++; } ; export const getCount = () => count; このパッケージが shared-ui にpeerDependenciesとして追加されていると仮定して、 shared-ui から addCount() を呼んでも、 apps/main から getCount() を呼ぶと0が返ってきます。別のインスタンスの別の変数を見ているからですね。 Reactも同様で、 useContext や useMemo などグローバルな状態に依存する機能を使うとエラーが発生します。一方、date-fnsやes-toolkitのような純粋な関数だけを提供するパッケージは、同じ実装が2箇所に存在するだけなので動作には問題ありません。 解決策 この問題に対するアプローチは大きく2つあります。いずれも peerDependencies を同じモジュールの実体に解決させる という方向性は同じです。 解決策1: バンドラーやテストフレームワークで依存解決をオーバーライドする 1つ目は、依存解決が行われる各ツールで、モジュールの解決先を上書きする方法です。 Jestの場合はこんな感じで設定します // jest.config.ts import { createRequire } from 'node:module' ; const require = createRequire(import.meta. url ); const reactPath = require.resolve( 'react' ); const jestConfig = { moduleNameMapper : { '^react$' : reactPath, } , } ; export default jestConfig; webpackを使うNext.jsやStorybookでも、同様に resolve.alias で上書きできます。Viteでも dedupe オプションが提供されています。 メリット: シンボリックリンクの利点(即座に反映、watch不要、軽量)を維持できる 必要な箇所だけパッチを当てられる デメリット: Next.js、Storybook、Jest/Vitestなど、依存解決を行うすべてのツールで設定が必要 新しいツールを追加するたびに設定を追加しないといけない 潜在的な問題は残る Ex. 実はグローバルな状態を持っていてロジックが壊れていることに後から気づく Ex. 同じコードが重複してバンドルに含まれてしまう 解決策2: pnpm の dependenciesMeta.*.injected を使う 2つ目は、pnpmが提供する injected オプションを使う方法です。 解決策1がパッチを充てるような対策だったことに対して、こちらは根本解決になります。 // apps/main/package.json { " dependencies ": { " react ": " ^18.0.0 ", " shared-ui ": " workspace:* " } , " dependenciesMeta ": { " shared-ui ": { " injected ": true } } } これを設定すると、pnpmは .pnpm ディレクトリ内に node_modules を持たないパッケージのクローン を作成します。 ディレクトリ構造は下記のようになります。 ./ ├── apps │ └── main │ └── node_modules │ ├── .pnpm │ │ └── <shared-ui-clone> │ │ ├── src (shared-ui のコピー) │ │ └── node_modules (空) │ └── @my-pkg │ └── shared-ui --> .pnpm/<shared-ui-clone> ├── packages │ └── shared-ui └── package.json apps/main からはこのクローンを参照するようになるので、 shared-ui のコードからReactをimportしても、クローン側の node_modules は空であるため親ディレクトリをたどり、 apps/main/node_modules/react へ解決されます。 つまり、 apps/main のコードと shared-ui のコードが同じreactインスタンスを参照するようになります。 見ての通り根本的な解決であり、理想的に見えますが開発体験が致命的に悪いという問題を含んでいます。 injected による開発体験の劣化と融和策 injected を使うと、 packages/shared-ui/src とそのクローンである apps/main/node_modules/.pnpm/<shared-ui-clone>/src は基本的にファイルごとにハードリンクで同期されます。 したがってファイルが追加されたり、削除されたりする場合には同期されません。 また、特定条件下でハードリンクではなく単なるコピーが行われるというUndocumentedな挙動があり、我々のプロジェクトではこの条件( shared-workspace-lockfile=false , postinstall あり)を満たすためコピーに寄って作成され、変更すら同期されないという状態でした。 起票したIssue: https://github.com/pnpm/pnpm/issues/9828 この問題を補完するために、 pnpm-sync-dependencies-meta-injected というツールがあります。これを使うと、開発中にソースコードの変更をwatchしてハードリンクを更新してくれるので、injectedを使いながらも快適な開発体験を維持できます。 ちなみにpnpm v10では syncInjectedDepsAfterScripts という公式オプションも追加されています。任意のスクリプト実行後にハードリンクを再同期してくれるもので、将来的にはこちらを使う選択肢もありそうです。 ただこれで全部解決だよね!ということでもなく「node_modulesを削除してpnpm iしなおさないと更新されない状態に定期的に陥る」といった問題が実際に発生しており、開発体験としては非常に悪い状態が残っています。 結局どちらを採用すればよいのか 少なくとも現在(2025年12月)ではどちらかが完璧な解決策にはなっておらず、トレードオフを考慮して選ぶ必要があります。 観点 解決策1(オーバーライド) 解決策2(injected) 開発体験 ◎ △ 設定の煩雑さ・手間 × ◎ 根本解決 × ◎ バンドルサイズ △(重複の可能性) ◎(重複なし) 可能であれば根本解決であるinjectedに寄せていきたい気持ちはありつつ、開発体験が悪すぎるので現状はオーバーライドに寄せています。 overrides 方式では解決できない問題もある 基本開発体験を優先してoverridesに寄せていますが、一部の共通パッケージではoverridesで解決できない問題があり、injectedも利用しています。 具体的には型解決の問題です。 型解決についてはpeerDependenciesの実態が異なるものに解決されていたとしてもバージョンさえ揃っていれば構造的部分型で型チェックされるため基本的には問題は起きません。 import { User } from 'peer-dep-pkg' import { getUser } from '@my-pkg/dep' const user: User /* node_modules/peer-dep-pkg */ = getUser() /* packages/dep/node_modules/peer-dep-pkg */ のように実態が異なっていても構造は同一であるため、型エラーにはなりえません。 弊プロダクトの場合はpnpm catalogを使って依存を管理しているため、バージョンは固定する運用をしているのでバージョン違いも起きません。 https://pnpm.io/ja/catalogs 一方、TypeScriptでも「classを使っておりprivateプロパティを持つ場合」では例外的に総称型で型チェックされるケースが存在し、そういった型が使われているライブラリがpeerDependenciesに存在すると型チェックは適切に行えません。 // ライブライ側のコード例 class ApiClient { private cache : unknown ; // ... } import { ApiClient } from '@my-pkg/peerDep' import { createApiClient } from '@my-pkg/dep' const apiClient: ApiClient /* node_modules/peer-dep-pkg */ = createApiClient() /* packages/dep/node_modules/peer-dep-pkg */ ; // => 実態が異なるため総称型でチェックされ型エラー この問題は解決のワークアラウンド的に回避も難しいので、一部のパッケージでは開発体験の劣化を許容してinjectedを採用しています。 終わりに pnpm workspaceにおけるpeerDependenciesの問題と解決策について整理しました。 シンボリックリンクによるモジュール解決の仕組み上、peerDependenciesが異なるインスタンスを参照してしまう問題がある グローバルな状態を持つパッケージ(Reactなど)や総称型になる型を公開するパッケージで問題が顕在化する 解決策としては「各ツールでオーバーライド」か「injectedオプション」の2つ 正直、overridesも設定が煩雑すぎるしワークアラウンドであること、injectedも体験が悪くてしっくりは来ていないのですが背景理解と方針検討にも苦労したので、同じようなworkspace化等に取り組んでいる方の参考になれば幸いです! また、pnpmのリポジトリでのこの問題は議論されているので、injected(根本解決)ベースでより開発体験も維持できるソリューションが出てくると良いなと思っています。 https://github.com/orgs/pnpm/discussions/3938
アバター
はじめに こんにちは!カイポケコネクトの開発推進チームでエンジニアをしている @_kimuson です。主にフロントエンドを中心に開発生産性の向上に取り組んでいます。 今回は、カイポケコネクトのフロントエンドを単一のNext.js構成からマイクロフロントエンド化した話を紹介します。 スパンで言うと提案をしてから9か月ほど経っているのですが、ようやく形になってきたので方針や試行錯誤した知見を共有できればと思います。 背景・元々のアーキテクチャ まず、元々の構成を簡単に説明しておきます。 カイポケコネクトのフロントエンドは、GraphQL Federation *1 を行うBFF *2 Serverに対して巨大なフロントエンドが建っている構造です。 Next.jsを使っていますが、SSR *3 は行わず静的な構成です。Pages RouterでStatic Buildした成果物をS3でホスティングするだけのシンプルな形ですね。 組織構造としては、ストリームアラインドチーム *4 がNext.js内のページで区切られた小さいアプリケーションごとにオーナーシップを持っています。 例えば図のapp1のチームは主に src/pages/app1 , src/services/app1 のオーナーシップを持つような形です。 分割のモチベーション もともとはミニマムに単一のNext.jsで開始していたこのプロダクトですが、組織拡大・時間経過とともに肥大化を続けており、ペインが出てきている状況でした。 課題1: 各チームごとにバリューストリームを持ちたい まずそれぞれのアプリケーションの開発はチームが分かれているので、当然それぞれのチームでバリューストリームを持ち、リリースをしていきたいのですがビルドが一括になる都合上個別でのデプロイを行うことができませんでした。 結果、2週間毎にまとめて足並みをそろえてリリースを行う「リリーストレイン」を長らく実施していましたが もっと高頻度にリリースをしていきたい アプリ単位でQAやリリースを行って行きたい と言った需要が大きくなっていきました。 課題2: CI やローカル環境の肥大化 本来自チームとは関係ない・全チームのソースコードが含まれているパッケージになってしまっているので 変更に関係ない対象のCI(test, build, lint, 型チェック)を動かす必要がある ローカルで全部入りのNext.js Dev Serverを立てる必要がある と言った状態でした。 今後もアプリの小グループやコードベースも増えていくので今の構成のまま進んで大丈夫か?という懸念が出つつありました。 マイクロフロントエンドで解決を目指す これらの課題に対して、Verticalにフロントエンドを分割する構成に移行することで解決を目指しました。 アプリごとに独立したNext.jsアプリケーションを持てるようにします。 これにより ローカル環境で自分が触らないアプリは共有環境のリソースに接続できる CI/CDも分割され、アプリ単位で必要なCIに絞って回すことができるようになる アプリのデプロイ単位を分割できるため、チームごとに自分のアプリだけデプロイすることが可能になる という形でペインが解消されていく構想で開始しました。 実現方法 分割の実施には複数のアプローチを検討しましたが、分割すること自体が非常に大きな取り組みでありスコープを極力絞ったミニマムな方針で分割を行いました。 例えばアプリごとにサブドメインを分ける等も考えられましたが アプリのパスは変えない( /app1 ならapp1のNext.jsが動くだけ、という構造) パスを変えないので旧パス・新パスのリダイレクト等も不要 これまで同様単一のs3バケットにデプロイする形を維持 とすることで、パッケージを分割する以外の関心をあまり気にせず進められるようにしました。 basePath を活用したインフラ構成がほぼ変わらない方法 Next.jsには basePath オプション が存在しており、サブパスにおいて配信する構成に対応しています。 これを使ってそれぞれのアプリを basePath ありでビルドし、成果物を結合してS3にアップロードします。 # 各アプリのビルド成果物 apps/ ├── app1/out/ # basePath: /app1 でビルド │ ├── _next/ │ └── index.html ├── app2/out/ # basePath: /app2 でビルド │ ├── _next/ │ └── index.html └── app3/out/ # basePath: /app3 でビルド ├── _next/ └── index.html ↓ cp -r で結合 # S3にアップロードする成果物 dist/ ├── app1-path/ │ ├── _next/ │ └── index.html ├── app2-path/ │ ├── _next/ │ └── index.html └── app3-path/ ├── _next/ └── index.html 静的ホスティングでの Dynamic routes 対応 上記で基本的には期待通り動くのですが、Dynamic routesの解決に関しては追加の対応が必要です。 そもそも分割関係なく一般的にNext.jsの静的ホスティングではDynamic Routesが動作しないのでワークアラウンドを行う必要があることが知られています。 詳細な方法はインフラ等にも寄るのでまちまちですが、概ねこういう対応が必要です。 インフラ側: /posts/10 のようなアセットが存在しないパスへのリクエストでも /404.html 等を返すようにする フロントエンド側: /404 等受けたFE側で window.location.pathname ( /posts/10 ) を確認し、存在するルートあれば router.replace してフォールバックする アプリ分割すると当然アプリごとの /404 からしかソフトナビゲーションで正規のパスにフォールバックできませんから、個別の /404 に流す必要があります。 我々の場合は、CloudFront Functionsで下記のような対応を入れることでDynamic Routesに上手く対応しています。 静的アセットは正規表現でマッチさせてそのまま帰す それ以外はCloudFront Functionsが各アプリのパスを知っており、 /app1-path → /app1-path/404.html を返す、というような処理を生やす これにより、分割して結合したビルド成果物をs3に配信する形でDynamic Routes含め正常に動かすことができるようになりました。 モノレポの管理 続いて、ローカルやCIでのパッケージ管理についてです。 モノレポ管理はpnpm workspace + Turborepoの構成を採用しています。 pnpm workspaceは他のパッケージマネージャーと比較してもワークスペース周りの機能が充実しています。 基本的にはワークスペースプロトコルを使用し、内部パッケージ間の依存を解決しています。ワークスペースプロトコルを使用すると依存元のnode_modules/pkg-name部分がパッケージの実態へのシンボリックリンクになるため、ホットリロード等がほぼ同一パッケージ内で依存していた場合と変わらないような体験で利用できます。 ./ ├── packages/ │ └── shared/ # 実際のパッケージの実体 │ ├── package.json │ └── src/ │ └── index.ts │ └── apps/ └── app1/ ├── package.json # "shared": "workspace:*" と記述 └── node_modules/ └── shared/ # → ../../packages/shared へのシンボリックリンク また、パッケージを跨ぐスクリプトの管理にTurborepoを採用しています。 Turborepoでは --affected というオプションが提供されており、例えば turbo run build --affected と実行するとgitのdiffから依存関係を含めて実行する必要があるパッケージを計算し、実行してくれます。 また、我々はまだそういう構成が必要になっていないので利用していませんが、「ビルドを実行するには依存するパッケージがビルドされている必要がある」というような依存関係も定義できるのでここういった実行すべきタスクの管理がお任せできて便利です。 internal package における TypeScript の型解決 internalでないパッケージではビルドを行い d.ts と .js を公開して利用させる構造が一般的です。 // 一般的な npm package の公開方法 { "type": "module", "exports": { ".": { "types": "./dist/index.d.ts", "require": "./dist/index.cjs", "import": "./dist/index.mjs" } } } ただしinternal packageでは一々共通パッケージでビルドしていると開発体験が悪いので、 .ts ファイルを直接公開し、実際にトランスパイルを行うのはアプリ側にしています。 // TypeScript ファイルを直接公開する { "type": "module", "exports": { ".": "./src/index.ts" } } 基本この形で開発者体験を維持して依存解決をしていますが、一部ビルドをしたいパッケージもワークスペース内に存在しているのでそちらについてはcustom conditionを使って内部でのみ .ts に解決させたりもしています。 この辺りは以前記事を書いているのでよければあわせてご参照ください。 直接 .ts を公開するために気をつけること 体験が圧倒的に良いので .ts の直接公開がおすすめですが、いくつか気をつけておくべきポイントがあります。 まずは、 .ts を公開するということは複数のパッケージから公開されたtsファイルの型チェックが行われるということです。 そのため、極力パッケージ間の型チェックに関するtsconfigのオプションを統一しておくことが望ましいです。我々の場合は共通のtsconfigを packages/tsconfig として用意し、これをextendsして必要な箇所だけ上書きする構造にしています。 { "extends": "@my-pkg/tsconfig/base.json", "compilerOptions": { // ... 上書きする設定 } } 次に同様の理由でパッケージのバージョンを揃えておくことが望ましいです。 アプリごとのTypeScript本体のバージョンが異なっていたり、依存のバージョンが異なっていると一方では通る型チェックがもう一方では通らないと言ったことがありえます。 pnpmではCatalogsというワークスペース内で利用するバージョンを揃える機能が提供されているのでこれを使うと統一を簡単に実現できます。 # pnpm-workspace.yaml packages : - apps/** - packages/** catalog : 'react' : 19.0.0 catalogMode : strict cleanupUnusedCatalogs : true // package.json { " dependencies ": { " react ": " catalog: " } } 共通のアプリケーションコードをどうするか問題 app1とapp2で「共通で利用しているコード」は、パッケージに切り出す必要があります。いわゆるcommonやutilsという名前がつきがちなコード郡ですね。 まず理想形を言うとちゃんと責務ごとにパッケージを分けていくのが良いと思います。 一方、現実的にはリファクタコストが大きくて分割のコストが大きくなってしまうため、どかっとまとめて単一のsharedなパッケージとして切り出すことにしました。 これはパッケージ間の循環参照を許容しないためです。 例えば auth パッケージと test パッケージを用意して、testではauthにある型が必要、authではテストするのにtestパッケージのユーテリティが必要となると相互依存の関係になってしまうことになります。これを許容してしまうとビルド等の依存関係を正しく解決できますか?とか、型やパッケージ自体の依存解決は問題ないか?等と考えることが増えるため許容しないことにしています。 上記を基本方針としながら まずは単一のNext.jsアプリがワークスペースの中で動く構成 命名からcommonのようにわかりやすく共通部分となっている箇所を切り出し、1のアプリがそこに依存する状態を作る 小さい・変更の少ないアプリから実際に移行しながら必要な箇所を特定して共通パッケージに切り出す という流れで共通コードを分離しながら分割を進めていきました。 結果 この記事を書いている2026年1月現在ですべてのアプリ分割は終わっていませんが、残すことあと1アプリとなりました。 現時点での結果をまとめようと思います。 開発環境で必要なアプリだけ起動できるようになった ビルド単位が別れたことで触るアプリケーションの next dev のみ立てれば良い形になりました。他のアプリに関しては共有環境のbuildをそのまま受け取るだけで良いので開発マシンにも優しいですし、他アプリ起因のトラブルが起きづらく成りました。 CI は turborepo --affected で必要な依存だけ実行 冒頭で紹介した通りTurborepoには --affected オプションがあり、変更に影響があるスクリプトだけ実行することができます。 CIの構築も手軽で、CI用のワークフローを用意して --affected でテスト等を実行するだけで必要なパッケージに絞ったテスト等が実行されるようになります。 ライブラリの段階移行がしやすくなった 副次的に狙っていたことではありますが、ライブラリのアップデートをアプリ単位で進められるようになりました。 コードベースが大きいので、密に依存しているライブラリのMajorアップデートや別ライブラリへの移行等は大変になりがちです。例えばJestはESM Support周りが辛いのでVitestへ移行をしているのですが、こういう話をアプリごとで実施できるようになりました。 ちなみに、前述した通り我々はパッケージのバージョンはすべてpnpm catalogで一元管理していますが、named catalogsを使って複数バージョンの共存も可能になっています。 # pnpm-workspace.yaml # 通常のカタログ catalog : react : 17.0.2 react-dom : 17.0.2 # Named Catalog catalogs : react17 : react : 17.0.2 react-dom : 17.0.2 react18 : react : 18.2.0 react-dom : 18.2.0 これでバージョンを一元管理しつつも、特定のパッケージだけMajorバージョンアップさせると言った対応が可能になっています。 https://pnpm.io/catalogs#named-catalogs 残っている課題:shared 肥大化問題 分割後に課題もいくつか残っているのですが、特に重要なので「sharedの肥大化問題」です。 まず、アプリケーションの共通部分を切り出したsharedのパッケージの変更ではホットリロードが効かないという問題があります。 これはpeerDependenciesを持つパッケージを workspace:* プロトコルで解決する際に起こってしまう問題であり、事情が複雑なのでこれ自体で記事を書いています。 また、ホットリロードの問題を置いておいてもsharedがファットだと結局変更が他チームに影響することが増えます。調整ごとの時間が増えてしまうので、組織的にも良くないと思っています。 対策としてsharedを減らしていく方向で進めています。 そもそも移行を優先してどかっと持ってきてしまったので デザインシステムとして昇華されていないが、ドメインの事情を持たないUI Patternはデザインシステムに持っていけないか? アプリを跨ぐ共通のUIは本当に共通にしないとダメなものか?コピーの方が望ましくないか? 等を検討し、ちゃんと用途ごとのinternal packageに分離してsharedを縮小していきたいと思っています。 まとめ 巨大なNext.jsアプリケーションをマイクロフロントエンド化した話を紹介しました。 ページパスやインフラ構成への影響を最小限に抑えたことで、現実的に分割を進められました。 おかげでCIや開発環境、依存ライブラリの段階的アップデートなどアプリケーション単位で実施できることが増えました。 まだ課題が残っているのは書いたとおりなので、引き続き改善を続けていきたいと思っています。 *1 : 別途のグラフを持つ複数のGraphQL Serverを束ねるGateway Serverを用意し、クライアントから統合された1つのグラフに対してリクエストを行うことができるアプローチ。 *2 : Backend For Frontend の略。フロントエンドとバックエンドの中間に配置されるサーバーで、フロントエンドから見て複雑なバックエンド呼び出しを隠蔽する等の責務を持ちます。 *3 : Server-Side Rendering の略。意味が揺れがちですが、ここでは「リクエストごとにサーバー側でHTMLを組み立てて返す構成」の意味で使用しています。 *4 : 書籍チームトポロジーにて紹介されているチーム分類の1つです。基盤を提供したり高度な専門領域を扱うチームと対比してビジネスドメインに沿ってプロダクトの開発を進めるチームを指します。
アバター
はじめに こんにちは。カイポケコネクトの開発推進チームでエンジニアをしている @_kimuson です。 開発推進チームではエンジニアの生産性向上をミッションに掲げているため、最近では積極的にAI活用を推進しています。 上記エントリでは、タスクごとの協業レベルを定義しより低い協業レベル(=できるだけLLMに移譲しきる)を実現するための方針を紹介しました。 このエントリではより具体的に、Claude Codeをフル活用してこういったワークフローの設計を組織に適用する際の知見をまとめてみようと思います。 設計するワークフローの協業レベルを意識する 前回のエントリでは、ワークフローを設計するに当たって協業レベル、つまりどの程度LLMに権限移譲するか、をデリゲーションポーカーの分類を借りて整理しました。 よくあるLLMの利活用シーンと対応する移譲レベルは下記のように対応します: 流れ 協業レベル 主体 ChatGPTに設計について相談し、自分で実装 Consult 人間 Copilot や Cursor の補完を使いながら、自分で実装 Consult 人間 細かい指示を出しながらエージェントが実装。Driver がエージェントなモブプロ Agree 人間 & LLM LLM にチケットを渡してPRまで作ってもらう Inquire LLM LLM にチケットを渡してPRを作ってもらい、LLMによるレビューでマージまで完結 Delegate LLM 広く「AI活用」といっても、目的ややりたいこと次第で設計すべきワークフローの協業レベルは変わってきます AgreeやConsultレベルで、エンジニアとAIが協業しながら出せるアウトプットの質を高めようという話なのか Inquireレベルに持っていくことで、責務を移譲しデザイナーが簡単なFE実装をできるようにしたり、QAの人が自動テストを書きやすくしたりといった職能の拡張をしたいのか Inquireレベルに持っていくことで、エンジニアの作業時間をブロックせずに小さいタスクをゴリゴリ進めてもらおうとしているのか これらはすべてAI活用ですが、ワークフロー設計の考え方が大きく変わってきます。 なので最初に明確に意識しておくのが良いと思っています。 エージェントはコンテキストがすべて エージェント設計でもっとも重要なことはコンテキスト管理だと思っています。 イメージしてみてください。われわれ人間が開発をするうえでも 設計をしているときは、要件・非機能要件を満たせるか、対応できないEdgeケースがないかをワーキングメモリに置きながら考えるし フロントエンド開発をするときはTypeScriptの型システムやReactの思想を意識しながらコーディングするし 同じフロントエンド開発でもユニットテストを書くときは仕様の漏れや境界値など検証すべき内容を意識しているし と言った形でワーキングメモリに置いていることが全く違います。 人間はこういうコンテキストの切り替えを自然にできるんですが、LLMはこれができません。 したがって工夫をしないと、エージェントはこれらすべての視点をすべてワーキングメモリに載せた注意散漫な状態で様々な作業を行う羽目になります。これではいくらモデル性能があがっても質の高いアウトプットは出しづらいです。 → つまり、 コンテキストを最適化すること、それを実現するためのプロンプトがすべて なので、そこに投資をするのが大事になってきます。 コンテキストをどうやって分離するのか このコンテキスト分離の答えの1つがマルチエージェントです。 単一のエージェント・会話セッションでやりたいことを全部載せると指示も会話ログが非常に長くなってしまうので、それぞれの個別の責務を持つエージェントが協業することでコンテキストを分離しようという考え方です。 この領域ではGoogleが発表されている A2A プロトコル 等もありますが、普及しているコーディングエージェントのCLIツールで利用できるわけではないのでとっつきづらさがあります。 Claude Codeではやや制約もある *1 もののタスクツール・サブエージェントが組み込まれているので、手軽にこれを実現できます。 例として、実装を丸投げしようと思ってオーケストレーションなしでワークフローを組んでみます。チケット確認→設計→API Spec決定→BE実装→FE実装→型エラー・テスト対応→報告、という流れです。 この場合、すべてのステップの注意事項やガイドラインを含むと、プロンプトが数百行にもなります。こういうのを作ってみるとわかりますが、 それぞれのステップで重要な観点を担保できない どこかのステップで詰まると解決できない・長引くと型エラー対応などのステップがスキップされる といったことが多くなります。 一方、これをオーケストレーション前提で組んでみると、メインセッションの責務はオーケストレーションだけになります。各ステップの状況を追っていって、サブエージェント(サブタスク)を呼び出して内容は移譲する。これだけです。 こうするとサブエージェントには単一責任なプロンプトを持たせた上で起動できるので、「設計において重要なこと」「BE実装のガイドライン」などそれぞれの責務で必要なプロンプト のみ を渡した上で各ステップを実行できるようになります。 オーケストレーションなしだと他のステップの会話ログも全部N回投げつけることになるのに対して、コンテキストは適時破棄されるのでトークン効率も良いです。 オーケストレーションのトレードオフ という感じでオーケストレーションは良いことづくめに見えますが、すべてのケースで適用すべきというものでもありません。 実際に使ってみると弱い点もあります 理論上解ける問題の範囲を広げやすいが、現実にはオーケストレーターと各サブエージェントの関わり方の設計の難易度が高い。 対話を重ねることによって成果物をブラッシュアップすることにも向きません。 ブラッシュアップに向かないのがなぜかというと、オーケストレーションではサブエージェントの実装のコンテキストを知ることができません(むしろそれを目的に分離している)。 したがって、ユーザーとメインセッションの会話はいわば伝言ゲーム状態であり、メインセッションも実装の詳細を知らないため、詳しくない者同士が対話している状態になります。 ということで協業レベルを意識することが大事になるわけです。 Agreeレベルで一緒に作っていきたい内容はオーケストレーションの採用を限定的にした方が良いですし、逆にInquire以上のレベルでまるっと移譲したいタスクは積極的にオーケストレーションしていくと良いです。 また、0/100ではなく部分的に使う案はバランスが良くAgreeレベルでも採用しやすく私は好んで使っています。 メインセッションの責務は「実装 & オーケストレーション」にする メインセッションは実装に集中しつつ、設計やコードレビュー・動作確認などは自分で行わずサブエージェントへ移譲する これだと「もうちょいここ直さないとでしょ〜」みたいなフィードバックを柔軟に受けつつ、各ステップの品質を安定させやすくなります。 サブエージェント vs Skill vs コマンド エージェントに適切なコンテキストを付与する手段としてClaude Codeは複数の手段を提供していて、特に効果的に活用しようと思うと使い分けの判断が難しいのでは?と思ってます。 Slash Command Subagents Agent Skills CLAUDE.md ここでは私なりにそれぞれが得意とする観点や用途を整理しようと思います。 Progressive Disclosure Progressive Disclosureは、最近よく主張されているタスクごとに必要なコンテキストだけを段階的に読み込ませるプラクティスです。 Claude Skillsの公式のプラクティス で推奨されている概念で、LLMのコンテキストウィンドウを効率的に使うための重要な考え方です。 例えばCLAUDE.mdやMCPはすべてのセッションで例外無く読まれます。10%のセッションでしか使わない知識を入れると無駄になってしまいます。 一方、Skillやコマンド、サブエージェントは必要なセッションでだけプロンプトを読み込む仕組みです。「限られた場面でしか使わない専門的な知識」を置く場所として優れています。 良い面ばかり言われるのでネガティブな面にもフォーカスすると、段階的にSkillを使えれば理想なんですが、自動で適切に適用していくのは現実的に難しいと感じています。 Must always be enabled when writing/reviewing React code のような強めのdescriptionを入れておいてもReactを触るときに必ず使ってくれるわけではない サブエージェントもLLMが自律的に使うかを決めますが、プロンプトなしでReactを書くときにReact agentへ移譲するとは限りません ですので、もちろん重要な仕組みではあるのですが、Skillやサブエージェントを増やし続けても人間のように自然なコンテキストスイッチをして実装してくれるわけではありません。 とはいえ考え方自体は非常に重要なので、すべてのコンテキストに読まれてしまうCLAUDE.mdは以下のような方針で最小限にしています。 いかに育てるかではなく、いかに情報を落とすかこそが大事。 むやみに増やさない 80%以上のタスクで必要になるか、プロジェクト概要など解像度を上げるため必要なむしろ抽象的な情報を中心にする 情報そのものよりProgressiveにDisclosureするための起点となる情報(例えばトラブルシュートについてはこのドキュメントにまとまっているよ、など)を記載する また、MCPについてはSkillsの登場の背景としてMCPがProgressiveにDisclosureできないことから説明されていたのでネガティブな印象を受けがちですが、Claude Codeでは段階的な読み込みがサポートされており有効にすればProgressive Disclosure面での不利はもうないので、用途にあわせてSkillsと適切に選択できると良いと思います。 Command vs Skills vs Agent CLAUDE.mdはProgressive Disclosureの文脈でむやみに育てるべきではないので、代わりにコマンド・スキル・サブエージェントを育てていくのが良いと思います。 これらの使い分けについては悩みがちだと思うので私なりの整理を書いておこうと思います。 Command vs Skill は本来の用途に寄せる まず機能面の話をすると、コマンドについては実はシステムコンテキストとしてコマンドとスキルはもうほぼ同一視されているので境界はユーザーのわかりやすさ程度しかありません。 ユーザーも「 /<skill名> 」でスキルをコマンドのように呼び出せますし、エージェントも「 Skill(skill=<コマンド名>) 」の形でコマンドを有効化できます。エージェントに「スキルの一覧教えて」等と問いかけると当たり前にスラッシュコマンドを含むリストを区別なく返す状態なので、我々利用側も同一のものと考えてしまって良いと思います。 ですので、あとはわれわれのわかりやすさで使い分ければ良い範囲ですが、個人的にコマンドとスキルは設計思想が異なるので「人が直接実行するのを主な目的とするか」で区別して使い分けています。 コマンド 人が直接実行する 知識というより手順を載せ、エージェントの動き方を制御したい スキル 人が直接実行することもあるが、サブエージェントで指定されたりProgressive Disclosureで知識を補完するのがメイン 全体の作業手順はあまり書かない(複数のスキルを有効にしたとき、指示矛盾が起きやすい) Skill vs Agent で重要なのは複数利用したいか サブエージェントについては @<agent-name> でサブエージェントを指定できるようになってはいますが、コマンド的にメインセッションにモデルやプロンプトを適用することはできません。 Skill vs Agentで重要なのは1セッションに対して複数利用できるかという観点で、例えばReact skill × TypeScript skillはできますが、React agent × TypeScript agentはできません。 なので再利用可能な知識の単位としてSkillを分離し、サブエージェントに読み込むスキルを指定することが望ましいと思っています。 name: frontend-engineer description: フロントエンドの実装を行う skills: - typescript - react --- < prompt > 責務分解点 責務、つまりサブエージェントをどう切るか。いろいろなパターンがあるかなとは思うのですが、私がよく切るパターンを紹介します。 これらすべてを使うわけではなく、タスクの難易度だったりで部分的に使ったりする前提です。 コンテキスト収集プロセス カイポケリニューアルのプロジェクトのフロントエンドでは開発時のガイドラインなどがかなりちゃんとガイドライン化されています。 逆にそれがゆえにすべてのドキュメントを読み込ませてしまうとコンテキストが膨れてしまうので、今回のタスクに関連するガイドラインのみを抽出して渡すようなプロセスが効果的です。 今回必要なガイドラインのみ集める・LLM向けに不要な情報を削り、LLM向けの整理されたコンテキストを用意することが責務ですね。 計画・設計プロセス これはよくあるパターンなので改めて書くことはそれほどないですが、設計において重要な観点などをプロンプトに含めておき設計に集中させます。 Tipsとして非常に重要なステップなのでSonnetを普段使いしていてもここだけOpusにしたり、また少し大変ですがこういうステップはOpenAI系のモデルのほうが得意なのでそちらに流すのもオススメです。 LiteLLM やBashツールでCodex CLIを使うことでこういうステップのみAnthropic以外のモデルに任せることもできます。 参考: https://code.claude.com/docs/en/common-workflows#use-extended-thinking https://docs.litellm.ai/docs/tutorials/claude_responses_api コードレビュー コードレビューも切り出すと効果的です。 体感「Aをしてはいけません」というルールを守らせながらコードを書かせる精度は出づらいように感じており、実装側には否定形ではなく例など用いて「こんな風に書いてください」を書くことが多いです。 逆にレビューステップだと否定形をかなり上手く扱って指摘できるので、特にレビューステップ用のプロンプトではやらないでほしいことを書くことが多いですね。 また、レビューはミュータブルでないタスクなので並列化しやすく、かつ毎回異なるフィードバックが返ってくるので質を上げたければN並列で依頼するのも効果的です。 QA アプリケーション次第ですが、例えばローカル環境を用意しやすいWebアプリケーションであれば開発サーバーを起動してE2Eで試したり、API開発であればcurlで叩いてみたりします。 プロンプトを書くメタプロンプト ここまで書いてきたように ワークフローをまず丁寧に設計すること そのワークフロー通りに動かせるようにプロンプトを書くこと が重要ですが、こういった知見はあっても守った状態で大量のプロンプトを書くのは非常に大変なのでプロンプトを書く用のコマンドを用意しておくのがオススメです。 参考までに私が使っているコマンドを貼っておきます: このコマンドは私が言語化したプロンプトのプラクティスに沿ってプロンプトを書き、サブタスクを使ったレビュープロセスを行うことである程度期待に沿ったプロンプトを書いてくれるコマンドです。 私が確認する前段である程度問題を検出して修正できますが、とはいえ意図と異なる結果になることも多いので、丸投げはできず全体をちゃんと確認しています。 私はまだそれほど質で困っていないので使っていませんが、 DSPy などプロンプトの評価プロセス自体を持つ仕組みを使うことで質を挙げていくことも有用だと思います。 まとめ Claude Codeを使ったAI活用のワークフロー設計について、私なりの知見をまとめてみました。 協業レベル・責任境界を意識してワークフローを丁寧に設計する ワークフローは協業レベルが低ければオーケストレーション要素を強化し、自律的に動かせるようにする。逆に高密度で協業するのであればレビューやデバッグなど本来の目的と異なる副次的な作業をサブセッションで分ける程度に留める、といった設計が大事 CLAUDE.mdを育てない・Skillを再利用単位として適切な粒度で育てる。エントリーポイントとしてコマンドやサブエージェントを構成 プロンプトの品質向上には時間がかかりますが、言語化・体系化する機会にもなるので、プロンプトを書くためのプロンプトを用意しておくと良い 皆さんのAI活用の参考になれば幸いです! *1 : サードパーティのLLM APIのProxy等で複雑な構成を組まない限りAnthropic以外のモデルを選択することができません。またサブエージェントはサブエージェントを呼び出すこともできません。一方、制約はこれくらいで以前はチャットの再開が不可能である問題等もありましたが対応され、極端に大規模なマルチエージェント協業が必要でなければ十分な機能を持っています。
アバター
はじめに カイポケコネクトの開発推進チームでエンジニアをしている @_kimuson です。 チームで「リファインメント文字起こしからLLMにチケット詳細を自動でメンテナンスしてもらう」という活用を試したところ非常に感触が良かったので紹介しようと思います! 課題 まず前提としてカイポケリニューアルではLeSS *1 を採用しており、スプリントを回す各チームが協業することでプロダクトを前に進めています。 われわれのチームは主にフロントエンドの開発生産性向上をミッションに置き、メンバー2人で一通りのスクラムイベントを実施しています。 その中でリファインメントを実施して、プロダクトバックログアイテムをReadyな状態(Acceptance Criteria(以降、ACと略します)が合意されており、Storypointが振られている)に持っていけるようにしています。 リファインメントで合意した内容は極力チケットに記載するようにはしてはいたんですが、気をつけていてもどうしても漏れは出てしまうのと、チケットに議論した内容をできるだけもれなく記載していくのもコストが掛かってしまっていました。特に2人チームということもあり、議論に参加していない暇な人が書くといったこともしづらいので議事録を丁寧に取っていると議論のスピードが落ちやすく、省力化して運用できると良いなーと思っていました。 結果、「どこかで会話したはずだけどチケットには残ってない」といった内容もたまに発生していました。 解決策 思いつきでリファインメントの文字起こしをそのまま食わせてMCP(Model Context Protocol)を介してJiraチケットにこういった情報を書くのを丸投げしちゃえば良いんじゃね?という話になりました。 文章をサマライズするのは、文字起こしの不安定さを考慮してもLLMにやらせるタスクとしては十分難易度が低いですし、コンテキストサイズも過度に枯渇する内容ではないので行けそうだよねとなり実施しました。 議事録(サマリー)ではなく文字起こしを使う われわれはSlackのhuddleでリファインメントを実施しているのでhuddleを使っていますが、huddleに限らずだいたいのMTGツールの議事録機能には 人が読んで読みやすいように良い感じにサマライズしてまとめられる議事録 正確な情報を追うためにサマライズされていないプレーンな文字起こし の2つが付いていることが多いと思います。 議事録はさっと見るには嬉しいですが かなり情報が削がれてしまっている 言ってない内容でサマライズされていることが多く信頼性に欠ける(huddle議事録に対する個人的な印象) といった点で文字起こしの方がおすすめです。 Atlassian MCPとの連携 文字起こしはコピペして食わせるだけなので、あとはその文字起こしの内容からチケットを更新できる必要があります。 われわれはプロダクトバックログの管理にJiraを利用しているため、Atlassianから提供されている公式のRemote MCPを利用しています。 OAuthに対応しているのでAPI Keyなどの発行も不要で使いやすいです。 { " mcpServers ": { " type ": " sse ", " url ": " https://mcp.atlassian.com/v1/sse " } } これを繋いでOAuthの承認だけ行えばClaude CodeでJiraチケットを探したりアップデートしたりができるようになります。 コマンド化 道具は揃ったのでコマンド化して再利用できるようにします。 以下の内容を .claude/commands/update-jira-from-minutes.md として保存します。 --- description: '議事録からJiraチケットを自動検出し、内容を要約してチケット情報を更新する' allowed-tools: mcp __ atlassian __ searchJiraIssuesUsingJql, mcp __ atlassian __ getJiraIssue, mcp __ atlassian __ editJiraIssue, AskUserQuestion --- You will analyze meeting minutes provided by the user, automatically detect Jira tickets mentioned in the text, and update those tickets with relevant information from the minutes. ## Process ### 1. Extract Jira Ticket References Scan the meeting minutes text for < PROJECT > ticket references in the format: - ` <PROJECT>-XXX ` (direct mention) - URLs containing ` /browse/<PROJECT>-XXX ` - Any other patterns that clearly reference < PROJECT > tickets Create a comprehensive list of all unique ticket IDs found. ### 2. Retrieve Current Ticket Information For each detected ticket: - Use ` mcp__atlassian__getJiraIssue ` with cloudId: ` https://<domain>.atlassian.net ` to fetch current ticket details - Retrieve: summary, description, status, issuetype, parent (epic), and subtasks if any - If a ticket has subtasks, also retrieve their information ### 3. Analyze Meeting Minutes Content For each ticket found: - Extract all relevant information from the minutes that relates to this ticket - Identify: - Technical decisions made - Implementation approaches discussed - Concerns or blockers mentioned - Dependencies on other tickets - Timeline or priority changes - Any acceptance criteria or requirements clarified ### 4. Generate Update Proposal For each ticket, prepare: - **Updated Description**: Enhance the existing description with information from the minutes - Preserve existing content structure (Why, Acceptance Criteria, Note sections) - Add new sections if needed (Implementation Plan, Technical Background, Dependencies, etc.) - Use markdown formatting - Write in Japanese - **Epic Assignment**: If the discussion indicates this ticket should belong to an epic, identify the epic ID ### 5. Present Updates for Confirmation Use ` AskUserQuestion ` to present all proposed updates: - Show ticket ID and summary - Display both the current description and proposed description in full - Show epic assignment changes if any - Ask user to confirm which tickets should be updated Format the question clearly: \`\`\` 以下のチケットを更新します。内容を確認してください: [Ticket ID]-[Summary] 現在のDescription: ... 更新後のDescription: ... Epic:[現在]→[更新後] 更新を実行しますか? \`\`\` Provide options: - "すべて更新" (Update all) - "選択して更新" (Select which to update) - "キャンセル" (Cancel) ### 6. Execute Updates Based on user confirmation: - Use ` mcp__atlassian__editJiraIssue ` to update each approved ticket - Update the ` description ` field with the enhanced content - If epic assignment is needed, update the ` parent ` field with the epic ID - Confirm successful updates ## Important Notes - **Cloud ID**: Always use ` https://<domain>.atlassian.net ` as the cloudId - **Project**: Only process < PROJECT > tickets - **Language**: Write all descriptions in Japanese - **Preserve Information**: Never remove existing information; only enhance and add - **Structure**: Maintain markdown formatting and section structure - **Accuracy**: Only include information that is clearly stated in the minutes - **Parent/Epic Format**: When setting parent/epic, use the format: ` {"key": "<PROJECT>-XXX"} ` ## Error Handling If any ticket cannot be retrieved or updated: - Note the error - Continue processing other tickets - Report all errors at the end 一応更新前に承認を行ってから反映するようにしています。 結果 このワークフローを使い始めてから非常に運用が楽になりました。 リファインメントではACの合意は大事なのでリアルタイムに会話し記載をします。それ以外の情報は基本書いてませんが、リファインメント直後に上記のコマンドを実行して文字起こしの全文を渡します。あとは承認だけして終わりです。 Claude CodeがACだけでなく どういう議論をしてそういう結論になったのか 気をつけるべきポイントはどこか 実装時に考慮すべきポイントはどこか といったリファインメントで議論された情報がちゃんと書かれています。 しかも、書かれた情報を読み返してみても議論した内容がかなり正確に整理されて記載されており、違和感のある内容や嘘が書かれていることがほぼほぼないので非常に上手くワークしていると感じます。 リファインメントを実施したら必ず実行する形で定着しているので、今後可能であれば自動で上記のワークフローが実行されるようにできると良いなと思っています。 まとめ リファインメント議事録を元にClaude Codeを使ってコストをかけずにJiraチケットをちゃんとメンテナンスする手法を紹介しました。 特にわれわれはメンバーが2人なのでかっちりと決め切るより緩く運用している側面もあり、そこにすごく刺さりました。少人数チームだとこういった雑務に割く時間のコスパも悪いので省力化してチケットの質もあがって良いことづくめでした。 皆さんのチームでも良ければお試しください!めっちゃおすすめです。 *1 : Large-Scale Scrum。スクラムを拡張したフレームワークで、単一のプロジェクトについて複数のスクラムチームが連携して協業します。
アバター
こんにちは!カイポケリニューアルのケア領域でPO *1 を務めている岩下です。 育休から復帰し、仕事と家事・育児の両立に奮闘する中で、「AIの活用」はもはや必須の仕事術となりました。 この記事では、そんな私がAIを仕事でどのように活用しているのか、具体例を交えてご紹介します。基本的な活用法が中心ですが、もしかしたら皆さんの日々の業務のヒントになるかもしれない――そんな思いで書いてみたので、ぜひ、最後までお付き合いください! プロダクトマネージャーはAIをどう使っている? 私のAI活用はまだ手探り状態ではありますが、実践例として特に以下の5つの方法を紹介します。 1. PRD(プロダクト要求定義)の叩き台作成 PRD(プロダクト要求定義)とは、「何のために、どのようなプロダクト(機能)を作るのか」を開発チームやビジネス関係者に共有するための設計図となる文書です。これから開発する機能のWhyやWhat、Howを言語化していくものです。 私は Gemini をPRDの叩き台作成に活用しています。 具体的には、社内で使用しているesa *2 のテンプレート(14項目)を直接貼り付けてインプットし、そのフォーマットに沿ってPRDの初稿を作成してもらいます。特に便利なのは、 会社のGoogleドライブを情報源としてPRDを作成してくれる 点です。 例えば、情報ソースの取得元として【既存のプロダクトについてのVoC(Voice of Customer)をまとめたスプレッドシートURL】や【介護保険制度についての最新情報を集約している共有フォルダ】をプロンプトに入れておきます。これにより、社内の既存ドキュメントや最新情報を参照した、より精度の高いPRDを迅速に作成できます。 これを叩き台に、詳細を追記、あるいは内容に誤りがあれば修正していくことで、短時間で80点くらいのPRDができあがります。 ちなみに、出来上がったPRDは社内の運用ルールとしてリスマネ観点でもレビューをしており、こちらもAIによるチェックができるようになっています。 ※ Geminiの「Deep Research」機能は社内で活用が推進されているものではありますが、ソースが一般的なWeb情報になるため、会社のGoogleドライブに格納された情報の信頼性の方が高く、私の業務ではあまり使用していませんでした。が、2025/11から「Deep Research」機能がアップデートされ、Gmailやドライブ(ドキュメント、スライド、スプレッドシート、PDF等)、Google Chatを情報ソースとして直接指定できるようになりました。今後は「Deep Research」も活用していくようになると思います! 2. 開発チェックリストの作成 開発者とのコミュニケーションを円滑に進めるため、 Gemini で開発のチェックリストを作成しています。例えば、新規のスマホ対応機能についてエンジニアと話す前にGeminiへ依頼して「スマホ向けの開発で考慮が必要なこと」のチェック項目をリストアップしてもらったことがありました。これにより、自身の知識レベルを上げ、より質の高い議論の準備ができます。 ▼具体例)「スマホ向け開発」での考慮ポイントのチェックリスト(経験者には当たり前のことかもしれませんが、”非機能要件”の洗い出し等、自分が経験したことがない分野についての開発前にはとても助かりました) 生成されたチェックリスト 3. 優先度判断のための議論深化 プロダクト開発における優先度判断は常に難しい課題です。そんなシーンではGeminiへのプロンプトを工夫し、議論を深めています。詳細なシナリオと登場人物の設定を与えて討論させることで、単なる質疑応答を超えた、思考のシミュレーションツールとして活用するイメージです!複数の選択肢やトレードオフについてGeminiの結論を参考にすることで、より客観的かつ多角的な視点から優先度を判断できるようになります。(あくまで最終的な判断をするのは自分です) 具体的には、議論を深める役割としてプロダクトマネジメントトライアングル的に3者(顧客・開発者・ビジネス)を設定し、それぞれの観点を用いて議論を展開するようにプロンプトを書き、話を深めてもらっています。(また、これはまだやったことはありませんが、役割に加えて「警戒派」「楽観派」「現実派」といったスタンスを与えることでも検討内容は深まるようです。) ▼具体例)訪問記録の開発ロードマップについて「思考モード」を使って3者の立場から議論を深めてもらいました プロンプトの入力例 この時には、上のようにプロンプトを入力するとそれぞれの立場からの意見と議論が展開された後、以下のようにMSP *3 の構成が提案されました。更に、これを元にしたユーザーストーリーやロードマップの提案も続けて行われました。 Geminiからの返答(一部抜粋) 意思決定からくる行動・アクションは「やる・やらない」の2択であるにも関わらず、プロセス(HOWや登り方)は基本的にはグラデーションになっているので、優先度判断には多角的に判断軸(私がしているように3者の観点を踏まえるなど)を持って思考することが求められます。上記の「討論させてみる」手法は、そのためのインプットとして使えると思います! 4. 議事録のポッドキャスト化と情報収集 情報量が多く、内容が複雑な会議の議事録は、後から読み返すのが大変ですよね。私はこの課題を NotebookLM で解決しています。 議事録を読み込ませて音声化するプロセス NotebookLMの魅力は、読み込ませたドキュメントを基に、その内容を要約したり、質問に答えたり、そして「音声」として再生できる点です。 資料の用意とアップロード: 普段利用しているesaや議事録をPDFやGoogleドキュメントとして用意し、NotebookLMのソースとして読み込ませます。 音声生成と再生: 読み込み後、NotebookLMの機能(「音声解説」ボタン一つで実行)を使って、平文のドキュメントをポッドキャスト風の音声に変換します。 音声化のメリット:対話形式で情報がスッと入る この機能のすごいところは、ただテキストを機械的に読み上げるだけでなく、対話形式やストーリー形式で音声を作成してくれる点です。 通勤中の「ながら」学習:生成された音声をラジオのようにイヤホンで聞くことで、通勤中や家事の合間など、移動時間やスキマ時間を有効活用できます。 理解度の向上:単調なテキストを読むよりも、対話形式の音声の方が頭に入ってきやすく、内容のキャッチアップ効率が格段に上がりました。 現在はmiroやesaの情報をPDF化してNotebookLMに読み込ませる工程に少し時間がかかるのが課題ですが、この「ながら学習」のメリットがそれを補って余りあると感じています。 5. Vercel v0によるUXの不確実性削減への挑戦 最近、Vercelのv0というAIコーディングツールの活用を始めました。 v0は、「シンプルなログインフォーム」「和風デザインのランディングページ」といった自然言語でのプロンプトを入力するだけで、 WebページのUIデザインやコード(React/Next.js/Tailwind CSSベース)を自動で生成 してくれるツールです。Webデザインやプログラミングの知識がなくても、 数秒でデザインの叩き台やプロトタイプを作れる ため、アイディアを素早く可視化できます。 PRDで言語化された要求定義だけでは、しばしばUX(ユーザー体験)の部分の不確実性が残りがちです。v0で簡単なプロトタイプやUIコンポーネントを素早く作成し、それをベースにチームと会話することで、開発に入る前の早い段階で認識のズレを解消し、不確実性を減らせると期待しています。まだ試行段階ですが、他チームのPOの間でも活用が広がっていて、私自身もその効果を楽しみにしています! ▼具体例)「スマホの訪問記録向け開発」で、画面左側にある吹き出しで「ここをこうして欲しい」のコメントをすると、右側にあるような画面が秒で出来上がります。 v0で生成されたプロトタイプの例 まとめ プロダクトマネージャー業に携わる人がAIを業務に取り入れると、手軽に周辺領域の知識を習得できるようになったり、より質の良い意思決定ができるようになるため、数々の業務を効率的かつスムーズに進めていけるようになります。また更に一歩踏み込んで、v0のようなツールを通じてUXの早期検証を行えば、業務領域を広げる形でアウトプットの質を高めることも可能です!今回ご紹介した事例が、皆さんの日々の業務に少しでも役立てば幸いです。 皆さんはどのようなAI活用術をお持ちですか?ぜひ教えてください! *1 : プロダクトオーナー;プロダクトマネージャーの役割の一部として、プロダクトの価値を最大化する責任を負うポジションです *2 : チーム・組織内の情報共有のためのドキュメントサービスです *3 : Minimum Sellable Product;販売可能な価値を持つ最小限のプロダクトのこと
アバター
これは 株式会社エス・エム・エス Advent Calendar 2025 の12月25日の記事です。 カイポケリニューアルプロジェクトでエンジニアリングマネージャーをしている荒巻( @hotpepsi )です。 今年の11月に LeSS' Yoaké 2025 というカンファレンスに参加したり、 認定LeSS実践者研修 を受けてきました。これらのイベントや研修では、学習の重要性や、調整役を置かないことの意義が語られていて印象に残ったので、それについて書いてみます。 スクラムとLeSSの関係 以下の話の前提として、LeSSについて少し補足します。 LeSS (Large-Scale Scrum) は大規模スクラムを謳うフレームワークで、単一のバックログと一人のプロダクトオーナー、複数のチームからなります。 LeSSはスクラムである と宣言しており、基本的にはスクラムの原則がそのまま適用されます。各チームは小さく、クロスファンクショナルなフィーチャーチームであることが望ましいです。 ※ 用語について クロスファンクショナル 一般的には、チーム内に複数の職能の人たちがいる状態。LeSSの文脈では、チームに求められるスキルを持っているか、習得可能であること フィーチャーチーム 顧客中心の視点を持ち、価値のある機能(フィーチャー)を届けるための全ての工程に関わり、完成させることに責任を持つチーム。必然的にクロスファンクショナルであることが求められる。対義語は、特定領域を担当する「コンポーネントチーム」 LeSSでの学習とは LeSSは、人は学習可能であるという前提に立ち、実験・学習・考察に振り切ったフレームワークとなっています。 研修では、繰り返し学習の重要性が語られました。ここでの学習は、チームの能力を高める全ての活動のことです。技術領域を広げたり業務ドメインを理解するなどの開発業務に関わる学習だけでなく、顧客インタビューや価値検証のような、要求分析の領域なども含まれます。すなわちLeSSが想定する組織学習とは、学習して影響範囲を広げていくことで、全体に対してオーナーシップを発揮して価値提供していくというものになります。 スクラムと全体最適 スクラムガイド ではスクラムの外の世界についてあまり語られていません。一方、「 大規模スクラム Large-Scale Scrum(LeSS) 」によると、LeSSでは「プロダクトが使われる現場」と「プロダクトが開発される現場」の2つの現場があるとしていて、チームの外側にも明確に意識が向いています。 一般に、プロダクトが成長して人が増えるとチームも増えていき、役割分担が生まれて境界ができます。そして次第に守備範囲が狭くなり、徐々に「プロダクトが使われる現場」から遠ざかり局所最適化されていきます。これに対してLeSSでは個人の学習能力に期待することで、自然な力学に逆らい、全体最適を目指します。ここで顧客価値とは「プロダクトが使われる現場」の価値であって、スクラムが注力することそのものです。なので全体最適に向かうというLeSSの原則は、スクラムと矛盾するものではなく、スクラムの明確化の一形態として解釈できるのかなと思いました。 LeSSそのものを学ぶ 開発者が学習の重要性を意識するためには、土台となる原則の理解が必要になります。研修ではいくつかのワークショップがあり、それを通じて、原則の理解や共感というのは、スクラムマスターがただ内容を紹介するだけで身につくものではなさそうだという感覚も得られました。 システム思考など実験・分析・改善活動などを通じて、開発者自身が議論し思考し、原則がどのように作用するのかを実感し記憶することで、原則とプラクティスが相互に結びつくと感じました。 調整役を置かないこと LeSSではなるべく調整役を置かないことも推奨されています。ここでの調整役は、プロダクトオーナーやフィーチャーチーム以外の様々な役割のことです。例えば設計を専門的に行う人やチームが存在すると、調整コストや待ちの要因となります。そうした依存関係は仕事のパイプライン化を生み出し、スプリント内で作業を完結させることを困難にします。また、チームから見ると調整役が学習機会を奪っていることにもなります。そのためLeSSでは、チームの外部に存在する役割=調整役がいないのが理想的です。 LeSSを導入してからの現在地 ここからは我々のLeSSの状況についてお伝えしたいと思います。カイポケリニューアルプロジェクトでは、チーム数が徐々に増え、複数のプロダクトオーナーと複数の職能別チームが存在する状況で、昨年LeSSを導入しました。 そこから徐々に、職能別チームからフィーチャーチームへ再編成し、少しずつLeSSの形に近づいている状況です。LeSS導入の少し前には、プロジェクトリードというチーム間のハブとなる役割を廃止したことがありましたが、これはまさに調整役を減らす活動だったのだと意義が再認識できました。 このあたりの導入による変化については、プロダクトオーナーやチームメンバーの声を座談会形式で記事化しています。 私自身はLeSSのマネージャーとしても活動しています。調整役にならないようにしたいなと思っています。 現在地としては、全体の半分くらいのチームがLeSSを実践しており、それ以外にも特定領域を担当するチームがいくつか存在します。またLeSS内部でも、プロダクトバックログ全体を管理するプロダクトオーナーの他に、特定ドメインのプロダクトバックログアイテムに責任を持つプロダクトマネージャーもいます。そうした状況で、このままLeSSを拡大していくのか、それともLeSS Hugeに向かうのか、などの議論も生まれたりしています。今後も、業務ドメインが複雑であるというプロダクトの特性を考慮しながら、どのようなLeSSに向かうのかを議論し実験していきたいと思っています。 LeSSの実践に興味のある方は、ぜひカジュアル面談でお話しましょう。以下のリンクからお気軽にお申し込みください。
アバター
はじめに こんにちは!カイポケコネクトの開発推進チームでエンジニアをしている @_kimuson です。 私たちのチームは、ストリームアラインドチーム *1 の生産性向上をミッションに持つチームで、その文脈で生成AIの活用にも取り組んでいます。 LLMを使った開発が当たり前になってきて、実際に生産性が上がる側面を実感している一方で、できることが多すぎるが故に夢が広がりつつも、現実では壁も多くて難しいポイントが多いのも事実です。人間の作業がボトルネックになってしまい、思ったほど生産性が上がらないという課題にもぶつかります。 このエントリでは、生産性向上をゴールに、AIをどう活用していくのが良いかの整理と、私たちのチームで実際にどういうコンセプトでAI活用を進めているのかを紹介したいと思います。 LLMへの移譲レベルを定義する AIエージェントを活用して仕事をするということは、仕事の一部をLLMに権限移譲するということです。 したがってLLMとの責務の境界を明確に意識しておくと良いだろうなと思っています。 ここでは、チーム開発でよく使われる Delegation Poker *2 の移譲レベルの分類を借りて、人間とAIエージェント間の責務の移譲レベルを分類してみます。 よくあるLLMの利活用シーンと対応する移譲レベルは下記のように対応します: 流れ 移譲レベル 主体 ChatGPTに設計について相談し、自分で実装 Consult 人間 Copilotの補完を使いながら、自分で実装 Consult 人間 細かい指示を出しながらエージェントが実装。Driver がエージェントなモブプロ Agree 人間 & LLM LLM にチケットを渡してPRまで作ってもらう Inquire LLM LLM にチケットを渡してPRを作ってもらい、LLMによるレビューでマージまで完結 Delegate LLM ConsultレベルではあくまでLLMに相談をするだけなので主体は常にわれわれ人間です。AgreeレベルになることでLLMも共同で責務を持ちますが、われわれ人間も一緒に作業をすることになります。 この辺りまでの活用が現実的に多く行われている・採用されやすい移譲レベルだと思います。 そこからさらに移譲レベルを上げていくと 最終レビューはするけどタスクの主体はLLMに移譲されているInquireレベル さらにはわれわれ人間による承認さえ不要なDelegateレベル に到達することなります。 移譲レベルがなぜ重要か まず大前提として、コーディングを初めとしてタスクの作業時間はわれわれ人間よりLLMの方が圧倒的に早いです。物理的な速度もそうですし、LLMは並列で作業を進めることも可能です。 生産性を上げるには、ボトルネックを解消していくことが大切であり、作業がLLMのほうが高速だからこそ移譲レベルを上げていくことでボトルネックが解消し、移り変わっていきます。 Consult / Agree レベル : 人間の作業時間がボトルネック 人間が必ずつきっきりで作業する必要があるため Inquire レベル : 人間の認知負荷がボトルネック 人間が変更内容を認知し、責任を持ってマージする必要があるため Delegate レベル : 人間のチケット生成速度がボトルネック 理論上すべてがDelegateなら、お金が許す限り24時間開発を進められるが、チケットの方が足りなくなるはず つまり、生産性を上げるには、LLMへの移譲レベルをいかに上げていくかがポイントになりそうです。 移譲レベルをどう上げていくか アウトプット(アウトカムではない)の生産性だけを考えた場合、理想論で言うと、人間はボトルネックにしかならないのですべてのタスクをDelegateレベルで進めるのがベストです。 とはいえ、現実的には クオリティコントロール 方向性の維持 アウトカムにならない といった側面を考えると、すべてを丸投げするのは無理があります(できたらシンギュラリティですね)。 現実的には、 開発タスクの中で移譲レベルを上げやすいタスクを移譲していき、手離れさせた時間でエンジニアがより難しい問題にAgree/Consultレベルで協業することで生産性を高める のが良いと思っています。 より具体的には 主要なエンジニアのタスクを分類する 丸投げビリティ *3 を評価し、どのタスクをどの移譲レベルに持っていけるか評価する DelegateやInquireレベルに移せるタスクを移譲していく という流れで方針を作っています。 丸投げビリティという指標 LLMに丸投げしやすいタスクを定性的にでも評価できないと、どのタスクを移譲すべきか判断しづらいです。 個人的には、以下のような観点で評価できそうだと考えています。 最悪壊れても良いか ワークフロー設計でアウトプットを安定させるのは前提としても、LLMなので(まあ人間でも同じですが)一定のミスは許容せざるを得ません。それがどの程度許容できるかという観点です。 例えば、CIの設定ならまあ壊れるときもあるよね、という許容度があると思います。一方で、本番機能のコアロジックだとそうはいきません。 LLMでどれだけ期待値ラインに近いアウトプットを出せるか ふわっとしていますが、そもそもある程度見て「よし!」となるアウトプットが出づらいなら最初からAgreeでやれば良い、という話です。 個人的にLLMが解けるかはコンテキストがすべてだと思っています。暗黙知でコンテキストに含められない、デカすぎて含められない、といった制約がなければ、情報の集め方やワークフロー設計を適切にチューニングすればある程度どうとでもなる気はします。 チーム内での相対的な指標としては Storypoint が使えるかもしれません。ただし、Storypointはチーム固有の基準なので、丸投げビリティの評価には定性的な観点も併せて考慮する必要があります。 その作業内容が自動化されることでどれだけ嬉しいか 一般的な自動化と同じ観点です。例えば年1でしか発生しないタスクに適用していきますか?という話ですね。コマンドの整備などにもコストがかかりますから。 私たちはこの考え方で評価をしていますが、「どういうタスクがLLMに丸投げできるか」の評価が行えていれば手段はなんでも良いと思っています。 例えば下記の記事では「生成されたコードを自分が読んでいない割合」が基準になるという考え方が紹介されています。読んでない=すでに移譲されている、ということなので丸投げできるよね、という話で、こういったシンプルな考え方もアリだなと思います。 移譲レベルの上げ方 タスクごとの移譲レベルの評価ができたら、あとは実際にLLMの責務を増やしていく必要があるので少し具体に踏み込んで、実際にどうやって移譲レベルを上げていくのかを紹介します。 Agree から Inquire へ LLMへの移譲レベルを上げる=LLMに移譲する責務を増やす= コンテキスト分解が必須 、というのがAgreeからInquireへの移行のキモだと思っています。 コンテキストウィンドウの物理的なサイズももちろんありますが、それ以上に複数のことをやらせるとわかりやすく性能が落ちます。 つまり、 LLMでも単一責任の原則が重要 であり、複数の責務を持たせるならオーケストレーションが前提になります。 エージェントが別のプロンプト(=責務)を持つエージェントを呼び出せる必要があります。Claude CodeはTaskツールとサブエージェントによってこれをサポートしているので、サブエージェントとコマンドを作成してみました。 作業イメージとしてはこんな感じです: エンジニア: /inquire_xxx <チケットURL> の実装をして Claude Code(Orchestrator) use: オーケストレーションスキル Claude Code(context-collector): 情報収集 Claude Code(architect): 設計 Claude Code(engineer): TDD実装 Claude Code(reviewer): レビュー×N Claude Code(pr-creator): PR作成 & CIチェック Claude Code(QA): 動作検証 → レポート 単一の責務を持つエージェントが協業することで質の高いアウトプットは出やすくなりますが、手離れする以上、期待と異なる場合に止めることができないので渡せる難易度に限界はあります。 Inquire から Delegate へ Inquire時点で実装面は移譲されているので、Delegateへの壁は大きく2つあります。 GitHubにおけるマージブロックをどう適切なタスクでのみ回避するのか AIレビューでどう品質保証するか 前者は、Delegateレベルで移譲できる対象のソースコードに対してCODEOWNERSでオーナーをもたせることでエージェントによるレビュー及び承認・マージを行うことができます。 後者は、「エンジニアがコードレビューで担保している内容」を言語化する必要があります。加えてタスクごとに観点が異なるはず(機能実装とCIの管理では観点ぜんぜん違いますよね)なので、タスクごとに個別で考える必要があります。 例: Renovate の Delegate 化 直近ありがたみが大きそうで取り組んでいるのがRenovateの手離れです。 下記は実際には運用されていない叩きですが、例えば1例としてこういった確認及びLLMによるオートマージが考えられます。 0.x以外のpatchバージョンは変更内容をざっと読んで問題なければカジュアルにApprove minor updateや0.xアップデートはClaude CodeにBreaking Changesを中心に影響範囲を調査してもらい、影響がないかを確認 影響なし → Approve 影響あり → Inquireレベルで修正を行ってもらい、エンジニアのレビューにフォールバック Majorなどでもパッケージごとのルールで制御 Majorはデフォルトでは慎重に扱う 例えば開発環境で使うパッケージはpnpm buildが通ればOKとか、グループごとに確認すべき内容を言語化。通っていればマージまでOK アプリで使うパッケージでも、例えば副作用のないdate-fnsやes-toolkit等はCI通過を持って問題なしとできそう こんな感じの確認がClaude Codeによって行われれば、Approve & マージして良いのではないか、という考え方です。 ここはチームでの合意形成がタスクごとに必要となります。例えばRenovateは上記のようなガイドラインを持ってマージOKとしてよいか、といった議論ですね。 並列で Inquire 以上のタスクを行う仕組み Inquireレベルでゴリゴリのタスクを進めるのには一定の仕組みが必要です。 現状は私の開発マシンで1つのworktreeをClaude Code用に割り当て、手前味噌ですが私が作っている d-kimuson/claude-code-viewer *4 のスケジューラ機能(cron式にマッチする時間で自動でメッセージ送信)を使うことで完全に手離れしたところで軽いタスクを実行しています。 LLM タスクのリファインメント 上記で挙げた丸投げビリティ(定量的にはStorypoint、定性的にはACの充実度や内容)を言語化し、LLM自身に「やるべきタスク」「やらないべきタスク」を分類させる Claude Codeに実施させるべきかの最終チェックは一応したいので分類された情報をみて、承認したチケットをQueueに積む 自動実行 Queueから定期的にチケットを取り出してinquireレベルでClaude Codeを実行 まとめ LLMへの移譲レベルを整理し、私たちが現在取り組んでいるLLMによる開発生産性向上の考え方を紹介しました。 移譲レベル(Consult / Agree / Inquire / Delegate)を意識的に設計することで、エンジニアは本当に難しい問題・楽しめる問題に集中できるはずです。 実際に判断基準をより正確に言語化したり、一部のタスクをDelegateに移していくところはまだまだ私たちも取り組んでいる最中であり、チームでの合意形成を大事にしながら進めていきたいと思っています。 *1 : チームトポロジーで紹介されているチームの分類の1つで、ドメインに沿って継続的にプロダクト開発を行うチームがこれに該当します。 *2 : Management 3.0 で紹介されている管理者からチームへ権限移譲をするためのゲームです。権限を7つのレベルに分類し、移譲レベルの期待値をすり合わせます。この記事では人間からAIへの権限移譲の度合いを分類するために利用しています。 *3 : タスクをLLMに移譲しやすいかどうかを評価する指標。造語です *4 : Claude CodeのGUIクライアント
アバター
こんにちは、カイポケの開発組織責任者をしている酒井です。 今日は私の大切な仕事の1つである「エンジニア採用」について書きます。この記事は 株式会社エス・エム・エス Advent Calendar 2025 の23日目の記事です。 「意思決定をスケールするエンジニア」がなぜ重要なのか エス・エム・エスの事業領域は、制度・業務・環境が複雑に絡み合う変化の大きい「社会課題」という不確実性の高い領域です。プロダクト開発において重要なのは、こうした不確実性と向き合いながら、試行錯誤を通じてソフトウェア設計へと落とし込み続けられるかどうかだと考えています。 事前に立てた計画どおりに進めれば問題が解決するわけではなく、絶対的な正解が存在しない中で、状況に応じた判断を積み重ねながら物事を前に進めていく力が求められます。そのためには、特定の個人の力量に依存するのではなく、組織としてスキルやマインドセットをバランスよく身につけていく必要があります。 私たちが採用したいエンジニアは、単に経験が豊富な人という意味での「強い個人」ではありません。実装が速いことや難しい技術に詳しいことは前提条件の一部にすぎず、本当に重視しているのは、判断を引き受け、学習をチームに広げ、不確実性の中でも組織として前に進められる状態をつくれるかどうかです。 プロダクトや組織が成長するにつれ、意思決定は一部の人に集中しやすくなり、結果としてスピードを失ってしまうことがあります。そのボトルネックを解消するためには、強い個人を増やすこと以上に、判断と学習が分散する構造をつくることが重要だと考えています。 このような「意思決定をスケールするエンジニア」に対して、私たちは何を大切にし、採用の現場ではどのような点を見ているのかについて以降で整理していきます。 何のための「技術力」か 私たちの採用プロセスでも、他の多くの企業と同様に、ソフトウェアエンジニアの選考において「技術面接」を行っています。ここでは、過去のご経験を伺いながら現実の問題やビジネス上の課題をどのように捉え、ソフトウェアとしてどのように設計してきたのか、その難易度やスケール感などを理解させていただいています。 技術面接の結果は、能力を把握するうえで非常に重要な情報ではありますが、それだけで採用を決める十分条件とはしていません。特により高い期待役割を担っていただくポジションになるほど、技術的な正しさだけで問題を閉じてしまわず、周囲の思考を広げ、チーム全体の学習速度を高める関わり方ができるかどうかも含めて見ていきたいと考えています。 問題解決の能力 前述のとおり、私たちが向き合っている事業領域には常に高い不確実性が存在します。ときには、機能を開発する意味(WHY)そのものが明確に定義されていない状態で向き合うこともあります。 そのような状況において私たちが期待しているのは、「これはプロダクトオーナーが決めること」と線を引くことではなく、必要なコンテキストを集め、自分ごととして課題設定に関わりながら少しずつソフトウェア設計の材料をつくっていく姿勢です。 表層的に現れている問題だけを塞ぐのではなく、なぜその問題が起きているのかを考え、アーキテクチャや組織、プロセスといった複数の観点を行き来しながら構造的に課題を捉え直していくことが重要であり、そのための手段として「技術」を使っているエンジニアと一緒に仕事をしたいと私たちは考えています。 面接では「設計」の経験について多く伺いますが、一般論としての設計ではなく、向き合ったドメイン固有の不確実性に対してどのような設計判断を行ったのか、その設計が時間の経過とともにどのように変化していったのか、複数の選択肢の中からなぜその判断を選んだのか、何を守り、何を捨てたのかといった「判断の背景」を重視しています。 リーダーシップのあり方 技術力と同様に、採用においてはエンジニアとしての「リーダーシップのあり方」も大切にしています。 私自身は、強いリーダーシップに必ずしも肩書きは必要ないと考えています。権限の有無に関わらず、状況を理解し根本的な課題を設定しながら物事を前に進めていく力や、意見の対立を恐れずに状況を整理し、挑戦の道筋を描いていく力こそが重要だと考えています。 これまで多くの面接を担当してきた中で、複雑な問題解決を経験してきたエンジニアの多くは、再現性のある「技術以外の能力」を併せ持っていると感じることが多くありました。特に、社会的背景や業界特性、ビジネス上の文脈といったコンテキストを理解しようとする姿勢が強く、「なぜ(WHY)、このタイミングで(WHEN)、誰のために(WHO)、この問題を(WHAT)、解く必要があるのか」といった点について、解像度の高い説明をしていただけることが多い印象です。 また、そのようなリーダーシップを発揮する方ほど、他者や状況からのフィードバックに対しても柔軟であるように感じます。他者からの指摘や、判断の結果としてユーザーから寄せられた意見に対して、防御的になるのではなく自分の前提を疑い問題を捉え直そうとする姿勢を持っているかどうかを大切に見ています。 面接の場では、私自身が理解しきれなかった点や違和感を覚えた点については率直に質問するようにしています。もちろん、意図的に揚げ足を取るような聞き方をすることはありませんが、お互いの考えをすり合わせるための対話として、その背景や判断理由を丁寧に伺うようにしています。 制約が強い環境であること 問題解決力やリーダーシップに加えて、外部環境による制約と向き合いながらエンジニアリングを行ってきた経験があるかどうかも、大切な観点の1つです。 法制度や業界特有の商習慣、リソースや時間の制約、大規模で複雑なソフトウェアといった前提条件の中で、理想を理解しつつも現実的な着地点を見出してきた経験やその中で「将来変えられるもの」と「変えられないもの」をどのように切り分けて考えてきたのかは、必ず伺うようにしています。 介護業界においては、複雑な法制度が顧客業務にとって大きな制約となり、同時にソフトウェア設計にも強く影響します。これらを無視することはできない中で、何を選び、何を捨てるのかという判断を積み重ねてきた経験は、私たちが特に大切にしたいポイントです。 頭数で解決しない採用 私たちは、複雑で不確実性の高い社会課題に向き合いながらソフトウェア開発を行っています。もちろん、ここまでに書いたすべての能力や経験を一人のエンジニアに求めているわけではありません。 採用において私が大切にしているのは、人数を目標にして「強い個人」を集めることではなく、未来の「強いチーム」をつくるために必要な視点や役割を持った方と出会うことです。 選考の場で多くのエンジニアの方と対話する中で、私自身も日々多くの学びを得ています。これまでエス・エム・エスの採用に関わってくださったすべての方に感謝しつつ、これからも、組織の未来を一緒につくっていける方との出会いを大切にしていきたいと考えています。 引き続き、カジュアル面談や選考へのご応募をお待ちしています。
アバター
この記事は 株式会社エス・エム・エス Advent Calendar 2025 の22日目の記事です。 はじめに こんにちは! ウェルミージョブ 、 シカトル 、 カイゴジョブアカデミー でエンジニアリングマネージャーとプロダクトマネージャーを担当している 豊濱 です。 自己紹介は先日の記事をご覧ください。 tech.bm-sms.co.jp 今回は「作らない技術」についてお話します。 ものづくりに関わるコストや時間が大きく減らせる時代になったからこそ、こういった視点を持って取り組むことで本質的な課題に取り組めるのでは、と考えています。 いつもやっていること プロダクト開発に関わっている方は、細かい違いはあれど概ね以下の流れで進めていることが多いのではと思います。 現状や市場、顧客の分析 達成したい目的はなにか 提供できる価値はなにか プロダクトとして提供すべきものはなにか 設計、開発、テスト、リリース 検証 1〜3番あたりに戻り、発展させる ものづくりをするために、感覚や定性ではなく、目的や価値を明確にして、チーム全体で同じ目線を持って取り組むのはとてもいいことです。 ただそれを検証するために「なにかものを作らないといけない」という流れが当然になっていないでしょうか。 なにかを作るには人件費やインフラ・アプリケーションの構築、リリースをするのであれば運用やメンテナンス・顧客対応、そしてそれらをこなすための「時間」など、投資が必ず必要になってきます。そこへ至る前にまだやれることがたくさんあるのでは、と考えています。 作らないための工夫 いまあるものの組み合わせや見直しでどうにかする 社内ツールの使いづらさを解消したい リードタイムを短くしたい 人的リソースを最適化したい 複雑なフローをなくしたい こういった目的や成果をもって進めることはよくあります。 新しいプロダクトで価値提供するのが適切な場合もありますが、いまあるフローをガラッと変えるのも大きなコストと時間がかかってしまいます。 いまの複雑なフローを全く新しいシンプルなものにする →いまのフローのなかで、待ちが発生している部分を並列化する 複数ある管理画面を1つに集約する →複数の管理画面は変えず、まずはデータの連携を人手(コピペ)でやっている部分を自動化する といったところから入るのも次に進める大きな一歩だと考えています。 現状の理解や可視化に時間がかかってしまうのでは、という見方もありますが、なぜそれが存在しているのか?を確認することは新しくプロダクトを作るにしても必要なプロセスなので、全く無駄になるとはあまり考えていません。 そのプロダクト・業務をなくしたらどうなるのか もはや、作らないどころか作ってあるものをなくす話になっていますが、いまあるものが本当に必要なのか、という検証もやっていくべきです。 なくすことで失われる価値はなんなのか 維持していくための投資を鑑みるとどうなるのか 新しくプロダクトを作って移行させるコストとのトレードオフはどうなのか 実はなくすだけで、目的が達成できるのではないか もちろん、単なる投資対効果やトレードオフだけで判断できるわけではありませんが、発想として「なくしてみる」も考えていくのが大事だなと思います。 まとめ さまざまな技術ソリューション・近年の生成AI・IDEに代表されるツールによって、10年前に比べて作るハードルは圧倒的に下がり、作りながら試行していいものに発展させていく、というのがスタンダードな時代になってきました。 そんな時代だからこそ、「いかに作らずに成果を出せるか」みたいな視点を持っておくのも1つの「技術」なんじゃないかなと考えて、こんなタイトルをつけてみました。
アバター
この記事は 株式会社エス・エム・エス Advent Calendar 2025 の19日目の記事です。 こんにちは。エス・エム・エスの人材紹介開発グループでマネージャーをしている @kenjiszk です。今回はカジュアルな私の悩みについて書いてみたいと思います。 コードを書かないマネージャー 業務でコードを書くかどうかはマネジメントしている領域や組織規模に寄ると思いますが、私は現在、業務において「コードを書かないマネージャー」というスタイルをとっています。コードを書くことが嫌いではないですし、実際にコードを書くことで手触り感を持ってプロダクトを扱うことは非常に好きです。ただし、マネジメントという観点において以下の理由でコードを書くことをしていません。 マネジメント業務に関わらず、仕事はレバレッジが効くポイントに力を割くことが良いと考えている 残念ながらスーパーエンジニアではない私は自分がコードを書くことで効かせられるレバレッジよりも、マネジメント業務によって事業に与えられるレバレッジの方が大きいと感じている むしろ、今の状態で中途半端にコードを書き始めると2種類のボトルネックを発生させる 1つ目は、兼務としてコードを書くので、おそらく実装速度やクオリティは高くない、大きなissueは扱えない 2つ目は、コードを書くことに時間を使うことで、本来するべき意思決定や戦略づくりなどに時間が割けなくなる ということで、意図的にコードを書いていないし、それが良いと思っています。 生成AIの登場 ただ、この状況は自分が過去にコードを書いたことがあるから成立しているとも言えます。また、SREやらマイクロサービスの運用やらもやっていた経験があるのでシステム全体を通してある程度理解できるという前提があり成立していそうです。 そんなこんなでまあなんとかやっているわけですが、最近はこんな事をよく口にしているなあと思いました。 「その辺はAIに書かせちゃっても大丈夫そうですね」 「この処理はAIが得意そうなんで書かせちゃいましょうか」 「AIでコード書くの当たり前になってきてますねー」 そして気づきました、はて、私はAIを活用してコードを書いたことあったっけ?、と。 コードを書かないマネージャーはどこでAIを使ってコードを書く? やったことがないならやってみればいいのですが、私はコードを書く業務をしていないのでAIを活用したコーディングを試す場所がありません、どうしよう。 無理やり業務の中にコーディングのタスクをねじ込んでも、前述のように自分がボトルネックになることは目に見えています。じっくり腰を据えて取り組めば良いのでしょうが、色々な仕込みをしている最中でもあって、そっちはそっちで時間を割かないといけない状況でした。 であればもう個人開発しかなさそうです。 以前は個人開発をしていたこともあったのですが、なかなか継続的にコードを書く時間が確保できるわけでもないのでここ数年は断念していました。AIがコードを書いてくれるなら時間をそこまで使わなくても何か作れそうなので個人開発を再開してみることにしました。 このブログで伝えたいことの主題ではないのでアプリの機能や中身については触れませんが、Flutter + Firebaseという構成でシンプルなiOSアプリを作ってみました。デザインも含めて実装していて、なんとなくの雰囲気は以下のようなものになります。 やって良かったvibe coding AIによるコーディングについてはあらゆるところでその効果が語られていますので改めてここでまとめるつもりはないですが、実際に手を動かしてやってみたことは価値があったなと思います。 まず当たり前の話ですが、圧倒的に便利でした(笑)。 first commitが2025/11/6ですが、そこから週に数時間空いた時間を使う程度で約1か月で概ねの機能を作り終えることができました。シンプルな構成とはいえ、Firebaseによる認証周り、Firestoreへのデータ保存、トップページから複数ページへの遷移など、一通りの機能を持つアプリです。これらが予想外に簡単に作れてしまいました。 個人的に特に便利だと感じたのは、実現したい機能の概要を伝えると、それに適したライブラリの選定から提案してくれる点です。最新の言語・フレームワーク事情をそこまでキャッチアップできていない身としては、この辺りの調査の時間を大幅にショートカットできるのは非常に助かりました。 AI開発は領域によって、能力のスケールアウトをしてくれる場合とスケールアップをしてくれる場合があるということもわかりました(スケールアウト/スケールアップは本来インフラ用語で、それぞれ「台数を増やして処理能力を上げる」「単体の性能を上げる」という意味です。ここでは「自分と同じ能力を持つ分身を増やす」「自分にない専門能力を獲得する」という意味で使っています)。 自分が得意な領域や実現方法を詳しく知っている箇所では能力をスケールアウトしてくれる感覚になります。私の場合は、Flutter + Firebaseという構成でアプリを作ることは何度か経験があるので、この部分は自分が出来ることを代わり単に高速でコーディングをしてくれているという感覚でした。自分がもう一人いる感覚です。 一方で、デザインだったりアプリのアイコンの作成というところでは、私は専門家ではないのでなんとなくのイメージを伝えるだけで、それなりのクオリティのものを作り出してくれました。ここは確実に能力のスケールアップがされている実感があり、AIを使っていて一番テンションが上がるポイントでした。これは自分がもう一人いるというよりは、専門家が自分のために働いてくれているという感覚でした。 別の観点として、自分で手を動かすにはちょっとテンションが上がらないけどやらないといけないタスクは全部AIがやってくれるのはとても良く、私の場合は、ストアにアプリを出すための設定やストアに表示する魅力的な文言作成は全然やる気が出ないのでこの辺りを全部任せられるのはとてもありがたいなと思いました。これは文句を言わない秘書的な感覚です。 ちなみに全く新規の言語やフレームワークだときつそうだなというのも同時に感じました。少なくとも私が扱うと、言語やフレームワークの特徴や設計方針などを理解していない状態でAIに任せると結構酷いものが出来上がってきそうです。 また、実装についてはマルっと任せると冗長で適当なただ動くだけの巨大なコードを出してくるので、必然的に意味のある最小単位の指示をAIに出すことになり、細かい修正を少しずつ積んでいくというスタイルに自然となりました。 最後に AIによるコーディングでアプリ開発をするという実績を解除しましたので、今後は自信を持って「これ系の処理はAIが得意そうだからAIに書かせましょう」とか「AIで開発するとレビュー大変ですよね」と声高に言っていこうと思います! 冒頭で触れた、「コードを書かないマネージャー」という選択も今後変わってくるかもしれません。今回はAIをコーディングに活用する部分だけに触れていますが、あらゆる業務でAIの活用は積極的にしていきたいと考えています。 AIの登場によりどんなことでも色々と触って試してみるコストが大幅に下がったと思うので、積極的に新しいことを取り入れチャレンジする組織を目指していきます。
アバター
はじめに この記事は 株式会社エス・エム・エス Advent Calendar 2025 の18日目の記事です。 はじめまして。エス・エム・エス プロダクト推進本部 採用・組織開発支援グループの韓です。 今年の8月に入社したばかりですが、なぜ私がエス・エム・エスに入社したのか、今やっていること、そして今後やっていきたいことを紹介します。 今までの経歴 前職はデジタルマーケティングSaaSを提供する企業に所属していました。2018年にエンジニアとして入社し、約5年間開発に携わりました。開発業務だけでなく、チームリードとしてのマネジメントや外部登壇など、様々な経験をさせてもらいました。 チームで開発したプロダクトがスピーディーに形になり、ユーザーから嬉しいフィードバックが届く瞬間こそ開発の醍醐味だと感じ、エンジニアとして楽しく仕事に向き合っていました。 転機が訪れたのは、エンジニア採用が芳しくなく、誰かエンジニア採用を牽引する必要が出てきたときでした。エンジニアバックグラウンドがあり、コミュニケーションが比較的得意だった私に白羽の矢が立ち、2023年7月からエンジニア採用責任者としてのキャリアが始まりました。 面接官以外の採用業務は未経験でしたが、周囲の協力もあり、大変ながらも期待に応える成果を出すことができました。エンジニア時代に感じた、チームで開発したプロダクトがスピーディーに形になる体験において、優秀なチームメンバーが存在したことが1つの要因であると感じています。そのチームメンバーがいたのも当時の採用があったからこそであり、会社・組織・チームにとっては採用が重要であるのを身をもって感じました。また、組織やプロダクトの課題と候補者のキャリアに深く入り込むことで、なぜその候補者に入社してもらいたいのかのストーリーを作り、結果として候補者に熱量高く入社してもらえたときの嬉しさはかけがえのないものでした。そういった成功体験を通じて、採用をはじめとした「人・組織」に向き合う楽しさ、そして優秀なメンバーを採用することのインパクトの大きさを肌で感じました。 エス・エム・エスに入社した理由 前職での人事の仕事も楽しかったのですが、人事へのジョブチェンジから2年、在籍期間も6年半が経ち、「他にどのような環境があるのか」と外の世界にも興味を持ち始めました。 そんな中、現在のグループ長である @emfurupon777 とカジュアル面談をする機会があり、エス・エム・エスについて詳しく聞きました。特に刺さったのは、企業理念やミッション・バリューにあるように「続」を重視している点です。つまり、「長期を見据えて価値を出し続けること」を大事にしているという文化です。 エス・エム・エスが向き合う日本の高齢社会の課題は非常に壮大です。1年やそこらで解決できるものではなく、10年、20年というスパンで考えなければなりません。そのためには長期視点で思考し、継続的にアクションを取り続けることが重要になります。 私自身、そこまで長期で思考した経験はありませんでしたが、「将来組織を牽引する人材になるには、長期的な視座が必要だ」という課題感を持っていました。また、この壮大な課題に向き合う組織を作ることは非常にチャレンジングだと感じ、強く惹かれました。 選考過程で、技術責任者の @sunaot 、人材紹介開発部EMの @kenjiszk 、人事の @fkc_hr ともお話ししましたが、皆さんが優秀であることはもちろん、「エス・エム・エスを通じて社会を良くしたい」という熱い思いがひしひしと伝わってきました。そのパッションに感化され、次のチャレンジの場としてエス・エム・エスを選び、今年8月に入社しました。 今エス・エム・エスでやっていること 現在は、プロダクト推進本部のエンジニア採用をメインで進めています。 採用業務は多岐に渡りますが、時間的にも比重が大きいのは「カジュアル面談」と「候補者の選考フロー構築」の2つです。 カジュアル面談について 前職での経験から、「カジュアル面談でどれだけ自分たちに高い関心を持ってもらえるか」が候補者の意思決定における重要な要素だと考えています。また、入社時に期待されていたことの1つが、今までのエス・エム・エスにはない「私ならではの熱量・アプローチ」でした。そのため、自分の言葉でエス・エム・エスの魅力を語れるようになる必要がありました。 組織や事業のキャッチアップが落ち着いた入社1か月半頃から、まずは田辺さんやEM陣の面談に同席して見学。次に私が作成した資料を使ってメインで話し、 @sunaot や @emfurupon777 に同席してもらってフィードバックをもらう……というサイクルでブラッシュアップしていきました。皆さんとても協力的で、しっかりフィードバックをもらえる環境は本当にありがたいです! 採用チームでは今年9月以降、ダイレクトリクルーティングや紹介会社連携の強化により母集団形成がうまくいっており、私の9月から12月現在のカジュアル面談担当数も70件を超えました。 まだ精度を上げる余地はありますが、短期間で多くの機会をもらえたおかげで、候補者の方から「韓さんとの面談が有意義でした」といったポジティブな感想をいただけることも増えました。また、私が担当した候補者の方がオファーや内定承諾に至るケースも出てきています。 カジュアル面談で話すたびに、エス・エム・エスの向き合う課題の壮大さと、やるべきことの多さを再認識します。私が魅力を感じたときのように、候補者の方にも「今とこれからのエス・エム・エス」の魅力を感じてもらえるよう、今後も力を入れていきたいです。 選考フローの構築 入社して驚いたことの1つが、「候補者への向き合い」に相当な力を入れている点です。 現在は毎朝、採用チームでパイプラインを確認しています。各候補者の方にどのような選考フローを組むべきか、この場で決められない場合は誰に相談するかを話し合います。エス・エム・エスの選考は画一的なフローではなく、候補者ごとに柔軟にカスタマイズするため、しっかりとした議論が必要です。 当初は「ここまで時間をかけて決めるのか」と驚きましたが、エンジニア採用の難易度が年々高まる中、候補者一人ひとりに深く入り込む重要性を再認識しています。(もちろん、ただ時間をかければ良いわけではないので、効率化できる部分は工夫も必要ですが) 採用は人を巻き込む業務が多いですが、エス・エム・エスのメンバーはコミュニケーションが非常に円滑で、建設的な議論ができる方ばかりです。だからこそ、候補者に合わせた最適な選考フローが提供できているのだと思います。 これからやっていきたいこと 入社して5か月になりますが、今のところとても楽しく働いています。 私は、仕事を長く続けるには「その仕事が面白いか」が最も大事だと思っています。「面白い仕事」の条件は、「向き合っている課題が挑戦的であること」そして「一緒に働く仲間が優秀で良いメンバーであること」です。 重要なので繰り返しますが、エス・エム・エスの取り組んでいる課題は壮大で、一筋縄ではいきません。そのため、初めて会う候補者の方に一言で説明するのは難しいです。しかし、だからこそあらゆる切り口での魅力があり、候補者の入り口となる人事は、事業・プロダクト・組織・人に深く入り込み、広く知る必要があります。 まだ理解が浅い領域もありますが、その分、飽きずにやれることがたくさんあると感じています。そして、一緒に取り組む心強い仲間もいます。 つまり、腰を据えて長期であらゆることに取り組めそうだと思っています! まずは採用を軸にしつつ、人事として組織・人の観点で「今後どうあるべきか」を考え、日頃から問いを立ててアクションしていきたいです。 そして、エス・エム・エスではあらゆるポジションのエンジニア、デザイナー、PdMを募集しています。ぜひ私や現場のメンバーのカジュアル面談でお話しましょう。お待ちしています!
アバター
この記事は「株式会社エス・エム・エス Advent Calendar 2025」の12/17の記事です。 qiita.com はじめに 介護/障害福祉事業者向け経営支援サービス「カイポケ」でQAを担当している中村です。気づけば入社して4年が経ちました。現在は複数のQAチームに横断的に関わりつつ、チームのサポートや改善活動の推進、組織マネジメントなど幅広い業務を担当しています。 エス・エム・エスでは全社員が生成AIを業務に活用できる環境が整っており、私たちQAエンジニアもテスト活動や業務効率化に活用を進めています。しかし、現状は個人レベルでの活用がメインとなっており、組織的な仕組み化やナレッジの横展開という面では、まだまだ生成AIを活かしきれていないというのが実情です。 こうした課題に対し、今回の記事では生成AIを品質活動に取り入れようとしている具体的なチャレンジ事例について2つ紹介します。まだ充分な成果が出ているフェーズではなく、あくまで試行錯誤している内容として、率直に記載していますのでご了承ください。 事例1. 不具合分析を生成AIで効率化 導入の背景 カイポケでは、不具合データに複数の分類を持たせて、定量的に分析する仕組みを取り入れています。詳細は過去に私が執筆した記事をご参照ください。 tech.bm-sms.co.jp 不具合分析は主に以下のプロセスで進めていますが、生成AIを取り入れたのは「3.JIRAのデータを元に分析する」フェーズです。 不具合をJIRAチケットに起票 JIRAのデータをスプレッドシートで集計・可視化 JIRAのデータを元に分析する ※AI活用ポイント この「分析する」のフェーズは、分析経験・プロダクト理解・そして熟練者の勘所も必要であり、属人化しやすく時間が掛かってしまうという課題を抱えていました。そこで、構造化された不具合データを生成AIと連携させ、分析の作業効率化とプロセスの標準化を目指すことで、この課題を打破できると考えました。 どう実現しているか? 使用する生成AIはGeminiです。まず、JIRAに蓄積されている分析対象の「不具合データ」を用意し、インプットとしてAIに渡します。次に分析の目的や期待するアウトプットの形式といった具体的な分析指示をプロンプトで入力し、不具合分析を実施していきます。不具合データやプロンプトの詳細は後述します。 不具合データ 不具合データは、普段から集計・活用しているJIRAチケットの情報がベースです。このデータは、不具合の発生状況だけでなく、品質改善に向けた深掘り分析を可能にするため、複数の「分類軸」を持たせてています。 不具合データが持っている主要な情報は以下の通りです。 項目名 説明 基本情報 要約、再現手順、期待値など、不具合の概要情報 テストフェーズ 不具合を検出したテストフェーズ (例:DEVテスト、STGテスト など) 不具合種別 不具合の種別 (例:新規、既存 など) 検出種別 検出した不具合の内容 (例:機能不備、レイアウト不備 など) 検出分類 不具合を検出した経路 (例:QAテストケース、QAテストケース外など) 原因分類 不具合が混入した原因 (例:仕様理解不足、コード実装不備 など) プロンプト 分析の品質を均一化、誰でも悩まずに効率的にアウトプットが得られるよう、「標準プロンプト」を設計しました。この標準プロンプトは生成AIを特定の専門家として機能させるための要素を含んでいます。 標準プロンプトを構成する主な要素は以下の通りです。 構成要素 目的と具体的な指示 役割 ペルソナを設定し、出力のトーンと視点を設定する 背景 分析やテストの目的など、データでは分からない文脈を設定する ルール 具体的な分析作業を指示、分析軸の指定やアウトプットの構成要素を設定する ※プロンプト例 役割 ・経験豊富なQAチームリーダーの視点で分析してください 背景 ・分析目的:プロジェクトの品質傾向を把握したい ・プロジェクト名:XXX機能の追加 ・テスト目的:追加したXXX機能が正しく動作すること、関連する既存機能に影響がないことを確認する) ルール ・「検出種別 × 原因分類」の軸で分析をしてください ・不具合から品質傾向を客観的な視点でレポートしてください ・全体を通して「である調」で記述してください ・結論から先に述べる構成にしてください 今後に向けて 生成AIが最初に出してくるアウトプットはあくまで「下書きレベル」であるため、人間によるレビューやAIとの対話が多く必要になっています。今後は、チーム全体での積極的な活用とフィードバックを通じて分析精度の向上を図り、レビューのコストを削減できればと考えています。また、データ集計から言語化までのプロセスをより効率化するため、スプレッドシートのAI関数なども活用し、シームレスな連携を実現できるよう改善を進めていくことも検討していきます。 事例2. 不具合情報を持ったチャットボットの活用 導入の背景 テスト設計において、過去の不具合情報やドメイン知識は重要な参考情報ですが、情報が膨大ゆえに探索コストが高く情報が漏れてしまうというリスクがありました。生成AIはこれらの課題解決に加え、テスト観点の提案やレビュー効果も期待できるため、対話式のチャットボット作成を目指すことにしました。 どう実現しているか? GeminiのGem機能 を使い、「カスタム指示」と「知識」に必要な情報を設定することで専用のチャットボットを作成しました。プロンプトは入力に迷わないようにいくつかサンプルを作成して公開しています。運用イメージは下記図をご参照ください。 カスタム指示 ドメイン知識を持ったQA専門のアシスタントとして機能するように設定しています。カスタム指示には、AIが回答の際に参照すべき「役割」「ドメイン」「ルール」を詳細に定義しています。 カスタム指示に設定した内容は以下の通りです。 構成要素 目的と具体的な指示 役割 ペルソナを設定し、出力のトーンと視点を設定する (例:あなたは過去不具合とドメイン知識に精通したQAアシスタントです) ドメイン サービスの業界、業務領域、技術的・制度的な制約といった、背景知識を設定する ルール 行動指針(情報検索方法、事実に基づいた回答の徹底)と、アウトプットの構成要素を設定する 知識 知識には事例1で紹介した不具合データを設定します。 チャットボットとの対話例 実際にチャットボットとやりとしている内容の例を紹介します。最終的な判断は人が行う必要がありますが、叩きとしては充分な内容で高速で出力されることもあり非常に効率的です。 目的 プロンプト例 回答概要 1. リスク分析と観点抽出 ログイン機能に変更が入るので、過去の不具合から見て注意すべき観点を教えて 過去の不具合事例に基づき、 認証・セッション安定性 、 セキュリティ(権限昇格) 、 連携機能への影響 など、最もリスクの高い重点観点を提示 2. テストケースの提案 勤怠管理の改修が入るので、具体的なテストケースを提案して 過去事例を反映し、 境界値テスト や 機能連携テスト など、具体的な操作手順を含むテストケースの例を提案 3. 不具合発生時の対応支援 特定のデータで画面が真っ白になったけど、過去に似た事象はある? 類似する 過去の不具合事例 や 想定される原因 を特定し、調査の方向性を提示 4. ドメイン知識の問合せ 請求情報と勤怠情報の連携で、過去にどんな問題が起きた? 特定の機能連携に関して、過去に発生した データ不整合のリスク や、それに対する 現在のシステム対応状況 を説明 今後に向けて 実運用はまだQAチームに閉じていますが、今後は広く展開し不具合データだけでなく、膨大なドメイン情報や社内の知見も取り込み、QAや品質に関するあらゆる「困りごと」の相談相手となるAIアシスタントに育てていきたいと考えています。 さいごに 本記事では、カイポケQAチームの生成AIを使った具体的な取り組み事例を紹介させていただきました。技術の進化を「絵に描いた餅」で終わらせず、品質活動に積極的に取り入れるモチベーションを持って日々業務に取り組んでいます。その他、GitHub CopilotやClaude Codeを使って、プロダクトコードやPRの情報からテスト観点を効率的に抽出する活動もトライしています。 私たちQAエンジニアは単にバグを見つけるだけでなく、プロセスのデザインやチームの知識を構造化したり、最新のテクノロジーを活用してプロダクト全体の品質を高める役割を担っていると考えています。そういった新しい技術を品質活動に取り入れることにワクワクし、試行錯誤できるモチベーションがある方を積極募集中ですし、一緒に仕事をしたいと思っています。 興味がある方は、ぜひカジュアル面談でお話ししましょう!!
アバター
この記事は株式会社エス・エム・エスAdvent Calendar 2025の12月11日の記事です。 qiita.com こんにちは、介護/障害福祉事業者向け経営支援サービス「カイポケ」のリニューアルプロジェクトでSREを担当 していた 加我 ( @TAKA_0411 ) です。 私事ではありますが、9月にSREチームからプロダクト開発チームへ異動しました。現在はEmbedded SREではなく、開発者としてKotlinを用いたバックエンド開発を主に担当しています。 これまでの私のキャリアはQA → インフラ → SREという流れで、アプリケーション開発の経験はほとんどありませんでした。そのため、自分のスキルを広げたいという思いから挑戦させてもらっています。 当初はIntelliJのセットアップやDDDの理解などに苦戦しましたが、チームメンバーの多大なサポートもあり、ようやく少しずつ慣れてきたと感じています。 前のチームでの私は「どのようにオブザーバビリティを実現・高めていくか」ということに注力していたのですが、実際に開発チームで動いているうちにオブザーバビリティに関する考え方や取り組みに関して新しい視点が芽生えてきたと感じているので紹介します。 きっかけはいつもカンファレンス まず、私にとってひとつの転機となったイベントを紹介します。2025年10月27日に開催されたObservability Conference Tokyo 2025です。 o11ycon.jp このカンファレンスはオブザーバビリティという技術領域にフォーカスしたイベントで、ロール・文化・運用・OpenTelemetryなど幅広いテーマが扱われていました。弊社もロゴスポンサーとして協賛しています。 中でも特に印象に残ったのは、LINEヤフー株式会社のToshiya Katoさんによる「オブザーバビリティが育む開発者のシステム理解と好奇心」というセッションです。アーカイブ動画も公開されているので、ぜひご覧ください。 o11ycon.jp youtu.be このセッションは「オブザーバビリティツール、本当に使われていますか?」という問いかけから始まります。 サービスのテレメトリーデータを一通り揃えている しかし、テレメトリーデータが実際に活用されるシチュエーションはリリース時や問題発生時であった 問題発生時にはオブザーバビリティツールを用いたものではなくベテランによる解決というケースが起こっていた テレメトリーデータやオブザーバビリティツールが日常的に利用されているとは言えない状況 つまり、システムを理解するためのオブザーバビリティではなく、オブザーバビリティツールを導入してモニタリングしていただけでは?という状況であると整理されています。 このようなプロダクトやチームの状況をふまえ、メンタルモデルという切り口から下記のように課題を整理していました。 私たちはドキュメントやコード、ダッシュボードなどを通じてシステムの姿を思い描いている(≒メンタルモデル) メンタルモデルとシステムがズレると仕様の誤解や障害が発生する 担当しているサービスでは障害が少なかった反面、メンタルモデルのズレの修正を行う機会を得ることができなかった 開発者とSREとではメンタルモデルが異なっており、開発者はコードやドキュメントから、SREはダッシュボードなどのテレメトリーデータから構築している 上記のメンタルモデルの違いはDevOpsのサイクルと一致しているようである オブザーバビリティツールはDevOpsの "Ops" のサイクルで拡充されがちで、開発者にとっては使い慣れないツールになってしまう "Dev" のサイクルでオブザーバビリティツールを使うことでシステムの理解と好奇心を育てることができるのではないか DevOpsサイクル ( https://www.pagerduty.co.jp/blog/devsecops-cultural-transformation ) 上記の課題を解決するため、"Dev" のサイクルで本番同等の観測ができる環境と負荷が必要と考え、GitHubのPRごとのPreview環境、そしてカジュアルに実施できる負荷試験のシステムを整備した取り組みを紹介していました。 ここまでの発表内容から自分の取り組みを振り返ってみると、SREとしての私が注力してきたのは "Ops" のサイクルでのテレメトリーデータ拡充にすぎず、それこそ "オブザーバビリティツールでモニタリングをしているだけ" という状況に陥っているということに気付きました。これからの自分に必要なのは "Ops" のサイクルで得られた学びやフィードバックを適切に "Dev" のサイクルに取り込み、オブザーバビリティツールを活用してシステム理解を深め、問題をより迅速に解決できる環境を整えることだと強く認識しました。 このセッションが自分にとって大きな転換点となったこと、そして貴重な発表をしていただいたことを改めてToshiya Katoさんに感謝したいと思います。 オンコール対応から見えてきた課題 カンファレンスを経て意識が変化した直後の11月末、珍しくオンコール対応が必要なアラートを検知しました。私も開発者としてトラブルシューティングに参加しました。 DatadogのAPMを見つつエラーの箇所を追っていくと、DatadogのGitHub Integrationが役立ち、問題箇所と原因を早い段階で把握できました。しかし、実際にどのようなリクエストが送られているのか、なぜそのようなリクエストが送られてしまったのかといった調査にとても手間取ってしまいました。 また、Slackに通知されたアラートメッセージが抽象的だったこともあり、そのアラートを見て我々がどのようなアクションが必要なのかという点で初動の遅れに繋がってしまいました。 12月3日の記事 にあるとおり、私たちが開発しているカイポケのシステムは分散システムとなっています。WebフロントエンドからAPIのGatewayを経由するデータや、コンポーネント間の通信データもあります。そのため、オブザーバビリティの考え方としては問題を引き起こしたデータがどこから来たのか、その中身が何であったのかを収集しておいてすぐに確認できる状態であるべきでした。 システム概要図 また、私を含め開発者がトラブルシューティングのためのDatadogの利用に不慣れであったり、オンコール対応のためのRunbookがまだ整備されていないという状況でした。最終的には開発経験が長く詳しい人にデータを調査をお願いし、事なきを得てひとまず落ち着きました。これはつまり下記の状況です。 問題発生時にはオブザーバビリティツールを用いたものではなくベテランによる解決 システムを理解するためのオブザーバビリティではなくツールを導入してモニタリングしていただけ 開発者にとっては使い慣れないオブザーバビリティツール このような状況を踏まえ、同じチームの同僚と簡易的なポストモーテムを実施し、現状の課題を整理しつつ迅速なオンコール対応を可能にするための改善を進めています。プロダクト開発チームに所属することで私に当事者意識が芽生えたのでしょうか、チーム全体での "開発者が自分たちの手で観測・理解できる環境づくり" への意欲が高まっています。"Dev" のサイクルでオブザーバビリティツールを使うこと、それに慣れること、そしてシステムの理解を深めることの重要さを痛感しました。 まとめ 今回のチーム異動とObservability Conference Tokyo 2025への参加を通じて、オブザーバビリティへの理解が大きく変化しました。 今後はより開発サイクルに密着した実践的なオブザーバビリティ改善を進めていきたいと思います。 社内で「オブザーバビリティ・エンジニアリング」の輪読会を開くなど、私の所属チーム以外でもオブザーバビリティへの関心が広がりつつあります。 今回の改善でどのような成果が見られたのかについては、結果が出次第またブログや登壇で共有する予定です。今後の報告もぜひご期待ください。
アバター
この記事は 株式会社エス・エム・エス Advent Calendar 2025 の12月9日の記事です。 はじめに ちょうど1年ぶりのテックブログへの登場となりました。BPR推進部EA推進グループでエンジニアをしている、おうえ @kotaoue です。 この記事は「エス・エム・エスに入社してから、楽しく仕事をするためにおうえがやってきたこと」を振り返ってみようという、個人的な想いドリブンで書いています。 ※ 同時に「BPR推進部ってどんなチーム?」や「エス・エム・エスのBPR推進部でエンジニアとして働く楽しさ」みたいなものを、もしかして将来一緒にチームとして仕事をすることになる誰かに伝わると本当に最高だなという期待も結構込めています! 具体的なTechの話というよりは、開発フローやカルチャーに関するポエムです。 簡単な自己紹介 まずは、おうえの略歴を簡単に書いておきます。 これから「エス・エム・エスのBPR推進部でエンジニアとして働く楽しさ」に触れますが、それは「ただしこんな感じで仕事をしてきた人にとって」という前提が付く話にはなるので略歴を書いておきます。 20年以上前に就職してから、ゲームプランナー → 起業試みて失敗 → Slerでプログラマ → 事業会社の社内SE → ゲーム会社のサーバーエンジニア → SaaSスタートアップのエンジニア といった感じで仕事をしてきています。 ちょっと変則的なキャリアプランで参考にならないよと思われるかもですが… 「Slerのプログラマ」「個人事業主」「スタートアップのメンバー」のような何個かの視点から語っていると感じていただけると幸いです。 BPRって何? EA推進グループって何? エス・エム・エスには「プロダクト開発部」とは別に「BPR推進部」という組織も存在します。 そこで、 昨年の記事 と同様に、所属している部署の紹介を記載しておきます。 まず、BPR推進部ですが、BPR(Business Process Re-engineering)の略称です。 とても簡単に言うと「部署を横断して会社全体の業務プロセスを改善していくこと」が目標のチームとなっています。 ちなみに、EA推進グループは、EA(Enterprise Architecture)の略称で、ここにも「ビジネス戦略とITを統合してシナジーを最大化していこう」といった想いが入ったチーム名となっています。 「プロダクト開発部」と「BPR推進部」では、お互いに「マーケット(市場・業界)へ向けて」価値を届けるという方向性は同じですが、そこへ至るアプローチとして、主に以下のような役割分担をしています。 プロダクト開発部⇒マーケットに向けて直接価値提供するサービスの開発・提供 BPR推進部⇒マーケットへのサービスデリバリーまでを含めた社内の業務プロセス改善/業務システム構築 もちろん、この役割分担も固定的なものではなく、お互いにやるべきと思ったことをオーバーラップしたり協業したりしながら組織横断的に成果に向かって動いています。 ※ このTechBlogも、プロダクト開発部とBPR推進部の両方のメンバーがエントリーを書いて運営しています。 テーマとしていた働き方の発表 規模としては多くの従業員数が所属するいわゆるエンタープライズ企業ということで、エンタープライズ企業ならではの働き方が必要だと入社前から少し身構えていました。 例えば、組織として守るべきルールや、登場人物が多いことによる調整の難しさや独特のスピード感については、前職のスタートアップとは大きく異なると想像していました。 そんな組織の中で、自分として楽しく仕事をする&組織に価値を還元できるように、「とりあえずやってみる」を自分のテーマにしてきました。 ※ 裏テーマは「誰かに怒られるまではやってみる」でした。 それは、これまで主に「Slerのプログラマ」「個人事業主」「スタートアップのメンバー」として働いてきた人間が「エンタープライズ企業」でプレゼンスを示すためには、ちょっと違った視点ややり方が大事だと思って作成したテーマです。 その一例として「自分の好きな仕事」について部全体に共有会を実施することになった件を紹介します。 「自分の好きな仕事」について部全体に共有会を開催することになった件 おうえはポストモーテムが大好きです!色々ある仕事の中で一番好きだと言っても過言では無いです。 そして「楽しく仕事をしたい」し「とりあえずやってみる」「誰かに怒られるまではやってみる」がテーマの一年だったので、「よし、ポストモーテムの魅力を伝える共有会をみんなに向けて開催しよう」と動いてみた結果の振り返りです。 開催までの流れ 共有会を開催したいなと思う 「ポストモーテムが大好きなので、ポストモーテムの魅力を伝える会を開催したいです!参加者募集です」とSlackのBPR全体チャンネルに投稿 実際のメッセージのスクリーンショットは↓ 部長から「何より楽しいっていうところが良いですね!」というコメントを貰って喜ぶ 開催 という感じでした。 入社前に身構えていたエンタープライズ企業的な難しさもなく、「やりたい」→「いいね」→「やる」という軽いフットワークで開催することができました。 また、個人の思いつきが起点となった共有会ですが「一緒に内容ブラッシュアップしよう」とマネージャーも前のめりで協力してくれるなど、とても動きやすい環境でした。 この時点で「BPRという組織は、入社前に想像していたよりも数倍〜数十倍の裁量範囲がある」ということを感じ、それは「誰かに怒られるまではやってみる」という限界を攻めるのも難しいなという、大きな驚きになっていました。 開催した結果 結果として、共有会の冒頭に表示されるスライドで「好きだから」を全面に押し出すような、個人的な想いが満載の共有会を開催することになりました。 反応も上々で 共有会を聞いて、3秒後にesaでポストモーテムを仕組み化、チームのドキュメントとしてシェアしました! さっそくチームでポストモーテムやってみました!再発防止策はみんなで考えようぜ!という意識で議論できたのは良かったと思います! 経営管理本部とか他部署のメンバーにも共有したいなぁと思うんですけど、エス・エム・エス全体に広げちゃってもいいですか? といった感じで、BPRの中で反応するだけではなくて他部署にも波及するという、当初抱いていたエンタープライズ企業という想像を打ち砕くような軽やかな結果となりました。 ポストモーテムが好きな理由の補足 今回の記事は「楽しく仕事をするためにテーマとしていたことの振り返り」なので、ポストモーテムについては深堀りしないようにしようと思いましたが、やはり好きなのでちょっとだけ語らせてください。 ポストモーテムが好きな理由は「 ポストモーテムを楽しめるメンバーの集まったチームが好きだから 」です。 そしてその理由を細分化すると ポストモーテムが好きってことは、失敗しても批判されないし、失敗からポジティブな学びを得るチーム 失敗からポジティブな学びを得るってことは、新しいことにチャレンジできるチーム 新しいことにチャレンジできるってことは、もっと良いチームになる可能性が高いチーム もっと良いチームになる可能性が高いチームってのは、参加していて楽しいチーム! という感じです。 そして、共有会を開催するまでの流れで「 自分が参加しているチームはもっと良いチームになる可能性が高いと感じることができた 」というのが最高のフィードバックでした。 まとめ 入社前にはエンタープライズ企業だからと身構えていましたが…実際には「やってみたいな」と自分からアクションを起こせば、やってみたい仕事にチャレンジできる機会がある、拍子抜けするくらい軽いフットワークで色々なことができる組織でした。 「はじめてのポストモーテム」だけではなく「LT大会やってみませんか」「輪読会やってみませんか」「プロダクト開発で実施しているボードゲーム会に参加して良いですか?」といったチームカルチャーにまつわる相談にはもちろん「やってみよう」という反応で進行することになりました。 また「ダッシュボード作りましょう」や「AIエージェント使ってみたいです」や「次はJavaじゃなくてGoでPoCやりたいです」といった技術的な希望や「ITGCの統制対象となっているシステムの開発フローだがやりづらい部分があるので変更したい」といった開発フローやIT全般統制に関わるようなややこしい要望であっても、一度も「NG」と言われることがない組織でした。 他にも「ちょっと1on1したいです」とSlackで声をかければ、技術責任者の田辺さん( @sunaot )とも1on1実施できるという、スタートアップのようなとても距離の近いコミュニケーションも可能な組織でした。 もちろん「とりあえずやってみよう」ではなくて「それをやるためには情報を整理して然るべきルートで承認申請してから」といった進め方になることはあります。 それでも「じゃあどうすれば実現できるか」という視点で動けるのが、BPR(Business Process Re-engineering)という言葉が入った組織の強みであり、エス・エム・エスという会社の社風なんだなと感じたのが、一年を振り返った率直な感想です。 さいごに アドベントカレンダーの中ではありますが…BPR推進部では一緒にエス・エム・エスのBPRに取り組んでくれるメンバーを募集中です。 BPR推進・コーポレート エンジニア職種一覧 としてまとめております。 もちろん「とりあえず、1回話をしてみたいなー」というカジュアル面談も大歓迎なので、↓にあるカジュアル面談のリンクをクリックしてみてください。 また、BPRについてもう少し知りたい方向けへの記事もリンクしておきます。 規模はエンタープライズですが、スタートアップ的な速度感で動くことのできる組織なので、「チャレンジしがいのある大きなissue」がたくさんありますし、ぜひ一緒にチャレンジしたいと思っていますという想いを伝えて筆を置きます。 もうすこしBPRについて知りたい方向けへの記事 組織のやっていることを知りたい方へ 多様なビジネスモデルを展開している成長企業のBPR組織のミッション BPRらしさの詰まったビジネスアーキテクトという職種について知りたい方へ ビジネスアーキテクト × Salesforce:改善だけで終わらない、戦略推進と戦術実行を追求する その他、BPRメンバーの書いた記事は こちら
アバター
この記事は株式会社エス・エム・エスAdvent Calendar 2025の12月8日の記事です。 qiita.com プロダクト開発部 人材紹介開発グループの田実です。 人材紹介サービスは今年から Career Portal という新しい社内基盤を開発・運用しています。 本記事ではこのCareer Portalの概要や利用技術について紹介したいと思います! 人材紹介サービスの課題とCareer Portal エス・エム・エスでは医療・介護・保育などの領域で人材紹介サービスを提供しています。 人材紹介サービスは「キャリアパートナー」と呼ばれる、従事者(求職者)と事業者を繋ぐ転職・採用支援担当が双方とコミュニケーションをとり、より良いマッチングを創出しています。 より良いマッチングを提供するために、昨年末からキャリアパートナーにご協力いただきキャリアパートナーの全ての業務プロセスをヒアリングしました。 業務プロセス理解の取り組みに関しては以下の記事でも紹介しておりますので、こちらも読んでもらえると嬉しいです! tech.bm-sms.co.jp ヒアリングした業務プロセスを一部抜粋したものが以下になります。 ご覧の通り、キャリアパートナーの業務は非常に多岐に渡り業務量がとても多い状態でした。センシティブな情報を扱ったり不確実性が高い業務も多く、タスクの質・量ともに負担が大きい状況でした。 また、情報の粒度やストア先、コミュニケーションツール、ノウハウなどが各チームや個人で異なっているといった課題もありました。 このヒアリングを通じて より最適なマッチングを提供するためには業務プロセスの見直しや業務システムの改善を行う必要がある ということが改めてわかりました。 これらの課題を解決すべく、今年から開発チームが主体となりプロジェクトを発足しました。 このプロジェクトで作った社内業務アプリケーションが Career Portal です。 1 ここからはこのCareer Portalの技術基盤について詳細を紹介していきます。 全体的な構成図 インフラはGoogle Cloudで構築しており、以下のような構成になっています。 ユーザー(キャリアパートナー)がLoad Balancer経由でWebアプリケーション( Cloud Run )にアクセスし、DB( Cloud SQL )からデータを入出力するような一般的な構成です。社内アプリケーションなのでLoad BalancerにはIAPによるアクセス制限も入れています。 Cloud SQLのデータは日次でBigQueryに連携しています。 Looker Studio からBigQueryに接続してダッシュボードを作成しており、データやGAから利用状況の確認・分析を行っています。 上記の構成図からは割愛していますが、バッチ処理もあり、Cloud Run Jobsのアプリケーションを Cloud Scheduler で動かしています。 VertexAI を使った生成AIの機能は別アプリケーションとして実装しています。生成AIのレスポンスは数十秒以上かかるため、Webから直接AIアプリケーションにリクエストするのではなく、 Cloud Tasks を使った非同期化やリトライ管理も行っています。 Celeryなどのジョブワーカーを利用する方法も案としてはありましたが、Career Portalリリース時点ではCloud Run Worker Poolsがパブリックプレビューだったことや、運用・保守の負荷の面を考慮しCloud Tasksを採用しました。 アプリケーションアーキテクチャ WebアプリケーションはTypeScript / Next.js を使って実装しています。認証は NextAuth.js 2 を使っており、社内GoogleアカウントのOIDCによる認証基盤を実装しています。ORMとして Prisma 、UIライブラリは shadcn/ui 、フォーマッタ・Linterは Biome 、テストは Vitest と最近よく見かけるような技術構成になっています。 AIアプリケーションはPython / FastAPI を使っています。パッケージ管理は uv 、型チェックは mypy 、フォーマッタ・linterは Ruff とこちらもPythonアプリケーションでよく見られる技術構成になっています。 バッチアプリケーションはNode.js (TypeStrippingを使ったTypeScript)で実装しており、CLIフレームワークとして Commander.js 、フォーマッタ・Linter・テストはWebと同じくBiome、Vitestを使っています。 リポジトリはインフラも含めてモノレポで管理しています。 ロギング 全てのアプリケーションで構造化ログを採用しています。 Webアプリケーションではロガーライブラリとして pino を使っています。開発環境ではコンソール上でJSON形式のログが見づらい問題があったため pino-pretty も使っています。 Pythonのロガーは標準のloggingライブラリを使ってJSON形式のフォーマッタを定義・適用しています。 class JsonFormatter (logging.Formatter): default_msec_format = "%s.%03d" def formatTime (self, record): dt = datetime.datetime.fromtimestamp(record.created, tz=JST) return dt.isoformat() def format (self, record): log_record = { "timestamp" : self.formatTime(record), "logger" : record.name, "level" : record.levelname, "levelno" : record.levelno, "message" : record.getMessage(), "args" : record.args, "created" : record.created, "context" : { "pathname" : record.pathname, "module" : record.module, "funcName" : record.funcName, "lineno" : record.lineno, }, } return json.dumps(log_record, ensure_ascii= False ) def getLogger (name: str ) -> logging.Logger: logger = logging.getLogger(name) handler = logging.StreamHandler() handler.setFormatter(JsonFormatter()) logger.addHandler(handler) CI/CD CI/CDはインフラも含め全てGitHub Actionsで行っています。GitHub Actionsのアクションは pinact でpinningしており、 actionlint によるワークフローファイルの静的解析も行っています。Google CloudのAPIにアクセスする場合は Workload Identity連携 を使ってGitHub Actionsから認証しています。 CIの内容としてはWeb・バッチは Biome・tsc・Vitest、AIアプリケーションではRuff・mypy・pytest、インフラでは tflint ・terraform fmt・ trivy によるチェックを動かしています。さらに、 lefthook を使ってローカル環境でコミット・プッシュする前にLinter・フォーマッタを動かして対応漏れを防いでいます。 アプリケーションの自動デプロイは mainブランチへのデプロイ => 開発環境への自動デプロイ GitHubのリリースを切る => 本番環境への自動デプロイ としています。 production用のブランチを切って運用する方法もありましたが、その分プルリクエストやブランチが増えて管理性が悪くなるデメリットもあったので、Career PortalではGitHubでリリースを切る方式を採用しました。 mainブランチにマージして開発環境での動作確認した後は速やかにリリースする、といった開発・本番環境の乖離が発生しづらいフローを想定していたというのも理由の1つです。 GitHubのリリースを手動作成するのも面倒なので、mainブランチにマージしたらDraftリリースを自動で作成し、そのリリースをPublishするだけで自動デプロイができる仕組みも実装しました。 DraftリリースのリンクはSlackに通知しているためDraftリリースのリンクを探す手間も無くなるようにしました。 o11y インフラのメトリクスをDatadogのダッシュボードで閲覧できるようにしたり、メトリクスが閾値を超えたらSlackに通知するような仕組みも実装しました。 Sentryも各アプリケーションに実装しており、予期せぬ不具合が発生した場合はSentry経由でSlackに通知されます。 RDBの特定のデータを監視して異常がないかどうかの確認もバッチ処理で実装しています。 MetabaseやRedashなどのBIツールを使ってデータを監視・通知する方法も実装案にあったのですが、総合的な実装・運用コストがバッチによるスクラッチ実装のほうが少ないと判断しました。 BIツールという観点だとアドホックなクエリを打てる環境が運用上求められることがありますが、こちらはCloud SQL Studioで対応しています。Cloud SQL StudioだとGoogle Groupを使ったIAM認証が利用できるため、クレデンシャル管理せずにデータアクセスができて非常に便利です。 検証環境 ローカル開発環境では検証しきれないため、本番相当のインフラ構成である検証環境も構築しました。 検証環境はプルリクエストごとに作成できると良かったのですが、IAPやCloud Storage、Eventarcの設定など純粋にアプリケーションの分離だけでは設定が不十分になってしまうことや、実質的に5つ程度の環境があれば十分開発が回る体制だったため、事前に構築した検証環境を各開発者に提供する形にしました。 Cloud Runはリクエストされていないときにコンテナを停止できて、コールドスタートも開発検証する上では許容範囲だったため、コスト的な心配や開発体験の影響もなく安定して運用ができています。 その他 Renovateによる定期バージョンアップ Renovateによる定期バージョンアップも行っています。各アプリケーション・依存グループでグルーピングされたプルリクエストが週次で作成されるように設定していて、CIが通っていて動作確認上も問題なければ良きタイミングでリリースしています。 生成AIによる開発プロセス効率化 例に漏れず、弊社でも生成AIを活用し、開発プロセスをより効率的にしています。 具体的にはClaude Codeによるコード自動生成、GitHub Copilotによるコード補完、GitHub CopilotによるPRコードレビュー、Meetの録音からの議事録自動生成、Geminiによる設計の壁打ち・技術課題解決、NotebookLMによる情報整理・検索など、様々な業務で活用しています。 初回リリース時は3〜4名で2〜3か月で構築していますが、生成AIの活用無しではこの人数・スピード感でのリリースは難しかったのではないかと思います。 ふつうの技術基盤を作りたい 新しくアプリケーションを立ち上げたり、あるいは既存のアプリケーションの改善をする場合、「ふつう」な技術基盤を作ることを意識しています。 「ふつう」の水準はエンジニアのレベル感でだいぶ変わってくるのですが、自分にとっては シンプルなインフラ構成で シンプルなアプリケーション設計で ローカル開発環境はコンテナで用意されていて 本番相当の適切な検証環境も用意されていて テストがそれなりに書かれていて CI/CDも用意されていて インフラ・DBやログなどのメトリクスを閲覧したり異常を適切に検知できる環境があって インフラはIaCで管理されていて 分析できる環境が整っていて ライブラリは定期的にアップデートされて最新のものが利用できるようになっていて 開発プロセスも生成AIを適切に使っている みたいな基盤です。 「ふつう」な技術基盤にすることで考えることを少なく・シンプルにして技術的な運用負荷を減らし、結果として、業務・ビジネスの課題解決によりフォーカスできるようになることを期待しています。 これら全てをやるにはそれなりの経験が必要ですが、ブログ記事などWeb上にサンプルコードのような「答え」を見つけるのが比較的容易な領域とも言えます。 今だと生成AIを使えば壁打ちしながらすぐに実装できるかもしれません。 結局コツコツと基盤を改善していくしかなく、この辺がサクッと導入できるくらいのスキルや経験も積み上げていくしかないのかなぁと思っています。 まとめ 社内業務アプリケーションのCareer Portalで使われている技術について紹介をしました。 Career Portalリリース後、「業務負荷が下がった」「最適なマッチングを提供できた」というポジティブなフィードバックを多くいただいています。 今後も業務理解・整理・システム改善を繰り返し、従事者と事業者の最適なマッチングを通じて 医療・介護/障害福祉の人手不足と偏在の解消 3 を目指していきたいと思っています。 既存の業務システム基盤は、今後の機能追加や事業の変化に対応し続けることが困難だったため、システムを切り離して新たに実装しました。 ↩ ぼちぼちBetter Authに切り替えようかなぁと思ってます。 ↩ https://www.bm-sms.co.jp/service/career/ ↩
アバター
こんにちは、プロダクト推進本部人事のふかしろ( @fkc_hr )です。 11月21日と12月4日に開催された、プロダクトマネージャーカンファレンス 2025(pmconf 2025)にエス・エム・エスはSilver Sponsorとして協賛しました。また、カイポケSaaS部部長の川合とカイポケアーキテクトの三浦が登壇の機会をいただきました。この記事では、発表資料とともに登壇機会をいただくために行った工夫をお伝えいたします。 それぞれの発表タイトル・要旨・資料は以下のとおりです。 大阪会場 The Modelの罠を超える!売上9割"SLG"のVertical SaaSが挑む「PLG × SLG ハイブリッド戦略」 プロダクト・レッド・グロース(PLG)はバズワード化する一方、複雑な業務ドメインを扱うVertical SaaS、特に強力なセールス組織を持つ企業にとって、その導入は決して平坦な道のりではありません。むしろ、既存のセールス・レッド・グロース(SLG)との衝突や組織間の壁が、変革の大きな足枷となることもあります。 本セッションでは、売上の9割をセールス組織が担う介護SaaS「カイポケ」を事例に、私たちが挑んだPLGとSLGの「両利き」を目指すハイブリッドGTM戦略のリアルをお話しします。 「PLG ONLYは幻想」と割り切り、現実的なスコープを見極める方法 。The Model型組織における部門間の壁を乗り越え、プロダクトがGTMに貢献できる具体的なポイントをオンボーディング体験のリサーチから見つけ出したプロセス など、成功譚だけでなく泥臭い失敗談も交えて共有します。 ( セッション内容紹介 より) speakerdeck.com 東京会場 マルチプロダクトのカオスを制す。「プロダクトディシジョンレコード」で実現するチーム横断のアラインメント戦略 「なぜこの機能が?」「あの決定は誰が?」マルチプロダクトで並行開発するからこそ頻発する“意思決定の迷子”に悩んでいませんか? 私たちの組織も、プロダクト間での連携がうまく進まずカオスに苦しんでいました。この状況を打破すべく導入したのが、意思決定の背景と結果を記録する「プロダクトディシジョンレコード(PDR)」です。 本セッションでは、なぜPDRが必要だったのかという背景から、私たちが実際に運用しているテンプレート、導入プロセスで直面した「書くのが面倒」「何を書けばいいのかわからない」といったリアルな壁、そしてそれを乗り越えて得られた「納得感のあるアラインメント」と「自律的なチーム」への変化を、具体的な事例と共にお話しします。明日から使えるPDRのフォーマットと、組織に定着させるための実践的なヒントを持ち帰っていただけます。 ( セッション内容紹介 より) speakerdeck.com プロポーザル提出までの流れ 今年度は人事中心ではなく、各領域のコミュニティに近いメンバーが協賛関連の検討・企画・準備をしていました。その際にPdM陣が行っていたプロポーザルを提出するまでの取り組みや工夫についてご紹介します。 傾向分析 セッション応募すること自体がはじめてなメンバーも多かったこともあり、採択されるにはどうすればいいか?から検討していました。 公式から出ている募集要項や概要、昨年度のレポート、pmconf2025kickoff動画も参考に登壇テーマの分類や参加者属性、メッセージの内容などをまとめていました。 例えば、今回の登壇セッションは以下の8つのテーマに分類されていました。 プロダクトデリバリー プロダクトディスカバリー プロダクト戦略 事業戦略 Go To Market 意思決定とアライン 価値の検証と学習 組織戦略・キャリア・役割分担 その中でもプロダクトのフェーズからターゲットにしやすいテーマを複数にしぼることで、話しやすいテーマ出しにつながりました。 10件のセッション申し込み 気づいたら決まっていたのですが、セッションを10件出すという目標を掲げ、早々に「pmconf 公募セッション申込進捗状況」というスプレッドシートで管理されていました。定期的に検討している件数や申込締め切りをウォッチしていました。 結果として、PdMのみならず、開発部長や事業部長も提出しており、チームが珍しく期日ドリブンで動いていて、お祭りのようでした。 頼れる「Gemini のGem」の活用 いざセッションへ申し込もうと思っていても、登壇の全体像が完璧に見えてから提出できるわけではありません。そこで活躍したのが、壁打ちやプロポーザル提出の文章の叩きを作ってくれるプロンプトでした! ぼんやりとタイトルやテーマが決まっているが、まだ内容を作りきれていないというときに大活躍でした。 プロポーザルの提出までの試行錯誤が格段にしやすくなり、なんとか10件申請にたどり着くことができました。Gem&プロンプトを作ってくれた方に大感謝です。 結果相対的に通過率が高い取り組みに 結果としては、10件の申請中、2件採択いただきました。 ちなみに、pmconf の発表によると、応募数は323件、採択数は34件で、倍率は10倍とのことでした。比較すると、私たちの取り組みは相対的に通過率の高い結果となっています。 おわりに イベントの運営本当にありがとうございました。 また、川合さんの登壇レポートは、別途noteでも発信する予定です。ぜひチェックしてください。 来年のpmconfで、皆さんとお会いできるのを楽しみにしています! 発表内容やPdMの取り組みについてより詳しく聞いてみたいという方は、 ぜひカジュアル面談にお越しください!
アバター
この記事は株式会社エス・エム・エスAdvent Calendar 2025 vol.1の12月4日の記事です。 qiita.com エス・エム・エスで全社SREというロールで活動しているSecurity Hub芸人の山口( @yamaguchi_tk )です。おすすめのAWSサービスは営業です(いつもお世話になっています)。 はじめに 私が所属している全社SREチームで監視基盤の入れ替えを行った際、そのタイミングで監視設定自体も見直しました。 その見直しの中で、 脅威モデリング の考え方をインフラレイヤーの監視設計に応用して整理を行いました。 本記事では、そのときに実施した手法をできるだけ具体的に言語化し、同じように監視を見直したい方の参考になることを目指して紹介します。 背景 オンプレからAWSにLiftした際にオンプレと同じ構成でAmazon EC2を構築し、監視もZabbixを利用してオンプレと同じ構成で構築しました。 Zabbix自体のアップデートやAmazon EC2、Amazon RDSのアップデートを何度か実施した結果、アップデートの検証工数やZabbix自体の運用工数が課題になってきたため、フルマネージドサービスであるAmazon CloudWatchへの移行を行いました。 監視設定の整理の目的 監視設定自体はZabbixの基本テンプレートをベースに一部をカスタマイズして利用しており、特にメトリクスベースのアラートがシステムの実態と合っておらず 無駄なアラートが発生している 状況でした。 無駄と判断した監視設定は、発生のたびに削除したり閾値や監視するメトリクス自体を見直ししたりしていましたが、発生都度の対応ではどうしてもいたちごっこになってしまいます。 そこで、Amazon CloudWatchへの移行を機に方針を決めて監視設定を整理することにしました。 インフラレイヤーの監視設定への応用 インフラレイヤーの監視設定に脅威モデリングの手法を応用できないか検討し、実際に行ってみました。 これは古典的なセキュリティ原則の「可用性(Availability)」にフォーカスして脅威モデリングを実施した、と言えるかもしれません。 脅威モデリングについて 脅威モデリング(Threat Modeling)は、システムやアプリケーションのセキュリティを設計段階から考慮するための手法です。 アダム・ショースタック氏の以下のフレームワークが有名です。 Shostack’s Four Question Framework for Threat Modeling 何に取り組んでいるのか? What are we working on? 何が問題を引き起こす可能性があるか? What can go wrong? それに対して何をするのか? What are we going to do about it? 十分によい対応をしたか? Did we do a good job? 具体的には、DFD等でシステムの構成要素を可視化し、「起こったら困ること」という視点で脅威を特定します。 特定した脅威をSTRIDE等でカテゴリ分けし、その発生確率をアタックツリーで分析し影響度と併せてリスク評価することで、セキュリティリスクを事前に把握し、適切な対策を検討することができる手法です。 実施手順 ここからは、実際に行った手順を紹介します。 発生したら困ることを洗い出す まず、AWSの構成図を作成します。 1つ目の構成図(EC2版)には、オンラインWebサービスと同じインスタンスで処理されるバッチ処理が含まれています。 2つ目の構成図(AWS Fargate版)には、オンラインWebサービスとバッチ処理、非同期処理が含まれています。 AWSの構成図を見ながら、 起こったら困ること を基準に付箋などを張ったりして洗い出していきます。 例示したAWSの構成図の場合だと、以下のようになります。 オンラインサービス系 オンラインサービスが応答しない オンラインサービスの応答が遅延している バッチ系 バッチが処理されない 非同期処理系 非同期処理が処理されない 非同期処理が遅延している 処理フローを可視化する 洗い出した 起こったら困ること に対して、その処理(オンラインサービス系/バッチ系/非同期処理系)がどのようなシステムフローで実行されているかを、AWS構成図ごとに可視化していきます。 作成したAWS構成図から必要な処理フローを抜き出して構成します。 以下に上で例示したAWS構成図での可視化例を示します。 オンラインサービス系(EC2) バッチ系(EC2) オンラインサービス系(Fargate) バッチ系(Fargate) 非同期処理系(Fargate) 発生要因を分析する 洗い出した 起こったら困ること に対して、処理フロー図を見ながらその発生要因を抽象度高めで洗い出していきます。 ここでは「xxxというエラーでyyyが発生して処理が異常終了する」という詳細な記載ではなく、「処理が正常終了しない」といった抽象度高めの粒度で洗い出しを行います。 洗い出した例を図で示します。 オンラインサービス系発生要因(EC2) バッチ系発生要因(EC2) オンラインサービス系発生要因(Fargate) バッチ系発生要因(Fargate) 非同期処理系発生要因(Fargate) 直接的に監視できるところを探す 分析で洗い出した発生要因に対して、 一番直接的に観測できる箇所・監視内容 や、 処理の急所(ここを監視すればシステムフローの下流の異常が検知できる箇所・監視内容) を処理フローから探します。 洗い出した例を図で示します。 オンラインサービス系監視ポイント(EC2) バッチ系監視ポイント(EC2) ハートビート系処理でログを監視する場合は、ハートビート系処理をどこで動作させるのか、バッチ処理のログをどこに出力するのか、といった点もあわせて考慮する必要があります。 オンラインサービス系監視ポイント(Fargate) バッチ系監視ポイント(Fargate) 非同期処理系監視ポイント(Fargate) バッチ系監視ポイント(Fargate)では、以下の理由でAWS Step Functions以降を監視することで全体をカバーしています。 Throttlingが発生する可能性がある場合は通常Amazon SQS等を挟むため、このシステムはThrottlingが発生する状況になることは少ないと考えられる。 Amazon S3とAmazon EventBridge間でThrottlingが発生してもAWS Step Functions自体は起動することを期待している。 Amazon EventBridgeの定時処理は少なくとも1回は起動することを期待している。 まとめ 監視設定の整理に脅威モデリングの手法を応用することで、以下の効果が得られました。 システムの実態に合った 最低限必要な監視設定 が可能になります。 「起こったら困ること」を起点にすることで、無駄なアラートを削減できます。 処理フローを可視化することで、監視ポイントの選定根拠が明確になります。 この手法は既存の監視設定の見直しだけでなく、新規システムの監視設計にも応用できると考えています。
アバター
この記事は株式会社エス・エム・エスAdvent Calendar 2025 12月3日の記事です。 qiita.com 介護/障害福祉事業者向け経営支援サービス「カイポケ」のソフトウェア開発者の空中清高(@soranakk) です。 本記事ではカイポケのバックエンド開発で取り入れている、スキーマからのコード生成に焦点を当てて取り組みや工夫を紹介したいと思います。 カイポケのシステム概要 まずはこちらの図をご覧ください。 こちらはカイポケのシステム概要図となっています。 バックエンドにはいくつかのサーバーが存在していて、それぞれのサーバーはGraphQL APIを公開しています。 そして、それらをGatewayで1つのGraphQL APIにまとめて、フロントエンドに公開しています。 またそれぞれのバックエンドはSpring Bootで構成されていて、KotlinとSpring for GraphQLを利用して開発しています。 さらにそれぞれのバックエンド毎にデータストアとしてPostgreSQLのデータベースを持っている構成になっています。 本記事ではこれらのバックエンド開発のコード生成に焦点を当てて紹介したいと思います。 コード生成を利用している箇所 コード生成は主に2箇所で利用していて、1つ目はGraphQL Schemaからのコード生成です。 それぞれのバックエンドで公開しているGraphQL SchemaからKotlinのコードを生成して利用しています。 もう1つはデータベースアクセスでPostgreSQLのスキーマからKotlinのコードを生成して利用しています。 GraphQL Schema からのコード生成 カイポケのバックエンドではSpring for GraphQLを利用してGraphQL APIの開発をしているのですが、Spring for GraphQLにはコード生成の機能が存在しません。 そのため、カイポケではDGS Framework (Domain Graph Service)というライブラリのコード生成を利用しています。 DGS frameworkはNetflixが提供している、Spring BootでGraphQLを開発するためのライブラリです。 リンク: https://netflix.github.io/dgs/ DGS frameworkにはコード生成のためGradleプラグインが用意されていて、それを使うとKotlinのコードを生成することができます。 ちなみにSpring for GraphQLの開発においてコード生成でDGS frameworkを利用することは公式ドキュメントでも触れられていますので、半分ぐらい公式的な方法です。 リンク: https://docs.spring.io/spring-graphql/reference/codegen.html データベーススキーマからのコード生成 カイポケのバックエンドではjOOQというORMを利用してデータベースアクセス層の開発をしています。 jOOQにはデータベーススキーマからのコード生成プラグインが提供されているので、それを利用しています。 jOOQを使ったデータベースアクセスは、例えばソースコードはこのような感じになります。 by公式ドキュメント: https://www.jooq.org/doc/3.20/manual/sql-building/dsl-api/ val result: Result <Record?> = create.select() .from(AUTHOR) .join(BOOK).on(AUTHOR.ID.eq(BOOK.AUTHOR_ID)) . where (AUTHOR.YEAR_OF_BIRTH.gt( 1920 )) .and(AUTHOR.FIRST_NAME.eq( "Paulo" )) .orderBy(BOOK.TITLE) .fetch() この時に利用するテーブル名やカラム名、View名やRecord型などがjOOQによって生成されたコードとなっています。 生成するときはデータベースのスキーマを直接参照して生成することになるので、DockerなどでPostgreSQLを起動しておいたり、 TestContainersを使ったりしてPostgreSQLを起動した状態でコード生成します。 コード生成のメリット:GraphQL GraphQLのコード生成によってリクエストやレスポンス型のdata classを自分で書く必要がなくなります。 これらはGraphQL Schemaに合わせて定型的な型を定義して利用するだけなので、コード生成できると楽です。 さらにGraphQL Schemaからコード生成されるため、GraphQL Schemaを修正すれば自動的にコードへ反映されるようになります。 そのためGraphQL Schemaを更新したけどコードへ反映が漏れていた、みたいなことにコンパイルエラーで気づくことができます。 ユニットテストやCIでも気づくことはできるのですが、やはり修正してすぐに手元のエディタで気づけるほうが開発者体験として良いです。 コード生成のメリット:データベース データベースのスキーマは何らかのマイグレーションツールを使って管理していると思います。 カイポケではgolang migrateというGo言語で作られたマイグレーションツールを利用しています。 リンク: https://github.com/golang-migrate/migrate こちらのツールではデータベースの更新をSQLファイルを使って管理します。 そのためデータベースのスキーマを管理するSQLとアプリケーションコードのKotlinで変更を同期しておかないと、データベースは更新したのにアプリケーションが発行するSQLは古いままでエラーになってしまう、みたいなことが発生しがちです。 そこでデータベーススキーマからコード生成することで、自動的に同期できるようになります。 なのでGraphQLの時と同じく、データベースを更新したけどコードが古いままになっていた、みたいなことにコンパイルエラーで気づくことができます。 CI での活用 スキーマとコードのズレにコンパイルエラーで気づける、という話をしましたがそれでも漏れてしまうこともあります。 そこでGraphQL SchemaやデータベースのマイグレーションのSQLが追加された時などをトリガーにしてCIでコードの自動生成を行なって、変更がコードに反映されているかどうかをチェックしています。 手元で修正漏れがあったとしてもPRのCIでチェックされるので、整合性が取れていない場合はマージされることもありません。 このような自動化が行えることも自動生成の利点だと思います。 まとめ 本記事ではカイポケのバックエンド開発で取り入れているコード生成に焦点を当てて紹介しました。 コード生成を活用することで外向けのインターフェースと内部のソースコードの整合性のチェックが自動化できたり、開発者がコンパイルエラーで不整合に気づけるというメリットがあります。 DGSのようにコード生成部分だけ利用するってケースもありかなって思っています。 また、本記事の内容はKotlin Fest 2025やJJUG CCC 2025 Fallのカンファレンスのエス・エム・エスのブースでも紹介していました。 ブースでは実際のソースコードも見せながら色んな工夫を話すことが出来たので、今後もエス・エム・エスのブースを見かけたら立ち寄ってもらえると嬉しいです。
アバター
この記事は 株式会社エス・エム・エス Advent Calendar 2025 の12月1日の記事です。 qiita.com  みなさんこんにちは。Analytics & Innovation推進部の井手です。あっという間に今年も12月。心の準備も済まぬ間にカウントダウンが始まる時期に突入です。そして今回の記事はAdvent Calendar 2025の記事の一つでありしかも1日目とのこと。華々しい幕開けとなれるかどうか。はてさて。 RecSys2025に参加してきました  前回の 私の記事 でも書きましたが、私は現在求職者と事業者に対してリコメンドを提供するシステムの作成に関わっています。そしてインプットの一環として、だいぶ過日となってしまいましたが、秋にチェコはプラハで開催された RecSys というリコメンドシステムの国際カンファレンスに参加してきました。開催地域である中央ヨーロッパの国々を中心に、アメリカ、中国、イギリス、フランスなど世界各国からリコメンドシステムに関わる研究者や実務者が集まり、研究が共有されました。また、Music, Travel, News, HRといったドメインごとのワークショップも開かれ、実用的な観点での学びも多分にありました。 今回のRecSys 簡単に前置き  あまりこの界隈に馴染みのない読者の方も多くいらっしゃると思いますので簡単に前置きをいれておきます。ここで言うリコメンドシステムとは、ECサイトではおなじみの「あなたへのおすすめ」と表示されて自動的に商品が提案されるような、過去のデータを利用してターゲットとなるユーザーの好みやニーズを推測し、それにマッチするアイテム(群)を抽出するシステムのことを指します。リコメンドアイテムを決定するアルゴリズムは「自分の行動と似た行動をとっているユーザーのパターンを参考に決定する」「自分が高評価したアイテムの中身と似たコンテンツのものを探し決定する」などのアプローチの側面があり、またそれらの側面から、クラスタリングアルゴリズムの適用、行列分解、ディープラーニングを利用した手法などを用いてアイテムの決定がなされます。特に最近は、精度や柔軟性の高さから、ディープラーニングを用いた手法が中心となっており、今回のRecSysの発表の傾向も同様でした。 ドメインごとにみたRecSys   RecSysでは様々なドメインでのリコメンドについて扱われていますが、ここでは大きく「リコメンドシステムが成熟してきているドメイン」と「新たなドメインへのリコメンドシステムの適用」という2側面で今回のカンファレンスの特徴をまとめてみたいと思います。 成熟してきているドメインでのリコメンドの深化  E-commerceや音楽、映画、オンライン広告など、すでにリコメンドシステムが多数のユーザーが利用するサービスに取り込まれ、アーキテクチャーの実績がすでにある分野では、エンベディングの頑健性や安定性を高める取り組み、あるいはリコメンド結果の多様性に焦点をあてている研究などが多かったように思えます。例えばMetaのスタッフを中心とした Zhengらの発表 では、広告表示のリコメンドに関連する課題を解決するための提案でした。SNSで表示される予定の広告は、量が膨大なのはもちろん、一方で表示される広告とそうでない広告の差が激しく分布が歪んでいたり、広告の入れ替わりが激しくIDドリフトが生じやすいなどの点でエンベディングが不安定になりやすい。それを解決するために、似た意味を持つ広告をクラスタにして情報を共有するSemantic IDを用いる手法を提案しています。  また、新規ユーザーのように、該当サービスにほとんど過去の情報を持っていない中でリコメンドを行うコールドスタート問題については、今回も複数の研究発表がありました。クロスドメインやマルチモーダル等、入力のバリエーションを増やすことで解決する提案が多かったように思え、これは近年の傾向と言えるでしょう。コールドスタート状態における情報の補完を、他ドメインあるいは他メディアで補完する場合、いかにそれらの情報を目標とするドメインで利用できるフォーマットに変換するかが課題となります。マルチモーダルの例だと、イメージ情報、または音声情報をテキストのエンベディングに変換するのは今までであればかなりハードルの高いタスクでした。しかしLLMが登場し、この部分をLLMが担うことで、ハードルが劇的に下がり各ドメインへの適用結果の報告がかなり増えてきました。LLMは適用が容易ですので、今後多くのドメインでコールドスタートのリコメンドの質が改善されていくことでしょう。 新たなドメインへのリコメンドシステムの適用  一方、リコメンドシステムを新たな領域に適用しようという動きも多く見られました。例えば Bereket らは、アートセラピーにおける、絵や音楽のリコメンドに関する手法。既存研究では絵の刺激に対するクライアントの反応からリコメンドのモデルを学習させていたところを、音楽のモデリングも含めた双方からのクロスドメインで行うという研究を報告していました。また、オンラインコースのリコメンドでは、受講者の興味をモデリングする手法が既にありましたが、これは受講者の興味がシフトしたタイミングを把握できないという欠点が指摘されてきています。この課題に対して両方を達成するためのモデルの提案が Li らによってなされ、その効果と有効性が主張されていました。こういった領域は今後しばらくは様々な側面からのリコメンドアーキテクチャが提案されていくなかで、徐々にデファクトの方法が定まり、現在のe-commerceの領域のように深化していくのだろうなという印象を受けました。  また、本のリコメンドに関する研究や実装はすでに珍しくなくなっておりますが、リコメンド対象を子供に限定し、さらに既存のリコメンドで前提となるプロフィール情報やインタラクションログなどが(privacy lawなどで)取得できないという制限された状況でのリコメンドエージェントを作成するという Hillら による発表は非常に目新しく、そして大変興味深かったです。子供の各年齢がどのような感情パターンを持つかをグルーピングしたものと、リコメンド対象の本を感情的側面でベクトル化したものでLLMをファインチューニングしてエージェントを作成するという手法で、その有効性を主張されていました。発表者は今後の課題点など挙げられており、確かに今後改善するポイントは多くあるのかもしれないとは思ったものの、それ以上に発表者(学生さんたちでした)の「子どもたちにもっとたくさん本を読んでもらいたい」という研究のきっかけとなったモチベーションは素晴らしく、とても魅力的な研究であるなと感じました。 個人的に特に気になった研究  どのドメインの発表も興味深く聞くことができたのですが、上記全体を通して一番印象に残った研究は、NetflixのZielnickiらによる、 Orthogonal Low Rank Embedding Stabilization というタイトルの発表でした。  実際のサービスでリコメンドシステムを運用するにあたり、日々更新されるデータからの再学習は避けては通れません。ただし、再学習を行うにあたっては計算負荷や、再学習されたモデルから生成されるエンベディングが今までのものと変化してしまうという問題が大きな課題となります。この再学習で生成されたエンベディングの計算負荷や不安定性に対し、QR分解とSVDを用いて次元を下げた後に、プロクラステス回転を行い前の行列に近づけるというアプローチを提案しています。彼らはNetflixの実際のデータ(基準日および基準日から数週間後の日)をこの方法で学習させ比較し、両者の間に強い類似度が生じていることを確認したと報告していました。  通常、あるモデルを利用して検索用のベクトルインデックスを構築している場合などは、再学習でモデルを作り直すと、インデックスも作り直すことになります。データが膨大な場合にはモデルの学習と合わせてかなりの時間がかかることになります。今回提案された方法は、モデル学習のみならず、軸をプロクラステス回転で過去の軸に合わせることでインデックスの作り直しを避けることができる(可能性がある)点で非常に魅力的で、参考になりました。 RecSys in HR  RecSysでは、メインの研究発表とは別に初日と最終日にワークショップがあります。ワークショップなので、メインの研究発表よりもさらに領域特化した感じのトピックになりますが、基本的には研究発表がメインという点は同じです。いくつか参加しましたが、ここでは本業であるHRに関するワークショップ(RecSys in HR)について簡単に紹介します。  今回の発表は7件ほどあったと思うのですが、AI(特に生成AI)が生み出すバイアスに注目する研究が多かった印象です。LLM登場直後の、ジェンダーや人種などに対する極端なバイアスではなく、文脈がより多様化した中でのバイアスが扱われていました。例えば、 Hoffmannらの発表 では、職業のマッチングについて、そのマッチングがどの程度適切かをLLMに評価させた際のバイアスについて報告していました。それによると、アラビア系国籍の人はヨーロッパ系国籍の人に比べて、経験が少ないことをより大きなペナルティと判断したなど、細かい部分にバイアスが見られたということでした。  このような、細かいところに見え隠れする小さなバイアスというのは、ときに誰かを大きく傷つける可能性があります。LLMが持つ数多くの偏りに常に注意深くあることは、とりわけ我々のような、数多くの人に利用していただくサービスにAIを適用しようと考えている者にとっては非常に重要なことです。  ワークショップの最後には組織内でAIを利用するリスクとその対策について、研究者、実務者、弁護士を含めたパネルディスカッションが行われました。そこでも話題の中心は、LLMが持つバイアスについてであり、バイアスについて注意深くあること、そしてバイアスがあることを前提としたレギュレーションを明確に敷いていくことは絶対に必要であるし、国際的な流れでもあるという話がなされていました。これは大変手間のかかる作業ではあります。ただ、今後AIを使わないという未来はないことを考えれば、いつかは絶対にやらなくてはいけないことです。私たちの準備はどうであるか、今一度考えるとてもいいきっかけになりました。 自身の領域を改めて考える  さて、こうやって今回参加したRecSysのメインカンファレンスおよびHRのワークショップを振り返り、改めて私が目下取り組んでいるテーマや領域について考えてみたときになにを強く思うかというと、やはりHR領域でのマッチング課題というのは、他ドメインのマッチング課題とは課題構造が少なからず異なるなということです。  HR領域におけるリコメンドは、多くの場合コールドスタート問題を抱えています。転職希望者のなかの多くは、転職をするのが初めての求職者です。そして転職が当たり前の時代になっているとはいえ、総回数はe-commerceなどにおける買い物などとは比較にならないほど少ない。つまり過去の履歴はあったとしてもかなり少ない。インタビューなどを通じて把握した、求職者のニーズだけを頼りに適切なマッチングを行っていく必要があります。また、リコメンドモデルを作成するのに利用する過去の求人情報は、ほとんどの場合現在はすでにありません。希望の求人が満たされた時点でなくなってしまいます。学習時に成立した求人と、類似した求人を探す必要が出てきます。すると、リコメンドされる求人は「現在の求職者さんと似たニーズを持つ人にマッチしたものと、似た求人」と、だいぶ誤差の範囲の広いおすすめになってしまう可能性があります。  また、求職者のニーズというのは、他領域のリコメンドにおける「好み」とは異なり、制約に近いニュアンスを持ちます。好みであれば、多様性で許容されるかもしれませんが、制約となるとより強い条件をリコメンドシステムに課すことになります。しばしばジョブマッチングは、問題構造をデートのマッチングのアナロジーとして取り扱われることもありますが、私はこの好みと制約の違いから、デートマッチングとも大きく異なると考えています。つまり、RecSysで発表されるような研究をはじめリコメンドに関する研究は世の中に数多くあるものの、HR領域への効果的な適用を考えていくと、領域特化して考えなくてはならない部分が多分にあるのではないかと感じるようになっています。参考になるもの、そして独自に考えなくてはならないもの、それをきちんと切り分けながら課題を設定し、解決していく必要があります。考えてみると割と当たり前のことなのですが、様々な領域の話を聞くと、この点が輪郭を持って浮き上がってきます。すごい大事なことなんですよね。その気付きも含めて、実りの多いカンファレンスでした。  RecSysへの参加、自分ではもちろん意義があることだと思っていたけど、それをうまく伝えられるかなーなんて少し不安に思っていたのですが、上司である田辺さんに相談したところ、快諾してくださいました(勉強だけじゃなくせっかくだから楽しんできてください!とまで言ってもらえました)。自身の成長の背中をぽんと押してくれるのは、田辺さんはもちろんこの会社全体の文化なんだなあと大変ありがたく思っています。  というわけでこの経験を成長につなげ、日々の仕事に還元してまいります。求職者、そして事業者が最も欲しているリコメンドができるよう、引き続き精進していきます。 皆さんにも「いいものができたよ!」と報告できる日を楽しみに、アドベントカレンダー2日目にバトンを渡したいと思います。それではまた!!
アバター