TECH PLAY

株式会社メドレー

株式会社メドレー の技術ブログ

1363

こんにちは。医療プラットフォーム第一本部プロダクト開発室所属エンジニアの髙橋です。 普段の業務では、 医療 SaaS プラットフォーム CLINICS の医療機関向けアプリケーション(以下、CLINICS)の開発を担当しています。 CLINICS では、昨年 10 月頃から React コードベースのリアーキテクチャに取り組んでいます。 その取り組みの 1 つとして、非同期状態管理に関連する実装を Tanstack Query を使って刷新しています。 この記事では、CLINICS における Tanstack Query の活用方法を導入背景と狙いを含めて紹介します。 目次 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> Tanstack Query について Tanstack Query を活用したフロントエンドアーキテクチャ Resource Operation の実装 ディレクトリ構成 cache.ts queries.ts mutations.ts ViewModel の実装 ディレクトリ構成 サーバステート フロントエンドに閉じたスキーマ フロントエンドに閉じた型 Tanstack Query 導入の背景とアーキテクチャの狙い 背景:コード品質の課題 狙い 1:Tanstack Query の導入による開発体験の向上 狙い 2:シンプルなレイヤー分割による学習容易性の向上 狙い 3:ドメインロジックを純粋関数で表現することによる可読性・テスト容易性の向上 まとめ さいごに Tanstack Query について Tanstack Query は、Web アプリケーションのサーバ状態の取得、キャッシュ、同期、更新を簡単に行うことができるライブラリです。 1 類似ライブラリには SWR、Apollo Client、RTK Query 等が挙げられます。 2 私たちは、機能性・ドキュメントの充実度・コミュニティの将来性を総合的に判断した結果、Tanstack Query(React Query)を採用して React コードベースの再構築を進めています。 導入背景の詳細は、後のセクションで詳しく紹介します。 Tanstack Query を活用したフロントエンドアーキテクチャ それでは本題の Tanstack Query を活用したフロントエンドアーキテクチャについて紹介します。 始めに、アーキテクチャの全体像を示した次の図をご覧ください。 まず注目して頂きたいポイントは、Backend と View の間にある Resource Operation です。 Resource Operation は Tanstack Query を主軸に実装されているレイヤーです。 役割を大きく分類すると次の 2 つが挙げられます。 非同期状態管理に関連する実装の集約 Backend で扱うデータ(OpenAPI スキーマ)と View で扱うデータ(ViewModel)の相互変換 全体像を把握するために、左右のレイヤーにも注目してください。 右の Backend は CLINICS では Ruby on Rails で実装された REST API を提供するモノリシックなサーバです。 API で扱うスキーマは OpenAPI を使って定義しています。 OpenAPI スキーマは、 committee-rails を使ったレスポンス検証と、 openapi-generator を使った API クライアントコードの自動生成に活用しています。 左の View は CLINICS では React で実装されたコンポーネントになります。 View では Backend の OpenAPI スキーマを直接参照することを避け、 ViewModel と呼ばれるフロントエンドで定義したモデル 3 のみを参照する設計としています。 Resource Operation は View と Backend の境界レイヤーとして非同期状態管理とデータの相互変換を行うシンプルなレイヤーとなっています。 続いて、上記の運用のための実装詳細を紹介します。 Resource Operation の実装 ディレクトリ構成 Resource Operation レイヤーでは、リソースごとに非同期状態管理とデータの相互変換の処理をまとめています。 ディレクトリツリーで Resource Operation レイヤー全体を表現すると次のようになります。 src/resourceOperations ├── resourceTagName # 例: todo │ ├── cache.ts │ ├── mutations.ts │ └── queries.ts └── resourceGroupTagName # 例: systemSettings(リソースに階層がある場合) └── resourceTagName # 例: organization(リソースグループ配下のリソース) ├── cache.ts ├── mutations.ts └── queries.ts ディレクトリ名の resourceTagName と resourceGroupTagName は OpenAPI スキーマでエンドポイントごとに割り振られている tag を元に命名しています。 ディレクトリごとに 次の 3 つのファイルを定義しています。 ファイル 役割 cache.ts Query Key の定義、キャッシュ操作のためのカスタムフックの定義 queries.ts useQuery をラップしたカスタムフックの定義、API Request/Response の整形 mutations.ts useMutation をラップしたカスタムフックの定義、API Request の整形 cache.ts cache.ts は次のように実装しています。 // ① Query Key // queries.ts がある場合に必要に応じて宣言 export const todoKeys = { all: [ "todo" ] as const , list : () => [... todoKeys . all , "list" ] as const , paginateList : ( page ?: number ) => [... todoKeys . list (), { page }] as const , detail : ( id : string ) => [... todoKeys . all , "detail" , id ] as const , }; // ② キャッシュ操作のためのカスタムフック // mutations.ts がある場合に必要に応じて宣言 export function useTodoCache () { const queryClient = useQueryClient (); return useMemo ( () => ({ invalidateList : () => queryClient . invalidateQueries ( todoKeys . list ()), invalidateDetail : ( id : string ) => queryClient . invalidateQueries ( todoKeys . detail ( id )), }), [ queryClient ] ); } ① Query Key は、 useQuery の queryKey オプションに与える値を定義した定数です。これは後述の queries.ts で利用します。 実装は Tanstack Query メンテナの Dominik さんのブログで紹介されている Query Key factories パターンを使っています。 ディレクトリ名を all の値とすることで、大規模なアプリケーションにおいてもキーの衝突を防ぐことが可能です。 ② キャッシュ操作のためのカスタムフックでは QueryClient を利用したキャッシュ操作をまとめています。これは後述の mutations.ts で利用します。 CLINICS では 楽観的更新 をしない方針としているため、 invalidateQueries を実行する関数群のみを定義しています。 queries.ts queries.ts は次のように実装しています。 import { type Todo } from "@/viewModels/todo" ; import { todoApi , type GetTodoDetailRequest } from "@/api/generated" ; // ③ queryFn export const query = { getTodoList : async (): Promise &#x3C; Todo []> => { const { data } = await todoApi . getTodoList (); return data . todoList ; }, getTodoDetail : async ( request : GetTodoDetailRequest ): Promise &#x3C; Todo > => { const { data } = await todoApi . getTodoDetail ( request ); return data . todo ; }, }; // ④ Request Selector export const request = { getTodoDetail : ( id : string | undefined ): GetTodoDetailRequest => { if ( id === undefined ) { // `enabled: false` となる条件の引数が与えられた場合例外とすることで、 // queryFn 内の型の整合性を保つ throw new InvalidRequestParameterError ( "Required parameter id was undefined when calling request.getTodoDetail." ); } return { id , }; }, }; ③ queryFn は、API へのリクエストを責務とした関数群です。 ここで使用している ApiClient や Request の型は openapi-generator から生成しています。 queryFn の戻り値の型は、後述の ViewModel で定義した型を明示 しています。 このようにフロントエンド側でサーバステートの型を別途定義することで、フロントエンドとバックエンドを分業して実装する際に開発しやすくなるメリットがあります。 省略していますが、レスポンスに応じたエラーの throw もここで行います。 ④ Request Selector は View から受け取った値をリクエストパラメータへマッピングすることを責務とした関数群です。 useQuery の条件付き実行を制御する enabled オプションで false となる条件を例外とすることで、queryFn 内で 型ガードをしなくて良い設計としています。 これらを使って View とのインターフェースとなる useQuery ラッパーを定義します。 // ⑤ Base Query // API Response を整形せずに返す export type UseTodoDetailQueryProps &#x3C; QueryResult > = { id : string | undefined ; select ?: ( data : Todo ) => QueryResult ; }; export function useTodoDetailQuery &#x3C; QueryResult = Todo >( props : UseTodoDetailQueryProps &#x3C; QueryResult > ) { return useQuery ({ queryKey: todoKeys . detail ( props . id ), queryFn : () => { return query . getTodoDetail ( request . getTodoDetail ( props . id )); }, enabled: !! props . id , select: props . select , useErrorBoundary: true , }); } import { selectTodoForm } from "@/viewModels/todo/todoForm" ; // ⑥ Selector Query // API Response を View で参照したいフォーマットに整形して返す export function useInitialTodoFormQuery () { // Base Query に select オプションを与える return useTodoDetailQuery ({ select: selectTodoForm , // selectTodoForm は Todo を TodoForm に変換する ViewModel Selector(後述) }); } useQuery ラッパーは、⑤ Base Query と ⑥ Selector Query に分けて定義しています。 CLINICS では同じデータソースに対して画面ごとに異なるフォーマットで表示することが多くあります。 Selector Query で 任意のフォーマットに整形することで、画面ごとに最適化されたデータの取得をスケーラブルに実現しています。 4 加えて、この手法では Selector にドメインロジックが凝集されるため、 Selector を重点的にテストすることで品質を担保しやすい メリットがあります。 Query のエラー制御は useErrorBoundary オプションを使って Error Boundary を表示する方針としています。 mutations.ts mutations.ts は次のように実装しています。 import { type TodoForm } from "@/viewModels/todo/todoForm" ; import { todoApi , type PostTodoRequest } "@/api/generated" ; // ⑦ mutationFn export const mutation = { createTodo : ( request : PostTodoRequest ) => { return todoApi . postTodo ( request ); }, }; // ⑧ Request Selector // ViewModel から API Request へ変換する export const request = { createTodo : ( todoForm : TodoForm ): PostTodoRequest => { return { PostTodoRequest : { todo : { title : todoForm . title , description : todoForm . description , status : todoForm . status , favorite : todoForm . favorite === "true" ? true : false , }, }, }; }, }; // ⑨ Custom Mutation export function useCreateTodoMutation () { const { invalidateList } = useTodoCache (); return useMutation ({ mutationFn : ( todoForm : TodoForm ) => { return mutation . createTodo ( request . createTodo ( todoForm )); }, onSuccess : () => { return invalidateList (); }, }); } 基本的な構成は queries.ts と同じです。 mutations.ts でも queries.ts と同様に ⑧ Request Selector を定義します。 Request Selector には、データ整形を扱うためドメインロジックが集まりやすいです。 そのため、入出力、境界値、例外のテストを積極的に書いて品質の担保に繋げています。 Mutation のエラーは画面ごとに UI でフィードバックするため、コンポーネント側で制御しています。 Resource Operation レイヤーに関する実装の紹介は以上です。 ViewModel の実装 ViewModel とは、View(React Component) で扱うデータのスキーマと型です。 Resource Operation の実装では、API Response を ViewModel に整形して View に提供することを紹介しました。 全体の理解を深めるため、ViewModel の実装も紹介します。 ディレクトリ構成 ViewModel は関心を分離するため Resource Operation とは別のディレクトリに定義しています。 src/viewModels └── todo ├── todo.ts # サーバステート ├── todoForm.test.ts # フロントエンドに閉じたスキーマのテスト ├── todoForm.ts # フロントエンドに閉じたスキーマ └── todoSearchCondition.ts # フロントエンドに閉じた型 ViewModel で定義するスキーマ・型は大きく分けて 3 つに分類されます。 サーバステート フロントエンドに閉じたスキーマ フロントエンドに閉じた型 サーバステート サーバステートは、 API Response として期待するデータのスキーマ及び型です。 前述の queries.ts 内の ③ queryFn で使用します。 zod を使って次のように実装しています。 // src/viewModels/todo/todo.ts // enum は View で &#x3C;option value={todoStatus.enum.ready} /> のように使用する export const todoStatus = z . enum ([ "ready" , "doing" , "done" ]); // ⑩ サーバステートのスキーマ // 開発中のみ ③ queryFn 内で API Response を parse することで、スキーマの不整合を検出するための補助輪として使用する export const todo = z . object ({ id: z . string (), title: z . string (), description: z . string (). nullable (), status: todoStatus , favorite: z . boolean (), }); // ⑪ サーバステートの型 // ③ queryFn の戻り値の型として使用する export type Todo = z . infer &#x3C; typeof todo >; ⑩ サーバステートのスキーマは、⑪ サーバステートの型の生成と開発時のスキーマ検証に使用しています。 開発時のみ API Response を検証することで、⑩ サーバステートのスキーマと Open API スキーマの整合性を確認しています。 // src/resourceOperations/todo/queries.ts import { todo , type Todo } from "@/viewModels/todo" ; export const query = { getTodoList : async (): Promise &#x3C; Todo []> => { const { data } = await todoApi . getTodoList (); // 開発時、APIとの結合タイミングで検証してフロントエンドとバックエンドでスキーマの齟齬がないことを確認する // 検証が済んだら parse 処理を外す return data . todoList . map (( x ) => todo . parse ( x )); }, }; 例外として、 外部サービスから取得したデータに関しては常にサーバステートのスキーマを使って検証 しています。 例えば、外部サービスの仕様として長さが 1 以上の配列が返ってくると決まっていて、フロントエンド側もその仕様に基づいた処理を実装している場合、 z.array().min(1) のスキーマで常に検証します。 ネットワークに近い箇所で不正なデータを検出することで、例外発生時の調査を容易にするメリットがあると考えています。 フロントエンドに閉じたスキーマ フロントエンドに閉じたスキーマは、そのほとんどがフォームのバリデーションスキーマです。 こちらも zod を使って定義しています。 // src/viewModels/todo/todoForm.ts import { type Todo , todo } from "./todo" ; // フロントエンドに閉じたスキーマ // フォームのスキーマは react-hook-form と連携して使用する(省略) export const todoForm = z . object ({ title: z . string () . min ( 1 , "入力してください" ) . max ( 200 , "200字以内で入力してください" ), description: z . string (). max ( 500 , "500字以内で入力してください" ), status: todo . shape . status , favorite: z . union ([ z . literal ( "true" ), z . literal ( "false" )]), }); export type TodoForm = z . infer &#x3C; typeof todoForm >; // ⑫ ViewModel Selector // サーバステートからフロントエンドに閉じたスキーマへ変換する // 前述の queries.ts 内 ⑥ Selector Query で使用する export function selectTodoForm ( todo : Todo ): TodoForm { return { title: todo . title , description: todo . description ?? "" , status: todo . status , favorite: todo . favorite ? "true" : "false" , }; } フロントエンドに閉じたスキーマは、必ずサーバステートを元に生成する運用としています。 そのために、フロントエンドに閉じたスキーマを宣言した直下に、サーバステートからフロントエンドに閉じたスキーマへ変換する ⑫ ViewModel Selector を定義します。 null の 空文字への変換や時間データのフォーマットのような、 サーバステート と View で使う値の差分吸収はこのセレクタ内で行います。 このように、 ViewModel レイヤーでは他のレイヤーと依存しないようにドメインロジックを表現 しています。 ドメインロジックを Tanstack Query に依存しないことで、今後技術基盤を刷新する場合でも影響を最小限に留めることを狙いとしています。 フロントエンドに閉じた型 依存関係を整えるため、Resource Operation と View から参照するフロントエンドに閉じた型は viewModels 配下に宣言しています。 // src/viewModels/todo/todoSearchParams.ts export type TodoSearchCondition = { sort ?: "created_at_asc" | "created_at_desc" ; }; ViewModel に関する実装の紹介は以上です。 Tanstack Query 導入の背景とアーキテクチャの狙い 背景:コード品質の課題 Tanstack Query を導入する以前の CLINICS の非同期処理周辺のコードベースではいくつかの課題がありました。 開発体験の課題 非同期処理のためのミニマムな基盤フックを独自に実装していた 5 ことにより、開発体験の観点で次のような課題がありました。 新しい要件(ポーリング・無限読み込み等)が発生した際に、最初に担当する開発者が都度機能拡張する必要がある テストが実装されていなかったため変更時に品質確認の負担が大きい 学習容易性の課題 上述の基盤フックにドキュメントがなかったため、学習容易性の観点で次のような課題がありました。 基盤フックにドキュメントがなく、新規メンバーの学習コストが高い 様々な実装手法が混在することで、実装時に迷いが生じている 可読性の課題 CLINICS は開発開始から約 7 年以上が経過しています。 その中でもフロントエンドは技術トレンドの変化が早いため、様々なライブラリやパターンを使って実装されています。 特に近年から漸進的に導入した React を使った実装については、明確な設計が確立されていませんでした。 これらの背景からチームで安定したアウトプットを出すことが困難で、可読性の観点で次のような課題がありました。 画面によってフックや関数の粒度、定義場所が違うことでコードリーディングの負荷が高い コードレビュー時のレビュワーの負担が大きい テスト容易性の課題 多くのドメインロジックがコンポーネントやカスタムフック内に混在していることで、テスト容易性の観点で次のような課題がありました。 テストが実装しづらい テストカバレッジが低い CLINICS はオンライン診療・電子カルテ等の医療機関業務を支える機能を提供する SaaS プラットフォームです。 今後長きに渡って多くの医療機関の方々に CLINICS を利用して頂くためには、 コードを読みやすく、変更しやすく、維持しやすい状態に保ち続けること が重要です。 前の章で紹介したアーキテクチャは 持続可能な開発を目指して 設計しました。 具体的には、 開発体験・学習容易性・可読性・テスト容易性を向上することを狙い としています。 狙い 1:Tanstack Query の導入による開発体験の向上 背景で説明したとおり、Tanstack Query 導入以前は独自実装したフックを使って非同期処理を実装していました。 Tanstack Query を導入したきっかけは、チーム内での雑談の中で、独自実装のフックが使いづらいという声が挙がったことです。 そこで、独自実装のフック自体の質を高めるか、質の高いライブラリを導入するかを議論した結果、次の理由でライブラリを導入する決定をしました。 極力自分たちでコードを書かずに非同期処理・状態管理の実装を実現したい 実装に困ったときにドキュメントを読めば解決する環境にしたい CLINICS では技術的な背景 6 から Tanstack Query と SWR が候補に上がりましたが、 select オプションや invalidateQueries の Partial Query Matching 等の機能性と、ドキュメントの充実度合いの観点から Tanstack Query を採用しました。 Tanstack Query を採用したことで、 非同期処理のためのコードの記述量が大幅に削減 されたほか、データの特性に応じて Query ごとにキャッシュの時間を調整することが可能となり開発体験が大幅に向上しました。 狙い 2:シンプルなレイヤー分割による学習容易性の向上 CLINICS では、事業の拡大にともないコードベースに関わるエンジニアが増え続けています。 実装の進め方はプロジェクトによって最適な形式を選択しています。 バックエンドからフロントエンドまで一気通貫で実装 技術領域に分けて分業 このように多くのエンジニアが様々な形で関わる環境では、コードベースの学習容易性を高め、実装からコードレビューの完了までをスムーズに行えることが重要です。 前の章で紹介したアーキテクチャは、 レイヤー分割をシンプルにすることでコードベースに慣れるまでの時間を最小限にする ことを意識しています。 加えて、 実装パターンを定形化することで、コードベースに馴染みがなくても迷いなく実装できる ほか、関数の粒度が統一されることで、コードレビューの負荷軽減にも繋がっています。 レイヤー分割の粒度や実装パターンについては、次の記事を参考にさせて頂きました。 フロントエンドアーキテクチャの話: Resource Set の紹介 ほかにも CLINICS では学習容易性の向上の取り組みとして、 新しいライブラリやアーキテクチャを導入した際は勉強会を開催 してライブラリの基本的な使い方や頻出の実装パターンに関する知見を共有しています。 狙い 3:ドメインロジックを純粋関数で表現することによる可読性・テスト容易性の向上 アーキテクチャを刷新する以前は、多くのドメインロジックがコンポーネントやカスタムフック内に混在していることで、可読性やテスト容易性に支障をきたしていました。 CLINICS のフロントエンドには、医療システムに関する複雑なドメインロジックが多いため、 シンプルでテストしやすいコードベース を作っていくことがとりわけ重要だと考えています。 この課題は、ドメインロジックが集まる傾向にある queries.ts、 mutations.ts の Request Selector や ViewModel Selector の実装を定型化し、純粋関数で表現することにより解決しました。 まとめ Tanstack Query を使ったフロントエンドアーキテクチャの実例を紹介しました。 Resource Operation レイヤーに Tanstack Query の実装を定型化して集約しています。 レイヤー分割をシンプルにし、実装を定型化することで、開発組織のスケールに対応しています。 useQuery の select オプションを使い、スケーラブルにデータ変換処理を記述しています。 データ変換処理にはドメインロジックが集まりやすいため、なるべく小さい粒度の純粋関数で表現することで、可読性・テスト容易性の向上を狙っています。 この記事の内容が Tanstack Query の導入を考えている方の参考になれば幸いです。 さいごに CLINICS では、機能開発と並行してフロントエンド基盤を改善する取り組みも実施しています。 Redux から Tanstack Query への移行 UI ライブラリの Mithril から React への移行 7 デザインシステムの構築とプロダクトへの反映 このような取り組みに興味がある方は次のリンクから是非ご連絡ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp Footnotes Overview | TanStack Query Docs ↩ Comparison | React Query vs SWR vs Apollo vs RTK Query vs React Router | TanStack Query Docs ↩ この記事で紹介している ViewModel は View レイヤー専用のモデルを表す概念です。 MVVM アーキテクチャの ViewModel とは異なります。 ↩ Tanstack Query におけるデータ変換手法の詳細は React Query Data Transformations | TkDodo’s blog で紹介されています。 ↩ Tanstack Query 導入以前の非同期処理は useAsync React Hook - useHooks をカスタマイズしたフックを使って実装していました。 ↩ CLINICS では 一部の実装箇所で Redux を使用していますが、 RTK Query は今回採用するライブラリの候補から除外しました。これは、現在使用している Redux のバージョンが低く、レガシーな周辺ライブラリも複数使用している背景で Redux Toolkit への移行に相当な工数を必要とするためです。 ↩ 2023 年 3 月現在、 CLINICS のフロントエンドの 約 50% は、 Mithril と Redux で構成されています。開発体験の向上のため、 React への完全移行を目指して日々改善を続けています。 ↩
こんにちは。医療プラットフォーム第一本部プロダクト開発室所属エンジニアの髙橋です。 普段の業務では、 医療 SaaS プラットフォーム CLINICS の医療機関向けアプリケーション(以下、CLINICS)の開発を担当しています。 CLINICS では、昨年 10 月頃から React コードベースのリアーキテクチャに取り組んでいます。 その取り組みの 1 つとして、非同期状態管理に関連する実装を Tanstack Query を使って刷新しています。 この記事では、CLINICS における Tanstack Query の活用方法を導入背景と狙いを含めて紹介します。 目次 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> Tanstack Query について Tanstack Query を活用したフロントエンドアーキテクチャ Resource Operation の実装 ディレクトリ構成 cache.ts queries.ts mutations.ts ViewModel の実装 ディレクトリ構成 サーバステート フロントエンドに閉じたスキーマ フロントエンドに閉じた型 Tanstack Query 導入の背景とアーキテクチャの狙い 背景:コード品質の課題 狙い 1:Tanstack Query の導入による開発体験の向上 狙い 2:シンプルなレイヤー分割による学習容易性の向上 狙い 3:ドメインロジックを純粋関数で表現することによる可読性・テスト容易性の向上 まとめ さいごに Tanstack Query について Tanstack Query は、Web アプリケーションのサーバ状態の取得、キャッシュ、同期、更新を簡単に行うことができるライブラリです。 1 類似ライブラリには SWR、Apollo Client、RTK Query 等が挙げられます。 2 私たちは、機能性・ドキュメントの充実度・コミュニティの将来性を総合的に判断した結果、Tanstack Query(React Query)を採用して React コードベースの再構築を進めています。 導入背景の詳細は、後のセクションで詳しく紹介します。 Tanstack Query を活用したフロントエンドアーキテクチャ それでは本題の Tanstack Query を活用したフロントエンドアーキテクチャについて紹介します。 始めに、アーキテクチャの全体像を示した次の図をご覧ください。 まず注目して頂きたいポイントは、Backend と View の間にある Resource Operation です。 Resource Operation は Tanstack Query を主軸に実装されているレイヤーです。 役割を大きく分類すると次の 2 つが挙げられます。 非同期状態管理に関連する実装の集約 Backend で扱うデータ(OpenAPI スキーマ)と View で扱うデータ(ViewModel)の相互変換 全体像を把握するために、左右のレイヤーにも注目してください。 右の Backend は CLINICS では Ruby on Rails で実装された REST API を提供するモノリシックなサーバです。 API で扱うスキーマは OpenAPI を使って定義しています。 OpenAPI スキーマは、 committee-rails を使ったレスポンス検証と、 openapi-generator を使った API クライアントコードの自動生成に活用しています。 左の View は CLINICS では React で実装されたコンポーネントになります。 View では Backend の OpenAPI スキーマを直接参照することを避け、 ViewModel と呼ばれるフロントエンドで定義したモデル 3 のみを参照する設計としています。 Resource Operation は View と Backend の境界レイヤーとして非同期状態管理とデータの相互変換を行うシンプルなレイヤーとなっています。 続いて、上記の運用のための実装詳細を紹介します。 Resource Operation の実装 ディレクトリ構成 Resource Operation レイヤーでは、リソースごとに非同期状態管理とデータの相互変換の処理をまとめています。 ディレクトリツリーで Resource Operation レイヤー全体を表現すると次のようになります。 src/resourceOperations ├── resourceTagName # 例: todo │ ├── cache.ts │ ├── mutations.ts │ └── queries.ts └── resourceGroupTagName # 例: systemSettings(リソースに階層がある場合) └── resourceTagName # 例: organization(リソースグループ配下のリソース) ├── cache.ts ├── mutations.ts └── queries.ts ディレクトリ名の resourceTagName と resourceGroupTagName は OpenAPI スキーマでエンドポイントごとに割り振られている tag を元に命名しています。 ディレクトリごとに 次の 3 つのファイルを定義しています。 ファイル 役割 cache.ts Query Key の定義、キャッシュ操作のためのカスタムフックの定義 queries.ts useQuery をラップしたカスタムフックの定義、API Request/Response の整形 mutations.ts useMutation をラップしたカスタムフックの定義、API Request の整形 cache.ts cache.ts は次のように実装しています。 // ① Query Key // queries.ts がある場合に必要に応じて宣言 export const todoKeys = { all: [ "todo" ] as const , list : () => [... todoKeys . all , "list" ] as const , paginateList : ( page ?: number ) => [... todoKeys . list (), { page }] as const , detail : ( id : string ) => [... todoKeys . all , "detail" , id ] as const , }; // ② キャッシュ操作のためのカスタムフック // mutations.ts がある場合に必要に応じて宣言 export function useTodoCache () { const queryClient = useQueryClient (); return useMemo ( () => ({ invalidateList : () => queryClient . invalidateQueries ( todoKeys . list ()), invalidateDetail : ( id : string ) => queryClient . invalidateQueries ( todoKeys . detail ( id )), }), [ queryClient ] ); } ① Query Key は、 useQuery の queryKey オプションに与える値を定義した定数です。これは後述の queries.ts で利用します。 実装は Tanstack Query メンテナの Dominik さんのブログで紹介されている Query Key factories パターンを使っています。 ディレクトリ名を all の値とすることで、大規模なアプリケーションにおいてもキーの衝突を防ぐことが可能です。 ② キャッシュ操作のためのカスタムフックでは QueryClient を利用したキャッシュ操作をまとめています。これは後述の mutations.ts で利用します。 CLINICS では 楽観的更新 をしない方針としているため、 invalidateQueries を実行する関数群のみを定義しています。 queries.ts queries.ts は次のように実装しています。 import { type Todo } from "@/viewModels/todo" ; import { todoApi , type GetTodoDetailRequest } from "@/api/generated" ; // ③ queryFn export const query = { getTodoList : async (): Promise &#x3C; Todo []> => { const { data } = await todoApi . getTodoList (); return data . todoList ; }, getTodoDetail : async ( request : GetTodoDetailRequest ): Promise &#x3C; Todo > => { const { data } = await todoApi . getTodoDetail ( request ); return data . todo ; }, }; // ④ Request Selector export const request = { getTodoDetail : ( id : string | undefined ): GetTodoDetailRequest => { if ( id === undefined ) { // `enabled: false` となる条件の引数が与えられた場合例外とすることで、 // queryFn 内の型の整合性を保つ throw new InvalidRequestParameterError ( "Required parameter id was undefined when calling request.getTodoDetail." ); } return { id , }; }, }; ③ queryFn は、API へのリクエストを責務とした関数群です。 ここで使用している ApiClient や Request の型は openapi-generator から生成しています。 queryFn の戻り値の型は、後述の ViewModel で定義した型を明示 しています。 このようにフロントエンド側でサーバステートの型を別途定義することで、フロントエンドとバックエンドを分業して実装する際に開発しやすくなるメリットがあります。 省略していますが、レスポンスに応じたエラーの throw もここで行います。 ④ Request Selector は View から受け取った値をリクエストパラメータへマッピングすることを責務とした関数群です。 useQuery の条件付き実行を制御する enabled オプションで false となる条件を例外とすることで、queryFn 内で 型ガードをしなくて良い設計としています。 これらを使って View とのインターフェースとなる useQuery ラッパーを定義します。 // ⑤ Base Query // API Response を整形せずに返す export type UseTodoDetailQueryProps &#x3C; QueryResult > = { id : string | undefined ; select ?: ( data : Todo ) => QueryResult ; }; export function useTodoDetailQuery &#x3C; QueryResult = Todo >( props : UseTodoDetailQueryProps &#x3C; QueryResult > ) { return useQuery ({ queryKey: todoKeys . detail ( props . id ), queryFn : () => { return query . getTodoDetail ( request . getTodoDetail ( props . id )); }, enabled: !! props . id , select: props . select , useErrorBoundary: true , }); } import { selectTodoForm } from "@/viewModels/todo/todoForm" ; // ⑥ Selector Query // API Response を View で参照したいフォーマットに整形して返す export function useInitialTodoFormQuery () { // Base Query に select オプションを与える return useTodoDetailQuery ({ select: selectTodoForm , // selectTodoForm は Todo を TodoForm に変換する ViewModel Selector(後述) }); } useQuery ラッパーは、⑤ Base Query と ⑥ Selector Query に分けて定義しています。 CLINICS では同じデータソースに対して画面ごとに異なるフォーマットで表示することが多くあります。 Selector Query で 任意のフォーマットに整形することで、画面ごとに最適化されたデータの取得をスケーラブルに実現しています。 4 加えて、この手法では Selector にドメインロジックが凝集されるため、 Selector を重点的にテストすることで品質を担保しやすい メリットがあります。 Query のエラー制御は useErrorBoundary オプションを使って Error Boundary を表示する方針としています。 mutations.ts mutations.ts は次のように実装しています。 import { type TodoForm } from "@/viewModels/todo/todoForm" ; import { todoApi , type PostTodoRequest } "@/api/generated" ; // ⑦ mutationFn export const mutation = { createTodo : ( request : PostTodoRequest ) => { return todoApi . postTodo ( request ); }, }; // ⑧ Request Selector // ViewModel から API Request へ変換する export const request = { createTodo : ( todoForm : TodoForm ): PostTodoRequest => { return { PostTodoRequest : { todo : { title : todoForm . title , description : todoForm . description , status : todoForm . status , favorite : todoForm . favorite === "true" ? true : false , }, }, }; }, }; // ⑨ Custom Mutation export function useCreateTodoMutation () { const { invalidateList } = useTodoCache (); return useMutation ({ mutationFn : ( todoForm : TodoForm ) => { return mutation . createTodo ( request . createTodo ( todoForm )); }, onSuccess : () => { return invalidateList (); }, }); } 基本的な構成は queries.ts と同じです。 mutations.ts でも queries.ts と同様に ⑧ Request Selector を定義します。 Request Selector には、データ整形を扱うためドメインロジックが集まりやすいです。 そのため、入出力、境界値、例外のテストを積極的に書いて品質の担保に繋げています。 Mutation のエラーは画面ごとに UI でフィードバックするため、コンポーネント側で制御しています。 Resource Operation レイヤーに関する実装の紹介は以上です。 ViewModel の実装 ViewModel とは、View(React Component) で扱うデータのスキーマと型です。 Resource Operation の実装では、API Response を ViewModel に整形して View に提供することを紹介しました。 全体の理解を深めるため、ViewModel の実装も紹介します。 ディレクトリ構成 ViewModel は関心を分離するため Resource Operation とは別のディレクトリに定義しています。 src/viewModels └── todo ├── todo.ts # サーバステート ├── todoForm.test.ts # フロントエンドに閉じたスキーマのテスト ├── todoForm.ts # フロントエンドに閉じたスキーマ └── todoSearchCondition.ts # フロントエンドに閉じた型 ViewModel で定義するスキーマ・型は大きく分けて 3 つに分類されます。 サーバステート フロントエンドに閉じたスキーマ フロントエンドに閉じた型 サーバステート サーバステートは、 API Response として期待するデータのスキーマ及び型です。 前述の queries.ts 内の ③ queryFn で使用します。 zod を使って次のように実装しています。 // src/viewModels/todo/todo.ts // enum は View で &#x3C;option value={todoStatus.enum.ready} /> のように使用する export const todoStatus = z . enum ([ "ready" , "doing" , "done" ]); // ⑩ サーバステートのスキーマ // 開発中のみ ③ queryFn 内で API Response を parse することで、スキーマの不整合を検出するための補助輪として使用する export const todo = z . object ({ id: z . string (), title: z . string (), description: z . string (). nullable (), status: todoStatus , favorite: z . boolean (), }); // ⑪ サーバステートの型 // ③ queryFn の戻り値の型として使用する export type Todo = z . infer &#x3C; typeof todo >; ⑩ サーバステートのスキーマは、⑪ サーバステートの型の生成と開発時のスキーマ検証に使用しています。 開発時のみ API Response を検証することで、⑩ サーバステートのスキーマと Open API スキーマの整合性を確認しています。 // src/resourceOperations/todo/queries.ts import { todo , type Todo } from "@/viewModels/todo" ; export const query = { getTodoList : async (): Promise &#x3C; Todo []> => { const { data } = await todoApi . getTodoList (); // 開発時、APIとの結合タイミングで検証してフロントエンドとバックエンドでスキーマの齟齬がないことを確認する // 検証が済んだら parse 処理を外す return data . todoList . map (( x ) => todo . parse ( x )); }, }; 例外として、 外部サービスから取得したデータに関しては常にサーバステートのスキーマを使って検証 しています。 例えば、外部サービスの仕様として長さが 1 以上の配列が返ってくると決まっていて、フロントエンド側もその仕様に基づいた処理を実装している場合、 z.array().min(1) のスキーマで常に検証します。 ネットワークに近い箇所で不正なデータを検出することで、例外発生時の調査を容易にするメリットがあると考えています。 フロントエンドに閉じたスキーマ フロントエンドに閉じたスキーマは、そのほとんどがフォームのバリデーションスキーマです。 こちらも zod を使って定義しています。 // src/viewModels/todo/todoForm.ts import { type Todo , todo } from "./todo" ; // フロントエンドに閉じたスキーマ // フォームのスキーマは react-hook-form と連携して使用する(省略) export const todoForm = z . object ({ title: z . string () . min ( 1 , "入力してください" ) . max ( 200 , "200字以内で入力してください" ), description: z . string (). max ( 500 , "500字以内で入力してください" ), status: todo . shape . status , favorite: z . union ([ z . literal ( "true" ), z . literal ( "false" )]), }); export type TodoForm = z . infer &#x3C; typeof todoForm >; // ⑫ ViewModel Selector // サーバステートからフロントエンドに閉じたスキーマへ変換する // 前述の queries.ts 内 ⑥ Selector Query で使用する export function selectTodoForm ( todo : Todo ): TodoForm { return { title: todo . title , description: todo . description ?? "" , status: todo . status , favorite: todo . favorite ? "true" : "false" , }; } フロントエンドに閉じたスキーマは、必ずサーバステートを元に生成する運用としています。 そのために、フロントエンドに閉じたスキーマを宣言した直下に、サーバステートからフロントエンドに閉じたスキーマへ変換する ⑫ ViewModel Selector を定義します。 null の 空文字への変換や時間データのフォーマットのような、 サーバステート と View で使う値の差分吸収はこのセレクタ内で行います。 このように、 ViewModel レイヤーでは他のレイヤーと依存しないようにドメインロジックを表現 しています。 ドメインロジックを Tanstack Query に依存しないことで、今後技術基盤を刷新する場合でも影響を最小限に留めることを狙いとしています。 フロントエンドに閉じた型 依存関係を整えるため、Resource Operation と View から参照するフロントエンドに閉じた型は viewModels 配下に宣言しています。 // src/viewModels/todo/todoSearchParams.ts export type TodoSearchCondition = { sort ?: "created_at_asc" | "created_at_desc" ; }; ViewModel に関する実装の紹介は以上です。 Tanstack Query 導入の背景とアーキテクチャの狙い 背景:コード品質の課題 Tanstack Query を導入する以前の CLINICS の非同期処理周辺のコードベースではいくつかの課題がありました。 開発体験の課題 非同期処理のためのミニマムな基盤フックを独自に実装していた 5 ことにより、開発体験の観点で次のような課題がありました。 新しい要件(ポーリング・無限読み込み等)が発生した際に、最初に担当する開発者が都度機能拡張する必要がある テストが実装されていなかったため変更時に品質確認の負担が大きい 学習容易性の課題 上述の基盤フックにドキュメントがなかったため、学習容易性の観点で次のような課題がありました。 基盤フックにドキュメントがなく、新規メンバーの学習コストが高い 様々な実装手法が混在することで、実装時に迷いが生じている 可読性の課題 CLINICS は開発開始から約 7 年以上が経過しています。 その中でもフロントエンドは技術トレンドの変化が早いため、様々なライブラリやパターンを使って実装されています。 特に近年から漸進的に導入した React を使った実装については、明確な設計が確立されていませんでした。 これらの背景からチームで安定したアウトプットを出すことが困難で、可読性の観点で次のような課題がありました。 画面によってフックや関数の粒度、定義場所が違うことでコードリーディングの負荷が高い コードレビュー時のレビュワーの負担が大きい テスト容易性の課題 多くのドメインロジックがコンポーネントやカスタムフック内に混在していることで、テスト容易性の観点で次のような課題がありました。 テストが実装しづらい テストカバレッジが低い CLINICS はオンライン診療・電子カルテ等の医療機関業務を支える機能を提供する SaaS プラットフォームです。 今後長きに渡って多くの医療機関の方々に CLINICS を利用して頂くためには、 コードを読みやすく、変更しやすく、維持しやすい状態に保ち続けること が重要です。 前の章で紹介したアーキテクチャは 持続可能な開発を目指して 設計しました。 具体的には、 開発体験・学習容易性・可読性・テスト容易性を向上することを狙い としています。 狙い 1:Tanstack Query の導入による開発体験の向上 背景で説明したとおり、Tanstack Query 導入以前は独自実装したフックを使って非同期処理を実装していました。 Tanstack Query を導入したきっかけは、チーム内での雑談の中で、独自実装のフックが使いづらいという声が挙がったことです。 そこで、独自実装のフック自体の質を高めるか、質の高いライブラリを導入するかを議論した結果、次の理由でライブラリを導入する決定をしました。 極力自分たちでコードを書かずに非同期処理・状態管理の実装を実現したい 実装に困ったときにドキュメントを読めば解決する環境にしたい CLINICS では技術的な背景 6 から Tanstack Query と SWR が候補に上がりましたが、 select オプションや invalidateQueries の Partial Query Matching 等の機能性と、ドキュメントの充実度合いの観点から Tanstack Query を採用しました。 Tanstack Query を採用したことで、 非同期処理のためのコードの記述量が大幅に削減 されたほか、データの特性に応じて Query ごとにキャッシュの時間を調整することが可能となり開発体験が大幅に向上しました。 狙い 2:シンプルなレイヤー分割による学習容易性の向上 CLINICS では、事業の拡大にともないコードベースに関わるエンジニアが増え続けています。 実装の進め方はプロジェクトによって最適な形式を選択しています。 バックエンドからフロントエンドまで一気通貫で実装 技術領域に分けて分業 このように多くのエンジニアが様々な形で関わる環境では、コードベースの学習容易性を高め、実装からコードレビューの完了までをスムーズに行えることが重要です。 前の章で紹介したアーキテクチャは、 レイヤー分割をシンプルにすることでコードベースに慣れるまでの時間を最小限にする ことを意識しています。 加えて、 実装パターンを定形化することで、コードベースに馴染みがなくても迷いなく実装できる ほか、関数の粒度が統一されることで、コードレビューの負荷軽減にも繋がっています。 レイヤー分割の粒度や実装パターンについては、次の記事を参考にさせて頂きました。 フロントエンドアーキテクチャの話: Resource Set の紹介 ほかにも CLINICS では学習容易性の向上の取り組みとして、 新しいライブラリやアーキテクチャを導入した際は勉強会を開催 してライブラリの基本的な使い方や頻出の実装パターンに関する知見を共有しています。 狙い 3:ドメインロジックを純粋関数で表現することによる可読性・テスト容易性の向上 アーキテクチャを刷新する以前は、多くのドメインロジックがコンポーネントやカスタムフック内に混在していることで、可読性やテスト容易性に支障をきたしていました。 CLINICS のフロントエンドには、医療システムに関する複雑なドメインロジックが多いため、 シンプルでテストしやすいコードベース を作っていくことがとりわけ重要だと考えています。 この課題は、ドメインロジックが集まる傾向にある queries.ts、 mutations.ts の Request Selector や ViewModel Selector の実装を定型化し、純粋関数で表現することにより解決しました。 まとめ Tanstack Query を使ったフロントエンドアーキテクチャの実例を紹介しました。 Resource Operation レイヤーに Tanstack Query の実装を定型化して集約しています。 レイヤー分割をシンプルにし、実装を定型化することで、開発組織のスケールに対応しています。 useQuery の select オプションを使い、スケーラブルにデータ変換処理を記述しています。 データ変換処理にはドメインロジックが集まりやすいため、なるべく小さい粒度の純粋関数で表現することで、可読性・テスト容易性の向上を狙っています。 この記事の内容が Tanstack Query の導入を考えている方の参考になれば幸いです。 さいごに CLINICS では、機能開発と並行してフロントエンド基盤を改善する取り組みも実施しています。 Redux から Tanstack Query への移行 UI ライブラリの Mithril から React への移行 7 デザインシステムの構築とプロダクトへの反映 このような取り組みに興味がある方は次のリンクから是非ご連絡ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp Footnotes Overview | TanStack Query Docs ↩ Comparison | React Query vs SWR vs Apollo vs RTK Query vs React Router | TanStack Query Docs ↩ この記事で紹介している ViewModel は View レイヤー専用のモデルを表す概念です。 MVVM アーキテクチャの ViewModel とは異なります。 ↩ Tanstack Query におけるデータ変換手法の詳細は React Query Data Transformations | TkDodo’s blog で紹介されています。 ↩ Tanstack Query 導入以前の非同期処理は useAsync React Hook - useHooks をカスタマイズしたフックを使って実装していました。 ↩ CLINICS では 一部の実装箇所で Redux を使用していますが、 RTK Query は今回採用するライブラリの候補から除外しました。これは、現在使用している Redux のバージョンが低く、レガシーな周辺ライブラリも複数使用している背景で Redux Toolkit への移行に相当な工数を必要とするためです。 ↩ 2023 年 3 月現在、 CLINICS のフロントエンドの 約 50% は、 Mithril と Redux で構成されています。開発体験の向上のため、 React への完全移行を目指して日々改善を続けています。 ↩
こんにちは。医療プラットフォーム第一本部プロダクト開発室所属エンジニアの髙橋です。 普段の業務では、 医療 SaaS プラットフォーム CLINICS の医療機関向けアプリケーション(以下、CLINICS)の開発を担当しています。 CLINICS では、昨年 10 月頃から React コードベースのリアーキテクチャに取り組んでいます。 その取り組みの 1 つとして、非同期状態管理に関連する実装を Tanstack Query を使って刷新しています。 この記事では、CLINICS における Tanstack Query の活用方法を導入背景と狙いを含めて紹介します。 目次 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> Tanstack Query について Tanstack Query を活用したフロントエンドアーキテクチャ Resource Operation の実装 ディレクトリ構成 cache.ts queries.ts mutations.ts ViewModel の実装 ディレクトリ構成 サーバステート フロントエンドに閉じたスキーマ フロントエンドに閉じた型 Tanstack Query 導入の背景とアーキテクチャの狙い 背景:コード品質の課題 狙い 1:Tanstack Query の導入による開発体験の向上 狙い 2:シンプルなレイヤー分割による学習容易性の向上 狙い 3:ドメインロジックを純粋関数で表現することによる可読性・テスト容易性の向上 まとめ さいごに Tanstack Query について Tanstack Query は、Web アプリケーションのサーバ状態の取得、キャッシュ、同期、更新を簡単に行うことができるライブラリです。 1 類似ライブラリには SWR、Apollo Client、RTK Query 等が挙げられます。 2 私たちは、機能性・ドキュメントの充実度・コミュニティの将来性を総合的に判断した結果、Tanstack Query(React Query)を採用して React コードベースの再構築を進めています。 導入背景の詳細は、後のセクションで詳しく紹介します。 Tanstack Query を活用したフロントエンドアーキテクチャ それでは本題の Tanstack Query を活用したフロントエンドアーキテクチャについて紹介します。 始めに、アーキテクチャの全体像を示した次の図をご覧ください。 まず注目して頂きたいポイントは、Backend と View の間にある Resource Operation です。 Resource Operation は Tanstack Query を主軸に実装されているレイヤーです。 役割を大きく分類すると次の 2 つが挙げられます。 非同期状態管理に関連する実装の集約 Backend で扱うデータ(OpenAPI スキーマ)と View で扱うデータ(ViewModel)の相互変換 全体像を把握するために、左右のレイヤーにも注目してください。 右の Backend は CLINICS では Ruby on Rails で実装された REST API を提供するモノリシックなサーバです。 API で扱うスキーマは OpenAPI を使って定義しています。 OpenAPI スキーマは、 committee-rails を使ったレスポンス検証と、 openapi-generator を使った API クライアントコードの自動生成に活用しています。 左の View は CLINICS では React で実装されたコンポーネントになります。 View では Backend の OpenAPI スキーマを直接参照することを避け、 ViewModel と呼ばれるフロントエンドで定義したモデル 3 のみを参照する設計としています。 Resource Operation は View と Backend の境界レイヤーとして非同期状態管理とデータの相互変換を行うシンプルなレイヤーとなっています。 続いて、上記の運用のための実装詳細を紹介します。 Resource Operation の実装 ディレクトリ構成 Resource Operation レイヤーでは、リソースごとに非同期状態管理とデータの相互変換の処理をまとめています。 ディレクトリツリーで Resource Operation レイヤー全体を表現すると次のようになります。 src/resourceOperations ├── resourceTagName # 例: todo │ ├── cache.ts │ ├── mutations.ts │ └── queries.ts └── resourceGroupTagName # 例: systemSettings(リソースに階層がある場合) └── resourceTagName # 例: organization(リソースグループ配下のリソース) ├── cache.ts ├── mutations.ts └── queries.ts ディレクトリ名の resourceTagName と resourceGroupTagName は OpenAPI スキーマでエンドポイントごとに割り振られている tag を元に命名しています。 ディレクトリごとに 次の 3 つのファイルを定義しています。 ファイル 役割 cache.ts Query Key の定義、キャッシュ操作のためのカスタムフックの定義 queries.ts useQuery をラップしたカスタムフックの定義、API Request/Response の整形 mutations.ts useMutation をラップしたカスタムフックの定義、API Request の整形 cache.ts cache.ts は次のように実装しています。 // ① Query Key // queries.ts がある場合に必要に応じて宣言 export const todoKeys = { all: [ "todo" ] as const , list : () => [... todoKeys . all , "list" ] as const , paginateList : ( page ?: number ) => [... todoKeys . list (), { page }] as const , detail : ( id : string ) => [... todoKeys . all , "detail" , id ] as const , }; // ② キャッシュ操作のためのカスタムフック // mutations.ts がある場合に必要に応じて宣言 export function useTodoCache () { const queryClient = useQueryClient (); return useMemo ( () => ({ invalidateList : () => queryClient . invalidateQueries ( todoKeys . list ()), invalidateDetail : ( id : string ) => queryClient . invalidateQueries ( todoKeys . detail ( id )), }), [ queryClient ] ); } ① Query Key は、 useQuery の queryKey オプションに与える値を定義した定数です。これは後述の queries.ts で利用します。 実装は Tanstack Query メンテナの Dominik さんのブログで紹介されている Query Key factories パターンを使っています。 ディレクトリ名を all の値とすることで、大規模なアプリケーションにおいてもキーの衝突を防ぐことが可能です。 ② キャッシュ操作のためのカスタムフックでは QueryClient を利用したキャッシュ操作をまとめています。これは後述の mutations.ts で利用します。 CLINICS では 楽観的更新 をしない方針としているため、 invalidateQueries を実行する関数群のみを定義しています。 queries.ts queries.ts は次のように実装しています。 import { type Todo } from "@/viewModels/todo" ; import { todoApi , type GetTodoDetailRequest } from "@/api/generated" ; // ③ queryFn export const query = { getTodoList : async (): Promise &#x3C; Todo []> => { const { data } = await todoApi . getTodoList (); return data . todoList ; }, getTodoDetail : async ( request : GetTodoDetailRequest ): Promise &#x3C; Todo > => { const { data } = await todoApi . getTodoDetail ( request ); return data . todo ; }, }; // ④ Request Selector export const request = { getTodoDetail : ( id : string | undefined ): GetTodoDetailRequest => { if ( id === undefined ) { // `enabled: false` となる条件の引数が与えられた場合例外とすることで、 // queryFn 内の型の整合性を保つ throw new InvalidRequestParameterError ( "Required parameter id was undefined when calling request.getTodoDetail." ); } return { id , }; }, }; ③ queryFn は、API へのリクエストを責務とした関数群です。 ここで使用している ApiClient や Request の型は openapi-generator から生成しています。 queryFn の戻り値の型は、後述の ViewModel で定義した型を明示 しています。 このようにフロントエンド側でサーバステートの型を別途定義することで、フロントエンドとバックエンドを分業して実装する際に開発しやすくなるメリットがあります。 省略していますが、レスポンスに応じたエラーの throw もここで行います。 ④ Request Selector は View から受け取った値をリクエストパラメータへマッピングすることを責務とした関数群です。 useQuery の条件付き実行を制御する enabled オプションで false となる条件を例外とすることで、queryFn 内で 型ガードをしなくて良い設計としています。 これらを使って View とのインターフェースとなる useQuery ラッパーを定義します。 // ⑤ Base Query // API Response を整形せずに返す export type UseTodoDetailQueryProps &#x3C; QueryResult > = { id : string | undefined ; select ?: ( data : Todo ) => QueryResult ; }; export function useTodoDetailQuery &#x3C; QueryResult = Todo >( props : UseTodoDetailQueryProps &#x3C; QueryResult > ) { return useQuery ({ queryKey: todoKeys . detail ( props . id ), queryFn : () => { return query . getTodoDetail ( request . getTodoDetail ( props . id )); }, enabled: !! props . id , select: props . select , useErrorBoundary: true , }); } import { selectTodoForm } from "@/viewModels/todo/todoForm" ; // ⑥ Selector Query // API Response を View で参照したいフォーマットに整形して返す export function useInitialTodoFormQuery () { // Base Query に select オプションを与える return useTodoDetailQuery ({ select: selectTodoForm , // selectTodoForm は Todo を TodoForm に変換する ViewModel Selector(後述) }); } useQuery ラッパーは、⑤ Base Query と ⑥ Selector Query に分けて定義しています。 CLINICS では同じデータソースに対して画面ごとに異なるフォーマットで表示することが多くあります。 Selector Query で 任意のフォーマットに整形することで、画面ごとに最適化されたデータの取得をスケーラブルに実現しています。 4 加えて、この手法では Selector にドメインロジックが凝集されるため、 Selector を重点的にテストすることで品質を担保しやすい メリットがあります。 Query のエラー制御は useErrorBoundary オプションを使って Error Boundary を表示する方針としています。 mutations.ts mutations.ts は次のように実装しています。 import { type TodoForm } from "@/viewModels/todo/todoForm" ; import { todoApi , type PostTodoRequest } "@/api/generated" ; // ⑦ mutationFn export const mutation = { createTodo : ( request : PostTodoRequest ) => { return todoApi . postTodo ( request ); }, }; // ⑧ Request Selector // ViewModel から API Request へ変換する export const request = { createTodo : ( todoForm : TodoForm ): PostTodoRequest => { return { PostTodoRequest : { todo : { title : todoForm . title , description : todoForm . description , status : todoForm . status , favorite : todoForm . favorite === "true" ? true : false , }, }, }; }, }; // ⑨ Custom Mutation export function useCreateTodoMutation () { const { invalidateList } = useTodoCache (); return useMutation ({ mutationFn : ( todoForm : TodoForm ) => { return mutation . createTodo ( request . createTodo ( todoForm )); }, onSuccess : () => { return invalidateList (); }, }); } 基本的な構成は queries.ts と同じです。 mutations.ts でも queries.ts と同様に ⑧ Request Selector を定義します。 Request Selector には、データ整形を扱うためドメインロジックが集まりやすいです。 そのため、入出力、境界値、例外のテストを積極的に書いて品質の担保に繋げています。 Mutation のエラーは画面ごとに UI でフィードバックするため、コンポーネント側で制御しています。 Resource Operation レイヤーに関する実装の紹介は以上です。 ViewModel の実装 ViewModel とは、View(React Component) で扱うデータのスキーマと型です。 Resource Operation の実装では、API Response を ViewModel に整形して View に提供することを紹介しました。 全体の理解を深めるため、ViewModel の実装も紹介します。 ディレクトリ構成 ViewModel は関心を分離するため Resource Operation とは別のディレクトリに定義しています。 src/viewModels └── todo ├── todo.ts # サーバステート ├── todoForm.test.ts # フロントエンドに閉じたスキーマのテスト ├── todoForm.ts # フロントエンドに閉じたスキーマ └── todoSearchCondition.ts # フロントエンドに閉じた型 ViewModel で定義するスキーマ・型は大きく分けて 3 つに分類されます。 サーバステート フロントエンドに閉じたスキーマ フロントエンドに閉じた型 サーバステート サーバステートは、 API Response として期待するデータのスキーマ及び型です。 前述の queries.ts 内の ③ queryFn で使用します。 zod を使って次のように実装しています。 // src/viewModels/todo/todo.ts // enum は View で &#x3C;option value={todoStatus.enum.ready} /> のように使用する export const todoStatus = z . enum ([ "ready" , "doing" , "done" ]); // ⑩ サーバステートのスキーマ // 開発中のみ ③ queryFn 内で API Response を parse することで、スキーマの不整合を検出するための補助輪として使用する export const todo = z . object ({ id: z . string (), title: z . string (), description: z . string (). nullable (), status: todoStatus , favorite: z . boolean (), }); // ⑪ サーバステートの型 // ③ queryFn の戻り値の型として使用する export type Todo = z . infer &#x3C; typeof todo >; ⑩ サーバステートのスキーマは、⑪ サーバステートの型の生成と開発時のスキーマ検証に使用しています。 開発時のみ API Response を検証することで、⑩ サーバステートのスキーマと Open API スキーマの整合性を確認しています。 // src/resourceOperations/todo/queries.ts import { todo , type Todo } from "@/viewModels/todo" ; export const query = { getTodoList : async (): Promise &#x3C; Todo []> => { const { data } = await todoApi . getTodoList (); // 開発時、APIとの結合タイミングで検証してフロントエンドとバックエンドでスキーマの齟齬がないことを確認する // 検証が済んだら parse 処理を外す return data . todoList . map (( x ) => todo . parse ( x )); }, }; 例外として、 外部サービスから取得したデータに関しては常にサーバステートのスキーマを使って検証 しています。 例えば、外部サービスの仕様として長さが 1 以上の配列が返ってくると決まっていて、フロントエンド側もその仕様に基づいた処理を実装している場合、 z.array().min(1) のスキーマで常に検証します。 ネットワークに近い箇所で不正なデータを検出することで、例外発生時の調査を容易にするメリットがあると考えています。 フロントエンドに閉じたスキーマ フロントエンドに閉じたスキーマは、そのほとんどがフォームのバリデーションスキーマです。 こちらも zod を使って定義しています。 // src/viewModels/todo/todoForm.ts import { type Todo , todo } from "./todo" ; // フロントエンドに閉じたスキーマ // フォームのスキーマは react-hook-form と連携して使用する(省略) export const todoForm = z . object ({ title: z . string () . min ( 1 , "入力してください" ) . max ( 200 , "200字以内で入力してください" ), description: z . string (). max ( 500 , "500字以内で入力してください" ), status: todo . shape . status , favorite: z . union ([ z . literal ( "true" ), z . literal ( "false" )]), }); export type TodoForm = z . infer &#x3C; typeof todoForm >; // ⑫ ViewModel Selector // サーバステートからフロントエンドに閉じたスキーマへ変換する // 前述の queries.ts 内 ⑥ Selector Query で使用する export function selectTodoForm ( todo : Todo ): TodoForm { return { title: todo . title , description: todo . description ?? "" , status: todo . status , favorite: todo . favorite ? "true" : "false" , }; } フロントエンドに閉じたスキーマは、必ずサーバステートを元に生成する運用としています。 そのために、フロントエンドに閉じたスキーマを宣言した直下に、サーバステートからフロントエンドに閉じたスキーマへ変換する ⑫ ViewModel Selector を定義します。 null の 空文字への変換や時間データのフォーマットのような、 サーバステート と View で使う値の差分吸収はこのセレクタ内で行います。 このように、 ViewModel レイヤーでは他のレイヤーと依存しないようにドメインロジックを表現 しています。 ドメインロジックを Tanstack Query に依存しないことで、今後技術基盤を刷新する場合でも影響を最小限に留めることを狙いとしています。 フロントエンドに閉じた型 依存関係を整えるため、Resource Operation と View から参照するフロントエンドに閉じた型は viewModels 配下に宣言しています。 // src/viewModels/todo/todoSearchParams.ts export type TodoSearchCondition = { sort ?: "created_at_asc" | "created_at_desc" ; }; ViewModel に関する実装の紹介は以上です。 Tanstack Query 導入の背景とアーキテクチャの狙い 背景:コード品質の課題 Tanstack Query を導入する以前の CLINICS の非同期処理周辺のコードベースではいくつかの課題がありました。 開発体験の課題 非同期処理のためのミニマムな基盤フックを独自に実装していた 5 ことにより、開発体験の観点で次のような課題がありました。 新しい要件(ポーリング・無限読み込み等)が発生した際に、最初に担当する開発者が都度機能拡張する必要がある テストが実装されていなかったため変更時に品質確認の負担が大きい 学習容易性の課題 上述の基盤フックにドキュメントがなかったため、学習容易性の観点で次のような課題がありました。 基盤フックにドキュメントがなく、新規メンバーの学習コストが高い 様々な実装手法が混在することで、実装時に迷いが生じている 可読性の課題 CLINICS は開発開始から約 7 年以上が経過しています。 その中でもフロントエンドは技術トレンドの変化が早いため、様々なライブラリやパターンを使って実装されています。 特に近年から漸進的に導入した React を使った実装については、明確な設計が確立されていませんでした。 これらの背景からチームで安定したアウトプットを出すことが困難で、可読性の観点で次のような課題がありました。 画面によってフックや関数の粒度、定義場所が違うことでコードリーディングの負荷が高い コードレビュー時のレビュワーの負担が大きい テスト容易性の課題 多くのドメインロジックがコンポーネントやカスタムフック内に混在していることで、テスト容易性の観点で次のような課題がありました。 テストが実装しづらい テストカバレッジが低い CLINICS はオンライン診療・電子カルテ等の医療機関業務を支える機能を提供する SaaS プラットフォームです。 今後長きに渡って多くの医療機関の方々に CLINICS を利用して頂くためには、 コードを読みやすく、変更しやすく、維持しやすい状態に保ち続けること が重要です。 前の章で紹介したアーキテクチャは 持続可能な開発を目指して 設計しました。 具体的には、 開発体験・学習容易性・可読性・テスト容易性を向上することを狙い としています。 狙い 1:Tanstack Query の導入による開発体験の向上 背景で説明したとおり、Tanstack Query 導入以前は独自実装したフックを使って非同期処理を実装していました。 Tanstack Query を導入したきっかけは、チーム内での雑談の中で、独自実装のフックが使いづらいという声が挙がったことです。 そこで、独自実装のフック自体の質を高めるか、質の高いライブラリを導入するかを議論した結果、次の理由でライブラリを導入する決定をしました。 極力自分たちでコードを書かずに非同期処理・状態管理の実装を実現したい 実装に困ったときにドキュメントを読めば解決する環境にしたい CLINICS では技術的な背景 6 から Tanstack Query と SWR が候補に上がりましたが、 select オプションや invalidateQueries の Partial Query Matching 等の機能性と、ドキュメントの充実度合いの観点から Tanstack Query を採用しました。 Tanstack Query を採用したことで、 非同期処理のためのコードの記述量が大幅に削減 されたほか、データの特性に応じて Query ごとにキャッシュの時間を調整することが可能となり開発体験が大幅に向上しました。 狙い 2:シンプルなレイヤー分割による学習容易性の向上 CLINICS では、事業の拡大にともないコードベースに関わるエンジニアが増え続けています。 実装の進め方はプロジェクトによって最適な形式を選択しています。 バックエンドからフロントエンドまで一気通貫で実装 技術領域に分けて分業 このように多くのエンジニアが様々な形で関わる環境では、コードベースの学習容易性を高め、実装からコードレビューの完了までをスムーズに行えることが重要です。 前の章で紹介したアーキテクチャは、 レイヤー分割をシンプルにすることでコードベースに慣れるまでの時間を最小限にする ことを意識しています。 加えて、 実装パターンを定形化することで、コードベースに馴染みがなくても迷いなく実装できる ほか、関数の粒度が統一されることで、コードレビューの負荷軽減にも繋がっています。 レイヤー分割の粒度や実装パターンについては、次の記事を参考にさせて頂きました。 フロントエンドアーキテクチャの話: Resource Set の紹介 ほかにも CLINICS では学習容易性の向上の取り組みとして、 新しいライブラリやアーキテクチャを導入した際は勉強会を開催 してライブラリの基本的な使い方や頻出の実装パターンに関する知見を共有しています。 狙い 3:ドメインロジックを純粋関数で表現することによる可読性・テスト容易性の向上 アーキテクチャを刷新する以前は、多くのドメインロジックがコンポーネントやカスタムフック内に混在していることで、可読性やテスト容易性に支障をきたしていました。 CLINICS のフロントエンドには、医療システムに関する複雑なドメインロジックが多いため、 シンプルでテストしやすいコードベース を作っていくことがとりわけ重要だと考えています。 この課題は、ドメインロジックが集まる傾向にある queries.ts、 mutations.ts の Request Selector や ViewModel Selector の実装を定型化し、純粋関数で表現することにより解決しました。 まとめ Tanstack Query を使ったフロントエンドアーキテクチャの実例を紹介しました。 Resource Operation レイヤーに Tanstack Query の実装を定型化して集約しています。 レイヤー分割をシンプルにし、実装を定型化することで、開発組織のスケールに対応しています。 useQuery の select オプションを使い、スケーラブルにデータ変換処理を記述しています。 データ変換処理にはドメインロジックが集まりやすいため、なるべく小さい粒度の純粋関数で表現することで、可読性・テスト容易性の向上を狙っています。 この記事の内容が Tanstack Query の導入を考えている方の参考になれば幸いです。 さいごに CLINICS では、機能開発と並行してフロントエンド基盤を改善する取り組みも実施しています。 Redux から Tanstack Query への移行 UI ライブラリの Mithril から React への移行 7 デザインシステムの構築とプロダクトへの反映 このような取り組みに興味がある方は次のリンクから是非ご連絡ください。 https://www.medley.jp/jobs/ Footnotes Overview | TanStack Query Docs ↩ Comparison | React Query vs SWR vs Apollo vs RTK Query vs React Router | TanStack Query Docs ↩ この記事で紹介している ViewModel は View レイヤー専用のモデルを表す概念です。 MVVM アーキテクチャの ViewModel とは異なります。 ↩ Tanstack Query におけるデータ変換手法の詳細は React Query Data Transformations | TkDodo’s blog で紹介されています。 ↩ Tanstack Query 導入以前の非同期処理は useAsync React Hook - useHooks をカスタマイズしたフックを使って実装していました。 ↩ CLINICS では 一部の実装箇所で Redux を使用していますが、 RTK Query は今回採用するライブラリの候補から除外しました。これは、現在使用している Redux のバージョンが低く、レガシーな周辺ライブラリも複数使用している背景で Redux Toolkit への移行に相当な工数を必要とするためです。 ↩ 2023 年 3 月現在、 CLINICS のフロントエンドの 約 50% は、 Mithril と Redux で構成されています。開発体験の向上のため、 React への完全移行を目指して日々改善を続けています。 ↩
こんにちは。医療プラットフォーム第一本部プロダクト開発室所属エンジニアの髙橋です。 普段の業務では、 医療 SaaS プラットフォーム CLINICS の医療機関向けアプリケーション(以下、CLINICS)の開発を担当しています。 CLINICS では、昨年 10 月頃から React コードベースのリアーキテクチャに取り組んでいます。 その取り組みの 1 つとして、非同期状態管理に関連する実装を Tanstack Query を使って刷新しています。 この記事では、CLINICS における Tanstack Query の活用方法を導入背景と狙いを含めて紹介します。 目次 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> Tanstack Query について Tanstack Query を活用したフロントエンドアーキテクチャ Resource Operation の実装 ディレクトリ構成 cache.ts queries.ts mutations.ts ViewModel の実装 ディレクトリ構成 サーバステート フロントエンドに閉じたスキーマ フロントエンドに閉じた型 Tanstack Query 導入の背景とアーキテクチャの狙い 背景:コード品質の課題 狙い 1:Tanstack Query の導入による開発体験の向上 狙い 2:シンプルなレイヤー分割による学習容易性の向上 狙い 3:ドメインロジックを純粋関数で表現することによる可読性・テスト容易性の向上 まとめ さいごに Tanstack Query について Tanstack Query は、Web アプリケーションのサーバ状態の取得、キャッシュ、同期、更新を簡単に行うことができるライブラリです。 1 類似ライブラリには SWR、Apollo Client、RTK Query 等が挙げられます。 2 私たちは、機能性・ドキュメントの充実度・コミュニティの将来性を総合的に判断した結果、Tanstack Query(React Query)を採用して React コードベースの再構築を進めています。 導入背景の詳細は、後のセクションで詳しく紹介します。 Tanstack Query を活用したフロントエンドアーキテクチャ それでは本題の Tanstack Query を活用したフロントエンドアーキテクチャについて紹介します。 始めに、アーキテクチャの全体像を示した次の図をご覧ください。 まず注目して頂きたいポイントは、Backend と View の間にある Resource Operation です。 Resource Operation は Tanstack Query を主軸に実装されているレイヤーです。 役割を大きく分類すると次の 2 つが挙げられます。 非同期状態管理に関連する実装の集約 Backend で扱うデータ(OpenAPI スキーマ)と View で扱うデータ(ViewModel)の相互変換 全体像を把握するために、左右のレイヤーにも注目してください。 右の Backend は CLINICS では Ruby on Rails で実装された REST API を提供するモノリシックなサーバです。 API で扱うスキーマは OpenAPI を使って定義しています。 OpenAPI スキーマは、 committee-rails を使ったレスポンス検証と、 openapi-generator を使った API クライアントコードの自動生成に活用しています。 左の View は CLINICS では React で実装されたコンポーネントになります。 View では Backend の OpenAPI スキーマを直接参照することを避け、 ViewModel と呼ばれるフロントエンドで定義したモデル 3 のみを参照する設計としています。 Resource Operation は View と Backend の境界レイヤーとして非同期状態管理とデータの相互変換を行うシンプルなレイヤーとなっています。 続いて、上記の運用のための実装詳細を紹介します。 Resource Operation の実装 ディレクトリ構成 Resource Operation レイヤーでは、リソースごとに非同期状態管理とデータの相互変換の処理をまとめています。 ディレクトリツリーで Resource Operation レイヤー全体を表現すると次のようになります。 src/resourceOperations ├── resourceTagName # 例: todo │ ├── cache.ts │ ├── mutations.ts │ └── queries.ts └── resourceGroupTagName # 例: systemSettings(リソースに階層がある場合) └── resourceTagName # 例: organization(リソースグループ配下のリソース) ├── cache.ts ├── mutations.ts └── queries.ts ディレクトリ名の resourceTagName と resourceGroupTagName は OpenAPI スキーマでエンドポイントごとに割り振られている tag を元に命名しています。 ディレクトリごとに 次の 3 つのファイルを定義しています。 ファイル 役割 cache.ts Query Key の定義、キャッシュ操作のためのカスタムフックの定義 queries.ts useQuery をラップしたカスタムフックの定義、API Request/Response の整形 mutations.ts useMutation をラップしたカスタムフックの定義、API Request の整形 cache.ts cache.ts は次のように実装しています。 // ① Query Key // queries.ts がある場合に必要に応じて宣言 export const todoKeys = { all: [ "todo" ] as const , list : () => [... todoKeys . all , "list" ] as const , paginateList : ( page ?: number ) => [... todoKeys . list (), { page }] as const , detail : ( id : string ) => [... todoKeys . all , "detail" , id ] as const , }; // ② キャッシュ操作のためのカスタムフック // mutations.ts がある場合に必要に応じて宣言 export function useTodoCache () { const queryClient = useQueryClient (); return useMemo ( () => ({ invalidateList : () => queryClient . invalidateQueries ( todoKeys . list ()), invalidateDetail : ( id : string ) => queryClient . invalidateQueries ( todoKeys . detail ( id )), }), [ queryClient ] ); } ① Query Key は、 useQuery の queryKey オプションに与える値を定義した定数です。これは後述の queries.ts で利用します。 実装は Tanstack Query メンテナの Dominik さんのブログで紹介されている Query Key factories パターンを使っています。 ディレクトリ名を all の値とすることで、大規模なアプリケーションにおいてもキーの衝突を防ぐことが可能です。 ② キャッシュ操作のためのカスタムフックでは QueryClient を利用したキャッシュ操作をまとめています。これは後述の mutations.ts で利用します。 CLINICS では 楽観的更新 をしない方針としているため、 invalidateQueries を実行する関数群のみを定義しています。 queries.ts queries.ts は次のように実装しています。 import { type Todo } from "@/viewModels/todo" ; import { todoApi , type GetTodoDetailRequest } from "@/api/generated" ; // ③ queryFn export const query = { getTodoList : async (): Promise &#x3C; Todo []> => { const { data } = await todoApi . getTodoList (); return data . todoList ; }, getTodoDetail : async ( request : GetTodoDetailRequest ): Promise &#x3C; Todo > => { const { data } = await todoApi . getTodoDetail ( request ); return data . todo ; }, }; // ④ Request Selector export const request = { getTodoDetail : ( id : string | undefined ): GetTodoDetailRequest => { if ( id === undefined ) { // `enabled: false` となる条件の引数が与えられた場合例外とすることで、 // queryFn 内の型の整合性を保つ throw new InvalidRequestParameterError ( "Required parameter id was undefined when calling request.getTodoDetail." ); } return { id , }; }, }; ③ queryFn は、API へのリクエストを責務とした関数群です。 ここで使用している ApiClient や Request の型は openapi-generator から生成しています。 queryFn の戻り値の型は、後述の ViewModel で定義した型を明示 しています。 このようにフロントエンド側でサーバステートの型を別途定義することで、フロントエンドとバックエンドを分業して実装する際に開発しやすくなるメリットがあります。 省略していますが、レスポンスに応じたエラーの throw もここで行います。 ④ Request Selector は View から受け取った値をリクエストパラメータへマッピングすることを責務とした関数群です。 useQuery の条件付き実行を制御する enabled オプションで false となる条件を例外とすることで、queryFn 内で 型ガードをしなくて良い設計としています。 これらを使って View とのインターフェースとなる useQuery ラッパーを定義します。 // ⑤ Base Query // API Response を整形せずに返す export type UseTodoDetailQueryProps &#x3C; QueryResult > = { id : string | undefined ; select ?: ( data : Todo ) => QueryResult ; }; export function useTodoDetailQuery &#x3C; QueryResult = Todo >( props : UseTodoDetailQueryProps &#x3C; QueryResult > ) { return useQuery ({ queryKey: todoKeys . detail ( props . id ), queryFn : () => { return query . getTodoDetail ( request . getTodoDetail ( props . id )); }, enabled: !! props . id , select: props . select , useErrorBoundary: true , }); } import { selectTodoForm } from "@/viewModels/todo/todoForm" ; // ⑥ Selector Query // API Response を View で参照したいフォーマットに整形して返す export function useInitialTodoFormQuery () { // Base Query に select オプションを与える return useTodoDetailQuery ({ select: selectTodoForm , // selectTodoForm は Todo を TodoForm に変換する ViewModel Selector(後述) }); } useQuery ラッパーは、⑤ Base Query と ⑥ Selector Query に分けて定義しています。 CLINICS では同じデータソースに対して画面ごとに異なるフォーマットで表示することが多くあります。 Selector Query で 任意のフォーマットに整形することで、画面ごとに最適化されたデータの取得をスケーラブルに実現しています。 4 加えて、この手法では Selector にドメインロジックが凝集されるため、 Selector を重点的にテストすることで品質を担保しやすい メリットがあります。 Query のエラー制御は useErrorBoundary オプションを使って Error Boundary を表示する方針としています。 mutations.ts mutations.ts は次のように実装しています。 import { type TodoForm } from "@/viewModels/todo/todoForm" ; import { todoApi , type PostTodoRequest } "@/api/generated" ; // ⑦ mutationFn export const mutation = { createTodo : ( request : PostTodoRequest ) => { return todoApi . postTodo ( request ); }, }; // ⑧ Request Selector // ViewModel から API Request へ変換する export const request = { createTodo : ( todoForm : TodoForm ): PostTodoRequest => { return { PostTodoRequest : { todo : { title : todoForm . title , description : todoForm . description , status : todoForm . status , favorite : todoForm . favorite === "true" ? true : false , }, }, }; }, }; // ⑨ Custom Mutation export function useCreateTodoMutation () { const { invalidateList } = useTodoCache (); return useMutation ({ mutationFn : ( todoForm : TodoForm ) => { return mutation . createTodo ( request . createTodo ( todoForm )); }, onSuccess : () => { return invalidateList (); }, }); } 基本的な構成は queries.ts と同じです。 mutations.ts でも queries.ts と同様に ⑧ Request Selector を定義します。 Request Selector には、データ整形を扱うためドメインロジックが集まりやすいです。 そのため、入出力、境界値、例外のテストを積極的に書いて品質の担保に繋げています。 Mutation のエラーは画面ごとに UI でフィードバックするため、コンポーネント側で制御しています。 Resource Operation レイヤーに関する実装の紹介は以上です。 ViewModel の実装 ViewModel とは、View(React Component) で扱うデータのスキーマと型です。 Resource Operation の実装では、API Response を ViewModel に整形して View に提供することを紹介しました。 全体の理解を深めるため、ViewModel の実装も紹介します。 ディレクトリ構成 ViewModel は関心を分離するため Resource Operation とは別のディレクトリに定義しています。 src/viewModels └── todo ├── todo.ts # サーバステート ├── todoForm.test.ts # フロントエンドに閉じたスキーマのテスト ├── todoForm.ts # フロントエンドに閉じたスキーマ └── todoSearchCondition.ts # フロントエンドに閉じた型 ViewModel で定義するスキーマ・型は大きく分けて 3 つに分類されます。 サーバステート フロントエンドに閉じたスキーマ フロントエンドに閉じた型 サーバステート サーバステートは、 API Response として期待するデータのスキーマ及び型です。 前述の queries.ts 内の ③ queryFn で使用します。 zod を使って次のように実装しています。 // src/viewModels/todo/todo.ts // enum は View で &#x3C;option value={todoStatus.enum.ready} /> のように使用する export const todoStatus = z . enum ([ "ready" , "doing" , "done" ]); // ⑩ サーバステートのスキーマ // 開発中のみ ③ queryFn 内で API Response を parse することで、スキーマの不整合を検出するための補助輪として使用する export const todo = z . object ({ id: z . string (), title: z . string (), description: z . string (). nullable (), status: todoStatus , favorite: z . boolean (), }); // ⑪ サーバステートの型 // ③ queryFn の戻り値の型として使用する export type Todo = z . infer &#x3C; typeof todo >; ⑩ サーバステートのスキーマは、⑪ サーバステートの型の生成と開発時のスキーマ検証に使用しています。 開発時のみ API Response を検証することで、⑩ サーバステートのスキーマと Open API スキーマの整合性を確認しています。 // src/resourceOperations/todo/queries.ts import { todo , type Todo } from "@/viewModels/todo" ; export const query = { getTodoList : async (): Promise &#x3C; Todo []> => { const { data } = await todoApi . getTodoList (); // 開発時、APIとの結合タイミングで検証してフロントエンドとバックエンドでスキーマの齟齬がないことを確認する // 検証が済んだら parse 処理を外す return data . todoList . map (( x ) => todo . parse ( x )); }, }; 例外として、 外部サービスから取得したデータに関しては常にサーバステートのスキーマを使って検証 しています。 例えば、外部サービスの仕様として長さが 1 以上の配列が返ってくると決まっていて、フロントエンド側もその仕様に基づいた処理を実装している場合、 z.array().min(1) のスキーマで常に検証します。 ネットワークに近い箇所で不正なデータを検出することで、例外発生時の調査を容易にするメリットがあると考えています。 フロントエンドに閉じたスキーマ フロントエンドに閉じたスキーマは、そのほとんどがフォームのバリデーションスキーマです。 こちらも zod を使って定義しています。 // src/viewModels/todo/todoForm.ts import { type Todo , todo } from "./todo" ; // フロントエンドに閉じたスキーマ // フォームのスキーマは react-hook-form と連携して使用する(省略) export const todoForm = z . object ({ title: z . string () . min ( 1 , "入力してください" ) . max ( 200 , "200字以内で入力してください" ), description: z . string (). max ( 500 , "500字以内で入力してください" ), status: todo . shape . status , favorite: z . union ([ z . literal ( "true" ), z . literal ( "false" )]), }); export type TodoForm = z . infer &#x3C; typeof todoForm >; // ⑫ ViewModel Selector // サーバステートからフロントエンドに閉じたスキーマへ変換する // 前述の queries.ts 内 ⑥ Selector Query で使用する export function selectTodoForm ( todo : Todo ): TodoForm { return { title: todo . title , description: todo . description ?? "" , status: todo . status , favorite: todo . favorite ? "true" : "false" , }; } フロントエンドに閉じたスキーマは、必ずサーバステートを元に生成する運用としています。 そのために、フロントエンドに閉じたスキーマを宣言した直下に、サーバステートからフロントエンドに閉じたスキーマへ変換する ⑫ ViewModel Selector を定義します。 null の 空文字への変換や時間データのフォーマットのような、 サーバステート と View で使う値の差分吸収はこのセレクタ内で行います。 このように、 ViewModel レイヤーでは他のレイヤーと依存しないようにドメインロジックを表現 しています。 ドメインロジックを Tanstack Query に依存しないことで、今後技術基盤を刷新する場合でも影響を最小限に留めることを狙いとしています。 フロントエンドに閉じた型 依存関係を整えるため、Resource Operation と View から参照するフロントエンドに閉じた型は viewModels 配下に宣言しています。 // src/viewModels/todo/todoSearchParams.ts export type TodoSearchCondition = { sort ?: "created_at_asc" | "created_at_desc" ; }; ViewModel に関する実装の紹介は以上です。 Tanstack Query 導入の背景とアーキテクチャの狙い 背景:コード品質の課題 Tanstack Query を導入する以前の CLINICS の非同期処理周辺のコードベースではいくつかの課題がありました。 開発体験の課題 非同期処理のためのミニマムな基盤フックを独自に実装していた 5 ことにより、開発体験の観点で次のような課題がありました。 新しい要件(ポーリング・無限読み込み等)が発生した際に、最初に担当する開発者が都度機能拡張する必要がある テストが実装されていなかったため変更時に品質確認の負担が大きい 学習容易性の課題 上述の基盤フックにドキュメントがなかったため、学習容易性の観点で次のような課題がありました。 基盤フックにドキュメントがなく、新規メンバーの学習コストが高い 様々な実装手法が混在することで、実装時に迷いが生じている 可読性の課題 CLINICS は開発開始から約 7 年以上が経過しています。 その中でもフロントエンドは技術トレンドの変化が早いため、様々なライブラリやパターンを使って実装されています。 特に近年から漸進的に導入した React を使った実装については、明確な設計が確立されていませんでした。 これらの背景からチームで安定したアウトプットを出すことが困難で、可読性の観点で次のような課題がありました。 画面によってフックや関数の粒度、定義場所が違うことでコードリーディングの負荷が高い コードレビュー時のレビュワーの負担が大きい テスト容易性の課題 多くのドメインロジックがコンポーネントやカスタムフック内に混在していることで、テスト容易性の観点で次のような課題がありました。 テストが実装しづらい テストカバレッジが低い CLINICS はオンライン診療・電子カルテ等の医療機関業務を支える機能を提供する SaaS プラットフォームです。 今後長きに渡って多くの医療機関の方々に CLINICS を利用して頂くためには、 コードを読みやすく、変更しやすく、維持しやすい状態に保ち続けること が重要です。 前の章で紹介したアーキテクチャは 持続可能な開発を目指して 設計しました。 具体的には、 開発体験・学習容易性・可読性・テスト容易性を向上することを狙い としています。 狙い 1:Tanstack Query の導入による開発体験の向上 背景で説明したとおり、Tanstack Query 導入以前は独自実装したフックを使って非同期処理を実装していました。 Tanstack Query を導入したきっかけは、チーム内での雑談の中で、独自実装のフックが使いづらいという声が挙がったことです。 そこで、独自実装のフック自体の質を高めるか、質の高いライブラリを導入するかを議論した結果、次の理由でライブラリを導入する決定をしました。 極力自分たちでコードを書かずに非同期処理・状態管理の実装を実現したい 実装に困ったときにドキュメントを読めば解決する環境にしたい CLINICS では技術的な背景 6 から Tanstack Query と SWR が候補に上がりましたが、 select オプションや invalidateQueries の Partial Query Matching 等の機能性と、ドキュメントの充実度合いの観点から Tanstack Query を採用しました。 Tanstack Query を採用したことで、 非同期処理のためのコードの記述量が大幅に削減 されたほか、データの特性に応じて Query ごとにキャッシュの時間を調整することが可能となり開発体験が大幅に向上しました。 狙い 2:シンプルなレイヤー分割による学習容易性の向上 CLINICS では、事業の拡大にともないコードベースに関わるエンジニアが増え続けています。 実装の進め方はプロジェクトによって最適な形式を選択しています。 バックエンドからフロントエンドまで一気通貫で実装 技術領域に分けて分業 このように多くのエンジニアが様々な形で関わる環境では、コードベースの学習容易性を高め、実装からコードレビューの完了までをスムーズに行えることが重要です。 前の章で紹介したアーキテクチャは、 レイヤー分割をシンプルにすることでコードベースに慣れるまでの時間を最小限にする ことを意識しています。 加えて、 実装パターンを定形化することで、コードベースに馴染みがなくても迷いなく実装できる ほか、関数の粒度が統一されることで、コードレビューの負荷軽減にも繋がっています。 レイヤー分割の粒度や実装パターンについては、次の記事を参考にさせて頂きました。 フロントエンドアーキテクチャの話: Resource Set の紹介 ほかにも CLINICS では学習容易性の向上の取り組みとして、 新しいライブラリやアーキテクチャを導入した際は勉強会を開催 してライブラリの基本的な使い方や頻出の実装パターンに関する知見を共有しています。 狙い 3:ドメインロジックを純粋関数で表現することによる可読性・テスト容易性の向上 アーキテクチャを刷新する以前は、多くのドメインロジックがコンポーネントやカスタムフック内に混在していることで、可読性やテスト容易性に支障をきたしていました。 CLINICS のフロントエンドには、医療システムに関する複雑なドメインロジックが多いため、 シンプルでテストしやすいコードベース を作っていくことがとりわけ重要だと考えています。 この課題は、ドメインロジックが集まる傾向にある queries.ts、 mutations.ts の Request Selector や ViewModel Selector の実装を定型化し、純粋関数で表現することにより解決しました。 まとめ Tanstack Query を使ったフロントエンドアーキテクチャの実例を紹介しました。 Resource Operation レイヤーに Tanstack Query の実装を定型化して集約しています。 レイヤー分割をシンプルにし、実装を定型化することで、開発組織のスケールに対応しています。 useQuery の select オプションを使い、スケーラブルにデータ変換処理を記述しています。 データ変換処理にはドメインロジックが集まりやすいため、なるべく小さい粒度の純粋関数で表現することで、可読性・テスト容易性の向上を狙っています。 この記事の内容が Tanstack Query の導入を考えている方の参考になれば幸いです。 さいごに CLINICS では、機能開発と並行してフロントエンド基盤を改善する取り組みも実施しています。 Redux から Tanstack Query への移行 UI ライブラリの Mithril から React への移行 7 デザインシステムの構築とプロダクトへの反映 このような取り組みに興味がある方は次のリンクから是非ご連絡ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp Footnotes Overview | TanStack Query Docs ↩ Comparison | React Query vs SWR vs Apollo vs RTK Query vs React Router | TanStack Query Docs ↩ この記事で紹介している ViewModel は View レイヤー専用のモデルを表す概念です。 MVVM アーキテクチャの ViewModel とは異なります。 ↩ Tanstack Query におけるデータ変換手法の詳細は React Query Data Transformations | TkDodo’s blog で紹介されています。 ↩ Tanstack Query 導入以前の非同期処理は useAsync React Hook - useHooks をカスタマイズしたフックを使って実装していました。 ↩ CLINICS では 一部の実装箇所で Redux を使用していますが、 RTK Query は今回採用するライブラリの候補から除外しました。これは、現在使用している Redux のバージョンが低く、レガシーな周辺ライブラリも複数使用している背景で Redux Toolkit への移行に相当な工数を必要とするためです。 ↩ 2023 年 3 月現在、 CLINICS のフロントエンドの 約 50% は、 Mithril と Redux で構成されています。開発体験の向上のため、 React への完全移行を目指して日々改善を続けています。 ↩
こんにちは。医療プラットフォーム第一本部プロダクト開発室所属エンジニアの髙橋です。 普段の業務では、 医療 SaaS プラットフォーム CLINICS の医療機関向けアプリケーション(以下、CLINICS)の開発を担当しています。 CLINICS では、昨年 10 月頃から React コードベースのリアーキテクチャに取り組んでいます。 その取り組みの 1 つとして、非同期状態管理に関連する実装を Tanstack Query を使って刷新しています。 この記事では、CLINICS における Tanstack Query の活用方法を導入背景と狙いを含めて紹介します。 目次 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> Tanstack Query について Tanstack Query を活用したフロントエンドアーキテクチャ Resource Operation の実装 ディレクトリ構成 cache.ts queries.ts mutations.ts ViewModel の実装 ディレクトリ構成 サーバステート フロントエンドに閉じたスキーマ フロントエンドに閉じた型 Tanstack Query 導入の背景とアーキテクチャの狙い 背景:コード品質の課題 狙い 1:Tanstack Query の導入による開発体験の向上 狙い 2:シンプルなレイヤー分割による学習容易性の向上 狙い 3:ドメインロジックを純粋関数で表現することによる可読性・テスト容易性の向上 まとめ さいごに Tanstack Query について Tanstack Query は、Web アプリケーションのサーバ状態の取得、キャッシュ、同期、更新を簡単に行うことができるライブラリです。 1 類似ライブラリには SWR、Apollo Client、RTK Query 等が挙げられます。 2 私たちは、機能性・ドキュメントの充実度・コミュニティの将来性を総合的に判断した結果、Tanstack Query(React Query)を採用して React コードベースの再構築を進めています。 導入背景の詳細は、後のセクションで詳しく紹介します。 Tanstack Query を活用したフロントエンドアーキテクチャ それでは本題の Tanstack Query を活用したフロントエンドアーキテクチャについて紹介します。 始めに、アーキテクチャの全体像を示した次の図をご覧ください。 まず注目して頂きたいポイントは、Backend と View の間にある Resource Operation です。 Resource Operation は Tanstack Query を主軸に実装されているレイヤーです。 役割を大きく分類すると次の 2 つが挙げられます。 非同期状態管理に関連する実装の集約 Backend で扱うデータ(OpenAPI スキーマ)と View で扱うデータ(ViewModel)の相互変換 全体像を把握するために、左右のレイヤーにも注目してください。 右の Backend は CLINICS では Ruby on Rails で実装された REST API を提供するモノリシックなサーバです。 API で扱うスキーマは OpenAPI を使って定義しています。 OpenAPI スキーマは、 committee-rails を使ったレスポンス検証と、 openapi-generator を使った API クライアントコードの自動生成に活用しています。 左の View は CLINICS では React で実装されたコンポーネントになります。 View では Backend の OpenAPI スキーマを直接参照することを避け、 ViewModel と呼ばれるフロントエンドで定義したモデル 3 のみを参照する設計としています。 Resource Operation は View と Backend の境界レイヤーとして非同期状態管理とデータの相互変換を行うシンプルなレイヤーとなっています。 続いて、上記の運用のための実装詳細を紹介します。 Resource Operation の実装 ディレクトリ構成 Resource Operation レイヤーでは、リソースごとに非同期状態管理とデータの相互変換の処理をまとめています。 ディレクトリツリーで Resource Operation レイヤー全体を表現すると次のようになります。 src/resourceOperations ├── resourceTagName # 例: todo │ ├── cache.ts │ ├── mutations.ts │ └── queries.ts └── resourceGroupTagName # 例: systemSettings(リソースに階層がある場合) └── resourceTagName # 例: organization(リソースグループ配下のリソース) ├── cache.ts ├── mutations.ts └── queries.ts ディレクトリ名の resourceTagName と resourceGroupTagName は OpenAPI スキーマでエンドポイントごとに割り振られている tag を元に命名しています。 ディレクトリごとに 次の 3 つのファイルを定義しています。 ファイル 役割 cache.ts Query Key の定義、キャッシュ操作のためのカスタムフックの定義 queries.ts useQuery をラップしたカスタムフックの定義、API Request/Response の整形 mutations.ts useMutation をラップしたカスタムフックの定義、API Request の整形 cache.ts cache.ts は次のように実装しています。 // ① Query Key // queries.ts がある場合に必要に応じて宣言 export const todoKeys = { all: [ "todo" ] as const , list : () => [... todoKeys . all , "list" ] as const , paginateList : ( page ?: number ) => [... todoKeys . list (), { page }] as const , detail : ( id : string ) => [... todoKeys . all , "detail" , id ] as const , }; // ② キャッシュ操作のためのカスタムフック // mutations.ts がある場合に必要に応じて宣言 export function useTodoCache () { const queryClient = useQueryClient (); return useMemo ( () => ({ invalidateList : () => queryClient . invalidateQueries ( todoKeys . list ()), invalidateDetail : ( id : string ) => queryClient . invalidateQueries ( todoKeys . detail ( id )), }), [ queryClient ] ); } ① Query Key は、 useQuery の queryKey オプションに与える値を定義した定数です。これは後述の queries.ts で利用します。 実装は Tanstack Query メンテナの Dominik さんのブログで紹介されている Query Key factories パターンを使っています。 ディレクトリ名を all の値とすることで、大規模なアプリケーションにおいてもキーの衝突を防ぐことが可能です。 ② キャッシュ操作のためのカスタムフックでは QueryClient を利用したキャッシュ操作をまとめています。これは後述の mutations.ts で利用します。 CLINICS では 楽観的更新 をしない方針としているため、 invalidateQueries を実行する関数群のみを定義しています。 queries.ts queries.ts は次のように実装しています。 import { type Todo } from "@/viewModels/todo" ; import { todoApi , type GetTodoDetailRequest } from "@/api/generated" ; // ③ queryFn export const query = { getTodoList : async (): Promise &#x3C; Todo []> => { const { data } = await todoApi . getTodoList (); return data . todoList ; }, getTodoDetail : async ( request : GetTodoDetailRequest ): Promise &#x3C; Todo > => { const { data } = await todoApi . getTodoDetail ( request ); return data . todo ; }, }; // ④ Request Selector export const request = { getTodoDetail : ( id : string | undefined ): GetTodoDetailRequest => { if ( id === undefined ) { // `enabled: false` となる条件の引数が与えられた場合例外とすることで、 // queryFn 内の型の整合性を保つ throw new InvalidRequestParameterError ( "Required parameter id was undefined when calling request.getTodoDetail." ); } return { id , }; }, }; ③ queryFn は、API へのリクエストを責務とした関数群です。 ここで使用している ApiClient や Request の型は openapi-generator から生成しています。 queryFn の戻り値の型は、後述の ViewModel で定義した型を明示 しています。 このようにフロントエンド側でサーバステートの型を別途定義することで、フロントエンドとバックエンドを分業して実装する際に開発しやすくなるメリットがあります。 省略していますが、レスポンスに応じたエラーの throw もここで行います。 ④ Request Selector は View から受け取った値をリクエストパラメータへマッピングすることを責務とした関数群です。 useQuery の条件付き実行を制御する enabled オプションで false となる条件を例外とすることで、queryFn 内で 型ガードをしなくて良い設計としています。 これらを使って View とのインターフェースとなる useQuery ラッパーを定義します。 // ⑤ Base Query // API Response を整形せずに返す export type UseTodoDetailQueryProps &#x3C; QueryResult > = { id : string | undefined ; select ?: ( data : Todo ) => QueryResult ; }; export function useTodoDetailQuery &#x3C; QueryResult = Todo >( props : UseTodoDetailQueryProps &#x3C; QueryResult > ) { return useQuery ({ queryKey: todoKeys . detail ( props . id ), queryFn : () => { return query . getTodoDetail ( request . getTodoDetail ( props . id )); }, enabled: !! props . id , select: props . select , useErrorBoundary: true , }); } import { selectTodoForm } from "@/viewModels/todo/todoForm" ; // ⑥ Selector Query // API Response を View で参照したいフォーマットに整形して返す export function useInitialTodoFormQuery () { // Base Query に select オプションを与える return useTodoDetailQuery ({ select: selectTodoForm , // selectTodoForm は Todo を TodoForm に変換する ViewModel Selector(後述) }); } useQuery ラッパーは、⑤ Base Query と ⑥ Selector Query に分けて定義しています。 CLINICS では同じデータソースに対して画面ごとに異なるフォーマットで表示することが多くあります。 Selector Query で 任意のフォーマットに整形することで、画面ごとに最適化されたデータの取得をスケーラブルに実現しています。 4 加えて、この手法では Selector にドメインロジックが凝集されるため、 Selector を重点的にテストすることで品質を担保しやすい メリットがあります。 Query のエラー制御は useErrorBoundary オプションを使って Error Boundary を表示する方針としています。 mutations.ts mutations.ts は次のように実装しています。 import { type TodoForm } from "@/viewModels/todo/todoForm" ; import { todoApi , type PostTodoRequest } "@/api/generated" ; // ⑦ mutationFn export const mutation = { createTodo : ( request : PostTodoRequest ) => { return todoApi . postTodo ( request ); }, }; // ⑧ Request Selector // ViewModel から API Request へ変換する export const request = { createTodo : ( todoForm : TodoForm ): PostTodoRequest => { return { PostTodoRequest : { todo : { title : todoForm . title , description : todoForm . description , status : todoForm . status , favorite : todoForm . favorite === "true" ? true : false , }, }, }; }, }; // ⑨ Custom Mutation export function useCreateTodoMutation () { const { invalidateList } = useTodoCache (); return useMutation ({ mutationFn : ( todoForm : TodoForm ) => { return mutation . createTodo ( request . createTodo ( todoForm )); }, onSuccess : () => { return invalidateList (); }, }); } 基本的な構成は queries.ts と同じです。 mutations.ts でも queries.ts と同様に ⑧ Request Selector を定義します。 Request Selector には、データ整形を扱うためドメインロジックが集まりやすいです。 そのため、入出力、境界値、例外のテストを積極的に書いて品質の担保に繋げています。 Mutation のエラーは画面ごとに UI でフィードバックするため、コンポーネント側で制御しています。 Resource Operation レイヤーに関する実装の紹介は以上です。 ViewModel の実装 ViewModel とは、View(React Component) で扱うデータのスキーマと型です。 Resource Operation の実装では、API Response を ViewModel に整形して View に提供することを紹介しました。 全体の理解を深めるため、ViewModel の実装も紹介します。 ディレクトリ構成 ViewModel は関心を分離するため Resource Operation とは別のディレクトリに定義しています。 src/viewModels └── todo ├── todo.ts # サーバステート ├── todoForm.test.ts # フロントエンドに閉じたスキーマのテスト ├── todoForm.ts # フロントエンドに閉じたスキーマ └── todoSearchCondition.ts # フロントエンドに閉じた型 ViewModel で定義するスキーマ・型は大きく分けて 3 つに分類されます。 サーバステート フロントエンドに閉じたスキーマ フロントエンドに閉じた型 サーバステート サーバステートは、 API Response として期待するデータのスキーマ及び型です。 前述の queries.ts 内の ③ queryFn で使用します。 zod を使って次のように実装しています。 // src/viewModels/todo/todo.ts // enum は View で &#x3C;option value={todoStatus.enum.ready} /> のように使用する export const todoStatus = z . enum ([ "ready" , "doing" , "done" ]); // ⑩ サーバステートのスキーマ // 開発中のみ ③ queryFn 内で API Response を parse することで、スキーマの不整合を検出するための補助輪として使用する export const todo = z . object ({ id: z . string (), title: z . string (), description: z . string (). nullable (), status: todoStatus , favorite: z . boolean (), }); // ⑪ サーバステートの型 // ③ queryFn の戻り値の型として使用する export type Todo = z . infer &#x3C; typeof todo >; ⑩ サーバステートのスキーマは、⑪ サーバステートの型の生成と開発時のスキーマ検証に使用しています。 開発時のみ API Response を検証することで、⑩ サーバステートのスキーマと Open API スキーマの整合性を確認しています。 // src/resourceOperations/todo/queries.ts import { todo , type Todo } from "@/viewModels/todo" ; export const query = { getTodoList : async (): Promise &#x3C; Todo []> => { const { data } = await todoApi . getTodoList (); // 開発時、APIとの結合タイミングで検証してフロントエンドとバックエンドでスキーマの齟齬がないことを確認する // 検証が済んだら parse 処理を外す return data . todoList . map (( x ) => todo . parse ( x )); }, }; 例外として、 外部サービスから取得したデータに関しては常にサーバステートのスキーマを使って検証 しています。 例えば、外部サービスの仕様として長さが 1 以上の配列が返ってくると決まっていて、フロントエンド側もその仕様に基づいた処理を実装している場合、 z.array().min(1) のスキーマで常に検証します。 ネットワークに近い箇所で不正なデータを検出することで、例外発生時の調査を容易にするメリットがあると考えています。 フロントエンドに閉じたスキーマ フロントエンドに閉じたスキーマは、そのほとんどがフォームのバリデーションスキーマです。 こちらも zod を使って定義しています。 // src/viewModels/todo/todoForm.ts import { type Todo , todo } from "./todo" ; // フロントエンドに閉じたスキーマ // フォームのスキーマは react-hook-form と連携して使用する(省略) export const todoForm = z . object ({ title: z . string () . min ( 1 , "入力してください" ) . max ( 200 , "200字以内で入力してください" ), description: z . string (). max ( 500 , "500字以内で入力してください" ), status: todo . shape . status , favorite: z . union ([ z . literal ( "true" ), z . literal ( "false" )]), }); export type TodoForm = z . infer &#x3C; typeof todoForm >; // ⑫ ViewModel Selector // サーバステートからフロントエンドに閉じたスキーマへ変換する // 前述の queries.ts 内 ⑥ Selector Query で使用する export function selectTodoForm ( todo : Todo ): TodoForm { return { title: todo . title , description: todo . description ?? "" , status: todo . status , favorite: todo . favorite ? "true" : "false" , }; } フロントエンドに閉じたスキーマは、必ずサーバステートを元に生成する運用としています。 そのために、フロントエンドに閉じたスキーマを宣言した直下に、サーバステートからフロントエンドに閉じたスキーマへ変換する ⑫ ViewModel Selector を定義します。 null の 空文字への変換や時間データのフォーマットのような、 サーバステート と View で使う値の差分吸収はこのセレクタ内で行います。 このように、 ViewModel レイヤーでは他のレイヤーと依存しないようにドメインロジックを表現 しています。 ドメインロジックを Tanstack Query に依存しないことで、今後技術基盤を刷新する場合でも影響を最小限に留めることを狙いとしています。 フロントエンドに閉じた型 依存関係を整えるため、Resource Operation と View から参照するフロントエンドに閉じた型は viewModels 配下に宣言しています。 // src/viewModels/todo/todoSearchParams.ts export type TodoSearchCondition = { sort ?: "created_at_asc" | "created_at_desc" ; }; ViewModel に関する実装の紹介は以上です。 Tanstack Query 導入の背景とアーキテクチャの狙い 背景:コード品質の課題 Tanstack Query を導入する以前の CLINICS の非同期処理周辺のコードベースではいくつかの課題がありました。 開発体験の課題 非同期処理のためのミニマムな基盤フックを独自に実装していた 5 ことにより、開発体験の観点で次のような課題がありました。 新しい要件(ポーリング・無限読み込み等)が発生した際に、最初に担当する開発者が都度機能拡張する必要がある テストが実装されていなかったため変更時に品質確認の負担が大きい 学習容易性の課題 上述の基盤フックにドキュメントがなかったため、学習容易性の観点で次のような課題がありました。 基盤フックにドキュメントがなく、新規メンバーの学習コストが高い 様々な実装手法が混在することで、実装時に迷いが生じている 可読性の課題 CLINICS は開発開始から約 7 年以上が経過しています。 その中でもフロントエンドは技術トレンドの変化が早いため、様々なライブラリやパターンを使って実装されています。 特に近年から漸進的に導入した React を使った実装については、明確な設計が確立されていませんでした。 これらの背景からチームで安定したアウトプットを出すことが困難で、可読性の観点で次のような課題がありました。 画面によってフックや関数の粒度、定義場所が違うことでコードリーディングの負荷が高い コードレビュー時のレビュワーの負担が大きい テスト容易性の課題 多くのドメインロジックがコンポーネントやカスタムフック内に混在していることで、テスト容易性の観点で次のような課題がありました。 テストが実装しづらい テストカバレッジが低い CLINICS はオンライン診療・電子カルテ等の医療機関業務を支える機能を提供する SaaS プラットフォームです。 今後長きに渡って多くの医療機関の方々に CLINICS を利用して頂くためには、 コードを読みやすく、変更しやすく、維持しやすい状態に保ち続けること が重要です。 前の章で紹介したアーキテクチャは 持続可能な開発を目指して 設計しました。 具体的には、 開発体験・学習容易性・可読性・テスト容易性を向上することを狙い としています。 狙い 1:Tanstack Query の導入による開発体験の向上 背景で説明したとおり、Tanstack Query 導入以前は独自実装したフックを使って非同期処理を実装していました。 Tanstack Query を導入したきっかけは、チーム内での雑談の中で、独自実装のフックが使いづらいという声が挙がったことです。 そこで、独自実装のフック自体の質を高めるか、質の高いライブラリを導入するかを議論した結果、次の理由でライブラリを導入する決定をしました。 極力自分たちでコードを書かずに非同期処理・状態管理の実装を実現したい 実装に困ったときにドキュメントを読めば解決する環境にしたい CLINICS では技術的な背景 6 から Tanstack Query と SWR が候補に上がりましたが、 select オプションや invalidateQueries の Partial Query Matching 等の機能性と、ドキュメントの充実度合いの観点から Tanstack Query を採用しました。 Tanstack Query を採用したことで、 非同期処理のためのコードの記述量が大幅に削減 されたほか、データの特性に応じて Query ごとにキャッシュの時間を調整することが可能となり開発体験が大幅に向上しました。 狙い 2:シンプルなレイヤー分割による学習容易性の向上 CLINICS では、事業の拡大にともないコードベースに関わるエンジニアが増え続けています。 実装の進め方はプロジェクトによって最適な形式を選択しています。 バックエンドからフロントエンドまで一気通貫で実装 技術領域に分けて分業 このように多くのエンジニアが様々な形で関わる環境では、コードベースの学習容易性を高め、実装からコードレビューの完了までをスムーズに行えることが重要です。 前の章で紹介したアーキテクチャは、 レイヤー分割をシンプルにすることでコードベースに慣れるまでの時間を最小限にする ことを意識しています。 加えて、 実装パターンを定形化することで、コードベースに馴染みがなくても迷いなく実装できる ほか、関数の粒度が統一されることで、コードレビューの負荷軽減にも繋がっています。 レイヤー分割の粒度や実装パターンについては、次の記事を参考にさせて頂きました。 フロントエンドアーキテクチャの話: Resource Set の紹介 ほかにも CLINICS では学習容易性の向上の取り組みとして、 新しいライブラリやアーキテクチャを導入した際は勉強会を開催 してライブラリの基本的な使い方や頻出の実装パターンに関する知見を共有しています。 狙い 3:ドメインロジックを純粋関数で表現することによる可読性・テスト容易性の向上 アーキテクチャを刷新する以前は、多くのドメインロジックがコンポーネントやカスタムフック内に混在していることで、可読性やテスト容易性に支障をきたしていました。 CLINICS のフロントエンドには、医療システムに関する複雑なドメインロジックが多いため、 シンプルでテストしやすいコードベース を作っていくことがとりわけ重要だと考えています。 この課題は、ドメインロジックが集まる傾向にある queries.ts、 mutations.ts の Request Selector や ViewModel Selector の実装を定型化し、純粋関数で表現することにより解決しました。 まとめ Tanstack Query を使ったフロントエンドアーキテクチャの実例を紹介しました。 Resource Operation レイヤーに Tanstack Query の実装を定型化して集約しています。 レイヤー分割をシンプルにし、実装を定型化することで、開発組織のスケールに対応しています。 useQuery の select オプションを使い、スケーラブルにデータ変換処理を記述しています。 データ変換処理にはドメインロジックが集まりやすいため、なるべく小さい粒度の純粋関数で表現することで、可読性・テスト容易性の向上を狙っています。 この記事の内容が Tanstack Query の導入を考えている方の参考になれば幸いです。 さいごに CLINICS では、機能開発と並行してフロントエンド基盤を改善する取り組みも実施しています。 Redux から Tanstack Query への移行 UI ライブラリの Mithril から React への移行 7 デザインシステムの構築とプロダクトへの反映 このような取り組みに興味がある方は次のリンクから是非ご連絡ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp Footnotes Overview | TanStack Query Docs ↩ Comparison | React Query vs SWR vs Apollo vs RTK Query vs React Router | TanStack Query Docs ↩ この記事で紹介している ViewModel は View レイヤー専用のモデルを表す概念です。 MVVM アーキテクチャの ViewModel とは異なります。 ↩ Tanstack Query におけるデータ変換手法の詳細は React Query Data Transformations | TkDodo’s blog で紹介されています。 ↩ Tanstack Query 導入以前の非同期処理は useAsync React Hook - useHooks をカスタマイズしたフックを使って実装していました。 ↩ CLINICS では 一部の実装箇所で Redux を使用していますが、 RTK Query は今回採用するライブラリの候補から除外しました。これは、現在使用している Redux のバージョンが低く、レガシーな周辺ライブラリも複数使用している背景で Redux Toolkit への移行に相当な工数を必要とするためです。 ↩ 2023 年 3 月現在、 CLINICS のフロントエンドの 約 50% は、 Mithril と Redux で構成されています。開発体験の向上のため、 React への完全移行を目指して日々改善を続けています。 ↩
はじめに みなさん、こんにちは。エンジニアの新居です。今回は医療介護求人サイトの ジョブメドレー 開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いていこうと思います。 インタビュイー紹介 堀内さん 2021 年入社。人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属。ジョブメドレーの開発を担当。現在は求職者側の UI/UX 改善などの開発に携わる。 堀内さん メドレーに入社した理由 新居 : まずはじめに、堀内さんはどういう経緯でメドレーに入社したのでしたっけ? 堀内 : 以前のインタビュー( 21 年新卒入社エンジニアと座談会で振り返る新卒研修 )の時にもお話したのですが、就活の軸として、自分が成長できそうか・風通しが良いか・合理的な社風かなどを軸として探していました。特に、 ビジネス側と開発側の距離が近い 部分に惹かれ、総合的にメドレーが良さそうだと考えて入社しました。 新居 : 入社前の印象と実際働きだしてからで、ズレなどはありましたか? 堀内 : そんなにギャップはなくて、社風とか人間関係みたいなところは入社前のイメージ通りでした。入社前に何回か会社見学をさせてもらったり、その時の社員と複数回面談をセッティングしてもらったりして、そこでイメージをすり合わせできたことが大きかったですね。 新居 : 何人くらいの社員と会ったのですか? 堀内 : 6 ~ 7 人です。色々なポジションや性格の人達と話ができたので、安心して入社することができました。 新卒研修の開発 OJT ではどんなことをやっていたか? 新居 : 堀内さんは新卒研修の開発 OJT のタイミングでジョブメドレー開発チームに仮配属されたのですよね。当時はどういうことをやってたのですか? 堀内 : まずはジョブメドレーのドメイン知識をしっかり吸収していこうということで、業界や業務自体の理解を進めました。 その後、求職者と事業者のサポート業務を行う社内オペレーターが利用する画面の拡張タスクに取り組みました。 技術スタックは Next.js / React / TypeScript / GraphQL といったシステムで、個人的にあまり触れてこなかった領域だったので、技術のキャッチアップもしつつ丁寧に対応していきました。 また、開発 OJT よりも前の研修で輪読会も経験したのですが、その時に学んだ Cookie や Session まわりの知識が役に立ったり、座学で学んだことがこうやって実践で活きてくるんだな、ということを実感 できてとても良い経験になりました。 今までとは比べ物にならないくらい大規模なシステムだったので、プレッシャーも大きかったのですが、チームの人達と相談したり、細かいレビュー・サポートがあったおかげで、しっかり成果を出すことができたと思います。 新居 : 良い OJT ですね!堀内さん自身の努力があったのは勿論ですが、チームに新しいメンバーを迎え入れて成長をサポートする環境が整っていたのも良かったんでしょうね。 ちなみに開発 OJT の中で大変だったこととかはありますか? 堀内 : そうですね、やはり大規模な既存コードの把握・理解が大変でした。社内オペレーターが使う画面とジョブメドレー本体はそれぞれ別システムとして連携しているので、ジョブメドレー本体のコードや仕組みの理解も必要です。 大変だった分、 システムの全体像を理解するのに大いに役立った ので、今思い返すと良い経験だったなあと思います。 色々な経験を積めた求人カード改修プロジェクト 新居 : では、新卒研修を終えてから 1 年以上経っていて、入社 2 年目の終わりに差し掛かっている現在ですが、直近で堀内さんがリードを務めていた、ジョブメドレーの求人カード改修プロジェクトについて話を聞いていきたいと思います。まずは、このプロジェクトの概要について、教えてもらえますか? 求人カード改修プロジェクトとは? 堀内 : それでは、プロジェクト説明に先立ちまして、まず、ジョブメドレーの「求人カード」について簡単に説明します。 ジョブメドレーで求人を検索すると、検索結果が並ぶ画面が出てきます。我々が「求人カード」と呼んでいるのは、この検索結果画面上に表示される、求人情報のことになります。 検索結果画面の求人カードでは、給与や大まかな業務内容、応募要件、職場の住所などが分かる形になっていますが、求職者の方々がより便利に使っていただけるよう、情報の出し方を見直すことが今回のプロジェクト内容です。 もう少し詳しくお話すると、採用決定率の改善を目的に、どのような改善を実施すべきか検討しています。 求職者にとって欲しい情報が手に入りやすくなるということは、その分、応募に繋がりやすくなると言い換えることもできますので、改修による効果については、継続的に検証し、効果の有無を明らかにしたいと考えています。 新居 : なるほど。端的に言うと、ジョブメドレーにおける求人情報検索の体験を更に良くする為のプロジェクトということですね。 プロジェクトのはじまり 新居 : 今回のプロジェクトをどのようなメンバーと何をどのように行っていったのか、チーム構成から教えてください。 堀内 : チーム構成としては、自分を含めたエンジニアが 2 人いて、その他は、デザイナー、マーケター、プロダクトマネージャー(以下、PdM)がそれぞれ 1 人ずつプロジェクトにアサインされました。 プロジェクトのきっかけとしては、求職者体験を更に良くする為の企画の一つとしてマーケターと PdM が企画したところから始まり、その後、エンジニアとマーケター間で、細かい具体的な要件まで落とし込んで、プロジェクト化に至りました。 仕様についてメンバー間で徹底議論 堀内 : 要件まで落とし込んだ後は、具体的にそれらをどうやって実装するかを検討しました。特に UI の仕様については、デザイナーを中心にかなり長い時間をかけて詰めていきました 。 新居 : 確かに、情報の出し方によっては、かえって、求職者体験を悪化させてしまう可能性がありそうですね…。 堀内 : はい。使い勝手の良い UI にする為に、デザイナーがまず Figma でモックアップを作り、プロジェクトが始まった最初の週からデイリーの夕会などで、UI に関して徹底的にプロジェクトメンバー間で議論をしていました。 その時、特にメンバー全員が気を付けていたのは、実際に求職者が新しい一覧画面を使う時に、価値を感じてもらえるかどうかです。メドレーの Our Essentials の一つとして、 長期のカスタマー価値を追求 という項目があります。「全ての利用者にとって価値のある施策であること」、「利用者の片方だけを見て、もう片方を無視することがあってはならない」ということで、事業者側の求人情報を多く載せるだけではなく、求職者にとっての使い勝手も向上しているか?などを徹底的にチェックしていました。 使い勝手をチェックするにあたって、ジョブメドレーのメインユーザーの年齢層などが予め分かっていたので、その方達にとって使いやすいかどうかを判断軸として、考えていました。 デザイナーが完成させた UI でも、でき上がったモックアップをメンバーそれぞれの視点で触ってみると、改善点が出る場面がありました。そういった場合には、こういう UI にしてみたらどうか?などとそれぞれが意見し、良さそうな案についてはデザイナーがそれらの提案を取り込んだり、改良したりして、UI の仕様を詰めていきました。 技術面について 新居 : UI を実装に落とし込む際、技術面で工夫した点や気を付けていたポイントなどはありますか? 堀内 : 今回のプロジェクトでは、求人カードのデザインを大幅に変更する為、事前の影響範囲の洗い出しを徹底するよう注意していました。例えば、一覧画面における求人情報の出力量がかなり増えることで、1 ページあたりの読み込み処理に時間がかかってしまうことが予想された為、コンテンツの遅延読み込みやキャッシュの利用によりチューニングを行っています。また、デザイン変更が多くのページにわたって発生する為、リリース後の不具合を起こさないような対策を複数行っています。 担当したプロジェクトのリード 新居 : 今回のプロジェクトでは、エンジニアとして開発をしつつ、堀内さんがプロジェクトのリードを任されていたと聞いています。リードとしては、どんな動きをプロジェクトの中でされていたのでしょうか? 堀内 : まずは、最初にプロジェクト全体の大まかなスケジュールを引き、PdM と確認しました。次に、タスク分解をしてメンバーへのアサインを行ったり、プロジェクトのタスクのなかで不確実性を極力少なくする為に、自分達だけではコントロールできなさそうな部分を洗い出したりしました。洗い出したアンコントローラブルな部分は企画者と共に関係各所へ連絡・調整をしてもらうようお願いしていました。 新居 : なるほど。開発スケジュールの出し方など、その辺りで工夫した点などはありますか? 堀内 : スケジュールの共有方法については、視覚的に分かりやすいように、 FigJam を使って、ガントチャートのような形式で、ボードを作成しました。 メンバー毎にレーンを振り分けて、誰がいつまでに何をしなければいけないのかを夕会で確認していました。また、スケジュールのボードは常に、 第三者が見ても進捗状況が直ぐに分かるような状態にしておく よう努めました。 スケジュールボード スケジューリングで特に意識していたことは、不確実な部分を早めに洗い出すことでした。タスク分解をしていく中で、どこが自分達だけではコントロールできなさそうな部分なのかを把握することから始めていました。 現状の仕様や実装がどうなっているのかなどは直ぐに確認できるので、そういったところから、なるべく不確実な部分を減らしていき、最終的に各タスクの工数を算出するにあたって、不確実性の高さに応じて、バッファを設定しました。 新居 : 少し手を動かせば確認できるようなところは直ぐに確認して、なるべく解像度が高い状態にもっていく進め方は確かに良さそうですね!工数を見積もるにあたって、解像度が低い状態だと、見積もりの精度が低くなってしまいますからね。 堀内 : また、タスクは GitHub Issue を発行して管理していたのですが、切り出す粒度はなるべく細かくしていました。Issue の粒度が荒くなってしまうと、それに比例して手戻りリスクが高くなってしまうので、目安としては、長くても 2 日以内には完了できる粒度で切り出していました。 結果、今回のプロジェクトにおいては、ほとんど手戻りすることなく、ほぼ全ての期間において、オンスケジュールで進めることができました。 A/B テストの導入(効果検証の仕組み化) 新居 : オンスケで進められていたのは素晴らしいですね!開発者として実装することと、リードとしてプロジェクトを管理することの他、堀内さんが今回のプロジェクトで担当していた役割などはありますか? 堀内 : 今回のプロジェクトでは、効果検証を行う為の仕組みとして、A/B テストを本格的に導入する目標がありました。なぜ A/B テストを導入するのか? A/B テストで何をしたいのか?を明確にして、A/B テストの設計から導入、分析までを行いました。 統計的な分析手法の選択から実際のテスト環境の開発みたいなところまで一気通貫で、PdM、マーケター、エンジニアを巻き込んで行いました。 A/B テストで大事なのは、論理的な仮説を立ててそれを検証し、その結果からより効果があると判断できた施策を導入していく、というサイクルだと考えています。とはいえ、有意差が出たからといって必ずしも導入したほうが良いというわけではありません。その時の経営面のメリット・デメリットを考慮し、これまでの経験にもとづいた判断も加えて、より効果の高い施策を継続的に導入できるようにする、というのが今回の目標です。 今回のプロジェクトを皮切りに、他のプロジェクトでも同じように A/B テストの仕組みを使える状態にしたい。そういった横展開や汎用性みたいなところも、プロジェクト当初からセットで考えていました。 新居 : とても興味深い取り組みですね!実際、どのように仕組み化を進めていくのでしょうか? 堀内 : A/B テストに必要な知見をドキュメント化して、それを見るだけで、エンジニアのサポートを受けつつ誰でも A/B テストが実施できるようにしていく取り組みや、スプレッドシートにデータを入力すれば、簡単に結果が判るような仕組み化を進行中です。 機能のリリース時は、その結果に応じて結局、どうすれば良いのか?といった指針も Issue やドキュメントに記載しております。 実装や設計面における悩み 新居 : 実装や設計面において、今回のプロジェクトで悩んだようなところはありましたか? 堀内 : はい。自分自身の力量の問題で、ドメイン知識や技術力などが不足していたことから、ジョブメドレーのコードの中で今までの慣習を受け継ぎつつ、良い感じに使い回しができるコードをどう上手く書いていくか、という点について悩んでいました。 新居 : なるほど…。ジョブメドレー自体が 10 年以上の歴史があるサービスなので、そこに手を入れていくのは確かに難しい部分もありそうですね。その悩みに対しては、どう対策をされていたのでしょう? 堀内 : そうですね。今の自分の知見だけで設計と実装を進めてしまうと手戻りが発生してしまう恐れがあったので、まずはドメイン知識が豊富な、同じチーム内のベテラン社員に設計レビューをお願いしました。そこで、ジョブメドレーのインフラ構成におけるキャッシュ戦略や、保守性を高める為の実装の切り出し方など、壁打ちで相談させてもらっていました。 その先輩社員との設計レビュー以外にも、普段から行ってもらっている 1on1 ミーティングを通じて、自分のメンターと相談させてもらい、どういう設計だったら使いやすいのか、などを様々な視点から検討しました。 新居 : なるべく手戻りが発生しないようにする為に、 設計段階で身近にいるエンジニアを巻き込んで進めていった のですね。直ぐに相談できる相手がいるのは心強いですね。 学びになった点 新居 : 今回のプロジェクトを通じて、特に学びになった点を教えていただけますか? 堀内 : 技術的な面とプロジェクトの進め方の面でそれぞれ学びがありました。 技術的な面で言うと、 保守性が高く、今後、運用しやすいコードとはどういうものなのかというのを、今回の実装を通じて体得できた ところだと思っています。 プロジェクトの進め方の面では、過去のプロジェクトのドキュメントを参考に、そこに書かれていた知見を活かしながら、プロジェクトを進めていったので、ノウハウのようなところが学べたかと思っています。 新卒研修・開発実践のリードとの違い 新居 : 堀内さんは、新卒研修の一環で行われた開発実践の期間中、新卒同士のチーム開発でもリードを務めていたと思います。今回のプロジェクトを通じて経験されたリードとの違いについて教えてください。 堀内 : プロジェクトの規模や影響範囲の違いなどから、研修時よりも多くの関係者との調整が必要でした。全員と情報を共有・連携しつつ、プロジェクトを進める必要があった点が一番大きな違いでした。 また、開発実践のプロジェクトはあくまで社内向けシステムを構築するものだったので、会社の売上について意識することはありませんでしたが、今回のプロジェクトでは、施策によって応募率が下がった場合に売上に悪影響を及ぼす可能性がありましたので、開発部署以外への影響を意識して管理する必要があったことも大きいポイントだったと思っています。 ジョブメドレーの開発におけるやりがい 新居 : ここまで直近で担当されていたプロジェクトについて聞いてきましたが、ジョブメドレーの開発をしていて、特にやりがいを感じる場面について、教えてもらえますか? 堀内 : やはり、利用してくださっている求職者や事業者の方々に向けて、新規機能開発や機能改修を行う工程です。目的と照らし合わせて仕様面から妥協せずに考え抜き、リリースするまでの過程にやりがいを感じています。ユーザー数がとても多いだけに、良い機能をリリースできたときの反響も大きく、非常に楽しいです。 勿論、エンジニアとして技術力を上げていくことも貪欲に今後もやっていかなければならない部分だと思っていますが、企画として案件があがってきた段階で、それをなぜ作るのか?なぜそれで効果が出ると思っているのか?などと考えることが、企画職だけでなく、エンジニアにとっても大事だと考えています。 リリースした後には、実際に効果が出たのか出なかったのか、なぜ効果が出たのか、出なかったのか?などの効果検証を行った上で、PdM を中心にエンジニア以外のメンバーも交えて振り返り、次の施策に活かしていくといった、 PDCA サイクルを回していっている開発スタイルに楽しさを感じています 。 これは、自分の入社時にやりたいこととしてあげていた、エンジニアリング面だけではなくビジネス面についても多くの経験を積みたい、というところにリンクしていて、今在籍しているジョブメドレーの開発チームでは、企画やマーケティングを行う人達とエンジニアがとても近い距離にいることで、様々な知見を得られつつアウトプットできる、とても学びになる環境だと感じています。 ジョブメドレー開発チームにおける 1 日の流れ 新居 : 現在のジョブメドレー開発チームは、Growth Unit(求職者の利便性を高める施策を行うチーム)と Customer Unit(求人を掲載する事業者が使う採用管理システムの改善施策を行うチーム)の 2 つに分かれているんですよね。堀内さんが所属しているのは、Growth Unit の方ですが、参加されているミーティングなど、1 日の流れについて教えてください。 堀内 : まず、勤務開始の 10:00 頃には Slack に投稿する形で、その日のタスクや参加予定のミーティングなどについて、開発用 channel で共有します。 現在の自分のスケジュールでは、午前中は特に定常的なミーティングはありません。但し、アサインされるプロジェクトによっては、午前中に「朝会」としてプロジェクトメンバー同士が集まって、進捗共有などを行っています。 お昼過ぎの 14:30 からは、 30 分程度、Growth Unit のエンジニアだけで集まるミーティングがあります。この場では、各自の進捗状況や抱えている不安・不明点などを共有します。今何に困っているのかを共有するだけでなく、 困りごとに対し、各々が知恵を出し合って、問題解決までのリードタイムを短くする ことができる貴重な場となっています。 18:00 からは「夕会」として、PdM とデザイナーも含めた Growth Unit 全体のミーティングがあります。そこで各自、「今日やったこと」や担当 Issue のリリース目処などを共有しています。この場では、エンジニア以外のメンバーも参加しているので、そこまで技術的に突っ込んだ話まではしません。 さらに、終業前にはまたエンジニアだけで集まり、FigJam を使って付箋を貼っていく形で、 各自が技術的トピックや、特定のドメインについて聞きたいことなどを持ち寄り、ざっくばらんに雑談形式で話す 「技術共有会」を行っています。 技術共有会の雰囲気 ミーティングの場以外は黙々と開発を進めていきますが、聞きたいことや相談したいことが出てきたら都度、開発用のスレッドに投稿しています。 新居 : なるほど。質問や相談はしやすい感じですか? 堀内 : はい。勿論、自分で調べられることはなるべく調べて、自己解決するのが基本ではありますが、あまり調査に時間をかけ過ぎて全然進まないような事態に陥っても良くないので、それらを皆、前提として意識した上で、聞くべき時は躊躇なく聞ける雰囲気があります。 新居 : ミーティングの種類としては、先ほどあげてもらったもの以外にありますか? 堀内 : あとは、週一で行われている「プロダクト定例」と隔週で行ってもらっている「1on1 ミーティング」があります。 プロダクト定例は、比較的規模の大きいミーティングで、我々、プロダクト開発室以外のメンバーの他、マーケティング室や事業企画室のメンバーも含めて、大体いつも 40 名前後の人数で参加しています。 この場では、現在追っている KPI だったり、ジョブメドレーで動いている全てのプロジェクト状況が共有されます。そこで共有される KPI の数字によって、次に着手すべき Issue にも影響してくるので、この場で共有される情報をしっかりとインプットして、今後の心構えとすることにしています。 直近で担当したプロジェクトの話でもちらっと出てきましたが、1on1 ミーティングは担当メンターと行なっています。普段の仕事の相談から、自分のキャリアに関するような相談をしており、メンターさんの経験なども教えていただきながら、勉強させていただいています。 1on1 ミーティングの意義としては、そういった仕事の他、 プライベートのことも含めて、何でも相談ができる場 としてあるのですが、1on1 を通じて、メンターさんとの信頼関係も築けるので、チームビルディングの一つとしても機能しています。 ジョブメドレー開発チームで一緒に働きたいと思う人はどんな人? 新居 : 堀内さんがジョブメドレー開発チームで一緒に働きたいと思う人はどんな人でしょうか? 堀内 : 今の環境で働いてて良かったなあと思うことは、 人間関係が良好であること です。もちろん馴れ合いの中での業務というわけではなく、各々が しっかりと仕事に対して責任を持ちつつ、チーム内で生じた違和感は遠慮なく指摘し合い受けとめる という、良い意味で心理的安全性のある環境でプロダクト開発に集中して取り組めることです。 このような環境で、課題に対して長期的な視点で本質を捉え、目的を設定し、ボトムアップで解決策の立案から改善までを主導すること、さらには仕組み化まで行うことが好きな方、またはそれらをやってみたい方が合っているのではないか、と思います。 自分もそのような人間になりたいと思いながら、日々仕事に取り組んでいます。 さいごに ジョブメドレー開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いてきましたが、いかがだったでしょうか? こんなチームでプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの新居です。今回は医療介護求人サイトの ジョブメドレー 開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いていこうと思います。 インタビュイー紹介 堀内さん 2021 年入社。人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属。ジョブメドレーの開発を担当。現在は求職者側の UI/UX 改善などの開発に携わる。 堀内さん メドレーに入社した理由 新居 : まずはじめに、堀内さんはどういう経緯でメドレーに入社したのでしたっけ? 堀内 : 以前のインタビュー( 21 年新卒入社エンジニアと座談会で振り返る新卒研修 )の時にもお話したのですが、就活の軸として、自分が成長できそうか・風通しが良いか・合理的な社風かなどを軸として探していました。特に、 ビジネス側と開発側の距離が近い 部分に惹かれ、総合的にメドレーが良さそうだと考えて入社しました。 新居 : 入社前の印象と実際働きだしてからで、ズレなどはありましたか? 堀内 : そんなにギャップはなくて、社風とか人間関係みたいなところは入社前のイメージ通りでした。入社前に何回か会社見学をさせてもらったり、その時の社員と複数回面談をセッティングしてもらったりして、そこでイメージをすり合わせできたことが大きかったですね。 新居 : 何人くらいの社員と会ったのですか? 堀内 : 6 ~ 7 人です。色々なポジションや性格の人達と話ができたので、安心して入社することができました。 新卒研修の開発 OJT ではどんなことをやっていたか? 新居 : 堀内さんは新卒研修の開発 OJT のタイミングでジョブメドレー開発チームに仮配属されたのですよね。当時はどういうことをやってたのですか? 堀内 : まずはジョブメドレーのドメイン知識をしっかり吸収していこうということで、業界や業務自体の理解を進めました。 その後、求職者と事業者のサポート業務を行う社内オペレーターが利用する画面の拡張タスクに取り組みました。 技術スタックは Next.js / React / TypeScript / GraphQL といったシステムで、個人的にあまり触れてこなかった領域だったので、技術のキャッチアップもしつつ丁寧に対応していきました。 また、開発 OJT よりも前の研修で輪読会も経験したのですが、その時に学んだ Cookie や Session まわりの知識が役に立ったり、座学で学んだことがこうやって実践で活きてくるんだな、ということを実感 できてとても良い経験になりました。 今までとは比べ物にならないくらい大規模なシステムだったので、プレッシャーも大きかったのですが、チームの人達と相談したり、細かいレビュー・サポートがあったおかげで、しっかり成果を出すことができたと思います。 新居 : 良い OJT ですね!堀内さん自身の努力があったのは勿論ですが、チームに新しいメンバーを迎え入れて成長をサポートする環境が整っていたのも良かったんでしょうね。 ちなみに開発 OJT の中で大変だったこととかはありますか? 堀内 : そうですね、やはり大規模な既存コードの把握・理解が大変でした。社内オペレーターが使う画面とジョブメドレー本体はそれぞれ別システムとして連携しているので、ジョブメドレー本体のコードや仕組みの理解も必要です。 大変だった分、 システムの全体像を理解するのに大いに役立った ので、今思い返すと良い経験だったなあと思います。 色々な経験を積めた求人カード改修プロジェクト 新居 : では、新卒研修を終えてから 1 年以上経っていて、入社 2 年目の終わりに差し掛かっている現在ですが、直近で堀内さんがリードを務めていた、ジョブメドレーの求人カード改修プロジェクトについて話を聞いていきたいと思います。まずは、このプロジェクトの概要について、教えてもらえますか? 求人カード改修プロジェクトとは? 堀内 : それでは、プロジェクト説明に先立ちまして、まず、ジョブメドレーの「求人カード」について簡単に説明します。 ジョブメドレーで求人を検索すると、検索結果が並ぶ画面が出てきます。我々が「求人カード」と呼んでいるのは、この検索結果画面上に表示される、求人情報のことになります。 検索結果画面の求人カードでは、給与や大まかな業務内容、応募要件、職場の住所などが分かる形になっていますが、求職者の方々がより便利に使っていただけるよう、情報の出し方を見直すことが今回のプロジェクト内容です。 もう少し詳しくお話すると、採用決定率の改善を目的に、どのような改善を実施すべきか検討しています。 求職者にとって欲しい情報が手に入りやすくなるということは、その分、応募に繋がりやすくなると言い換えることもできますので、改修による効果については、継続的に検証し、効果の有無を明らかにしたいと考えています。 新居 : なるほど。端的に言うと、ジョブメドレーにおける求人情報検索の体験を更に良くする為のプロジェクトということですね。 プロジェクトのはじまり 新居 : 今回のプロジェクトをどのようなメンバーと何をどのように行っていったのか、チーム構成から教えてください。 堀内 : チーム構成としては、自分を含めたエンジニアが 2 人いて、その他は、デザイナー、マーケター、プロダクトマネージャー(以下、PdM)がそれぞれ 1 人ずつプロジェクトにアサインされました。 プロジェクトのきっかけとしては、求職者体験を更に良くする為の企画の一つとしてマーケターと PdM が企画したところから始まり、その後、エンジニアとマーケター間で、細かい具体的な要件まで落とし込んで、プロジェクト化に至りました。 仕様についてメンバー間で徹底議論 堀内 : 要件まで落とし込んだ後は、具体的にそれらをどうやって実装するかを検討しました。特に UI の仕様については、デザイナーを中心にかなり長い時間をかけて詰めていきました 。 新居 : 確かに、情報の出し方によっては、かえって、求職者体験を悪化させてしまう可能性がありそうですね…。 堀内 : はい。使い勝手の良い UI にする為に、デザイナーがまず Figma でモックアップを作り、プロジェクトが始まった最初の週からデイリーの夕会などで、UI に関して徹底的にプロジェクトメンバー間で議論をしていました。 その時、特にメンバー全員が気を付けていたのは、実際に求職者が新しい一覧画面を使う時に、価値を感じてもらえるかどうかです。メドレーの Our Essentials の一つとして、 長期のカスタマー価値を追求 という項目があります。「全ての利用者にとって価値のある施策であること」、「利用者の片方だけを見て、もう片方を無視することがあってはならない」ということで、事業者側の求人情報を多く載せるだけではなく、求職者にとっての使い勝手も向上しているか?などを徹底的にチェックしていました。 使い勝手をチェックするにあたって、ジョブメドレーのメインユーザーの年齢層などが予め分かっていたので、その方達にとって使いやすいかどうかを判断軸として、考えていました。 デザイナーが完成させた UI でも、でき上がったモックアップをメンバーそれぞれの視点で触ってみると、改善点が出る場面がありました。そういった場合には、こういう UI にしてみたらどうか?などとそれぞれが意見し、良さそうな案についてはデザイナーがそれらの提案を取り込んだり、改良したりして、UI の仕様を詰めていきました。 技術面について 新居 : UI を実装に落とし込む際、技術面で工夫した点や気を付けていたポイントなどはありますか? 堀内 : 今回のプロジェクトでは、求人カードのデザインを大幅に変更する為、事前の影響範囲の洗い出しを徹底するよう注意していました。例えば、一覧画面における求人情報の出力量がかなり増えることで、1 ページあたりの読み込み処理に時間がかかってしまうことが予想された為、コンテンツの遅延読み込みやキャッシュの利用によりチューニングを行っています。また、デザイン変更が多くのページにわたって発生する為、リリース後の不具合を起こさないような対策を複数行っています。 担当したプロジェクトのリード 新居 : 今回のプロジェクトでは、エンジニアとして開発をしつつ、堀内さんがプロジェクトのリードを任されていたと聞いています。リードとしては、どんな動きをプロジェクトの中でされていたのでしょうか? 堀内 : まずは、最初にプロジェクト全体の大まかなスケジュールを引き、PdM と確認しました。次に、タスク分解をしてメンバーへのアサインを行ったり、プロジェクトのタスクのなかで不確実性を極力少なくする為に、自分達だけではコントロールできなさそうな部分を洗い出したりしました。洗い出したアンコントローラブルな部分は企画者と共に関係各所へ連絡・調整をしてもらうようお願いしていました。 新居 : なるほど。開発スケジュールの出し方など、その辺りで工夫した点などはありますか? 堀内 : スケジュールの共有方法については、視覚的に分かりやすいように、 FigJam を使って、ガントチャートのような形式で、ボードを作成しました。 メンバー毎にレーンを振り分けて、誰がいつまでに何をしなければいけないのかを夕会で確認していました。また、スケジュールのボードは常に、 第三者が見ても進捗状況が直ぐに分かるような状態にしておく よう努めました。 スケジュールボード スケジューリングで特に意識していたことは、不確実な部分を早めに洗い出すことでした。タスク分解をしていく中で、どこが自分達だけではコントロールできなさそうな部分なのかを把握することから始めていました。 現状の仕様や実装がどうなっているのかなどは直ぐに確認できるので、そういったところから、なるべく不確実な部分を減らしていき、最終的に各タスクの工数を算出するにあたって、不確実性の高さに応じて、バッファを設定しました。 新居 : 少し手を動かせば確認できるようなところは直ぐに確認して、なるべく解像度が高い状態にもっていく進め方は確かに良さそうですね!工数を見積もるにあたって、解像度が低い状態だと、見積もりの精度が低くなってしまいますからね。 堀内 : また、タスクは GitHub Issue を発行して管理していたのですが、切り出す粒度はなるべく細かくしていました。Issue の粒度が荒くなってしまうと、それに比例して手戻りリスクが高くなってしまうので、目安としては、長くても 2 日以内には完了できる粒度で切り出していました。 結果、今回のプロジェクトにおいては、ほとんど手戻りすることなく、ほぼ全ての期間において、オンスケジュールで進めることができました。 A/B テストの導入(効果検証の仕組み化) 新居 : オンスケで進められていたのは素晴らしいですね!開発者として実装することと、リードとしてプロジェクトを管理することの他、堀内さんが今回のプロジェクトで担当していた役割などはありますか? 堀内 : 今回のプロジェクトでは、効果検証を行う為の仕組みとして、A/B テストを本格的に導入する目標がありました。なぜ A/B テストを導入するのか? A/B テストで何をしたいのか?を明確にして、A/B テストの設計から導入、分析までを行いました。 統計的な分析手法の選択から実際のテスト環境の開発みたいなところまで一気通貫で、PdM、マーケター、エンジニアを巻き込んで行いました。 A/B テストで大事なのは、論理的な仮説を立ててそれを検証し、その結果からより効果があると判断できた施策を導入していく、というサイクルだと考えています。とはいえ、有意差が出たからといって必ずしも導入したほうが良いというわけではありません。その時の経営面のメリット・デメリットを考慮し、これまでの経験にもとづいた判断も加えて、より効果の高い施策を継続的に導入できるようにする、というのが今回の目標です。 今回のプロジェクトを皮切りに、他のプロジェクトでも同じように A/B テストの仕組みを使える状態にしたい。そういった横展開や汎用性みたいなところも、プロジェクト当初からセットで考えていました。 新居 : とても興味深い取り組みですね!実際、どのように仕組み化を進めていくのでしょうか? 堀内 : A/B テストに必要な知見をドキュメント化して、それを見るだけで、エンジニアのサポートを受けつつ誰でも A/B テストが実施できるようにしていく取り組みや、スプレッドシートにデータを入力すれば、簡単に結果が判るような仕組み化を進行中です。 機能のリリース時は、その結果に応じて結局、どうすれば良いのか?といった指針も Issue やドキュメントに記載しております。 実装や設計面における悩み 新居 : 実装や設計面において、今回のプロジェクトで悩んだようなところはありましたか? 堀内 : はい。自分自身の力量の問題で、ドメイン知識や技術力などが不足していたことから、ジョブメドレーのコードの中で今までの慣習を受け継ぎつつ、良い感じに使い回しができるコードをどう上手く書いていくか、という点について悩んでいました。 新居 : なるほど…。ジョブメドレー自体が 10 年以上の歴史があるサービスなので、そこに手を入れていくのは確かに難しい部分もありそうですね。その悩みに対しては、どう対策をされていたのでしょう? 堀内 : そうですね。今の自分の知見だけで設計と実装を進めてしまうと手戻りが発生してしまう恐れがあったので、まずはドメイン知識が豊富な、同じチーム内のベテラン社員に設計レビューをお願いしました。そこで、ジョブメドレーのインフラ構成におけるキャッシュ戦略や、保守性を高める為の実装の切り出し方など、壁打ちで相談させてもらっていました。 その先輩社員との設計レビュー以外にも、普段から行ってもらっている 1on1 ミーティングを通じて、自分のメンターと相談させてもらい、どういう設計だったら使いやすいのか、などを様々な視点から検討しました。 新居 : なるべく手戻りが発生しないようにする為に、 設計段階で身近にいるエンジニアを巻き込んで進めていった のですね。直ぐに相談できる相手がいるのは心強いですね。 学びになった点 新居 : 今回のプロジェクトを通じて、特に学びになった点を教えていただけますか? 堀内 : 技術的な面とプロジェクトの進め方の面でそれぞれ学びがありました。 技術的な面で言うと、 保守性が高く、今後、運用しやすいコードとはどういうものなのかというのを、今回の実装を通じて体得できた ところだと思っています。 プロジェクトの進め方の面では、過去のプロジェクトのドキュメントを参考に、そこに書かれていた知見を活かしながら、プロジェクトを進めていったので、ノウハウのようなところが学べたかと思っています。 新卒研修・開発実践のリードとの違い 新居 : 堀内さんは、新卒研修の一環で行われた開発実践の期間中、新卒同士のチーム開発でもリードを務めていたと思います。今回のプロジェクトを通じて経験されたリードとの違いについて教えてください。 堀内 : プロジェクトの規模や影響範囲の違いなどから、研修時よりも多くの関係者との調整が必要でした。全員と情報を共有・連携しつつ、プロジェクトを進める必要があった点が一番大きな違いでした。 また、開発実践のプロジェクトはあくまで社内向けシステムを構築するものだったので、会社の売上について意識することはありませんでしたが、今回のプロジェクトでは、施策によって応募率が下がった場合に売上に悪影響を及ぼす可能性がありましたので、開発部署以外への影響を意識して管理する必要があったことも大きいポイントだったと思っています。 ジョブメドレーの開発におけるやりがい 新居 : ここまで直近で担当されていたプロジェクトについて聞いてきましたが、ジョブメドレーの開発をしていて、特にやりがいを感じる場面について、教えてもらえますか? 堀内 : やはり、利用してくださっている求職者や事業者の方々に向けて、新規機能開発や機能改修を行う工程です。目的と照らし合わせて仕様面から妥協せずに考え抜き、リリースするまでの過程にやりがいを感じています。ユーザー数がとても多いだけに、良い機能をリリースできたときの反響も大きく、非常に楽しいです。 勿論、エンジニアとして技術力を上げていくことも貪欲に今後もやっていかなければならない部分だと思っていますが、企画として案件があがってきた段階で、それをなぜ作るのか?なぜそれで効果が出ると思っているのか?などと考えることが、企画職だけでなく、エンジニアにとっても大事だと考えています。 リリースした後には、実際に効果が出たのか出なかったのか、なぜ効果が出たのか、出なかったのか?などの効果検証を行った上で、PdM を中心にエンジニア以外のメンバーも交えて振り返り、次の施策に活かしていくといった、 PDCA サイクルを回していっている開発スタイルに楽しさを感じています 。 これは、自分の入社時にやりたいこととしてあげていた、エンジニアリング面だけではなくビジネス面についても多くの経験を積みたい、というところにリンクしていて、今在籍しているジョブメドレーの開発チームでは、企画やマーケティングを行う人達とエンジニアがとても近い距離にいることで、様々な知見を得られつつアウトプットできる、とても学びになる環境だと感じています。 ジョブメドレー開発チームにおける 1 日の流れ 新居 : 現在のジョブメドレー開発チームは、Growth Unit(求職者の利便性を高める施策を行うチーム)と Customer Unit(求人を掲載する事業者が使う採用管理システムの改善施策を行うチーム)の 2 つに分かれているんですよね。堀内さんが所属しているのは、Growth Unit の方ですが、参加されているミーティングなど、1 日の流れについて教えてください。 堀内 : まず、勤務開始の 10:00 頃には Slack に投稿する形で、その日のタスクや参加予定のミーティングなどについて、開発用 channel で共有します。 現在の自分のスケジュールでは、午前中は特に定常的なミーティングはありません。但し、アサインされるプロジェクトによっては、午前中に「朝会」としてプロジェクトメンバー同士が集まって、進捗共有などを行っています。 お昼過ぎの 14:30 からは、 30 分程度、Growth Unit のエンジニアだけで集まるミーティングがあります。この場では、各自の進捗状況や抱えている不安・不明点などを共有します。今何に困っているのかを共有するだけでなく、 困りごとに対し、各々が知恵を出し合って、問題解決までのリードタイムを短くする ことができる貴重な場となっています。 18:00 からは「夕会」として、PdM とデザイナーも含めた Growth Unit 全体のミーティングがあります。そこで各自、「今日やったこと」や担当 Issue のリリース目処などを共有しています。この場では、エンジニア以外のメンバーも参加しているので、そこまで技術的に突っ込んだ話まではしません。 さらに、終業前にはまたエンジニアだけで集まり、FigJam を使って付箋を貼っていく形で、 各自が技術的トピックや、特定のドメインについて聞きたいことなどを持ち寄り、ざっくばらんに雑談形式で話す 「技術共有会」を行っています。 技術共有会の雰囲気 ミーティングの場以外は黙々と開発を進めていきますが、聞きたいことや相談したいことが出てきたら都度、開発用のスレッドに投稿しています。 新居 : なるほど。質問や相談はしやすい感じですか? 堀内 : はい。勿論、自分で調べられることはなるべく調べて、自己解決するのが基本ではありますが、あまり調査に時間をかけ過ぎて全然進まないような事態に陥っても良くないので、それらを皆、前提として意識した上で、聞くべき時は躊躇なく聞ける雰囲気があります。 新居 : ミーティングの種類としては、先ほどあげてもらったもの以外にありますか? 堀内 : あとは、週一で行われている「プロダクト定例」と隔週で行ってもらっている「1on1 ミーティング」があります。 プロダクト定例は、比較的規模の大きいミーティングで、我々、プロダクト開発室以外のメンバーの他、マーケティング室や事業企画室のメンバーも含めて、大体いつも 40 名前後の人数で参加しています。 この場では、現在追っている KPI だったり、ジョブメドレーで動いている全てのプロジェクト状況が共有されます。そこで共有される KPI の数字によって、次に着手すべき Issue にも影響してくるので、この場で共有される情報をしっかりとインプットして、今後の心構えとすることにしています。 直近で担当したプロジェクトの話でもちらっと出てきましたが、1on1 ミーティングは担当メンターと行なっています。普段の仕事の相談から、自分のキャリアに関するような相談をしており、メンターさんの経験なども教えていただきながら、勉強させていただいています。 1on1 ミーティングの意義としては、そういった仕事の他、 プライベートのことも含めて、何でも相談ができる場 としてあるのですが、1on1 を通じて、メンターさんとの信頼関係も築けるので、チームビルディングの一つとしても機能しています。 ジョブメドレー開発チームで一緒に働きたいと思う人はどんな人? 新居 : 堀内さんがジョブメドレー開発チームで一緒に働きたいと思う人はどんな人でしょうか? 堀内 : 今の環境で働いてて良かったなあと思うことは、 人間関係が良好であること です。もちろん馴れ合いの中での業務というわけではなく、各々が しっかりと仕事に対して責任を持ちつつ、チーム内で生じた違和感は遠慮なく指摘し合い受けとめる という、良い意味で心理的安全性のある環境でプロダクト開発に集中して取り組めることです。 このような環境で、課題に対して長期的な視点で本質を捉え、目的を設定し、ボトムアップで解決策の立案から改善までを主導すること、さらには仕組み化まで行うことが好きな方、またはそれらをやってみたい方が合っているのではないか、と思います。 自分もそのような人間になりたいと思いながら、日々仕事に取り組んでいます。 さいごに ジョブメドレー開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いてきましたが、いかがだったでしょうか? こんなチームでプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの新居です。今回は医療介護求人サイトの ジョブメドレー 開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いていこうと思います。 インタビュイー紹介 堀内さん 2021 年入社。人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属。ジョブメドレーの開発を担当。現在は求職者側の UI/UX 改善などの開発に携わる。 堀内さん メドレーに入社した理由 新居 : まずはじめに、堀内さんはどういう経緯でメドレーに入社したのでしたっけ? 堀内 : 以前のインタビュー( 21 年新卒入社エンジニアと座談会で振り返る新卒研修 )の時にもお話したのですが、就活の軸として、自分が成長できそうか・風通しが良いか・合理的な社風かなどを軸として探していました。特に、 ビジネス側と開発側の距離が近い 部分に惹かれ、総合的にメドレーが良さそうだと考えて入社しました。 新居 : 入社前の印象と実際働きだしてからで、ズレなどはありましたか? 堀内 : そんなにギャップはなくて、社風とか人間関係みたいなところは入社前のイメージ通りでした。入社前に何回か会社見学をさせてもらったり、その時の社員と複数回面談をセッティングしてもらったりして、そこでイメージをすり合わせできたことが大きかったですね。 新居 : 何人くらいの社員と会ったのですか? 堀内 : 6 ~ 7 人です。色々なポジションや性格の人達と話ができたので、安心して入社することができました。 新卒研修の開発 OJT ではどんなことをやっていたか? 新居 : 堀内さんは新卒研修の開発 OJT のタイミングでジョブメドレー開発チームに仮配属されたのですよね。当時はどういうことをやってたのですか? 堀内 : まずはジョブメドレーのドメイン知識をしっかり吸収していこうということで、業界や業務自体の理解を進めました。 その後、求職者と事業者のサポート業務を行う社内オペレーターが利用する画面の拡張タスクに取り組みました。 技術スタックは Next.js / React / TypeScript / GraphQL といったシステムで、個人的にあまり触れてこなかった領域だったので、技術のキャッチアップもしつつ丁寧に対応していきました。 また、開発 OJT よりも前の研修で輪読会も経験したのですが、その時に学んだ Cookie や Session まわりの知識が役に立ったり、座学で学んだことがこうやって実践で活きてくるんだな、ということを実感 できてとても良い経験になりました。 今までとは比べ物にならないくらい大規模なシステムだったので、プレッシャーも大きかったのですが、チームの人達と相談したり、細かいレビュー・サポートがあったおかげで、しっかり成果を出すことができたと思います。 新居 : 良い OJT ですね!堀内さん自身の努力があったのは勿論ですが、チームに新しいメンバーを迎え入れて成長をサポートする環境が整っていたのも良かったんでしょうね。 ちなみに開発 OJT の中で大変だったこととかはありますか? 堀内 : そうですね、やはり大規模な既存コードの把握・理解が大変でした。社内オペレーターが使う画面とジョブメドレー本体はそれぞれ別システムとして連携しているので、ジョブメドレー本体のコードや仕組みの理解も必要です。 大変だった分、 システムの全体像を理解するのに大いに役立った ので、今思い返すと良い経験だったなあと思います。 色々な経験を積めた求人カード改修プロジェクト 新居 : では、新卒研修を終えてから 1 年以上経っていて、入社 2 年目の終わりに差し掛かっている現在ですが、直近で堀内さんがリードを務めていた、ジョブメドレーの求人カード改修プロジェクトについて話を聞いていきたいと思います。まずは、このプロジェクトの概要について、教えてもらえますか? 求人カード改修プロジェクトとは? 堀内 : それでは、プロジェクト説明に先立ちまして、まず、ジョブメドレーの「求人カード」について簡単に説明します。 ジョブメドレーで求人を検索すると、検索結果が並ぶ画面が出てきます。我々が「求人カード」と呼んでいるのは、この検索結果画面上に表示される、求人情報のことになります。 検索結果画面の求人カードでは、給与や大まかな業務内容、応募要件、職場の住所などが分かる形になっていますが、求職者の方々がより便利に使っていただけるよう、情報の出し方を見直すことが今回のプロジェクト内容です。 もう少し詳しくお話すると、採用決定率の改善を目的に、どのような改善を実施すべきか検討しています。 求職者にとって欲しい情報が手に入りやすくなるということは、その分、応募に繋がりやすくなると言い換えることもできますので、改修による効果については、継続的に検証し、効果の有無を明らかにしたいと考えています。 新居 : なるほど。端的に言うと、ジョブメドレーにおける求人情報検索の体験を更に良くする為のプロジェクトということですね。 プロジェクトのはじまり 新居 : 今回のプロジェクトをどのようなメンバーと何をどのように行っていったのか、チーム構成から教えてください。 堀内 : チーム構成としては、自分を含めたエンジニアが 2 人いて、その他は、デザイナー、マーケター、プロダクトマネージャー(以下、PdM)がそれぞれ 1 人ずつプロジェクトにアサインされました。 プロジェクトのきっかけとしては、求職者体験を更に良くする為の企画の一つとしてマーケターと PdM が企画したところから始まり、その後、エンジニアとマーケター間で、細かい具体的な要件まで落とし込んで、プロジェクト化に至りました。 仕様についてメンバー間で徹底議論 堀内 : 要件まで落とし込んだ後は、具体的にそれらをどうやって実装するかを検討しました。特に UI の仕様については、デザイナーを中心にかなり長い時間をかけて詰めていきました 。 新居 : 確かに、情報の出し方によっては、かえって、求職者体験を悪化させてしまう可能性がありそうですね…。 堀内 : はい。使い勝手の良い UI にする為に、デザイナーがまず Figma でモックアップを作り、プロジェクトが始まった最初の週からデイリーの夕会などで、UI に関して徹底的にプロジェクトメンバー間で議論をしていました。 その時、特にメンバー全員が気を付けていたのは、実際に求職者が新しい一覧画面を使う時に、価値を感じてもらえるかどうかです。メドレーの Our Essentials の一つとして、 長期のカスタマー価値を追求 という項目があります。「全ての利用者にとって価値のある施策であること」、「利用者の片方だけを見て、もう片方を無視することがあってはならない」ということで、事業者側の求人情報を多く載せるだけではなく、求職者にとっての使い勝手も向上しているか?などを徹底的にチェックしていました。 使い勝手をチェックするにあたって、ジョブメドレーのメインユーザーの年齢層などが予め分かっていたので、その方達にとって使いやすいかどうかを判断軸として、考えていました。 デザイナーが完成させた UI でも、でき上がったモックアップをメンバーそれぞれの視点で触ってみると、改善点が出る場面がありました。そういった場合には、こういう UI にしてみたらどうか?などとそれぞれが意見し、良さそうな案についてはデザイナーがそれらの提案を取り込んだり、改良したりして、UI の仕様を詰めていきました。 技術面について 新居 : UI を実装に落とし込む際、技術面で工夫した点や気を付けていたポイントなどはありますか? 堀内 : 今回のプロジェクトでは、求人カードのデザインを大幅に変更する為、事前の影響範囲の洗い出しを徹底するよう注意していました。例えば、一覧画面における求人情報の出力量がかなり増えることで、1 ページあたりの読み込み処理に時間がかかってしまうことが予想された為、コンテンツの遅延読み込みやキャッシュの利用によりチューニングを行っています。また、デザイン変更が多くのページにわたって発生する為、リリース後の不具合を起こさないような対策を複数行っています。 担当したプロジェクトのリード 新居 : 今回のプロジェクトでは、エンジニアとして開発をしつつ、堀内さんがプロジェクトのリードを任されていたと聞いています。リードとしては、どんな動きをプロジェクトの中でされていたのでしょうか? 堀内 : まずは、最初にプロジェクト全体の大まかなスケジュールを引き、PdM と確認しました。次に、タスク分解をしてメンバーへのアサインを行ったり、プロジェクトのタスクのなかで不確実性を極力少なくする為に、自分達だけではコントロールできなさそうな部分を洗い出したりしました。洗い出したアンコントローラブルな部分は企画者と共に関係各所へ連絡・調整をしてもらうようお願いしていました。 新居 : なるほど。開発スケジュールの出し方など、その辺りで工夫した点などはありますか? 堀内 : スケジュールの共有方法については、視覚的に分かりやすいように、 FigJam を使って、ガントチャートのような形式で、ボードを作成しました。 メンバー毎にレーンを振り分けて、誰がいつまでに何をしなければいけないのかを夕会で確認していました。また、スケジュールのボードは常に、 第三者が見ても進捗状況が直ぐに分かるような状態にしておく よう努めました。 スケジュールボード スケジューリングで特に意識していたことは、不確実な部分を早めに洗い出すことでした。タスク分解をしていく中で、どこが自分達だけではコントロールできなさそうな部分なのかを把握することから始めていました。 現状の仕様や実装がどうなっているのかなどは直ぐに確認できるので、そういったところから、なるべく不確実な部分を減らしていき、最終的に各タスクの工数を算出するにあたって、不確実性の高さに応じて、バッファを設定しました。 新居 : 少し手を動かせば確認できるようなところは直ぐに確認して、なるべく解像度が高い状態にもっていく進め方は確かに良さそうですね!工数を見積もるにあたって、解像度が低い状態だと、見積もりの精度が低くなってしまいますからね。 堀内 : また、タスクは GitHub Issue を発行して管理していたのですが、切り出す粒度はなるべく細かくしていました。Issue の粒度が荒くなってしまうと、それに比例して手戻りリスクが高くなってしまうので、目安としては、長くても 2 日以内には完了できる粒度で切り出していました。 結果、今回のプロジェクトにおいては、ほとんど手戻りすることなく、ほぼ全ての期間において、オンスケジュールで進めることができました。 A/B テストの導入(効果検証の仕組み化) 新居 : オンスケで進められていたのは素晴らしいですね!開発者として実装することと、リードとしてプロジェクトを管理することの他、堀内さんが今回のプロジェクトで担当していた役割などはありますか? 堀内 : 今回のプロジェクトでは、効果検証を行う為の仕組みとして、A/B テストを本格的に導入する目標がありました。なぜ A/B テストを導入するのか? A/B テストで何をしたいのか?を明確にして、A/B テストの設計から導入、分析までを行いました。 統計的な分析手法の選択から実際のテスト環境の開発みたいなところまで一気通貫で、PdM、マーケター、エンジニアを巻き込んで行いました。 A/B テストで大事なのは、論理的な仮説を立ててそれを検証し、その結果からより効果があると判断できた施策を導入していく、というサイクルだと考えています。とはいえ、有意差が出たからといって必ずしも導入したほうが良いというわけではありません。その時の経営面のメリット・デメリットを考慮し、これまでの経験にもとづいた判断も加えて、より効果の高い施策を継続的に導入できるようにする、というのが今回の目標です。 今回のプロジェクトを皮切りに、他のプロジェクトでも同じように A/B テストの仕組みを使える状態にしたい。そういった横展開や汎用性みたいなところも、プロジェクト当初からセットで考えていました。 新居 : とても興味深い取り組みですね!実際、どのように仕組み化を進めていくのでしょうか? 堀内 : A/B テストに必要な知見をドキュメント化して、それを見るだけで、エンジニアのサポートを受けつつ誰でも A/B テストが実施できるようにしていく取り組みや、スプレッドシートにデータを入力すれば、簡単に結果が判るような仕組み化を進行中です。 機能のリリース時は、その結果に応じて結局、どうすれば良いのか?といった指針も Issue やドキュメントに記載しております。 実装や設計面における悩み 新居 : 実装や設計面において、今回のプロジェクトで悩んだようなところはありましたか? 堀内 : はい。自分自身の力量の問題で、ドメイン知識や技術力などが不足していたことから、ジョブメドレーのコードの中で今までの慣習を受け継ぎつつ、良い感じに使い回しができるコードをどう上手く書いていくか、という点について悩んでいました。 新居 : なるほど…。ジョブメドレー自体が 10 年以上の歴史があるサービスなので、そこに手を入れていくのは確かに難しい部分もありそうですね。その悩みに対しては、どう対策をされていたのでしょう? 堀内 : そうですね。今の自分の知見だけで設計と実装を進めてしまうと手戻りが発生してしまう恐れがあったので、まずはドメイン知識が豊富な、同じチーム内のベテラン社員に設計レビューをお願いしました。そこで、ジョブメドレーのインフラ構成におけるキャッシュ戦略や、保守性を高める為の実装の切り出し方など、壁打ちで相談させてもらっていました。 その先輩社員との設計レビュー以外にも、普段から行ってもらっている 1on1 ミーティングを通じて、自分のメンターと相談させてもらい、どういう設計だったら使いやすいのか、などを様々な視点から検討しました。 新居 : なるべく手戻りが発生しないようにする為に、 設計段階で身近にいるエンジニアを巻き込んで進めていった のですね。直ぐに相談できる相手がいるのは心強いですね。 学びになった点 新居 : 今回のプロジェクトを通じて、特に学びになった点を教えていただけますか? 堀内 : 技術的な面とプロジェクトの進め方の面でそれぞれ学びがありました。 技術的な面で言うと、 保守性が高く、今後、運用しやすいコードとはどういうものなのかというのを、今回の実装を通じて体得できた ところだと思っています。 プロジェクトの進め方の面では、過去のプロジェクトのドキュメントを参考に、そこに書かれていた知見を活かしながら、プロジェクトを進めていったので、ノウハウのようなところが学べたかと思っています。 新卒研修・開発実践のリードとの違い 新居 : 堀内さんは、新卒研修の一環で行われた開発実践の期間中、新卒同士のチーム開発でもリードを務めていたと思います。今回のプロジェクトを通じて経験されたリードとの違いについて教えてください。 堀内 : プロジェクトの規模や影響範囲の違いなどから、研修時よりも多くの関係者との調整が必要でした。全員と情報を共有・連携しつつ、プロジェクトを進める必要があった点が一番大きな違いでした。 また、開発実践のプロジェクトはあくまで社内向けシステムを構築するものだったので、会社の売上について意識することはありませんでしたが、今回のプロジェクトでは、施策によって応募率が下がった場合に売上に悪影響を及ぼす可能性がありましたので、開発部署以外への影響を意識して管理する必要があったことも大きいポイントだったと思っています。 ジョブメドレーの開発におけるやりがい 新居 : ここまで直近で担当されていたプロジェクトについて聞いてきましたが、ジョブメドレーの開発をしていて、特にやりがいを感じる場面について、教えてもらえますか? 堀内 : やはり、利用してくださっている求職者や事業者の方々に向けて、新規機能開発や機能改修を行う工程です。目的と照らし合わせて仕様面から妥協せずに考え抜き、リリースするまでの過程にやりがいを感じています。ユーザー数がとても多いだけに、良い機能をリリースできたときの反響も大きく、非常に楽しいです。 勿論、エンジニアとして技術力を上げていくことも貪欲に今後もやっていかなければならない部分だと思っていますが、企画として案件があがってきた段階で、それをなぜ作るのか?なぜそれで効果が出ると思っているのか?などと考えることが、企画職だけでなく、エンジニアにとっても大事だと考えています。 リリースした後には、実際に効果が出たのか出なかったのか、なぜ効果が出たのか、出なかったのか?などの効果検証を行った上で、PdM を中心にエンジニア以外のメンバーも交えて振り返り、次の施策に活かしていくといった、 PDCA サイクルを回していっている開発スタイルに楽しさを感じています 。 これは、自分の入社時にやりたいこととしてあげていた、エンジニアリング面だけではなくビジネス面についても多くの経験を積みたい、というところにリンクしていて、今在籍しているジョブメドレーの開発チームでは、企画やマーケティングを行う人達とエンジニアがとても近い距離にいることで、様々な知見を得られつつアウトプットできる、とても学びになる環境だと感じています。 ジョブメドレー開発チームにおける 1 日の流れ 新居 : 現在のジョブメドレー開発チームは、Growth Unit(求職者の利便性を高める施策を行うチーム)と Customer Unit(求人を掲載する事業者が使う採用管理システムの改善施策を行うチーム)の 2 つに分かれているんですよね。堀内さんが所属しているのは、Growth Unit の方ですが、参加されているミーティングなど、1 日の流れについて教えてください。 堀内 : まず、勤務開始の 10:00 頃には Slack に投稿する形で、その日のタスクや参加予定のミーティングなどについて、開発用 channel で共有します。 現在の自分のスケジュールでは、午前中は特に定常的なミーティングはありません。但し、アサインされるプロジェクトによっては、午前中に「朝会」としてプロジェクトメンバー同士が集まって、進捗共有などを行っています。 お昼過ぎの 14:30 からは、 30 分程度、Growth Unit のエンジニアだけで集まるミーティングがあります。この場では、各自の進捗状況や抱えている不安・不明点などを共有します。今何に困っているのかを共有するだけでなく、 困りごとに対し、各々が知恵を出し合って、問題解決までのリードタイムを短くする ことができる貴重な場となっています。 18:00 からは「夕会」として、PdM とデザイナーも含めた Growth Unit 全体のミーティングがあります。そこで各自、「今日やったこと」や担当 Issue のリリース目処などを共有しています。この場では、エンジニア以外のメンバーも参加しているので、そこまで技術的に突っ込んだ話まではしません。 さらに、終業前にはまたエンジニアだけで集まり、FigJam を使って付箋を貼っていく形で、 各自が技術的トピックや、特定のドメインについて聞きたいことなどを持ち寄り、ざっくばらんに雑談形式で話す 「技術共有会」を行っています。 技術共有会の雰囲気 ミーティングの場以外は黙々と開発を進めていきますが、聞きたいことや相談したいことが出てきたら都度、開発用のスレッドに投稿しています。 新居 : なるほど。質問や相談はしやすい感じですか? 堀内 : はい。勿論、自分で調べられることはなるべく調べて、自己解決するのが基本ではありますが、あまり調査に時間をかけ過ぎて全然進まないような事態に陥っても良くないので、それらを皆、前提として意識した上で、聞くべき時は躊躇なく聞ける雰囲気があります。 新居 : ミーティングの種類としては、先ほどあげてもらったもの以外にありますか? 堀内 : あとは、週一で行われている「プロダクト定例」と隔週で行ってもらっている「1on1 ミーティング」があります。 プロダクト定例は、比較的規模の大きいミーティングで、我々、プロダクト開発室以外のメンバーの他、マーケティング室や事業企画室のメンバーも含めて、大体いつも 40 名前後の人数で参加しています。 この場では、現在追っている KPI だったり、ジョブメドレーで動いている全てのプロジェクト状況が共有されます。そこで共有される KPI の数字によって、次に着手すべき Issue にも影響してくるので、この場で共有される情報をしっかりとインプットして、今後の心構えとすることにしています。 直近で担当したプロジェクトの話でもちらっと出てきましたが、1on1 ミーティングは担当メンターと行なっています。普段の仕事の相談から、自分のキャリアに関するような相談をしており、メンターさんの経験なども教えていただきながら、勉強させていただいています。 1on1 ミーティングの意義としては、そういった仕事の他、 プライベートのことも含めて、何でも相談ができる場 としてあるのですが、1on1 を通じて、メンターさんとの信頼関係も築けるので、チームビルディングの一つとしても機能しています。 ジョブメドレー開発チームで一緒に働きたいと思う人はどんな人? 新居 : 堀内さんがジョブメドレー開発チームで一緒に働きたいと思う人はどんな人でしょうか? 堀内 : 今の環境で働いてて良かったなあと思うことは、 人間関係が良好であること です。もちろん馴れ合いの中での業務というわけではなく、各々が しっかりと仕事に対して責任を持ちつつ、チーム内で生じた違和感は遠慮なく指摘し合い受けとめる という、良い意味で心理的安全性のある環境でプロダクト開発に集中して取り組めることです。 このような環境で、課題に対して長期的な視点で本質を捉え、目的を設定し、ボトムアップで解決策の立案から改善までを主導すること、さらには仕組み化まで行うことが好きな方、またはそれらをやってみたい方が合っているのではないか、と思います。 自分もそのような人間になりたいと思いながら、日々仕事に取り組んでいます。 さいごに ジョブメドレー開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いてきましたが、いかがだったでしょうか? こんなチームでプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! https://www.medley.jp/jobs/
はじめに みなさん、こんにちは。エンジニアの新居です。今回は医療介護求人サイトの ジョブメドレー 開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いていこうと思います。 インタビュイー紹介 堀内さん 2021 年入社。人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属。ジョブメドレーの開発を担当。現在は求職者側の UI/UX 改善などの開発に携わる。 堀内さん メドレーに入社した理由 新居 : まずはじめに、堀内さんはどういう経緯でメドレーに入社したのでしたっけ? 堀内 : 以前のインタビュー( 21 年新卒入社エンジニアと座談会で振り返る新卒研修 )の時にもお話したのですが、就活の軸として、自分が成長できそうか・風通しが良いか・合理的な社風かなどを軸として探していました。特に、 ビジネス側と開発側の距離が近い 部分に惹かれ、総合的にメドレーが良さそうだと考えて入社しました。 新居 : 入社前の印象と実際働きだしてからで、ズレなどはありましたか? 堀内 : そんなにギャップはなくて、社風とか人間関係みたいなところは入社前のイメージ通りでした。入社前に何回か会社見学をさせてもらったり、その時の社員と複数回面談をセッティングしてもらったりして、そこでイメージをすり合わせできたことが大きかったですね。 新居 : 何人くらいの社員と会ったのですか? 堀内 : 6 ~ 7 人です。色々なポジションや性格の人達と話ができたので、安心して入社することができました。 新卒研修の開発 OJT ではどんなことをやっていたか? 新居 : 堀内さんは新卒研修の開発 OJT のタイミングでジョブメドレー開発チームに仮配属されたのですよね。当時はどういうことをやってたのですか? 堀内 : まずはジョブメドレーのドメイン知識をしっかり吸収していこうということで、業界や業務自体の理解を進めました。 その後、求職者と事業者のサポート業務を行う社内オペレーターが利用する画面の拡張タスクに取り組みました。 技術スタックは Next.js / React / TypeScript / GraphQL といったシステムで、個人的にあまり触れてこなかった領域だったので、技術のキャッチアップもしつつ丁寧に対応していきました。 また、開発 OJT よりも前の研修で輪読会も経験したのですが、その時に学んだ Cookie や Session まわりの知識が役に立ったり、座学で学んだことがこうやって実践で活きてくるんだな、ということを実感 できてとても良い経験になりました。 今までとは比べ物にならないくらい大規模なシステムだったので、プレッシャーも大きかったのですが、チームの人達と相談したり、細かいレビュー・サポートがあったおかげで、しっかり成果を出すことができたと思います。 新居 : 良い OJT ですね!堀内さん自身の努力があったのは勿論ですが、チームに新しいメンバーを迎え入れて成長をサポートする環境が整っていたのも良かったんでしょうね。 ちなみに開発 OJT の中で大変だったこととかはありますか? 堀内 : そうですね、やはり大規模な既存コードの把握・理解が大変でした。社内オペレーターが使う画面とジョブメドレー本体はそれぞれ別システムとして連携しているので、ジョブメドレー本体のコードや仕組みの理解も必要です。 大変だった分、 システムの全体像を理解するのに大いに役立った ので、今思い返すと良い経験だったなあと思います。 色々な経験を積めた求人カード改修プロジェクト 新居 : では、新卒研修を終えてから 1 年以上経っていて、入社 2 年目の終わりに差し掛かっている現在ですが、直近で堀内さんがリードを務めていた、ジョブメドレーの求人カード改修プロジェクトについて話を聞いていきたいと思います。まずは、このプロジェクトの概要について、教えてもらえますか? 求人カード改修プロジェクトとは? 堀内 : それでは、プロジェクト説明に先立ちまして、まず、ジョブメドレーの「求人カード」について簡単に説明します。 ジョブメドレーで求人を検索すると、検索結果が並ぶ画面が出てきます。我々が「求人カード」と呼んでいるのは、この検索結果画面上に表示される、求人情報のことになります。 検索結果画面の求人カードでは、給与や大まかな業務内容、応募要件、職場の住所などが分かる形になっていますが、求職者の方々がより便利に使っていただけるよう、情報の出し方を見直すことが今回のプロジェクト内容です。 もう少し詳しくお話すると、採用決定率の改善を目的に、どのような改善を実施すべきか検討しています。 求職者にとって欲しい情報が手に入りやすくなるということは、その分、応募に繋がりやすくなると言い換えることもできますので、改修による効果については、継続的に検証し、効果の有無を明らかにしたいと考えています。 新居 : なるほど。端的に言うと、ジョブメドレーにおける求人情報検索の体験を更に良くする為のプロジェクトということですね。 プロジェクトのはじまり 新居 : 今回のプロジェクトをどのようなメンバーと何をどのように行っていったのか、チーム構成から教えてください。 堀内 : チーム構成としては、自分を含めたエンジニアが 2 人いて、その他は、デザイナー、マーケター、プロダクトマネージャー(以下、PdM)がそれぞれ 1 人ずつプロジェクトにアサインされました。 プロジェクトのきっかけとしては、求職者体験を更に良くする為の企画の一つとしてマーケターと PdM が企画したところから始まり、その後、エンジニアとマーケター間で、細かい具体的な要件まで落とし込んで、プロジェクト化に至りました。 仕様についてメンバー間で徹底議論 堀内 : 要件まで落とし込んだ後は、具体的にそれらをどうやって実装するかを検討しました。特に UI の仕様については、デザイナーを中心にかなり長い時間をかけて詰めていきました 。 新居 : 確かに、情報の出し方によっては、かえって、求職者体験を悪化させてしまう可能性がありそうですね…。 堀内 : はい。使い勝手の良い UI にする為に、デザイナーがまず Figma でモックアップを作り、プロジェクトが始まった最初の週からデイリーの夕会などで、UI に関して徹底的にプロジェクトメンバー間で議論をしていました。 その時、特にメンバー全員が気を付けていたのは、実際に求職者が新しい一覧画面を使う時に、価値を感じてもらえるかどうかです。メドレーの Our Essentials の一つとして、 長期のカスタマー価値を追求 という項目があります。「全ての利用者にとって価値のある施策であること」、「利用者の片方だけを見て、もう片方を無視することがあってはならない」ということで、事業者側の求人情報を多く載せるだけではなく、求職者にとっての使い勝手も向上しているか?などを徹底的にチェックしていました。 使い勝手をチェックするにあたって、ジョブメドレーのメインユーザーの年齢層などが予め分かっていたので、その方達にとって使いやすいかどうかを判断軸として、考えていました。 デザイナーが完成させた UI でも、でき上がったモックアップをメンバーそれぞれの視点で触ってみると、改善点が出る場面がありました。そういった場合には、こういう UI にしてみたらどうか?などとそれぞれが意見し、良さそうな案についてはデザイナーがそれらの提案を取り込んだり、改良したりして、UI の仕様を詰めていきました。 技術面について 新居 : UI を実装に落とし込む際、技術面で工夫した点や気を付けていたポイントなどはありますか? 堀内 : 今回のプロジェクトでは、求人カードのデザインを大幅に変更する為、事前の影響範囲の洗い出しを徹底するよう注意していました。例えば、一覧画面における求人情報の出力量がかなり増えることで、1 ページあたりの読み込み処理に時間がかかってしまうことが予想された為、コンテンツの遅延読み込みやキャッシュの利用によりチューニングを行っています。また、デザイン変更が多くのページにわたって発生する為、リリース後の不具合を起こさないような対策を複数行っています。 担当したプロジェクトのリード 新居 : 今回のプロジェクトでは、エンジニアとして開発をしつつ、堀内さんがプロジェクトのリードを任されていたと聞いています。リードとしては、どんな動きをプロジェクトの中でされていたのでしょうか? 堀内 : まずは、最初にプロジェクト全体の大まかなスケジュールを引き、PdM と確認しました。次に、タスク分解をしてメンバーへのアサインを行ったり、プロジェクトのタスクのなかで不確実性を極力少なくする為に、自分達だけではコントロールできなさそうな部分を洗い出したりしました。洗い出したアンコントローラブルな部分は企画者と共に関係各所へ連絡・調整をしてもらうようお願いしていました。 新居 : なるほど。開発スケジュールの出し方など、その辺りで工夫した点などはありますか? 堀内 : スケジュールの共有方法については、視覚的に分かりやすいように、 FigJam を使って、ガントチャートのような形式で、ボードを作成しました。 メンバー毎にレーンを振り分けて、誰がいつまでに何をしなければいけないのかを夕会で確認していました。また、スケジュールのボードは常に、 第三者が見ても進捗状況が直ぐに分かるような状態にしておく よう努めました。 スケジュールボード スケジューリングで特に意識していたことは、不確実な部分を早めに洗い出すことでした。タスク分解をしていく中で、どこが自分達だけではコントロールできなさそうな部分なのかを把握することから始めていました。 現状の仕様や実装がどうなっているのかなどは直ぐに確認できるので、そういったところから、なるべく不確実な部分を減らしていき、最終的に各タスクの工数を算出するにあたって、不確実性の高さに応じて、バッファを設定しました。 新居 : 少し手を動かせば確認できるようなところは直ぐに確認して、なるべく解像度が高い状態にもっていく進め方は確かに良さそうですね!工数を見積もるにあたって、解像度が低い状態だと、見積もりの精度が低くなってしまいますからね。 堀内 : また、タスクは GitHub Issue を発行して管理していたのですが、切り出す粒度はなるべく細かくしていました。Issue の粒度が荒くなってしまうと、それに比例して手戻りリスクが高くなってしまうので、目安としては、長くても 2 日以内には完了できる粒度で切り出していました。 結果、今回のプロジェクトにおいては、ほとんど手戻りすることなく、ほぼ全ての期間において、オンスケジュールで進めることができました。 A/B テストの導入(効果検証の仕組み化) 新居 : オンスケで進められていたのは素晴らしいですね!開発者として実装することと、リードとしてプロジェクトを管理することの他、堀内さんが今回のプロジェクトで担当していた役割などはありますか? 堀内 : 今回のプロジェクトでは、効果検証を行う為の仕組みとして、A/B テストを本格的に導入する目標がありました。なぜ A/B テストを導入するのか? A/B テストで何をしたいのか?を明確にして、A/B テストの設計から導入、分析までを行いました。 統計的な分析手法の選択から実際のテスト環境の開発みたいなところまで一気通貫で、PdM、マーケター、エンジニアを巻き込んで行いました。 A/B テストで大事なのは、論理的な仮説を立ててそれを検証し、その結果からより効果があると判断できた施策を導入していく、というサイクルだと考えています。とはいえ、有意差が出たからといって必ずしも導入したほうが良いというわけではありません。その時の経営面のメリット・デメリットを考慮し、これまでの経験にもとづいた判断も加えて、より効果の高い施策を継続的に導入できるようにする、というのが今回の目標です。 今回のプロジェクトを皮切りに、他のプロジェクトでも同じように A/B テストの仕組みを使える状態にしたい。そういった横展開や汎用性みたいなところも、プロジェクト当初からセットで考えていました。 新居 : とても興味深い取り組みですね!実際、どのように仕組み化を進めていくのでしょうか? 堀内 : A/B テストに必要な知見をドキュメント化して、それを見るだけで、エンジニアのサポートを受けつつ誰でも A/B テストが実施できるようにしていく取り組みや、スプレッドシートにデータを入力すれば、簡単に結果が判るような仕組み化を進行中です。 機能のリリース時は、その結果に応じて結局、どうすれば良いのか?といった指針も Issue やドキュメントに記載しております。 実装や設計面における悩み 新居 : 実装や設計面において、今回のプロジェクトで悩んだようなところはありましたか? 堀内 : はい。自分自身の力量の問題で、ドメイン知識や技術力などが不足していたことから、ジョブメドレーのコードの中で今までの慣習を受け継ぎつつ、良い感じに使い回しができるコードをどう上手く書いていくか、という点について悩んでいました。 新居 : なるほど…。ジョブメドレー自体が 10 年以上の歴史があるサービスなので、そこに手を入れていくのは確かに難しい部分もありそうですね。その悩みに対しては、どう対策をされていたのでしょう? 堀内 : そうですね。今の自分の知見だけで設計と実装を進めてしまうと手戻りが発生してしまう恐れがあったので、まずはドメイン知識が豊富な、同じチーム内のベテラン社員に設計レビューをお願いしました。そこで、ジョブメドレーのインフラ構成におけるキャッシュ戦略や、保守性を高める為の実装の切り出し方など、壁打ちで相談させてもらっていました。 その先輩社員との設計レビュー以外にも、普段から行ってもらっている 1on1 ミーティングを通じて、自分のメンターと相談させてもらい、どういう設計だったら使いやすいのか、などを様々な視点から検討しました。 新居 : なるべく手戻りが発生しないようにする為に、 設計段階で身近にいるエンジニアを巻き込んで進めていった のですね。直ぐに相談できる相手がいるのは心強いですね。 学びになった点 新居 : 今回のプロジェクトを通じて、特に学びになった点を教えていただけますか? 堀内 : 技術的な面とプロジェクトの進め方の面でそれぞれ学びがありました。 技術的な面で言うと、 保守性が高く、今後、運用しやすいコードとはどういうものなのかというのを、今回の実装を通じて体得できた ところだと思っています。 プロジェクトの進め方の面では、過去のプロジェクトのドキュメントを参考に、そこに書かれていた知見を活かしながら、プロジェクトを進めていったので、ノウハウのようなところが学べたかと思っています。 新卒研修・開発実践のリードとの違い 新居 : 堀内さんは、新卒研修の一環で行われた開発実践の期間中、新卒同士のチーム開発でもリードを務めていたと思います。今回のプロジェクトを通じて経験されたリードとの違いについて教えてください。 堀内 : プロジェクトの規模や影響範囲の違いなどから、研修時よりも多くの関係者との調整が必要でした。全員と情報を共有・連携しつつ、プロジェクトを進める必要があった点が一番大きな違いでした。 また、開発実践のプロジェクトはあくまで社内向けシステムを構築するものだったので、会社の売上について意識することはありませんでしたが、今回のプロジェクトでは、施策によって応募率が下がった場合に売上に悪影響を及ぼす可能性がありましたので、開発部署以外への影響を意識して管理する必要があったことも大きいポイントだったと思っています。 ジョブメドレーの開発におけるやりがい 新居 : ここまで直近で担当されていたプロジェクトについて聞いてきましたが、ジョブメドレーの開発をしていて、特にやりがいを感じる場面について、教えてもらえますか? 堀内 : やはり、利用してくださっている求職者や事業者の方々に向けて、新規機能開発や機能改修を行う工程です。目的と照らし合わせて仕様面から妥協せずに考え抜き、リリースするまでの過程にやりがいを感じています。ユーザー数がとても多いだけに、良い機能をリリースできたときの反響も大きく、非常に楽しいです。 勿論、エンジニアとして技術力を上げていくことも貪欲に今後もやっていかなければならない部分だと思っていますが、企画として案件があがってきた段階で、それをなぜ作るのか?なぜそれで効果が出ると思っているのか?などと考えることが、企画職だけでなく、エンジニアにとっても大事だと考えています。 リリースした後には、実際に効果が出たのか出なかったのか、なぜ効果が出たのか、出なかったのか?などの効果検証を行った上で、PdM を中心にエンジニア以外のメンバーも交えて振り返り、次の施策に活かしていくといった、 PDCA サイクルを回していっている開発スタイルに楽しさを感じています 。 これは、自分の入社時にやりたいこととしてあげていた、エンジニアリング面だけではなくビジネス面についても多くの経験を積みたい、というところにリンクしていて、今在籍しているジョブメドレーの開発チームでは、企画やマーケティングを行う人達とエンジニアがとても近い距離にいることで、様々な知見を得られつつアウトプットできる、とても学びになる環境だと感じています。 ジョブメドレー開発チームにおける 1 日の流れ 新居 : 現在のジョブメドレー開発チームは、Growth Unit(求職者の利便性を高める施策を行うチーム)と Customer Unit(求人を掲載する事業者が使う採用管理システムの改善施策を行うチーム)の 2 つに分かれているんですよね。堀内さんが所属しているのは、Growth Unit の方ですが、参加されているミーティングなど、1 日の流れについて教えてください。 堀内 : まず、勤務開始の 10:00 頃には Slack に投稿する形で、その日のタスクや参加予定のミーティングなどについて、開発用 channel で共有します。 現在の自分のスケジュールでは、午前中は特に定常的なミーティングはありません。但し、アサインされるプロジェクトによっては、午前中に「朝会」としてプロジェクトメンバー同士が集まって、進捗共有などを行っています。 お昼過ぎの 14:30 からは、 30 分程度、Growth Unit のエンジニアだけで集まるミーティングがあります。この場では、各自の進捗状況や抱えている不安・不明点などを共有します。今何に困っているのかを共有するだけでなく、 困りごとに対し、各々が知恵を出し合って、問題解決までのリードタイムを短くする ことができる貴重な場となっています。 18:00 からは「夕会」として、PdM とデザイナーも含めた Growth Unit 全体のミーティングがあります。そこで各自、「今日やったこと」や担当 Issue のリリース目処などを共有しています。この場では、エンジニア以外のメンバーも参加しているので、そこまで技術的に突っ込んだ話まではしません。 さらに、終業前にはまたエンジニアだけで集まり、FigJam を使って付箋を貼っていく形で、 各自が技術的トピックや、特定のドメインについて聞きたいことなどを持ち寄り、ざっくばらんに雑談形式で話す 「技術共有会」を行っています。 技術共有会の雰囲気 ミーティングの場以外は黙々と開発を進めていきますが、聞きたいことや相談したいことが出てきたら都度、開発用のスレッドに投稿しています。 新居 : なるほど。質問や相談はしやすい感じですか? 堀内 : はい。勿論、自分で調べられることはなるべく調べて、自己解決するのが基本ではありますが、あまり調査に時間をかけ過ぎて全然進まないような事態に陥っても良くないので、それらを皆、前提として意識した上で、聞くべき時は躊躇なく聞ける雰囲気があります。 新居 : ミーティングの種類としては、先ほどあげてもらったもの以外にありますか? 堀内 : あとは、週一で行われている「プロダクト定例」と隔週で行ってもらっている「1on1 ミーティング」があります。 プロダクト定例は、比較的規模の大きいミーティングで、我々、プロダクト開発室以外のメンバーの他、マーケティング室や事業企画室のメンバーも含めて、大体いつも 40 名前後の人数で参加しています。 この場では、現在追っている KPI だったり、ジョブメドレーで動いている全てのプロジェクト状況が共有されます。そこで共有される KPI の数字によって、次に着手すべき Issue にも影響してくるので、この場で共有される情報をしっかりとインプットして、今後の心構えとすることにしています。 直近で担当したプロジェクトの話でもちらっと出てきましたが、1on1 ミーティングは担当メンターと行なっています。普段の仕事の相談から、自分のキャリアに関するような相談をしており、メンターさんの経験なども教えていただきながら、勉強させていただいています。 1on1 ミーティングの意義としては、そういった仕事の他、 プライベートのことも含めて、何でも相談ができる場 としてあるのですが、1on1 を通じて、メンターさんとの信頼関係も築けるので、チームビルディングの一つとしても機能しています。 ジョブメドレー開発チームで一緒に働きたいと思う人はどんな人? 新居 : 堀内さんがジョブメドレー開発チームで一緒に働きたいと思う人はどんな人でしょうか? 堀内 : 今の環境で働いてて良かったなあと思うことは、 人間関係が良好であること です。もちろん馴れ合いの中での業務というわけではなく、各々が しっかりと仕事に対して責任を持ちつつ、チーム内で生じた違和感は遠慮なく指摘し合い受けとめる という、良い意味で心理的安全性のある環境でプロダクト開発に集中して取り組めることです。 このような環境で、課題に対して長期的な視点で本質を捉え、目的を設定し、ボトムアップで解決策の立案から改善までを主導すること、さらには仕組み化まで行うことが好きな方、またはそれらをやってみたい方が合っているのではないか、と思います。 自分もそのような人間になりたいと思いながら、日々仕事に取り組んでいます。 さいごに ジョブメドレー開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いてきましたが、いかがだったでしょうか? こんなチームでプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの新居です。今回は医療介護求人サイトの ジョブメドレー 開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いていこうと思います。 インタビュイー紹介 堀内さん 2021 年入社。人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属。ジョブメドレーの開発を担当。現在は求職者側の UI/UX 改善などの開発に携わる。 堀内さん メドレーに入社した理由 新居 : まずはじめに、堀内さんはどういう経緯でメドレーに入社したのでしたっけ? 堀内 : 以前のインタビュー( 21 年新卒入社エンジニアと座談会で振り返る新卒研修 )の時にもお話したのですが、就活の軸として、自分が成長できそうか・風通しが良いか・合理的な社風かなどを軸として探していました。特に、 ビジネス側と開発側の距離が近い 部分に惹かれ、総合的にメドレーが良さそうだと考えて入社しました。 新居 : 入社前の印象と実際働きだしてからで、ズレなどはありましたか? 堀内 : そんなにギャップはなくて、社風とか人間関係みたいなところは入社前のイメージ通りでした。入社前に何回か会社見学をさせてもらったり、その時の社員と複数回面談をセッティングしてもらったりして、そこでイメージをすり合わせできたことが大きかったですね。 新居 : 何人くらいの社員と会ったのですか? 堀内 : 6 ~ 7 人です。色々なポジションや性格の人達と話ができたので、安心して入社することができました。 新卒研修の開発 OJT ではどんなことをやっていたか? 新居 : 堀内さんは新卒研修の開発 OJT のタイミングでジョブメドレー開発チームに仮配属されたのですよね。当時はどういうことをやってたのですか? 堀内 : まずはジョブメドレーのドメイン知識をしっかり吸収していこうということで、業界や業務自体の理解を進めました。 その後、求職者と事業者のサポート業務を行う社内オペレーターが利用する画面の拡張タスクに取り組みました。 技術スタックは Next.js / React / TypeScript / GraphQL といったシステムで、個人的にあまり触れてこなかった領域だったので、技術のキャッチアップもしつつ丁寧に対応していきました。 また、開発 OJT よりも前の研修で輪読会も経験したのですが、その時に学んだ Cookie や Session まわりの知識が役に立ったり、座学で学んだことがこうやって実践で活きてくるんだな、ということを実感 できてとても良い経験になりました。 今までとは比べ物にならないくらい大規模なシステムだったので、プレッシャーも大きかったのですが、チームの人達と相談したり、細かいレビュー・サポートがあったおかげで、しっかり成果を出すことができたと思います。 新居 : 良い OJT ですね!堀内さん自身の努力があったのは勿論ですが、チームに新しいメンバーを迎え入れて成長をサポートする環境が整っていたのも良かったんでしょうね。 ちなみに開発 OJT の中で大変だったこととかはありますか? 堀内 : そうですね、やはり大規模な既存コードの把握・理解が大変でした。社内オペレーターが使う画面とジョブメドレー本体はそれぞれ別システムとして連携しているので、ジョブメドレー本体のコードや仕組みの理解も必要です。 大変だった分、 システムの全体像を理解するのに大いに役立った ので、今思い返すと良い経験だったなあと思います。 色々な経験を積めた求人カード改修プロジェクト 新居 : では、新卒研修を終えてから 1 年以上経っていて、入社 2 年目の終わりに差し掛かっている現在ですが、直近で堀内さんがリードを務めていた、ジョブメドレーの求人カード改修プロジェクトについて話を聞いていきたいと思います。まずは、このプロジェクトの概要について、教えてもらえますか? 求人カード改修プロジェクトとは? 堀内 : それでは、プロジェクト説明に先立ちまして、まず、ジョブメドレーの「求人カード」について簡単に説明します。 ジョブメドレーで求人を検索すると、検索結果が並ぶ画面が出てきます。我々が「求人カード」と呼んでいるのは、この検索結果画面上に表示される、求人情報のことになります。 検索結果画面の求人カードでは、給与や大まかな業務内容、応募要件、職場の住所などが分かる形になっていますが、求職者の方々がより便利に使っていただけるよう、情報の出し方を見直すことが今回のプロジェクト内容です。 もう少し詳しくお話すると、採用決定率の改善を目的に、どのような改善を実施すべきか検討しています。 求職者にとって欲しい情報が手に入りやすくなるということは、その分、応募に繋がりやすくなると言い換えることもできますので、改修による効果については、継続的に検証し、効果の有無を明らかにしたいと考えています。 新居 : なるほど。端的に言うと、ジョブメドレーにおける求人情報検索の体験を更に良くする為のプロジェクトということですね。 プロジェクトのはじまり 新居 : 今回のプロジェクトをどのようなメンバーと何をどのように行っていったのか、チーム構成から教えてください。 堀内 : チーム構成としては、自分を含めたエンジニアが 2 人いて、その他は、デザイナー、マーケター、プロダクトマネージャー(以下、PdM)がそれぞれ 1 人ずつプロジェクトにアサインされました。 プロジェクトのきっかけとしては、求職者体験を更に良くする為の企画の一つとしてマーケターと PdM が企画したところから始まり、その後、エンジニアとマーケター間で、細かい具体的な要件まで落とし込んで、プロジェクト化に至りました。 仕様についてメンバー間で徹底議論 堀内 : 要件まで落とし込んだ後は、具体的にそれらをどうやって実装するかを検討しました。特に UI の仕様については、デザイナーを中心にかなり長い時間をかけて詰めていきました 。 新居 : 確かに、情報の出し方によっては、かえって、求職者体験を悪化させてしまう可能性がありそうですね…。 堀内 : はい。使い勝手の良い UI にする為に、デザイナーがまず Figma でモックアップを作り、プロジェクトが始まった最初の週からデイリーの夕会などで、UI に関して徹底的にプロジェクトメンバー間で議論をしていました。 その時、特にメンバー全員が気を付けていたのは、実際に求職者が新しい一覧画面を使う時に、価値を感じてもらえるかどうかです。メドレーの Our Essentials の一つとして、 長期のカスタマー価値を追求 という項目があります。「全ての利用者にとって価値のある施策であること」、「利用者の片方だけを見て、もう片方を無視することがあってはならない」ということで、事業者側の求人情報を多く載せるだけではなく、求職者にとっての使い勝手も向上しているか?などを徹底的にチェックしていました。 使い勝手をチェックするにあたって、ジョブメドレーのメインユーザーの年齢層などが予め分かっていたので、その方達にとって使いやすいかどうかを判断軸として、考えていました。 デザイナーが完成させた UI でも、でき上がったモックアップをメンバーそれぞれの視点で触ってみると、改善点が出る場面がありました。そういった場合には、こういう UI にしてみたらどうか?などとそれぞれが意見し、良さそうな案についてはデザイナーがそれらの提案を取り込んだり、改良したりして、UI の仕様を詰めていきました。 技術面について 新居 : UI を実装に落とし込む際、技術面で工夫した点や気を付けていたポイントなどはありますか? 堀内 : 今回のプロジェクトでは、求人カードのデザインを大幅に変更する為、事前の影響範囲の洗い出しを徹底するよう注意していました。例えば、一覧画面における求人情報の出力量がかなり増えることで、1 ページあたりの読み込み処理に時間がかかってしまうことが予想された為、コンテンツの遅延読み込みやキャッシュの利用によりチューニングを行っています。また、デザイン変更が多くのページにわたって発生する為、リリース後の不具合を起こさないような対策を複数行っています。 担当したプロジェクトのリード 新居 : 今回のプロジェクトでは、エンジニアとして開発をしつつ、堀内さんがプロジェクトのリードを任されていたと聞いています。リードとしては、どんな動きをプロジェクトの中でされていたのでしょうか? 堀内 : まずは、最初にプロジェクト全体の大まかなスケジュールを引き、PdM と確認しました。次に、タスク分解をしてメンバーへのアサインを行ったり、プロジェクトのタスクのなかで不確実性を極力少なくする為に、自分達だけではコントロールできなさそうな部分を洗い出したりしました。洗い出したアンコントローラブルな部分は企画者と共に関係各所へ連絡・調整をしてもらうようお願いしていました。 新居 : なるほど。開発スケジュールの出し方など、その辺りで工夫した点などはありますか? 堀内 : スケジュールの共有方法については、視覚的に分かりやすいように、 FigJam を使って、ガントチャートのような形式で、ボードを作成しました。 メンバー毎にレーンを振り分けて、誰がいつまでに何をしなければいけないのかを夕会で確認していました。また、スケジュールのボードは常に、 第三者が見ても進捗状況が直ぐに分かるような状態にしておく よう努めました。 スケジュールボード スケジューリングで特に意識していたことは、不確実な部分を早めに洗い出すことでした。タスク分解をしていく中で、どこが自分達だけではコントロールできなさそうな部分なのかを把握することから始めていました。 現状の仕様や実装がどうなっているのかなどは直ぐに確認できるので、そういったところから、なるべく不確実な部分を減らしていき、最終的に各タスクの工数を算出するにあたって、不確実性の高さに応じて、バッファを設定しました。 新居 : 少し手を動かせば確認できるようなところは直ぐに確認して、なるべく解像度が高い状態にもっていく進め方は確かに良さそうですね!工数を見積もるにあたって、解像度が低い状態だと、見積もりの精度が低くなってしまいますからね。 堀内 : また、タスクは GitHub Issue を発行して管理していたのですが、切り出す粒度はなるべく細かくしていました。Issue の粒度が荒くなってしまうと、それに比例して手戻りリスクが高くなってしまうので、目安としては、長くても 2 日以内には完了できる粒度で切り出していました。 結果、今回のプロジェクトにおいては、ほとんど手戻りすることなく、ほぼ全ての期間において、オンスケジュールで進めることができました。 A/B テストの導入(効果検証の仕組み化) 新居 : オンスケで進められていたのは素晴らしいですね!開発者として実装することと、リードとしてプロジェクトを管理することの他、堀内さんが今回のプロジェクトで担当していた役割などはありますか? 堀内 : 今回のプロジェクトでは、効果検証を行う為の仕組みとして、A/B テストを本格的に導入する目標がありました。なぜ A/B テストを導入するのか? A/B テストで何をしたいのか?を明確にして、A/B テストの設計から導入、分析までを行いました。 統計的な分析手法の選択から実際のテスト環境の開発みたいなところまで一気通貫で、PdM、マーケター、エンジニアを巻き込んで行いました。 A/B テストで大事なのは、論理的な仮説を立ててそれを検証し、その結果からより効果があると判断できた施策を導入していく、というサイクルだと考えています。とはいえ、有意差が出たからといって必ずしも導入したほうが良いというわけではありません。その時の経営面のメリット・デメリットを考慮し、これまでの経験にもとづいた判断も加えて、より効果の高い施策を継続的に導入できるようにする、というのが今回の目標です。 今回のプロジェクトを皮切りに、他のプロジェクトでも同じように A/B テストの仕組みを使える状態にしたい。そういった横展開や汎用性みたいなところも、プロジェクト当初からセットで考えていました。 新居 : とても興味深い取り組みですね!実際、どのように仕組み化を進めていくのでしょうか? 堀内 : A/B テストに必要な知見をドキュメント化して、それを見るだけで、エンジニアのサポートを受けつつ誰でも A/B テストが実施できるようにしていく取り組みや、スプレッドシートにデータを入力すれば、簡単に結果が判るような仕組み化を進行中です。 機能のリリース時は、その結果に応じて結局、どうすれば良いのか?といった指針も Issue やドキュメントに記載しております。 実装や設計面における悩み 新居 : 実装や設計面において、今回のプロジェクトで悩んだようなところはありましたか? 堀内 : はい。自分自身の力量の問題で、ドメイン知識や技術力などが不足していたことから、ジョブメドレーのコードの中で今までの慣習を受け継ぎつつ、良い感じに使い回しができるコードをどう上手く書いていくか、という点について悩んでいました。 新居 : なるほど…。ジョブメドレー自体が 10 年以上の歴史があるサービスなので、そこに手を入れていくのは確かに難しい部分もありそうですね。その悩みに対しては、どう対策をされていたのでしょう? 堀内 : そうですね。今の自分の知見だけで設計と実装を進めてしまうと手戻りが発生してしまう恐れがあったので、まずはドメイン知識が豊富な、同じチーム内のベテラン社員に設計レビューをお願いしました。そこで、ジョブメドレーのインフラ構成におけるキャッシュ戦略や、保守性を高める為の実装の切り出し方など、壁打ちで相談させてもらっていました。 その先輩社員との設計レビュー以外にも、普段から行ってもらっている 1on1 ミーティングを通じて、自分のメンターと相談させてもらい、どういう設計だったら使いやすいのか、などを様々な視点から検討しました。 新居 : なるべく手戻りが発生しないようにする為に、 設計段階で身近にいるエンジニアを巻き込んで進めていった のですね。直ぐに相談できる相手がいるのは心強いですね。 学びになった点 新居 : 今回のプロジェクトを通じて、特に学びになった点を教えていただけますか? 堀内 : 技術的な面とプロジェクトの進め方の面でそれぞれ学びがありました。 技術的な面で言うと、 保守性が高く、今後、運用しやすいコードとはどういうものなのかというのを、今回の実装を通じて体得できた ところだと思っています。 プロジェクトの進め方の面では、過去のプロジェクトのドキュメントを参考に、そこに書かれていた知見を活かしながら、プロジェクトを進めていったので、ノウハウのようなところが学べたかと思っています。 新卒研修・開発実践のリードとの違い 新居 : 堀内さんは、新卒研修の一環で行われた開発実践の期間中、新卒同士のチーム開発でもリードを務めていたと思います。今回のプロジェクトを通じて経験されたリードとの違いについて教えてください。 堀内 : プロジェクトの規模や影響範囲の違いなどから、研修時よりも多くの関係者との調整が必要でした。全員と情報を共有・連携しつつ、プロジェクトを進める必要があった点が一番大きな違いでした。 また、開発実践のプロジェクトはあくまで社内向けシステムを構築するものだったので、会社の売上について意識することはありませんでしたが、今回のプロジェクトでは、施策によって応募率が下がった場合に売上に悪影響を及ぼす可能性がありましたので、開発部署以外への影響を意識して管理する必要があったことも大きいポイントだったと思っています。 ジョブメドレーの開発におけるやりがい 新居 : ここまで直近で担当されていたプロジェクトについて聞いてきましたが、ジョブメドレーの開発をしていて、特にやりがいを感じる場面について、教えてもらえますか? 堀内 : やはり、利用してくださっている求職者や事業者の方々に向けて、新規機能開発や機能改修を行う工程です。目的と照らし合わせて仕様面から妥協せずに考え抜き、リリースするまでの過程にやりがいを感じています。ユーザー数がとても多いだけに、良い機能をリリースできたときの反響も大きく、非常に楽しいです。 勿論、エンジニアとして技術力を上げていくことも貪欲に今後もやっていかなければならない部分だと思っていますが、企画として案件があがってきた段階で、それをなぜ作るのか?なぜそれで効果が出ると思っているのか?などと考えることが、企画職だけでなく、エンジニアにとっても大事だと考えています。 リリースした後には、実際に効果が出たのか出なかったのか、なぜ効果が出たのか、出なかったのか?などの効果検証を行った上で、PdM を中心にエンジニア以外のメンバーも交えて振り返り、次の施策に活かしていくといった、 PDCA サイクルを回していっている開発スタイルに楽しさを感じています 。 これは、自分の入社時にやりたいこととしてあげていた、エンジニアリング面だけではなくビジネス面についても多くの経験を積みたい、というところにリンクしていて、今在籍しているジョブメドレーの開発チームでは、企画やマーケティングを行う人達とエンジニアがとても近い距離にいることで、様々な知見を得られつつアウトプットできる、とても学びになる環境だと感じています。 ジョブメドレー開発チームにおける 1 日の流れ 新居 : 現在のジョブメドレー開発チームは、Growth Unit(求職者の利便性を高める施策を行うチーム)と Customer Unit(求人を掲載する事業者が使う採用管理システムの改善施策を行うチーム)の 2 つに分かれているんですよね。堀内さんが所属しているのは、Growth Unit の方ですが、参加されているミーティングなど、1 日の流れについて教えてください。 堀内 : まず、勤務開始の 10:00 頃には Slack に投稿する形で、その日のタスクや参加予定のミーティングなどについて、開発用 channel で共有します。 現在の自分のスケジュールでは、午前中は特に定常的なミーティングはありません。但し、アサインされるプロジェクトによっては、午前中に「朝会」としてプロジェクトメンバー同士が集まって、進捗共有などを行っています。 お昼過ぎの 14:30 からは、 30 分程度、Growth Unit のエンジニアだけで集まるミーティングがあります。この場では、各自の進捗状況や抱えている不安・不明点などを共有します。今何に困っているのかを共有するだけでなく、 困りごとに対し、各々が知恵を出し合って、問題解決までのリードタイムを短くする ことができる貴重な場となっています。 18:00 からは「夕会」として、PdM とデザイナーも含めた Growth Unit 全体のミーティングがあります。そこで各自、「今日やったこと」や担当 Issue のリリース目処などを共有しています。この場では、エンジニア以外のメンバーも参加しているので、そこまで技術的に突っ込んだ話まではしません。 さらに、終業前にはまたエンジニアだけで集まり、FigJam を使って付箋を貼っていく形で、 各自が技術的トピックや、特定のドメインについて聞きたいことなどを持ち寄り、ざっくばらんに雑談形式で話す 「技術共有会」を行っています。 技術共有会の雰囲気 ミーティングの場以外は黙々と開発を進めていきますが、聞きたいことや相談したいことが出てきたら都度、開発用のスレッドに投稿しています。 新居 : なるほど。質問や相談はしやすい感じですか? 堀内 : はい。勿論、自分で調べられることはなるべく調べて、自己解決するのが基本ではありますが、あまり調査に時間をかけ過ぎて全然進まないような事態に陥っても良くないので、それらを皆、前提として意識した上で、聞くべき時は躊躇なく聞ける雰囲気があります。 新居 : ミーティングの種類としては、先ほどあげてもらったもの以外にありますか? 堀内 : あとは、週一で行われている「プロダクト定例」と隔週で行ってもらっている「1on1 ミーティング」があります。 プロダクト定例は、比較的規模の大きいミーティングで、我々、プロダクト開発室以外のメンバーの他、マーケティング室や事業企画室のメンバーも含めて、大体いつも 40 名前後の人数で参加しています。 この場では、現在追っている KPI だったり、ジョブメドレーで動いている全てのプロジェクト状況が共有されます。そこで共有される KPI の数字によって、次に着手すべき Issue にも影響してくるので、この場で共有される情報をしっかりとインプットして、今後の心構えとすることにしています。 直近で担当したプロジェクトの話でもちらっと出てきましたが、1on1 ミーティングは担当メンターと行なっています。普段の仕事の相談から、自分のキャリアに関するような相談をしており、メンターさんの経験なども教えていただきながら、勉強させていただいています。 1on1 ミーティングの意義としては、そういった仕事の他、 プライベートのことも含めて、何でも相談ができる場 としてあるのですが、1on1 を通じて、メンターさんとの信頼関係も築けるので、チームビルディングの一つとしても機能しています。 ジョブメドレー開発チームで一緒に働きたいと思う人はどんな人? 新居 : 堀内さんがジョブメドレー開発チームで一緒に働きたいと思う人はどんな人でしょうか? 堀内 : 今の環境で働いてて良かったなあと思うことは、 人間関係が良好であること です。もちろん馴れ合いの中での業務というわけではなく、各々が しっかりと仕事に対して責任を持ちつつ、チーム内で生じた違和感は遠慮なく指摘し合い受けとめる という、良い意味で心理的安全性のある環境でプロダクト開発に集中して取り組めることです。 このような環境で、課題に対して長期的な視点で本質を捉え、目的を設定し、ボトムアップで解決策の立案から改善までを主導すること、さらには仕組み化まで行うことが好きな方、またはそれらをやってみたい方が合っているのではないか、と思います。 自分もそのような人間になりたいと思いながら、日々仕事に取り組んでいます。 さいごに ジョブメドレー開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いてきましたが、いかがだったでしょうか? こんなチームでプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの新居です。今回は医療介護求人サイトの ジョブメドレー 開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いていこうと思います。 インタビュイー紹介 堀内さん 2021 年入社。人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属。ジョブメドレーの開発を担当。現在は求職者側の UI/UX 改善などの開発に携わる。 堀内さん メドレーに入社した理由 新居 : まずはじめに、堀内さんはどういう経緯でメドレーに入社したのでしたっけ? 堀内 : 以前のインタビュー( 21 年新卒入社エンジニアと座談会で振り返る新卒研修 )の時にもお話したのですが、就活の軸として、自分が成長できそうか・風通しが良いか・合理的な社風かなどを軸として探していました。特に、 ビジネス側と開発側の距離が近い 部分に惹かれ、総合的にメドレーが良さそうだと考えて入社しました。 新居 : 入社前の印象と実際働きだしてからで、ズレなどはありましたか? 堀内 : そんなにギャップはなくて、社風とか人間関係みたいなところは入社前のイメージ通りでした。入社前に何回か会社見学をさせてもらったり、その時の社員と複数回面談をセッティングしてもらったりして、そこでイメージをすり合わせできたことが大きかったですね。 新居 : 何人くらいの社員と会ったのですか? 堀内 : 6 ~ 7 人です。色々なポジションや性格の人達と話ができたので、安心して入社することができました。 新卒研修の開発 OJT ではどんなことをやっていたか? 新居 : 堀内さんは新卒研修の開発 OJT のタイミングでジョブメドレー開発チームに仮配属されたのですよね。当時はどういうことをやってたのですか? 堀内 : まずはジョブメドレーのドメイン知識をしっかり吸収していこうということで、業界や業務自体の理解を進めました。 その後、求職者と事業者のサポート業務を行う社内オペレーターが利用する画面の拡張タスクに取り組みました。 技術スタックは Next.js / React / TypeScript / GraphQL といったシステムで、個人的にあまり触れてこなかった領域だったので、技術のキャッチアップもしつつ丁寧に対応していきました。 また、開発 OJT よりも前の研修で輪読会も経験したのですが、その時に学んだ Cookie や Session まわりの知識が役に立ったり、座学で学んだことがこうやって実践で活きてくるんだな、ということを実感 できてとても良い経験になりました。 今までとは比べ物にならないくらい大規模なシステムだったので、プレッシャーも大きかったのですが、チームの人達と相談したり、細かいレビュー・サポートがあったおかげで、しっかり成果を出すことができたと思います。 新居 : 良い OJT ですね!堀内さん自身の努力があったのは勿論ですが、チームに新しいメンバーを迎え入れて成長をサポートする環境が整っていたのも良かったんでしょうね。 ちなみに開発 OJT の中で大変だったこととかはありますか? 堀内 : そうですね、やはり大規模な既存コードの把握・理解が大変でした。社内オペレーターが使う画面とジョブメドレー本体はそれぞれ別システムとして連携しているので、ジョブメドレー本体のコードや仕組みの理解も必要です。 大変だった分、 システムの全体像を理解するのに大いに役立った ので、今思い返すと良い経験だったなあと思います。 色々な経験を積めた求人カード改修プロジェクト 新居 : では、新卒研修を終えてから 1 年以上経っていて、入社 2 年目の終わりに差し掛かっている現在ですが、直近で堀内さんがリードを務めていた、ジョブメドレーの求人カード改修プロジェクトについて話を聞いていきたいと思います。まずは、このプロジェクトの概要について、教えてもらえますか? 求人カード改修プロジェクトとは? 堀内 : それでは、プロジェクト説明に先立ちまして、まず、ジョブメドレーの「求人カード」について簡単に説明します。 ジョブメドレーで求人を検索すると、検索結果が並ぶ画面が出てきます。我々が「求人カード」と呼んでいるのは、この検索結果画面上に表示される、求人情報のことになります。 検索結果画面の求人カードでは、給与や大まかな業務内容、応募要件、職場の住所などが分かる形になっていますが、求職者の方々がより便利に使っていただけるよう、情報の出し方を見直すことが今回のプロジェクト内容です。 もう少し詳しくお話すると、採用決定率の改善を目的に、どのような改善を実施すべきか検討しています。 求職者にとって欲しい情報が手に入りやすくなるということは、その分、応募に繋がりやすくなると言い換えることもできますので、改修による効果については、継続的に検証し、効果の有無を明らかにしたいと考えています。 新居 : なるほど。端的に言うと、ジョブメドレーにおける求人情報検索の体験を更に良くする為のプロジェクトということですね。 プロジェクトのはじまり 新居 : 今回のプロジェクトをどのようなメンバーと何をどのように行っていったのか、チーム構成から教えてください。 堀内 : チーム構成としては、自分を含めたエンジニアが 2 人いて、その他は、デザイナー、マーケター、プロダクトマネージャー(以下、PdM)がそれぞれ 1 人ずつプロジェクトにアサインされました。 プロジェクトのきっかけとしては、求職者体験を更に良くする為の企画の一つとしてマーケターと PdM が企画したところから始まり、その後、エンジニアとマーケター間で、細かい具体的な要件まで落とし込んで、プロジェクト化に至りました。 仕様についてメンバー間で徹底議論 堀内 : 要件まで落とし込んだ後は、具体的にそれらをどうやって実装するかを検討しました。特に UI の仕様については、デザイナーを中心にかなり長い時間をかけて詰めていきました 。 新居 : 確かに、情報の出し方によっては、かえって、求職者体験を悪化させてしまう可能性がありそうですね…。 堀内 : はい。使い勝手の良い UI にする為に、デザイナーがまず Figma でモックアップを作り、プロジェクトが始まった最初の週からデイリーの夕会などで、UI に関して徹底的にプロジェクトメンバー間で議論をしていました。 その時、特にメンバー全員が気を付けていたのは、実際に求職者が新しい一覧画面を使う時に、価値を感じてもらえるかどうかです。メドレーの Our Essentials の一つとして、 長期のカスタマー価値を追求 という項目があります。「全ての利用者にとって価値のある施策であること」、「利用者の片方だけを見て、もう片方を無視することがあってはならない」ということで、事業者側の求人情報を多く載せるだけではなく、求職者にとっての使い勝手も向上しているか?などを徹底的にチェックしていました。 使い勝手をチェックするにあたって、ジョブメドレーのメインユーザーの年齢層などが予め分かっていたので、その方達にとって使いやすいかどうかを判断軸として、考えていました。 デザイナーが完成させた UI でも、でき上がったモックアップをメンバーそれぞれの視点で触ってみると、改善点が出る場面がありました。そういった場合には、こういう UI にしてみたらどうか?などとそれぞれが意見し、良さそうな案についてはデザイナーがそれらの提案を取り込んだり、改良したりして、UI の仕様を詰めていきました。 技術面について 新居 : UI を実装に落とし込む際、技術面で工夫した点や気を付けていたポイントなどはありますか? 堀内 : 今回のプロジェクトでは、求人カードのデザインを大幅に変更する為、事前の影響範囲の洗い出しを徹底するよう注意していました。例えば、一覧画面における求人情報の出力量がかなり増えることで、1 ページあたりの読み込み処理に時間がかかってしまうことが予想された為、コンテンツの遅延読み込みやキャッシュの利用によりチューニングを行っています。また、デザイン変更が多くのページにわたって発生する為、リリース後の不具合を起こさないような対策を複数行っています。 担当したプロジェクトのリード 新居 : 今回のプロジェクトでは、エンジニアとして開発をしつつ、堀内さんがプロジェクトのリードを任されていたと聞いています。リードとしては、どんな動きをプロジェクトの中でされていたのでしょうか? 堀内 : まずは、最初にプロジェクト全体の大まかなスケジュールを引き、PdM と確認しました。次に、タスク分解をしてメンバーへのアサインを行ったり、プロジェクトのタスクのなかで不確実性を極力少なくする為に、自分達だけではコントロールできなさそうな部分を洗い出したりしました。洗い出したアンコントローラブルな部分は企画者と共に関係各所へ連絡・調整をしてもらうようお願いしていました。 新居 : なるほど。開発スケジュールの出し方など、その辺りで工夫した点などはありますか? 堀内 : スケジュールの共有方法については、視覚的に分かりやすいように、 FigJam を使って、ガントチャートのような形式で、ボードを作成しました。 メンバー毎にレーンを振り分けて、誰がいつまでに何をしなければいけないのかを夕会で確認していました。また、スケジュールのボードは常に、 第三者が見ても進捗状況が直ぐに分かるような状態にしておく よう努めました。 スケジュールボード スケジューリングで特に意識していたことは、不確実な部分を早めに洗い出すことでした。タスク分解をしていく中で、どこが自分達だけではコントロールできなさそうな部分なのかを把握することから始めていました。 現状の仕様や実装がどうなっているのかなどは直ぐに確認できるので、そういったところから、なるべく不確実な部分を減らしていき、最終的に各タスクの工数を算出するにあたって、不確実性の高さに応じて、バッファを設定しました。 新居 : 少し手を動かせば確認できるようなところは直ぐに確認して、なるべく解像度が高い状態にもっていく進め方は確かに良さそうですね!工数を見積もるにあたって、解像度が低い状態だと、見積もりの精度が低くなってしまいますからね。 堀内 : また、タスクは GitHub Issue を発行して管理していたのですが、切り出す粒度はなるべく細かくしていました。Issue の粒度が荒くなってしまうと、それに比例して手戻りリスクが高くなってしまうので、目安としては、長くても 2 日以内には完了できる粒度で切り出していました。 結果、今回のプロジェクトにおいては、ほとんど手戻りすることなく、ほぼ全ての期間において、オンスケジュールで進めることができました。 A/B テストの導入(効果検証の仕組み化) 新居 : オンスケで進められていたのは素晴らしいですね!開発者として実装することと、リードとしてプロジェクトを管理することの他、堀内さんが今回のプロジェクトで担当していた役割などはありますか? 堀内 : 今回のプロジェクトでは、効果検証を行う為の仕組みとして、A/B テストを本格的に導入する目標がありました。なぜ A/B テストを導入するのか? A/B テストで何をしたいのか?を明確にして、A/B テストの設計から導入、分析までを行いました。 統計的な分析手法の選択から実際のテスト環境の開発みたいなところまで一気通貫で、PdM、マーケター、エンジニアを巻き込んで行いました。 A/B テストで大事なのは、論理的な仮説を立ててそれを検証し、その結果からより効果があると判断できた施策を導入していく、というサイクルだと考えています。とはいえ、有意差が出たからといって必ずしも導入したほうが良いというわけではありません。その時の経営面のメリット・デメリットを考慮し、これまでの経験にもとづいた判断も加えて、より効果の高い施策を継続的に導入できるようにする、というのが今回の目標です。 今回のプロジェクトを皮切りに、他のプロジェクトでも同じように A/B テストの仕組みを使える状態にしたい。そういった横展開や汎用性みたいなところも、プロジェクト当初からセットで考えていました。 新居 : とても興味深い取り組みですね!実際、どのように仕組み化を進めていくのでしょうか? 堀内 : A/B テストに必要な知見をドキュメント化して、それを見るだけで、エンジニアのサポートを受けつつ誰でも A/B テストが実施できるようにしていく取り組みや、スプレッドシートにデータを入力すれば、簡単に結果が判るような仕組み化を進行中です。 機能のリリース時は、その結果に応じて結局、どうすれば良いのか?といった指針も Issue やドキュメントに記載しております。 実装や設計面における悩み 新居 : 実装や設計面において、今回のプロジェクトで悩んだようなところはありましたか? 堀内 : はい。自分自身の力量の問題で、ドメイン知識や技術力などが不足していたことから、ジョブメドレーのコードの中で今までの慣習を受け継ぎつつ、良い感じに使い回しができるコードをどう上手く書いていくか、という点について悩んでいました。 新居 : なるほど…。ジョブメドレー自体が 10 年以上の歴史があるサービスなので、そこに手を入れていくのは確かに難しい部分もありそうですね。その悩みに対しては、どう対策をされていたのでしょう? 堀内 : そうですね。今の自分の知見だけで設計と実装を進めてしまうと手戻りが発生してしまう恐れがあったので、まずはドメイン知識が豊富な、同じチーム内のベテラン社員に設計レビューをお願いしました。そこで、ジョブメドレーのインフラ構成におけるキャッシュ戦略や、保守性を高める為の実装の切り出し方など、壁打ちで相談させてもらっていました。 その先輩社員との設計レビュー以外にも、普段から行ってもらっている 1on1 ミーティングを通じて、自分のメンターと相談させてもらい、どういう設計だったら使いやすいのか、などを様々な視点から検討しました。 新居 : なるべく手戻りが発生しないようにする為に、 設計段階で身近にいるエンジニアを巻き込んで進めていった のですね。直ぐに相談できる相手がいるのは心強いですね。 学びになった点 新居 : 今回のプロジェクトを通じて、特に学びになった点を教えていただけますか? 堀内 : 技術的な面とプロジェクトの進め方の面でそれぞれ学びがありました。 技術的な面で言うと、 保守性が高く、今後、運用しやすいコードとはどういうものなのかというのを、今回の実装を通じて体得できた ところだと思っています。 プロジェクトの進め方の面では、過去のプロジェクトのドキュメントを参考に、そこに書かれていた知見を活かしながら、プロジェクトを進めていったので、ノウハウのようなところが学べたかと思っています。 新卒研修・開発実践のリードとの違い 新居 : 堀内さんは、新卒研修の一環で行われた開発実践の期間中、新卒同士のチーム開発でもリードを務めていたと思います。今回のプロジェクトを通じて経験されたリードとの違いについて教えてください。 堀内 : プロジェクトの規模や影響範囲の違いなどから、研修時よりも多くの関係者との調整が必要でした。全員と情報を共有・連携しつつ、プロジェクトを進める必要があった点が一番大きな違いでした。 また、開発実践のプロジェクトはあくまで社内向けシステムを構築するものだったので、会社の売上について意識することはありませんでしたが、今回のプロジェクトでは、施策によって応募率が下がった場合に売上に悪影響を及ぼす可能性がありましたので、開発部署以外への影響を意識して管理する必要があったことも大きいポイントだったと思っています。 ジョブメドレーの開発におけるやりがい 新居 : ここまで直近で担当されていたプロジェクトについて聞いてきましたが、ジョブメドレーの開発をしていて、特にやりがいを感じる場面について、教えてもらえますか? 堀内 : やはり、利用してくださっている求職者や事業者の方々に向けて、新規機能開発や機能改修を行う工程です。目的と照らし合わせて仕様面から妥協せずに考え抜き、リリースするまでの過程にやりがいを感じています。ユーザー数がとても多いだけに、良い機能をリリースできたときの反響も大きく、非常に楽しいです。 勿論、エンジニアとして技術力を上げていくことも貪欲に今後もやっていかなければならない部分だと思っていますが、企画として案件があがってきた段階で、それをなぜ作るのか?なぜそれで効果が出ると思っているのか?などと考えることが、企画職だけでなく、エンジニアにとっても大事だと考えています。 リリースした後には、実際に効果が出たのか出なかったのか、なぜ効果が出たのか、出なかったのか?などの効果検証を行った上で、PdM を中心にエンジニア以外のメンバーも交えて振り返り、次の施策に活かしていくといった、 PDCA サイクルを回していっている開発スタイルに楽しさを感じています 。 これは、自分の入社時にやりたいこととしてあげていた、エンジニアリング面だけではなくビジネス面についても多くの経験を積みたい、というところにリンクしていて、今在籍しているジョブメドレーの開発チームでは、企画やマーケティングを行う人達とエンジニアがとても近い距離にいることで、様々な知見を得られつつアウトプットできる、とても学びになる環境だと感じています。 ジョブメドレー開発チームにおける 1 日の流れ 新居 : 現在のジョブメドレー開発チームは、Growth Unit(求職者の利便性を高める施策を行うチーム)と Customer Unit(求人を掲載する事業者が使う採用管理システムの改善施策を行うチーム)の 2 つに分かれているんですよね。堀内さんが所属しているのは、Growth Unit の方ですが、参加されているミーティングなど、1 日の流れについて教えてください。 堀内 : まず、勤務開始の 10:00 頃には Slack に投稿する形で、その日のタスクや参加予定のミーティングなどについて、開発用 channel で共有します。 現在の自分のスケジュールでは、午前中は特に定常的なミーティングはありません。但し、アサインされるプロジェクトによっては、午前中に「朝会」としてプロジェクトメンバー同士が集まって、進捗共有などを行っています。 お昼過ぎの 14:30 からは、 30 分程度、Growth Unit のエンジニアだけで集まるミーティングがあります。この場では、各自の進捗状況や抱えている不安・不明点などを共有します。今何に困っているのかを共有するだけでなく、 困りごとに対し、各々が知恵を出し合って、問題解決までのリードタイムを短くする ことができる貴重な場となっています。 18:00 からは「夕会」として、PdM とデザイナーも含めた Growth Unit 全体のミーティングがあります。そこで各自、「今日やったこと」や担当 Issue のリリース目処などを共有しています。この場では、エンジニア以外のメンバーも参加しているので、そこまで技術的に突っ込んだ話まではしません。 さらに、終業前にはまたエンジニアだけで集まり、FigJam を使って付箋を貼っていく形で、 各自が技術的トピックや、特定のドメインについて聞きたいことなどを持ち寄り、ざっくばらんに雑談形式で話す 「技術共有会」を行っています。 技術共有会の雰囲気 ミーティングの場以外は黙々と開発を進めていきますが、聞きたいことや相談したいことが出てきたら都度、開発用のスレッドに投稿しています。 新居 : なるほど。質問や相談はしやすい感じですか? 堀内 : はい。勿論、自分で調べられることはなるべく調べて、自己解決するのが基本ではありますが、あまり調査に時間をかけ過ぎて全然進まないような事態に陥っても良くないので、それらを皆、前提として意識した上で、聞くべき時は躊躇なく聞ける雰囲気があります。 新居 : ミーティングの種類としては、先ほどあげてもらったもの以外にありますか? 堀内 : あとは、週一で行われている「プロダクト定例」と隔週で行ってもらっている「1on1 ミーティング」があります。 プロダクト定例は、比較的規模の大きいミーティングで、我々、プロダクト開発室以外のメンバーの他、マーケティング室や事業企画室のメンバーも含めて、大体いつも 40 名前後の人数で参加しています。 この場では、現在追っている KPI だったり、ジョブメドレーで動いている全てのプロジェクト状況が共有されます。そこで共有される KPI の数字によって、次に着手すべき Issue にも影響してくるので、この場で共有される情報をしっかりとインプットして、今後の心構えとすることにしています。 直近で担当したプロジェクトの話でもちらっと出てきましたが、1on1 ミーティングは担当メンターと行なっています。普段の仕事の相談から、自分のキャリアに関するような相談をしており、メンターさんの経験なども教えていただきながら、勉強させていただいています。 1on1 ミーティングの意義としては、そういった仕事の他、 プライベートのことも含めて、何でも相談ができる場 としてあるのですが、1on1 を通じて、メンターさんとの信頼関係も築けるので、チームビルディングの一つとしても機能しています。 ジョブメドレー開発チームで一緒に働きたいと思う人はどんな人? 新居 : 堀内さんがジョブメドレー開発チームで一緒に働きたいと思う人はどんな人でしょうか? 堀内 : 今の環境で働いてて良かったなあと思うことは、 人間関係が良好であること です。もちろん馴れ合いの中での業務というわけではなく、各々が しっかりと仕事に対して責任を持ちつつ、チーム内で生じた違和感は遠慮なく指摘し合い受けとめる という、良い意味で心理的安全性のある環境でプロダクト開発に集中して取り組めることです。 このような環境で、課題に対して長期的な視点で本質を捉え、目的を設定し、ボトムアップで解決策の立案から改善までを主導すること、さらには仕組み化まで行うことが好きな方、またはそれらをやってみたい方が合っているのではないか、と思います。 自分もそのような人間になりたいと思いながら、日々仕事に取り組んでいます。 さいごに ジョブメドレー開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いてきましたが、いかがだったでしょうか? こんなチームでプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの新居です。今回は医療介護求人サイトの ジョブメドレー 開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いていこうと思います。 インタビュイー紹介 堀内さん 2021 年入社。人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属。ジョブメドレーの開発を担当。現在は求職者側の UI/UX 改善などの開発に携わる。 堀内さん メドレーに入社した理由 新居 : まずはじめに、堀内さんはどういう経緯でメドレーに入社したのでしたっけ? 堀内 : 以前のインタビュー( 21 年新卒入社エンジニアと座談会で振り返る新卒研修 )の時にもお話したのですが、就活の軸として、自分が成長できそうか・風通しが良いか・合理的な社風かなどを軸として探していました。特に、 ビジネス側と開発側の距離が近い 部分に惹かれ、総合的にメドレーが良さそうだと考えて入社しました。 新居 : 入社前の印象と実際働きだしてからで、ズレなどはありましたか? 堀内 : そんなにギャップはなくて、社風とか人間関係みたいなところは入社前のイメージ通りでした。入社前に何回か会社見学をさせてもらったり、その時の社員と複数回面談をセッティングしてもらったりして、そこでイメージをすり合わせできたことが大きかったですね。 新居 : 何人くらいの社員と会ったのですか? 堀内 : 6 ~ 7 人です。色々なポジションや性格の人達と話ができたので、安心して入社することができました。 新卒研修の開発 OJT ではどんなことをやっていたか? 新居 : 堀内さんは新卒研修の開発 OJT のタイミングでジョブメドレー開発チームに仮配属されたのですよね。当時はどういうことをやってたのですか? 堀内 : まずはジョブメドレーのドメイン知識をしっかり吸収していこうということで、業界や業務自体の理解を進めました。 その後、求職者と事業者のサポート業務を行う社内オペレーターが利用する画面の拡張タスクに取り組みました。 技術スタックは Next.js / React / TypeScript / GraphQL といったシステムで、個人的にあまり触れてこなかった領域だったので、技術のキャッチアップもしつつ丁寧に対応していきました。 また、開発 OJT よりも前の研修で輪読会も経験したのですが、その時に学んだ Cookie や Session まわりの知識が役に立ったり、座学で学んだことがこうやって実践で活きてくるんだな、ということを実感 できてとても良い経験になりました。 今までとは比べ物にならないくらい大規模なシステムだったので、プレッシャーも大きかったのですが、チームの人達と相談したり、細かいレビュー・サポートがあったおかげで、しっかり成果を出すことができたと思います。 新居 : 良い OJT ですね!堀内さん自身の努力があったのは勿論ですが、チームに新しいメンバーを迎え入れて成長をサポートする環境が整っていたのも良かったんでしょうね。 ちなみに開発 OJT の中で大変だったこととかはありますか? 堀内 : そうですね、やはり大規模な既存コードの把握・理解が大変でした。社内オペレーターが使う画面とジョブメドレー本体はそれぞれ別システムとして連携しているので、ジョブメドレー本体のコードや仕組みの理解も必要です。 大変だった分、 システムの全体像を理解するのに大いに役立った ので、今思い返すと良い経験だったなあと思います。 色々な経験を積めた求人カード改修プロジェクト 新居 : では、新卒研修を終えてから 1 年以上経っていて、入社 2 年目の終わりに差し掛かっている現在ですが、直近で堀内さんがリードを務めていた、ジョブメドレーの求人カード改修プロジェクトについて話を聞いていきたいと思います。まずは、このプロジェクトの概要について、教えてもらえますか? 求人カード改修プロジェクトとは? 堀内 : それでは、プロジェクト説明に先立ちまして、まず、ジョブメドレーの「求人カード」について簡単に説明します。 ジョブメドレーで求人を検索すると、検索結果が並ぶ画面が出てきます。我々が「求人カード」と呼んでいるのは、この検索結果画面上に表示される、求人情報のことになります。 検索結果画面の求人カードでは、給与や大まかな業務内容、応募要件、職場の住所などが分かる形になっていますが、求職者の方々がより便利に使っていただけるよう、情報の出し方を見直すことが今回のプロジェクト内容です。 もう少し詳しくお話すると、採用決定率の改善を目的に、どのような改善を実施すべきか検討しています。 求職者にとって欲しい情報が手に入りやすくなるということは、その分、応募に繋がりやすくなると言い換えることもできますので、改修による効果については、継続的に検証し、効果の有無を明らかにしたいと考えています。 新居 : なるほど。端的に言うと、ジョブメドレーにおける求人情報検索の体験を更に良くする為のプロジェクトということですね。 プロジェクトのはじまり 新居 : 今回のプロジェクトをどのようなメンバーと何をどのように行っていったのか、チーム構成から教えてください。 堀内 : チーム構成としては、自分を含めたエンジニアが 2 人いて、その他は、デザイナー、マーケター、プロダクトマネージャー(以下、PdM)がそれぞれ 1 人ずつプロジェクトにアサインされました。 プロジェクトのきっかけとしては、求職者体験を更に良くする為の企画の一つとしてマーケターと PdM が企画したところから始まり、その後、エンジニアとマーケター間で、細かい具体的な要件まで落とし込んで、プロジェクト化に至りました。 仕様についてメンバー間で徹底議論 堀内 : 要件まで落とし込んだ後は、具体的にそれらをどうやって実装するかを検討しました。特に UI の仕様については、デザイナーを中心にかなり長い時間をかけて詰めていきました 。 新居 : 確かに、情報の出し方によっては、かえって、求職者体験を悪化させてしまう可能性がありそうですね…。 堀内 : はい。使い勝手の良い UI にする為に、デザイナーがまず Figma でモックアップを作り、プロジェクトが始まった最初の週からデイリーの夕会などで、UI に関して徹底的にプロジェクトメンバー間で議論をしていました。 その時、特にメンバー全員が気を付けていたのは、実際に求職者が新しい一覧画面を使う時に、価値を感じてもらえるかどうかです。メドレーの Our Essentials の一つとして、 長期のカスタマー価値を追求 という項目があります。「全ての利用者にとって価値のある施策であること」、「利用者の片方だけを見て、もう片方を無視することがあってはならない」ということで、事業者側の求人情報を多く載せるだけではなく、求職者にとっての使い勝手も向上しているか?などを徹底的にチェックしていました。 使い勝手をチェックするにあたって、ジョブメドレーのメインユーザーの年齢層などが予め分かっていたので、その方達にとって使いやすいかどうかを判断軸として、考えていました。 デザイナーが完成させた UI でも、でき上がったモックアップをメンバーそれぞれの視点で触ってみると、改善点が出る場面がありました。そういった場合には、こういう UI にしてみたらどうか?などとそれぞれが意見し、良さそうな案についてはデザイナーがそれらの提案を取り込んだり、改良したりして、UI の仕様を詰めていきました。 技術面について 新居 : UI を実装に落とし込む際、技術面で工夫した点や気を付けていたポイントなどはありますか? 堀内 : 今回のプロジェクトでは、求人カードのデザインを大幅に変更する為、事前の影響範囲の洗い出しを徹底するよう注意していました。例えば、一覧画面における求人情報の出力量がかなり増えることで、1 ページあたりの読み込み処理に時間がかかってしまうことが予想された為、コンテンツの遅延読み込みやキャッシュの利用によりチューニングを行っています。また、デザイン変更が多くのページにわたって発生する為、リリース後の不具合を起こさないような対策を複数行っています。 担当したプロジェクトのリード 新居 : 今回のプロジェクトでは、エンジニアとして開発をしつつ、堀内さんがプロジェクトのリードを任されていたと聞いています。リードとしては、どんな動きをプロジェクトの中でされていたのでしょうか? 堀内 : まずは、最初にプロジェクト全体の大まかなスケジュールを引き、PdM と確認しました。次に、タスク分解をしてメンバーへのアサインを行ったり、プロジェクトのタスクのなかで不確実性を極力少なくする為に、自分達だけではコントロールできなさそうな部分を洗い出したりしました。洗い出したアンコントローラブルな部分は企画者と共に関係各所へ連絡・調整をしてもらうようお願いしていました。 新居 : なるほど。開発スケジュールの出し方など、その辺りで工夫した点などはありますか? 堀内 : スケジュールの共有方法については、視覚的に分かりやすいように、 FigJam を使って、ガントチャートのような形式で、ボードを作成しました。 メンバー毎にレーンを振り分けて、誰がいつまでに何をしなければいけないのかを夕会で確認していました。また、スケジュールのボードは常に、 第三者が見ても進捗状況が直ぐに分かるような状態にしておく よう努めました。 スケジュールボード スケジューリングで特に意識していたことは、不確実な部分を早めに洗い出すことでした。タスク分解をしていく中で、どこが自分達だけではコントロールできなさそうな部分なのかを把握することから始めていました。 現状の仕様や実装がどうなっているのかなどは直ぐに確認できるので、そういったところから、なるべく不確実な部分を減らしていき、最終的に各タスクの工数を算出するにあたって、不確実性の高さに応じて、バッファを設定しました。 新居 : 少し手を動かせば確認できるようなところは直ぐに確認して、なるべく解像度が高い状態にもっていく進め方は確かに良さそうですね!工数を見積もるにあたって、解像度が低い状態だと、見積もりの精度が低くなってしまいますからね。 堀内 : また、タスクは GitHub Issue を発行して管理していたのですが、切り出す粒度はなるべく細かくしていました。Issue の粒度が荒くなってしまうと、それに比例して手戻りリスクが高くなってしまうので、目安としては、長くても 2 日以内には完了できる粒度で切り出していました。 結果、今回のプロジェクトにおいては、ほとんど手戻りすることなく、ほぼ全ての期間において、オンスケジュールで進めることができました。 A/B テストの導入(効果検証の仕組み化) 新居 : オンスケで進められていたのは素晴らしいですね!開発者として実装することと、リードとしてプロジェクトを管理することの他、堀内さんが今回のプロジェクトで担当していた役割などはありますか? 堀内 : 今回のプロジェクトでは、効果検証を行う為の仕組みとして、A/B テストを本格的に導入する目標がありました。なぜ A/B テストを導入するのか? A/B テストで何をしたいのか?を明確にして、A/B テストの設計から導入、分析までを行いました。 統計的な分析手法の選択から実際のテスト環境の開発みたいなところまで一気通貫で、PdM、マーケター、エンジニアを巻き込んで行いました。 A/B テストで大事なのは、論理的な仮説を立ててそれを検証し、その結果からより効果があると判断できた施策を導入していく、というサイクルだと考えています。とはいえ、有意差が出たからといって必ずしも導入したほうが良いというわけではありません。その時の経営面のメリット・デメリットを考慮し、これまでの経験にもとづいた判断も加えて、より効果の高い施策を継続的に導入できるようにする、というのが今回の目標です。 今回のプロジェクトを皮切りに、他のプロジェクトでも同じように A/B テストの仕組みを使える状態にしたい。そういった横展開や汎用性みたいなところも、プロジェクト当初からセットで考えていました。 新居 : とても興味深い取り組みですね!実際、どのように仕組み化を進めていくのでしょうか? 堀内 : A/B テストに必要な知見をドキュメント化して、それを見るだけで、エンジニアのサポートを受けつつ誰でも A/B テストが実施できるようにしていく取り組みや、スプレッドシートにデータを入力すれば、簡単に結果が判るような仕組み化を進行中です。 機能のリリース時は、その結果に応じて結局、どうすれば良いのか?といった指針も Issue やドキュメントに記載しております。 実装や設計面における悩み 新居 : 実装や設計面において、今回のプロジェクトで悩んだようなところはありましたか? 堀内 : はい。自分自身の力量の問題で、ドメイン知識や技術力などが不足していたことから、ジョブメドレーのコードの中で今までの慣習を受け継ぎつつ、良い感じに使い回しができるコードをどう上手く書いていくか、という点について悩んでいました。 新居 : なるほど…。ジョブメドレー自体が 10 年以上の歴史があるサービスなので、そこに手を入れていくのは確かに難しい部分もありそうですね。その悩みに対しては、どう対策をされていたのでしょう? 堀内 : そうですね。今の自分の知見だけで設計と実装を進めてしまうと手戻りが発生してしまう恐れがあったので、まずはドメイン知識が豊富な、同じチーム内のベテラン社員に設計レビューをお願いしました。そこで、ジョブメドレーのインフラ構成におけるキャッシュ戦略や、保守性を高める為の実装の切り出し方など、壁打ちで相談させてもらっていました。 その先輩社員との設計レビュー以外にも、普段から行ってもらっている 1on1 ミーティングを通じて、自分のメンターと相談させてもらい、どういう設計だったら使いやすいのか、などを様々な視点から検討しました。 新居 : なるべく手戻りが発生しないようにする為に、 設計段階で身近にいるエンジニアを巻き込んで進めていった のですね。直ぐに相談できる相手がいるのは心強いですね。 学びになった点 新居 : 今回のプロジェクトを通じて、特に学びになった点を教えていただけますか? 堀内 : 技術的な面とプロジェクトの進め方の面でそれぞれ学びがありました。 技術的な面で言うと、 保守性が高く、今後、運用しやすいコードとはどういうものなのかというのを、今回の実装を通じて体得できた ところだと思っています。 プロジェクトの進め方の面では、過去のプロジェクトのドキュメントを参考に、そこに書かれていた知見を活かしながら、プロジェクトを進めていったので、ノウハウのようなところが学べたかと思っています。 新卒研修・開発実践のリードとの違い 新居 : 堀内さんは、新卒研修の一環で行われた開発実践の期間中、新卒同士のチーム開発でもリードを務めていたと思います。今回のプロジェクトを通じて経験されたリードとの違いについて教えてください。 堀内 : プロジェクトの規模や影響範囲の違いなどから、研修時よりも多くの関係者との調整が必要でした。全員と情報を共有・連携しつつ、プロジェクトを進める必要があった点が一番大きな違いでした。 また、開発実践のプロジェクトはあくまで社内向けシステムを構築するものだったので、会社の売上について意識することはありませんでしたが、今回のプロジェクトでは、施策によって応募率が下がった場合に売上に悪影響を及ぼす可能性がありましたので、開発部署以外への影響を意識して管理する必要があったことも大きいポイントだったと思っています。 ジョブメドレーの開発におけるやりがい 新居 : ここまで直近で担当されていたプロジェクトについて聞いてきましたが、ジョブメドレーの開発をしていて、特にやりがいを感じる場面について、教えてもらえますか? 堀内 : やはり、利用してくださっている求職者や事業者の方々に向けて、新規機能開発や機能改修を行う工程です。目的と照らし合わせて仕様面から妥協せずに考え抜き、リリースするまでの過程にやりがいを感じています。ユーザー数がとても多いだけに、良い機能をリリースできたときの反響も大きく、非常に楽しいです。 勿論、エンジニアとして技術力を上げていくことも貪欲に今後もやっていかなければならない部分だと思っていますが、企画として案件があがってきた段階で、それをなぜ作るのか?なぜそれで効果が出ると思っているのか?などと考えることが、企画職だけでなく、エンジニアにとっても大事だと考えています。 リリースした後には、実際に効果が出たのか出なかったのか、なぜ効果が出たのか、出なかったのか?などの効果検証を行った上で、PdM を中心にエンジニア以外のメンバーも交えて振り返り、次の施策に活かしていくといった、 PDCA サイクルを回していっている開発スタイルに楽しさを感じています 。 これは、自分の入社時にやりたいこととしてあげていた、エンジニアリング面だけではなくビジネス面についても多くの経験を積みたい、というところにリンクしていて、今在籍しているジョブメドレーの開発チームでは、企画やマーケティングを行う人達とエンジニアがとても近い距離にいることで、様々な知見を得られつつアウトプットできる、とても学びになる環境だと感じています。 ジョブメドレー開発チームにおける 1 日の流れ 新居 : 現在のジョブメドレー開発チームは、Growth Unit(求職者の利便性を高める施策を行うチーム)と Customer Unit(求人を掲載する事業者が使う採用管理システムの改善施策を行うチーム)の 2 つに分かれているんですよね。堀内さんが所属しているのは、Growth Unit の方ですが、参加されているミーティングなど、1 日の流れについて教えてください。 堀内 : まず、勤務開始の 10:00 頃には Slack に投稿する形で、その日のタスクや参加予定のミーティングなどについて、開発用 channel で共有します。 現在の自分のスケジュールでは、午前中は特に定常的なミーティングはありません。但し、アサインされるプロジェクトによっては、午前中に「朝会」としてプロジェクトメンバー同士が集まって、進捗共有などを行っています。 お昼過ぎの 14:30 からは、 30 分程度、Growth Unit のエンジニアだけで集まるミーティングがあります。この場では、各自の進捗状況や抱えている不安・不明点などを共有します。今何に困っているのかを共有するだけでなく、 困りごとに対し、各々が知恵を出し合って、問題解決までのリードタイムを短くする ことができる貴重な場となっています。 18:00 からは「夕会」として、PdM とデザイナーも含めた Growth Unit 全体のミーティングがあります。そこで各自、「今日やったこと」や担当 Issue のリリース目処などを共有しています。この場では、エンジニア以外のメンバーも参加しているので、そこまで技術的に突っ込んだ話まではしません。 さらに、終業前にはまたエンジニアだけで集まり、FigJam を使って付箋を貼っていく形で、 各自が技術的トピックや、特定のドメインについて聞きたいことなどを持ち寄り、ざっくばらんに雑談形式で話す 「技術共有会」を行っています。 技術共有会の雰囲気 ミーティングの場以外は黙々と開発を進めていきますが、聞きたいことや相談したいことが出てきたら都度、開発用のスレッドに投稿しています。 新居 : なるほど。質問や相談はしやすい感じですか? 堀内 : はい。勿論、自分で調べられることはなるべく調べて、自己解決するのが基本ではありますが、あまり調査に時間をかけ過ぎて全然進まないような事態に陥っても良くないので、それらを皆、前提として意識した上で、聞くべき時は躊躇なく聞ける雰囲気があります。 新居 : ミーティングの種類としては、先ほどあげてもらったもの以外にありますか? 堀内 : あとは、週一で行われている「プロダクト定例」と隔週で行ってもらっている「1on1 ミーティング」があります。 プロダクト定例は、比較的規模の大きいミーティングで、我々、プロダクト開発室以外のメンバーの他、マーケティング室や事業企画室のメンバーも含めて、大体いつも 40 名前後の人数で参加しています。 この場では、現在追っている KPI だったり、ジョブメドレーで動いている全てのプロジェクト状況が共有されます。そこで共有される KPI の数字によって、次に着手すべき Issue にも影響してくるので、この場で共有される情報をしっかりとインプットして、今後の心構えとすることにしています。 直近で担当したプロジェクトの話でもちらっと出てきましたが、1on1 ミーティングは担当メンターと行なっています。普段の仕事の相談から、自分のキャリアに関するような相談をしており、メンターさんの経験なども教えていただきながら、勉強させていただいています。 1on1 ミーティングの意義としては、そういった仕事の他、 プライベートのことも含めて、何でも相談ができる場 としてあるのですが、1on1 を通じて、メンターさんとの信頼関係も築けるので、チームビルディングの一つとしても機能しています。 ジョブメドレー開発チームで一緒に働きたいと思う人はどんな人? 新居 : 堀内さんがジョブメドレー開発チームで一緒に働きたいと思う人はどんな人でしょうか? 堀内 : 今の環境で働いてて良かったなあと思うことは、 人間関係が良好であること です。もちろん馴れ合いの中での業務というわけではなく、各々が しっかりと仕事に対して責任を持ちつつ、チーム内で生じた違和感は遠慮なく指摘し合い受けとめる という、良い意味で心理的安全性のある環境でプロダクト開発に集中して取り組めることです。 このような環境で、課題に対して長期的な視点で本質を捉え、目的を設定し、ボトムアップで解決策の立案から改善までを主導すること、さらには仕組み化まで行うことが好きな方、またはそれらをやってみたい方が合っているのではないか、と思います。 自分もそのような人間になりたいと思いながら、日々仕事に取り組んでいます。 さいごに ジョブメドレー開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いてきましたが、いかがだったでしょうか? こんなチームでプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの新居です。今回は医療介護求人サイトの ジョブメドレー 開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いていこうと思います。 インタビュイー紹介 堀内さん 2021 年入社。人材プラットフォーム本部 プロダクト開発室 第一開発グループ所属。ジョブメドレーの開発を担当。現在は求職者側の UI/UX 改善などの開発に携わる。 堀内さん メドレーに入社した理由 新居 : まずはじめに、堀内さんはどういう経緯でメドレーに入社したのでしたっけ? 堀内 : 以前のインタビュー( 21 年新卒入社エンジニアと座談会で振り返る新卒研修 )の時にもお話したのですが、就活の軸として、自分が成長できそうか・風通しが良いか・合理的な社風かなどを軸として探していました。特に、 ビジネス側と開発側の距離が近い 部分に惹かれ、総合的にメドレーが良さそうだと考えて入社しました。 新居 : 入社前の印象と実際働きだしてからで、ズレなどはありましたか? 堀内 : そんなにギャップはなくて、社風とか人間関係みたいなところは入社前のイメージ通りでした。入社前に何回か会社見学をさせてもらったり、その時の社員と複数回面談をセッティングしてもらったりして、そこでイメージをすり合わせできたことが大きかったですね。 新居 : 何人くらいの社員と会ったのですか? 堀内 : 6 ~ 7 人です。色々なポジションや性格の人達と話ができたので、安心して入社することができました。 新卒研修の開発 OJT ではどんなことをやっていたか? 新居 : 堀内さんは新卒研修の開発 OJT のタイミングでジョブメドレー開発チームに仮配属されたのですよね。当時はどういうことをやってたのですか? 堀内 : まずはジョブメドレーのドメイン知識をしっかり吸収していこうということで、業界や業務自体の理解を進めました。 その後、求職者と事業者のサポート業務を行う社内オペレーターが利用する画面の拡張タスクに取り組みました。 技術スタックは Next.js / React / TypeScript / GraphQL といったシステムで、個人的にあまり触れてこなかった領域だったので、技術のキャッチアップもしつつ丁寧に対応していきました。 また、開発 OJT よりも前の研修で輪読会も経験したのですが、その時に学んだ Cookie や Session まわりの知識が役に立ったり、座学で学んだことがこうやって実践で活きてくるんだな、ということを実感 できてとても良い経験になりました。 今までとは比べ物にならないくらい大規模なシステムだったので、プレッシャーも大きかったのですが、チームの人達と相談したり、細かいレビュー・サポートがあったおかげで、しっかり成果を出すことができたと思います。 新居 : 良い OJT ですね!堀内さん自身の努力があったのは勿論ですが、チームに新しいメンバーを迎え入れて成長をサポートする環境が整っていたのも良かったんでしょうね。 ちなみに開発 OJT の中で大変だったこととかはありますか? 堀内 : そうですね、やはり大規模な既存コードの把握・理解が大変でした。社内オペレーターが使う画面とジョブメドレー本体はそれぞれ別システムとして連携しているので、ジョブメドレー本体のコードや仕組みの理解も必要です。 大変だった分、 システムの全体像を理解するのに大いに役立った ので、今思い返すと良い経験だったなあと思います。 色々な経験を積めた求人カード改修プロジェクト 新居 : では、新卒研修を終えてから 1 年以上経っていて、入社 2 年目の終わりに差し掛かっている現在ですが、直近で堀内さんがリードを務めていた、ジョブメドレーの求人カード改修プロジェクトについて話を聞いていきたいと思います。まずは、このプロジェクトの概要について、教えてもらえますか? 求人カード改修プロジェクトとは? 堀内 : それでは、プロジェクト説明に先立ちまして、まず、ジョブメドレーの「求人カード」について簡単に説明します。 ジョブメドレーで求人を検索すると、検索結果が並ぶ画面が出てきます。我々が「求人カード」と呼んでいるのは、この検索結果画面上に表示される、求人情報のことになります。 検索結果画面の求人カードでは、給与や大まかな業務内容、応募要件、職場の住所などが分かる形になっていますが、求職者の方々がより便利に使っていただけるよう、情報の出し方を見直すことが今回のプロジェクト内容です。 もう少し詳しくお話すると、採用決定率の改善を目的に、どのような改善を実施すべきか検討しています。 求職者にとって欲しい情報が手に入りやすくなるということは、その分、応募に繋がりやすくなると言い換えることもできますので、改修による効果については、継続的に検証し、効果の有無を明らかにしたいと考えています。 新居 : なるほど。端的に言うと、ジョブメドレーにおける求人情報検索の体験を更に良くする為のプロジェクトということですね。 プロジェクトのはじまり 新居 : 今回のプロジェクトをどのようなメンバーと何をどのように行っていったのか、チーム構成から教えてください。 堀内 : チーム構成としては、自分を含めたエンジニアが 2 人いて、その他は、デザイナー、マーケター、プロダクトマネージャー(以下、PdM)がそれぞれ 1 人ずつプロジェクトにアサインされました。 プロジェクトのきっかけとしては、求職者体験を更に良くする為の企画の一つとしてマーケターと PdM が企画したところから始まり、その後、エンジニアとマーケター間で、細かい具体的な要件まで落とし込んで、プロジェクト化に至りました。 仕様についてメンバー間で徹底議論 堀内 : 要件まで落とし込んだ後は、具体的にそれらをどうやって実装するかを検討しました。特に UI の仕様については、デザイナーを中心にかなり長い時間をかけて詰めていきました 。 新居 : 確かに、情報の出し方によっては、かえって、求職者体験を悪化させてしまう可能性がありそうですね…。 堀内 : はい。使い勝手の良い UI にする為に、デザイナーがまず Figma でモックアップを作り、プロジェクトが始まった最初の週からデイリーの夕会などで、UI に関して徹底的にプロジェクトメンバー間で議論をしていました。 その時、特にメンバー全員が気を付けていたのは、実際に求職者が新しい一覧画面を使う時に、価値を感じてもらえるかどうかです。メドレーの Our Essentials の一つとして、 長期のカスタマー価値を追求 という項目があります。「全ての利用者にとって価値のある施策であること」、「利用者の片方だけを見て、もう片方を無視することがあってはならない」ということで、事業者側の求人情報を多く載せるだけではなく、求職者にとっての使い勝手も向上しているか?などを徹底的にチェックしていました。 使い勝手をチェックするにあたって、ジョブメドレーのメインユーザーの年齢層などが予め分かっていたので、その方達にとって使いやすいかどうかを判断軸として、考えていました。 デザイナーが完成させた UI でも、でき上がったモックアップをメンバーそれぞれの視点で触ってみると、改善点が出る場面がありました。そういった場合には、こういう UI にしてみたらどうか?などとそれぞれが意見し、良さそうな案についてはデザイナーがそれらの提案を取り込んだり、改良したりして、UI の仕様を詰めていきました。 技術面について 新居 : UI を実装に落とし込む際、技術面で工夫した点や気を付けていたポイントなどはありますか? 堀内 : 今回のプロジェクトでは、求人カードのデザインを大幅に変更する為、事前の影響範囲の洗い出しを徹底するよう注意していました。例えば、一覧画面における求人情報の出力量がかなり増えることで、1 ページあたりの読み込み処理に時間がかかってしまうことが予想された為、コンテンツの遅延読み込みやキャッシュの利用によりチューニングを行っています。また、デザイン変更が多くのページにわたって発生する為、リリース後の不具合を起こさないような対策を複数行っています。 担当したプロジェクトのリード 新居 : 今回のプロジェクトでは、エンジニアとして開発をしつつ、堀内さんがプロジェクトのリードを任されていたと聞いています。リードとしては、どんな動きをプロジェクトの中でされていたのでしょうか? 堀内 : まずは、最初にプロジェクト全体の大まかなスケジュールを引き、PdM と確認しました。次に、タスク分解をしてメンバーへのアサインを行ったり、プロジェクトのタスクのなかで不確実性を極力少なくする為に、自分達だけではコントロールできなさそうな部分を洗い出したりしました。洗い出したアンコントローラブルな部分は企画者と共に関係各所へ連絡・調整をしてもらうようお願いしていました。 新居 : なるほど。開発スケジュールの出し方など、その辺りで工夫した点などはありますか? 堀内 : スケジュールの共有方法については、視覚的に分かりやすいように、 FigJam を使って、ガントチャートのような形式で、ボードを作成しました。 メンバー毎にレーンを振り分けて、誰がいつまでに何をしなければいけないのかを夕会で確認していました。また、スケジュールのボードは常に、 第三者が見ても進捗状況が直ぐに分かるような状態にしておく よう努めました。 スケジュールボード スケジューリングで特に意識していたことは、不確実な部分を早めに洗い出すことでした。タスク分解をしていく中で、どこが自分達だけではコントロールできなさそうな部分なのかを把握することから始めていました。 現状の仕様や実装がどうなっているのかなどは直ぐに確認できるので、そういったところから、なるべく不確実な部分を減らしていき、最終的に各タスクの工数を算出するにあたって、不確実性の高さに応じて、バッファを設定しました。 新居 : 少し手を動かせば確認できるようなところは直ぐに確認して、なるべく解像度が高い状態にもっていく進め方は確かに良さそうですね!工数を見積もるにあたって、解像度が低い状態だと、見積もりの精度が低くなってしまいますからね。 堀内 : また、タスクは GitHub Issue を発行して管理していたのですが、切り出す粒度はなるべく細かくしていました。Issue の粒度が荒くなってしまうと、それに比例して手戻りリスクが高くなってしまうので、目安としては、長くても 2 日以内には完了できる粒度で切り出していました。 結果、今回のプロジェクトにおいては、ほとんど手戻りすることなく、ほぼ全ての期間において、オンスケジュールで進めることができました。 A/B テストの導入(効果検証の仕組み化) 新居 : オンスケで進められていたのは素晴らしいですね!開発者として実装することと、リードとしてプロジェクトを管理することの他、堀内さんが今回のプロジェクトで担当していた役割などはありますか? 堀内 : 今回のプロジェクトでは、効果検証を行う為の仕組みとして、A/B テストを本格的に導入する目標がありました。なぜ A/B テストを導入するのか? A/B テストで何をしたいのか?を明確にして、A/B テストの設計から導入、分析までを行いました。 統計的な分析手法の選択から実際のテスト環境の開発みたいなところまで一気通貫で、PdM、マーケター、エンジニアを巻き込んで行いました。 A/B テストで大事なのは、論理的な仮説を立ててそれを検証し、その結果からより効果があると判断できた施策を導入していく、というサイクルだと考えています。とはいえ、有意差が出たからといって必ずしも導入したほうが良いというわけではありません。その時の経営面のメリット・デメリットを考慮し、これまでの経験にもとづいた判断も加えて、より効果の高い施策を継続的に導入できるようにする、というのが今回の目標です。 今回のプロジェクトを皮切りに、他のプロジェクトでも同じように A/B テストの仕組みを使える状態にしたい。そういった横展開や汎用性みたいなところも、プロジェクト当初からセットで考えていました。 新居 : とても興味深い取り組みですね!実際、どのように仕組み化を進めていくのでしょうか? 堀内 : A/B テストに必要な知見をドキュメント化して、それを見るだけで、エンジニアのサポートを受けつつ誰でも A/B テストが実施できるようにしていく取り組みや、スプレッドシートにデータを入力すれば、簡単に結果が判るような仕組み化を進行中です。 機能のリリース時は、その結果に応じて結局、どうすれば良いのか?といった指針も Issue やドキュメントに記載しております。 実装や設計面における悩み 新居 : 実装や設計面において、今回のプロジェクトで悩んだようなところはありましたか? 堀内 : はい。自分自身の力量の問題で、ドメイン知識や技術力などが不足していたことから、ジョブメドレーのコードの中で今までの慣習を受け継ぎつつ、良い感じに使い回しができるコードをどう上手く書いていくか、という点について悩んでいました。 新居 : なるほど…。ジョブメドレー自体が 10 年以上の歴史があるサービスなので、そこに手を入れていくのは確かに難しい部分もありそうですね。その悩みに対しては、どう対策をされていたのでしょう? 堀内 : そうですね。今の自分の知見だけで設計と実装を進めてしまうと手戻りが発生してしまう恐れがあったので、まずはドメイン知識が豊富な、同じチーム内のベテラン社員に設計レビューをお願いしました。そこで、ジョブメドレーのインフラ構成におけるキャッシュ戦略や、保守性を高める為の実装の切り出し方など、壁打ちで相談させてもらっていました。 その先輩社員との設計レビュー以外にも、普段から行ってもらっている 1on1 ミーティングを通じて、自分のメンターと相談させてもらい、どういう設計だったら使いやすいのか、などを様々な視点から検討しました。 新居 : なるべく手戻りが発生しないようにする為に、 設計段階で身近にいるエンジニアを巻き込んで進めていった のですね。直ぐに相談できる相手がいるのは心強いですね。 学びになった点 新居 : 今回のプロジェクトを通じて、特に学びになった点を教えていただけますか? 堀内 : 技術的な面とプロジェクトの進め方の面でそれぞれ学びがありました。 技術的な面で言うと、 保守性が高く、今後、運用しやすいコードとはどういうものなのかというのを、今回の実装を通じて体得できた ところだと思っています。 プロジェクトの進め方の面では、過去のプロジェクトのドキュメントを参考に、そこに書かれていた知見を活かしながら、プロジェクトを進めていったので、ノウハウのようなところが学べたかと思っています。 新卒研修・開発実践のリードとの違い 新居 : 堀内さんは、新卒研修の一環で行われた開発実践の期間中、新卒同士のチーム開発でもリードを務めていたと思います。今回のプロジェクトを通じて経験されたリードとの違いについて教えてください。 堀内 : プロジェクトの規模や影響範囲の違いなどから、研修時よりも多くの関係者との調整が必要でした。全員と情報を共有・連携しつつ、プロジェクトを進める必要があった点が一番大きな違いでした。 また、開発実践のプロジェクトはあくまで社内向けシステムを構築するものだったので、会社の売上について意識することはありませんでしたが、今回のプロジェクトでは、施策によって応募率が下がった場合に売上に悪影響を及ぼす可能性がありましたので、開発部署以外への影響を意識して管理する必要があったことも大きいポイントだったと思っています。 ジョブメドレーの開発におけるやりがい 新居 : ここまで直近で担当されていたプロジェクトについて聞いてきましたが、ジョブメドレーの開発をしていて、特にやりがいを感じる場面について、教えてもらえますか? 堀内 : やはり、利用してくださっている求職者や事業者の方々に向けて、新規機能開発や機能改修を行う工程です。目的と照らし合わせて仕様面から妥協せずに考え抜き、リリースするまでの過程にやりがいを感じています。ユーザー数がとても多いだけに、良い機能をリリースできたときの反響も大きく、非常に楽しいです。 勿論、エンジニアとして技術力を上げていくことも貪欲に今後もやっていかなければならない部分だと思っていますが、企画として案件があがってきた段階で、それをなぜ作るのか?なぜそれで効果が出ると思っているのか?などと考えることが、企画職だけでなく、エンジニアにとっても大事だと考えています。 リリースした後には、実際に効果が出たのか出なかったのか、なぜ効果が出たのか、出なかったのか?などの効果検証を行った上で、PdM を中心にエンジニア以外のメンバーも交えて振り返り、次の施策に活かしていくといった、 PDCA サイクルを回していっている開発スタイルに楽しさを感じています 。 これは、自分の入社時にやりたいこととしてあげていた、エンジニアリング面だけではなくビジネス面についても多くの経験を積みたい、というところにリンクしていて、今在籍しているジョブメドレーの開発チームでは、企画やマーケティングを行う人達とエンジニアがとても近い距離にいることで、様々な知見を得られつつアウトプットできる、とても学びになる環境だと感じています。 ジョブメドレー開発チームにおける 1 日の流れ 新居 : 現在のジョブメドレー開発チームは、Growth Unit(求職者の利便性を高める施策を行うチーム)と Customer Unit(求人を掲載する事業者が使う採用管理システムの改善施策を行うチーム)の 2 つに分かれているんですよね。堀内さんが所属しているのは、Growth Unit の方ですが、参加されているミーティングなど、1 日の流れについて教えてください。 堀内 : まず、勤務開始の 10:00 頃には Slack に投稿する形で、その日のタスクや参加予定のミーティングなどについて、開発用 channel で共有します。 現在の自分のスケジュールでは、午前中は特に定常的なミーティングはありません。但し、アサインされるプロジェクトによっては、午前中に「朝会」としてプロジェクトメンバー同士が集まって、進捗共有などを行っています。 お昼過ぎの 14:30 からは、 30 分程度、Growth Unit のエンジニアだけで集まるミーティングがあります。この場では、各自の進捗状況や抱えている不安・不明点などを共有します。今何に困っているのかを共有するだけでなく、 困りごとに対し、各々が知恵を出し合って、問題解決までのリードタイムを短くする ことができる貴重な場となっています。 18:00 からは「夕会」として、PdM とデザイナーも含めた Growth Unit 全体のミーティングがあります。そこで各自、「今日やったこと」や担当 Issue のリリース目処などを共有しています。この場では、エンジニア以外のメンバーも参加しているので、そこまで技術的に突っ込んだ話まではしません。 さらに、終業前にはまたエンジニアだけで集まり、FigJam を使って付箋を貼っていく形で、 各自が技術的トピックや、特定のドメインについて聞きたいことなどを持ち寄り、ざっくばらんに雑談形式で話す 「技術共有会」を行っています。 技術共有会の雰囲気 ミーティングの場以外は黙々と開発を進めていきますが、聞きたいことや相談したいことが出てきたら都度、開発用のスレッドに投稿しています。 新居 : なるほど。質問や相談はしやすい感じですか? 堀内 : はい。勿論、自分で調べられることはなるべく調べて、自己解決するのが基本ではありますが、あまり調査に時間をかけ過ぎて全然進まないような事態に陥っても良くないので、それらを皆、前提として意識した上で、聞くべき時は躊躇なく聞ける雰囲気があります。 新居 : ミーティングの種類としては、先ほどあげてもらったもの以外にありますか? 堀内 : あとは、週一で行われている「プロダクト定例」と隔週で行ってもらっている「1on1 ミーティング」があります。 プロダクト定例は、比較的規模の大きいミーティングで、我々、プロダクト開発室以外のメンバーの他、マーケティング室や事業企画室のメンバーも含めて、大体いつも 40 名前後の人数で参加しています。 この場では、現在追っている KPI だったり、ジョブメドレーで動いている全てのプロジェクト状況が共有されます。そこで共有される KPI の数字によって、次に着手すべき Issue にも影響してくるので、この場で共有される情報をしっかりとインプットして、今後の心構えとすることにしています。 直近で担当したプロジェクトの話でもちらっと出てきましたが、1on1 ミーティングは担当メンターと行なっています。普段の仕事の相談から、自分のキャリアに関するような相談をしており、メンターさんの経験なども教えていただきながら、勉強させていただいています。 1on1 ミーティングの意義としては、そういった仕事の他、 プライベートのことも含めて、何でも相談ができる場 としてあるのですが、1on1 を通じて、メンターさんとの信頼関係も築けるので、チームビルディングの一つとしても機能しています。 ジョブメドレー開発チームで一緒に働きたいと思う人はどんな人? 新居 : 堀内さんがジョブメドレー開発チームで一緒に働きたいと思う人はどんな人でしょうか? 堀内 : 今の環境で働いてて良かったなあと思うことは、 人間関係が良好であること です。もちろん馴れ合いの中での業務というわけではなく、各々が しっかりと仕事に対して責任を持ちつつ、チーム内で生じた違和感は遠慮なく指摘し合い受けとめる という、良い意味で心理的安全性のある環境でプロダクト開発に集中して取り組めることです。 このような環境で、課題に対して長期的な視点で本質を捉え、目的を設定し、ボトムアップで解決策の立案から改善までを主導すること、さらには仕組み化まで行うことが好きな方、またはそれらをやってみたい方が合っているのではないか、と思います。 自分もそのような人間になりたいと思いながら、日々仕事に取り組んでいます。 さいごに ジョブメドレー開発チームに所属している堀内さんに、2 年目の成長と活躍というテーマで話を聞いてきましたが、いかがだったでしょうか? こんなチームでプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回は Pharms 開発チームがチームビルディングの一環として行なっている「TGIF」という施策について参加している皆さんに、どのような効果があるのかなどを聞いていこうと思います。 インタビュイー紹介 新居さん 2016 年入社。ソーシャルゲーム開発会社を経て、メドレーへ入社。入社当初はジョブメドレーのプロダクト開発などを担当していたが、2020 年から Pharms で中規模施策の開発を中心に業務を行なう。 小田さん 2021 年入社。大手メーカーで病院や調剤薬局向けの IT システムの開発業務を経て、メドレー入社。Pharms では中規模施策の開発を中心に業務を行なう。 兒玉さん 2018 年入社。2021 年 4 月まで CLINICS オンライン診療のセールス部に所属。その後エンジニアに社内転職し、Pharms の開発に携わる。現在はサーバサイドの開発を中心に担当している。 新倉さん 2021 年入社。不動産テック企業でのデザイン業務を経て、メドレーへ入社。入社時から Pharms のデザイナーとして各種デザインの業務を行なう。 古川さん 2022 年に新卒入社。現在はプロダクトの改善を幅広く担当している。 Pharms というプロダクトについて 山田 : まず「TGIF」について聞いていく前に、Pharms とはどのようなプロダクトなのか教えてもらってもよいですか? 新居 : 一言で言うと、薬局の業務効率化支援システムです。 CLINICS オンライン診療 で診察を受けた患者さんや、直接来院された患者さんからの処方箋などを受けて、Pharms を通して服薬指導や、会計、服薬フォローアップなどを一貫して行なえるシステムになっています。 新居さん 山田 : ユーザーについてですが、例えば、自分が開発を担当している医療介護求人サイトの ジョブメドレー では、お仕事をお探しの求職者の方々と、求人情報を掲載いただく医院・事業所の方々が使用しているシステムなのですが、Pharms はどのような方に使われるのでしょうか? 小田 : CLINICS アプリを通して間接的に患者さんが使っていますが、大きい割合を占めるのは顧客である薬局のスタッフさん(薬剤師など)ですね。 小田さん 山田 : やはり現場の薬剤師さんが使っているサービスという性格が強いんですね。 Pharms 開発チームの TGIF という試みについて TGIF を始める前の課題感 山田 : それでは、いよいよ Pharms 開発チームで行なっている TGIF について聞いていきたいと思います。元々、この言葉はどんな経緯で付いたんですか? 小田 : 元々は英語圏で言われている “Thank God It’s Friday” というのが語源で、そのまま金曜日の夕方に開催されるものだったのでこの名前を付けています。ちょっと昔の言葉で言うと「花金」ですね。 山田 : 「1 週間が終わりだー!」っていう開放感を表わしてるんですね。どういうきっかけでこの TGIF をやっていこうと思ったんでしょうか? 小田 : 一昨年の自分の入社前のタイミングで、Pharms 立ち上げ時のメンバーが別のプロジェクトに集中することとなり、チーム体制がガラッと変わる時期がありました。それ以降、チーム内の情報がサイロ化したり、チームとしての一体感がないという課題を感じていました。この課題を解決するために 雑多に何でも話せるコミュニケーションの場 を作ろうというのが、きっかけでした。 新居 : もちろん、それまでもチームで話す機会が全く無かったわけではなく、開発計画をクォーター単位で作っているのでそのタイミングでディスカッションする場を設けていました。でも もうちょっとコミュニケーションの頻度を増やしたらより良くなりそうだよね という話があり、毎週金曜日の TGIF が誕生しました。 山田 : そういう経緯だったんですね。それまでエンジニア・デザイナーが参加する定例のような会議体はどのようなものがあったんですか? 新居 : クォーター毎のディスカッション以外だと、週一で行なわれる「プロダクト定例」だけでした。こちらは開発メンバーだけではなく、事業部のメンバーも参加してプロダクト開発全般についての共有を目的にしたものです。それ以外で定例の会議体はなく、デイリーで行われるような「朝会・夕会」も Pharms 開発チームでは行なっていませんでした。 山田 : チーム体制の変更後、新しいメンバーの加入もあり、今までのコミュニケーション量では足りなくなってしまったんですね。 新居 : はい。チームの状況が変わって行く中で、 もっと生産性を上げていきたいという思いがありました 。チームが小規模だった頃は隣の席の同僚エンジニアに相談しつつ開発をドンドン前に進めていくことができたんですが、人数が増えてくるとそうしたコミュニケーションも希薄になります。具体的には、開発をしていて設計方針・実装方針などがズレることは多々あると思うんですが、それをさっと会話して軌道修正し、開発を推進していくというのが難しくなってきていました。 TGIF の実際 山田 : では、具体的に TGIF でどのような事を話し合われるんでしょうか? 新居 : まずは今週の振り返りから始まります。前週の TGIF で Try に対して個々がどう動いていくかを決めているので、それに対して振り返りをしていきます。各自コメントを添えて何%達成したのか話していきます。 小田 : その後、今週のチーム全体の動きに対して KPT を実施します。チーム全体として良かった動きや課題などを挙げていき、内容を深ぼったり課題解決に向けて意見を交換したりしています。また、以前はチームの動きとは関係ない個人の話などが挙がってくることが多かったのですが、 チーム全体での生産性の向上ということを目的として考えた場合、それと関連のない個人の話をしても寄与しない ので、チーム全体に関わることをメインにするよう改善し今の形になってきました。 KPT が終わったら、隔週でチーム目標と Problem で挙がったことなども踏まえて各自の来週の Try を決めます。 新居 : 最後に、ちょっとしたことでも良いのでチームに共有したい「気になること」を話していきます。ここは開発に関係する・しないに関わらない話題を出しています。例えば 、Pharms のプロダクト自体や会社の組織的なこと、プルリクエストのレビューのお願いなど雑多な話題ですね。 その中で「ライブラリアップデート」についての課題や進捗などの共有もします。単純にバージョンアップして済むというものであれば良いですが、アップデートの結果、広範囲に影響があったりだとか、そもそもエラーなどで上手くアップデートできないなどがあるので、そうした困り事の共有と解決を目的としています。 山田 : 先ほどチーム目標を隔週で決めるというお話でしたが、こちらはどういう風に決めていくんですか? 小田 : KPT の内容を踏まえて、基本的には Our Essentials (以下 OE) に照らし合わせて決めていきます。そのチーム目標に対して、個人の目標をブレイクダウンして決める形にしています。 固くなりすぎない会を目指して 山田 : なるほど。 OE に合わせて目標設定していくというのは、分かりやすくて良い ですね。話は少し逸れますが、この TGIF では毎回お菓子を持ち寄って行なっているということですが、どうしてなのでしょうか? 小田 : TGIF を行なっている内に、メンバー間での意思疎通の少なさによって、チームの生産性が上がらないという課題は解決していきました。しかし、続けている内に段々と会自体が固い雰囲気になってきて、かっちりとした振り返りの場という感じになってしまいました。元々 TGIF の在り方としてはどういうものだっけという原点に立ち返ると、やはり 「気軽にコミュニケーションができる場」という点が重要 だと再認識しまして、そこからは場の雰囲気を柔らげるためにお菓子を持ち寄り始めました。 お菓子を囲んでの TGIF 山田 : 確かに固すぎると、ちょっと会の名前とそぐわない感じになってしまいますね。 TGIF 運用の秘訣 山田 : 他に TGIF を運用する上での工夫などはありますか? 新倉 : 会の性質上、話が盛り上がって時間がオーバーしがちなので「 TGIF で話す内容はチームの成長につながることに限定する」など、 効率的かつ意味のある運用 になるよう進め方の見直しと実践は日々試行錯誤しています。 新倉さん 古川 : 自分は去年の新卒入社ですので、「言ってもしょうがないかな?」というような事もあるのですが、あえてそういった事を考えずに積極的に発言するようにしています。 実際に発言したために他の方との共有もできますし、学びになるような議論に発展することも多い ので、言って良かったという場合が多いです。 古川さん 兒玉 : ささいな事でも、誰かが チームの為に動いた事に対しては積極的にお礼を言っています 。誰かがちゃんと自分のやったことを見てくれていると認識できることが、チームの雰囲気を良くし、チーム運営の好循環に繋がっているかと思います。 兒玉さん 新居 : ちゃんとチーム全員にとって意味がある時間にするということは意識しています。仮にあまり生産的な時間ではなくなったら TGIF を止めてしまっても良いと思っています。その為には 「チームのために話しをした方がよいことは必ず話す」「本音ベースで建設的に議論する」「出てきた課題は必ずクリアにして次週以降に解決を目指す」 という事を意識しています。 こうして話すとある意味当たり前の事ばかりなんですが、参加者全員がこうした点を共通認識として持っていないと、単に KPT を発表するだけの場となり会自体が形骸化していってしまいます。ですので、きちんとこの共通認識を持って臨めるようにするというのを大切にしています。 小田 : また、TGIF で話が盛り上がるのは良いことなのですが、週に一回のこの場を待たずにもっと会話を活性化する取り組みとして 週の頭に Slack のプロダクトチャンネルにテディベアがリマインダーで現れる ので、そのスレッドで話しをするようにしています。 山田 : へえー!何でテディベアなんですか? 新居 : プログラミングで自分で解決できない問題が出てきたときに、テディベアのぬいぐるみに話しかけると問題が整理されて解決できるというテディベア効果から取っています。 テディベアの リマインダーにコメントが付いている様子 山田 : なるほど、それでテディベアが出てくるんですね。 小田 : TGIF を待つまでの時間がムダだよねっていう話もあって先程の効率化の話も含めて行なっている施策です。 新倉 : これまで TGIF やテディベアが無い時は下手をすると一週間誰とも会話をしていないということもあったんですが、この取り組みをしてから 本当にコミュニケーションが増えました 。 TGIF の利点とこれからについて 山田 : ありがとうございます。既に話にも出ていますが、TGIF をやって良かった点はどういったものがあるか改めて聞かせてください。 小田 : やっぱり格段に「チームでの開発」をしているという意識が高まったことが良いことでした。それまではやはりコミュニケーションなどが不足していたのですが、今は TGIF などのおかげですぐに相談しようという感じにもなって良いチームになってきたなという感触があります。 さらにチームを良くする土壌 にするようにしていきたいです。 新居 : 1 つ目は以前よりお互いのことを知れるようになって心理的安全性が高まったこと、2 つ目はチームとしての方針や共通認識を合わせる場として機能していること、3 つ目は毎週前週からの課題を改善でき、チームや個人の成長を感じられるようになったことです。 全体的にチーム運営にプラス になっていると感じています。 新倉 : 私は TGIF が始まってから半年後にジョインしました。元々はエンジニアの皆さんだけのものだと思ってちょっと尻込みしていたところがあったのですが、蓋を開けてみるとプロダクトやチーム自体についての話が多く、 デザイナーとしても非常に学び があるものでした。全体的にはクォーター毎に目標を決めてそれに向けて動くのですが、その目標までの道のりを埋めるという意味でも、TGIF があって良かったです。 新居 : 時にはエンジニアだけにしかわからない話題などもあったりもしますが、大部分は新倉さんが話したようにプロダクトやチームの改善をいかに行なうかという話題になっているので、そこでデザイナー視点での話が聞けてエンジニアとしても大変助かります。 古川 : 自分は入ってからずっと TGIF があるのである種当たり前の存在なのですが…。チームメンバーの人となりなどが分かるのは大変助かります。また、自分とは全然違う経歴を持っているエンジニアの方などは同じ話題でも、 自分とは全然違う視点を持って話をしていたりするので、すごく勉強になっています 。チームの課題が話し合われるので、以前は自分のタスクが終わったら、「次は何をしよう」という感じになっていたんですが、TGIF のおかげで「チームがここで困っているなら自分がやろう」と客観的に分かるようになっています。また、OE は半年毎の自己評価の際にも体現できているかどうかチェックすることになるので、TGIF での目標達成度がそれに役立ちます。 兒玉 : 一番は心理的安全性が高くなったことです。エンジニアとしての働き方とセールスをしていた時の働き方が全然違うところが多かったので、1on1 で新居さんとそういった差異について聞くという機会しかなかったんですが、今は TGIF があるのでここで皆さんに聞けるようになって様々な視点でのアドバイスを頂けるようになりました。また、エンジニアとしてお勧めの書籍や勉強法など普通に聞こうとすると、 あまり機会がないような部分も気軽に聞けるようになったので、勉強の効率も良くなりました 。 山田 : みなさん、ありがとうございます。それぞれの立場ですごく有用な会なのが分かりますね。 TGIF のこれからについてもお話ください。 新居 : 先ほども少し話しましたが、形骸化してしまい惰性で行なってしまうということになるのが本当に怖いと思っています。運用することが目的になってしまい、本来の目的が達成できないと本末転倒ですし。今もやっていることですが、ちゃんとメンバー構成や状況に応じて運用をアップデートしていくということが一番大切だと思っています。 小田 : 今までエンジニアだけの会から TGIF は始まっていますが、デザイナーの新倉さんが参加してくれたのを始めとして、昨年末から企画職の方も入ってくれるようになったので、裾野の広がりを感じています。こういった立場が別々のメンバーが入ってもスムーズに目的が達成できるように TGIF のアップデートを続けていきたいですね。 新倉 : 色々な役割の人がフラットに発言できる場があるおかげで、プロダクトについて総合的な視点を持つことができたと感じています。実際に自分達が試行錯誤して出した機能が「お客様にこんな風に使われて、こんな感想をもらえた」などすぐに共有してもらえるので、開発のモチベーションにもつながる良いサイクルになっています。 Pharms 開発チームでは、どんな人と一緒に働きたいと考えている? 山田 : ありがとうございます。最後に Pharms の開発チームには、どんな方がジョインされると嬉しいですか? 小田 : いつもチーム内で話をしているのが「 顧客ファースト 」という言葉です。これは Pharms に限った話ではなくメドレー全体の話でもあるのですが、顧客や患者などサービスを使っている人達に対して提供できる価値は何かということをきちんと意識して開発ができる方です。技術や数字だけを見るわけじゃなく、その先の顧客をちゃんと考えて開発ができる人ですね。 新居 : メドレーの事業やバリューに共感があるのは大前提です。また、小田さんが話されたことに加えて、プロダクトをより良くしようと考えてるメンバーと共に、自分はこうしたいという意志も持ちつつ、ときには建設的に議論もしながらプロダクト開発を推進できる人と一緒に仕事ができると嬉しいです。大変なことも勿論ありますが、それを楽しみ、周囲を巻き込みながら技術・技術以外の部分も含め前に進めていける人が良いなと思います。 山田 : TGIF も顧客ファーストの理念から生まれたものということになりますよね。ありがとうございました! さいごに Pharms 開発チームが実践している「TGIF」というチームビルディングの方法についてインタビューしてきましたが、いかがだったでしょうか? 実際に TGIF をしている様子などを見ると、和やかな雰囲気ではありますが、全員真剣にプロダクトについて議論をしている姿が印象的でした。 こんなチームで調剤薬局や患者のためのプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回は Pharms 開発チームがチームビルディングの一環として行なっている「TGIF」という施策について参加している皆さんに、どのような効果があるのかなどを聞いていこうと思います。 インタビュイー紹介 新居さん 2016 年入社。ソーシャルゲーム開発会社を経て、メドレーへ入社。入社当初はジョブメドレーのプロダクト開発などを担当していたが、2020 年から Pharms で中規模施策の開発を中心に業務を行なう。 小田さん 2021 年入社。大手メーカーで病院や調剤薬局向けの IT システムの開発業務を経て、メドレー入社。Pharms では中規模施策の開発を中心に業務を行なう。 兒玉さん 2018 年入社。2021 年 4 月まで CLINICS オンライン診療のセールス部に所属。その後エンジニアに社内転職し、Pharms の開発に携わる。現在はサーバサイドの開発を中心に担当している。 新倉さん 2021 年入社。不動産テック企業でのデザイン業務を経て、メドレーへ入社。入社時から Pharms のデザイナーとして各種デザインの業務を行なう。 古川さん 2022 年に新卒入社。現在はプロダクトの改善を幅広く担当している。 Pharms というプロダクトについて 山田 : まず「TGIF」について聞いていく前に、Pharms とはどのようなプロダクトなのか教えてもらってもよいですか? 新居 : 一言で言うと、薬局の業務効率化支援システムです。 CLINICS オンライン診療 で診察を受けた患者さんや、直接来院された患者さんからの処方箋などを受けて、Pharms を通して服薬指導や、会計、服薬フォローアップなどを一貫して行なえるシステムになっています。 新居さん 山田 : ユーザーについてですが、例えば、自分が開発を担当している医療介護求人サイトの ジョブメドレー では、お仕事をお探しの求職者の方々と、求人情報を掲載いただく医院・事業所の方々が使用しているシステムなのですが、Pharms はどのような方に使われるのでしょうか? 小田 : CLINICS アプリを通して間接的に患者さんが使っていますが、大きい割合を占めるのは顧客である薬局のスタッフさん(薬剤師など)ですね。 小田さん 山田 : やはり現場の薬剤師さんが使っているサービスという性格が強いんですね。 Pharms 開発チームの TGIF という試みについて TGIF を始める前の課題感 山田 : それでは、いよいよ Pharms 開発チームで行なっている TGIF について聞いていきたいと思います。元々、この言葉はどんな経緯で付いたんですか? 小田 : 元々は英語圏で言われている “Thank God It’s Friday” というのが語源で、そのまま金曜日の夕方に開催されるものだったのでこの名前を付けています。ちょっと昔の言葉で言うと「花金」ですね。 山田 : 「1 週間が終わりだー!」っていう開放感を表わしてるんですね。どういうきっかけでこの TGIF をやっていこうと思ったんでしょうか? 小田 : 一昨年の自分の入社前のタイミングで、Pharms 立ち上げ時のメンバーが別のプロジェクトに集中することとなり、チーム体制がガラッと変わる時期がありました。それ以降、チーム内の情報がサイロ化したり、チームとしての一体感がないという課題を感じていました。この課題を解決するために 雑多に何でも話せるコミュニケーションの場 を作ろうというのが、きっかけでした。 新居 : もちろん、それまでもチームで話す機会が全く無かったわけではなく、開発計画をクォーター単位で作っているのでそのタイミングでディスカッションする場を設けていました。でも もうちょっとコミュニケーションの頻度を増やしたらより良くなりそうだよね という話があり、毎週金曜日の TGIF が誕生しました。 山田 : そういう経緯だったんですね。それまでエンジニア・デザイナーが参加する定例のような会議体はどのようなものがあったんですか? 新居 : クォーター毎のディスカッション以外だと、週一で行なわれる「プロダクト定例」だけでした。こちらは開発メンバーだけではなく、事業部のメンバーも参加してプロダクト開発全般についての共有を目的にしたものです。それ以外で定例の会議体はなく、デイリーで行われるような「朝会・夕会」も Pharms 開発チームでは行なっていませんでした。 山田 : チーム体制の変更後、新しいメンバーの加入もあり、今までのコミュニケーション量では足りなくなってしまったんですね。 新居 : はい。チームの状況が変わって行く中で、 もっと生産性を上げていきたいという思いがありました 。チームが小規模だった頃は隣の席の同僚エンジニアに相談しつつ開発をドンドン前に進めていくことができたんですが、人数が増えてくるとそうしたコミュニケーションも希薄になります。具体的には、開発をしていて設計方針・実装方針などがズレることは多々あると思うんですが、それをさっと会話して軌道修正し、開発を推進していくというのが難しくなってきていました。 TGIF の実際 山田 : では、具体的に TGIF でどのような事を話し合われるんでしょうか? 新居 : まずは今週の振り返りから始まります。前週の TGIF で Try に対して個々がどう動いていくかを決めているので、それに対して振り返りをしていきます。各自コメントを添えて何%達成したのか話していきます。 小田 : その後、今週のチーム全体の動きに対して KPT を実施します。チーム全体として良かった動きや課題などを挙げていき、内容を深ぼったり課題解決に向けて意見を交換したりしています。また、以前はチームの動きとは関係ない個人の話などが挙がってくることが多かったのですが、 チーム全体での生産性の向上ということを目的として考えた場合、それと関連のない個人の話をしても寄与しない ので、チーム全体に関わることをメインにするよう改善し今の形になってきました。 KPT が終わったら、隔週でチーム目標と Problem で挙がったことなども踏まえて各自の来週の Try を決めます。 新居 : 最後に、ちょっとしたことでも良いのでチームに共有したい「気になること」を話していきます。ここは開発に関係する・しないに関わらない話題を出しています。例えば 、Pharms のプロダクト自体や会社の組織的なこと、プルリクエストのレビューのお願いなど雑多な話題ですね。 その中で「ライブラリアップデート」についての課題や進捗などの共有もします。単純にバージョンアップして済むというものであれば良いですが、アップデートの結果、広範囲に影響があったりだとか、そもそもエラーなどで上手くアップデートできないなどがあるので、そうした困り事の共有と解決を目的としています。 山田 : 先ほどチーム目標を隔週で決めるというお話でしたが、こちらはどういう風に決めていくんですか? 小田 : KPT の内容を踏まえて、基本的には Our Essentials (以下 OE) に照らし合わせて決めていきます。そのチーム目標に対して、個人の目標をブレイクダウンして決める形にしています。 固くなりすぎない会を目指して 山田 : なるほど。 OE に合わせて目標設定していくというのは、分かりやすくて良い ですね。話は少し逸れますが、この TGIF では毎回お菓子を持ち寄って行なっているということですが、どうしてなのでしょうか? 小田 : TGIF を行なっている内に、メンバー間での意思疎通の少なさによって、チームの生産性が上がらないという課題は解決していきました。しかし、続けている内に段々と会自体が固い雰囲気になってきて、かっちりとした振り返りの場という感じになってしまいました。元々 TGIF の在り方としてはどういうものだっけという原点に立ち返ると、やはり 「気軽にコミュニケーションができる場」という点が重要 だと再認識しまして、そこからは場の雰囲気を柔らげるためにお菓子を持ち寄り始めました。 お菓子を囲んでの TGIF 山田 : 確かに固すぎると、ちょっと会の名前とそぐわない感じになってしまいますね。 TGIF 運用の秘訣 山田 : 他に TGIF を運用する上での工夫などはありますか? 新倉 : 会の性質上、話が盛り上がって時間がオーバーしがちなので「 TGIF で話す内容はチームの成長につながることに限定する」など、 効率的かつ意味のある運用 になるよう進め方の見直しと実践は日々試行錯誤しています。 新倉さん 古川 : 自分は去年の新卒入社ですので、「言ってもしょうがないかな?」というような事もあるのですが、あえてそういった事を考えずに積極的に発言するようにしています。 実際に発言したために他の方との共有もできますし、学びになるような議論に発展することも多い ので、言って良かったという場合が多いです。 古川さん 兒玉 : ささいな事でも、誰かが チームの為に動いた事に対しては積極的にお礼を言っています 。誰かがちゃんと自分のやったことを見てくれていると認識できることが、チームの雰囲気を良くし、チーム運営の好循環に繋がっているかと思います。 兒玉さん 新居 : ちゃんとチーム全員にとって意味がある時間にするということは意識しています。仮にあまり生産的な時間ではなくなったら TGIF を止めてしまっても良いと思っています。その為には 「チームのために話しをした方がよいことは必ず話す」「本音ベースで建設的に議論する」「出てきた課題は必ずクリアにして次週以降に解決を目指す」 という事を意識しています。 こうして話すとある意味当たり前の事ばかりなんですが、参加者全員がこうした点を共通認識として持っていないと、単に KPT を発表するだけの場となり会自体が形骸化していってしまいます。ですので、きちんとこの共通認識を持って臨めるようにするというのを大切にしています。 小田 : また、TGIF で話が盛り上がるのは良いことなのですが、週に一回のこの場を待たずにもっと会話を活性化する取り組みとして 週の頭に Slack のプロダクトチャンネルにテディベアがリマインダーで現れる ので、そのスレッドで話しをするようにしています。 山田 : へえー!何でテディベアなんですか? 新居 : プログラミングで自分で解決できない問題が出てきたときに、テディベアのぬいぐるみに話しかけると問題が整理されて解決できるというテディベア効果から取っています。 テディベアの リマインダーにコメントが付いている様子 山田 : なるほど、それでテディベアが出てくるんですね。 小田 : TGIF を待つまでの時間がムダだよねっていう話もあって先程の効率化の話も含めて行なっている施策です。 新倉 : これまで TGIF やテディベアが無い時は下手をすると一週間誰とも会話をしていないということもあったんですが、この取り組みをしてから 本当にコミュニケーションが増えました 。 TGIF の利点とこれからについて 山田 : ありがとうございます。既に話にも出ていますが、TGIF をやって良かった点はどういったものがあるか改めて聞かせてください。 小田 : やっぱり格段に「チームでの開発」をしているという意識が高まったことが良いことでした。それまではやはりコミュニケーションなどが不足していたのですが、今は TGIF などのおかげですぐに相談しようという感じにもなって良いチームになってきたなという感触があります。 さらにチームを良くする土壌 にするようにしていきたいです。 新居 : 1 つ目は以前よりお互いのことを知れるようになって心理的安全性が高まったこと、2 つ目はチームとしての方針や共通認識を合わせる場として機能していること、3 つ目は毎週前週からの課題を改善でき、チームや個人の成長を感じられるようになったことです。 全体的にチーム運営にプラス になっていると感じています。 新倉 : 私は TGIF が始まってから半年後にジョインしました。元々はエンジニアの皆さんだけのものだと思ってちょっと尻込みしていたところがあったのですが、蓋を開けてみるとプロダクトやチーム自体についての話が多く、 デザイナーとしても非常に学び があるものでした。全体的にはクォーター毎に目標を決めてそれに向けて動くのですが、その目標までの道のりを埋めるという意味でも、TGIF があって良かったです。 新居 : 時にはエンジニアだけにしかわからない話題などもあったりもしますが、大部分は新倉さんが話したようにプロダクトやチームの改善をいかに行なうかという話題になっているので、そこでデザイナー視点での話が聞けてエンジニアとしても大変助かります。 古川 : 自分は入ってからずっと TGIF があるのである種当たり前の存在なのですが…。チームメンバーの人となりなどが分かるのは大変助かります。また、自分とは全然違う経歴を持っているエンジニアの方などは同じ話題でも、 自分とは全然違う視点を持って話をしていたりするので、すごく勉強になっています 。チームの課題が話し合われるので、以前は自分のタスクが終わったら、「次は何をしよう」という感じになっていたんですが、TGIF のおかげで「チームがここで困っているなら自分がやろう」と客観的に分かるようになっています。また、OE は半年毎の自己評価の際にも体現できているかどうかチェックすることになるので、TGIF での目標達成度がそれに役立ちます。 兒玉 : 一番は心理的安全性が高くなったことです。エンジニアとしての働き方とセールスをしていた時の働き方が全然違うところが多かったので、1on1 で新居さんとそういった差異について聞くという機会しかなかったんですが、今は TGIF があるのでここで皆さんに聞けるようになって様々な視点でのアドバイスを頂けるようになりました。また、エンジニアとしてお勧めの書籍や勉強法など普通に聞こうとすると、 あまり機会がないような部分も気軽に聞けるようになったので、勉強の効率も良くなりました 。 山田 : みなさん、ありがとうございます。それぞれの立場ですごく有用な会なのが分かりますね。 TGIF のこれからについてもお話ください。 新居 : 先ほども少し話しましたが、形骸化してしまい惰性で行なってしまうということになるのが本当に怖いと思っています。運用することが目的になってしまい、本来の目的が達成できないと本末転倒ですし。今もやっていることですが、ちゃんとメンバー構成や状況に応じて運用をアップデートしていくということが一番大切だと思っています。 小田 : 今までエンジニアだけの会から TGIF は始まっていますが、デザイナーの新倉さんが参加してくれたのを始めとして、昨年末から企画職の方も入ってくれるようになったので、裾野の広がりを感じています。こういった立場が別々のメンバーが入ってもスムーズに目的が達成できるように TGIF のアップデートを続けていきたいですね。 新倉 : 色々な役割の人がフラットに発言できる場があるおかげで、プロダクトについて総合的な視点を持つことができたと感じています。実際に自分達が試行錯誤して出した機能が「お客様にこんな風に使われて、こんな感想をもらえた」などすぐに共有してもらえるので、開発のモチベーションにもつながる良いサイクルになっています。 Pharms 開発チームでは、どんな人と一緒に働きたいと考えている? 山田 : ありがとうございます。最後に Pharms の開発チームには、どんな方がジョインされると嬉しいですか? 小田 : いつもチーム内で話をしているのが「 顧客ファースト 」という言葉です。これは Pharms に限った話ではなくメドレー全体の話でもあるのですが、顧客や患者などサービスを使っている人達に対して提供できる価値は何かということをきちんと意識して開発ができる方です。技術や数字だけを見るわけじゃなく、その先の顧客をちゃんと考えて開発ができる人ですね。 新居 : メドレーの事業やバリューに共感があるのは大前提です。また、小田さんが話されたことに加えて、プロダクトをより良くしようと考えてるメンバーと共に、自分はこうしたいという意志も持ちつつ、ときには建設的に議論もしながらプロダクト開発を推進できる人と一緒に仕事ができると嬉しいです。大変なことも勿論ありますが、それを楽しみ、周囲を巻き込みながら技術・技術以外の部分も含め前に進めていける人が良いなと思います。 山田 : TGIF も顧客ファーストの理念から生まれたものということになりますよね。ありがとうございました! さいごに Pharms 開発チームが実践している「TGIF」というチームビルディングの方法についてインタビューしてきましたが、いかがだったでしょうか? 実際に TGIF をしている様子などを見ると、和やかな雰囲気ではありますが、全員真剣にプロダクトについて議論をしている姿が印象的でした。 こんなチームで調剤薬局や患者のためのプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回は Pharms 開発チームがチームビルディングの一環として行なっている「TGIF」という施策について参加している皆さんに、どのような効果があるのかなどを聞いていこうと思います。 インタビュイー紹介 新居さん 2016 年入社。ソーシャルゲーム開発会社を経て、メドレーへ入社。入社当初はジョブメドレーのプロダクト開発などを担当していたが、2020 年から Pharms で中規模施策の開発を中心に業務を行なう。 小田さん 2021 年入社。大手メーカーで病院や調剤薬局向けの IT システムの開発業務を経て、メドレー入社。Pharms では中規模施策の開発を中心に業務を行なう。 兒玉さん 2018 年入社。2021 年 4 月まで CLINICS オンライン診療のセールス部に所属。その後エンジニアに社内転職し、Pharms の開発に携わる。現在はサーバサイドの開発を中心に担当している。 新倉さん 2021 年入社。不動産テック企業でのデザイン業務を経て、メドレーへ入社。入社時から Pharms のデザイナーとして各種デザインの業務を行なう。 古川さん 2022 年に新卒入社。現在はプロダクトの改善を幅広く担当している。 Pharms というプロダクトについて 山田 : まず「TGIF」について聞いていく前に、Pharms とはどのようなプロダクトなのか教えてもらってもよいですか? 新居 : 一言で言うと、薬局の業務効率化支援システムです。 CLINICS オンライン診療 で診察を受けた患者さんや、直接来院された患者さんからの処方箋などを受けて、Pharms を通して服薬指導や、会計、服薬フォローアップなどを一貫して行なえるシステムになっています。 新居さん 山田 : ユーザーについてですが、例えば、自分が開発を担当している医療介護求人サイトの ジョブメドレー では、お仕事をお探しの求職者の方々と、求人情報を掲載いただく医院・事業所の方々が使用しているシステムなのですが、Pharms はどのような方に使われるのでしょうか? 小田 : CLINICS アプリを通して間接的に患者さんが使っていますが、大きい割合を占めるのは顧客である薬局のスタッフさん(薬剤師など)ですね。 小田さん 山田 : やはり現場の薬剤師さんが使っているサービスという性格が強いんですね。 Pharms 開発チームの TGIF という試みについて TGIF を始める前の課題感 山田 : それでは、いよいよ Pharms 開発チームで行なっている TGIF について聞いていきたいと思います。元々、この言葉はどんな経緯で付いたんですか? 小田 : 元々は英語圏で言われている “Thank God It’s Friday” というのが語源で、そのまま金曜日の夕方に開催されるものだったのでこの名前を付けています。ちょっと昔の言葉で言うと「花金」ですね。 山田 : 「1 週間が終わりだー!」っていう開放感を表わしてるんですね。どういうきっかけでこの TGIF をやっていこうと思ったんでしょうか? 小田 : 一昨年の自分の入社前のタイミングで、Pharms 立ち上げ時のメンバーが別のプロジェクトに集中することとなり、チーム体制がガラッと変わる時期がありました。それ以降、チーム内の情報がサイロ化したり、チームとしての一体感がないという課題を感じていました。この課題を解決するために 雑多に何でも話せるコミュニケーションの場 を作ろうというのが、きっかけでした。 新居 : もちろん、それまでもチームで話す機会が全く無かったわけではなく、開発計画をクォーター単位で作っているのでそのタイミングでディスカッションする場を設けていました。でも もうちょっとコミュニケーションの頻度を増やしたらより良くなりそうだよね という話があり、毎週金曜日の TGIF が誕生しました。 山田 : そういう経緯だったんですね。それまでエンジニア・デザイナーが参加する定例のような会議体はどのようなものがあったんですか? 新居 : クォーター毎のディスカッション以外だと、週一で行なわれる「プロダクト定例」だけでした。こちらは開発メンバーだけではなく、事業部のメンバーも参加してプロダクト開発全般についての共有を目的にしたものです。それ以外で定例の会議体はなく、デイリーで行われるような「朝会・夕会」も Pharms 開発チームでは行なっていませんでした。 山田 : チーム体制の変更後、新しいメンバーの加入もあり、今までのコミュニケーション量では足りなくなってしまったんですね。 新居 : はい。チームの状況が変わって行く中で、 もっと生産性を上げていきたいという思いがありました 。チームが小規模だった頃は隣の席の同僚エンジニアに相談しつつ開発をドンドン前に進めていくことができたんですが、人数が増えてくるとそうしたコミュニケーションも希薄になります。具体的には、開発をしていて設計方針・実装方針などがズレることは多々あると思うんですが、それをさっと会話して軌道修正し、開発を推進していくというのが難しくなってきていました。 TGIF の実際 山田 : では、具体的に TGIF でどのような事を話し合われるんでしょうか? 新居 : まずは今週の振り返りから始まります。前週の TGIF で Try に対して個々がどう動いていくかを決めているので、それに対して振り返りをしていきます。各自コメントを添えて何%達成したのか話していきます。 小田 : その後、今週のチーム全体の動きに対して KPT を実施します。チーム全体として良かった動きや課題などを挙げていき、内容を深ぼったり課題解決に向けて意見を交換したりしています。また、以前はチームの動きとは関係ない個人の話などが挙がってくることが多かったのですが、 チーム全体での生産性の向上ということを目的として考えた場合、それと関連のない個人の話をしても寄与しない ので、チーム全体に関わることをメインにするよう改善し今の形になってきました。 KPT が終わったら、隔週でチーム目標と Problem で挙がったことなども踏まえて各自の来週の Try を決めます。 新居 : 最後に、ちょっとしたことでも良いのでチームに共有したい「気になること」を話していきます。ここは開発に関係する・しないに関わらない話題を出しています。例えば 、Pharms のプロダクト自体や会社の組織的なこと、プルリクエストのレビューのお願いなど雑多な話題ですね。 その中で「ライブラリアップデート」についての課題や進捗などの共有もします。単純にバージョンアップして済むというものであれば良いですが、アップデートの結果、広範囲に影響があったりだとか、そもそもエラーなどで上手くアップデートできないなどがあるので、そうした困り事の共有と解決を目的としています。 山田 : 先ほどチーム目標を隔週で決めるというお話でしたが、こちらはどういう風に決めていくんですか? 小田 : KPT の内容を踏まえて、基本的には Our Essentials (以下 OE) に照らし合わせて決めていきます。そのチーム目標に対して、個人の目標をブレイクダウンして決める形にしています。 固くなりすぎない会を目指して 山田 : なるほど。 OE に合わせて目標設定していくというのは、分かりやすくて良い ですね。話は少し逸れますが、この TGIF では毎回お菓子を持ち寄って行なっているということですが、どうしてなのでしょうか? 小田 : TGIF を行なっている内に、メンバー間での意思疎通の少なさによって、チームの生産性が上がらないという課題は解決していきました。しかし、続けている内に段々と会自体が固い雰囲気になってきて、かっちりとした振り返りの場という感じになってしまいました。元々 TGIF の在り方としてはどういうものだっけという原点に立ち返ると、やはり 「気軽にコミュニケーションができる場」という点が重要 だと再認識しまして、そこからは場の雰囲気を柔らげるためにお菓子を持ち寄り始めました。 お菓子を囲んでの TGIF 山田 : 確かに固すぎると、ちょっと会の名前とそぐわない感じになってしまいますね。 TGIF 運用の秘訣 山田 : 他に TGIF を運用する上での工夫などはありますか? 新倉 : 会の性質上、話が盛り上がって時間がオーバーしがちなので「 TGIF で話す内容はチームの成長につながることに限定する」など、 効率的かつ意味のある運用 になるよう進め方の見直しと実践は日々試行錯誤しています。 新倉さん 古川 : 自分は去年の新卒入社ですので、「言ってもしょうがないかな?」というような事もあるのですが、あえてそういった事を考えずに積極的に発言するようにしています。 実際に発言したために他の方との共有もできますし、学びになるような議論に発展することも多い ので、言って良かったという場合が多いです。 古川さん 兒玉 : ささいな事でも、誰かが チームの為に動いた事に対しては積極的にお礼を言っています 。誰かがちゃんと自分のやったことを見てくれていると認識できることが、チームの雰囲気を良くし、チーム運営の好循環に繋がっているかと思います。 兒玉さん 新居 : ちゃんとチーム全員にとって意味がある時間にするということは意識しています。仮にあまり生産的な時間ではなくなったら TGIF を止めてしまっても良いと思っています。その為には 「チームのために話しをした方がよいことは必ず話す」「本音ベースで建設的に議論する」「出てきた課題は必ずクリアにして次週以降に解決を目指す」 という事を意識しています。 こうして話すとある意味当たり前の事ばかりなんですが、参加者全員がこうした点を共通認識として持っていないと、単に KPT を発表するだけの場となり会自体が形骸化していってしまいます。ですので、きちんとこの共通認識を持って臨めるようにするというのを大切にしています。 小田 : また、TGIF で話が盛り上がるのは良いことなのですが、週に一回のこの場を待たずにもっと会話を活性化する取り組みとして 週の頭に Slack のプロダクトチャンネルにテディベアがリマインダーで現れる ので、そのスレッドで話しをするようにしています。 山田 : へえー!何でテディベアなんですか? 新居 : プログラミングで自分で解決できない問題が出てきたときに、テディベアのぬいぐるみに話しかけると問題が整理されて解決できるというテディベア効果から取っています。 テディベアの リマインダーにコメントが付いている様子 山田 : なるほど、それでテディベアが出てくるんですね。 小田 : TGIF を待つまでの時間がムダだよねっていう話もあって先程の効率化の話も含めて行なっている施策です。 新倉 : これまで TGIF やテディベアが無い時は下手をすると一週間誰とも会話をしていないということもあったんですが、この取り組みをしてから 本当にコミュニケーションが増えました 。 TGIF の利点とこれからについて 山田 : ありがとうございます。既に話にも出ていますが、TGIF をやって良かった点はどういったものがあるか改めて聞かせてください。 小田 : やっぱり格段に「チームでの開発」をしているという意識が高まったことが良いことでした。それまではやはりコミュニケーションなどが不足していたのですが、今は TGIF などのおかげですぐに相談しようという感じにもなって良いチームになってきたなという感触があります。 さらにチームを良くする土壌 にするようにしていきたいです。 新居 : 1 つ目は以前よりお互いのことを知れるようになって心理的安全性が高まったこと、2 つ目はチームとしての方針や共通認識を合わせる場として機能していること、3 つ目は毎週前週からの課題を改善でき、チームや個人の成長を感じられるようになったことです。 全体的にチーム運営にプラス になっていると感じています。 新倉 : 私は TGIF が始まってから半年後にジョインしました。元々はエンジニアの皆さんだけのものだと思ってちょっと尻込みしていたところがあったのですが、蓋を開けてみるとプロダクトやチーム自体についての話が多く、 デザイナーとしても非常に学び があるものでした。全体的にはクォーター毎に目標を決めてそれに向けて動くのですが、その目標までの道のりを埋めるという意味でも、TGIF があって良かったです。 新居 : 時にはエンジニアだけにしかわからない話題などもあったりもしますが、大部分は新倉さんが話したようにプロダクトやチームの改善をいかに行なうかという話題になっているので、そこでデザイナー視点での話が聞けてエンジニアとしても大変助かります。 古川 : 自分は入ってからずっと TGIF があるのである種当たり前の存在なのですが…。チームメンバーの人となりなどが分かるのは大変助かります。また、自分とは全然違う経歴を持っているエンジニアの方などは同じ話題でも、 自分とは全然違う視点を持って話をしていたりするので、すごく勉強になっています 。チームの課題が話し合われるので、以前は自分のタスクが終わったら、「次は何をしよう」という感じになっていたんですが、TGIF のおかげで「チームがここで困っているなら自分がやろう」と客観的に分かるようになっています。また、OE は半年毎の自己評価の際にも体現できているかどうかチェックすることになるので、TGIF での目標達成度がそれに役立ちます。 兒玉 : 一番は心理的安全性が高くなったことです。エンジニアとしての働き方とセールスをしていた時の働き方が全然違うところが多かったので、1on1 で新居さんとそういった差異について聞くという機会しかなかったんですが、今は TGIF があるのでここで皆さんに聞けるようになって様々な視点でのアドバイスを頂けるようになりました。また、エンジニアとしてお勧めの書籍や勉強法など普通に聞こうとすると、 あまり機会がないような部分も気軽に聞けるようになったので、勉強の効率も良くなりました 。 山田 : みなさん、ありがとうございます。それぞれの立場ですごく有用な会なのが分かりますね。 TGIF のこれからについてもお話ください。 新居 : 先ほども少し話しましたが、形骸化してしまい惰性で行なってしまうということになるのが本当に怖いと思っています。運用することが目的になってしまい、本来の目的が達成できないと本末転倒ですし。今もやっていることですが、ちゃんとメンバー構成や状況に応じて運用をアップデートしていくということが一番大切だと思っています。 小田 : 今までエンジニアだけの会から TGIF は始まっていますが、デザイナーの新倉さんが参加してくれたのを始めとして、昨年末から企画職の方も入ってくれるようになったので、裾野の広がりを感じています。こういった立場が別々のメンバーが入ってもスムーズに目的が達成できるように TGIF のアップデートを続けていきたいですね。 新倉 : 色々な役割の人がフラットに発言できる場があるおかげで、プロダクトについて総合的な視点を持つことができたと感じています。実際に自分達が試行錯誤して出した機能が「お客様にこんな風に使われて、こんな感想をもらえた」などすぐに共有してもらえるので、開発のモチベーションにもつながる良いサイクルになっています。 Pharms 開発チームでは、どんな人と一緒に働きたいと考えている? 山田 : ありがとうございます。最後に Pharms の開発チームには、どんな方がジョインされると嬉しいですか? 小田 : いつもチーム内で話をしているのが「 顧客ファースト 」という言葉です。これは Pharms に限った話ではなくメドレー全体の話でもあるのですが、顧客や患者などサービスを使っている人達に対して提供できる価値は何かということをきちんと意識して開発ができる方です。技術や数字だけを見るわけじゃなく、その先の顧客をちゃんと考えて開発ができる人ですね。 新居 : メドレーの事業やバリューに共感があるのは大前提です。また、小田さんが話されたことに加えて、プロダクトをより良くしようと考えてるメンバーと共に、自分はこうしたいという意志も持ちつつ、ときには建設的に議論もしながらプロダクト開発を推進できる人と一緒に仕事ができると嬉しいです。大変なことも勿論ありますが、それを楽しみ、周囲を巻き込みながら技術・技術以外の部分も含め前に進めていける人が良いなと思います。 山田 : TGIF も顧客ファーストの理念から生まれたものということになりますよね。ありがとうございました! さいごに Pharms 開発チームが実践している「TGIF」というチームビルディングの方法についてインタビューしてきましたが、いかがだったでしょうか? 実際に TGIF をしている様子などを見ると、和やかな雰囲気ではありますが、全員真剣にプロダクトについて議論をしている姿が印象的でした。 こんなチームで調剤薬局や患者のためのプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回は Pharms 開発チームがチームビルディングの一環として行なっている「TGIF」という施策について参加している皆さんに、どのような効果があるのかなどを聞いていこうと思います。 インタビュイー紹介 新居さん 2016 年入社。ソーシャルゲーム開発会社を経て、メドレーへ入社。入社当初はジョブメドレーのプロダクト開発などを担当していたが、2020 年から Pharms で中規模施策の開発を中心に業務を行なう。 小田さん 2021 年入社。大手メーカーで病院や調剤薬局向けの IT システムの開発業務を経て、メドレー入社。Pharms では中規模施策の開発を中心に業務を行なう。 兒玉さん 2018 年入社。2021 年 4 月まで CLINICS オンライン診療のセールス部に所属。その後エンジニアに社内転職し、Pharms の開発に携わる。現在はサーバサイドの開発を中心に担当している。 新倉さん 2021 年入社。不動産テック企業でのデザイン業務を経て、メドレーへ入社。入社時から Pharms のデザイナーとして各種デザインの業務を行なう。 古川さん 2022 年に新卒入社。現在はプロダクトの改善を幅広く担当している。 Pharms というプロダクトについて 山田 : まず「TGIF」について聞いていく前に、Pharms とはどのようなプロダクトなのか教えてもらってもよいですか? 新居 : 一言で言うと、薬局の業務効率化支援システムです。 CLINICS オンライン診療 で診察を受けた患者さんや、直接来院された患者さんからの処方箋などを受けて、Pharms を通して服薬指導や、会計、服薬フォローアップなどを一貫して行なえるシステムになっています。 新居さん 山田 : ユーザーについてですが、例えば、自分が開発を担当している医療介護求人サイトの ジョブメドレー では、お仕事をお探しの求職者の方々と、求人情報を掲載いただく医院・事業所の方々が使用しているシステムなのですが、Pharms はどのような方に使われるのでしょうか? 小田 : CLINICS アプリを通して間接的に患者さんが使っていますが、大きい割合を占めるのは顧客である薬局のスタッフさん(薬剤師など)ですね。 小田さん 山田 : やはり現場の薬剤師さんが使っているサービスという性格が強いんですね。 Pharms 開発チームの TGIF という試みについて TGIF を始める前の課題感 山田 : それでは、いよいよ Pharms 開発チームで行なっている TGIF について聞いていきたいと思います。元々、この言葉はどんな経緯で付いたんですか? 小田 : 元々は英語圏で言われている “Thank God It’s Friday” というのが語源で、そのまま金曜日の夕方に開催されるものだったのでこの名前を付けています。ちょっと昔の言葉で言うと「花金」ですね。 山田 : 「1 週間が終わりだー!」っていう開放感を表わしてるんですね。どういうきっかけでこの TGIF をやっていこうと思ったんでしょうか? 小田 : 一昨年の自分の入社前のタイミングで、Pharms 立ち上げ時のメンバーが別のプロジェクトに集中することとなり、チーム体制がガラッと変わる時期がありました。それ以降、チーム内の情報がサイロ化したり、チームとしての一体感がないという課題を感じていました。この課題を解決するために 雑多に何でも話せるコミュニケーションの場 を作ろうというのが、きっかけでした。 新居 : もちろん、それまでもチームで話す機会が全く無かったわけではなく、開発計画をクォーター単位で作っているのでそのタイミングでディスカッションする場を設けていました。でも もうちょっとコミュニケーションの頻度を増やしたらより良くなりそうだよね という話があり、毎週金曜日の TGIF が誕生しました。 山田 : そういう経緯だったんですね。それまでエンジニア・デザイナーが参加する定例のような会議体はどのようなものがあったんですか? 新居 : クォーター毎のディスカッション以外だと、週一で行なわれる「プロダクト定例」だけでした。こちらは開発メンバーだけではなく、事業部のメンバーも参加してプロダクト開発全般についての共有を目的にしたものです。それ以外で定例の会議体はなく、デイリーで行われるような「朝会・夕会」も Pharms 開発チームでは行なっていませんでした。 山田 : チーム体制の変更後、新しいメンバーの加入もあり、今までのコミュニケーション量では足りなくなってしまったんですね。 新居 : はい。チームの状況が変わって行く中で、 もっと生産性を上げていきたいという思いがありました 。チームが小規模だった頃は隣の席の同僚エンジニアに相談しつつ開発をドンドン前に進めていくことができたんですが、人数が増えてくるとそうしたコミュニケーションも希薄になります。具体的には、開発をしていて設計方針・実装方針などがズレることは多々あると思うんですが、それをさっと会話して軌道修正し、開発を推進していくというのが難しくなってきていました。 TGIF の実際 山田 : では、具体的に TGIF でどのような事を話し合われるんでしょうか? 新居 : まずは今週の振り返りから始まります。前週の TGIF で Try に対して個々がどう動いていくかを決めているので、それに対して振り返りをしていきます。各自コメントを添えて何%達成したのか話していきます。 小田 : その後、今週のチーム全体の動きに対して KPT を実施します。チーム全体として良かった動きや課題などを挙げていき、内容を深ぼったり課題解決に向けて意見を交換したりしています。また、以前はチームの動きとは関係ない個人の話などが挙がってくることが多かったのですが、 チーム全体での生産性の向上ということを目的として考えた場合、それと関連のない個人の話をしても寄与しない ので、チーム全体に関わることをメインにするよう改善し今の形になってきました。 KPT が終わったら、隔週でチーム目標と Problem で挙がったことなども踏まえて各自の来週の Try を決めます。 新居 : 最後に、ちょっとしたことでも良いのでチームに共有したい「気になること」を話していきます。ここは開発に関係する・しないに関わらない話題を出しています。例えば 、Pharms のプロダクト自体や会社の組織的なこと、プルリクエストのレビューのお願いなど雑多な話題ですね。 その中で「ライブラリアップデート」についての課題や進捗などの共有もします。単純にバージョンアップして済むというものであれば良いですが、アップデートの結果、広範囲に影響があったりだとか、そもそもエラーなどで上手くアップデートできないなどがあるので、そうした困り事の共有と解決を目的としています。 山田 : 先ほどチーム目標を隔週で決めるというお話でしたが、こちらはどういう風に決めていくんですか? 小田 : KPT の内容を踏まえて、基本的には Our Essentials (以下 OE) に照らし合わせて決めていきます。そのチーム目標に対して、個人の目標をブレイクダウンして決める形にしています。 固くなりすぎない会を目指して 山田 : なるほど。 OE に合わせて目標設定していくというのは、分かりやすくて良い ですね。話は少し逸れますが、この TGIF では毎回お菓子を持ち寄って行なっているということですが、どうしてなのでしょうか? 小田 : TGIF を行なっている内に、メンバー間での意思疎通の少なさによって、チームの生産性が上がらないという課題は解決していきました。しかし、続けている内に段々と会自体が固い雰囲気になってきて、かっちりとした振り返りの場という感じになってしまいました。元々 TGIF の在り方としてはどういうものだっけという原点に立ち返ると、やはり 「気軽にコミュニケーションができる場」という点が重要 だと再認識しまして、そこからは場の雰囲気を柔らげるためにお菓子を持ち寄り始めました。 お菓子を囲んでの TGIF 山田 : 確かに固すぎると、ちょっと会の名前とそぐわない感じになってしまいますね。 TGIF 運用の秘訣 山田 : 他に TGIF を運用する上での工夫などはありますか? 新倉 : 会の性質上、話が盛り上がって時間がオーバーしがちなので「 TGIF で話す内容はチームの成長につながることに限定する」など、 効率的かつ意味のある運用 になるよう進め方の見直しと実践は日々試行錯誤しています。 新倉さん 古川 : 自分は去年の新卒入社ですので、「言ってもしょうがないかな?」というような事もあるのですが、あえてそういった事を考えずに積極的に発言するようにしています。 実際に発言したために他の方との共有もできますし、学びになるような議論に発展することも多い ので、言って良かったという場合が多いです。 古川さん 兒玉 : ささいな事でも、誰かが チームの為に動いた事に対しては積極的にお礼を言っています 。誰かがちゃんと自分のやったことを見てくれていると認識できることが、チームの雰囲気を良くし、チーム運営の好循環に繋がっているかと思います。 兒玉さん 新居 : ちゃんとチーム全員にとって意味がある時間にするということは意識しています。仮にあまり生産的な時間ではなくなったら TGIF を止めてしまっても良いと思っています。その為には 「チームのために話しをした方がよいことは必ず話す」「本音ベースで建設的に議論する」「出てきた課題は必ずクリアにして次週以降に解決を目指す」 という事を意識しています。 こうして話すとある意味当たり前の事ばかりなんですが、参加者全員がこうした点を共通認識として持っていないと、単に KPT を発表するだけの場となり会自体が形骸化していってしまいます。ですので、きちんとこの共通認識を持って臨めるようにするというのを大切にしています。 小田 : また、TGIF で話が盛り上がるのは良いことなのですが、週に一回のこの場を待たずにもっと会話を活性化する取り組みとして 週の頭に Slack のプロダクトチャンネルにテディベアがリマインダーで現れる ので、そのスレッドで話しをするようにしています。 山田 : へえー!何でテディベアなんですか? 新居 : プログラミングで自分で解決できない問題が出てきたときに、テディベアのぬいぐるみに話しかけると問題が整理されて解決できるというテディベア効果から取っています。 テディベアの リマインダーにコメントが付いている様子 山田 : なるほど、それでテディベアが出てくるんですね。 小田 : TGIF を待つまでの時間がムダだよねっていう話もあって先程の効率化の話も含めて行なっている施策です。 新倉 : これまで TGIF やテディベアが無い時は下手をすると一週間誰とも会話をしていないということもあったんですが、この取り組みをしてから 本当にコミュニケーションが増えました 。 TGIF の利点とこれからについて 山田 : ありがとうございます。既に話にも出ていますが、TGIF をやって良かった点はどういったものがあるか改めて聞かせてください。 小田 : やっぱり格段に「チームでの開発」をしているという意識が高まったことが良いことでした。それまではやはりコミュニケーションなどが不足していたのですが、今は TGIF などのおかげですぐに相談しようという感じにもなって良いチームになってきたなという感触があります。 さらにチームを良くする土壌 にするようにしていきたいです。 新居 : 1 つ目は以前よりお互いのことを知れるようになって心理的安全性が高まったこと、2 つ目はチームとしての方針や共通認識を合わせる場として機能していること、3 つ目は毎週前週からの課題を改善でき、チームや個人の成長を感じられるようになったことです。 全体的にチーム運営にプラス になっていると感じています。 新倉 : 私は TGIF が始まってから半年後にジョインしました。元々はエンジニアの皆さんだけのものだと思ってちょっと尻込みしていたところがあったのですが、蓋を開けてみるとプロダクトやチーム自体についての話が多く、 デザイナーとしても非常に学び があるものでした。全体的にはクォーター毎に目標を決めてそれに向けて動くのですが、その目標までの道のりを埋めるという意味でも、TGIF があって良かったです。 新居 : 時にはエンジニアだけにしかわからない話題などもあったりもしますが、大部分は新倉さんが話したようにプロダクトやチームの改善をいかに行なうかという話題になっているので、そこでデザイナー視点での話が聞けてエンジニアとしても大変助かります。 古川 : 自分は入ってからずっと TGIF があるのである種当たり前の存在なのですが…。チームメンバーの人となりなどが分かるのは大変助かります。また、自分とは全然違う経歴を持っているエンジニアの方などは同じ話題でも、 自分とは全然違う視点を持って話をしていたりするので、すごく勉強になっています 。チームの課題が話し合われるので、以前は自分のタスクが終わったら、「次は何をしよう」という感じになっていたんですが、TGIF のおかげで「チームがここで困っているなら自分がやろう」と客観的に分かるようになっています。また、OE は半年毎の自己評価の際にも体現できているかどうかチェックすることになるので、TGIF での目標達成度がそれに役立ちます。 兒玉 : 一番は心理的安全性が高くなったことです。エンジニアとしての働き方とセールスをしていた時の働き方が全然違うところが多かったので、1on1 で新居さんとそういった差異について聞くという機会しかなかったんですが、今は TGIF があるのでここで皆さんに聞けるようになって様々な視点でのアドバイスを頂けるようになりました。また、エンジニアとしてお勧めの書籍や勉強法など普通に聞こうとすると、 あまり機会がないような部分も気軽に聞けるようになったので、勉強の効率も良くなりました 。 山田 : みなさん、ありがとうございます。それぞれの立場ですごく有用な会なのが分かりますね。 TGIF のこれからについてもお話ください。 新居 : 先ほども少し話しましたが、形骸化してしまい惰性で行なってしまうということになるのが本当に怖いと思っています。運用することが目的になってしまい、本来の目的が達成できないと本末転倒ですし。今もやっていることですが、ちゃんとメンバー構成や状況に応じて運用をアップデートしていくということが一番大切だと思っています。 小田 : 今までエンジニアだけの会から TGIF は始まっていますが、デザイナーの新倉さんが参加してくれたのを始めとして、昨年末から企画職の方も入ってくれるようになったので、裾野の広がりを感じています。こういった立場が別々のメンバーが入ってもスムーズに目的が達成できるように TGIF のアップデートを続けていきたいですね。 新倉 : 色々な役割の人がフラットに発言できる場があるおかげで、プロダクトについて総合的な視点を持つことができたと感じています。実際に自分達が試行錯誤して出した機能が「お客様にこんな風に使われて、こんな感想をもらえた」などすぐに共有してもらえるので、開発のモチベーションにもつながる良いサイクルになっています。 Pharms 開発チームでは、どんな人と一緒に働きたいと考えている? 山田 : ありがとうございます。最後に Pharms の開発チームには、どんな方がジョインされると嬉しいですか? 小田 : いつもチーム内で話をしているのが「 顧客ファースト 」という言葉です。これは Pharms に限った話ではなくメドレー全体の話でもあるのですが、顧客や患者などサービスを使っている人達に対して提供できる価値は何かということをきちんと意識して開発ができる方です。技術や数字だけを見るわけじゃなく、その先の顧客をちゃんと考えて開発ができる人ですね。 新居 : メドレーの事業やバリューに共感があるのは大前提です。また、小田さんが話されたことに加えて、プロダクトをより良くしようと考えてるメンバーと共に、自分はこうしたいという意志も持ちつつ、ときには建設的に議論もしながらプロダクト開発を推進できる人と一緒に仕事ができると嬉しいです。大変なことも勿論ありますが、それを楽しみ、周囲を巻き込みながら技術・技術以外の部分も含め前に進めていける人が良いなと思います。 山田 : TGIF も顧客ファーストの理念から生まれたものということになりますよね。ありがとうございました! さいごに Pharms 開発チームが実践している「TGIF」というチームビルディングの方法についてインタビューしてきましたが、いかがだったでしょうか? 実際に TGIF をしている様子などを見ると、和やかな雰囲気ではありますが、全員真剣にプロダクトについて議論をしている姿が印象的でした。 こんなチームで調剤薬局や患者のためのプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回は Pharms 開発チームがチームビルディングの一環として行なっている「TGIF」という施策について参加している皆さんに、どのような効果があるのかなどを聞いていこうと思います。 インタビュイー紹介 新居さん 2016 年入社。ソーシャルゲーム開発会社を経て、メドレーへ入社。入社当初はジョブメドレーのプロダクト開発などを担当していたが、2020 年から Pharms で中規模施策の開発を中心に業務を行なう。 小田さん 2021 年入社。大手メーカーで病院や調剤薬局向けの IT システムの開発業務を経て、メドレー入社。Pharms では中規模施策の開発を中心に業務を行なう。 兒玉さん 2018 年入社。2021 年 4 月まで CLINICS オンライン診療のセールス部に所属。その後エンジニアに社内転職し、Pharms の開発に携わる。現在はサーバサイドの開発を中心に担当している。 新倉さん 2021 年入社。不動産テック企業でのデザイン業務を経て、メドレーへ入社。入社時から Pharms のデザイナーとして各種デザインの業務を行なう。 古川さん 2022 年に新卒入社。現在はプロダクトの改善を幅広く担当している。 Pharms というプロダクトについて 山田 : まず「TGIF」について聞いていく前に、Pharms とはどのようなプロダクトなのか教えてもらってもよいですか? 新居 : 一言で言うと、薬局の業務効率化支援システムです。 CLINICS オンライン診療 で診察を受けた患者さんや、直接来院された患者さんからの処方箋などを受けて、Pharms を通して服薬指導や、会計、服薬フォローアップなどを一貫して行なえるシステムになっています。 新居さん 山田 : ユーザーについてですが、例えば、自分が開発を担当している医療介護求人サイトの ジョブメドレー では、お仕事をお探しの求職者の方々と、求人情報を掲載いただく医院・事業所の方々が使用しているシステムなのですが、Pharms はどのような方に使われるのでしょうか? 小田 : CLINICS アプリを通して間接的に患者さんが使っていますが、大きい割合を占めるのは顧客である薬局のスタッフさん(薬剤師など)ですね。 小田さん 山田 : やはり現場の薬剤師さんが使っているサービスという性格が強いんですね。 Pharms 開発チームの TGIF という試みについて TGIF を始める前の課題感 山田 : それでは、いよいよ Pharms 開発チームで行なっている TGIF について聞いていきたいと思います。元々、この言葉はどんな経緯で付いたんですか? 小田 : 元々は英語圏で言われている “Thank God It’s Friday” というのが語源で、そのまま金曜日の夕方に開催されるものだったのでこの名前を付けています。ちょっと昔の言葉で言うと「花金」ですね。 山田 : 「1 週間が終わりだー!」っていう開放感を表わしてるんですね。どういうきっかけでこの TGIF をやっていこうと思ったんでしょうか? 小田 : 一昨年の自分の入社前のタイミングで、Pharms 立ち上げ時のメンバーが別のプロジェクトに集中することとなり、チーム体制がガラッと変わる時期がありました。それ以降、チーム内の情報がサイロ化したり、チームとしての一体感がないという課題を感じていました。この課題を解決するために 雑多に何でも話せるコミュニケーションの場 を作ろうというのが、きっかけでした。 新居 : もちろん、それまでもチームで話す機会が全く無かったわけではなく、開発計画をクォーター単位で作っているのでそのタイミングでディスカッションする場を設けていました。でも もうちょっとコミュニケーションの頻度を増やしたらより良くなりそうだよね という話があり、毎週金曜日の TGIF が誕生しました。 山田 : そういう経緯だったんですね。それまでエンジニア・デザイナーが参加する定例のような会議体はどのようなものがあったんですか? 新居 : クォーター毎のディスカッション以外だと、週一で行なわれる「プロダクト定例」だけでした。こちらは開発メンバーだけではなく、事業部のメンバーも参加してプロダクト開発全般についての共有を目的にしたものです。それ以外で定例の会議体はなく、デイリーで行われるような「朝会・夕会」も Pharms 開発チームでは行なっていませんでした。 山田 : チーム体制の変更後、新しいメンバーの加入もあり、今までのコミュニケーション量では足りなくなってしまったんですね。 新居 : はい。チームの状況が変わって行く中で、 もっと生産性を上げていきたいという思いがありました 。チームが小規模だった頃は隣の席の同僚エンジニアに相談しつつ開発をドンドン前に進めていくことができたんですが、人数が増えてくるとそうしたコミュニケーションも希薄になります。具体的には、開発をしていて設計方針・実装方針などがズレることは多々あると思うんですが、それをさっと会話して軌道修正し、開発を推進していくというのが難しくなってきていました。 TGIF の実際 山田 : では、具体的に TGIF でどのような事を話し合われるんでしょうか? 新居 : まずは今週の振り返りから始まります。前週の TGIF で Try に対して個々がどう動いていくかを決めているので、それに対して振り返りをしていきます。各自コメントを添えて何%達成したのか話していきます。 小田 : その後、今週のチーム全体の動きに対して KPT を実施します。チーム全体として良かった動きや課題などを挙げていき、内容を深ぼったり課題解決に向けて意見を交換したりしています。また、以前はチームの動きとは関係ない個人の話などが挙がってくることが多かったのですが、 チーム全体での生産性の向上ということを目的として考えた場合、それと関連のない個人の話をしても寄与しない ので、チーム全体に関わることをメインにするよう改善し今の形になってきました。 KPT が終わったら、隔週でチーム目標と Problem で挙がったことなども踏まえて各自の来週の Try を決めます。 新居 : 最後に、ちょっとしたことでも良いのでチームに共有したい「気になること」を話していきます。ここは開発に関係する・しないに関わらない話題を出しています。例えば 、Pharms のプロダクト自体や会社の組織的なこと、プルリクエストのレビューのお願いなど雑多な話題ですね。 その中で「ライブラリアップデート」についての課題や進捗などの共有もします。単純にバージョンアップして済むというものであれば良いですが、アップデートの結果、広範囲に影響があったりだとか、そもそもエラーなどで上手くアップデートできないなどがあるので、そうした困り事の共有と解決を目的としています。 山田 : 先ほどチーム目標を隔週で決めるというお話でしたが、こちらはどういう風に決めていくんですか? 小田 : KPT の内容を踏まえて、基本的には Our Essentials (以下 OE) に照らし合わせて決めていきます。そのチーム目標に対して、個人の目標をブレイクダウンして決める形にしています。 固くなりすぎない会を目指して 山田 : なるほど。 OE に合わせて目標設定していくというのは、分かりやすくて良い ですね。話は少し逸れますが、この TGIF では毎回お菓子を持ち寄って行なっているということですが、どうしてなのでしょうか? 小田 : TGIF を行なっている内に、メンバー間での意思疎通の少なさによって、チームの生産性が上がらないという課題は解決していきました。しかし、続けている内に段々と会自体が固い雰囲気になってきて、かっちりとした振り返りの場という感じになってしまいました。元々 TGIF の在り方としてはどういうものだっけという原点に立ち返ると、やはり 「気軽にコミュニケーションができる場」という点が重要 だと再認識しまして、そこからは場の雰囲気を柔らげるためにお菓子を持ち寄り始めました。 お菓子を囲んでの TGIF 山田 : 確かに固すぎると、ちょっと会の名前とそぐわない感じになってしまいますね。 TGIF 運用の秘訣 山田 : 他に TGIF を運用する上での工夫などはありますか? 新倉 : 会の性質上、話が盛り上がって時間がオーバーしがちなので「 TGIF で話す内容はチームの成長につながることに限定する」など、 効率的かつ意味のある運用 になるよう進め方の見直しと実践は日々試行錯誤しています。 新倉さん 古川 : 自分は去年の新卒入社ですので、「言ってもしょうがないかな?」というような事もあるのですが、あえてそういった事を考えずに積極的に発言するようにしています。 実際に発言したために他の方との共有もできますし、学びになるような議論に発展することも多い ので、言って良かったという場合が多いです。 古川さん 兒玉 : ささいな事でも、誰かが チームの為に動いた事に対しては積極的にお礼を言っています 。誰かがちゃんと自分のやったことを見てくれていると認識できることが、チームの雰囲気を良くし、チーム運営の好循環に繋がっているかと思います。 兒玉さん 新居 : ちゃんとチーム全員にとって意味がある時間にするということは意識しています。仮にあまり生産的な時間ではなくなったら TGIF を止めてしまっても良いと思っています。その為には 「チームのために話しをした方がよいことは必ず話す」「本音ベースで建設的に議論する」「出てきた課題は必ずクリアにして次週以降に解決を目指す」 という事を意識しています。 こうして話すとある意味当たり前の事ばかりなんですが、参加者全員がこうした点を共通認識として持っていないと、単に KPT を発表するだけの場となり会自体が形骸化していってしまいます。ですので、きちんとこの共通認識を持って臨めるようにするというのを大切にしています。 小田 : また、TGIF で話が盛り上がるのは良いことなのですが、週に一回のこの場を待たずにもっと会話を活性化する取り組みとして 週の頭に Slack のプロダクトチャンネルにテディベアがリマインダーで現れる ので、そのスレッドで話しをするようにしています。 山田 : へえー!何でテディベアなんですか? 新居 : プログラミングで自分で解決できない問題が出てきたときに、テディベアのぬいぐるみに話しかけると問題が整理されて解決できるというテディベア効果から取っています。 テディベアの リマインダーにコメントが付いている様子 山田 : なるほど、それでテディベアが出てくるんですね。 小田 : TGIF を待つまでの時間がムダだよねっていう話もあって先程の効率化の話も含めて行なっている施策です。 新倉 : これまで TGIF やテディベアが無い時は下手をすると一週間誰とも会話をしていないということもあったんですが、この取り組みをしてから 本当にコミュニケーションが増えました 。 TGIF の利点とこれからについて 山田 : ありがとうございます。既に話にも出ていますが、TGIF をやって良かった点はどういったものがあるか改めて聞かせてください。 小田 : やっぱり格段に「チームでの開発」をしているという意識が高まったことが良いことでした。それまではやはりコミュニケーションなどが不足していたのですが、今は TGIF などのおかげですぐに相談しようという感じにもなって良いチームになってきたなという感触があります。 さらにチームを良くする土壌 にするようにしていきたいです。 新居 : 1 つ目は以前よりお互いのことを知れるようになって心理的安全性が高まったこと、2 つ目はチームとしての方針や共通認識を合わせる場として機能していること、3 つ目は毎週前週からの課題を改善でき、チームや個人の成長を感じられるようになったことです。 全体的にチーム運営にプラス になっていると感じています。 新倉 : 私は TGIF が始まってから半年後にジョインしました。元々はエンジニアの皆さんだけのものだと思ってちょっと尻込みしていたところがあったのですが、蓋を開けてみるとプロダクトやチーム自体についての話が多く、 デザイナーとしても非常に学び があるものでした。全体的にはクォーター毎に目標を決めてそれに向けて動くのですが、その目標までの道のりを埋めるという意味でも、TGIF があって良かったです。 新居 : 時にはエンジニアだけにしかわからない話題などもあったりもしますが、大部分は新倉さんが話したようにプロダクトやチームの改善をいかに行なうかという話題になっているので、そこでデザイナー視点での話が聞けてエンジニアとしても大変助かります。 古川 : 自分は入ってからずっと TGIF があるのである種当たり前の存在なのですが…。チームメンバーの人となりなどが分かるのは大変助かります。また、自分とは全然違う経歴を持っているエンジニアの方などは同じ話題でも、 自分とは全然違う視点を持って話をしていたりするので、すごく勉強になっています 。チームの課題が話し合われるので、以前は自分のタスクが終わったら、「次は何をしよう」という感じになっていたんですが、TGIF のおかげで「チームがここで困っているなら自分がやろう」と客観的に分かるようになっています。また、OE は半年毎の自己評価の際にも体現できているかどうかチェックすることになるので、TGIF での目標達成度がそれに役立ちます。 兒玉 : 一番は心理的安全性が高くなったことです。エンジニアとしての働き方とセールスをしていた時の働き方が全然違うところが多かったので、1on1 で新居さんとそういった差異について聞くという機会しかなかったんですが、今は TGIF があるのでここで皆さんに聞けるようになって様々な視点でのアドバイスを頂けるようになりました。また、エンジニアとしてお勧めの書籍や勉強法など普通に聞こうとすると、 あまり機会がないような部分も気軽に聞けるようになったので、勉強の効率も良くなりました 。 山田 : みなさん、ありがとうございます。それぞれの立場ですごく有用な会なのが分かりますね。 TGIF のこれからについてもお話ください。 新居 : 先ほども少し話しましたが、形骸化してしまい惰性で行なってしまうということになるのが本当に怖いと思っています。運用することが目的になってしまい、本来の目的が達成できないと本末転倒ですし。今もやっていることですが、ちゃんとメンバー構成や状況に応じて運用をアップデートしていくということが一番大切だと思っています。 小田 : 今までエンジニアだけの会から TGIF は始まっていますが、デザイナーの新倉さんが参加してくれたのを始めとして、昨年末から企画職の方も入ってくれるようになったので、裾野の広がりを感じています。こういった立場が別々のメンバーが入ってもスムーズに目的が達成できるように TGIF のアップデートを続けていきたいですね。 新倉 : 色々な役割の人がフラットに発言できる場があるおかげで、プロダクトについて総合的な視点を持つことができたと感じています。実際に自分達が試行錯誤して出した機能が「お客様にこんな風に使われて、こんな感想をもらえた」などすぐに共有してもらえるので、開発のモチベーションにもつながる良いサイクルになっています。 Pharms 開発チームでは、どんな人と一緒に働きたいと考えている? 山田 : ありがとうございます。最後に Pharms の開発チームには、どんな方がジョインされると嬉しいですか? 小田 : いつもチーム内で話をしているのが「 顧客ファースト 」という言葉です。これは Pharms に限った話ではなくメドレー全体の話でもあるのですが、顧客や患者などサービスを使っている人達に対して提供できる価値は何かということをきちんと意識して開発ができる方です。技術や数字だけを見るわけじゃなく、その先の顧客をちゃんと考えて開発ができる人ですね。 新居 : メドレーの事業やバリューに共感があるのは大前提です。また、小田さんが話されたことに加えて、プロダクトをより良くしようと考えてるメンバーと共に、自分はこうしたいという意志も持ちつつ、ときには建設的に議論もしながらプロダクト開発を推進できる人と一緒に仕事ができると嬉しいです。大変なことも勿論ありますが、それを楽しみ、周囲を巻き込みながら技術・技術以外の部分も含め前に進めていける人が良いなと思います。 山田 : TGIF も顧客ファーストの理念から生まれたものということになりますよね。ありがとうございました! さいごに Pharms 開発チームが実践している「TGIF」というチームビルディングの方法についてインタビューしてきましたが、いかがだったでしょうか? 実際に TGIF をしている様子などを見ると、和やかな雰囲気ではありますが、全員真剣にプロダクトについて議論をしている姿が印象的でした。 こんなチームで調剤薬局や患者のためのプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回は Pharms 開発チームがチームビルディングの一環として行なっている「TGIF」という施策について参加している皆さんに、どのような効果があるのかなどを聞いていこうと思います。 インタビュイー紹介 新居さん 2016 年入社。ソーシャルゲーム開発会社を経て、メドレーへ入社。入社当初はジョブメドレーのプロダクト開発などを担当していたが、2020 年から Pharms で中規模施策の開発を中心に業務を行なう。 小田さん 2021 年入社。大手メーカーで病院や調剤薬局向けの IT システムの開発業務を経て、メドレー入社。Pharms では中規模施策の開発を中心に業務を行なう。 兒玉さん 2018 年入社。2021 年 4 月まで CLINICS オンライン診療のセールス部に所属。その後エンジニアに社内転職し、Pharms の開発に携わる。現在はサーバサイドの開発を中心に担当している。 新倉さん 2021 年入社。不動産テック企業でのデザイン業務を経て、メドレーへ入社。入社時から Pharms のデザイナーとして各種デザインの業務を行なう。 古川さん 2022 年に新卒入社。現在はプロダクトの改善を幅広く担当している。 Pharms というプロダクトについて 山田 : まず「TGIF」について聞いていく前に、Pharms とはどのようなプロダクトなのか教えてもらってもよいですか? 新居 : 一言で言うと、薬局の業務効率化支援システムです。 CLINICS オンライン診療 で診察を受けた患者さんや、直接来院された患者さんからの処方箋などを受けて、Pharms を通して服薬指導や、会計、服薬フォローアップなどを一貫して行なえるシステムになっています。 新居さん 山田 : ユーザーについてですが、例えば、自分が開発を担当している医療介護求人サイトの ジョブメドレー では、お仕事をお探しの求職者の方々と、求人情報を掲載いただく医院・事業所の方々が使用しているシステムなのですが、Pharms はどのような方に使われるのでしょうか? 小田 : CLINICS アプリを通して間接的に患者さんが使っていますが、大きい割合を占めるのは顧客である薬局のスタッフさん(薬剤師など)ですね。 小田さん 山田 : やはり現場の薬剤師さんが使っているサービスという性格が強いんですね。 Pharms 開発チームの TGIF という試みについて TGIF を始める前の課題感 山田 : それでは、いよいよ Pharms 開発チームで行なっている TGIF について聞いていきたいと思います。元々、この言葉はどんな経緯で付いたんですか? 小田 : 元々は英語圏で言われている “Thank God It’s Friday” というのが語源で、そのまま金曜日の夕方に開催されるものだったのでこの名前を付けています。ちょっと昔の言葉で言うと「花金」ですね。 山田 : 「1 週間が終わりだー!」っていう開放感を表わしてるんですね。どういうきっかけでこの TGIF をやっていこうと思ったんでしょうか? 小田 : 一昨年の自分の入社前のタイミングで、Pharms 立ち上げ時のメンバーが別のプロジェクトに集中することとなり、チーム体制がガラッと変わる時期がありました。それ以降、チーム内の情報がサイロ化したり、チームとしての一体感がないという課題を感じていました。この課題を解決するために 雑多に何でも話せるコミュニケーションの場 を作ろうというのが、きっかけでした。 新居 : もちろん、それまでもチームで話す機会が全く無かったわけではなく、開発計画をクォーター単位で作っているのでそのタイミングでディスカッションする場を設けていました。でも もうちょっとコミュニケーションの頻度を増やしたらより良くなりそうだよね という話があり、毎週金曜日の TGIF が誕生しました。 山田 : そういう経緯だったんですね。それまでエンジニア・デザイナーが参加する定例のような会議体はどのようなものがあったんですか? 新居 : クォーター毎のディスカッション以外だと、週一で行なわれる「プロダクト定例」だけでした。こちらは開発メンバーだけではなく、事業部のメンバーも参加してプロダクト開発全般についての共有を目的にしたものです。それ以外で定例の会議体はなく、デイリーで行われるような「朝会・夕会」も Pharms 開発チームでは行なっていませんでした。 山田 : チーム体制の変更後、新しいメンバーの加入もあり、今までのコミュニケーション量では足りなくなってしまったんですね。 新居 : はい。チームの状況が変わって行く中で、 もっと生産性を上げていきたいという思いがありました 。チームが小規模だった頃は隣の席の同僚エンジニアに相談しつつ開発をドンドン前に進めていくことができたんですが、人数が増えてくるとそうしたコミュニケーションも希薄になります。具体的には、開発をしていて設計方針・実装方針などがズレることは多々あると思うんですが、それをさっと会話して軌道修正し、開発を推進していくというのが難しくなってきていました。 TGIF の実際 山田 : では、具体的に TGIF でどのような事を話し合われるんでしょうか? 新居 : まずは今週の振り返りから始まります。前週の TGIF で Try に対して個々がどう動いていくかを決めているので、それに対して振り返りをしていきます。各自コメントを添えて何%達成したのか話していきます。 小田 : その後、今週のチーム全体の動きに対して KPT を実施します。チーム全体として良かった動きや課題などを挙げていき、内容を深ぼったり課題解決に向けて意見を交換したりしています。また、以前はチームの動きとは関係ない個人の話などが挙がってくることが多かったのですが、 チーム全体での生産性の向上ということを目的として考えた場合、それと関連のない個人の話をしても寄与しない ので、チーム全体に関わることをメインにするよう改善し今の形になってきました。 KPT が終わったら、隔週でチーム目標と Problem で挙がったことなども踏まえて各自の来週の Try を決めます。 新居 : 最後に、ちょっとしたことでも良いのでチームに共有したい「気になること」を話していきます。ここは開発に関係する・しないに関わらない話題を出しています。例えば 、Pharms のプロダクト自体や会社の組織的なこと、プルリクエストのレビューのお願いなど雑多な話題ですね。 その中で「ライブラリアップデート」についての課題や進捗などの共有もします。単純にバージョンアップして済むというものであれば良いですが、アップデートの結果、広範囲に影響があったりだとか、そもそもエラーなどで上手くアップデートできないなどがあるので、そうした困り事の共有と解決を目的としています。 山田 : 先ほどチーム目標を隔週で決めるというお話でしたが、こちらはどういう風に決めていくんですか? 小田 : KPT の内容を踏まえて、基本的には Our Essentials (以下 OE) に照らし合わせて決めていきます。そのチーム目標に対して、個人の目標をブレイクダウンして決める形にしています。 固くなりすぎない会を目指して 山田 : なるほど。 OE に合わせて目標設定していくというのは、分かりやすくて良い ですね。話は少し逸れますが、この TGIF では毎回お菓子を持ち寄って行なっているということですが、どうしてなのでしょうか? 小田 : TGIF を行なっている内に、メンバー間での意思疎通の少なさによって、チームの生産性が上がらないという課題は解決していきました。しかし、続けている内に段々と会自体が固い雰囲気になってきて、かっちりとした振り返りの場という感じになってしまいました。元々 TGIF の在り方としてはどういうものだっけという原点に立ち返ると、やはり 「気軽にコミュニケーションができる場」という点が重要 だと再認識しまして、そこからは場の雰囲気を柔らげるためにお菓子を持ち寄り始めました。 お菓子を囲んでの TGIF 山田 : 確かに固すぎると、ちょっと会の名前とそぐわない感じになってしまいますね。 TGIF 運用の秘訣 山田 : 他に TGIF を運用する上での工夫などはありますか? 新倉 : 会の性質上、話が盛り上がって時間がオーバーしがちなので「 TGIF で話す内容はチームの成長につながることに限定する」など、 効率的かつ意味のある運用 になるよう進め方の見直しと実践は日々試行錯誤しています。 新倉さん 古川 : 自分は去年の新卒入社ですので、「言ってもしょうがないかな?」というような事もあるのですが、あえてそういった事を考えずに積極的に発言するようにしています。 実際に発言したために他の方との共有もできますし、学びになるような議論に発展することも多い ので、言って良かったという場合が多いです。 古川さん 兒玉 : ささいな事でも、誰かが チームの為に動いた事に対しては積極的にお礼を言っています 。誰かがちゃんと自分のやったことを見てくれていると認識できることが、チームの雰囲気を良くし、チーム運営の好循環に繋がっているかと思います。 兒玉さん 新居 : ちゃんとチーム全員にとって意味がある時間にするということは意識しています。仮にあまり生産的な時間ではなくなったら TGIF を止めてしまっても良いと思っています。その為には 「チームのために話しをした方がよいことは必ず話す」「本音ベースで建設的に議論する」「出てきた課題は必ずクリアにして次週以降に解決を目指す」 という事を意識しています。 こうして話すとある意味当たり前の事ばかりなんですが、参加者全員がこうした点を共通認識として持っていないと、単に KPT を発表するだけの場となり会自体が形骸化していってしまいます。ですので、きちんとこの共通認識を持って臨めるようにするというのを大切にしています。 小田 : また、TGIF で話が盛り上がるのは良いことなのですが、週に一回のこの場を待たずにもっと会話を活性化する取り組みとして 週の頭に Slack のプロダクトチャンネルにテディベアがリマインダーで現れる ので、そのスレッドで話しをするようにしています。 山田 : へえー!何でテディベアなんですか? 新居 : プログラミングで自分で解決できない問題が出てきたときに、テディベアのぬいぐるみに話しかけると問題が整理されて解決できるというテディベア効果から取っています。 テディベアの リマインダーにコメントが付いている様子 山田 : なるほど、それでテディベアが出てくるんですね。 小田 : TGIF を待つまでの時間がムダだよねっていう話もあって先程の効率化の話も含めて行なっている施策です。 新倉 : これまで TGIF やテディベアが無い時は下手をすると一週間誰とも会話をしていないということもあったんですが、この取り組みをしてから 本当にコミュニケーションが増えました 。 TGIF の利点とこれからについて 山田 : ありがとうございます。既に話にも出ていますが、TGIF をやって良かった点はどういったものがあるか改めて聞かせてください。 小田 : やっぱり格段に「チームでの開発」をしているという意識が高まったことが良いことでした。それまではやはりコミュニケーションなどが不足していたのですが、今は TGIF などのおかげですぐに相談しようという感じにもなって良いチームになってきたなという感触があります。 さらにチームを良くする土壌 にするようにしていきたいです。 新居 : 1 つ目は以前よりお互いのことを知れるようになって心理的安全性が高まったこと、2 つ目はチームとしての方針や共通認識を合わせる場として機能していること、3 つ目は毎週前週からの課題を改善でき、チームや個人の成長を感じられるようになったことです。 全体的にチーム運営にプラス になっていると感じています。 新倉 : 私は TGIF が始まってから半年後にジョインしました。元々はエンジニアの皆さんだけのものだと思ってちょっと尻込みしていたところがあったのですが、蓋を開けてみるとプロダクトやチーム自体についての話が多く、 デザイナーとしても非常に学び があるものでした。全体的にはクォーター毎に目標を決めてそれに向けて動くのですが、その目標までの道のりを埋めるという意味でも、TGIF があって良かったです。 新居 : 時にはエンジニアだけにしかわからない話題などもあったりもしますが、大部分は新倉さんが話したようにプロダクトやチームの改善をいかに行なうかという話題になっているので、そこでデザイナー視点での話が聞けてエンジニアとしても大変助かります。 古川 : 自分は入ってからずっと TGIF があるのである種当たり前の存在なのですが…。チームメンバーの人となりなどが分かるのは大変助かります。また、自分とは全然違う経歴を持っているエンジニアの方などは同じ話題でも、 自分とは全然違う視点を持って話をしていたりするので、すごく勉強になっています 。チームの課題が話し合われるので、以前は自分のタスクが終わったら、「次は何をしよう」という感じになっていたんですが、TGIF のおかげで「チームがここで困っているなら自分がやろう」と客観的に分かるようになっています。また、OE は半年毎の自己評価の際にも体現できているかどうかチェックすることになるので、TGIF での目標達成度がそれに役立ちます。 兒玉 : 一番は心理的安全性が高くなったことです。エンジニアとしての働き方とセールスをしていた時の働き方が全然違うところが多かったので、1on1 で新居さんとそういった差異について聞くという機会しかなかったんですが、今は TGIF があるのでここで皆さんに聞けるようになって様々な視点でのアドバイスを頂けるようになりました。また、エンジニアとしてお勧めの書籍や勉強法など普通に聞こうとすると、 あまり機会がないような部分も気軽に聞けるようになったので、勉強の効率も良くなりました 。 山田 : みなさん、ありがとうございます。それぞれの立場ですごく有用な会なのが分かりますね。 TGIF のこれからについてもお話ください。 新居 : 先ほども少し話しましたが、形骸化してしまい惰性で行なってしまうということになるのが本当に怖いと思っています。運用することが目的になってしまい、本来の目的が達成できないと本末転倒ですし。今もやっていることですが、ちゃんとメンバー構成や状況に応じて運用をアップデートしていくということが一番大切だと思っています。 小田 : 今までエンジニアだけの会から TGIF は始まっていますが、デザイナーの新倉さんが参加してくれたのを始めとして、昨年末から企画職の方も入ってくれるようになったので、裾野の広がりを感じています。こういった立場が別々のメンバーが入ってもスムーズに目的が達成できるように TGIF のアップデートを続けていきたいですね。 新倉 : 色々な役割の人がフラットに発言できる場があるおかげで、プロダクトについて総合的な視点を持つことができたと感じています。実際に自分達が試行錯誤して出した機能が「お客様にこんな風に使われて、こんな感想をもらえた」などすぐに共有してもらえるので、開発のモチベーションにもつながる良いサイクルになっています。 Pharms 開発チームでは、どんな人と一緒に働きたいと考えている? 山田 : ありがとうございます。最後に Pharms の開発チームには、どんな方がジョインされると嬉しいですか? 小田 : いつもチーム内で話をしているのが「 顧客ファースト 」という言葉です。これは Pharms に限った話ではなくメドレー全体の話でもあるのですが、顧客や患者などサービスを使っている人達に対して提供できる価値は何かということをきちんと意識して開発ができる方です。技術や数字だけを見るわけじゃなく、その先の顧客をちゃんと考えて開発ができる人ですね。 新居 : メドレーの事業やバリューに共感があるのは大前提です。また、小田さんが話されたことに加えて、プロダクトをより良くしようと考えてるメンバーと共に、自分はこうしたいという意志も持ちつつ、ときには建設的に議論もしながらプロダクト開発を推進できる人と一緒に仕事ができると嬉しいです。大変なことも勿論ありますが、それを楽しみ、周囲を巻き込みながら技術・技術以外の部分も含め前に進めていける人が良いなと思います。 山田 : TGIF も顧客ファーストの理念から生まれたものということになりますよね。ありがとうございました! さいごに Pharms 開発チームが実践している「TGIF」というチームビルディングの方法についてインタビューしてきましたが、いかがだったでしょうか? 実際に TGIF をしている様子などを見ると、和やかな雰囲気ではありますが、全員真剣にプロダクトについて議論をしている姿が印象的でした。 こんなチームで調剤薬局や患者のためのプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回は Pharms 開発チームがチームビルディングの一環として行なっている「TGIF」という施策について参加している皆さんに、どのような効果があるのかなどを聞いていこうと思います。 インタビュイー紹介 新居さん 2016 年入社。ソーシャルゲーム開発会社を経て、メドレーへ入社。入社当初はジョブメドレーのプロダクト開発などを担当していたが、2020 年から Pharms で中規模施策の開発を中心に業務を行なう。 小田さん 2021 年入社。大手メーカーで病院や調剤薬局向けの IT システムの開発業務を経て、メドレー入社。Pharms では中規模施策の開発を中心に業務を行なう。 兒玉さん 2018 年入社。2021 年 4 月まで CLINICS オンライン診療のセールス部に所属。その後エンジニアに社内転職し、Pharms の開発に携わる。現在はサーバサイドの開発を中心に担当している。 新倉さん 2021 年入社。不動産テック企業でのデザイン業務を経て、メドレーへ入社。入社時から Pharms のデザイナーとして各種デザインの業務を行なう。 古川さん 2022 年に新卒入社。現在はプロダクトの改善を幅広く担当している。 Pharms というプロダクトについて 山田 : まず「TGIF」について聞いていく前に、Pharms とはどのようなプロダクトなのか教えてもらってもよいですか? 新居 : 一言で言うと、薬局の業務効率化支援システムです。 CLINICS オンライン診療 で診察を受けた患者さんや、直接来院された患者さんからの処方箋などを受けて、Pharms を通して服薬指導や、会計、服薬フォローアップなどを一貫して行なえるシステムになっています。 新居さん 山田 : ユーザーについてですが、例えば、自分が開発を担当している医療介護求人サイトの ジョブメドレー では、お仕事をお探しの求職者の方々と、求人情報を掲載いただく医院・事業所の方々が使用しているシステムなのですが、Pharms はどのような方に使われるのでしょうか? 小田 : CLINICS アプリを通して間接的に患者さんが使っていますが、大きい割合を占めるのは顧客である薬局のスタッフさん(薬剤師など)ですね。 小田さん 山田 : やはり現場の薬剤師さんが使っているサービスという性格が強いんですね。 Pharms 開発チームの TGIF という試みについて TGIF を始める前の課題感 山田 : それでは、いよいよ Pharms 開発チームで行なっている TGIF について聞いていきたいと思います。元々、この言葉はどんな経緯で付いたんですか? 小田 : 元々は英語圏で言われている “Thank God It’s Friday” というのが語源で、そのまま金曜日の夕方に開催されるものだったのでこの名前を付けています。ちょっと昔の言葉で言うと「花金」ですね。 山田 : 「1 週間が終わりだー!」っていう開放感を表わしてるんですね。どういうきっかけでこの TGIF をやっていこうと思ったんでしょうか? 小田 : 一昨年の自分の入社前のタイミングで、Pharms 立ち上げ時のメンバーが別のプロジェクトに集中することとなり、チーム体制がガラッと変わる時期がありました。それ以降、チーム内の情報がサイロ化したり、チームとしての一体感がないという課題を感じていました。この課題を解決するために 雑多に何でも話せるコミュニケーションの場 を作ろうというのが、きっかけでした。 新居 : もちろん、それまでもチームで話す機会が全く無かったわけではなく、開発計画をクォーター単位で作っているのでそのタイミングでディスカッションする場を設けていました。でも もうちょっとコミュニケーションの頻度を増やしたらより良くなりそうだよね という話があり、毎週金曜日の TGIF が誕生しました。 山田 : そういう経緯だったんですね。それまでエンジニア・デザイナーが参加する定例のような会議体はどのようなものがあったんですか? 新居 : クォーター毎のディスカッション以外だと、週一で行なわれる「プロダクト定例」だけでした。こちらは開発メンバーだけではなく、事業部のメンバーも参加してプロダクト開発全般についての共有を目的にしたものです。それ以外で定例の会議体はなく、デイリーで行われるような「朝会・夕会」も Pharms 開発チームでは行なっていませんでした。 山田 : チーム体制の変更後、新しいメンバーの加入もあり、今までのコミュニケーション量では足りなくなってしまったんですね。 新居 : はい。チームの状況が変わって行く中で、 もっと生産性を上げていきたいという思いがありました 。チームが小規模だった頃は隣の席の同僚エンジニアに相談しつつ開発をドンドン前に進めていくことができたんですが、人数が増えてくるとそうしたコミュニケーションも希薄になります。具体的には、開発をしていて設計方針・実装方針などがズレることは多々あると思うんですが、それをさっと会話して軌道修正し、開発を推進していくというのが難しくなってきていました。 TGIF の実際 山田 : では、具体的に TGIF でどのような事を話し合われるんでしょうか? 新居 : まずは今週の振り返りから始まります。前週の TGIF で Try に対して個々がどう動いていくかを決めているので、それに対して振り返りをしていきます。各自コメントを添えて何%達成したのか話していきます。 小田 : その後、今週のチーム全体の動きに対して KPT を実施します。チーム全体として良かった動きや課題などを挙げていき、内容を深ぼったり課題解決に向けて意見を交換したりしています。また、以前はチームの動きとは関係ない個人の話などが挙がってくることが多かったのですが、 チーム全体での生産性の向上ということを目的として考えた場合、それと関連のない個人の話をしても寄与しない ので、チーム全体に関わることをメインにするよう改善し今の形になってきました。 KPT が終わったら、隔週でチーム目標と Problem で挙がったことなども踏まえて各自の来週の Try を決めます。 新居 : 最後に、ちょっとしたことでも良いのでチームに共有したい「気になること」を話していきます。ここは開発に関係する・しないに関わらない話題を出しています。例えば 、Pharms のプロダクト自体や会社の組織的なこと、プルリクエストのレビューのお願いなど雑多な話題ですね。 その中で「ライブラリアップデート」についての課題や進捗などの共有もします。単純にバージョンアップして済むというものであれば良いですが、アップデートの結果、広範囲に影響があったりだとか、そもそもエラーなどで上手くアップデートできないなどがあるので、そうした困り事の共有と解決を目的としています。 山田 : 先ほどチーム目標を隔週で決めるというお話でしたが、こちらはどういう風に決めていくんですか? 小田 : KPT の内容を踏まえて、基本的には Our Essentials (以下 OE) に照らし合わせて決めていきます。そのチーム目標に対して、個人の目標をブレイクダウンして決める形にしています。 固くなりすぎない会を目指して 山田 : なるほど。 OE に合わせて目標設定していくというのは、分かりやすくて良い ですね。話は少し逸れますが、この TGIF では毎回お菓子を持ち寄って行なっているということですが、どうしてなのでしょうか? 小田 : TGIF を行なっている内に、メンバー間での意思疎通の少なさによって、チームの生産性が上がらないという課題は解決していきました。しかし、続けている内に段々と会自体が固い雰囲気になってきて、かっちりとした振り返りの場という感じになってしまいました。元々 TGIF の在り方としてはどういうものだっけという原点に立ち返ると、やはり 「気軽にコミュニケーションができる場」という点が重要 だと再認識しまして、そこからは場の雰囲気を柔らげるためにお菓子を持ち寄り始めました。 お菓子を囲んでの TGIF 山田 : 確かに固すぎると、ちょっと会の名前とそぐわない感じになってしまいますね。 TGIF 運用の秘訣 山田 : 他に TGIF を運用する上での工夫などはありますか? 新倉 : 会の性質上、話が盛り上がって時間がオーバーしがちなので「 TGIF で話す内容はチームの成長につながることに限定する」など、 効率的かつ意味のある運用 になるよう進め方の見直しと実践は日々試行錯誤しています。 新倉さん 古川 : 自分は去年の新卒入社ですので、「言ってもしょうがないかな?」というような事もあるのですが、あえてそういった事を考えずに積極的に発言するようにしています。 実際に発言したために他の方との共有もできますし、学びになるような議論に発展することも多い ので、言って良かったという場合が多いです。 古川さん 兒玉 : ささいな事でも、誰かが チームの為に動いた事に対しては積極的にお礼を言っています 。誰かがちゃんと自分のやったことを見てくれていると認識できることが、チームの雰囲気を良くし、チーム運営の好循環に繋がっているかと思います。 兒玉さん 新居 : ちゃんとチーム全員にとって意味がある時間にするということは意識しています。仮にあまり生産的な時間ではなくなったら TGIF を止めてしまっても良いと思っています。その為には 「チームのために話しをした方がよいことは必ず話す」「本音ベースで建設的に議論する」「出てきた課題は必ずクリアにして次週以降に解決を目指す」 という事を意識しています。 こうして話すとある意味当たり前の事ばかりなんですが、参加者全員がこうした点を共通認識として持っていないと、単に KPT を発表するだけの場となり会自体が形骸化していってしまいます。ですので、きちんとこの共通認識を持って臨めるようにするというのを大切にしています。 小田 : また、TGIF で話が盛り上がるのは良いことなのですが、週に一回のこの場を待たずにもっと会話を活性化する取り組みとして 週の頭に Slack のプロダクトチャンネルにテディベアがリマインダーで現れる ので、そのスレッドで話しをするようにしています。 山田 : へえー!何でテディベアなんですか? 新居 : プログラミングで自分で解決できない問題が出てきたときに、テディベアのぬいぐるみに話しかけると問題が整理されて解決できるというテディベア効果から取っています。 テディベアの リマインダーにコメントが付いている様子 山田 : なるほど、それでテディベアが出てくるんですね。 小田 : TGIF を待つまでの時間がムダだよねっていう話もあって先程の効率化の話も含めて行なっている施策です。 新倉 : これまで TGIF やテディベアが無い時は下手をすると一週間誰とも会話をしていないということもあったんですが、この取り組みをしてから 本当にコミュニケーションが増えました 。 TGIF の利点とこれからについて 山田 : ありがとうございます。既に話にも出ていますが、TGIF をやって良かった点はどういったものがあるか改めて聞かせてください。 小田 : やっぱり格段に「チームでの開発」をしているという意識が高まったことが良いことでした。それまではやはりコミュニケーションなどが不足していたのですが、今は TGIF などのおかげですぐに相談しようという感じにもなって良いチームになってきたなという感触があります。 さらにチームを良くする土壌 にするようにしていきたいです。 新居 : 1 つ目は以前よりお互いのことを知れるようになって心理的安全性が高まったこと、2 つ目はチームとしての方針や共通認識を合わせる場として機能していること、3 つ目は毎週前週からの課題を改善でき、チームや個人の成長を感じられるようになったことです。 全体的にチーム運営にプラス になっていると感じています。 新倉 : 私は TGIF が始まってから半年後にジョインしました。元々はエンジニアの皆さんだけのものだと思ってちょっと尻込みしていたところがあったのですが、蓋を開けてみるとプロダクトやチーム自体についての話が多く、 デザイナーとしても非常に学び があるものでした。全体的にはクォーター毎に目標を決めてそれに向けて動くのですが、その目標までの道のりを埋めるという意味でも、TGIF があって良かったです。 新居 : 時にはエンジニアだけにしかわからない話題などもあったりもしますが、大部分は新倉さんが話したようにプロダクトやチームの改善をいかに行なうかという話題になっているので、そこでデザイナー視点での話が聞けてエンジニアとしても大変助かります。 古川 : 自分は入ってからずっと TGIF があるのである種当たり前の存在なのですが…。チームメンバーの人となりなどが分かるのは大変助かります。また、自分とは全然違う経歴を持っているエンジニアの方などは同じ話題でも、 自分とは全然違う視点を持って話をしていたりするので、すごく勉強になっています 。チームの課題が話し合われるので、以前は自分のタスクが終わったら、「次は何をしよう」という感じになっていたんですが、TGIF のおかげで「チームがここで困っているなら自分がやろう」と客観的に分かるようになっています。また、OE は半年毎の自己評価の際にも体現できているかどうかチェックすることになるので、TGIF での目標達成度がそれに役立ちます。 兒玉 : 一番は心理的安全性が高くなったことです。エンジニアとしての働き方とセールスをしていた時の働き方が全然違うところが多かったので、1on1 で新居さんとそういった差異について聞くという機会しかなかったんですが、今は TGIF があるのでここで皆さんに聞けるようになって様々な視点でのアドバイスを頂けるようになりました。また、エンジニアとしてお勧めの書籍や勉強法など普通に聞こうとすると、 あまり機会がないような部分も気軽に聞けるようになったので、勉強の効率も良くなりました 。 山田 : みなさん、ありがとうございます。それぞれの立場ですごく有用な会なのが分かりますね。 TGIF のこれからについてもお話ください。 新居 : 先ほども少し話しましたが、形骸化してしまい惰性で行なってしまうということになるのが本当に怖いと思っています。運用することが目的になってしまい、本来の目的が達成できないと本末転倒ですし。今もやっていることですが、ちゃんとメンバー構成や状況に応じて運用をアップデートしていくということが一番大切だと思っています。 小田 : 今までエンジニアだけの会から TGIF は始まっていますが、デザイナーの新倉さんが参加してくれたのを始めとして、昨年末から企画職の方も入ってくれるようになったので、裾野の広がりを感じています。こういった立場が別々のメンバーが入ってもスムーズに目的が達成できるように TGIF のアップデートを続けていきたいですね。 新倉 : 色々な役割の人がフラットに発言できる場があるおかげで、プロダクトについて総合的な視点を持つことができたと感じています。実際に自分達が試行錯誤して出した機能が「お客様にこんな風に使われて、こんな感想をもらえた」などすぐに共有してもらえるので、開発のモチベーションにもつながる良いサイクルになっています。 Pharms 開発チームでは、どんな人と一緒に働きたいと考えている? 山田 : ありがとうございます。最後に Pharms の開発チームには、どんな方がジョインされると嬉しいですか? 小田 : いつもチーム内で話をしているのが「 顧客ファースト 」という言葉です。これは Pharms に限った話ではなくメドレー全体の話でもあるのですが、顧客や患者などサービスを使っている人達に対して提供できる価値は何かということをきちんと意識して開発ができる方です。技術や数字だけを見るわけじゃなく、その先の顧客をちゃんと考えて開発ができる人ですね。 新居 : メドレーの事業やバリューに共感があるのは大前提です。また、小田さんが話されたことに加えて、プロダクトをより良くしようと考えてるメンバーと共に、自分はこうしたいという意志も持ちつつ、ときには建設的に議論もしながらプロダクト開発を推進できる人と一緒に仕事ができると嬉しいです。大変なことも勿論ありますが、それを楽しみ、周囲を巻き込みながら技術・技術以外の部分も含め前に進めていける人が良いなと思います。 山田 : TGIF も顧客ファーストの理念から生まれたものということになりますよね。ありがとうございました! さいごに Pharms 開発チームが実践している「TGIF」というチームビルディングの方法についてインタビューしてきましたが、いかがだったでしょうか? 実際に TGIF をしている様子などを見ると、和やかな雰囲気ではありますが、全員真剣にプロダクトについて議論をしている姿が印象的でした。 こんなチームで調剤薬局や患者のためのプロダクトを作っていきたいと思う方は、ぜひお気軽にお話をしましょう! https://www.medley.jp/jobs/