TECH PLAY

株式会社メドレー

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

1359

はじめに みなさん、こんにちは。エンジニアの新居です。 今回のインタビューは前回の CTO へのインタビュー に続いて、メドレーの開発組織についてご紹介していきたいと思います。 メドレーでは開発組織をリードするロールとして、CTO の他、「グループマネージャ」(以下、GM)という役職があります。今回は医療プラットフォーム(以下、医療 PF)における開発グループの GM を担う二人に、具体的にどのような役割なのか聞いていきます。 インタビュイー紹介 今回の紹介はインタビュー中で前職からメドレー入社の話を聞いているので、シンプルにお送りします。 平山さん 医療 PF 第一本部 プロダクト開発室 第四開発グループ GM。現在は CLINICS 基幹システムチームのマネジメントを担当。 岡村さん 医療 PF 第一本部 プロダクト開発室 第三開発グループ GM。現在はレセコン(電子カルテの機能のうち主に会計など診療報酬の計算をするソフトウェア)新規開発チームのマネジメントを担当。 左から岡村さん、平山さん、筆者 前職までの経歴とメドレー入社の理由について 二人の経験について 新居 : 早速始めていきますが、まずはお二人の経歴を教えていただきたいと思います。 平山 : メドレーは 2 社目の会社となります。前職は SIer で 14~5 年くらい受託開発や客先常駐して開発をしていました。SIer というと場合によっては二次請け・三次請けの開発をしているところもあるんですが、自分は一次請けとして直接顧客と関わる形で業務を行っていました。 顧客のサービスをステークホルダーと提案・折衝しながら、技術選定・要件定義から開発、リリースまで行い、そこからの保守運用といった部分までの一連の流れを様々な業態の顧客と関わらせて頂きました。 新居 : ありがとうございます。かなり自社開発と同じ感覚の開発を長年されてきたんですね。それでは、岡村さんお願いします。 岡村 : 新卒で外資系の IT コンサルティング会社で IT コンサルタントとしてキャリアを始めました。その中でも特に「情報系」と呼ばれるデータアナリティクスや BI (Business Intelligence) などを取り扱う部門で業務をしていました。領域的には金融・製薬・省庁など堅めの領域を主に担当していたので、いわゆる Web アプリケーションを作るというよりは SQL をガリガリ書いてデータ分析を基に業務改善していく…という感じの業務が多かったです。 2 社目はスタートアップ企業の立ち上げフェイズにジョインしました。その会社はいわゆる Insure Tech と呼ばれる保険業界 x テクノロジー領域のサービスを作っている会社でした。そこで、その会社でのプロダクトマネージャ業務と、運転資金を確保するために大手保険会社向けのコンサルタント業務に半々で従事していました。 その後にメドレーにジョインするという経歴です。 岡村さん メドレー入社の経緯について 新居 : ありがとうございます、お二人はメドレーに入社してどのくらい経ちますっけ? 平山 : 私は 3 年半ですね。 岡村 : 私は 4 年経ちました。 新居 : もうそんなに経つんですね。みなさんの前職を転職しようと思った動機とはどんなものだったんですか? 平山 : 前職でプレイングマネージャとして業務をしてきましたが、キャリアを重ねてくると徐々にマネジメントの割合が増えてきます。それ自体は自然なことではあるのですが、前職の延長線上でマネジメントに比重を置くことに対して、危機感を感じていたんです。手を動かす部分を含めて、エンジニアとして良いキャリアを積むには別の環境に身を置く必要があると思い、転職を決意しました。 転職に際しては、自分が 身近に感じるサービスで特に自分の家族が関わるような領域が良いと考えており、医療はそうした意味でぴったりな領域 でした。これらの条件で転職エージェントを通じてメドレーに辿りつきました。 平山さん 岡村 : 先程も少し話しましたが、前職は自社プロダクトと他社向けコンサルティングを並行して運営していた会社でした。しかし、途中から経営方針が変わってコンサルティング業務をメインに据えるようになったんです。自分としては、自社プロダクトを作っている点を大事に考えていたため、転職を考えはじめました。 会社選びの軸としては、もちろん自社サービスが事業の主軸である会社というのが一番でした。他の条件としては「上場一歩手前」くらいのフェイズの会社というものがありました。これは、そのくらいのフェイズの会社であれば今までやっていた事業をいきなりピボットするという可能性が限りなく低いのではないかと考えたからです。そんな感じで会社を探していて、LinkedIn の広告経由でメドレーにアプローチして入社しました。 新居 : 岡村さんは次の会社の事業領域は、意識していたんでしょうか? 岡村 : 医療領域を名指しで考えていたわけではないです。しかし、いわゆる 昔ながらの慣習などが多く残るような業界や領域を、自分達が手がけたサービスで変えていくというのが良い という考えでした。これは前職でも考えていたことだったので、引き続き志向していました。 メドレー入社後にどのような仕事をしていたのか 新居 : ありがとうございます。そんな経緯でメドレーに入社したお二人ですが、入社後はどのような業務をされていたんでしょうか。 平山 : 私は入社後から一貫して CLINICS カルテを担当しています。入社後は医療に関する業務知識を身につけながら、周辺業務から徐々に関わっていきシステム面のキャッチアップをしていきました。一番最初に携わったプロジェクトとしては、CLINICS カルテにおける診察業務のコントロールを担う「診療ステータス」の拡張を行うという、カルテ全体に影響のある大きな改修に携わりました。それ以降も、小さいタスクを捌きつつ、中・大規模な施策に携わってきた感じです。 そこから医療 PF 横断のプロジェクトにも関わっていくようになりました。CLINICS というプロダクトは患者が使用するアプリを始め、調剤・歯科のサービスも提供しています。 医療 PF 全体の体験設計を踏まえて、周辺プロダクトとも積極的に関わっていく必要がある という点は特徴的だと思います。 岡村 : 私は入社当時は「社長室コンサル」という役割で入社し、この部署は M&A などをメインに担当していた部署だったんです。そのため最初にアサインされたのが NaCl メディカル社(現在はメドレー本体に統合)の業務面での PMI でした。これと平行して、現在も引き続き携わっている新しいレセコンを作るプロジェクトのプロダクトマネージャを担当してきました。 新規レセコンは CLINICS カルテと連携しているプロダクトなので、平山さんが率いるチームと連携 しながら、このプロダクトを作っている開発チームを率いています。 GM としてのミッションと就任してから変わったこと・変わらないこと 新居 : 話を聞いていると、お二人とも最初の仕事を順調にスケールアップしていったという形で現在の業務に繋っているんですね。さて、ここからは GM に就任してからの自身の業務がどのように変化してきたのかという点を聞きたいのですが、この点どうでしょうか。 平山 : ミッションはやはり変化しました。GM としてのミッションで一番求められるものは「 プロダクトを前進させる 」という部分と考えています。自身の管掌組織は当然ですが、組織を横断する形の取り組みも必要になります。 「ピープルマネジメント」という点も GM としての変化の1つかなと思います。とはいえ、先ほどの話と通じる部分はあり**「プロダクトに軸足を置いて目線を合わせていくこと」が基本**となります。また、組織上の持続可能性という意味においても「任せていく」というところは大事にしています。メンバーに優秀な方が多いので助けられている部分は多いと感じます。 エンジニアリングという意味では、手を動かす量は減りました。手の動かし方という意味では入社当初から横の動きを見るようにはしていたので GM になって変わったという部分は無いですね。 岡村 : レセコン新規開発プロジェクトでの GM のミッションとして、「プロジェクトの完遂」、「チームメンバーのピープルマネジメント」、「Tech Studio MATSUE(島根県にあるメドレーの拠点。レセコン新規開発プロジェクトの開発をしている)の管理」がメインになります。 GM に就任したタイミングは NaCl メディカル社がメドレー本体に吸収合併されたというタイミングでした。就任前はグループ会社ということもあり、開発業務を受発注するという動きに近い感じで協業してきました。GM になってからは、同じ会社になったのでより近い距離でチーム開発をするようになったり、メンバーのピープルマネジメントの割合が多くなるという変化がありました。 担当領域としてはレセコンのプロダクトマネジメントをしているという部分は変わらない部分です。また平山さんがおっしゃっていたように、いつまでも自分が上にいて指揮するという状態を良しとするのではなく、 メンバーの人をどんどんと上のロールに引き上げていくというのが GM の役割の 1 つ かと思うので、そういう意識でメンバーのマネジメントをしています。 エンジニアリングとマネジメントの比率はプロダクトのフェイズによって変わってくるし、変わるべきかなと思っています。現在は 8 対 2 でマネジメントの比率が多くなっています。一方でプロダクトの初期でどんどんと開発をしていかないといけないという場合には自分も含めて人手をかけて開発していくようにします。 新居 : お話をうかがうと、やはり GM 前よりもマネジメントの比率が多くなっているのではないかと思うのですが、葛藤などはありましたか。 平山 : 元々の自分のスタンスとして「自分も駒の一つ」としてプロダクト開発を推進していくという思考があります。GM の話が来たときにも、やれるかなという不安感はありましたが、今の場面においては自身がやった方が良いと感じて受けました。 岡村 : 私は出自がエンジニアではないというのもあって、葛藤のようなものも平山さんとは違って、特にありませんでした。元々 NaCl メディカル社だった松江オフィスとのハブになるのは、自分しかいないと思っていましたので躊躇のようなものも全く無かったです。 GM で苦労したポイント・どう克服していたか 新居 : ありがとうございます。では、GM になって苦労したとか大変だったというところってどんなところがありましたか。 平山 : 自分は口下手な方なんで、以前より喋る機会が増えたのが困ったところですね(笑) 新居 : 平山さんに口下手な印象は全然ありませんが(笑) 平山 : 他には、これはどちらかというと PdM 属性の仕事にも関わってくるんですが、開発計画の策定は大変です(メドレーでは 3 ヶ月ごとに開発計画を立てている)。 大規模な施策の実施と並行して優先度の高いタスクの差し込みを踏まえて、優先度の再設計や施策のフェージングや運用の検討したりしています。 自分で抱え込む必要はないですが、関係者と調整しつつも最終意思決定は自分になるので、様々な葛藤をしながら取り組んでいます 。 新居 : ピープルマネジメント面では何かありましたか。 平山 : 先程お話した「プロダクトに軸足を置いて目線を合わせていくこと」を大切にコミュニケーションを実施し、日々の業務を通してメンバー一人一人でシナジーが出る・良いキャリアが重ねられるようにしたいと考えていますが、先ほどの開発計画の難しさもここにあります。 新居 : ありがとうございます。では岡村さんはいかがでしょう。 岡村 : 平山さんに全く同感ですね(笑) 冗談は置いておいて、計画外の差し込みタスクなどが入ると、どうしてもチームの負荷が高くなってくるので、そこをコントロールしきれずに申し訳ないなと思うことがあります。 他には、メンバーのキャリア形成をどうするかという点が難しいと思います。 メンバーの志向に合わせての将来像を見据えて現在の業務に取り組んでもらいたい のですが、実際のタスクとそうしたキャリアの方向性が必ずしもぴったりと合うときばかりではないので、ここをどう揃えていくかという点にも苦労はしています。 新居 : なるほど、岡村さんの場合は元々として島根と東京という立地間でのマネジメントをされていたと思うんですが、リモートコミュニケーションが中心という環境での苦労は何かありましたか。 岡村 : 実はこれと言って無いんですよ。それでもちゃんと上手く機能している要因としては、2 つあります。1 つは島根の方に現地のメンバーを取りしきってくれるスキルを持ったメンバーが居てくれたことです。ここは今でも本当に助かっています。 また、当初から技術的な部分も含めて私もメンバーと一緒に勉強しながら、新しい取り組みをするというスタンスを持ってプロジェクトを推進できたことです。ですので、離れたところから命令する人のような感じではなく、 チームで一緒にプロジェクトを進める人という認識を持ってもらえた んじゃないかなと考えています。 もちろん、関係値を作れるまでは私が最初の頃は 1 ヶ月に 1~2 回程度島根に出張して、実際にメンバーと会ったりすることもしていましたが。 GM を担う上で、役に立った経験・知識 新居 : ここからは、お二人が今までの業務の中で積んできた経験・知識の内、現在 GM として活動する上で役に立っているものとして、どんなものがあるのか聞ければと思います。 平山 : そうですね、一番役に立っているなと感じているのは前職で経験した「 様々なステークホルダーとの主体性を持った折衝経験 」です。色々な会社や立場の方とコミュニケーションを取ることが多かったんですね。専門性もそれぞれ違う方達とお話して物事を進める経験ができたことは、今の GM という役割に本当に役立っていると思います。 岡村 : 私が役に立っていると感じる経験は、「 一回深く細かいところまで業務を見てみて理解をしてから、改めて俯瞰した視点で業務を見直してメンバーにお願いする 」という習慣です。この相反する方法をバランス良くできるようになっているというのが、良い点なのかなと思っています。また、深く見るというのは自分が興味を持っている分野でないと中々難しいので、何にでも興味を持って業務に取り組むという心持ちも必要になるかと考えています。 こうした形で業務を分解してメンバーにお願いできるところはしながら、GM としては適切に権限委譲していくというのに役立っています。 エンジニアのピープルマネジメントで意識していること 新居 : 今までのお話でも少し出ている話題になるのですが、GM としてエンジニアのマネジメントで意識しているポイントって、どんなことがありますか。 平山 : 自分達が作る プロダクトへの興味関心・解像度を高め、何を作るのか 。という点において各所コミュニケーションを含めて主体的に動くこと。そうした積み重ねの延長線上にあるエンジニアとしてのキャリア形成を意識しています。 この考え方に加えて「プロダクトの持続可能性を高める」というテーマで、エンジニア・デザイナー・ディレクター・ QA エンジニア・カスタマーサクセスといったプロダクトに関わる人達の専門性を踏まえたタスクの再配布・プロジェクト設計を行う。といった体制作りも推進しようとしています。 新居 : 「プロダクトの持続可能性を高める」というのは、すごく難しい試みですよね。 平山 : 確かにとても難しいなと思います。でも打ち手としては色々あるのではないかと考えていて。「属人性の排除」といった点は、分かりやすいのではないかなと思います。誰かに依存した状態は、組織変更や事業スケールの変化に弱くなりがちですよね。 大事なのは「人から組織への移行」「仕組み化」と捉えています。「できる人を増やす」については、大事だけど秘伝のタレが受け継がれるだけになってしまうので、持続可能性の観点では本質的ではないかなと。 一方で「仕組み化するための計画的な属人性」については推奨できると考えてます。新しいことをいきなり「組織に転換」や「人を増やしてローテする」だと、ナレッジが蓄積されづらく、次の一手が打ちづらくなるので。 新居 : そうした取り組みをするのに、例えばエンジニアだと作ってみたものがプロダクトの目指す方向性とズレが生じていたというような事態があると思うんですが、そうしたズレを事前に仕組みで抑えるような事をしているんですか。 平山 : きっちりとした仕組みなどは作ってはいないです。仕組み化というよりも、そうならないように メンバーから上がってきた疑問などに対する壁打ちなどは気軽にできる ようにしています。また、そういった相談を気軽にできるような自分自身のプロデュースといいますか、キャラ作りみたいなものも含めてメンバーが相談しにくいという空気を作らないようにはしていますね。 新居 : ありがとうございます。岡村さんはどんな意識をしていますか。 岡村 : やっぱり平山さんに全く同感ですね(笑) 付け加えると、ソフト面ですが メンバーであるエンジニアが楽しめる環境作り というところを意識しています。楽しさにも色々な種類があるとは思いますが、単にコーディングするだけではなく、やはりプロダクトそれ自体の提供価値というものをしっかりと共有して、プロダクトを作ること自体に楽しさを感じてもらう…というのは重要じゃないかなと思っています。 また楽しさの別側面ですが、新しいテクノロジーなどもどんどんと取り入れることができるような仕組み作りもしています。島根のメンバーは今回のプロジェクトで初めて Ruby を使うというメンバーも多かったので、ここで楽しさを感じられないと、こうしたテクノロジー自体を嫌いになってしまう恐れがあるなと思ったので。 そうした「楽しい仕事」にしていくと「時間をかけなきゃ」ではなく「時間をかけたい」という感じで取り組めるようになると思います。 GM という役割の醍醐味とは 新居 : それでは終盤になってきましたが、GM の「やりがい」や「醍醐味」はどういったものになりますか。 岡村 : 一番は「ピープルマネジメント」になるんじゃないかと考えています。最近、岸田首相の所信表明演説で聞いて感銘を受けたアフリカの諺ですが「早く行きたいなら 1 人で行け、遠くへ行きたいならみんなで行け」という言葉はピープルマネジメントの本質なんじゃないかなと思っています。 メンバーと一緒に遠くに行くために組織作り などをしていけるのが醍醐味だと感じています。 平山 : メンバーの 個人スキルを高めつつ「チーム」に還元してもらうための仕組み を作っていく。というのは、醍醐味と言えるのかなと。CTO インタビューで田中も言っていますが「チームバリュー」は大切だと考えています。 チームといっても、狭義のチームではなくプロダクトに関わる関係者全員を含めたチームという感じで意識しています。開発組織に閉じないバリューの発揮がメンバーそれぞれでできると心強いと思います。 メドレーで GM やリードエンジニアのロールに向いている方 新居 : 最後になるのですが、メドレーで GM やテックリードといったロールに向いているのは、どんな人だと思いますか。 平山 : 基本的に 物事を前に進めるための解像度を高く描ける人 かなと思っています。何を誰のためにというのを体現しつつ、関係者を引っぱっていける人ですね。メドレーの Our Essentials 全般必要だとは思いますが、その中でも「成果を出す」「自分をアップデート」「組織水準を高める」「革新と改善を主導」というところが必要になるかな。困難な状況でも結果を出しつつ、自分の行動も高めていき、結果組織・プロダクトを良くしていける…というのがメドレーのリードと呼ばれているエンジニアかと思います。 やり方としては、マイクロマネジメントではなく、基本となる考え方をちゃんと伝えてメンバーに学んでもらう…という、やり方ができる人が向いていると思います。 岡村 : 困難な状況であっても何とかして前に進めることができる人 でしょうか。無理矢理に進めるというわけではなく、状況の整理や色々な部署との調整などもしたりして、何とか結果という形にしていける能力がある人がリードとしてメンバーを引っぱっていける人だと思います。 もちろん、フェイズによってメンバーとの関わりなどはちゃんと調整していける柔軟さなども必要になると思います。 新居 : なるほど。本日は色々なお話をしていただきまして、ありがとうございました! さいごに メドレーの GM 二人に仕事について聞いていきました。 前回の CTO インタビューでも語られた開発組織の話ですが、より現場に近い GM という立場のメンバーからの話でまた違った雰囲気などを感じていただけたかと思います。 このような GM を「ぜひやりたい!」という方や「こんな上司の元で自分の力を発揮していきたい!」という方はぜひカジュアルにお話ができればと思います。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに みなさん、こんにちは。エンジニアの新居です。 今回のインタビューは前回の CTO へのインタビュー に続いて、メドレーの開発組織についてご紹介していきたいと思います。 メドレーでは開発組織をリードするロールとして、CTO の他、「グループマネージャ」(以下、GM)という役職があります。今回は医療プラットフォーム(以下、医療 PF)における開発グループの GM を担う二人に、具体的にどのような役割なのか聞いていきます。 インタビュイー紹介 今回の紹介はインタビュー中で前職からメドレー入社の話を聞いているので、シンプルにお送りします。 平山さん 医療 PF 第一本部 プロダクト開発室 第四開発グループ GM。現在は CLINICS 基幹システムチームのマネジメントを担当。 岡村さん 医療 PF 第一本部 プロダクト開発室 第三開発グループ GM。現在はレセコン(電子カルテの機能のうち主に会計など診療報酬の計算をするソフトウェア)新規開発チームのマネジメントを担当。 左から岡村さん、平山さん、筆者 前職までの経歴とメドレー入社の理由について 二人の経験について 新居 : 早速始めていきますが、まずはお二人の経歴を教えていただきたいと思います。 平山 : メドレーは 2 社目の会社となります。前職は SIer で 14~5 年くらい受託開発や客先常駐して開発をしていました。SIer というと場合によっては二次請け・三次請けの開発をしているところもあるんですが、自分は一次請けとして直接顧客と関わる形で業務を行っていました。 顧客のサービスをステークホルダーと提案・折衝しながら、技術選定・要件定義から開発、リリースまで行い、そこからの保守運用といった部分までの一連の流れを様々な業態の顧客と関わらせて頂きました。 新居 : ありがとうございます。かなり自社開発と同じ感覚の開発を長年されてきたんですね。それでは、岡村さんお願いします。 岡村 : 新卒で外資系の IT コンサルティング会社で IT コンサルタントとしてキャリアを始めました。その中でも特に「情報系」と呼ばれるデータアナリティクスや BI (Business Intelligence) などを取り扱う部門で業務をしていました。領域的には金融・製薬・省庁など堅めの領域を主に担当していたので、いわゆる Web アプリケーションを作るというよりは SQL をガリガリ書いてデータ分析を基に業務改善していく…という感じの業務が多かったです。 2 社目はスタートアップ企業の立ち上げフェイズにジョインしました。その会社はいわゆる Insure Tech と呼ばれる保険業界 x テクノロジー領域のサービスを作っている会社でした。そこで、その会社でのプロダクトマネージャ業務と、運転資金を確保するために大手保険会社向けのコンサルタント業務に半々で従事していました。 その後にメドレーにジョインするという経歴です。 岡村さん メドレー入社の経緯について 新居 : ありがとうございます、お二人はメドレーに入社してどのくらい経ちますっけ? 平山 : 私は 3 年半ですね。 岡村 : 私は 4 年経ちました。 新居 : もうそんなに経つんですね。みなさんの前職を転職しようと思った動機とはどんなものだったんですか? 平山 : 前職でプレイングマネージャとして業務をしてきましたが、キャリアを重ねてくると徐々にマネジメントの割合が増えてきます。それ自体は自然なことではあるのですが、前職の延長線上でマネジメントに比重を置くことに対して、危機感を感じていたんです。手を動かす部分を含めて、エンジニアとして良いキャリアを積むには別の環境に身を置く必要があると思い、転職を決意しました。 転職に際しては、自分が 身近に感じるサービスで特に自分の家族が関わるような領域が良いと考えており、医療はそうした意味でぴったりな領域 でした。これらの条件で転職エージェントを通じてメドレーに辿りつきました。 平山さん 岡村 : 先程も少し話しましたが、前職は自社プロダクトと他社向けコンサルティングを並行して運営していた会社でした。しかし、途中から経営方針が変わってコンサルティング業務をメインに据えるようになったんです。自分としては、自社プロダクトを作っている点を大事に考えていたため、転職を考えはじめました。 会社選びの軸としては、もちろん自社サービスが事業の主軸である会社というのが一番でした。他の条件としては「上場一歩手前」くらいのフェイズの会社というものがありました。これは、そのくらいのフェイズの会社であれば今までやっていた事業をいきなりピボットするという可能性が限りなく低いのではないかと考えたからです。そんな感じで会社を探していて、LinkedIn の広告経由でメドレーにアプローチして入社しました。 新居 : 岡村さんは次の会社の事業領域は、意識していたんでしょうか? 岡村 : 医療領域を名指しで考えていたわけではないです。しかし、いわゆる 昔ながらの慣習などが多く残るような業界や領域を、自分達が手がけたサービスで変えていくというのが良い という考えでした。これは前職でも考えていたことだったので、引き続き志向していました。 メドレー入社後にどのような仕事をしていたのか 新居 : ありがとうございます。そんな経緯でメドレーに入社したお二人ですが、入社後はどのような業務をされていたんでしょうか。 平山 : 私は入社後から一貫して CLINICS カルテを担当しています。入社後は医療に関する業務知識を身につけながら、周辺業務から徐々に関わっていきシステム面のキャッチアップをしていきました。一番最初に携わったプロジェクトとしては、CLINICS カルテにおける診察業務のコントロールを担う「診療ステータス」の拡張を行うという、カルテ全体に影響のある大きな改修に携わりました。それ以降も、小さいタスクを捌きつつ、中・大規模な施策に携わってきた感じです。 そこから医療 PF 横断のプロジェクトにも関わっていくようになりました。CLINICS というプロダクトは患者が使用するアプリを始め、調剤・歯科のサービスも提供しています。 医療 PF 全体の体験設計を踏まえて、周辺プロダクトとも積極的に関わっていく必要がある という点は特徴的だと思います。 岡村 : 私は入社当時は「社長室コンサル」という役割で入社し、この部署は M&A などをメインに担当していた部署だったんです。そのため最初にアサインされたのが NaCl メディカル社(現在はメドレー本体に統合)の業務面での PMI でした。これと平行して、現在も引き続き携わっている新しいレセコンを作るプロジェクトのプロダクトマネージャを担当してきました。 新規レセコンは CLINICS カルテと連携しているプロダクトなので、平山さんが率いるチームと連携 しながら、このプロダクトを作っている開発チームを率いています。 GM としてのミッションと就任してから変わったこと・変わらないこと 新居 : 話を聞いていると、お二人とも最初の仕事を順調にスケールアップしていったという形で現在の業務に繋っているんですね。さて、ここからは GM に就任してからの自身の業務がどのように変化してきたのかという点を聞きたいのですが、この点どうでしょうか。 平山 : ミッションはやはり変化しました。GM としてのミッションで一番求められるものは「 プロダクトを前進させる 」という部分と考えています。自身の管掌組織は当然ですが、組織を横断する形の取り組みも必要になります。 「ピープルマネジメント」という点も GM としての変化の1つかなと思います。とはいえ、先ほどの話と通じる部分はあり**「プロダクトに軸足を置いて目線を合わせていくこと」が基本**となります。また、組織上の持続可能性という意味においても「任せていく」というところは大事にしています。メンバーに優秀な方が多いので助けられている部分は多いと感じます。 エンジニアリングという意味では、手を動かす量は減りました。手の動かし方という意味では入社当初から横の動きを見るようにはしていたので GM になって変わったという部分は無いですね。 岡村 : レセコン新規開発プロジェクトでの GM のミッションとして、「プロジェクトの完遂」、「チームメンバーのピープルマネジメント」、「Tech Studio MATSUE(島根県にあるメドレーの拠点。レセコン新規開発プロジェクトの開発をしている)の管理」がメインになります。 GM に就任したタイミングは NaCl メディカル社がメドレー本体に吸収合併されたというタイミングでした。就任前はグループ会社ということもあり、開発業務を受発注するという動きに近い感じで協業してきました。GM になってからは、同じ会社になったのでより近い距離でチーム開発をするようになったり、メンバーのピープルマネジメントの割合が多くなるという変化がありました。 担当領域としてはレセコンのプロダクトマネジメントをしているという部分は変わらない部分です。また平山さんがおっしゃっていたように、いつまでも自分が上にいて指揮するという状態を良しとするのではなく、 メンバーの人をどんどんと上のロールに引き上げていくというのが GM の役割の 1 つ かと思うので、そういう意識でメンバーのマネジメントをしています。 エンジニアリングとマネジメントの比率はプロダクトのフェイズによって変わってくるし、変わるべきかなと思っています。現在は 8 対 2 でマネジメントの比率が多くなっています。一方でプロダクトの初期でどんどんと開発をしていかないといけないという場合には自分も含めて人手をかけて開発していくようにします。 新居 : お話をうかがうと、やはり GM 前よりもマネジメントの比率が多くなっているのではないかと思うのですが、葛藤などはありましたか。 平山 : 元々の自分のスタンスとして「自分も駒の一つ」としてプロダクト開発を推進していくという思考があります。GM の話が来たときにも、やれるかなという不安感はありましたが、今の場面においては自身がやった方が良いと感じて受けました。 岡村 : 私は出自がエンジニアではないというのもあって、葛藤のようなものも平山さんとは違って、特にありませんでした。元々 NaCl メディカル社だった松江オフィスとのハブになるのは、自分しかいないと思っていましたので躊躇のようなものも全く無かったです。 GM で苦労したポイント・どう克服していたか 新居 : ありがとうございます。では、GM になって苦労したとか大変だったというところってどんなところがありましたか。 平山 : 自分は口下手な方なんで、以前より喋る機会が増えたのが困ったところですね(笑) 新居 : 平山さんに口下手な印象は全然ありませんが(笑) 平山 : 他には、これはどちらかというと PdM 属性の仕事にも関わってくるんですが、開発計画の策定は大変です(メドレーでは 3 ヶ月ごとに開発計画を立てている)。 大規模な施策の実施と並行して優先度の高いタスクの差し込みを踏まえて、優先度の再設計や施策のフェージングや運用の検討したりしています。 自分で抱え込む必要はないですが、関係者と調整しつつも最終意思決定は自分になるので、様々な葛藤をしながら取り組んでいます 。 新居 : ピープルマネジメント面では何かありましたか。 平山 : 先程お話した「プロダクトに軸足を置いて目線を合わせていくこと」を大切にコミュニケーションを実施し、日々の業務を通してメンバー一人一人でシナジーが出る・良いキャリアが重ねられるようにしたいと考えていますが、先ほどの開発計画の難しさもここにあります。 新居 : ありがとうございます。では岡村さんはいかがでしょう。 岡村 : 平山さんに全く同感ですね(笑) 冗談は置いておいて、計画外の差し込みタスクなどが入ると、どうしてもチームの負荷が高くなってくるので、そこをコントロールしきれずに申し訳ないなと思うことがあります。 他には、メンバーのキャリア形成をどうするかという点が難しいと思います。 メンバーの志向に合わせての将来像を見据えて現在の業務に取り組んでもらいたい のですが、実際のタスクとそうしたキャリアの方向性が必ずしもぴったりと合うときばかりではないので、ここをどう揃えていくかという点にも苦労はしています。 新居 : なるほど、岡村さんの場合は元々として島根と東京という立地間でのマネジメントをされていたと思うんですが、リモートコミュニケーションが中心という環境での苦労は何かありましたか。 岡村 : 実はこれと言って無いんですよ。それでもちゃんと上手く機能している要因としては、2 つあります。1 つは島根の方に現地のメンバーを取りしきってくれるスキルを持ったメンバーが居てくれたことです。ここは今でも本当に助かっています。 また、当初から技術的な部分も含めて私もメンバーと一緒に勉強しながら、新しい取り組みをするというスタンスを持ってプロジェクトを推進できたことです。ですので、離れたところから命令する人のような感じではなく、 チームで一緒にプロジェクトを進める人という認識を持ってもらえた んじゃないかなと考えています。 もちろん、関係値を作れるまでは私が最初の頃は 1 ヶ月に 1~2 回程度島根に出張して、実際にメンバーと会ったりすることもしていましたが。 GM を担う上で、役に立った経験・知識 新居 : ここからは、お二人が今までの業務の中で積んできた経験・知識の内、現在 GM として活動する上で役に立っているものとして、どんなものがあるのか聞ければと思います。 平山 : そうですね、一番役に立っているなと感じているのは前職で経験した「 様々なステークホルダーとの主体性を持った折衝経験 」です。色々な会社や立場の方とコミュニケーションを取ることが多かったんですね。専門性もそれぞれ違う方達とお話して物事を進める経験ができたことは、今の GM という役割に本当に役立っていると思います。 岡村 : 私が役に立っていると感じる経験は、「 一回深く細かいところまで業務を見てみて理解をしてから、改めて俯瞰した視点で業務を見直してメンバーにお願いする 」という習慣です。この相反する方法をバランス良くできるようになっているというのが、良い点なのかなと思っています。また、深く見るというのは自分が興味を持っている分野でないと中々難しいので、何にでも興味を持って業務に取り組むという心持ちも必要になるかと考えています。 こうした形で業務を分解してメンバーにお願いできるところはしながら、GM としては適切に権限委譲していくというのに役立っています。 エンジニアのピープルマネジメントで意識していること 新居 : 今までのお話でも少し出ている話題になるのですが、GM としてエンジニアのマネジメントで意識しているポイントって、どんなことがありますか。 平山 : 自分達が作る プロダクトへの興味関心・解像度を高め、何を作るのか 。という点において各所コミュニケーションを含めて主体的に動くこと。そうした積み重ねの延長線上にあるエンジニアとしてのキャリア形成を意識しています。 この考え方に加えて「プロダクトの持続可能性を高める」というテーマで、エンジニア・デザイナー・ディレクター・ QA エンジニア・カスタマーサクセスといったプロダクトに関わる人達の専門性を踏まえたタスクの再配布・プロジェクト設計を行う。といった体制作りも推進しようとしています。 新居 : 「プロダクトの持続可能性を高める」というのは、すごく難しい試みですよね。 平山 : 確かにとても難しいなと思います。でも打ち手としては色々あるのではないかと考えていて。「属人性の排除」といった点は、分かりやすいのではないかなと思います。誰かに依存した状態は、組織変更や事業スケールの変化に弱くなりがちですよね。 大事なのは「人から組織への移行」「仕組み化」と捉えています。「できる人を増やす」については、大事だけど秘伝のタレが受け継がれるだけになってしまうので、持続可能性の観点では本質的ではないかなと。 一方で「仕組み化するための計画的な属人性」については推奨できると考えてます。新しいことをいきなり「組織に転換」や「人を増やしてローテする」だと、ナレッジが蓄積されづらく、次の一手が打ちづらくなるので。 新居 : そうした取り組みをするのに、例えばエンジニアだと作ってみたものがプロダクトの目指す方向性とズレが生じていたというような事態があると思うんですが、そうしたズレを事前に仕組みで抑えるような事をしているんですか。 平山 : きっちりとした仕組みなどは作ってはいないです。仕組み化というよりも、そうならないように メンバーから上がってきた疑問などに対する壁打ちなどは気軽にできる ようにしています。また、そういった相談を気軽にできるような自分自身のプロデュースといいますか、キャラ作りみたいなものも含めてメンバーが相談しにくいという空気を作らないようにはしていますね。 新居 : ありがとうございます。岡村さんはどんな意識をしていますか。 岡村 : やっぱり平山さんに全く同感ですね(笑) 付け加えると、ソフト面ですが メンバーであるエンジニアが楽しめる環境作り というところを意識しています。楽しさにも色々な種類があるとは思いますが、単にコーディングするだけではなく、やはりプロダクトそれ自体の提供価値というものをしっかりと共有して、プロダクトを作ること自体に楽しさを感じてもらう…というのは重要じゃないかなと思っています。 また楽しさの別側面ですが、新しいテクノロジーなどもどんどんと取り入れることができるような仕組み作りもしています。島根のメンバーは今回のプロジェクトで初めて Ruby を使うというメンバーも多かったので、ここで楽しさを感じられないと、こうしたテクノロジー自体を嫌いになってしまう恐れがあるなと思ったので。 そうした「楽しい仕事」にしていくと「時間をかけなきゃ」ではなく「時間をかけたい」という感じで取り組めるようになると思います。 GM という役割の醍醐味とは 新居 : それでは終盤になってきましたが、GM の「やりがい」や「醍醐味」はどういったものになりますか。 岡村 : 一番は「ピープルマネジメント」になるんじゃないかと考えています。最近、岸田首相の所信表明演説で聞いて感銘を受けたアフリカの諺ですが「早く行きたいなら 1 人で行け、遠くへ行きたいならみんなで行け」という言葉はピープルマネジメントの本質なんじゃないかなと思っています。 メンバーと一緒に遠くに行くために組織作り などをしていけるのが醍醐味だと感じています。 平山 : メンバーの 個人スキルを高めつつ「チーム」に還元してもらうための仕組み を作っていく。というのは、醍醐味と言えるのかなと。CTO インタビューで田中も言っていますが「チームバリュー」は大切だと考えています。 チームといっても、狭義のチームではなくプロダクトに関わる関係者全員を含めたチームという感じで意識しています。開発組織に閉じないバリューの発揮がメンバーそれぞれでできると心強いと思います。 メドレーで GM やリードエンジニアのロールに向いている方 新居 : 最後になるのですが、メドレーで GM やテックリードといったロールに向いているのは、どんな人だと思いますか。 平山 : 基本的に 物事を前に進めるための解像度を高く描ける人 かなと思っています。何を誰のためにというのを体現しつつ、関係者を引っぱっていける人ですね。メドレーの Our Essentials 全般必要だとは思いますが、その中でも「成果を出す」「自分をアップデート」「組織水準を高める」「革新と改善を主導」というところが必要になるかな。困難な状況でも結果を出しつつ、自分の行動も高めていき、結果組織・プロダクトを良くしていける…というのがメドレーのリードと呼ばれているエンジニアかと思います。 やり方としては、マイクロマネジメントではなく、基本となる考え方をちゃんと伝えてメンバーに学んでもらう…という、やり方ができる人が向いていると思います。 岡村 : 困難な状況であっても何とかして前に進めることができる人 でしょうか。無理矢理に進めるというわけではなく、状況の整理や色々な部署との調整などもしたりして、何とか結果という形にしていける能力がある人がリードとしてメンバーを引っぱっていける人だと思います。 もちろん、フェイズによってメンバーとの関わりなどはちゃんと調整していける柔軟さなども必要になると思います。 新居 : なるほど。本日は色々なお話をしていただきまして、ありがとうございました! さいごに メドレーの GM 二人に仕事について聞いていきました。 前回の CTO インタビューでも語られた開発組織の話ですが、より現場に近い GM という立場のメンバーからの話でまた違った雰囲気などを感じていただけたかと思います。 このような GM を「ぜひやりたい!」という方や「こんな上司の元で自分の力を発揮していきたい!」という方はぜひカジュアルにお話ができればと思います。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに みなさん、こんにちは。エンジニアの新居です。 今回のインタビューは前回の CTO へのインタビュー に続いて、メドレーの開発組織についてご紹介していきたいと思います。 メドレーでは開発組織をリードするロールとして、CTO の他、「グループマネージャ」(以下、GM)という役職があります。今回は医療プラットフォーム(以下、医療 PF)における開発グループの GM を担う二人に、具体的にどのような役割なのか聞いていきます。 インタビュイー紹介 今回の紹介はインタビュー中で前職からメドレー入社の話を聞いているので、シンプルにお送りします。 平山さん 医療 PF 第一本部 プロダクト開発室 第四開発グループ GM。現在は CLINICS 基幹システムチームのマネジメントを担当。 岡村さん 医療 PF 第一本部 プロダクト開発室 第三開発グループ GM。現在はレセコン(電子カルテの機能のうち主に会計など診療報酬の計算をするソフトウェア)新規開発チームのマネジメントを担当。 左から岡村さん、平山さん、筆者 前職までの経歴とメドレー入社の理由について 二人の経験について 新居 : 早速始めていきますが、まずはお二人の経歴を教えていただきたいと思います。 平山 : メドレーは 2 社目の会社となります。前職は SIer で 14~5 年くらい受託開発や客先常駐して開発をしていました。SIer というと場合によっては二次請け・三次請けの開発をしているところもあるんですが、自分は一次請けとして直接顧客と関わる形で業務を行っていました。 顧客のサービスをステークホルダーと提案・折衝しながら、技術選定・要件定義から開発、リリースまで行い、そこからの保守運用といった部分までの一連の流れを様々な業態の顧客と関わらせて頂きました。 新居 : ありがとうございます。かなり自社開発と同じ感覚の開発を長年されてきたんですね。それでは、岡村さんお願いします。 岡村 : 新卒で外資系の IT コンサルティング会社で IT コンサルタントとしてキャリアを始めました。その中でも特に「情報系」と呼ばれるデータアナリティクスや BI (Business Intelligence) などを取り扱う部門で業務をしていました。領域的には金融・製薬・省庁など堅めの領域を主に担当していたので、いわゆる Web アプリケーションを作るというよりは SQL をガリガリ書いてデータ分析を基に業務改善していく…という感じの業務が多かったです。 2 社目はスタートアップ企業の立ち上げフェイズにジョインしました。その会社はいわゆる Insure Tech と呼ばれる保険業界 x テクノロジー領域のサービスを作っている会社でした。そこで、その会社でのプロダクトマネージャ業務と、運転資金を確保するために大手保険会社向けのコンサルタント業務に半々で従事していました。 その後にメドレーにジョインするという経歴です。 岡村さん メドレー入社の経緯について 新居 : ありがとうございます、お二人はメドレーに入社してどのくらい経ちますっけ? 平山 : 私は 3 年半ですね。 岡村 : 私は 4 年経ちました。 新居 : もうそんなに経つんですね。みなさんの前職を転職しようと思った動機とはどんなものだったんですか? 平山 : 前職でプレイングマネージャとして業務をしてきましたが、キャリアを重ねてくると徐々にマネジメントの割合が増えてきます。それ自体は自然なことではあるのですが、前職の延長線上でマネジメントに比重を置くことに対して、危機感を感じていたんです。手を動かす部分を含めて、エンジニアとして良いキャリアを積むには別の環境に身を置く必要があると思い、転職を決意しました。 転職に際しては、自分が 身近に感じるサービスで特に自分の家族が関わるような領域が良いと考えており、医療はそうした意味でぴったりな領域 でした。これらの条件で転職エージェントを通じてメドレーに辿りつきました。 平山さん 岡村 : 先程も少し話しましたが、前職は自社プロダクトと他社向けコンサルティングを並行して運営していた会社でした。しかし、途中から経営方針が変わってコンサルティング業務をメインに据えるようになったんです。自分としては、自社プロダクトを作っている点を大事に考えていたため、転職を考えはじめました。 会社選びの軸としては、もちろん自社サービスが事業の主軸である会社というのが一番でした。他の条件としては「上場一歩手前」くらいのフェイズの会社というものがありました。これは、そのくらいのフェイズの会社であれば今までやっていた事業をいきなりピボットするという可能性が限りなく低いのではないかと考えたからです。そんな感じで会社を探していて、LinkedIn の広告経由でメドレーにアプローチして入社しました。 新居 : 岡村さんは次の会社の事業領域は、意識していたんでしょうか? 岡村 : 医療領域を名指しで考えていたわけではないです。しかし、いわゆる 昔ながらの慣習などが多く残るような業界や領域を、自分達が手がけたサービスで変えていくというのが良い という考えでした。これは前職でも考えていたことだったので、引き続き志向していました。 メドレー入社後にどのような仕事をしていたのか 新居 : ありがとうございます。そんな経緯でメドレーに入社したお二人ですが、入社後はどのような業務をされていたんでしょうか。 平山 : 私は入社後から一貫して CLINICS カルテを担当しています。入社後は医療に関する業務知識を身につけながら、周辺業務から徐々に関わっていきシステム面のキャッチアップをしていきました。一番最初に携わったプロジェクトとしては、CLINICS カルテにおける診察業務のコントロールを担う「診療ステータス」の拡張を行うという、カルテ全体に影響のある大きな改修に携わりました。それ以降も、小さいタスクを捌きつつ、中・大規模な施策に携わってきた感じです。 そこから医療 PF 横断のプロジェクトにも関わっていくようになりました。CLINICS というプロダクトは患者が使用するアプリを始め、調剤・歯科のサービスも提供しています。 医療 PF 全体の体験設計を踏まえて、周辺プロダクトとも積極的に関わっていく必要がある という点は特徴的だと思います。 岡村 : 私は入社当時は「社長室コンサル」という役割で入社し、この部署は M&A などをメインに担当していた部署だったんです。そのため最初にアサインされたのが NaCl メディカル社(現在はメドレー本体に統合)の業務面での PMI でした。これと平行して、現在も引き続き携わっている新しいレセコンを作るプロジェクトのプロダクトマネージャを担当してきました。 新規レセコンは CLINICS カルテと連携しているプロダクトなので、平山さんが率いるチームと連携 しながら、このプロダクトを作っている開発チームを率いています。 GM としてのミッションと就任してから変わったこと・変わらないこと 新居 : 話を聞いていると、お二人とも最初の仕事を順調にスケールアップしていったという形で現在の業務に繋っているんですね。さて、ここからは GM に就任してからの自身の業務がどのように変化してきたのかという点を聞きたいのですが、この点どうでしょうか。 平山 : ミッションはやはり変化しました。GM としてのミッションで一番求められるものは「 プロダクトを前進させる 」という部分と考えています。自身の管掌組織は当然ですが、組織を横断する形の取り組みも必要になります。 「ピープルマネジメント」という点も GM としての変化の1つかなと思います。とはいえ、先ほどの話と通じる部分はあり**「プロダクトに軸足を置いて目線を合わせていくこと」が基本**となります。また、組織上の持続可能性という意味においても「任せていく」というところは大事にしています。メンバーに優秀な方が多いので助けられている部分は多いと感じます。 エンジニアリングという意味では、手を動かす量は減りました。手の動かし方という意味では入社当初から横の動きを見るようにはしていたので GM になって変わったという部分は無いですね。 岡村 : レセコン新規開発プロジェクトでの GM のミッションとして、「プロジェクトの完遂」、「チームメンバーのピープルマネジメント」、「Tech Studio MATSUE(島根県にあるメドレーの拠点。レセコン新規開発プロジェクトの開発をしている)の管理」がメインになります。 GM に就任したタイミングは NaCl メディカル社がメドレー本体に吸収合併されたというタイミングでした。就任前はグループ会社ということもあり、開発業務を受発注するという動きに近い感じで協業してきました。GM になってからは、同じ会社になったのでより近い距離でチーム開発をするようになったり、メンバーのピープルマネジメントの割合が多くなるという変化がありました。 担当領域としてはレセコンのプロダクトマネジメントをしているという部分は変わらない部分です。また平山さんがおっしゃっていたように、いつまでも自分が上にいて指揮するという状態を良しとするのではなく、 メンバーの人をどんどんと上のロールに引き上げていくというのが GM の役割の 1 つ かと思うので、そういう意識でメンバーのマネジメントをしています。 エンジニアリングとマネジメントの比率はプロダクトのフェイズによって変わってくるし、変わるべきかなと思っています。現在は 8 対 2 でマネジメントの比率が多くなっています。一方でプロダクトの初期でどんどんと開発をしていかないといけないという場合には自分も含めて人手をかけて開発していくようにします。 新居 : お話をうかがうと、やはり GM 前よりもマネジメントの比率が多くなっているのではないかと思うのですが、葛藤などはありましたか。 平山 : 元々の自分のスタンスとして「自分も駒の一つ」としてプロダクト開発を推進していくという思考があります。GM の話が来たときにも、やれるかなという不安感はありましたが、今の場面においては自身がやった方が良いと感じて受けました。 岡村 : 私は出自がエンジニアではないというのもあって、葛藤のようなものも平山さんとは違って、特にありませんでした。元々 NaCl メディカル社だった松江オフィスとのハブになるのは、自分しかいないと思っていましたので躊躇のようなものも全く無かったです。 GM で苦労したポイント・どう克服していたか 新居 : ありがとうございます。では、GM になって苦労したとか大変だったというところってどんなところがありましたか。 平山 : 自分は口下手な方なんで、以前より喋る機会が増えたのが困ったところですね(笑) 新居 : 平山さんに口下手な印象は全然ありませんが(笑) 平山 : 他には、これはどちらかというと PdM 属性の仕事にも関わってくるんですが、開発計画の策定は大変です(メドレーでは 3 ヶ月ごとに開発計画を立てている)。 大規模な施策の実施と並行して優先度の高いタスクの差し込みを踏まえて、優先度の再設計や施策のフェージングや運用の検討したりしています。 自分で抱え込む必要はないですが、関係者と調整しつつも最終意思決定は自分になるので、様々な葛藤をしながら取り組んでいます 。 新居 : ピープルマネジメント面では何かありましたか。 平山 : 先程お話した「プロダクトに軸足を置いて目線を合わせていくこと」を大切にコミュニケーションを実施し、日々の業務を通してメンバー一人一人でシナジーが出る・良いキャリアが重ねられるようにしたいと考えていますが、先ほどの開発計画の難しさもここにあります。 新居 : ありがとうございます。では岡村さんはいかがでしょう。 岡村 : 平山さんに全く同感ですね(笑) 冗談は置いておいて、計画外の差し込みタスクなどが入ると、どうしてもチームの負荷が高くなってくるので、そこをコントロールしきれずに申し訳ないなと思うことがあります。 他には、メンバーのキャリア形成をどうするかという点が難しいと思います。 メンバーの志向に合わせての将来像を見据えて現在の業務に取り組んでもらいたい のですが、実際のタスクとそうしたキャリアの方向性が必ずしもぴったりと合うときばかりではないので、ここをどう揃えていくかという点にも苦労はしています。 新居 : なるほど、岡村さんの場合は元々として島根と東京という立地間でのマネジメントをされていたと思うんですが、リモートコミュニケーションが中心という環境での苦労は何かありましたか。 岡村 : 実はこれと言って無いんですよ。それでもちゃんと上手く機能している要因としては、2 つあります。1 つは島根の方に現地のメンバーを取りしきってくれるスキルを持ったメンバーが居てくれたことです。ここは今でも本当に助かっています。 また、当初から技術的な部分も含めて私もメンバーと一緒に勉強しながら、新しい取り組みをするというスタンスを持ってプロジェクトを推進できたことです。ですので、離れたところから命令する人のような感じではなく、 チームで一緒にプロジェクトを進める人という認識を持ってもらえた んじゃないかなと考えています。 もちろん、関係値を作れるまでは私が最初の頃は 1 ヶ月に 1~2 回程度島根に出張して、実際にメンバーと会ったりすることもしていましたが。 GM を担う上で、役に立った経験・知識 新居 : ここからは、お二人が今までの業務の中で積んできた経験・知識の内、現在 GM として活動する上で役に立っているものとして、どんなものがあるのか聞ければと思います。 平山 : そうですね、一番役に立っているなと感じているのは前職で経験した「 様々なステークホルダーとの主体性を持った折衝経験 」です。色々な会社や立場の方とコミュニケーションを取ることが多かったんですね。専門性もそれぞれ違う方達とお話して物事を進める経験ができたことは、今の GM という役割に本当に役立っていると思います。 岡村 : 私が役に立っていると感じる経験は、「 一回深く細かいところまで業務を見てみて理解をしてから、改めて俯瞰した視点で業務を見直してメンバーにお願いする 」という習慣です。この相反する方法をバランス良くできるようになっているというのが、良い点なのかなと思っています。また、深く見るというのは自分が興味を持っている分野でないと中々難しいので、何にでも興味を持って業務に取り組むという心持ちも必要になるかと考えています。 こうした形で業務を分解してメンバーにお願いできるところはしながら、GM としては適切に権限委譲していくというのに役立っています。 エンジニアのピープルマネジメントで意識していること 新居 : 今までのお話でも少し出ている話題になるのですが、GM としてエンジニアのマネジメントで意識しているポイントって、どんなことがありますか。 平山 : 自分達が作る プロダクトへの興味関心・解像度を高め、何を作るのか 。という点において各所コミュニケーションを含めて主体的に動くこと。そうした積み重ねの延長線上にあるエンジニアとしてのキャリア形成を意識しています。 この考え方に加えて「プロダクトの持続可能性を高める」というテーマで、エンジニア・デザイナー・ディレクター・ QA エンジニア・カスタマーサクセスといったプロダクトに関わる人達の専門性を踏まえたタスクの再配布・プロジェクト設計を行う。といった体制作りも推進しようとしています。 新居 : 「プロダクトの持続可能性を高める」というのは、すごく難しい試みですよね。 平山 : 確かにとても難しいなと思います。でも打ち手としては色々あるのではないかと考えていて。「属人性の排除」といった点は、分かりやすいのではないかなと思います。誰かに依存した状態は、組織変更や事業スケールの変化に弱くなりがちですよね。 大事なのは「人から組織への移行」「仕組み化」と捉えています。「できる人を増やす」については、大事だけど秘伝のタレが受け継がれるだけになってしまうので、持続可能性の観点では本質的ではないかなと。 一方で「仕組み化するための計画的な属人性」については推奨できると考えてます。新しいことをいきなり「組織に転換」や「人を増やしてローテする」だと、ナレッジが蓄積されづらく、次の一手が打ちづらくなるので。 新居 : そうした取り組みをするのに、例えばエンジニアだと作ってみたものがプロダクトの目指す方向性とズレが生じていたというような事態があると思うんですが、そうしたズレを事前に仕組みで抑えるような事をしているんですか。 平山 : きっちりとした仕組みなどは作ってはいないです。仕組み化というよりも、そうならないように メンバーから上がってきた疑問などに対する壁打ちなどは気軽にできる ようにしています。また、そういった相談を気軽にできるような自分自身のプロデュースといいますか、キャラ作りみたいなものも含めてメンバーが相談しにくいという空気を作らないようにはしていますね。 新居 : ありがとうございます。岡村さんはどんな意識をしていますか。 岡村 : やっぱり平山さんに全く同感ですね(笑) 付け加えると、ソフト面ですが メンバーであるエンジニアが楽しめる環境作り というところを意識しています。楽しさにも色々な種類があるとは思いますが、単にコーディングするだけではなく、やはりプロダクトそれ自体の提供価値というものをしっかりと共有して、プロダクトを作ること自体に楽しさを感じてもらう…というのは重要じゃないかなと思っています。 また楽しさの別側面ですが、新しいテクノロジーなどもどんどんと取り入れることができるような仕組み作りもしています。島根のメンバーは今回のプロジェクトで初めて Ruby を使うというメンバーも多かったので、ここで楽しさを感じられないと、こうしたテクノロジー自体を嫌いになってしまう恐れがあるなと思ったので。 そうした「楽しい仕事」にしていくと「時間をかけなきゃ」ではなく「時間をかけたい」という感じで取り組めるようになると思います。 GM という役割の醍醐味とは 新居 : それでは終盤になってきましたが、GM の「やりがい」や「醍醐味」はどういったものになりますか。 岡村 : 一番は「ピープルマネジメント」になるんじゃないかと考えています。最近、岸田首相の所信表明演説で聞いて感銘を受けたアフリカの諺ですが「早く行きたいなら 1 人で行け、遠くへ行きたいならみんなで行け」という言葉はピープルマネジメントの本質なんじゃないかなと思っています。 メンバーと一緒に遠くに行くために組織作り などをしていけるのが醍醐味だと感じています。 平山 : メンバーの 個人スキルを高めつつ「チーム」に還元してもらうための仕組み を作っていく。というのは、醍醐味と言えるのかなと。CTO インタビューで田中も言っていますが「チームバリュー」は大切だと考えています。 チームといっても、狭義のチームではなくプロダクトに関わる関係者全員を含めたチームという感じで意識しています。開発組織に閉じないバリューの発揮がメンバーそれぞれでできると心強いと思います。 メドレーで GM やリードエンジニアのロールに向いている方 新居 : 最後になるのですが、メドレーで GM やテックリードといったロールに向いているのは、どんな人だと思いますか。 平山 : 基本的に 物事を前に進めるための解像度を高く描ける人 かなと思っています。何を誰のためにというのを体現しつつ、関係者を引っぱっていける人ですね。メドレーの Our Essentials 全般必要だとは思いますが、その中でも「成果を出す」「自分をアップデート」「組織水準を高める」「革新と改善を主導」というところが必要になるかな。困難な状況でも結果を出しつつ、自分の行動も高めていき、結果組織・プロダクトを良くしていける…というのがメドレーのリードと呼ばれているエンジニアかと思います。 やり方としては、マイクロマネジメントではなく、基本となる考え方をちゃんと伝えてメンバーに学んでもらう…という、やり方ができる人が向いていると思います。 岡村 : 困難な状況であっても何とかして前に進めることができる人 でしょうか。無理矢理に進めるというわけではなく、状況の整理や色々な部署との調整などもしたりして、何とか結果という形にしていける能力がある人がリードとしてメンバーを引っぱっていける人だと思います。 もちろん、フェイズによってメンバーとの関わりなどはちゃんと調整していける柔軟さなども必要になると思います。 新居 : なるほど。本日は色々なお話をしていただきまして、ありがとうございました! さいごに メドレーの GM 二人に仕事について聞いていきました。 前回の CTO インタビューでも語られた開発組織の話ですが、より現場に近い GM という立場のメンバーからの話でまた違った雰囲気などを感じていただけたかと思います。 このような GM を「ぜひやりたい!」という方や「こんな上司の元で自分の力を発揮していきたい!」という方はぜひカジュアルにお話ができればと思います。 https://www.medley.jp/jobs/
アバター
はじめに みなさん、こんにちは。エンジニアの新居です。 今回のインタビューは前回の CTO へのインタビュー に続いて、メドレーの開発組織についてご紹介していきたいと思います。 メドレーでは開発組織をリードするロールとして、CTO の他、「グループマネージャ」(以下、GM)という役職があります。今回は医療プラットフォーム(以下、医療 PF)における開発グループの GM を担う二人に、具体的にどのような役割なのか聞いていきます。 インタビュイー紹介 今回の紹介はインタビュー中で前職からメドレー入社の話を聞いているので、シンプルにお送りします。 平山さん 医療 PF 第一本部 プロダクト開発室 第四開発グループ GM。現在は CLINICS 基幹システムチームのマネジメントを担当。 岡村さん 医療 PF 第一本部 プロダクト開発室 第三開発グループ GM。現在はレセコン(電子カルテの機能のうち主に会計など診療報酬の計算をするソフトウェア)新規開発チームのマネジメントを担当。 左から岡村さん、平山さん、筆者 前職までの経歴とメドレー入社の理由について 二人の経験について 新居 : 早速始めていきますが、まずはお二人の経歴を教えていただきたいと思います。 平山 : メドレーは 2 社目の会社となります。前職は SIer で 14~5 年くらい受託開発や客先常駐して開発をしていました。SIer というと場合によっては二次請け・三次請けの開発をしているところもあるんですが、自分は一次請けとして直接顧客と関わる形で業務を行っていました。 顧客のサービスをステークホルダーと提案・折衝しながら、技術選定・要件定義から開発、リリースまで行い、そこからの保守運用といった部分までの一連の流れを様々な業態の顧客と関わらせて頂きました。 新居 : ありがとうございます。かなり自社開発と同じ感覚の開発を長年されてきたんですね。それでは、岡村さんお願いします。 岡村 : 新卒で外資系の IT コンサルティング会社で IT コンサルタントとしてキャリアを始めました。その中でも特に「情報系」と呼ばれるデータアナリティクスや BI (Business Intelligence) などを取り扱う部門で業務をしていました。領域的には金融・製薬・省庁など堅めの領域を主に担当していたので、いわゆる Web アプリケーションを作るというよりは SQL をガリガリ書いてデータ分析を基に業務改善していく…という感じの業務が多かったです。 2 社目はスタートアップ企業の立ち上げフェイズにジョインしました。その会社はいわゆる Insure Tech と呼ばれる保険業界 x テクノロジー領域のサービスを作っている会社でした。そこで、その会社でのプロダクトマネージャ業務と、運転資金を確保するために大手保険会社向けのコンサルタント業務に半々で従事していました。 その後にメドレーにジョインするという経歴です。 岡村さん メドレー入社の経緯について 新居 : ありがとうございます、お二人はメドレーに入社してどのくらい経ちますっけ? 平山 : 私は 3 年半ですね。 岡村 : 私は 4 年経ちました。 新居 : もうそんなに経つんですね。みなさんの前職を転職しようと思った動機とはどんなものだったんですか? 平山 : 前職でプレイングマネージャとして業務をしてきましたが、キャリアを重ねてくると徐々にマネジメントの割合が増えてきます。それ自体は自然なことではあるのですが、前職の延長線上でマネジメントに比重を置くことに対して、危機感を感じていたんです。手を動かす部分を含めて、エンジニアとして良いキャリアを積むには別の環境に身を置く必要があると思い、転職を決意しました。 転職に際しては、自分が 身近に感じるサービスで特に自分の家族が関わるような領域が良いと考えており、医療はそうした意味でぴったりな領域 でした。これらの条件で転職エージェントを通じてメドレーに辿りつきました。 平山さん 岡村 : 先程も少し話しましたが、前職は自社プロダクトと他社向けコンサルティングを並行して運営していた会社でした。しかし、途中から経営方針が変わってコンサルティング業務をメインに据えるようになったんです。自分としては、自社プロダクトを作っている点を大事に考えていたため、転職を考えはじめました。 会社選びの軸としては、もちろん自社サービスが事業の主軸である会社というのが一番でした。他の条件としては「上場一歩手前」くらいのフェイズの会社というものがありました。これは、そのくらいのフェイズの会社であれば今までやっていた事業をいきなりピボットするという可能性が限りなく低いのではないかと考えたからです。そんな感じで会社を探していて、LinkedIn の広告経由でメドレーにアプローチして入社しました。 新居 : 岡村さんは次の会社の事業領域は、意識していたんでしょうか? 岡村 : 医療領域を名指しで考えていたわけではないです。しかし、いわゆる 昔ながらの慣習などが多く残るような業界や領域を、自分達が手がけたサービスで変えていくというのが良い という考えでした。これは前職でも考えていたことだったので、引き続き志向していました。 メドレー入社後にどのような仕事をしていたのか 新居 : ありがとうございます。そんな経緯でメドレーに入社したお二人ですが、入社後はどのような業務をされていたんでしょうか。 平山 : 私は入社後から一貫して CLINICS カルテを担当しています。入社後は医療に関する業務知識を身につけながら、周辺業務から徐々に関わっていきシステム面のキャッチアップをしていきました。一番最初に携わったプロジェクトとしては、CLINICS カルテにおける診察業務のコントロールを担う「診療ステータス」の拡張を行うという、カルテ全体に影響のある大きな改修に携わりました。それ以降も、小さいタスクを捌きつつ、中・大規模な施策に携わってきた感じです。 そこから医療 PF 横断のプロジェクトにも関わっていくようになりました。CLINICS というプロダクトは患者が使用するアプリを始め、調剤・歯科のサービスも提供しています。 医療 PF 全体の体験設計を踏まえて、周辺プロダクトとも積極的に関わっていく必要がある という点は特徴的だと思います。 岡村 : 私は入社当時は「社長室コンサル」という役割で入社し、この部署は M&A などをメインに担当していた部署だったんです。そのため最初にアサインされたのが NaCl メディカル社(現在はメドレー本体に統合)の業務面での PMI でした。これと平行して、現在も引き続き携わっている新しいレセコンを作るプロジェクトのプロダクトマネージャを担当してきました。 新規レセコンは CLINICS カルテと連携しているプロダクトなので、平山さんが率いるチームと連携 しながら、このプロダクトを作っている開発チームを率いています。 GM としてのミッションと就任してから変わったこと・変わらないこと 新居 : 話を聞いていると、お二人とも最初の仕事を順調にスケールアップしていったという形で現在の業務に繋っているんですね。さて、ここからは GM に就任してからの自身の業務がどのように変化してきたのかという点を聞きたいのですが、この点どうでしょうか。 平山 : ミッションはやはり変化しました。GM としてのミッションで一番求められるものは「 プロダクトを前進させる 」という部分と考えています。自身の管掌組織は当然ですが、組織を横断する形の取り組みも必要になります。 「ピープルマネジメント」という点も GM としての変化の1つかなと思います。とはいえ、先ほどの話と通じる部分はあり**「プロダクトに軸足を置いて目線を合わせていくこと」が基本**となります。また、組織上の持続可能性という意味においても「任せていく」というところは大事にしています。メンバーに優秀な方が多いので助けられている部分は多いと感じます。 エンジニアリングという意味では、手を動かす量は減りました。手の動かし方という意味では入社当初から横の動きを見るようにはしていたので GM になって変わったという部分は無いですね。 岡村 : レセコン新規開発プロジェクトでの GM のミッションとして、「プロジェクトの完遂」、「チームメンバーのピープルマネジメント」、「Tech Studio MATSUE(島根県にあるメドレーの拠点。レセコン新規開発プロジェクトの開発をしている)の管理」がメインになります。 GM に就任したタイミングは NaCl メディカル社がメドレー本体に吸収合併されたというタイミングでした。就任前はグループ会社ということもあり、開発業務を受発注するという動きに近い感じで協業してきました。GM になってからは、同じ会社になったのでより近い距離でチーム開発をするようになったり、メンバーのピープルマネジメントの割合が多くなるという変化がありました。 担当領域としてはレセコンのプロダクトマネジメントをしているという部分は変わらない部分です。また平山さんがおっしゃっていたように、いつまでも自分が上にいて指揮するという状態を良しとするのではなく、 メンバーの人をどんどんと上のロールに引き上げていくというのが GM の役割の 1 つ かと思うので、そういう意識でメンバーのマネジメントをしています。 エンジニアリングとマネジメントの比率はプロダクトのフェイズによって変わってくるし、変わるべきかなと思っています。現在は 8 対 2 でマネジメントの比率が多くなっています。一方でプロダクトの初期でどんどんと開発をしていかないといけないという場合には自分も含めて人手をかけて開発していくようにします。 新居 : お話をうかがうと、やはり GM 前よりもマネジメントの比率が多くなっているのではないかと思うのですが、葛藤などはありましたか。 平山 : 元々の自分のスタンスとして「自分も駒の一つ」としてプロダクト開発を推進していくという思考があります。GM の話が来たときにも、やれるかなという不安感はありましたが、今の場面においては自身がやった方が良いと感じて受けました。 岡村 : 私は出自がエンジニアではないというのもあって、葛藤のようなものも平山さんとは違って、特にありませんでした。元々 NaCl メディカル社だった松江オフィスとのハブになるのは、自分しかいないと思っていましたので躊躇のようなものも全く無かったです。 GM で苦労したポイント・どう克服していたか 新居 : ありがとうございます。では、GM になって苦労したとか大変だったというところってどんなところがありましたか。 平山 : 自分は口下手な方なんで、以前より喋る機会が増えたのが困ったところですね(笑) 新居 : 平山さんに口下手な印象は全然ありませんが(笑) 平山 : 他には、これはどちらかというと PdM 属性の仕事にも関わってくるんですが、開発計画の策定は大変です(メドレーでは 3 ヶ月ごとに開発計画を立てている)。 大規模な施策の実施と並行して優先度の高いタスクの差し込みを踏まえて、優先度の再設計や施策のフェージングや運用の検討したりしています。 自分で抱え込む必要はないですが、関係者と調整しつつも最終意思決定は自分になるので、様々な葛藤をしながら取り組んでいます 。 新居 : ピープルマネジメント面では何かありましたか。 平山 : 先程お話した「プロダクトに軸足を置いて目線を合わせていくこと」を大切にコミュニケーションを実施し、日々の業務を通してメンバー一人一人でシナジーが出る・良いキャリアが重ねられるようにしたいと考えていますが、先ほどの開発計画の難しさもここにあります。 新居 : ありがとうございます。では岡村さんはいかがでしょう。 岡村 : 平山さんに全く同感ですね(笑) 冗談は置いておいて、計画外の差し込みタスクなどが入ると、どうしてもチームの負荷が高くなってくるので、そこをコントロールしきれずに申し訳ないなと思うことがあります。 他には、メンバーのキャリア形成をどうするかという点が難しいと思います。 メンバーの志向に合わせての将来像を見据えて現在の業務に取り組んでもらいたい のですが、実際のタスクとそうしたキャリアの方向性が必ずしもぴったりと合うときばかりではないので、ここをどう揃えていくかという点にも苦労はしています。 新居 : なるほど、岡村さんの場合は元々として島根と東京という立地間でのマネジメントをされていたと思うんですが、リモートコミュニケーションが中心という環境での苦労は何かありましたか。 岡村 : 実はこれと言って無いんですよ。それでもちゃんと上手く機能している要因としては、2 つあります。1 つは島根の方に現地のメンバーを取りしきってくれるスキルを持ったメンバーが居てくれたことです。ここは今でも本当に助かっています。 また、当初から技術的な部分も含めて私もメンバーと一緒に勉強しながら、新しい取り組みをするというスタンスを持ってプロジェクトを推進できたことです。ですので、離れたところから命令する人のような感じではなく、 チームで一緒にプロジェクトを進める人という認識を持ってもらえた んじゃないかなと考えています。 もちろん、関係値を作れるまでは私が最初の頃は 1 ヶ月に 1~2 回程度島根に出張して、実際にメンバーと会ったりすることもしていましたが。 GM を担う上で、役に立った経験・知識 新居 : ここからは、お二人が今までの業務の中で積んできた経験・知識の内、現在 GM として活動する上で役に立っているものとして、どんなものがあるのか聞ければと思います。 平山 : そうですね、一番役に立っているなと感じているのは前職で経験した「 様々なステークホルダーとの主体性を持った折衝経験 」です。色々な会社や立場の方とコミュニケーションを取ることが多かったんですね。専門性もそれぞれ違う方達とお話して物事を進める経験ができたことは、今の GM という役割に本当に役立っていると思います。 岡村 : 私が役に立っていると感じる経験は、「 一回深く細かいところまで業務を見てみて理解をしてから、改めて俯瞰した視点で業務を見直してメンバーにお願いする 」という習慣です。この相反する方法をバランス良くできるようになっているというのが、良い点なのかなと思っています。また、深く見るというのは自分が興味を持っている分野でないと中々難しいので、何にでも興味を持って業務に取り組むという心持ちも必要になるかと考えています。 こうした形で業務を分解してメンバーにお願いできるところはしながら、GM としては適切に権限委譲していくというのに役立っています。 エンジニアのピープルマネジメントで意識していること 新居 : 今までのお話でも少し出ている話題になるのですが、GM としてエンジニアのマネジメントで意識しているポイントって、どんなことがありますか。 平山 : 自分達が作る プロダクトへの興味関心・解像度を高め、何を作るのか 。という点において各所コミュニケーションを含めて主体的に動くこと。そうした積み重ねの延長線上にあるエンジニアとしてのキャリア形成を意識しています。 この考え方に加えて「プロダクトの持続可能性を高める」というテーマで、エンジニア・デザイナー・ディレクター・ QA エンジニア・カスタマーサクセスといったプロダクトに関わる人達の専門性を踏まえたタスクの再配布・プロジェクト設計を行う。といった体制作りも推進しようとしています。 新居 : 「プロダクトの持続可能性を高める」というのは、すごく難しい試みですよね。 平山 : 確かにとても難しいなと思います。でも打ち手としては色々あるのではないかと考えていて。「属人性の排除」といった点は、分かりやすいのではないかなと思います。誰かに依存した状態は、組織変更や事業スケールの変化に弱くなりがちですよね。 大事なのは「人から組織への移行」「仕組み化」と捉えています。「できる人を増やす」については、大事だけど秘伝のタレが受け継がれるだけになってしまうので、持続可能性の観点では本質的ではないかなと。 一方で「仕組み化するための計画的な属人性」については推奨できると考えてます。新しいことをいきなり「組織に転換」や「人を増やしてローテする」だと、ナレッジが蓄積されづらく、次の一手が打ちづらくなるので。 新居 : そうした取り組みをするのに、例えばエンジニアだと作ってみたものがプロダクトの目指す方向性とズレが生じていたというような事態があると思うんですが、そうしたズレを事前に仕組みで抑えるような事をしているんですか。 平山 : きっちりとした仕組みなどは作ってはいないです。仕組み化というよりも、そうならないように メンバーから上がってきた疑問などに対する壁打ちなどは気軽にできる ようにしています。また、そういった相談を気軽にできるような自分自身のプロデュースといいますか、キャラ作りみたいなものも含めてメンバーが相談しにくいという空気を作らないようにはしていますね。 新居 : ありがとうございます。岡村さんはどんな意識をしていますか。 岡村 : やっぱり平山さんに全く同感ですね(笑) 付け加えると、ソフト面ですが メンバーであるエンジニアが楽しめる環境作り というところを意識しています。楽しさにも色々な種類があるとは思いますが、単にコーディングするだけではなく、やはりプロダクトそれ自体の提供価値というものをしっかりと共有して、プロダクトを作ること自体に楽しさを感じてもらう…というのは重要じゃないかなと思っています。 また楽しさの別側面ですが、新しいテクノロジーなどもどんどんと取り入れることができるような仕組み作りもしています。島根のメンバーは今回のプロジェクトで初めて Ruby を使うというメンバーも多かったので、ここで楽しさを感じられないと、こうしたテクノロジー自体を嫌いになってしまう恐れがあるなと思ったので。 そうした「楽しい仕事」にしていくと「時間をかけなきゃ」ではなく「時間をかけたい」という感じで取り組めるようになると思います。 GM という役割の醍醐味とは 新居 : それでは終盤になってきましたが、GM の「やりがい」や「醍醐味」はどういったものになりますか。 岡村 : 一番は「ピープルマネジメント」になるんじゃないかと考えています。最近、岸田首相の所信表明演説で聞いて感銘を受けたアフリカの諺ですが「早く行きたいなら 1 人で行け、遠くへ行きたいならみんなで行け」という言葉はピープルマネジメントの本質なんじゃないかなと思っています。 メンバーと一緒に遠くに行くために組織作り などをしていけるのが醍醐味だと感じています。 平山 : メンバーの 個人スキルを高めつつ「チーム」に還元してもらうための仕組み を作っていく。というのは、醍醐味と言えるのかなと。CTO インタビューで田中も言っていますが「チームバリュー」は大切だと考えています。 チームといっても、狭義のチームではなくプロダクトに関わる関係者全員を含めたチームという感じで意識しています。開発組織に閉じないバリューの発揮がメンバーそれぞれでできると心強いと思います。 メドレーで GM やリードエンジニアのロールに向いている方 新居 : 最後になるのですが、メドレーで GM やテックリードといったロールに向いているのは、どんな人だと思いますか。 平山 : 基本的に 物事を前に進めるための解像度を高く描ける人 かなと思っています。何を誰のためにというのを体現しつつ、関係者を引っぱっていける人ですね。メドレーの Our Essentials 全般必要だとは思いますが、その中でも「成果を出す」「自分をアップデート」「組織水準を高める」「革新と改善を主導」というところが必要になるかな。困難な状況でも結果を出しつつ、自分の行動も高めていき、結果組織・プロダクトを良くしていける…というのがメドレーのリードと呼ばれているエンジニアかと思います。 やり方としては、マイクロマネジメントではなく、基本となる考え方をちゃんと伝えてメンバーに学んでもらう…という、やり方ができる人が向いていると思います。 岡村 : 困難な状況であっても何とかして前に進めることができる人 でしょうか。無理矢理に進めるというわけではなく、状況の整理や色々な部署との調整などもしたりして、何とか結果という形にしていける能力がある人がリードとしてメンバーを引っぱっていける人だと思います。 もちろん、フェイズによってメンバーとの関わりなどはちゃんと調整していける柔軟さなども必要になると思います。 新居 : なるほど。本日は色々なお話をしていただきまして、ありがとうございました! さいごに メドレーの GM 二人に仕事について聞いていきました。 前回の CTO インタビューでも語られた開発組織の話ですが、より現場に近い GM という立場のメンバーからの話でまた違った雰囲気などを感じていただけたかと思います。 このような GM を「ぜひやりたい!」という方や「こんな上司の元で自分の力を発揮していきたい!」という方はぜひカジュアルにお話ができればと思います。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに みなさん、こんにちは。エンジニアの新居です。 今回のインタビューは前回の CTO へのインタビュー に続いて、メドレーの開発組織についてご紹介していきたいと思います。 メドレーでは開発組織をリードするロールとして、CTO の他、「グループマネージャ」(以下、GM)という役職があります。今回は医療プラットフォーム(以下、医療 PF)における開発グループの GM を担う二人に、具体的にどのような役割なのか聞いていきます。 インタビュイー紹介 今回の紹介はインタビュー中で前職からメドレー入社の話を聞いているので、シンプルにお送りします。 平山さん 医療 PF 第一本部 プロダクト開発室 第四開発グループ GM。現在は CLINICS 基幹システムチームのマネジメントを担当。 岡村さん 医療 PF 第一本部 プロダクト開発室 第三開発グループ GM。現在はレセコン(電子カルテの機能のうち主に会計など診療報酬の計算をするソフトウェア)新規開発チームのマネジメントを担当。 左から岡村さん、平山さん、筆者 前職までの経歴とメドレー入社の理由について 二人の経験について 新居 : 早速始めていきますが、まずはお二人の経歴を教えていただきたいと思います。 平山 : メドレーは 2 社目の会社となります。前職は SIer で 14~5 年くらい受託開発や客先常駐して開発をしていました。SIer というと場合によっては二次請け・三次請けの開発をしているところもあるんですが、自分は一次請けとして直接顧客と関わる形で業務を行っていました。 顧客のサービスをステークホルダーと提案・折衝しながら、技術選定・要件定義から開発、リリースまで行い、そこからの保守運用といった部分までの一連の流れを様々な業態の顧客と関わらせて頂きました。 新居 : ありがとうございます。かなり自社開発と同じ感覚の開発を長年されてきたんですね。それでは、岡村さんお願いします。 岡村 : 新卒で外資系の IT コンサルティング会社で IT コンサルタントとしてキャリアを始めました。その中でも特に「情報系」と呼ばれるデータアナリティクスや BI (Business Intelligence) などを取り扱う部門で業務をしていました。領域的には金融・製薬・省庁など堅めの領域を主に担当していたので、いわゆる Web アプリケーションを作るというよりは SQL をガリガリ書いてデータ分析を基に業務改善していく…という感じの業務が多かったです。 2 社目はスタートアップ企業の立ち上げフェイズにジョインしました。その会社はいわゆる Insure Tech と呼ばれる保険業界 x テクノロジー領域のサービスを作っている会社でした。そこで、その会社でのプロダクトマネージャ業務と、運転資金を確保するために大手保険会社向けのコンサルタント業務に半々で従事していました。 その後にメドレーにジョインするという経歴です。 岡村さん メドレー入社の経緯について 新居 : ありがとうございます、お二人はメドレーに入社してどのくらい経ちますっけ? 平山 : 私は 3 年半ですね。 岡村 : 私は 4 年経ちました。 新居 : もうそんなに経つんですね。みなさんの前職を転職しようと思った動機とはどんなものだったんですか? 平山 : 前職でプレイングマネージャとして業務をしてきましたが、キャリアを重ねてくると徐々にマネジメントの割合が増えてきます。それ自体は自然なことではあるのですが、前職の延長線上でマネジメントに比重を置くことに対して、危機感を感じていたんです。手を動かす部分を含めて、エンジニアとして良いキャリアを積むには別の環境に身を置く必要があると思い、転職を決意しました。 転職に際しては、自分が 身近に感じるサービスで特に自分の家族が関わるような領域が良いと考えており、医療はそうした意味でぴったりな領域 でした。これらの条件で転職エージェントを通じてメドレーに辿りつきました。 平山さん 岡村 : 先程も少し話しましたが、前職は自社プロダクトと他社向けコンサルティングを並行して運営していた会社でした。しかし、途中から経営方針が変わってコンサルティング業務をメインに据えるようになったんです。自分としては、自社プロダクトを作っている点を大事に考えていたため、転職を考えはじめました。 会社選びの軸としては、もちろん自社サービスが事業の主軸である会社というのが一番でした。他の条件としては「上場一歩手前」くらいのフェイズの会社というものがありました。これは、そのくらいのフェイズの会社であれば今までやっていた事業をいきなりピボットするという可能性が限りなく低いのではないかと考えたからです。そんな感じで会社を探していて、LinkedIn の広告経由でメドレーにアプローチして入社しました。 新居 : 岡村さんは次の会社の事業領域は、意識していたんでしょうか? 岡村 : 医療領域を名指しで考えていたわけではないです。しかし、いわゆる 昔ながらの慣習などが多く残るような業界や領域を、自分達が手がけたサービスで変えていくというのが良い という考えでした。これは前職でも考えていたことだったので、引き続き志向していました。 メドレー入社後にどのような仕事をしていたのか 新居 : ありがとうございます。そんな経緯でメドレーに入社したお二人ですが、入社後はどのような業務をされていたんでしょうか。 平山 : 私は入社後から一貫して CLINICS カルテを担当しています。入社後は医療に関する業務知識を身につけながら、周辺業務から徐々に関わっていきシステム面のキャッチアップをしていきました。一番最初に携わったプロジェクトとしては、CLINICS カルテにおける診察業務のコントロールを担う「診療ステータス」の拡張を行うという、カルテ全体に影響のある大きな改修に携わりました。それ以降も、小さいタスクを捌きつつ、中・大規模な施策に携わってきた感じです。 そこから医療 PF 横断のプロジェクトにも関わっていくようになりました。CLINICS というプロダクトは患者が使用するアプリを始め、調剤・歯科のサービスも提供しています。 医療 PF 全体の体験設計を踏まえて、周辺プロダクトとも積極的に関わっていく必要がある という点は特徴的だと思います。 岡村 : 私は入社当時は「社長室コンサル」という役割で入社し、この部署は M&A などをメインに担当していた部署だったんです。そのため最初にアサインされたのが NaCl メディカル社(現在はメドレー本体に統合)の業務面での PMI でした。これと平行して、現在も引き続き携わっている新しいレセコンを作るプロジェクトのプロダクトマネージャを担当してきました。 新規レセコンは CLINICS カルテと連携しているプロダクトなので、平山さんが率いるチームと連携 しながら、このプロダクトを作っている開発チームを率いています。 GM としてのミッションと就任してから変わったこと・変わらないこと 新居 : 話を聞いていると、お二人とも最初の仕事を順調にスケールアップしていったという形で現在の業務に繋っているんですね。さて、ここからは GM に就任してからの自身の業務がどのように変化してきたのかという点を聞きたいのですが、この点どうでしょうか。 平山 : ミッションはやはり変化しました。GM としてのミッションで一番求められるものは「 プロダクトを前進させる 」という部分と考えています。自身の管掌組織は当然ですが、組織を横断する形の取り組みも必要になります。 「ピープルマネジメント」という点も GM としての変化の1つかなと思います。とはいえ、先ほどの話と通じる部分はあり**「プロダクトに軸足を置いて目線を合わせていくこと」が基本**となります。また、組織上の持続可能性という意味においても「任せていく」というところは大事にしています。メンバーに優秀な方が多いので助けられている部分は多いと感じます。 エンジニアリングという意味では、手を動かす量は減りました。手の動かし方という意味では入社当初から横の動きを見るようにはしていたので GM になって変わったという部分は無いですね。 岡村 : レセコン新規開発プロジェクトでの GM のミッションとして、「プロジェクトの完遂」、「チームメンバーのピープルマネジメント」、「Tech Studio MATSUE(島根県にあるメドレーの拠点。レセコン新規開発プロジェクトの開発をしている)の管理」がメインになります。 GM に就任したタイミングは NaCl メディカル社がメドレー本体に吸収合併されたというタイミングでした。就任前はグループ会社ということもあり、開発業務を受発注するという動きに近い感じで協業してきました。GM になってからは、同じ会社になったのでより近い距離でチーム開発をするようになったり、メンバーのピープルマネジメントの割合が多くなるという変化がありました。 担当領域としてはレセコンのプロダクトマネジメントをしているという部分は変わらない部分です。また平山さんがおっしゃっていたように、いつまでも自分が上にいて指揮するという状態を良しとするのではなく、 メンバーの人をどんどんと上のロールに引き上げていくというのが GM の役割の 1 つ かと思うので、そういう意識でメンバーのマネジメントをしています。 エンジニアリングとマネジメントの比率はプロダクトのフェイズによって変わってくるし、変わるべきかなと思っています。現在は 8 対 2 でマネジメントの比率が多くなっています。一方でプロダクトの初期でどんどんと開発をしていかないといけないという場合には自分も含めて人手をかけて開発していくようにします。 新居 : お話をうかがうと、やはり GM 前よりもマネジメントの比率が多くなっているのではないかと思うのですが、葛藤などはありましたか。 平山 : 元々の自分のスタンスとして「自分も駒の一つ」としてプロダクト開発を推進していくという思考があります。GM の話が来たときにも、やれるかなという不安感はありましたが、今の場面においては自身がやった方が良いと感じて受けました。 岡村 : 私は出自がエンジニアではないというのもあって、葛藤のようなものも平山さんとは違って、特にありませんでした。元々 NaCl メディカル社だった松江オフィスとのハブになるのは、自分しかいないと思っていましたので躊躇のようなものも全く無かったです。 GM で苦労したポイント・どう克服していたか 新居 : ありがとうございます。では、GM になって苦労したとか大変だったというところってどんなところがありましたか。 平山 : 自分は口下手な方なんで、以前より喋る機会が増えたのが困ったところですね(笑) 新居 : 平山さんに口下手な印象は全然ありませんが(笑) 平山 : 他には、これはどちらかというと PdM 属性の仕事にも関わってくるんですが、開発計画の策定は大変です(メドレーでは 3 ヶ月ごとに開発計画を立てている)。 大規模な施策の実施と並行して優先度の高いタスクの差し込みを踏まえて、優先度の再設計や施策のフェージングや運用の検討したりしています。 自分で抱え込む必要はないですが、関係者と調整しつつも最終意思決定は自分になるので、様々な葛藤をしながら取り組んでいます 。 新居 : ピープルマネジメント面では何かありましたか。 平山 : 先程お話した「プロダクトに軸足を置いて目線を合わせていくこと」を大切にコミュニケーションを実施し、日々の業務を通してメンバー一人一人でシナジーが出る・良いキャリアが重ねられるようにしたいと考えていますが、先ほどの開発計画の難しさもここにあります。 新居 : ありがとうございます。では岡村さんはいかがでしょう。 岡村 : 平山さんに全く同感ですね(笑) 冗談は置いておいて、計画外の差し込みタスクなどが入ると、どうしてもチームの負荷が高くなってくるので、そこをコントロールしきれずに申し訳ないなと思うことがあります。 他には、メンバーのキャリア形成をどうするかという点が難しいと思います。 メンバーの志向に合わせての将来像を見据えて現在の業務に取り組んでもらいたい のですが、実際のタスクとそうしたキャリアの方向性が必ずしもぴったりと合うときばかりではないので、ここをどう揃えていくかという点にも苦労はしています。 新居 : なるほど、岡村さんの場合は元々として島根と東京という立地間でのマネジメントをされていたと思うんですが、リモートコミュニケーションが中心という環境での苦労は何かありましたか。 岡村 : 実はこれと言って無いんですよ。それでもちゃんと上手く機能している要因としては、2 つあります。1 つは島根の方に現地のメンバーを取りしきってくれるスキルを持ったメンバーが居てくれたことです。ここは今でも本当に助かっています。 また、当初から技術的な部分も含めて私もメンバーと一緒に勉強しながら、新しい取り組みをするというスタンスを持ってプロジェクトを推進できたことです。ですので、離れたところから命令する人のような感じではなく、 チームで一緒にプロジェクトを進める人という認識を持ってもらえた んじゃないかなと考えています。 もちろん、関係値を作れるまでは私が最初の頃は 1 ヶ月に 1~2 回程度島根に出張して、実際にメンバーと会ったりすることもしていましたが。 GM を担う上で、役に立った経験・知識 新居 : ここからは、お二人が今までの業務の中で積んできた経験・知識の内、現在 GM として活動する上で役に立っているものとして、どんなものがあるのか聞ければと思います。 平山 : そうですね、一番役に立っているなと感じているのは前職で経験した「 様々なステークホルダーとの主体性を持った折衝経験 」です。色々な会社や立場の方とコミュニケーションを取ることが多かったんですね。専門性もそれぞれ違う方達とお話して物事を進める経験ができたことは、今の GM という役割に本当に役立っていると思います。 岡村 : 私が役に立っていると感じる経験は、「 一回深く細かいところまで業務を見てみて理解をしてから、改めて俯瞰した視点で業務を見直してメンバーにお願いする 」という習慣です。この相反する方法をバランス良くできるようになっているというのが、良い点なのかなと思っています。また、深く見るというのは自分が興味を持っている分野でないと中々難しいので、何にでも興味を持って業務に取り組むという心持ちも必要になるかと考えています。 こうした形で業務を分解してメンバーにお願いできるところはしながら、GM としては適切に権限委譲していくというのに役立っています。 エンジニアのピープルマネジメントで意識していること 新居 : 今までのお話でも少し出ている話題になるのですが、GM としてエンジニアのマネジメントで意識しているポイントって、どんなことがありますか。 平山 : 自分達が作る プロダクトへの興味関心・解像度を高め、何を作るのか 。という点において各所コミュニケーションを含めて主体的に動くこと。そうした積み重ねの延長線上にあるエンジニアとしてのキャリア形成を意識しています。 この考え方に加えて「プロダクトの持続可能性を高める」というテーマで、エンジニア・デザイナー・ディレクター・ QA エンジニア・カスタマーサクセスといったプロダクトに関わる人達の専門性を踏まえたタスクの再配布・プロジェクト設計を行う。といった体制作りも推進しようとしています。 新居 : 「プロダクトの持続可能性を高める」というのは、すごく難しい試みですよね。 平山 : 確かにとても難しいなと思います。でも打ち手としては色々あるのではないかと考えていて。「属人性の排除」といった点は、分かりやすいのではないかなと思います。誰かに依存した状態は、組織変更や事業スケールの変化に弱くなりがちですよね。 大事なのは「人から組織への移行」「仕組み化」と捉えています。「できる人を増やす」については、大事だけど秘伝のタレが受け継がれるだけになってしまうので、持続可能性の観点では本質的ではないかなと。 一方で「仕組み化するための計画的な属人性」については推奨できると考えてます。新しいことをいきなり「組織に転換」や「人を増やしてローテする」だと、ナレッジが蓄積されづらく、次の一手が打ちづらくなるので。 新居 : そうした取り組みをするのに、例えばエンジニアだと作ってみたものがプロダクトの目指す方向性とズレが生じていたというような事態があると思うんですが、そうしたズレを事前に仕組みで抑えるような事をしているんですか。 平山 : きっちりとした仕組みなどは作ってはいないです。仕組み化というよりも、そうならないように メンバーから上がってきた疑問などに対する壁打ちなどは気軽にできる ようにしています。また、そういった相談を気軽にできるような自分自身のプロデュースといいますか、キャラ作りみたいなものも含めてメンバーが相談しにくいという空気を作らないようにはしていますね。 新居 : ありがとうございます。岡村さんはどんな意識をしていますか。 岡村 : やっぱり平山さんに全く同感ですね(笑) 付け加えると、ソフト面ですが メンバーであるエンジニアが楽しめる環境作り というところを意識しています。楽しさにも色々な種類があるとは思いますが、単にコーディングするだけではなく、やはりプロダクトそれ自体の提供価値というものをしっかりと共有して、プロダクトを作ること自体に楽しさを感じてもらう…というのは重要じゃないかなと思っています。 また楽しさの別側面ですが、新しいテクノロジーなどもどんどんと取り入れることができるような仕組み作りもしています。島根のメンバーは今回のプロジェクトで初めて Ruby を使うというメンバーも多かったので、ここで楽しさを感じられないと、こうしたテクノロジー自体を嫌いになってしまう恐れがあるなと思ったので。 そうした「楽しい仕事」にしていくと「時間をかけなきゃ」ではなく「時間をかけたい」という感じで取り組めるようになると思います。 GM という役割の醍醐味とは 新居 : それでは終盤になってきましたが、GM の「やりがい」や「醍醐味」はどういったものになりますか。 岡村 : 一番は「ピープルマネジメント」になるんじゃないかと考えています。最近、岸田首相の所信表明演説で聞いて感銘を受けたアフリカの諺ですが「早く行きたいなら 1 人で行け、遠くへ行きたいならみんなで行け」という言葉はピープルマネジメントの本質なんじゃないかなと思っています。 メンバーと一緒に遠くに行くために組織作り などをしていけるのが醍醐味だと感じています。 平山 : メンバーの 個人スキルを高めつつ「チーム」に還元してもらうための仕組み を作っていく。というのは、醍醐味と言えるのかなと。CTO インタビューで田中も言っていますが「チームバリュー」は大切だと考えています。 チームといっても、狭義のチームではなくプロダクトに関わる関係者全員を含めたチームという感じで意識しています。開発組織に閉じないバリューの発揮がメンバーそれぞれでできると心強いと思います。 メドレーで GM やリードエンジニアのロールに向いている方 新居 : 最後になるのですが、メドレーで GM やテックリードといったロールに向いているのは、どんな人だと思いますか。 平山 : 基本的に 物事を前に進めるための解像度を高く描ける人 かなと思っています。何を誰のためにというのを体現しつつ、関係者を引っぱっていける人ですね。メドレーの Our Essentials 全般必要だとは思いますが、その中でも「成果を出す」「自分をアップデート」「組織水準を高める」「革新と改善を主導」というところが必要になるかな。困難な状況でも結果を出しつつ、自分の行動も高めていき、結果組織・プロダクトを良くしていける…というのがメドレーのリードと呼ばれているエンジニアかと思います。 やり方としては、マイクロマネジメントではなく、基本となる考え方をちゃんと伝えてメンバーに学んでもらう…という、やり方ができる人が向いていると思います。 岡村 : 困難な状況であっても何とかして前に進めることができる人 でしょうか。無理矢理に進めるというわけではなく、状況の整理や色々な部署との調整などもしたりして、何とか結果という形にしていける能力がある人がリードとしてメンバーを引っぱっていける人だと思います。 もちろん、フェイズによってメンバーとの関わりなどはちゃんと調整していける柔軟さなども必要になると思います。 新居 : なるほど。本日は色々なお話をしていただきまして、ありがとうございました! さいごに メドレーの GM 二人に仕事について聞いていきました。 前回の CTO インタビューでも語られた開発組織の話ですが、より現場に近い GM という立場のメンバーからの話でまた違った雰囲気などを感じていただけたかと思います。 このような GM を「ぜひやりたい!」という方や「こんな上司の元で自分の力を発揮していきたい!」という方はぜひカジュアルにお話ができればと思います。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに みなさん、こんにちは。技術広報・エンジニアの平木です。 既に 2 月に 発表 されていますが、4 月より弊社の経営体制を大幅に変更しました。開発組織について大きく変わった部分として CTO 2 名体制になった点があります。そこで、なぜ CTO を 2 名にしたのかや、これからのメドレーの開発組織についてを CTO になった 2 人にインタビューしました。 インタビュイー紹介 稲本さん 執行役員。人材プラットフォーム(以下、人材 PF)CTO。独立系 SIer でのインフラエンジニアに始まり、インターネット企業での様々なサービスのインフラ構築を経て、音楽配信サービスやインターネットラジオサービスのサーバサイドエンジニアとして従事。2014 年メドレー入社後は創業時から運営しているジョブメドレーのプロダクト開発に従事。その後、同サービスのリードエンジニア、開発責任者を経て、2023 年 4 月より現職。 田中さん 執行役員。医療プラットフォーム(以下、医療 PF)CTO。独立系 SIer でのアプリケーションエンジニアや IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとしてソーシャルゲームや動画サービスなどの開発立ち上げに従事。2016 年メドレー入社後は CLINICS カルテの立ち上げを経験。その後 CLINICS 開発責任者を経て医療 PF プロダクト横断基盤の立ち上げ・開発責任者を経て 2023 年 4 月より現職。 左から田中さん、稲本さん CTO を 2 名体制にして開発組織の体制をアップデートした理由 平木 : さっそくですが、2023 年 4 月よりお二人がそれぞれ人材 PF と医療 PF の CTO に就任されて、改めて開発組織のアップデートがされましたが、どういった背景でこの体制になったんでしょうか。 田中 : 会社全体の組織設計として医療 PF と人材 PF に分かれたというのがまず最初にあったんですが、当初、開発組織は両 PF を横断する形で 1 人の CTO が見ていました。各 PF 内にそれぞれプロダクトがあり開発もプロダクトごとにチームを分けていましたが、それぞれの開発チームが有機的に動きつつも組織全体の小回りの効きやすさなども考えて、こうした体制になっていました。 ただ、時間が経つに連れ段々と開発チームも各 PF 自体 の規模も大きくなってきたので、 1 人の CTO ではマネジメントが難しくなってきました。また医療 PF と人材 PF で共通する開発組織の文化などはありますが、扱っている事業内容の差異から選定技術やプロジェクトの進め方などの違う部分も出てきたので、それぞれの PF で CTO を置いて組織面・技術面の課題に対応しようということになったためです。 稲本 : 開発組織の人数としては 100 名を越えているのですが、それだけではなく就業場所の違いやサービスの多様化など、それぞれの PF で特性が違う課題がこれから先も出てくるだろうということで、先を見据えての開発組織のアップデートの一環として CTO 2 名体制にしたというところですね。 平木 : これからの組織の変化を見据えた動きだということですね。 新しい開発体制になって変わっていく部分・変わらない部分 平木 : 新体制になってまだ日が浅いですが、これから変えていく部分というのはどういったところになっていくんでしょうか。 稲本 : 自分達 2 人が持っている色々な役割を分解していって他のメンバーへ役割を委譲していくことですね。CTO が色々持ちすぎると組織のスケールに対しボトルネックになることは目に見えているので。 それ以外には、役割を担ったチームやメンバーが自身で思考し行動しやすい組織にしていくことで、更なるチャレンジがしやすかったりパフォーマンスを発揮しやすい状態にしていけたらと考えています。 平木 : なるほど。田中さんは何かありますか。 田中 : 「役割の明確化」もその 1 つですが、将来取り組むべき課題に向けて中長期を見据えた開発組織の設計や運営に向き合うフェイズになってきたと感じており、強化していきたいと考えています。 平木 : 将来の課題に対して技術・組織なども先手を打っていくイメージですね。 新体制での開発組織のミッション 平木 : それでは新体制になって改めて開発組織のミッションとしてはどういったものがありますか。 田中 : 会社全体のミッションと基本は一緒ではあります。「医療」という大きい領域の課題に対して、何のために開発をするのかや、その開発をすることによってどういった顧客価値を提供できるのかというところは、ちゃんとミッションに紐付いている部分なので変えずにいきます。 その上で、有効な施策をどれだけ素早くデリバリーできるかというところには今以上に注力していきたいところです。そのためには無駄を省き、やるべき事に集中してより生産性の高い開発を目指します。 プロダクト開発において無駄な機能開発を行なうと、後からそこを削ろうと思っても難しいですし、作った結果誰も幸せにならないということになってしまいます。こうならないためにもスピード感は落とさないで、事業側メンバーと協力しながら適切な要件整理をしたりなど、どうやって顧客へ適切なプロダクトを届けるか?という部分により重きを置いていきたいです。 事業とプロダクト開発の関係性 平木 : さて、次に先程のお話にも出てきましたが、事業とプロダクト開発の関係性についてお聞きします。お二人が考えるこの関係性はどのようなものでしょうか。 田中 : ここも以前から変わらないスタンスですが、改めて定義するなら「テクノロジーを最大限に使って医療領域の課題を解決する」という関係になります。テクノロジーとメドレーの事業である医療領域の課題解決という 2 つの側面のどれが欠けても、プロダクトとして良いものは生まれないと考えています。 平木 : 主に Web のテクノロジーを適材適所で課題解決のために使っていくというスタンスですね。 田中 : そうですね。先に来るのはあくまでも、「医療領域の課題解決をする」という部分になるのですが、その手段として必要な技術は全部使っていこうという姿勢です。なので、まずは開発組織のメンバー一人ひとりが、ドメインの理解をするということが始めの一歩です。そうして課題の本質を見極めて解決に必要な手段は何があるのかを考えていく必要が出てきます。 解決のために最適であれば、その技術の新旧や使用実績などを問わず取り入れて開発していくという心構えですね。そのためにはきちんと技術動向を追っていき日頃からユースケースなどを想像しながら、その技術を触ったりする必要も出てきます。 稲本 : ドメイン知識とテクノロジーを最大限に駆使し、プロダクト開発を通して価値を届けるというのが我々のミッションなのかなと思います。 稲本さん 平木 : 医療領域の未来を変えていくために、技術を最大限に用いたプロダクトドリブンであることが大切ということですね。 田中 : メドレーではそれだけじゃない部分も意識していますね。一言で「プロダクト」と言ってもサービスとそれを作る開発者というだけではなく、顧客接点を持つ事業部などのメンバー全ての動きも合わせて「プロダクト」という意識を持っています。 平木 : なるほど、プロダクトに関わっている人全員を合わせて「プロダクト」だよということですか。 田中 : 例えば開発メンバーが良いプロダクトを提供したとしても、そのプロダクトを使ったユーザーが疑問に思った部分があったとします。そこで問い合わせをしたときに、その体験が良くない場合はやっぱり「プロダクトに関する体験が良くない」という印象になってしまいますよね。これはセールスなど他の領域でも起き得る話ですが、メドレーではそういった部分も含めて「プロダクト開発」という意識を持っているので、他部署だからということではなくトータルでユーザーに価値を感じてもらえるようにしていくということを大切にしています。 平木 : 確かにユーザーはそういった関わるもの全部一緒に「プロダクト」と思いますもんね。 稲本 : 日々の業務の中で直接ユーザーと対話してくれている方々がたくさんいます。 Our Essentials (以下、OE)で特に好きなものの一つで「信頼を獲得する」というものがあります。OE 自体は主に社内向けのメッセージにはなるんですが、日々の業務の中で他者からの信頼を得ることが出来るような振る舞いや成果を出すこと、その信頼の積み重ねがプロダクト全体にも反映され、結果としてユーザーからも信頼される/価値を感じてもらえるプロダクトの提供につながっているのだと感じています。 プロダクトと技術の関係について 平木 : もう少し開発についての話を掘り下げていこうと思います。弊社では現在のところ Ruby や Ruby on Rails(以下 Rails)で作られているプロダクトが大半ではありますが、Go や Node.js などをメインに使っているプロダクトもありますよね。そうした技術選定のときの基本姿勢としてはどういったものがありますか。 田中 : 基本として、「それぞれのプロダクトにとって現時点でこの技術で開発するのが、良いことだよね」というのを大切にしています。組織的に知見が多いということもあって、Rails を使っているプロダクトが多いのはそうなんですが、この技術じゃないとダメという縛りがあるわけではないです。Go や Node.js にしても、そのプロダクトに現状最適だという視点で技術選定をしています。 稲本 : 今までできなかったことが、新しい技術を使ったら解決するのにという場面も結構あると思います。そういうときに「今使ってないから…」というのではなく、その技術を使ったほうがプロダクトにプラスになると判断したら使っていくようにしています。 田中 : ですので、今プロダクトで使っている技術で守っていくというより、個々人でアンテナを張って新しい技術は積極的にキャッチアップしていきながら、チーム内で「これ使うと良さそうだからやっていこう」という感じですね。 平木 : 先程も出てきたユーザーに価値提供を素早くするという要素の一つということですね。そうすると、例えば開発者体験を良くするような技術やフローなんかを取り入れるというのも、そうした志向の一部ということになりますか? 田中 : そうですね。良いものを早くユーザーに届けるという一側面になります。開発者体験を良くするというのが最終的な目的ではなくて、その結果としてチームの生産性が上がるのなら体験を良くすると良いよねという。プロダクトコードの負債解消とかもそうですが、バランスが確かに難しいのですが、こういったところを疎かにすると結果として顧客への十分な価値提供ができなくなってしまうリスクもあるので、短期と中長期できちんとバランシングしつつ開発に取り組んでいきたいと思っています。 田中さん 平木 : 改めてここで現在の開発体制について伺っていければと思います。 田中 : 人材 PF も医療 PF もプロダクトに紐付いて複数のチームに分かれていて、チームの人数規模としてみると多少の上下はあるんですが、プロダクトマネージャ(以下、PdM) やデザイナー、エンジニアを合わせて 10 人前後というチームが多いです。 平木 : そうしたチーム内で、特にエンジニアはどのような役割分担をしていることが多いですか? 稲本 : もちろんチームによっても違ってくるんですが、チームの中に PM と TL を置いて、そのチームの中にメンバーが数人いる形が多いですね。この単位をベースにして toC 向けのプロジェクト、toB 向けのプロジェクトのような感じでチームを分けて開発を行なっています。 田中 : メンバー個々人はサーバサイドやフロントエンド、インフラのような得意領域がありそこを考慮した開発をしていっていますが、サーバサイドだけやる人という形ではなく、得意領域と隣り合う領域についても意識し、可能な限り理解してコラボレーションしていこうというやり方を取っています。これによりコミュニケーションコストが下がることで生産性高く、品質向上にも寄与するというのが理由になります。 もちろんプロとして自分の得意分野で力を存分に発揮はしてもらうというのは大前提ですが、その強みを周囲のメンバーに還元しつつ、得意領域以外はそこが得意なメンバーから還元してもらいながら、良いプロダクトを作っていくというのがメドレーの開発組織のスタンスになります。 開発組織の雰囲気やメンバーの働き方について 平木 : 今まで開発組織の制度的な側面を中心に聞いてきましたが、組織のソフト面についてお聞きします。メドレーの開発組織の雰囲気ですが、どんな雰囲気だったりしますか? 稲本 : 全体的にはわりと和やかな雰囲気は持ちつつも、各自のバリューに対してはストイックな感じかなと思います。 自分たちで決めた期日など守るところはしっかり守ろうという意識を持ちつつ、無理な期日になりそうであればスケジュールやスコープを調整したり、プロジェクトの進め方で上手く行ったこと/行かなかったことを振り返りを通して改善を図ったりするなど、当たり前のことを高い水準でやり遂げていく習慣が根付いていると思います。 コミュニケーションに関しては、非同期コミュニケーションがベースではあります。口頭での相談なんかももちろんしますが、認識がずれないように話したことを issue などに残したりしてます。 雑談みたいなものはミーティングの後半パートにあえて雑談パートを作るなどしていますが、業務中にずっとするようなことはないですね。開発中はそれぞれが集中していることが多いです。 田中 : チームごとに違いますが、朝会や夕会などでちゃんとコミュニケーションが取れるようにしているということが多いですね。なので、あんまり他の時間に雑談みたいなものが必要ないという感じです。 平木 : 和やかながらも、締めるところは締めるという雰囲気ですね。開発チームの皆さんはどんな働き方をしている人が多いですか? 稲本 : これも個々人で違っていますが出社とリモートのハイブリッドな方が多いかと思います。 やはり開発に集中したい場面ではリモートの方が割り込みも少なくなりやすく集中しやすいですし、物事の認識を揃えたり決めていく場合は、ミーティングなどで実際に顔を合わせた方が効率も良いことはあると思いますね。 田中 : 自分のバリューを最大化できるように働いていこうというのが基本になっています。 平木 : 各自ちゃんと考えてプロダクトに貢献できるようにということですね。そうした働き方の先にキャリアパスがあると思いますが、その観点ではどのようなパスがありますか? 田中 : 大きく分けては、マネジメントをメインにするか、テクニカル部分を特化させたスペシャリストとなるかという 2 つになります。役割分担にもちょっと関わりますが、内容としては、例えば PdM を目指していくというのも道としてはありますし、自分の得意領域を最大限に伸ばしてテックリードとして技術をもってプロダクトを引っぱっていくという道なんかもあります。1on1 や評価などでそうした部分は本人と刷り合わせをしていきながら決めていく形になります。 平木 : 評価のお話が出てきましたが、どういった観点で評価されますか? 稲本 : ベースとしては会社として決めた OE に沿ったものになります。OE を踏まえつつ、業務目標や技術的な目標を立ててそれができたかどうかが基準になりますね。 立てた目標が色々な理由で達成できなかったというケースももちろん出てくると思いますが、内容として自分はどういった部分は頑張ったとか、どういう理由で達成できなかったなどの自分なりの説明ができるかというのを重視しています。 田中 : 目標はチームバリューにいかに寄与したかを重視しています。OE に基づきチームとして達成しないといけない目標があってそれをブレイクダウンした上で、自分が寄与できるのはどこかという感じで目標にしてもらいます。一見目に見えにくい動きだったとしても、ちゃんとチームに貢献しているねとなれば、もちろん評価の対象になりますし、逆に例えばいくら頑張って新技術を習得したとなってもチームに貢献してなければ評価はされにくいです。 平木 : ありがとうございます。最後にメドレーで開発することの良さを教えてもらえればと思います。 田中 : メドレーは長期を見据えて課題に取り組んでいる会社であり、泥臭く地道に積み重ねなければいけないこともすごく多いです。そのため短期で結果が見えづらいこともあるかもしれません。ですが、医療領域でどっしりと腰を据えて長期的に本質を捉えた開発をしているため、技術面だけではなくプロダクトデザインとして何のために、どのようなアプローチが適切か、という思考力や設計能力も身に付きやすいのではないかと思います。 稲本 : 長期で課題に取り組むとなると「同じことの繰り返しではないか」と感じる方もいるかもしれませんが、プロダクトが成長すればフェーズも変わりますし、取り組むことにも変化が生じていくので成長や変化を楽しめる人には合っているのではないかと思います。 さいごに 新体制で CTO 2 人にメドレーの開発組織について色々と語ってもらいました。 2 人も話をしていましたが、メドレーでは長期思考・未来志向の考え方をベースに、ユーザーの本質的な課題はなにか、どうすればそれらを解決できるか?という部分にフォーカスを当てて開発をするという志向がとても強いと思います。 そうした環境でじっくりと確実にユーザーに価値を提供できるという仕事だと思いますので、ご興味を持たれた方はぜひカジュアルにお話をさせていただければと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに みなさん、こんにちは。技術広報・エンジニアの平木です。 既に 2 月に 発表 されていますが、4 月より弊社の経営体制を大幅に変更しました。開発組織について大きく変わった部分として CTO 2 名体制になった点があります。そこで、なぜ CTO を 2 名にしたのかや、これからのメドレーの開発組織についてを CTO になった 2 人にインタビューしました。 インタビュイー紹介 稲本さん 執行役員。人材プラットフォーム(以下、人材 PF)CTO。独立系 SIer でのインフラエンジニアに始まり、インターネット企業での様々なサービスのインフラ構築を経て、音楽配信サービスやインターネットラジオサービスのサーバサイドエンジニアとして従事。2014 年メドレー入社後は創業時から運営しているジョブメドレーのプロダクト開発に従事。その後、同サービスのリードエンジニア、開発責任者を経て、2023 年 4 月より現職。 田中さん 執行役員。医療プラットフォーム(以下、医療 PF)CTO。独立系 SIer でのアプリケーションエンジニアや IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとしてソーシャルゲームや動画サービスなどの開発立ち上げに従事。2016 年メドレー入社後は CLINICS カルテの立ち上げを経験。その後 CLINICS 開発責任者を経て医療 PF プロダクト横断基盤の立ち上げ・開発責任者を経て 2023 年 4 月より現職。 左から田中さん、稲本さん CTO を 2 名体制にして開発組織の体制をアップデートした理由 平木 : さっそくですが、2023 年 4 月よりお二人がそれぞれ人材 PF と医療 PF の CTO に就任されて、改めて開発組織のアップデートがされましたが、どういった背景でこの体制になったんでしょうか。 田中 : 会社全体の組織設計として医療 PF と人材 PF に分かれたというのがまず最初にあったんですが、当初、開発組織は両 PF を横断する形で 1 人の CTO が見ていました。各 PF 内にそれぞれプロダクトがあり開発もプロダクトごとにチームを分けていましたが、それぞれの開発チームが有機的に動きつつも組織全体の小回りの効きやすさなども考えて、こうした体制になっていました。 ただ、時間が経つに連れ段々と開発チームも各 PF 自体 の規模も大きくなってきたので、 1 人の CTO ではマネジメントが難しくなってきました。また医療 PF と人材 PF で共通する開発組織の文化などはありますが、扱っている事業内容の差異から選定技術やプロジェクトの進め方などの違う部分も出てきたので、それぞれの PF で CTO を置いて組織面・技術面の課題に対応しようということになったためです。 稲本 : 開発組織の人数としては 100 名を越えているのですが、それだけではなく就業場所の違いやサービスの多様化など、それぞれの PF で特性が違う課題がこれから先も出てくるだろうということで、先を見据えての開発組織のアップデートの一環として CTO 2 名体制にしたというところですね。 平木 : これからの組織の変化を見据えた動きだということですね。 新しい開発体制になって変わっていく部分・変わらない部分 平木 : 新体制になってまだ日が浅いですが、これから変えていく部分というのはどういったところになっていくんでしょうか。 稲本 : 自分達 2 人が持っている色々な役割を分解していって他のメンバーへ役割を委譲していくことですね。CTO が色々持ちすぎると組織のスケールに対しボトルネックになることは目に見えているので。 それ以外には、役割を担ったチームやメンバーが自身で思考し行動しやすい組織にしていくことで、更なるチャレンジがしやすかったりパフォーマンスを発揮しやすい状態にしていけたらと考えています。 平木 : なるほど。田中さんは何かありますか。 田中 : 「役割の明確化」もその 1 つですが、将来取り組むべき課題に向けて中長期を見据えた開発組織の設計や運営に向き合うフェイズになってきたと感じており、強化していきたいと考えています。 平木 : 将来の課題に対して技術・組織なども先手を打っていくイメージですね。 新体制での開発組織のミッション 平木 : それでは新体制になって改めて開発組織のミッションとしてはどういったものがありますか。 田中 : 会社全体のミッションと基本は一緒ではあります。「医療」という大きい領域の課題に対して、何のために開発をするのかや、その開発をすることによってどういった顧客価値を提供できるのかというところは、ちゃんとミッションに紐付いている部分なので変えずにいきます。 その上で、有効な施策をどれだけ素早くデリバリーできるかというところには今以上に注力していきたいところです。そのためには無駄を省き、やるべき事に集中してより生産性の高い開発を目指します。 プロダクト開発において無駄な機能開発を行なうと、後からそこを削ろうと思っても難しいですし、作った結果誰も幸せにならないということになってしまいます。こうならないためにもスピード感は落とさないで、事業側メンバーと協力しながら適切な要件整理をしたりなど、どうやって顧客へ適切なプロダクトを届けるか?という部分により重きを置いていきたいです。 事業とプロダクト開発の関係性 平木 : さて、次に先程のお話にも出てきましたが、事業とプロダクト開発の関係性についてお聞きします。お二人が考えるこの関係性はどのようなものでしょうか。 田中 : ここも以前から変わらないスタンスですが、改めて定義するなら「テクノロジーを最大限に使って医療領域の課題を解決する」という関係になります。テクノロジーとメドレーの事業である医療領域の課題解決という 2 つの側面のどれが欠けても、プロダクトとして良いものは生まれないと考えています。 平木 : 主に Web のテクノロジーを適材適所で課題解決のために使っていくというスタンスですね。 田中 : そうですね。先に来るのはあくまでも、「医療領域の課題解決をする」という部分になるのですが、その手段として必要な技術は全部使っていこうという姿勢です。なので、まずは開発組織のメンバー一人ひとりが、ドメインの理解をするということが始めの一歩です。そうして課題の本質を見極めて解決に必要な手段は何があるのかを考えていく必要が出てきます。 解決のために最適であれば、その技術の新旧や使用実績などを問わず取り入れて開発していくという心構えですね。そのためにはきちんと技術動向を追っていき日頃からユースケースなどを想像しながら、その技術を触ったりする必要も出てきます。 稲本 : ドメイン知識とテクノロジーを最大限に駆使し、プロダクト開発を通して価値を届けるというのが我々のミッションなのかなと思います。 稲本さん 平木 : 医療領域の未来を変えていくために、技術を最大限に用いたプロダクトドリブンであることが大切ということですね。 田中 : メドレーではそれだけじゃない部分も意識していますね。一言で「プロダクト」と言ってもサービスとそれを作る開発者というだけではなく、顧客接点を持つ事業部などのメンバー全ての動きも合わせて「プロダクト」という意識を持っています。 平木 : なるほど、プロダクトに関わっている人全員を合わせて「プロダクト」だよということですか。 田中 : 例えば開発メンバーが良いプロダクトを提供したとしても、そのプロダクトを使ったユーザーが疑問に思った部分があったとします。そこで問い合わせをしたときに、その体験が良くない場合はやっぱり「プロダクトに関する体験が良くない」という印象になってしまいますよね。これはセールスなど他の領域でも起き得る話ですが、メドレーではそういった部分も含めて「プロダクト開発」という意識を持っているので、他部署だからということではなくトータルでユーザーに価値を感じてもらえるようにしていくということを大切にしています。 平木 : 確かにユーザーはそういった関わるもの全部一緒に「プロダクト」と思いますもんね。 稲本 : 日々の業務の中で直接ユーザーと対話してくれている方々がたくさんいます。 Our Essentials (以下、OE)で特に好きなものの一つで「信頼を獲得する」というものがあります。OE 自体は主に社内向けのメッセージにはなるんですが、日々の業務の中で他者からの信頼を得ることが出来るような振る舞いや成果を出すこと、その信頼の積み重ねがプロダクト全体にも反映され、結果としてユーザーからも信頼される/価値を感じてもらえるプロダクトの提供につながっているのだと感じています。 プロダクトと技術の関係について 平木 : もう少し開発についての話を掘り下げていこうと思います。弊社では現在のところ Ruby や Ruby on Rails(以下 Rails)で作られているプロダクトが大半ではありますが、Go や Node.js などをメインに使っているプロダクトもありますよね。そうした技術選定のときの基本姿勢としてはどういったものがありますか。 田中 : 基本として、「それぞれのプロダクトにとって現時点でこの技術で開発するのが、良いことだよね」というのを大切にしています。組織的に知見が多いということもあって、Rails を使っているプロダクトが多いのはそうなんですが、この技術じゃないとダメという縛りがあるわけではないです。Go や Node.js にしても、そのプロダクトに現状最適だという視点で技術選定をしています。 稲本 : 今までできなかったことが、新しい技術を使ったら解決するのにという場面も結構あると思います。そういうときに「今使ってないから…」というのではなく、その技術を使ったほうがプロダクトにプラスになると判断したら使っていくようにしています。 田中 : ですので、今プロダクトで使っている技術で守っていくというより、個々人でアンテナを張って新しい技術は積極的にキャッチアップしていきながら、チーム内で「これ使うと良さそうだからやっていこう」という感じですね。 平木 : 先程も出てきたユーザーに価値提供を素早くするという要素の一つということですね。そうすると、例えば開発者体験を良くするような技術やフローなんかを取り入れるというのも、そうした志向の一部ということになりますか? 田中 : そうですね。良いものを早くユーザーに届けるという一側面になります。開発者体験を良くするというのが最終的な目的ではなくて、その結果としてチームの生産性が上がるのなら体験を良くすると良いよねという。プロダクトコードの負債解消とかもそうですが、バランスが確かに難しいのですが、こういったところを疎かにすると結果として顧客への十分な価値提供ができなくなってしまうリスクもあるので、短期と中長期できちんとバランシングしつつ開発に取り組んでいきたいと思っています。 田中さん 平木 : 改めてここで現在の開発体制について伺っていければと思います。 田中 : 人材 PF も医療 PF もプロダクトに紐付いて複数のチームに分かれていて、チームの人数規模としてみると多少の上下はあるんですが、プロダクトマネージャ(以下、PdM) やデザイナー、エンジニアを合わせて 10 人前後というチームが多いです。 平木 : そうしたチーム内で、特にエンジニアはどのような役割分担をしていることが多いですか? 稲本 : もちろんチームによっても違ってくるんですが、チームの中に PM と TL を置いて、そのチームの中にメンバーが数人いる形が多いですね。この単位をベースにして toC 向けのプロジェクト、toB 向けのプロジェクトのような感じでチームを分けて開発を行なっています。 田中 : メンバー個々人はサーバサイドやフロントエンド、インフラのような得意領域がありそこを考慮した開発をしていっていますが、サーバサイドだけやる人という形ではなく、得意領域と隣り合う領域についても意識し、可能な限り理解してコラボレーションしていこうというやり方を取っています。これによりコミュニケーションコストが下がることで生産性高く、品質向上にも寄与するというのが理由になります。 もちろんプロとして自分の得意分野で力を存分に発揮はしてもらうというのは大前提ですが、その強みを周囲のメンバーに還元しつつ、得意領域以外はそこが得意なメンバーから還元してもらいながら、良いプロダクトを作っていくというのがメドレーの開発組織のスタンスになります。 開発組織の雰囲気やメンバーの働き方について 平木 : 今まで開発組織の制度的な側面を中心に聞いてきましたが、組織のソフト面についてお聞きします。メドレーの開発組織の雰囲気ですが、どんな雰囲気だったりしますか? 稲本 : 全体的にはわりと和やかな雰囲気は持ちつつも、各自のバリューに対してはストイックな感じかなと思います。 自分たちで決めた期日など守るところはしっかり守ろうという意識を持ちつつ、無理な期日になりそうであればスケジュールやスコープを調整したり、プロジェクトの進め方で上手く行ったこと/行かなかったことを振り返りを通して改善を図ったりするなど、当たり前のことを高い水準でやり遂げていく習慣が根付いていると思います。 コミュニケーションに関しては、非同期コミュニケーションがベースではあります。口頭での相談なんかももちろんしますが、認識がずれないように話したことを issue などに残したりしてます。 雑談みたいなものはミーティングの後半パートにあえて雑談パートを作るなどしていますが、業務中にずっとするようなことはないですね。開発中はそれぞれが集中していることが多いです。 田中 : チームごとに違いますが、朝会や夕会などでちゃんとコミュニケーションが取れるようにしているということが多いですね。なので、あんまり他の時間に雑談みたいなものが必要ないという感じです。 平木 : 和やかながらも、締めるところは締めるという雰囲気ですね。開発チームの皆さんはどんな働き方をしている人が多いですか? 稲本 : これも個々人で違っていますが出社とリモートのハイブリッドな方が多いかと思います。 やはり開発に集中したい場面ではリモートの方が割り込みも少なくなりやすく集中しやすいですし、物事の認識を揃えたり決めていく場合は、ミーティングなどで実際に顔を合わせた方が効率も良いことはあると思いますね。 田中 : 自分のバリューを最大化できるように働いていこうというのが基本になっています。 平木 : 各自ちゃんと考えてプロダクトに貢献できるようにということですね。そうした働き方の先にキャリアパスがあると思いますが、その観点ではどのようなパスがありますか? 田中 : 大きく分けては、マネジメントをメインにするか、テクニカル部分を特化させたスペシャリストとなるかという 2 つになります。役割分担にもちょっと関わりますが、内容としては、例えば PdM を目指していくというのも道としてはありますし、自分の得意領域を最大限に伸ばしてテックリードとして技術をもってプロダクトを引っぱっていくという道なんかもあります。1on1 や評価などでそうした部分は本人と刷り合わせをしていきながら決めていく形になります。 平木 : 評価のお話が出てきましたが、どういった観点で評価されますか? 稲本 : ベースとしては会社として決めた OE に沿ったものになります。OE を踏まえつつ、業務目標や技術的な目標を立ててそれができたかどうかが基準になりますね。 立てた目標が色々な理由で達成できなかったというケースももちろん出てくると思いますが、内容として自分はどういった部分は頑張ったとか、どういう理由で達成できなかったなどの自分なりの説明ができるかというのを重視しています。 田中 : 目標はチームバリューにいかに寄与したかを重視しています。OE に基づきチームとして達成しないといけない目標があってそれをブレイクダウンした上で、自分が寄与できるのはどこかという感じで目標にしてもらいます。一見目に見えにくい動きだったとしても、ちゃんとチームに貢献しているねとなれば、もちろん評価の対象になりますし、逆に例えばいくら頑張って新技術を習得したとなってもチームに貢献してなければ評価はされにくいです。 平木 : ありがとうございます。最後にメドレーで開発することの良さを教えてもらえればと思います。 田中 : メドレーは長期を見据えて課題に取り組んでいる会社であり、泥臭く地道に積み重ねなければいけないこともすごく多いです。そのため短期で結果が見えづらいこともあるかもしれません。ですが、医療領域でどっしりと腰を据えて長期的に本質を捉えた開発をしているため、技術面だけではなくプロダクトデザインとして何のために、どのようなアプローチが適切か、という思考力や設計能力も身に付きやすいのではないかと思います。 稲本 : 長期で課題に取り組むとなると「同じことの繰り返しではないか」と感じる方もいるかもしれませんが、プロダクトが成長すればフェーズも変わりますし、取り組むことにも変化が生じていくので成長や変化を楽しめる人には合っているのではないかと思います。 さいごに 新体制で CTO 2 人にメドレーの開発組織について色々と語ってもらいました。 2 人も話をしていましたが、メドレーでは長期思考・未来志向の考え方をベースに、ユーザーの本質的な課題はなにか、どうすればそれらを解決できるか?という部分にフォーカスを当てて開発をするという志向がとても強いと思います。 そうした環境でじっくりと確実にユーザーに価値を提供できるという仕事だと思いますので、ご興味を持たれた方はぜひカジュアルにお話をさせていただければと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに みなさん、こんにちは。技術広報・エンジニアの平木です。 既に 2 月に 発表 されていますが、4 月より弊社の経営体制を大幅に変更しました。開発組織について大きく変わった部分として CTO 2 名体制になった点があります。そこで、なぜ CTO を 2 名にしたのかや、これからのメドレーの開発組織についてを CTO になった 2 人にインタビューしました。 インタビュイー紹介 稲本さん 執行役員。人材プラットフォーム(以下、人材 PF)CTO。独立系 SIer でのインフラエンジニアに始まり、インターネット企業での様々なサービスのインフラ構築を経て、音楽配信サービスやインターネットラジオサービスのサーバサイドエンジニアとして従事。2014 年メドレー入社後は創業時から運営しているジョブメドレーのプロダクト開発に従事。その後、同サービスのリードエンジニア、開発責任者を経て、2023 年 4 月より現職。 田中さん 執行役員。医療プラットフォーム(以下、医療 PF)CTO。独立系 SIer でのアプリケーションエンジニアや IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとしてソーシャルゲームや動画サービスなどの開発立ち上げに従事。2016 年メドレー入社後は CLINICS カルテの立ち上げを経験。その後 CLINICS 開発責任者を経て医療 PF プロダクト横断基盤の立ち上げ・開発責任者を経て 2023 年 4 月より現職。 左から田中さん、稲本さん CTO を 2 名体制にして開発組織の体制をアップデートした理由 平木 : さっそくですが、2023 年 4 月よりお二人がそれぞれ人材 PF と医療 PF の CTO に就任されて、改めて開発組織のアップデートがされましたが、どういった背景でこの体制になったんでしょうか。 田中 : 会社全体の組織設計として医療 PF と人材 PF に分かれたというのがまず最初にあったんですが、当初、開発組織は両 PF を横断する形で 1 人の CTO が見ていました。各 PF 内にそれぞれプロダクトがあり開発もプロダクトごとにチームを分けていましたが、それぞれの開発チームが有機的に動きつつも組織全体の小回りの効きやすさなども考えて、こうした体制になっていました。 ただ、時間が経つに連れ段々と開発チームも各 PF 自体 の規模も大きくなってきたので、 1 人の CTO ではマネジメントが難しくなってきました。また医療 PF と人材 PF で共通する開発組織の文化などはありますが、扱っている事業内容の差異から選定技術やプロジェクトの進め方などの違う部分も出てきたので、それぞれの PF で CTO を置いて組織面・技術面の課題に対応しようということになったためです。 稲本 : 開発組織の人数としては 100 名を越えているのですが、それだけではなく就業場所の違いやサービスの多様化など、それぞれの PF で特性が違う課題がこれから先も出てくるだろうということで、先を見据えての開発組織のアップデートの一環として CTO 2 名体制にしたというところですね。 平木 : これからの組織の変化を見据えた動きだということですね。 新しい開発体制になって変わっていく部分・変わらない部分 平木 : 新体制になってまだ日が浅いですが、これから変えていく部分というのはどういったところになっていくんでしょうか。 稲本 : 自分達 2 人が持っている色々な役割を分解していって他のメンバーへ役割を委譲していくことですね。CTO が色々持ちすぎると組織のスケールに対しボトルネックになることは目に見えているので。 それ以外には、役割を担ったチームやメンバーが自身で思考し行動しやすい組織にしていくことで、更なるチャレンジがしやすかったりパフォーマンスを発揮しやすい状態にしていけたらと考えています。 平木 : なるほど。田中さんは何かありますか。 田中 : 「役割の明確化」もその 1 つですが、将来取り組むべき課題に向けて中長期を見据えた開発組織の設計や運営に向き合うフェイズになってきたと感じており、強化していきたいと考えています。 平木 : 将来の課題に対して技術・組織なども先手を打っていくイメージですね。 新体制での開発組織のミッション 平木 : それでは新体制になって改めて開発組織のミッションとしてはどういったものがありますか。 田中 : 会社全体のミッションと基本は一緒ではあります。「医療」という大きい領域の課題に対して、何のために開発をするのかや、その開発をすることによってどういった顧客価値を提供できるのかというところは、ちゃんとミッションに紐付いている部分なので変えずにいきます。 その上で、有効な施策をどれだけ素早くデリバリーできるかというところには今以上に注力していきたいところです。そのためには無駄を省き、やるべき事に集中してより生産性の高い開発を目指します。 プロダクト開発において無駄な機能開発を行なうと、後からそこを削ろうと思っても難しいですし、作った結果誰も幸せにならないということになってしまいます。こうならないためにもスピード感は落とさないで、事業側メンバーと協力しながら適切な要件整理をしたりなど、どうやって顧客へ適切なプロダクトを届けるか?という部分により重きを置いていきたいです。 事業とプロダクト開発の関係性 平木 : さて、次に先程のお話にも出てきましたが、事業とプロダクト開発の関係性についてお聞きします。お二人が考えるこの関係性はどのようなものでしょうか。 田中 : ここも以前から変わらないスタンスですが、改めて定義するなら「テクノロジーを最大限に使って医療領域の課題を解決する」という関係になります。テクノロジーとメドレーの事業である医療領域の課題解決という 2 つの側面のどれが欠けても、プロダクトとして良いものは生まれないと考えています。 平木 : 主に Web のテクノロジーを適材適所で課題解決のために使っていくというスタンスですね。 田中 : そうですね。先に来るのはあくまでも、「医療領域の課題解決をする」という部分になるのですが、その手段として必要な技術は全部使っていこうという姿勢です。なので、まずは開発組織のメンバー一人ひとりが、ドメインの理解をするということが始めの一歩です。そうして課題の本質を見極めて解決に必要な手段は何があるのかを考えていく必要が出てきます。 解決のために最適であれば、その技術の新旧や使用実績などを問わず取り入れて開発していくという心構えですね。そのためにはきちんと技術動向を追っていき日頃からユースケースなどを想像しながら、その技術を触ったりする必要も出てきます。 稲本 : ドメイン知識とテクノロジーを最大限に駆使し、プロダクト開発を通して価値を届けるというのが我々のミッションなのかなと思います。 稲本さん 平木 : 医療領域の未来を変えていくために、技術を最大限に用いたプロダクトドリブンであることが大切ということですね。 田中 : メドレーではそれだけじゃない部分も意識していますね。一言で「プロダクト」と言ってもサービスとそれを作る開発者というだけではなく、顧客接点を持つ事業部などのメンバー全ての動きも合わせて「プロダクト」という意識を持っています。 平木 : なるほど、プロダクトに関わっている人全員を合わせて「プロダクト」だよということですか。 田中 : 例えば開発メンバーが良いプロダクトを提供したとしても、そのプロダクトを使ったユーザーが疑問に思った部分があったとします。そこで問い合わせをしたときに、その体験が良くない場合はやっぱり「プロダクトに関する体験が良くない」という印象になってしまいますよね。これはセールスなど他の領域でも起き得る話ですが、メドレーではそういった部分も含めて「プロダクト開発」という意識を持っているので、他部署だからということではなくトータルでユーザーに価値を感じてもらえるようにしていくということを大切にしています。 平木 : 確かにユーザーはそういった関わるもの全部一緒に「プロダクト」と思いますもんね。 稲本 : 日々の業務の中で直接ユーザーと対話してくれている方々がたくさんいます。 Our Essentials (以下、OE)で特に好きなものの一つで「信頼を獲得する」というものがあります。OE 自体は主に社内向けのメッセージにはなるんですが、日々の業務の中で他者からの信頼を得ることが出来るような振る舞いや成果を出すこと、その信頼の積み重ねがプロダクト全体にも反映され、結果としてユーザーからも信頼される/価値を感じてもらえるプロダクトの提供につながっているのだと感じています。 プロダクトと技術の関係について 平木 : もう少し開発についての話を掘り下げていこうと思います。弊社では現在のところ Ruby や Ruby on Rails(以下 Rails)で作られているプロダクトが大半ではありますが、Go や Node.js などをメインに使っているプロダクトもありますよね。そうした技術選定のときの基本姿勢としてはどういったものがありますか。 田中 : 基本として、「それぞれのプロダクトにとって現時点でこの技術で開発するのが、良いことだよね」というのを大切にしています。組織的に知見が多いということもあって、Rails を使っているプロダクトが多いのはそうなんですが、この技術じゃないとダメという縛りがあるわけではないです。Go や Node.js にしても、そのプロダクトに現状最適だという視点で技術選定をしています。 稲本 : 今までできなかったことが、新しい技術を使ったら解決するのにという場面も結構あると思います。そういうときに「今使ってないから…」というのではなく、その技術を使ったほうがプロダクトにプラスになると判断したら使っていくようにしています。 田中 : ですので、今プロダクトで使っている技術で守っていくというより、個々人でアンテナを張って新しい技術は積極的にキャッチアップしていきながら、チーム内で「これ使うと良さそうだからやっていこう」という感じですね。 平木 : 先程も出てきたユーザーに価値提供を素早くするという要素の一つということですね。そうすると、例えば開発者体験を良くするような技術やフローなんかを取り入れるというのも、そうした志向の一部ということになりますか? 田中 : そうですね。良いものを早くユーザーに届けるという一側面になります。開発者体験を良くするというのが最終的な目的ではなくて、その結果としてチームの生産性が上がるのなら体験を良くすると良いよねという。プロダクトコードの負債解消とかもそうですが、バランスが確かに難しいのですが、こういったところを疎かにすると結果として顧客への十分な価値提供ができなくなってしまうリスクもあるので、短期と中長期できちんとバランシングしつつ開発に取り組んでいきたいと思っています。 田中さん 平木 : 改めてここで現在の開発体制について伺っていければと思います。 田中 : 人材 PF も医療 PF もプロダクトに紐付いて複数のチームに分かれていて、チームの人数規模としてみると多少の上下はあるんですが、プロダクトマネージャ(以下、PdM) やデザイナー、エンジニアを合わせて 10 人前後というチームが多いです。 平木 : そうしたチーム内で、特にエンジニアはどのような役割分担をしていることが多いですか? 稲本 : もちろんチームによっても違ってくるんですが、チームの中に PM と TL を置いて、そのチームの中にメンバーが数人いる形が多いですね。この単位をベースにして toC 向けのプロジェクト、toB 向けのプロジェクトのような感じでチームを分けて開発を行なっています。 田中 : メンバー個々人はサーバサイドやフロントエンド、インフラのような得意領域がありそこを考慮した開発をしていっていますが、サーバサイドだけやる人という形ではなく、得意領域と隣り合う領域についても意識し、可能な限り理解してコラボレーションしていこうというやり方を取っています。これによりコミュニケーションコストが下がることで生産性高く、品質向上にも寄与するというのが理由になります。 もちろんプロとして自分の得意分野で力を存分に発揮はしてもらうというのは大前提ですが、その強みを周囲のメンバーに還元しつつ、得意領域以外はそこが得意なメンバーから還元してもらいながら、良いプロダクトを作っていくというのがメドレーの開発組織のスタンスになります。 開発組織の雰囲気やメンバーの働き方について 平木 : 今まで開発組織の制度的な側面を中心に聞いてきましたが、組織のソフト面についてお聞きします。メドレーの開発組織の雰囲気ですが、どんな雰囲気だったりしますか? 稲本 : 全体的にはわりと和やかな雰囲気は持ちつつも、各自のバリューに対してはストイックな感じかなと思います。 自分たちで決めた期日など守るところはしっかり守ろうという意識を持ちつつ、無理な期日になりそうであればスケジュールやスコープを調整したり、プロジェクトの進め方で上手く行ったこと/行かなかったことを振り返りを通して改善を図ったりするなど、当たり前のことを高い水準でやり遂げていく習慣が根付いていると思います。 コミュニケーションに関しては、非同期コミュニケーションがベースではあります。口頭での相談なんかももちろんしますが、認識がずれないように話したことを issue などに残したりしてます。 雑談みたいなものはミーティングの後半パートにあえて雑談パートを作るなどしていますが、業務中にずっとするようなことはないですね。開発中はそれぞれが集中していることが多いです。 田中 : チームごとに違いますが、朝会や夕会などでちゃんとコミュニケーションが取れるようにしているということが多いですね。なので、あんまり他の時間に雑談みたいなものが必要ないという感じです。 平木 : 和やかながらも、締めるところは締めるという雰囲気ですね。開発チームの皆さんはどんな働き方をしている人が多いですか? 稲本 : これも個々人で違っていますが出社とリモートのハイブリッドな方が多いかと思います。 やはり開発に集中したい場面ではリモートの方が割り込みも少なくなりやすく集中しやすいですし、物事の認識を揃えたり決めていく場合は、ミーティングなどで実際に顔を合わせた方が効率も良いことはあると思いますね。 田中 : 自分のバリューを最大化できるように働いていこうというのが基本になっています。 平木 : 各自ちゃんと考えてプロダクトに貢献できるようにということですね。そうした働き方の先にキャリアパスがあると思いますが、その観点ではどのようなパスがありますか? 田中 : 大きく分けては、マネジメントをメインにするか、テクニカル部分を特化させたスペシャリストとなるかという 2 つになります。役割分担にもちょっと関わりますが、内容としては、例えば PdM を目指していくというのも道としてはありますし、自分の得意領域を最大限に伸ばしてテックリードとして技術をもってプロダクトを引っぱっていくという道なんかもあります。1on1 や評価などでそうした部分は本人と刷り合わせをしていきながら決めていく形になります。 平木 : 評価のお話が出てきましたが、どういった観点で評価されますか? 稲本 : ベースとしては会社として決めた OE に沿ったものになります。OE を踏まえつつ、業務目標や技術的な目標を立ててそれができたかどうかが基準になりますね。 立てた目標が色々な理由で達成できなかったというケースももちろん出てくると思いますが、内容として自分はどういった部分は頑張ったとか、どういう理由で達成できなかったなどの自分なりの説明ができるかというのを重視しています。 田中 : 目標はチームバリューにいかに寄与したかを重視しています。OE に基づきチームとして達成しないといけない目標があってそれをブレイクダウンした上で、自分が寄与できるのはどこかという感じで目標にしてもらいます。一見目に見えにくい動きだったとしても、ちゃんとチームに貢献しているねとなれば、もちろん評価の対象になりますし、逆に例えばいくら頑張って新技術を習得したとなってもチームに貢献してなければ評価はされにくいです。 平木 : ありがとうございます。最後にメドレーで開発することの良さを教えてもらえればと思います。 田中 : メドレーは長期を見据えて課題に取り組んでいる会社であり、泥臭く地道に積み重ねなければいけないこともすごく多いです。そのため短期で結果が見えづらいこともあるかもしれません。ですが、医療領域でどっしりと腰を据えて長期的に本質を捉えた開発をしているため、技術面だけではなくプロダクトデザインとして何のために、どのようなアプローチが適切か、という思考力や設計能力も身に付きやすいのではないかと思います。 稲本 : 長期で課題に取り組むとなると「同じことの繰り返しではないか」と感じる方もいるかもしれませんが、プロダクトが成長すればフェーズも変わりますし、取り組むことにも変化が生じていくので成長や変化を楽しめる人には合っているのではないかと思います。 さいごに 新体制で CTO 2 人にメドレーの開発組織について色々と語ってもらいました。 2 人も話をしていましたが、メドレーでは長期思考・未来志向の考え方をベースに、ユーザーの本質的な課題はなにか、どうすればそれらを解決できるか?という部分にフォーカスを当てて開発をするという志向がとても強いと思います。 そうした環境でじっくりと確実にユーザーに価値を提供できるという仕事だと思いますので、ご興味を持たれた方はぜひカジュアルにお話をさせていただければと思います! https://www.medley.jp/jobs/
アバター
はじめに みなさん、こんにちは。技術広報・エンジニアの平木です。 既に 2 月に 発表 されていますが、4 月より弊社の経営体制を大幅に変更しました。開発組織について大きく変わった部分として CTO 2 名体制になった点があります。そこで、なぜ CTO を 2 名にしたのかや、これからのメドレーの開発組織についてを CTO になった 2 人にインタビューしました。 インタビュイー紹介 稲本さん 執行役員。人材プラットフォーム(以下、人材 PF)CTO。独立系 SIer でのインフラエンジニアに始まり、インターネット企業での様々なサービスのインフラ構築を経て、音楽配信サービスやインターネットラジオサービスのサーバサイドエンジニアとして従事。2014 年メドレー入社後は創業時から運営しているジョブメドレーのプロダクト開発に従事。その後、同サービスのリードエンジニア、開発責任者を経て、2023 年 4 月より現職。 田中さん 執行役員。医療プラットフォーム(以下、医療 PF)CTO。独立系 SIer でのアプリケーションエンジニアや IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとしてソーシャルゲームや動画サービスなどの開発立ち上げに従事。2016 年メドレー入社後は CLINICS カルテの立ち上げを経験。その後 CLINICS 開発責任者を経て医療 PF プロダクト横断基盤の立ち上げ・開発責任者を経て 2023 年 4 月より現職。 左から田中さん、稲本さん CTO を 2 名体制にして開発組織の体制をアップデートした理由 平木 : さっそくですが、2023 年 4 月よりお二人がそれぞれ人材 PF と医療 PF の CTO に就任されて、改めて開発組織のアップデートがされましたが、どういった背景でこの体制になったんでしょうか。 田中 : 会社全体の組織設計として医療 PF と人材 PF に分かれたというのがまず最初にあったんですが、当初、開発組織は両 PF を横断する形で 1 人の CTO が見ていました。各 PF 内にそれぞれプロダクトがあり開発もプロダクトごとにチームを分けていましたが、それぞれの開発チームが有機的に動きつつも組織全体の小回りの効きやすさなども考えて、こうした体制になっていました。 ただ、時間が経つに連れ段々と開発チームも各 PF 自体 の規模も大きくなってきたので、 1 人の CTO ではマネジメントが難しくなってきました。また医療 PF と人材 PF で共通する開発組織の文化などはありますが、扱っている事業内容の差異から選定技術やプロジェクトの進め方などの違う部分も出てきたので、それぞれの PF で CTO を置いて組織面・技術面の課題に対応しようということになったためです。 稲本 : 開発組織の人数としては 100 名を越えているのですが、それだけではなく就業場所の違いやサービスの多様化など、それぞれの PF で特性が違う課題がこれから先も出てくるだろうということで、先を見据えての開発組織のアップデートの一環として CTO 2 名体制にしたというところですね。 平木 : これからの組織の変化を見据えた動きだということですね。 新しい開発体制になって変わっていく部分・変わらない部分 平木 : 新体制になってまだ日が浅いですが、これから変えていく部分というのはどういったところになっていくんでしょうか。 稲本 : 自分達 2 人が持っている色々な役割を分解していって他のメンバーへ役割を委譲していくことですね。CTO が色々持ちすぎると組織のスケールに対しボトルネックになることは目に見えているので。 それ以外には、役割を担ったチームやメンバーが自身で思考し行動しやすい組織にしていくことで、更なるチャレンジがしやすかったりパフォーマンスを発揮しやすい状態にしていけたらと考えています。 平木 : なるほど。田中さんは何かありますか。 田中 : 「役割の明確化」もその 1 つですが、将来取り組むべき課題に向けて中長期を見据えた開発組織の設計や運営に向き合うフェイズになってきたと感じており、強化していきたいと考えています。 平木 : 将来の課題に対して技術・組織なども先手を打っていくイメージですね。 新体制での開発組織のミッション 平木 : それでは新体制になって改めて開発組織のミッションとしてはどういったものがありますか。 田中 : 会社全体のミッションと基本は一緒ではあります。「医療」という大きい領域の課題に対して、何のために開発をするのかや、その開発をすることによってどういった顧客価値を提供できるのかというところは、ちゃんとミッションに紐付いている部分なので変えずにいきます。 その上で、有効な施策をどれだけ素早くデリバリーできるかというところには今以上に注力していきたいところです。そのためには無駄を省き、やるべき事に集中してより生産性の高い開発を目指します。 プロダクト開発において無駄な機能開発を行なうと、後からそこを削ろうと思っても難しいですし、作った結果誰も幸せにならないということになってしまいます。こうならないためにもスピード感は落とさないで、事業側メンバーと協力しながら適切な要件整理をしたりなど、どうやって顧客へ適切なプロダクトを届けるか?という部分により重きを置いていきたいです。 事業とプロダクト開発の関係性 平木 : さて、次に先程のお話にも出てきましたが、事業とプロダクト開発の関係性についてお聞きします。お二人が考えるこの関係性はどのようなものでしょうか。 田中 : ここも以前から変わらないスタンスですが、改めて定義するなら「テクノロジーを最大限に使って医療領域の課題を解決する」という関係になります。テクノロジーとメドレーの事業である医療領域の課題解決という 2 つの側面のどれが欠けても、プロダクトとして良いものは生まれないと考えています。 平木 : 主に Web のテクノロジーを適材適所で課題解決のために使っていくというスタンスですね。 田中 : そうですね。先に来るのはあくまでも、「医療領域の課題解決をする」という部分になるのですが、その手段として必要な技術は全部使っていこうという姿勢です。なので、まずは開発組織のメンバー一人ひとりが、ドメインの理解をするということが始めの一歩です。そうして課題の本質を見極めて解決に必要な手段は何があるのかを考えていく必要が出てきます。 解決のために最適であれば、その技術の新旧や使用実績などを問わず取り入れて開発していくという心構えですね。そのためにはきちんと技術動向を追っていき日頃からユースケースなどを想像しながら、その技術を触ったりする必要も出てきます。 稲本 : ドメイン知識とテクノロジーを最大限に駆使し、プロダクト開発を通して価値を届けるというのが我々のミッションなのかなと思います。 稲本さん 平木 : 医療領域の未来を変えていくために、技術を最大限に用いたプロダクトドリブンであることが大切ということですね。 田中 : メドレーではそれだけじゃない部分も意識していますね。一言で「プロダクト」と言ってもサービスとそれを作る開発者というだけではなく、顧客接点を持つ事業部などのメンバー全ての動きも合わせて「プロダクト」という意識を持っています。 平木 : なるほど、プロダクトに関わっている人全員を合わせて「プロダクト」だよということですか。 田中 : 例えば開発メンバーが良いプロダクトを提供したとしても、そのプロダクトを使ったユーザーが疑問に思った部分があったとします。そこで問い合わせをしたときに、その体験が良くない場合はやっぱり「プロダクトに関する体験が良くない」という印象になってしまいますよね。これはセールスなど他の領域でも起き得る話ですが、メドレーではそういった部分も含めて「プロダクト開発」という意識を持っているので、他部署だからということではなくトータルでユーザーに価値を感じてもらえるようにしていくということを大切にしています。 平木 : 確かにユーザーはそういった関わるもの全部一緒に「プロダクト」と思いますもんね。 稲本 : 日々の業務の中で直接ユーザーと対話してくれている方々がたくさんいます。 Our Essentials (以下、OE)で特に好きなものの一つで「信頼を獲得する」というものがあります。OE 自体は主に社内向けのメッセージにはなるんですが、日々の業務の中で他者からの信頼を得ることが出来るような振る舞いや成果を出すこと、その信頼の積み重ねがプロダクト全体にも反映され、結果としてユーザーからも信頼される/価値を感じてもらえるプロダクトの提供につながっているのだと感じています。 プロダクトと技術の関係について 平木 : もう少し開発についての話を掘り下げていこうと思います。弊社では現在のところ Ruby や Ruby on Rails(以下 Rails)で作られているプロダクトが大半ではありますが、Go や Node.js などをメインに使っているプロダクトもありますよね。そうした技術選定のときの基本姿勢としてはどういったものがありますか。 田中 : 基本として、「それぞれのプロダクトにとって現時点でこの技術で開発するのが、良いことだよね」というのを大切にしています。組織的に知見が多いということもあって、Rails を使っているプロダクトが多いのはそうなんですが、この技術じゃないとダメという縛りがあるわけではないです。Go や Node.js にしても、そのプロダクトに現状最適だという視点で技術選定をしています。 稲本 : 今までできなかったことが、新しい技術を使ったら解決するのにという場面も結構あると思います。そういうときに「今使ってないから…」というのではなく、その技術を使ったほうがプロダクトにプラスになると判断したら使っていくようにしています。 田中 : ですので、今プロダクトで使っている技術で守っていくというより、個々人でアンテナを張って新しい技術は積極的にキャッチアップしていきながら、チーム内で「これ使うと良さそうだからやっていこう」という感じですね。 平木 : 先程も出てきたユーザーに価値提供を素早くするという要素の一つということですね。そうすると、例えば開発者体験を良くするような技術やフローなんかを取り入れるというのも、そうした志向の一部ということになりますか? 田中 : そうですね。良いものを早くユーザーに届けるという一側面になります。開発者体験を良くするというのが最終的な目的ではなくて、その結果としてチームの生産性が上がるのなら体験を良くすると良いよねという。プロダクトコードの負債解消とかもそうですが、バランスが確かに難しいのですが、こういったところを疎かにすると結果として顧客への十分な価値提供ができなくなってしまうリスクもあるので、短期と中長期できちんとバランシングしつつ開発に取り組んでいきたいと思っています。 田中さん 平木 : 改めてここで現在の開発体制について伺っていければと思います。 田中 : 人材 PF も医療 PF もプロダクトに紐付いて複数のチームに分かれていて、チームの人数規模としてみると多少の上下はあるんですが、プロダクトマネージャ(以下、PdM) やデザイナー、エンジニアを合わせて 10 人前後というチームが多いです。 平木 : そうしたチーム内で、特にエンジニアはどのような役割分担をしていることが多いですか? 稲本 : もちろんチームによっても違ってくるんですが、チームの中に PM と TL を置いて、そのチームの中にメンバーが数人いる形が多いですね。この単位をベースにして toC 向けのプロジェクト、toB 向けのプロジェクトのような感じでチームを分けて開発を行なっています。 田中 : メンバー個々人はサーバサイドやフロントエンド、インフラのような得意領域がありそこを考慮した開発をしていっていますが、サーバサイドだけやる人という形ではなく、得意領域と隣り合う領域についても意識し、可能な限り理解してコラボレーションしていこうというやり方を取っています。これによりコミュニケーションコストが下がることで生産性高く、品質向上にも寄与するというのが理由になります。 もちろんプロとして自分の得意分野で力を存分に発揮はしてもらうというのは大前提ですが、その強みを周囲のメンバーに還元しつつ、得意領域以外はそこが得意なメンバーから還元してもらいながら、良いプロダクトを作っていくというのがメドレーの開発組織のスタンスになります。 開発組織の雰囲気やメンバーの働き方について 平木 : 今まで開発組織の制度的な側面を中心に聞いてきましたが、組織のソフト面についてお聞きします。メドレーの開発組織の雰囲気ですが、どんな雰囲気だったりしますか? 稲本 : 全体的にはわりと和やかな雰囲気は持ちつつも、各自のバリューに対してはストイックな感じかなと思います。 自分たちで決めた期日など守るところはしっかり守ろうという意識を持ちつつ、無理な期日になりそうであればスケジュールやスコープを調整したり、プロジェクトの進め方で上手く行ったこと/行かなかったことを振り返りを通して改善を図ったりするなど、当たり前のことを高い水準でやり遂げていく習慣が根付いていると思います。 コミュニケーションに関しては、非同期コミュニケーションがベースではあります。口頭での相談なんかももちろんしますが、認識がずれないように話したことを issue などに残したりしてます。 雑談みたいなものはミーティングの後半パートにあえて雑談パートを作るなどしていますが、業務中にずっとするようなことはないですね。開発中はそれぞれが集中していることが多いです。 田中 : チームごとに違いますが、朝会や夕会などでちゃんとコミュニケーションが取れるようにしているということが多いですね。なので、あんまり他の時間に雑談みたいなものが必要ないという感じです。 平木 : 和やかながらも、締めるところは締めるという雰囲気ですね。開発チームの皆さんはどんな働き方をしている人が多いですか? 稲本 : これも個々人で違っていますが出社とリモートのハイブリッドな方が多いかと思います。 やはり開発に集中したい場面ではリモートの方が割り込みも少なくなりやすく集中しやすいですし、物事の認識を揃えたり決めていく場合は、ミーティングなどで実際に顔を合わせた方が効率も良いことはあると思いますね。 田中 : 自分のバリューを最大化できるように働いていこうというのが基本になっています。 平木 : 各自ちゃんと考えてプロダクトに貢献できるようにということですね。そうした働き方の先にキャリアパスがあると思いますが、その観点ではどのようなパスがありますか? 田中 : 大きく分けては、マネジメントをメインにするか、テクニカル部分を特化させたスペシャリストとなるかという 2 つになります。役割分担にもちょっと関わりますが、内容としては、例えば PdM を目指していくというのも道としてはありますし、自分の得意領域を最大限に伸ばしてテックリードとして技術をもってプロダクトを引っぱっていくという道なんかもあります。1on1 や評価などでそうした部分は本人と刷り合わせをしていきながら決めていく形になります。 平木 : 評価のお話が出てきましたが、どういった観点で評価されますか? 稲本 : ベースとしては会社として決めた OE に沿ったものになります。OE を踏まえつつ、業務目標や技術的な目標を立ててそれができたかどうかが基準になりますね。 立てた目標が色々な理由で達成できなかったというケースももちろん出てくると思いますが、内容として自分はどういった部分は頑張ったとか、どういう理由で達成できなかったなどの自分なりの説明ができるかというのを重視しています。 田中 : 目標はチームバリューにいかに寄与したかを重視しています。OE に基づきチームとして達成しないといけない目標があってそれをブレイクダウンした上で、自分が寄与できるのはどこかという感じで目標にしてもらいます。一見目に見えにくい動きだったとしても、ちゃんとチームに貢献しているねとなれば、もちろん評価の対象になりますし、逆に例えばいくら頑張って新技術を習得したとなってもチームに貢献してなければ評価はされにくいです。 平木 : ありがとうございます。最後にメドレーで開発することの良さを教えてもらえればと思います。 田中 : メドレーは長期を見据えて課題に取り組んでいる会社であり、泥臭く地道に積み重ねなければいけないこともすごく多いです。そのため短期で結果が見えづらいこともあるかもしれません。ですが、医療領域でどっしりと腰を据えて長期的に本質を捉えた開発をしているため、技術面だけではなくプロダクトデザインとして何のために、どのようなアプローチが適切か、という思考力や設計能力も身に付きやすいのではないかと思います。 稲本 : 長期で課題に取り組むとなると「同じことの繰り返しではないか」と感じる方もいるかもしれませんが、プロダクトが成長すればフェーズも変わりますし、取り組むことにも変化が生じていくので成長や変化を楽しめる人には合っているのではないかと思います。 さいごに 新体制で CTO 2 人にメドレーの開発組織について色々と語ってもらいました。 2 人も話をしていましたが、メドレーでは長期思考・未来志向の考え方をベースに、ユーザーの本質的な課題はなにか、どうすればそれらを解決できるか?という部分にフォーカスを当てて開発をするという志向がとても強いと思います。 そうした環境でじっくりと確実にユーザーに価値を提供できるという仕事だと思いますので、ご興味を持たれた方はぜひカジュアルにお話をさせていただければと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに みなさん、こんにちは。技術広報・エンジニアの平木です。 既に 2 月に 発表 されていますが、4 月より弊社の経営体制を大幅に変更しました。開発組織について大きく変わった部分として CTO 2 名体制になった点があります。そこで、なぜ CTO を 2 名にしたのかや、これからのメドレーの開発組織についてを CTO になった 2 人にインタビューしました。 インタビュイー紹介 稲本さん 執行役員。人材プラットフォーム(以下、人材 PF)CTO。独立系 SIer でのインフラエンジニアに始まり、インターネット企業での様々なサービスのインフラ構築を経て、音楽配信サービスやインターネットラジオサービスのサーバサイドエンジニアとして従事。2014 年メドレー入社後は創業時から運営しているジョブメドレーのプロダクト開発に従事。その後、同サービスのリードエンジニア、開発責任者を経て、2023 年 4 月より現職。 田中さん 執行役員。医療プラットフォーム(以下、医療 PF)CTO。独立系 SIer でのアプリケーションエンジニアや IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとしてソーシャルゲームや動画サービスなどの開発立ち上げに従事。2016 年メドレー入社後は CLINICS カルテの立ち上げを経験。その後 CLINICS 開発責任者を経て医療 PF プロダクト横断基盤の立ち上げ・開発責任者を経て 2023 年 4 月より現職。 左から田中さん、稲本さん CTO を 2 名体制にして開発組織の体制をアップデートした理由 平木 : さっそくですが、2023 年 4 月よりお二人がそれぞれ人材 PF と医療 PF の CTO に就任されて、改めて開発組織のアップデートがされましたが、どういった背景でこの体制になったんでしょうか。 田中 : 会社全体の組織設計として医療 PF と人材 PF に分かれたというのがまず最初にあったんですが、当初、開発組織は両 PF を横断する形で 1 人の CTO が見ていました。各 PF 内にそれぞれプロダクトがあり開発もプロダクトごとにチームを分けていましたが、それぞれの開発チームが有機的に動きつつも組織全体の小回りの効きやすさなども考えて、こうした体制になっていました。 ただ、時間が経つに連れ段々と開発チームも各 PF 自体 の規模も大きくなってきたので、 1 人の CTO ではマネジメントが難しくなってきました。また医療 PF と人材 PF で共通する開発組織の文化などはありますが、扱っている事業内容の差異から選定技術やプロジェクトの進め方などの違う部分も出てきたので、それぞれの PF で CTO を置いて組織面・技術面の課題に対応しようということになったためです。 稲本 : 開発組織の人数としては 100 名を越えているのですが、それだけではなく就業場所の違いやサービスの多様化など、それぞれの PF で特性が違う課題がこれから先も出てくるだろうということで、先を見据えての開発組織のアップデートの一環として CTO 2 名体制にしたというところですね。 平木 : これからの組織の変化を見据えた動きだということですね。 新しい開発体制になって変わっていく部分・変わらない部分 平木 : 新体制になってまだ日が浅いですが、これから変えていく部分というのはどういったところになっていくんでしょうか。 稲本 : 自分達 2 人が持っている色々な役割を分解していって他のメンバーへ役割を委譲していくことですね。CTO が色々持ちすぎると組織のスケールに対しボトルネックになることは目に見えているので。 それ以外には、役割を担ったチームやメンバーが自身で思考し行動しやすい組織にしていくことで、更なるチャレンジがしやすかったりパフォーマンスを発揮しやすい状態にしていけたらと考えています。 平木 : なるほど。田中さんは何かありますか。 田中 : 「役割の明確化」もその 1 つですが、将来取り組むべき課題に向けて中長期を見据えた開発組織の設計や運営に向き合うフェイズになってきたと感じており、強化していきたいと考えています。 平木 : 将来の課題に対して技術・組織なども先手を打っていくイメージですね。 新体制での開発組織のミッション 平木 : それでは新体制になって改めて開発組織のミッションとしてはどういったものがありますか。 田中 : 会社全体のミッションと基本は一緒ではあります。「医療」という大きい領域の課題に対して、何のために開発をするのかや、その開発をすることによってどういった顧客価値を提供できるのかというところは、ちゃんとミッションに紐付いている部分なので変えずにいきます。 その上で、有効な施策をどれだけ素早くデリバリーできるかというところには今以上に注力していきたいところです。そのためには無駄を省き、やるべき事に集中してより生産性の高い開発を目指します。 プロダクト開発において無駄な機能開発を行なうと、後からそこを削ろうと思っても難しいですし、作った結果誰も幸せにならないということになってしまいます。こうならないためにもスピード感は落とさないで、事業側メンバーと協力しながら適切な要件整理をしたりなど、どうやって顧客へ適切なプロダクトを届けるか?という部分により重きを置いていきたいです。 事業とプロダクト開発の関係性 平木 : さて、次に先程のお話にも出てきましたが、事業とプロダクト開発の関係性についてお聞きします。お二人が考えるこの関係性はどのようなものでしょうか。 田中 : ここも以前から変わらないスタンスですが、改めて定義するなら「テクノロジーを最大限に使って医療領域の課題を解決する」という関係になります。テクノロジーとメドレーの事業である医療領域の課題解決という 2 つの側面のどれが欠けても、プロダクトとして良いものは生まれないと考えています。 平木 : 主に Web のテクノロジーを適材適所で課題解決のために使っていくというスタンスですね。 田中 : そうですね。先に来るのはあくまでも、「医療領域の課題解決をする」という部分になるのですが、その手段として必要な技術は全部使っていこうという姿勢です。なので、まずは開発組織のメンバー一人ひとりが、ドメインの理解をするということが始めの一歩です。そうして課題の本質を見極めて解決に必要な手段は何があるのかを考えていく必要が出てきます。 解決のために最適であれば、その技術の新旧や使用実績などを問わず取り入れて開発していくという心構えですね。そのためにはきちんと技術動向を追っていき日頃からユースケースなどを想像しながら、その技術を触ったりする必要も出てきます。 稲本 : ドメイン知識とテクノロジーを最大限に駆使し、プロダクト開発を通して価値を届けるというのが我々のミッションなのかなと思います。 稲本さん 平木 : 医療領域の未来を変えていくために、技術を最大限に用いたプロダクトドリブンであることが大切ということですね。 田中 : メドレーではそれだけじゃない部分も意識していますね。一言で「プロダクト」と言ってもサービスとそれを作る開発者というだけではなく、顧客接点を持つ事業部などのメンバー全ての動きも合わせて「プロダクト」という意識を持っています。 平木 : なるほど、プロダクトに関わっている人全員を合わせて「プロダクト」だよということですか。 田中 : 例えば開発メンバーが良いプロダクトを提供したとしても、そのプロダクトを使ったユーザーが疑問に思った部分があったとします。そこで問い合わせをしたときに、その体験が良くない場合はやっぱり「プロダクトに関する体験が良くない」という印象になってしまいますよね。これはセールスなど他の領域でも起き得る話ですが、メドレーではそういった部分も含めて「プロダクト開発」という意識を持っているので、他部署だからということではなくトータルでユーザーに価値を感じてもらえるようにしていくということを大切にしています。 平木 : 確かにユーザーはそういった関わるもの全部一緒に「プロダクト」と思いますもんね。 稲本 : 日々の業務の中で直接ユーザーと対話してくれている方々がたくさんいます。 Our Essentials (以下、OE)で特に好きなものの一つで「信頼を獲得する」というものがあります。OE 自体は主に社内向けのメッセージにはなるんですが、日々の業務の中で他者からの信頼を得ることが出来るような振る舞いや成果を出すこと、その信頼の積み重ねがプロダクト全体にも反映され、結果としてユーザーからも信頼される/価値を感じてもらえるプロダクトの提供につながっているのだと感じています。 プロダクトと技術の関係について 平木 : もう少し開発についての話を掘り下げていこうと思います。弊社では現在のところ Ruby や Ruby on Rails(以下 Rails)で作られているプロダクトが大半ではありますが、Go や Node.js などをメインに使っているプロダクトもありますよね。そうした技術選定のときの基本姿勢としてはどういったものがありますか。 田中 : 基本として、「それぞれのプロダクトにとって現時点でこの技術で開発するのが、良いことだよね」というのを大切にしています。組織的に知見が多いということもあって、Rails を使っているプロダクトが多いのはそうなんですが、この技術じゃないとダメという縛りがあるわけではないです。Go や Node.js にしても、そのプロダクトに現状最適だという視点で技術選定をしています。 稲本 : 今までできなかったことが、新しい技術を使ったら解決するのにという場面も結構あると思います。そういうときに「今使ってないから…」というのではなく、その技術を使ったほうがプロダクトにプラスになると判断したら使っていくようにしています。 田中 : ですので、今プロダクトで使っている技術で守っていくというより、個々人でアンテナを張って新しい技術は積極的にキャッチアップしていきながら、チーム内で「これ使うと良さそうだからやっていこう」という感じですね。 平木 : 先程も出てきたユーザーに価値提供を素早くするという要素の一つということですね。そうすると、例えば開発者体験を良くするような技術やフローなんかを取り入れるというのも、そうした志向の一部ということになりますか? 田中 : そうですね。良いものを早くユーザーに届けるという一側面になります。開発者体験を良くするというのが最終的な目的ではなくて、その結果としてチームの生産性が上がるのなら体験を良くすると良いよねという。プロダクトコードの負債解消とかもそうですが、バランスが確かに難しいのですが、こういったところを疎かにすると結果として顧客への十分な価値提供ができなくなってしまうリスクもあるので、短期と中長期できちんとバランシングしつつ開発に取り組んでいきたいと思っています。 田中さん 平木 : 改めてここで現在の開発体制について伺っていければと思います。 田中 : 人材 PF も医療 PF もプロダクトに紐付いて複数のチームに分かれていて、チームの人数規模としてみると多少の上下はあるんですが、プロダクトマネージャ(以下、PdM) やデザイナー、エンジニアを合わせて 10 人前後というチームが多いです。 平木 : そうしたチーム内で、特にエンジニアはどのような役割分担をしていることが多いですか? 稲本 : もちろんチームによっても違ってくるんですが、チームの中に PM と TL を置いて、そのチームの中にメンバーが数人いる形が多いですね。この単位をベースにして toC 向けのプロジェクト、toB 向けのプロジェクトのような感じでチームを分けて開発を行なっています。 田中 : メンバー個々人はサーバサイドやフロントエンド、インフラのような得意領域がありそこを考慮した開発をしていっていますが、サーバサイドだけやる人という形ではなく、得意領域と隣り合う領域についても意識し、可能な限り理解してコラボレーションしていこうというやり方を取っています。これによりコミュニケーションコストが下がることで生産性高く、品質向上にも寄与するというのが理由になります。 もちろんプロとして自分の得意分野で力を存分に発揮はしてもらうというのは大前提ですが、その強みを周囲のメンバーに還元しつつ、得意領域以外はそこが得意なメンバーから還元してもらいながら、良いプロダクトを作っていくというのがメドレーの開発組織のスタンスになります。 開発組織の雰囲気やメンバーの働き方について 平木 : 今まで開発組織の制度的な側面を中心に聞いてきましたが、組織のソフト面についてお聞きします。メドレーの開発組織の雰囲気ですが、どんな雰囲気だったりしますか? 稲本 : 全体的にはわりと和やかな雰囲気は持ちつつも、各自のバリューに対してはストイックな感じかなと思います。 自分たちで決めた期日など守るところはしっかり守ろうという意識を持ちつつ、無理な期日になりそうであればスケジュールやスコープを調整したり、プロジェクトの進め方で上手く行ったこと/行かなかったことを振り返りを通して改善を図ったりするなど、当たり前のことを高い水準でやり遂げていく習慣が根付いていると思います。 コミュニケーションに関しては、非同期コミュニケーションがベースではあります。口頭での相談なんかももちろんしますが、認識がずれないように話したことを issue などに残したりしてます。 雑談みたいなものはミーティングの後半パートにあえて雑談パートを作るなどしていますが、業務中にずっとするようなことはないですね。開発中はそれぞれが集中していることが多いです。 田中 : チームごとに違いますが、朝会や夕会などでちゃんとコミュニケーションが取れるようにしているということが多いですね。なので、あんまり他の時間に雑談みたいなものが必要ないという感じです。 平木 : 和やかながらも、締めるところは締めるという雰囲気ですね。開発チームの皆さんはどんな働き方をしている人が多いですか? 稲本 : これも個々人で違っていますが出社とリモートのハイブリッドな方が多いかと思います。 やはり開発に集中したい場面ではリモートの方が割り込みも少なくなりやすく集中しやすいですし、物事の認識を揃えたり決めていく場合は、ミーティングなどで実際に顔を合わせた方が効率も良いことはあると思いますね。 田中 : 自分のバリューを最大化できるように働いていこうというのが基本になっています。 平木 : 各自ちゃんと考えてプロダクトに貢献できるようにということですね。そうした働き方の先にキャリアパスがあると思いますが、その観点ではどのようなパスがありますか? 田中 : 大きく分けては、マネジメントをメインにするか、テクニカル部分を特化させたスペシャリストとなるかという 2 つになります。役割分担にもちょっと関わりますが、内容としては、例えば PdM を目指していくというのも道としてはありますし、自分の得意領域を最大限に伸ばしてテックリードとして技術をもってプロダクトを引っぱっていくという道なんかもあります。1on1 や評価などでそうした部分は本人と刷り合わせをしていきながら決めていく形になります。 平木 : 評価のお話が出てきましたが、どういった観点で評価されますか? 稲本 : ベースとしては会社として決めた OE に沿ったものになります。OE を踏まえつつ、業務目標や技術的な目標を立ててそれができたかどうかが基準になりますね。 立てた目標が色々な理由で達成できなかったというケースももちろん出てくると思いますが、内容として自分はどういった部分は頑張ったとか、どういう理由で達成できなかったなどの自分なりの説明ができるかというのを重視しています。 田中 : 目標はチームバリューにいかに寄与したかを重視しています。OE に基づきチームとして達成しないといけない目標があってそれをブレイクダウンした上で、自分が寄与できるのはどこかという感じで目標にしてもらいます。一見目に見えにくい動きだったとしても、ちゃんとチームに貢献しているねとなれば、もちろん評価の対象になりますし、逆に例えばいくら頑張って新技術を習得したとなってもチームに貢献してなければ評価はされにくいです。 平木 : ありがとうございます。最後にメドレーで開発することの良さを教えてもらえればと思います。 田中 : メドレーは長期を見据えて課題に取り組んでいる会社であり、泥臭く地道に積み重ねなければいけないこともすごく多いです。そのため短期で結果が見えづらいこともあるかもしれません。ですが、医療領域でどっしりと腰を据えて長期的に本質を捉えた開発をしているため、技術面だけではなくプロダクトデザインとして何のために、どのようなアプローチが適切か、という思考力や設計能力も身に付きやすいのではないかと思います。 稲本 : 長期で課題に取り組むとなると「同じことの繰り返しではないか」と感じる方もいるかもしれませんが、プロダクトが成長すればフェーズも変わりますし、取り組むことにも変化が生じていくので成長や変化を楽しめる人には合っているのではないかと思います。 さいごに 新体制で CTO 2 人にメドレーの開発組織について色々と語ってもらいました。 2 人も話をしていましたが、メドレーでは長期思考・未来志向の考え方をベースに、ユーザーの本質的な課題はなにか、どうすればそれらを解決できるか?という部分にフォーカスを当てて開発をするという志向がとても強いと思います。 そうした環境でじっくりと確実にユーザーに価値を提供できるという仕事だと思いますので、ご興味を持たれた方はぜひカジュアルにお話をさせていただければと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに みなさん、こんにちは。技術広報・エンジニアの平木です。 既に 2 月に 発表 されていますが、4 月より弊社の経営体制を大幅に変更しました。開発組織について大きく変わった部分として CTO 2 名体制になった点があります。そこで、なぜ CTO を 2 名にしたのかや、これからのメドレーの開発組織についてを CTO になった 2 人にインタビューしました。 インタビュイー紹介 稲本さん 執行役員。人材プラットフォーム(以下、人材 PF)CTO。独立系 SIer でのインフラエンジニアに始まり、インターネット企業での様々なサービスのインフラ構築を経て、音楽配信サービスやインターネットラジオサービスのサーバサイドエンジニアとして従事。2014 年メドレー入社後は創業時から運営しているジョブメドレーのプロダクト開発に従事。その後、同サービスのリードエンジニア、開発責任者を経て、2023 年 4 月より現職。 田中さん 執行役員。医療プラットフォーム(以下、医療 PF)CTO。独立系 SIer でのアプリケーションエンジニアや IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとしてソーシャルゲームや動画サービスなどの開発立ち上げに従事。2016 年メドレー入社後は CLINICS カルテの立ち上げを経験。その後 CLINICS 開発責任者を経て医療 PF プロダクト横断基盤の立ち上げ・開発責任者を経て 2023 年 4 月より現職。 左から田中さん、稲本さん CTO を 2 名体制にして開発組織の体制をアップデートした理由 平木 : さっそくですが、2023 年 4 月よりお二人がそれぞれ人材 PF と医療 PF の CTO に就任されて、改めて開発組織のアップデートがされましたが、どういった背景でこの体制になったんでしょうか。 田中 : 会社全体の組織設計として医療 PF と人材 PF に分かれたというのがまず最初にあったんですが、当初、開発組織は両 PF を横断する形で 1 人の CTO が見ていました。各 PF 内にそれぞれプロダクトがあり開発もプロダクトごとにチームを分けていましたが、それぞれの開発チームが有機的に動きつつも組織全体の小回りの効きやすさなども考えて、こうした体制になっていました。 ただ、時間が経つに連れ段々と開発チームも各 PF 自体 の規模も大きくなってきたので、 1 人の CTO ではマネジメントが難しくなってきました。また医療 PF と人材 PF で共通する開発組織の文化などはありますが、扱っている事業内容の差異から選定技術やプロジェクトの進め方などの違う部分も出てきたので、それぞれの PF で CTO を置いて組織面・技術面の課題に対応しようということになったためです。 稲本 : 開発組織の人数としては 100 名を越えているのですが、それだけではなく就業場所の違いやサービスの多様化など、それぞれの PF で特性が違う課題がこれから先も出てくるだろうということで、先を見据えての開発組織のアップデートの一環として CTO 2 名体制にしたというところですね。 平木 : これからの組織の変化を見据えた動きだということですね。 新しい開発体制になって変わっていく部分・変わらない部分 平木 : 新体制になってまだ日が浅いですが、これから変えていく部分というのはどういったところになっていくんでしょうか。 稲本 : 自分達 2 人が持っている色々な役割を分解していって他のメンバーへ役割を委譲していくことですね。CTO が色々持ちすぎると組織のスケールに対しボトルネックになることは目に見えているので。 それ以外には、役割を担ったチームやメンバーが自身で思考し行動しやすい組織にしていくことで、更なるチャレンジがしやすかったりパフォーマンスを発揮しやすい状態にしていけたらと考えています。 平木 : なるほど。田中さんは何かありますか。 田中 : 「役割の明確化」もその 1 つですが、将来取り組むべき課題に向けて中長期を見据えた開発組織の設計や運営に向き合うフェイズになってきたと感じており、強化していきたいと考えています。 平木 : 将来の課題に対して技術・組織なども先手を打っていくイメージですね。 新体制での開発組織のミッション 平木 : それでは新体制になって改めて開発組織のミッションとしてはどういったものがありますか。 田中 : 会社全体のミッションと基本は一緒ではあります。「医療」という大きい領域の課題に対して、何のために開発をするのかや、その開発をすることによってどういった顧客価値を提供できるのかというところは、ちゃんとミッションに紐付いている部分なので変えずにいきます。 その上で、有効な施策をどれだけ素早くデリバリーできるかというところには今以上に注力していきたいところです。そのためには無駄を省き、やるべき事に集中してより生産性の高い開発を目指します。 プロダクト開発において無駄な機能開発を行なうと、後からそこを削ろうと思っても難しいですし、作った結果誰も幸せにならないということになってしまいます。こうならないためにもスピード感は落とさないで、事業側メンバーと協力しながら適切な要件整理をしたりなど、どうやって顧客へ適切なプロダクトを届けるか?という部分により重きを置いていきたいです。 事業とプロダクト開発の関係性 平木 : さて、次に先程のお話にも出てきましたが、事業とプロダクト開発の関係性についてお聞きします。お二人が考えるこの関係性はどのようなものでしょうか。 田中 : ここも以前から変わらないスタンスですが、改めて定義するなら「テクノロジーを最大限に使って医療領域の課題を解決する」という関係になります。テクノロジーとメドレーの事業である医療領域の課題解決という 2 つの側面のどれが欠けても、プロダクトとして良いものは生まれないと考えています。 平木 : 主に Web のテクノロジーを適材適所で課題解決のために使っていくというスタンスですね。 田中 : そうですね。先に来るのはあくまでも、「医療領域の課題解決をする」という部分になるのですが、その手段として必要な技術は全部使っていこうという姿勢です。なので、まずは開発組織のメンバー一人ひとりが、ドメインの理解をするということが始めの一歩です。そうして課題の本質を見極めて解決に必要な手段は何があるのかを考えていく必要が出てきます。 解決のために最適であれば、その技術の新旧や使用実績などを問わず取り入れて開発していくという心構えですね。そのためにはきちんと技術動向を追っていき日頃からユースケースなどを想像しながら、その技術を触ったりする必要も出てきます。 稲本 : ドメイン知識とテクノロジーを最大限に駆使し、プロダクト開発を通して価値を届けるというのが我々のミッションなのかなと思います。 稲本さん 平木 : 医療領域の未来を変えていくために、技術を最大限に用いたプロダクトドリブンであることが大切ということですね。 田中 : メドレーではそれだけじゃない部分も意識していますね。一言で「プロダクト」と言ってもサービスとそれを作る開発者というだけではなく、顧客接点を持つ事業部などのメンバー全ての動きも合わせて「プロダクト」という意識を持っています。 平木 : なるほど、プロダクトに関わっている人全員を合わせて「プロダクト」だよということですか。 田中 : 例えば開発メンバーが良いプロダクトを提供したとしても、そのプロダクトを使ったユーザーが疑問に思った部分があったとします。そこで問い合わせをしたときに、その体験が良くない場合はやっぱり「プロダクトに関する体験が良くない」という印象になってしまいますよね。これはセールスなど他の領域でも起き得る話ですが、メドレーではそういった部分も含めて「プロダクト開発」という意識を持っているので、他部署だからということではなくトータルでユーザーに価値を感じてもらえるようにしていくということを大切にしています。 平木 : 確かにユーザーはそういった関わるもの全部一緒に「プロダクト」と思いますもんね。 稲本 : 日々の業務の中で直接ユーザーと対話してくれている方々がたくさんいます。 Our Essentials (以下、OE)で特に好きなものの一つで「信頼を獲得する」というものがあります。OE 自体は主に社内向けのメッセージにはなるんですが、日々の業務の中で他者からの信頼を得ることが出来るような振る舞いや成果を出すこと、その信頼の積み重ねがプロダクト全体にも反映され、結果としてユーザーからも信頼される/価値を感じてもらえるプロダクトの提供につながっているのだと感じています。 プロダクトと技術の関係について 平木 : もう少し開発についての話を掘り下げていこうと思います。弊社では現在のところ Ruby や Ruby on Rails(以下 Rails)で作られているプロダクトが大半ではありますが、Go や Node.js などをメインに使っているプロダクトもありますよね。そうした技術選定のときの基本姿勢としてはどういったものがありますか。 田中 : 基本として、「それぞれのプロダクトにとって現時点でこの技術で開発するのが、良いことだよね」というのを大切にしています。組織的に知見が多いということもあって、Rails を使っているプロダクトが多いのはそうなんですが、この技術じゃないとダメという縛りがあるわけではないです。Go や Node.js にしても、そのプロダクトに現状最適だという視点で技術選定をしています。 稲本 : 今までできなかったことが、新しい技術を使ったら解決するのにという場面も結構あると思います。そういうときに「今使ってないから…」というのではなく、その技術を使ったほうがプロダクトにプラスになると判断したら使っていくようにしています。 田中 : ですので、今プロダクトで使っている技術で守っていくというより、個々人でアンテナを張って新しい技術は積極的にキャッチアップしていきながら、チーム内で「これ使うと良さそうだからやっていこう」という感じですね。 平木 : 先程も出てきたユーザーに価値提供を素早くするという要素の一つということですね。そうすると、例えば開発者体験を良くするような技術やフローなんかを取り入れるというのも、そうした志向の一部ということになりますか? 田中 : そうですね。良いものを早くユーザーに届けるという一側面になります。開発者体験を良くするというのが最終的な目的ではなくて、その結果としてチームの生産性が上がるのなら体験を良くすると良いよねという。プロダクトコードの負債解消とかもそうですが、バランスが確かに難しいのですが、こういったところを疎かにすると結果として顧客への十分な価値提供ができなくなってしまうリスクもあるので、短期と中長期できちんとバランシングしつつ開発に取り組んでいきたいと思っています。 田中さん 平木 : 改めてここで現在の開発体制について伺っていければと思います。 田中 : 人材 PF も医療 PF もプロダクトに紐付いて複数のチームに分かれていて、チームの人数規模としてみると多少の上下はあるんですが、プロダクトマネージャ(以下、PdM) やデザイナー、エンジニアを合わせて 10 人前後というチームが多いです。 平木 : そうしたチーム内で、特にエンジニアはどのような役割分担をしていることが多いですか? 稲本 : もちろんチームによっても違ってくるんですが、チームの中に PM と TL を置いて、そのチームの中にメンバーが数人いる形が多いですね。この単位をベースにして toC 向けのプロジェクト、toB 向けのプロジェクトのような感じでチームを分けて開発を行なっています。 田中 : メンバー個々人はサーバサイドやフロントエンド、インフラのような得意領域がありそこを考慮した開発をしていっていますが、サーバサイドだけやる人という形ではなく、得意領域と隣り合う領域についても意識し、可能な限り理解してコラボレーションしていこうというやり方を取っています。これによりコミュニケーションコストが下がることで生産性高く、品質向上にも寄与するというのが理由になります。 もちろんプロとして自分の得意分野で力を存分に発揮はしてもらうというのは大前提ですが、その強みを周囲のメンバーに還元しつつ、得意領域以外はそこが得意なメンバーから還元してもらいながら、良いプロダクトを作っていくというのがメドレーの開発組織のスタンスになります。 開発組織の雰囲気やメンバーの働き方について 平木 : 今まで開発組織の制度的な側面を中心に聞いてきましたが、組織のソフト面についてお聞きします。メドレーの開発組織の雰囲気ですが、どんな雰囲気だったりしますか? 稲本 : 全体的にはわりと和やかな雰囲気は持ちつつも、各自のバリューに対してはストイックな感じかなと思います。 自分たちで決めた期日など守るところはしっかり守ろうという意識を持ちつつ、無理な期日になりそうであればスケジュールやスコープを調整したり、プロジェクトの進め方で上手く行ったこと/行かなかったことを振り返りを通して改善を図ったりするなど、当たり前のことを高い水準でやり遂げていく習慣が根付いていると思います。 コミュニケーションに関しては、非同期コミュニケーションがベースではあります。口頭での相談なんかももちろんしますが、認識がずれないように話したことを issue などに残したりしてます。 雑談みたいなものはミーティングの後半パートにあえて雑談パートを作るなどしていますが、業務中にずっとするようなことはないですね。開発中はそれぞれが集中していることが多いです。 田中 : チームごとに違いますが、朝会や夕会などでちゃんとコミュニケーションが取れるようにしているということが多いですね。なので、あんまり他の時間に雑談みたいなものが必要ないという感じです。 平木 : 和やかながらも、締めるところは締めるという雰囲気ですね。開発チームの皆さんはどんな働き方をしている人が多いですか? 稲本 : これも個々人で違っていますが出社とリモートのハイブリッドな方が多いかと思います。 やはり開発に集中したい場面ではリモートの方が割り込みも少なくなりやすく集中しやすいですし、物事の認識を揃えたり決めていく場合は、ミーティングなどで実際に顔を合わせた方が効率も良いことはあると思いますね。 田中 : 自分のバリューを最大化できるように働いていこうというのが基本になっています。 平木 : 各自ちゃんと考えてプロダクトに貢献できるようにということですね。そうした働き方の先にキャリアパスがあると思いますが、その観点ではどのようなパスがありますか? 田中 : 大きく分けては、マネジメントをメインにするか、テクニカル部分を特化させたスペシャリストとなるかという 2 つになります。役割分担にもちょっと関わりますが、内容としては、例えば PdM を目指していくというのも道としてはありますし、自分の得意領域を最大限に伸ばしてテックリードとして技術をもってプロダクトを引っぱっていくという道なんかもあります。1on1 や評価などでそうした部分は本人と刷り合わせをしていきながら決めていく形になります。 平木 : 評価のお話が出てきましたが、どういった観点で評価されますか? 稲本 : ベースとしては会社として決めた OE に沿ったものになります。OE を踏まえつつ、業務目標や技術的な目標を立ててそれができたかどうかが基準になりますね。 立てた目標が色々な理由で達成できなかったというケースももちろん出てくると思いますが、内容として自分はどういった部分は頑張ったとか、どういう理由で達成できなかったなどの自分なりの説明ができるかというのを重視しています。 田中 : 目標はチームバリューにいかに寄与したかを重視しています。OE に基づきチームとして達成しないといけない目標があってそれをブレイクダウンした上で、自分が寄与できるのはどこかという感じで目標にしてもらいます。一見目に見えにくい動きだったとしても、ちゃんとチームに貢献しているねとなれば、もちろん評価の対象になりますし、逆に例えばいくら頑張って新技術を習得したとなってもチームに貢献してなければ評価はされにくいです。 平木 : ありがとうございます。最後にメドレーで開発することの良さを教えてもらえればと思います。 田中 : メドレーは長期を見据えて課題に取り組んでいる会社であり、泥臭く地道に積み重ねなければいけないこともすごく多いです。そのため短期で結果が見えづらいこともあるかもしれません。ですが、医療領域でどっしりと腰を据えて長期的に本質を捉えた開発をしているため、技術面だけではなくプロダクトデザインとして何のために、どのようなアプローチが適切か、という思考力や設計能力も身に付きやすいのではないかと思います。 稲本 : 長期で課題に取り組むとなると「同じことの繰り返しではないか」と感じる方もいるかもしれませんが、プロダクトが成長すればフェーズも変わりますし、取り組むことにも変化が生じていくので成長や変化を楽しめる人には合っているのではないかと思います。 さいごに 新体制で CTO 2 人にメドレーの開発組織について色々と語ってもらいました。 2 人も話をしていましたが、メドレーでは長期思考・未来志向の考え方をベースに、ユーザーの本質的な課題はなにか、どうすればそれらを解決できるか?という部分にフォーカスを当てて開発をするという志向がとても強いと思います。 そうした環境でじっくりと確実にユーザーに価値を提供できるという仕事だと思いますので、ご興味を持たれた方はぜひカジュアルにお話をさせていただければと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに みなさん、こんにちは。技術広報・エンジニアの平木です。 既に 2 月に 発表 されていますが、4 月より弊社の経営体制を大幅に変更しました。開発組織について大きく変わった部分として CTO 2 名体制になった点があります。そこで、なぜ CTO を 2 名にしたのかや、これからのメドレーの開発組織についてを CTO になった 2 人にインタビューしました。 インタビュイー紹介 稲本さん 執行役員。人材プラットフォーム(以下、人材 PF)CTO。独立系 SIer でのインフラエンジニアに始まり、インターネット企業での様々なサービスのインフラ構築を経て、音楽配信サービスやインターネットラジオサービスのサーバサイドエンジニアとして従事。2014 年メドレー入社後は創業時から運営しているジョブメドレーのプロダクト開発に従事。その後、同サービスのリードエンジニア、開発責任者を経て、2023 年 4 月より現職。 田中さん 執行役員。医療プラットフォーム(以下、医療 PF)CTO。独立系 SIer でのアプリケーションエンジニアや IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとしてソーシャルゲームや動画サービスなどの開発立ち上げに従事。2016 年メドレー入社後は CLINICS カルテの立ち上げを経験。その後 CLINICS 開発責任者を経て医療 PF プロダクト横断基盤の立ち上げ・開発責任者を経て 2023 年 4 月より現職。 左から田中さん、稲本さん CTO を 2 名体制にして開発組織の体制をアップデートした理由 平木 : さっそくですが、2023 年 4 月よりお二人がそれぞれ人材 PF と医療 PF の CTO に就任されて、改めて開発組織のアップデートがされましたが、どういった背景でこの体制になったんでしょうか。 田中 : 会社全体の組織設計として医療 PF と人材 PF に分かれたというのがまず最初にあったんですが、当初、開発組織は両 PF を横断する形で 1 人の CTO が見ていました。各 PF 内にそれぞれプロダクトがあり開発もプロダクトごとにチームを分けていましたが、それぞれの開発チームが有機的に動きつつも組織全体の小回りの効きやすさなども考えて、こうした体制になっていました。 ただ、時間が経つに連れ段々と開発チームも各 PF 自体 の規模も大きくなってきたので、 1 人の CTO ではマネジメントが難しくなってきました。また医療 PF と人材 PF で共通する開発組織の文化などはありますが、扱っている事業内容の差異から選定技術やプロジェクトの進め方などの違う部分も出てきたので、それぞれの PF で CTO を置いて組織面・技術面の課題に対応しようということになったためです。 稲本 : 開発組織の人数としては 100 名を越えているのですが、それだけではなく就業場所の違いやサービスの多様化など、それぞれの PF で特性が違う課題がこれから先も出てくるだろうということで、先を見据えての開発組織のアップデートの一環として CTO 2 名体制にしたというところですね。 平木 : これからの組織の変化を見据えた動きだということですね。 新しい開発体制になって変わっていく部分・変わらない部分 平木 : 新体制になってまだ日が浅いですが、これから変えていく部分というのはどういったところになっていくんでしょうか。 稲本 : 自分達 2 人が持っている色々な役割を分解していって他のメンバーへ役割を委譲していくことですね。CTO が色々持ちすぎると組織のスケールに対しボトルネックになることは目に見えているので。 それ以外には、役割を担ったチームやメンバーが自身で思考し行動しやすい組織にしていくことで、更なるチャレンジがしやすかったりパフォーマンスを発揮しやすい状態にしていけたらと考えています。 平木 : なるほど。田中さんは何かありますか。 田中 : 「役割の明確化」もその 1 つですが、将来取り組むべき課題に向けて中長期を見据えた開発組織の設計や運営に向き合うフェイズになってきたと感じており、強化していきたいと考えています。 平木 : 将来の課題に対して技術・組織なども先手を打っていくイメージですね。 新体制での開発組織のミッション 平木 : それでは新体制になって改めて開発組織のミッションとしてはどういったものがありますか。 田中 : 会社全体のミッションと基本は一緒ではあります。「医療」という大きい領域の課題に対して、何のために開発をするのかや、その開発をすることによってどういった顧客価値を提供できるのかというところは、ちゃんとミッションに紐付いている部分なので変えずにいきます。 その上で、有効な施策をどれだけ素早くデリバリーできるかというところには今以上に注力していきたいところです。そのためには無駄を省き、やるべき事に集中してより生産性の高い開発を目指します。 プロダクト開発において無駄な機能開発を行なうと、後からそこを削ろうと思っても難しいですし、作った結果誰も幸せにならないということになってしまいます。こうならないためにもスピード感は落とさないで、事業側メンバーと協力しながら適切な要件整理をしたりなど、どうやって顧客へ適切なプロダクトを届けるか?という部分により重きを置いていきたいです。 事業とプロダクト開発の関係性 平木 : さて、次に先程のお話にも出てきましたが、事業とプロダクト開発の関係性についてお聞きします。お二人が考えるこの関係性はどのようなものでしょうか。 田中 : ここも以前から変わらないスタンスですが、改めて定義するなら「テクノロジーを最大限に使って医療領域の課題を解決する」という関係になります。テクノロジーとメドレーの事業である医療領域の課題解決という 2 つの側面のどれが欠けても、プロダクトとして良いものは生まれないと考えています。 平木 : 主に Web のテクノロジーを適材適所で課題解決のために使っていくというスタンスですね。 田中 : そうですね。先に来るのはあくまでも、「医療領域の課題解決をする」という部分になるのですが、その手段として必要な技術は全部使っていこうという姿勢です。なので、まずは開発組織のメンバー一人ひとりが、ドメインの理解をするということが始めの一歩です。そうして課題の本質を見極めて解決に必要な手段は何があるのかを考えていく必要が出てきます。 解決のために最適であれば、その技術の新旧や使用実績などを問わず取り入れて開発していくという心構えですね。そのためにはきちんと技術動向を追っていき日頃からユースケースなどを想像しながら、その技術を触ったりする必要も出てきます。 稲本 : ドメイン知識とテクノロジーを最大限に駆使し、プロダクト開発を通して価値を届けるというのが我々のミッションなのかなと思います。 稲本さん 平木 : 医療領域の未来を変えていくために、技術を最大限に用いたプロダクトドリブンであることが大切ということですね。 田中 : メドレーではそれだけじゃない部分も意識していますね。一言で「プロダクト」と言ってもサービスとそれを作る開発者というだけではなく、顧客接点を持つ事業部などのメンバー全ての動きも合わせて「プロダクト」という意識を持っています。 平木 : なるほど、プロダクトに関わっている人全員を合わせて「プロダクト」だよということですか。 田中 : 例えば開発メンバーが良いプロダクトを提供したとしても、そのプロダクトを使ったユーザーが疑問に思った部分があったとします。そこで問い合わせをしたときに、その体験が良くない場合はやっぱり「プロダクトに関する体験が良くない」という印象になってしまいますよね。これはセールスなど他の領域でも起き得る話ですが、メドレーではそういった部分も含めて「プロダクト開発」という意識を持っているので、他部署だからということではなくトータルでユーザーに価値を感じてもらえるようにしていくということを大切にしています。 平木 : 確かにユーザーはそういった関わるもの全部一緒に「プロダクト」と思いますもんね。 稲本 : 日々の業務の中で直接ユーザーと対話してくれている方々がたくさんいます。 Our Essentials (以下、OE)で特に好きなものの一つで「信頼を獲得する」というものがあります。OE 自体は主に社内向けのメッセージにはなるんですが、日々の業務の中で他者からの信頼を得ることが出来るような振る舞いや成果を出すこと、その信頼の積み重ねがプロダクト全体にも反映され、結果としてユーザーからも信頼される/価値を感じてもらえるプロダクトの提供につながっているのだと感じています。 プロダクトと技術の関係について 平木 : もう少し開発についての話を掘り下げていこうと思います。弊社では現在のところ Ruby や Ruby on Rails(以下 Rails)で作られているプロダクトが大半ではありますが、Go や Node.js などをメインに使っているプロダクトもありますよね。そうした技術選定のときの基本姿勢としてはどういったものがありますか。 田中 : 基本として、「それぞれのプロダクトにとって現時点でこの技術で開発するのが、良いことだよね」というのを大切にしています。組織的に知見が多いということもあって、Rails を使っているプロダクトが多いのはそうなんですが、この技術じゃないとダメという縛りがあるわけではないです。Go や Node.js にしても、そのプロダクトに現状最適だという視点で技術選定をしています。 稲本 : 今までできなかったことが、新しい技術を使ったら解決するのにという場面も結構あると思います。そういうときに「今使ってないから…」というのではなく、その技術を使ったほうがプロダクトにプラスになると判断したら使っていくようにしています。 田中 : ですので、今プロダクトで使っている技術で守っていくというより、個々人でアンテナを張って新しい技術は積極的にキャッチアップしていきながら、チーム内で「これ使うと良さそうだからやっていこう」という感じですね。 平木 : 先程も出てきたユーザーに価値提供を素早くするという要素の一つということですね。そうすると、例えば開発者体験を良くするような技術やフローなんかを取り入れるというのも、そうした志向の一部ということになりますか? 田中 : そうですね。良いものを早くユーザーに届けるという一側面になります。開発者体験を良くするというのが最終的な目的ではなくて、その結果としてチームの生産性が上がるのなら体験を良くすると良いよねという。プロダクトコードの負債解消とかもそうですが、バランスが確かに難しいのですが、こういったところを疎かにすると結果として顧客への十分な価値提供ができなくなってしまうリスクもあるので、短期と中長期できちんとバランシングしつつ開発に取り組んでいきたいと思っています。 田中さん 平木 : 改めてここで現在の開発体制について伺っていければと思います。 田中 : 人材 PF も医療 PF もプロダクトに紐付いて複数のチームに分かれていて、チームの人数規模としてみると多少の上下はあるんですが、プロダクトマネージャ(以下、PdM) やデザイナー、エンジニアを合わせて 10 人前後というチームが多いです。 平木 : そうしたチーム内で、特にエンジニアはどのような役割分担をしていることが多いですか? 稲本 : もちろんチームによっても違ってくるんですが、チームの中に PM と TL を置いて、そのチームの中にメンバーが数人いる形が多いですね。この単位をベースにして toC 向けのプロジェクト、toB 向けのプロジェクトのような感じでチームを分けて開発を行なっています。 田中 : メンバー個々人はサーバサイドやフロントエンド、インフラのような得意領域がありそこを考慮した開発をしていっていますが、サーバサイドだけやる人という形ではなく、得意領域と隣り合う領域についても意識し、可能な限り理解してコラボレーションしていこうというやり方を取っています。これによりコミュニケーションコストが下がることで生産性高く、品質向上にも寄与するというのが理由になります。 もちろんプロとして自分の得意分野で力を存分に発揮はしてもらうというのは大前提ですが、その強みを周囲のメンバーに還元しつつ、得意領域以外はそこが得意なメンバーから還元してもらいながら、良いプロダクトを作っていくというのがメドレーの開発組織のスタンスになります。 開発組織の雰囲気やメンバーの働き方について 平木 : 今まで開発組織の制度的な側面を中心に聞いてきましたが、組織のソフト面についてお聞きします。メドレーの開発組織の雰囲気ですが、どんな雰囲気だったりしますか? 稲本 : 全体的にはわりと和やかな雰囲気は持ちつつも、各自のバリューに対してはストイックな感じかなと思います。 自分たちで決めた期日など守るところはしっかり守ろうという意識を持ちつつ、無理な期日になりそうであればスケジュールやスコープを調整したり、プロジェクトの進め方で上手く行ったこと/行かなかったことを振り返りを通して改善を図ったりするなど、当たり前のことを高い水準でやり遂げていく習慣が根付いていると思います。 コミュニケーションに関しては、非同期コミュニケーションがベースではあります。口頭での相談なんかももちろんしますが、認識がずれないように話したことを issue などに残したりしてます。 雑談みたいなものはミーティングの後半パートにあえて雑談パートを作るなどしていますが、業務中にずっとするようなことはないですね。開発中はそれぞれが集中していることが多いです。 田中 : チームごとに違いますが、朝会や夕会などでちゃんとコミュニケーションが取れるようにしているということが多いですね。なので、あんまり他の時間に雑談みたいなものが必要ないという感じです。 平木 : 和やかながらも、締めるところは締めるという雰囲気ですね。開発チームの皆さんはどんな働き方をしている人が多いですか? 稲本 : これも個々人で違っていますが出社とリモートのハイブリッドな方が多いかと思います。 やはり開発に集中したい場面ではリモートの方が割り込みも少なくなりやすく集中しやすいですし、物事の認識を揃えたり決めていく場合は、ミーティングなどで実際に顔を合わせた方が効率も良いことはあると思いますね。 田中 : 自分のバリューを最大化できるように働いていこうというのが基本になっています。 平木 : 各自ちゃんと考えてプロダクトに貢献できるようにということですね。そうした働き方の先にキャリアパスがあると思いますが、その観点ではどのようなパスがありますか? 田中 : 大きく分けては、マネジメントをメインにするか、テクニカル部分を特化させたスペシャリストとなるかという 2 つになります。役割分担にもちょっと関わりますが、内容としては、例えば PdM を目指していくというのも道としてはありますし、自分の得意領域を最大限に伸ばしてテックリードとして技術をもってプロダクトを引っぱっていくという道なんかもあります。1on1 や評価などでそうした部分は本人と刷り合わせをしていきながら決めていく形になります。 平木 : 評価のお話が出てきましたが、どういった観点で評価されますか? 稲本 : ベースとしては会社として決めた OE に沿ったものになります。OE を踏まえつつ、業務目標や技術的な目標を立ててそれができたかどうかが基準になりますね。 立てた目標が色々な理由で達成できなかったというケースももちろん出てくると思いますが、内容として自分はどういった部分は頑張ったとか、どういう理由で達成できなかったなどの自分なりの説明ができるかというのを重視しています。 田中 : 目標はチームバリューにいかに寄与したかを重視しています。OE に基づきチームとして達成しないといけない目標があってそれをブレイクダウンした上で、自分が寄与できるのはどこかという感じで目標にしてもらいます。一見目に見えにくい動きだったとしても、ちゃんとチームに貢献しているねとなれば、もちろん評価の対象になりますし、逆に例えばいくら頑張って新技術を習得したとなってもチームに貢献してなければ評価はされにくいです。 平木 : ありがとうございます。最後にメドレーで開発することの良さを教えてもらえればと思います。 田中 : メドレーは長期を見据えて課題に取り組んでいる会社であり、泥臭く地道に積み重ねなければいけないこともすごく多いです。そのため短期で結果が見えづらいこともあるかもしれません。ですが、医療領域でどっしりと腰を据えて長期的に本質を捉えた開発をしているため、技術面だけではなくプロダクトデザインとして何のために、どのようなアプローチが適切か、という思考力や設計能力も身に付きやすいのではないかと思います。 稲本 : 長期で課題に取り組むとなると「同じことの繰り返しではないか」と感じる方もいるかもしれませんが、プロダクトが成長すればフェーズも変わりますし、取り組むことにも変化が生じていくので成長や変化を楽しめる人には合っているのではないかと思います。 さいごに 新体制で CTO 2 人にメドレーの開発組織について色々と語ってもらいました。 2 人も話をしていましたが、メドレーでは長期思考・未来志向の考え方をベースに、ユーザーの本質的な課題はなにか、どうすればそれらを解決できるか?という部分にフォーカスを当てて開発をするという志向がとても強いと思います。 そうした環境でじっくりと確実にユーザーに価値を提供できるという仕事だと思いますので、ご興味を持たれた方はぜひカジュアルにお話をさせていただければと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
はじめに みなさん、こんにちは。技術広報・エンジニアの平木です。 既に 2 月に 発表 されていますが、4 月より弊社の経営体制を大幅に変更しました。開発組織について大きく変わった部分として CTO 2 名体制になった点があります。そこで、なぜ CTO を 2 名にしたのかや、これからのメドレーの開発組織についてを CTO になった 2 人にインタビューしました。 インタビュイー紹介 稲本さん 執行役員。人材プラットフォーム(以下、人材 PF)CTO。独立系 SIer でのインフラエンジニアに始まり、インターネット企業での様々なサービスのインフラ構築を経て、音楽配信サービスやインターネットラジオサービスのサーバサイドエンジニアとして従事。2014 年メドレー入社後は創業時から運営しているジョブメドレーのプロダクト開発に従事。その後、同サービスのリードエンジニア、開発責任者を経て、2023 年 4 月より現職。 田中さん 執行役員。医療プラットフォーム(以下、医療 PF)CTO。独立系 SIer でのアプリケーションエンジニアや IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとしてソーシャルゲームや動画サービスなどの開発立ち上げに従事。2016 年メドレー入社後は CLINICS カルテの立ち上げを経験。その後 CLINICS 開発責任者を経て医療 PF プロダクト横断基盤の立ち上げ・開発責任者を経て 2023 年 4 月より現職。 左から田中さん、稲本さん CTO を 2 名体制にして開発組織の体制をアップデートした理由 平木 : さっそくですが、2023 年 4 月よりお二人がそれぞれ人材 PF と医療 PF の CTO に就任されて、改めて開発組織のアップデートがされましたが、どういった背景でこの体制になったんでしょうか。 田中 : 会社全体の組織設計として医療 PF と人材 PF に分かれたというのがまず最初にあったんですが、当初、開発組織は両 PF を横断する形で 1 人の CTO が見ていました。各 PF 内にそれぞれプロダクトがあり開発もプロダクトごとにチームを分けていましたが、それぞれの開発チームが有機的に動きつつも組織全体の小回りの効きやすさなども考えて、こうした体制になっていました。 ただ、時間が経つに連れ段々と開発チームも各 PF 自体 の規模も大きくなってきたので、 1 人の CTO ではマネジメントが難しくなってきました。また医療 PF と人材 PF で共通する開発組織の文化などはありますが、扱っている事業内容の差異から選定技術やプロジェクトの進め方などの違う部分も出てきたので、それぞれの PF で CTO を置いて組織面・技術面の課題に対応しようということになったためです。 稲本 : 開発組織の人数としては 100 名を越えているのですが、それだけではなく就業場所の違いやサービスの多様化など、それぞれの PF で特性が違う課題がこれから先も出てくるだろうということで、先を見据えての開発組織のアップデートの一環として CTO 2 名体制にしたというところですね。 平木 : これからの組織の変化を見据えた動きだということですね。 新しい開発体制になって変わっていく部分・変わらない部分 平木 : 新体制になってまだ日が浅いですが、これから変えていく部分というのはどういったところになっていくんでしょうか。 稲本 : 自分達 2 人が持っている色々な役割を分解していって他のメンバーへ役割を委譲していくことですね。CTO が色々持ちすぎると組織のスケールに対しボトルネックになることは目に見えているので。 それ以外には、役割を担ったチームやメンバーが自身で思考し行動しやすい組織にしていくことで、更なるチャレンジがしやすかったりパフォーマンスを発揮しやすい状態にしていけたらと考えています。 平木 : なるほど。田中さんは何かありますか。 田中 : 「役割の明確化」もその 1 つですが、将来取り組むべき課題に向けて中長期を見据えた開発組織の設計や運営に向き合うフェイズになってきたと感じており、強化していきたいと考えています。 平木 : 将来の課題に対して技術・組織なども先手を打っていくイメージですね。 新体制での開発組織のミッション 平木 : それでは新体制になって改めて開発組織のミッションとしてはどういったものがありますか。 田中 : 会社全体のミッションと基本は一緒ではあります。「医療」という大きい領域の課題に対して、何のために開発をするのかや、その開発をすることによってどういった顧客価値を提供できるのかというところは、ちゃんとミッションに紐付いている部分なので変えずにいきます。 その上で、有効な施策をどれだけ素早くデリバリーできるかというところには今以上に注力していきたいところです。そのためには無駄を省き、やるべき事に集中してより生産性の高い開発を目指します。 プロダクト開発において無駄な機能開発を行なうと、後からそこを削ろうと思っても難しいですし、作った結果誰も幸せにならないということになってしまいます。こうならないためにもスピード感は落とさないで、事業側メンバーと協力しながら適切な要件整理をしたりなど、どうやって顧客へ適切なプロダクトを届けるか?という部分により重きを置いていきたいです。 事業とプロダクト開発の関係性 平木 : さて、次に先程のお話にも出てきましたが、事業とプロダクト開発の関係性についてお聞きします。お二人が考えるこの関係性はどのようなものでしょうか。 田中 : ここも以前から変わらないスタンスですが、改めて定義するなら「テクノロジーを最大限に使って医療領域の課題を解決する」という関係になります。テクノロジーとメドレーの事業である医療領域の課題解決という 2 つの側面のどれが欠けても、プロダクトとして良いものは生まれないと考えています。 平木 : 主に Web のテクノロジーを適材適所で課題解決のために使っていくというスタンスですね。 田中 : そうですね。先に来るのはあくまでも、「医療領域の課題解決をする」という部分になるのですが、その手段として必要な技術は全部使っていこうという姿勢です。なので、まずは開発組織のメンバー一人ひとりが、ドメインの理解をするということが始めの一歩です。そうして課題の本質を見極めて解決に必要な手段は何があるのかを考えていく必要が出てきます。 解決のために最適であれば、その技術の新旧や使用実績などを問わず取り入れて開発していくという心構えですね。そのためにはきちんと技術動向を追っていき日頃からユースケースなどを想像しながら、その技術を触ったりする必要も出てきます。 稲本 : ドメイン知識とテクノロジーを最大限に駆使し、プロダクト開発を通して価値を届けるというのが我々のミッションなのかなと思います。 稲本さん 平木 : 医療領域の未来を変えていくために、技術を最大限に用いたプロダクトドリブンであることが大切ということですね。 田中 : メドレーではそれだけじゃない部分も意識していますね。一言で「プロダクト」と言ってもサービスとそれを作る開発者というだけではなく、顧客接点を持つ事業部などのメンバー全ての動きも合わせて「プロダクト」という意識を持っています。 平木 : なるほど、プロダクトに関わっている人全員を合わせて「プロダクト」だよということですか。 田中 : 例えば開発メンバーが良いプロダクトを提供したとしても、そのプロダクトを使ったユーザーが疑問に思った部分があったとします。そこで問い合わせをしたときに、その体験が良くない場合はやっぱり「プロダクトに関する体験が良くない」という印象になってしまいますよね。これはセールスなど他の領域でも起き得る話ですが、メドレーではそういった部分も含めて「プロダクト開発」という意識を持っているので、他部署だからということではなくトータルでユーザーに価値を感じてもらえるようにしていくということを大切にしています。 平木 : 確かにユーザーはそういった関わるもの全部一緒に「プロダクト」と思いますもんね。 稲本 : 日々の業務の中で直接ユーザーと対話してくれている方々がたくさんいます。 Our Essentials (以下、OE)で特に好きなものの一つで「信頼を獲得する」というものがあります。OE 自体は主に社内向けのメッセージにはなるんですが、日々の業務の中で他者からの信頼を得ることが出来るような振る舞いや成果を出すこと、その信頼の積み重ねがプロダクト全体にも反映され、結果としてユーザーからも信頼される/価値を感じてもらえるプロダクトの提供につながっているのだと感じています。 プロダクトと技術の関係について 平木 : もう少し開発についての話を掘り下げていこうと思います。弊社では現在のところ Ruby や Ruby on Rails(以下 Rails)で作られているプロダクトが大半ではありますが、Go や Node.js などをメインに使っているプロダクトもありますよね。そうした技術選定のときの基本姿勢としてはどういったものがありますか。 田中 : 基本として、「それぞれのプロダクトにとって現時点でこの技術で開発するのが、良いことだよね」というのを大切にしています。組織的に知見が多いということもあって、Rails を使っているプロダクトが多いのはそうなんですが、この技術じゃないとダメという縛りがあるわけではないです。Go や Node.js にしても、そのプロダクトに現状最適だという視点で技術選定をしています。 稲本 : 今までできなかったことが、新しい技術を使ったら解決するのにという場面も結構あると思います。そういうときに「今使ってないから…」というのではなく、その技術を使ったほうがプロダクトにプラスになると判断したら使っていくようにしています。 田中 : ですので、今プロダクトで使っている技術で守っていくというより、個々人でアンテナを張って新しい技術は積極的にキャッチアップしていきながら、チーム内で「これ使うと良さそうだからやっていこう」という感じですね。 平木 : 先程も出てきたユーザーに価値提供を素早くするという要素の一つということですね。そうすると、例えば開発者体験を良くするような技術やフローなんかを取り入れるというのも、そうした志向の一部ということになりますか? 田中 : そうですね。良いものを早くユーザーに届けるという一側面になります。開発者体験を良くするというのが最終的な目的ではなくて、その結果としてチームの生産性が上がるのなら体験を良くすると良いよねという。プロダクトコードの負債解消とかもそうですが、バランスが確かに難しいのですが、こういったところを疎かにすると結果として顧客への十分な価値提供ができなくなってしまうリスクもあるので、短期と中長期できちんとバランシングしつつ開発に取り組んでいきたいと思っています。 田中さん 平木 : 改めてここで現在の開発体制について伺っていければと思います。 田中 : 人材 PF も医療 PF もプロダクトに紐付いて複数のチームに分かれていて、チームの人数規模としてみると多少の上下はあるんですが、プロダクトマネージャ(以下、PdM) やデザイナー、エンジニアを合わせて 10 人前後というチームが多いです。 平木 : そうしたチーム内で、特にエンジニアはどのような役割分担をしていることが多いですか? 稲本 : もちろんチームによっても違ってくるんですが、チームの中に PM と TL を置いて、そのチームの中にメンバーが数人いる形が多いですね。この単位をベースにして toC 向けのプロジェクト、toB 向けのプロジェクトのような感じでチームを分けて開発を行なっています。 田中 : メンバー個々人はサーバサイドやフロントエンド、インフラのような得意領域がありそこを考慮した開発をしていっていますが、サーバサイドだけやる人という形ではなく、得意領域と隣り合う領域についても意識し、可能な限り理解してコラボレーションしていこうというやり方を取っています。これによりコミュニケーションコストが下がることで生産性高く、品質向上にも寄与するというのが理由になります。 もちろんプロとして自分の得意分野で力を存分に発揮はしてもらうというのは大前提ですが、その強みを周囲のメンバーに還元しつつ、得意領域以外はそこが得意なメンバーから還元してもらいながら、良いプロダクトを作っていくというのがメドレーの開発組織のスタンスになります。 開発組織の雰囲気やメンバーの働き方について 平木 : 今まで開発組織の制度的な側面を中心に聞いてきましたが、組織のソフト面についてお聞きします。メドレーの開発組織の雰囲気ですが、どんな雰囲気だったりしますか? 稲本 : 全体的にはわりと和やかな雰囲気は持ちつつも、各自のバリューに対してはストイックな感じかなと思います。 自分たちで決めた期日など守るところはしっかり守ろうという意識を持ちつつ、無理な期日になりそうであればスケジュールやスコープを調整したり、プロジェクトの進め方で上手く行ったこと/行かなかったことを振り返りを通して改善を図ったりするなど、当たり前のことを高い水準でやり遂げていく習慣が根付いていると思います。 コミュニケーションに関しては、非同期コミュニケーションがベースではあります。口頭での相談なんかももちろんしますが、認識がずれないように話したことを issue などに残したりしてます。 雑談みたいなものはミーティングの後半パートにあえて雑談パートを作るなどしていますが、業務中にずっとするようなことはないですね。開発中はそれぞれが集中していることが多いです。 田中 : チームごとに違いますが、朝会や夕会などでちゃんとコミュニケーションが取れるようにしているということが多いですね。なので、あんまり他の時間に雑談みたいなものが必要ないという感じです。 平木 : 和やかながらも、締めるところは締めるという雰囲気ですね。開発チームの皆さんはどんな働き方をしている人が多いですか? 稲本 : これも個々人で違っていますが出社とリモートのハイブリッドな方が多いかと思います。 やはり開発に集中したい場面ではリモートの方が割り込みも少なくなりやすく集中しやすいですし、物事の認識を揃えたり決めていく場合は、ミーティングなどで実際に顔を合わせた方が効率も良いことはあると思いますね。 田中 : 自分のバリューを最大化できるように働いていこうというのが基本になっています。 平木 : 各自ちゃんと考えてプロダクトに貢献できるようにということですね。そうした働き方の先にキャリアパスがあると思いますが、その観点ではどのようなパスがありますか? 田中 : 大きく分けては、マネジメントをメインにするか、テクニカル部分を特化させたスペシャリストとなるかという 2 つになります。役割分担にもちょっと関わりますが、内容としては、例えば PdM を目指していくというのも道としてはありますし、自分の得意領域を最大限に伸ばしてテックリードとして技術をもってプロダクトを引っぱっていくという道なんかもあります。1on1 や評価などでそうした部分は本人と刷り合わせをしていきながら決めていく形になります。 平木 : 評価のお話が出てきましたが、どういった観点で評価されますか? 稲本 : ベースとしては会社として決めた OE に沿ったものになります。OE を踏まえつつ、業務目標や技術的な目標を立ててそれができたかどうかが基準になりますね。 立てた目標が色々な理由で達成できなかったというケースももちろん出てくると思いますが、内容として自分はどういった部分は頑張ったとか、どういう理由で達成できなかったなどの自分なりの説明ができるかというのを重視しています。 田中 : 目標はチームバリューにいかに寄与したかを重視しています。OE に基づきチームとして達成しないといけない目標があってそれをブレイクダウンした上で、自分が寄与できるのはどこかという感じで目標にしてもらいます。一見目に見えにくい動きだったとしても、ちゃんとチームに貢献しているねとなれば、もちろん評価の対象になりますし、逆に例えばいくら頑張って新技術を習得したとなってもチームに貢献してなければ評価はされにくいです。 平木 : ありがとうございます。最後にメドレーで開発することの良さを教えてもらえればと思います。 田中 : メドレーは長期を見据えて課題に取り組んでいる会社であり、泥臭く地道に積み重ねなければいけないこともすごく多いです。そのため短期で結果が見えづらいこともあるかもしれません。ですが、医療領域でどっしりと腰を据えて長期的に本質を捉えた開発をしているため、技術面だけではなくプロダクトデザインとして何のために、どのようなアプローチが適切か、という思考力や設計能力も身に付きやすいのではないかと思います。 稲本 : 長期で課題に取り組むとなると「同じことの繰り返しではないか」と感じる方もいるかもしれませんが、プロダクトが成長すればフェーズも変わりますし、取り組むことにも変化が生じていくので成長や変化を楽しめる人には合っているのではないかと思います。 さいごに 新体制で CTO 2 人にメドレーの開発組織について色々と語ってもらいました。 2 人も話をしていましたが、メドレーでは長期思考・未来志向の考え方をベースに、ユーザーの本質的な課題はなにか、どうすればそれらを解決できるか?という部分にフォーカスを当てて開発をするという志向がとても強いと思います。 そうした環境でじっくりと確実にユーザーに価値を提供できるという仕事だと思いますので、ご興味を持たれた方はぜひカジュアルにお話をさせていただければと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
アバター
こんにちは。医療プラットフォーム第一本部プロダクト開発室所属エンジニアの髙橋です。 普段の業務では、 医療 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 デザインシステムの構築とプロダクトへの反映 このような取り組みに興味がある方は次のリンクから是非ご連絡ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 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 への完全移行を目指して日々改善を続けています。 ↩
アバター