この記事は メドレー夏のブログリレー2025 5日目の記事です。 はじめに こんにちは!メドレーでDevRelをしている重田です。 突然ですが、転職先を探す時に実際に働く環境や雰囲気って気になりますよね? この記事では、メドレーのエンジニアが普段どんな環境で仕事をしているのかを写真メインでご紹介します! 「メドレーのオフィスで働きたい!」「働くイメージが持てた!」と思っていただけたら嬉しいです🙌 では、早速オフィスツアースタートです📣 🏢 私たちが働くオフィス 私たちのオフィスは日比谷線 六本木駅直結の六本木ヒルズ森タワーの12階と13階にあります。 詳細は以下記事でご紹介しています🙋♀️ メドレーオフィス(13階)へのアクセスと入館方法のご案内 [六本木ヒルズ森タワー]メドレーのオフィスを初披露 🏙️外観 六本木ヒルズ森タワーは54F建て、高さ238メートルの超高層オフィスビルです。 上を見上げる高さで、たまに見ると「おお〜〜都会だ!」となり背筋が伸びます。笑 🏥13階 エンジニアの中でも医療プラットフォームのプロダクト開発エンジニアやコーポレートITのメンバーが13階で働いています。 エントランス(受付) 13階のエントランスは清潔感のある白の壁に温かみのある木目調の床です。 憩いスペース 約100席と、広々したスペースです。テーブル席の他、ソファもあるのでゆったりと過ごせます。 カウンターには賞状などが飾られています。 🏥12階 エンジニアの中でも人材プラットフォームのプロダクト開発エンジニアが12階で働いています。 憩いスペース こちらは今年の春に増設されたスペースです! ランチだけではなく、1on1やチームミーティングでも大活躍のスペースです。 ボックス席にはディスプレイが付いており、チームミーティングの際に役立ちます。 ゆっくりしたいときはこの席がおすすめです🍵 東京タワーを眺めて仕事ができるのは六本木、そして12階に位置する特権です🗼 憩いスペース入口には弊社プロダクトを体験できるコーナーがあります。 また、自動販売機2台・コーヒー販売機2台・食品自動販売機1台・冷蔵庫が完備されています☕️ 小腹が減った時の救世主🙏コンビニに行くのが面倒な時に大活躍です! 持参/購入したお弁当を温められるのも嬉しいポイント💡 🏥共通 会議室 会議室は50室以上あり、来客専用・社内専用の会議室が分かれています。 ✨メドレーのカルチャー「クリーンデスクポリシー」に込められた想い メドレーが大切にしているカルチャーのひとつに「クリーンデスクポリシー」があります。これはデスクの上には仕事道具以外置かない、帰宅時にはモニタ・キーボード・マウス以外は全て片付けるというルールのことです。 「クリーンデスクポリシー」は代表の瀧口が 「佐藤可士和の超整理術」 という本から影響を受け、創業時から大切にしているカルチャーです。なぜ「クリーンデスクポリシー」が大切なのか、瀧口が社内で共有している資料から引用してご紹介します。 整理整頓には、視点が必要です。複雑なものであれば、それぞれの因果関係を見抜き、何が大切なのかの優先順位をつけることが必要です。「頭で理解しているつもりのこと」と「身体に染み付いて実践できること」には大きな隔たりがあります。実践への近道は、たった一つの象徴的なことを徹底することではないでしょうか。 例えば、この記事 「日本電産が赤字会社を速攻で再生できたワケ」 の「組織体質を変える一番の早道は」を読んでみてください。整理・整頓・清潔・躾(自主的な)を徹底して、営業回数を増やせば成功すると書いています。 そもそも仕事机は、仕事をするためのものです。クリーンデスクというのは整理整頓ができる組織であることの象徴です。このような背景で、クリーンデスクポリシーは僕にとって、大切にしたい考え方なのです。 実際、社員の机は常に整理されています。 また、過去にも「クリーンデスクポリシー」に触れているのでぜひご覧いただけると嬉しいです! [六本木ヒルズ森タワー]メドレーのオフィスを初披露 📚スキルアップ支援やライフステージに合わせた柔軟な働き方を実現 この記事では主にエンジニアの制度をご紹介します! 支援制度 社内勉強会支援 社内で勉強会を開催する際の飲食補助が出ます! 書籍購入の補助 会社の費用で購入可! ※資産管理の観点から電子書籍は除く 資格取得支援 AWS認定試験・Ruby技術認定試験を会社の費用で受験可! ※一定条件あり カンファレンス参加費用の補助 RubyKaigi などの外部カンファレンスへ会社の費用で参加可! 制度を利用する際は、以下のようにSlackで申請をしています。 希望PC・モニター・アーロンチェアの貸与 エンジニアの方には希望PCとモニターが貸与されます。 基本的にはスペックも自由に選べます🙆♀️ 湾曲型モニター また、希望者にはアーロンチェアも用意しています。 アーロンチェア 働く環境 よくある質問をもとにご紹介します! Q. 勤務時間は決まっていますか? A. 開発職では裁量労働制を採用しており、多くの社員は事業部の稼働時間に合わせて10:00~19:00で勤務しています。 一方で、子育て中の社員については、勤務時間中に一時的に離席するなど、個々の状況に応じた柔軟な体制をとっています。 Q. 出社とリモートの頻度はどのぐらいですか? A. エンジニア/デザイナーの場合は週2出社、週3リモートをベースにしています。 こちらも勤務時間と同様、体調やご家庭の事情など、柔軟に対応できる体制になっています。 Q. キャリアロールはどのようになっていますか? A. 「スペシャリスト」と「マネジメント」の2つに大別されます。事業責任者のロールも存在しますが、役職ではなく、一つのロールとして位置づけています。 Q. 担当するプロダクトは決まっていますか? A. エージェントからのご紹介時・スカウトをお送りする際などにプロダクトを限定している場合もありますが、基本的には選考を通じて皆様のご希望や志向、スキルスタックなども合わせてご提案・ご相談しながら決めています。 イベントでも大活躍のオフィス メドレーでは定期的にイベントを開催したり、オフィスを他社様や外部コミュニティのイベント会場として提供したりしています。 こちらは5月にリンケージ社とヘンリー社と開催したイベント、 HealthTech Meetup の様子です。 Roppongi.rb 、 Omotesando.rb の会場としても定期的にご利用いただいています🙌 🍀最後に 最後までご覧いただきありがとうございました! メドレーで働くイメージはつきましたでしょうか!? 直近では、以下のイベントを開催予定です! 少しでもご興味のある方は、ぜひイベントを機にオフィスにいらしてください!ご参加お待ちしております✨ 9/4(木)19:30 Omotesando.rb#113 9/5(金)19:00 【有料プランユーザー限定×オフライン】MagicPodユーザーミートアップ We’re hiring 最後まで読んでいただきありがとうございます! メドレーでは、一緒に働く仲間を大募集中です👫 少しでも興味を持っていただけましたら、ぜひカジュアル面談でお待ちしています! 🤝 募集一覧 🗣️ カジュアル面談 Medley Summer Tech Blog Relay 6 日目は、医療プラットフォーム本部の安東(Andō)さんの記事です! それでは良い週末をお過ごしください!
こんにちは!メドレーでDevRelをしている重田です。 今年も暑い日が続いていますがいかがお過ごしですか? メドレーでは夏企画として『MEDLEY Summer Tech Blog Relay』と題して、ブログリレーを開催します! 8/25(月)〜9/26(金)まで毎日異なるメンバーが技術やエンジニアリング、個人開発など幅広いテーマでテックブログを公開していきます! 本記事にて毎日ブログを追記更新していくので、ぜひお楽しみください✨ ※土日祝を除く ブログリレーカレンダー 🗓️第1週(8/25~8/29) Day1:SRE屋のひとりごと(玉井) Day2:AIで実現する10x時代の組織学習型QA(小島) Day3:Lambda@Edgeを使った画像リサイズ配信の構築記録(森川) Day4:Design→Codeの現実解を考える(中村) Day5:メドレーエンジニアの働く環境をご紹介(重田) 🗓️第2週(9/1~9/5) Day6:データアナリストの分析プロセスにおけるAI活用(安東) Day7:データの何かについて(山邊) Day8:事業部=>QAにジョブチェンジしてみた話(内堀) Day9:OpenSearchについて(仮)(山下) Day10:WACATE2025夏に参加した話(井津) 🗓️第3週(9/8~9/12) Day11:エンジニア組織におけるAI活用状況とモニタリング(仮)(倉林) Day12:デザイナーは何考えながらデザイン作ってるのか書きます(仮)(近藤) Day13:TBD(稲村) Day14:医療PFマーケティングアセットについて書きます(進) Day15:DRの話 or 何か(小泉) 🗓️第4週(9/16~9/19) Day16:TBD(山田) Day17:GraphQL ruby読んでみた(仮)(川原) Day18:歯科向け電子レセプトビューアを作ってみた(平林) Day19:Playwright * デザインシステムMCPを使って楽したい (仮)(池田) 🗓️第5週(9/22~9/26) Day20:生成AIを使用してバグを効率的に解消した話(仮)(山下) Day21:CLINICS負荷試験の話かSLO運用について話します(山田) Day22:生成AI時代に向けて、開発効率10xを支えるリリース戦略の見直し(桶谷・小島) Day23: TBD(前田) 🍉We’re hiring! メドレーでは、「医療ヘルスケアの未来をつくる」仲間を大募集しています! 少しでも興味をお持ちいただけましたら、ぜひ、カジュアル面談にお越しください🙌 メドレーで働く | 株式会社メドレー メドレーの組織文化や募集要項をご紹介します www.medley.jp Medley Engineer Entrance Book この度は株式会社メドレーに興味をお寄せいただきありがとうございます。本資料は、メドレーへの転職をご検討いただいている皆様に、当社をより深くご理解いただくために作成いたしました。 medley-inc.notion.site
はじめに こんにちは! 医療プラットフォーム本部 プラットフォーム開発室 SRE グループの山田です。 医療機関向け SaaS である CLINICS の安定稼働とシステム信頼性の向上に取り組んでいます。 メドレーは 7 月 11 日、12 日に TOC 有明 (東京都江東区)で開催された SRE NEXT 2025 に LOGO Sponsor として協賛しました! SRE NEXT は、信頼性に関するプラクティスに深い関心を持つエンジニアのためのカンファレンスです。 医療プラットフォーム本部 SRE グループは発足して間もないため、他社のさまざまな挑戦や SRE プラクティスを学ぶべく、私を含め数名のエンジニアが参加し、たくさんの方々と交流させていただきました。 本レポートでは、SRE NEXT 2025 の会場や企業ブースの様子、そして発表の内容についてご紹介します。 会場の様子 SRE NEXT 2025 は、オンラインとオフラインのハイブリッド形式で開催されました。 50 を超える企業の協賛のもと、740 名もの SRE エンジニアが現地に参加しました。 広々とした会場 企業ブースも多数出展しており、SRE にまつわるアンケートや SRE プラクティスの紹介など非常に面白かったです。 株式会社タイミー様のブースにお邪魔させていただきました 2 日目の最後には懇親会も催され、様々な SRE エンジニアの方と交流することができました。 懇親会の様子 発表の様子 どのセッションも大変興味深かったのですが、特に印象深かった下記のセッションについてご紹介します。 Day1: SRE 不在の開発チームが障害対応と 向き合った 100 日間 Day2: 伴走から自律へ:形式知へと導く SRE イネーブリングによる、プロダクトチームの信頼性オーナーシップ向上 Day2: Four Keys から始める信頼性の改善 SRE 不在の開発チームが障害対応と 向き合った 100 日間 (Loglass 勝丸真さん) 引用元: speakerdeck.com カスタマーサクセスチームからのフィードバックをきっかけに、インシデント対応の改善に取り組んだ際の課題と、その解決策が紹介されました。 開発チームが障害対応に直面する中で、エンジニアによって対応品質にばらつきがあり、カスタマーサクセスチームから「障害対応がスムーズに進まない」「全体の体制や連絡手段が曖昧」といったフィードバックを受けるという課題があったとのことです。特に、あるエンジニアは単純な修正作業のみを行う一方で、別のエンジニアは影響範囲の特定やカスタマーサクセスへの回避策伝達まで含めた包括的な対応を実施するなど、対応者によって大きな差が生じていました。 そこで、専任のインシデントコマンダーチームを編成し、全エンジニアによるローテーション制から専門化による品質向上を図るアプローチへ切り替えたことが説明されました。また、障害対応フローの明確化とシンプル化、インシデントレベルの再定義、外部ツール「Warroom」の導入による自動記録・AI 要約機能の活用についても具体的な手法が紹介されました。 さらに、プロジェクトの推進においてはプロセス整備だけでなく、ビジネスチームやプロダクトチームとの継続的な対話を重視し、「なぜこの変更が必要なのか」という背景を丁寧に共有することで組織全体の理解を深め、技術的なベストプラクティスとビジネス要求のギャップを埋めていく文化づくりの重要性も解説されていました。 所感 このセッションで特に印象的だったのは、教科書的なベストプラクティスをそのまま適用するのではなく、現場の実情に合わせて大胆な割り切りを行っていた点です。中でも、「全エンジニアがインシデントコマンダーになる」という理想を一旦捨て、あえて属人化を許容して専門チームを作るという判断は、実践が進んでいるからこその現実的な選択だと感じました。 CLINICS SRE でも、理想的なインシデント対応体制を構築しようとする際に、全エンジニアのスキルレベルやモチベーションのばらつきという現実的な課題に直面することがあります。この発表から学んだのは、完璧な体制を目指すよりも、まずは実効性のある仕組みを作り上げることの重要性です。専門チームによる安定した対応基盤があってこそ、その後の全体的なスキル向上や体制の民主化が可能になるのだと理解しました。 また、プロセス整備だけでは解決できない人間関係や組織文化の課題に対し、継続的な対話を通じて理解を深めていく姿勢も非常に参考になりました。CLINICS でも、開発チームや事業部との間で、障害対応時の連携や認識に齟齬が生じることは少なくありません。この発表から学んだ対話を重ねることが重要であるという点は、今後の SRE の取り組みに活かしていきたいと思います。 現場の泥臭い課題に真摯に向き合い、組織として解決策を模索する姿勢は、同じような課題に取り組む SRE チームにとって大きな学びとなる発表でした。 伴走から自律へ:形式知へと導く SRE イネーブリングによる、プロダクトチームの信頼性オーナーシップ向上 (ビズリーチ 佐々木康徳さん) 引用元: speakerdeck.com このセッションでは、WAF 運用をプロダクトチームへイネーブリングするにあたり、SECI モデルに基づいて実践された事例が紹介されました。 具体的には、共同化、表出化、連結化、内面化の 4 つのプロセスを通じて、SRE が持つ暗黙知を段階的にプロダクトチームに移転し、チームのオーナーシップを醸成する具体的な方法が示されました。 この取り組みにより、WAF 運用をプロダクトチームへ移管できただけでなく、プロダクト開発チームのオーナーシップ向上やコミュニケーションの効率化といった効果も得られたと説明されていました。 所感 このセッションで、SECI モデルという概念を初めて知りました。 私自身、暗黙知から他者への暗黙知のプロセスを飛ばしていきなりドキュメント化(表出化)から始めたり、連結化まで進めずに終わってしまったりと、SECI モデルの観点から見ると不完全な取り組みでイネーブリングに失敗した経験があります。そのため、今回の発表は非常に納得感があり、共感を深く覚えました。 今後、CLINICS SRE でも SECI モデルを意識した開発チームへのイネーブリングを進めていきたいと考えています。 Four Keys から始める信頼性の改善 (DMM 尾崎耕太さん) 引用元: speakerdeck.com このセッションは、Four Keys (チーム生産性を可視化することを目的とした指標のこと) を軸に DevOps 文化を作り、その結果としてユーザにとって魅力的で信頼されるプロダクトを提供することを目指す戦略を取ることで、信頼性の改善を進めたという事例と導入効果について紹介されました。 信頼性指標である SLI/SLO やエラーバジェットは、機能開発チームにとって理解しにくく、日々の開発サイクルに組み込みにくいという課題がありました。そこで、より開発プロセスに近く、チームがオーナーシップを持ちやすい Four Keys を採用。これを軸に DevOps 文化を醸成することで、段階的に信頼性を獲得するというアプローチをとったと説明されています。 その結果、Four Keys の数値が改善しただけでなく、プロダクト開発チームが自律的に変化に取り組むようになったという大きな効果があったと紹介されていました。 所感 最も印象的だったのは、「指標の改善が目的ではなく、その先の状態が目標」という考え方です。指標を取るのは気づきを得て、アクションに繋げるためのものであるため、「指標を改善すること」にこだわりすぎない方が良いという話には深く共感できました。数値の向上に注力するあまり、なぜその指標を見ているのかという根本的な目的を見失ってしまうことは、実際の現場でもよく起こりがちな問題です。 CLINICS でも SLO 運用を行っていますが、この考え方は非常に参考になりました。SLO の数値を改善することに集中してしまい、そもそもなぜ SLO を設定しているのか、SLO 運用を通じて何を実現したいのかという本質的な目的を見失わないよう気をつけたいと思います。 SLO 運用をした先に SRE チームが何を目指しているのか、サービスの信頼性向上によってどのような価値をユーザーや事業に提供したいのかという意識を、開発チームにもさらに伝えられるようにしたいと感じました。 また、開発チームがオーナーシップを持って変化に取り組んでいることが重要という点も非常に納得できました。具体的な成果よりも、開発チーム自身が主体的に改善活動を推進していることの方が本質的な価値があるという考え方は、持続可能な改善文化を構築する上で欠かせない要素だと思います。 実践面で特に参考になったのは、単純に Four Keys を導入するのではなく、プロダクト開発チームと対話してアクションに繋げられるように Keys を詳細化している点です。開発者が「なぜ悪化したのか」「どの活動が効果的だったのか」を理解できるよう工夫している取り組みは、実際の現場での指標活用を考える上で非常に実用的なアプローチだと感じました。 さいごに 他社のさまざまな挑戦や SRE プラクティスを学べただけでなく、たくさんの方々と交流することができて刺激的な 2 日間でした。 来年も 7 月 10 日、11 日に TOC 有明 での開催を予定しているそうです。 メドレーは今後も SRE NEXT だけでなく、他の技術イベントやコミュニティの発展を積極的に支援し、参加、貢献していきます。 運営スタッフの皆さん、登壇者の皆さん、一緒に盛り上がった参加者の皆さん、本当にありがとうございました! メドレーではエンジニアを積極採用中です! エンジニアを積極採用中です メドレーでは、「医療ヘルスケアの未来」を共に創っていく SRE エンジニアを積極的に採用しています。 興味を持たれた方は、以下のリンクより、ぜひカジュアル面談の応募をお願いします。 募集の一覧 https://www.medley.jp/jobs/ ※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください
はじめに こんにちは! 医療プラットフォーム本部 プラットフォーム開発室 SRE グループの山田です。 医療機関向け SaaS である CLINICS の安定稼働とシステム信頼性の向上に取り組んでいます。 メドレーは 7 月 11 日、12 日に TOC 有明 (東京都江東区)で開催された SRE NEXT 2025 に LOGO Sponsor として協賛しました! SRE NEXT は、信頼性に関するプラクティスに深い関心を持つエンジニアのためのカンファレンスです。 医療プラットフォーム本部 SRE グループは発足して間もないため、他社のさまざまな挑戦や SRE プラクティスを学ぶべく、私を含め数名のエンジニアが参加し、たくさんの方々と交流させていただきました。 本レポートでは、SRE NEXT 2025 の会場や企業ブースの様子、そして発表の内容についてご紹介します。 会場の様子 SRE NEXT 2025 は、オンラインとオフラインのハイブリッド形式で開催されました。 50 を超える企業の協賛のもと、740 名もの SRE エンジニアが現地に参加しました。 広々とした会場 企業ブースも多数出展しており、SRE にまつわるアンケートや SRE プラクティスの紹介など非常に面白かったです。 株式会社タイミー様のブースにお邪魔させていただきました 2 日目の最後には懇親会も催され、様々な SRE エンジニアの方と交流することができました。 懇親会の様子 発表の様子 どのセッションも大変興味深かったのですが、特に印象深かった下記のセッションについてご紹介します。 Day1: SRE 不在の開発チームが障害対応と 向き合った 100 日間 Day2: 伴走から自律へ:形式知へと導く SRE イネーブリングによる、プロダクトチームの信頼性オーナーシップ向上 Day2: Four Keys から始める信頼性の改善 SRE 不在の開発チームが障害対応と 向き合った 100 日間 (Loglass 勝丸真さん) 引用元: speakerdeck.com カスタマーサクセスチームからのフィードバックをきっかけに、インシデント対応の改善に取り組んだ際の課題と、その解決策が紹介されました。 開発チームが障害対応に直面する中で、エンジニアによって対応品質にばらつきがあり、カスタマーサクセスチームから「障害対応がスムーズに進まない」「全体の体制や連絡手段が曖昧」といったフィードバックを受けるという課題があったとのことです。特に、あるエンジニアは単純な修正作業のみを行う一方で、別のエンジニアは影響範囲の特定やカスタマーサクセスへの回避策伝達まで含めた包括的な対応を実施するなど、対応者によって大きな差が生じていました。 そこで、専任のインシデントコマンダーチームを編成し、全エンジニアによるローテーション制から専門化による品質向上を図るアプローチへ切り替えたことが説明されました。また、障害対応フローの明確化とシンプル化、インシデントレベルの再定義、外部ツール「Warroom」の導入による自動記録・AI 要約機能の活用についても具体的な手法が紹介されました。 さらに、プロジェクトの推進においてはプロセス整備だけでなく、ビジネスチームやプロダクトチームとの継続的な対話を重視し、「なぜこの変更が必要なのか」という背景を丁寧に共有することで組織全体の理解を深め、技術的なベストプラクティスとビジネス要求のギャップを埋めていく文化づくりの重要性も解説されていました。 所感 このセッションで特に印象的だったのは、教科書的なベストプラクティスをそのまま適用するのではなく、現場の実情に合わせて大胆な割り切りを行っていた点です。中でも、「全エンジニアがインシデントコマンダーになる」という理想を一旦捨て、あえて属人化を許容して専門チームを作るという判断は、実践が進んでいるからこその現実的な選択だと感じました。 CLINICS SRE でも、理想的なインシデント対応体制を構築しようとする際に、全エンジニアのスキルレベルやモチベーションのばらつきという現実的な課題に直面することがあります。この発表から学んだのは、完璧な体制を目指すよりも、まずは実効性のある仕組みを作り上げることの重要性です。専門チームによる安定した対応基盤があってこそ、その後の全体的なスキル向上や体制の民主化が可能になるのだと理解しました。 また、プロセス整備だけでは解決できない人間関係や組織文化の課題に対し、継続的な対話を通じて理解を深めていく姿勢も非常に参考になりました。CLINICS でも、開発チームや事業部との間で、障害対応時の連携や認識に齟齬が生じることは少なくありません。この発表から学んだ対話を重ねることが重要であるという点は、今後の SRE の取り組みに活かしていきたいと思います。 現場の泥臭い課題に真摯に向き合い、組織として解決策を模索する姿勢は、同じような課題に取り組む SRE チームにとって大きな学びとなる発表でした。 伴走から自律へ:形式知へと導く SRE イネーブリングによる、プロダクトチームの信頼性オーナーシップ向上 (ビズリーチ 佐々木康徳さん) 引用元: speakerdeck.com このセッションでは、WAF 運用をプロダクトチームへイネーブリングするにあたり、SECI モデルに基づいて実践された事例が紹介されました。 具体的には、共同化、表出化、連結化、内面化の 4 つのプロセスを通じて、SRE が持つ暗黙知を段階的にプロダクトチームに移転し、チームのオーナーシップを醸成する具体的な方法が示されました。 この取り組みにより、WAF 運用をプロダクトチームへ移管できただけでなく、プロダクト開発チームのオーナーシップ向上やコミュニケーションの効率化といった効果も得られたと説明されていました。 所感 このセッションで、SECI モデルという概念を初めて知りました。 私自身、暗黙知から他者への暗黙知のプロセスを飛ばしていきなりドキュメント化(表出化)から始めたり、連結化まで進めずに終わってしまったりと、SECI モデルの観点から見ると不完全な取り組みでイネーブリングに失敗した経験があります。そのため、今回の発表は非常に納得感があり、共感を深く覚えました。 今後、CLINICS SRE でも SECI モデルを意識した開発チームへのイネーブリングを進めていきたいと考えています。 Four Keys から始める信頼性の改善 (DMM 尾崎耕太さん) 引用元: speakerdeck.com このセッションは、Four Keys (チーム生産性を可視化することを目的とした指標のこと) を軸に DevOps 文化を作り、その結果としてユーザにとって魅力的で信頼されるプロダクトを提供することを目指す戦略を取ることで、信頼性の改善を進めたという事例と導入効果について紹介されました。 信頼性指標である SLI/SLO やエラーバジェットは、機能開発チームにとって理解しにくく、日々の開発サイクルに組み込みにくいという課題がありました。そこで、より開発プロセスに近く、チームがオーナーシップを持ちやすい Four Keys を採用。これを軸に DevOps 文化を醸成することで、段階的に信頼性を獲得するというアプローチをとったと説明されています。 その結果、Four Keys の数値が改善しただけでなく、プロダクト開発チームが自律的に変化に取り組むようになったという大きな効果があったと紹介されていました。 所感 最も印象的だったのは、「指標の改善が目的ではなく、その先の状態が目標」という考え方です。指標を取るのは気づきを得て、アクションに繋げるためのものであるため、「指標を改善すること」にこだわりすぎない方が良いという話には深く共感できました。数値の向上に注力するあまり、なぜその指標を見ているのかという根本的な目的を見失ってしまうことは、実際の現場でもよく起こりがちな問題です。 CLINICS でも SLO 運用を行っていますが、この考え方は非常に参考になりました。SLO の数値を改善することに集中してしまい、そもそもなぜ SLO を設定しているのか、SLO 運用を通じて何を実現したいのかという本質的な目的を見失わないよう気をつけたいと思います。 SLO 運用をした先に SRE チームが何を目指しているのか、サービスの信頼性向上によってどのような価値をユーザーや事業に提供したいのかという意識を、開発チームにもさらに伝えられるようにしたいと感じました。 また、開発チームがオーナーシップを持って変化に取り組んでいることが重要という点も非常に納得できました。具体的な成果よりも、開発チーム自身が主体的に改善活動を推進していることの方が本質的な価値があるという考え方は、持続可能な改善文化を構築する上で欠かせない要素だと思います。 実践面で特に参考になったのは、単純に Four Keys を導入するのではなく、プロダクト開発チームと対話してアクションに繋げられるように Keys を詳細化している点です。開発者が「なぜ悪化したのか」「どの活動が効果的だったのか」を理解できるよう工夫している取り組みは、実際の現場での指標活用を考える上で非常に実用的なアプローチだと感じました。 さいごに 他社のさまざまな挑戦や SRE プラクティスを学べただけでなく、たくさんの方々と交流することができて刺激的な 2 日間でした。 来年も 7 月 10 日、11 日に TOC 有明 での開催を予定しているそうです。 メドレーは今後も SRE NEXT だけでなく、他の技術イベントやコミュニティの発展を積極的に支援し、参加、貢献していきます。 運営スタッフの皆さん、登壇者の皆さん、一緒に盛り上がった参加者の皆さん、本当にありがとうございました! メドレーではエンジニアを積極採用中です! エンジニアを積極採用中です メドレーでは、「医療ヘルスケアの未来」を共に創っていく SRE エンジニアを積極的に採用しています。 興味を持たれた方は、以下のリンクより、ぜひカジュアル面談の応募をお願いします。 募集の一覧 https://www.medley.jp/jobs/ ※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください
こんにちは。医療プラットフォーム本部の日下( @mkusaka )です。 私の所属する統合基盤チームでは、医療プラットフォームの複数のシステムを支えるサービス群を運用しています。 これまではユーザーへの影響を最小限に抑えるため、リリース作業を深夜や早朝に限定していましたが、その結果として運用チームへの負担増やリリースタイミングの制約といった課題が生じていました。 こうした課題を解決し、日中でも安全かつ段階的にリリースを行うため、統合基盤に Blue/Green デプロイメントを導入しました。今回はその詳細を紹介します。 統合基盤コンポーネントの紹介 統合基盤チームでは、患者と医療機関の双方に使われる医療システムの根幹を支える重要な基盤の開発・運用を行っています。 管理するコンポーネントは、「医療機関向け」と「患者向け」の 2 つに分類できます。 「医療機関向け」機能は、 Pharms 、 Dentis 、 CLINICS などの各サービス間でコミュニケーションのハブとして機能し、イベントの配信を行います。 一方、「患者向け」機能としては、 総合医療アプリ CLINICS から送られたリクエストを適切なサービスに振り分けるゲートウェイの役割を担っています。 このようなアーキテクチャを採用した背景には、医療プラットフォームが複数のプロダクト(Pharms、Dentis、CLINICS など)を統合的に運用する必要があるという背景があります。 医療機関向けには、いずれか一方のシステムの状態に他方が引きずられない構成とすることで、各医療機関の業務システムの可用性を最大限高めるという狙いがあります。 一方、患者向けには、複数の医療機関向けシステムに対して共通のアプリからアクセスできるようにするため、患者情報の一元管理や統一された認証基盤の整備が不可欠です。これにより、患者にとってシームレスな体験を実現し、より効率的で質の高い医療サービスの提供を目指しています。 つまり、統合基盤が管理するこれらのコンポーネントに障害が発生すると、サービス間にまたがる業務だけでなく、患者向けのサービス提供にも影響が及ぶため、高い可用性が求められています。 Blue/Green デプロイメント導入の目的 統合基盤の各サービスは、AWS ECS(Elastic Container Service)上で運用されています。これまでは、運用のシンプルさからローリングデプロイを採用していました。しかしこの手法では、デプロイが開始されると新バージョンが一斉に展開されてしまうため、問題発生時の影響範囲が広く、迅速なロールバックも難しいという課題がありました。 こうした課題への対策として、Blue/Green デプロイメントを導入することを決定しました。 Blue/Green デプロイメントとは、新旧 2 つの環境(Blue と Green)を用意し、新バージョンを片方に展開した後、徐々にトラフィックを移行していく方法です。これにより、致命的な問題やパフォーマンスの劣化を早期に発見し、迅速なロールバックが可能になります。 Blue/Green デプロイメントの要件と独自実装の選択 システムを Blue/Green デプロイする際に、大きく分けて 2 つの考慮事項がありました。 1 つ目は 2 つの明確な検証フェーズを設けることです。 致命的なエラー検出フェーズ :新環境に約 10%のトラフィックを流し、システムの安定性を確認。 負荷時のパフォーマンス検証フェーズ :新環境に約 50%のトラフィックを流し、負荷によるパフォーマンスの劣化を確認。 これら 2 つのフェーズを明確に設け、十分な検証時間を取ることで、より高い自信をもってリリース作業を進めることが可能になると考えました。 2 つ目は非同期処理を担当する Worker サービスを新旧の環境それぞれで用意することです。 Worker サービスでは、ジョブが SQS(Simple Queue Service)キューに投入されてから実際に処理されるまでタイムラグがあります。その間に新しい環境へ切り替えが行われると、旧環境のジョブが新環境の Worker によって処理され、データの不整合が起きる可能性があります。このリスクを防ぐため、Blue と Green の環境それぞれに独立した SQS キューを用意し、それぞれのジョブが確実に自環境の Worker で処理を完了する形式を取ることとしました。 ECS には CodeDeploy を利用した標準的な Blue/Green の段階的デプロイ戦略として、Canary デプロイ(一部のトラフィックで検証後、一気に切り替え)や Linear デプロイ(一定割合ずつ徐々に適用)が提供されています。しかし、私たちが求めるようなトラフィック調整にはこれらの手法が十分に適しておらず、また SQS を分離するための Blue 環境/Green 環境どちらかを判定するような仕組みが整備されていないようだったので、内製化することとしました。 採用した構成 自前の Blue/Green デプロイを実現するため、以下の構成を採用しました。 ALB(Application Load Balancer)の weighted target groups を利用して、トラフィックを柔軟に調整しています。 ECS 環境は Blue/Green のそれぞれが並行稼働し、SQS は各環境ごとに独立したキューを設けます。 新バージョンは Green 環境として起動し、初期状態では Blue 環境(現行バージョン)がすべてのトラフィックを処理します。その後、Green 環境へのトラフィックを 10%、50%と段階的に増やしながら、各段階でメトリクスの監視を行います。 10%の段階で致命的なエラーがないことを確認し、50%の段階で負荷時のパフォーマンスに問題ないと判断されたら、最終的にすべてのトラフィックを Green 環境へ切り替えます(100%)。 また、各種ステップは承認操作やロールバックも含めて CI 上で完結するように整備を行うことで、デプロイの複雑性も抑えています。 実装サンプル Terraform と AWS CLI を組み合わせて実装しています。 ALB の weighted routing 設定(Terraform) resource "aws_lb_listener_rule" "weighted_routing" { listener_arn = aws_lb_listener. https . arn action { type = "forward" forward { target_group { arn = aws_lb_target_group. app_blue . arn weight = 100 # 初期状態では Blue が 100% } target_group { arn = aws_lb_target_group. app_green . arn weight = 0 # Green は 0% } } } # AWS CLI で weight を動的に調整するため、Terraform での変更を無視 lifecycle { ignore_changes = [ action ] } } トラフィック配分の変更(AWS CLI) デプロイ時のトラフィック配分は、AWS CLI を使用して動的に変更します。実際の運用では、Green の weight を指定すると Blue が自動的に 100 - Green になるように調整しています: # Green の weight を指定して Blue/Green の配分を設定 # 例:GREEN_WEIGHT=10 の場合、Blue=90、Green=10 に自動計算 set-weight: @aws elbv2 modify-rule \ --rule-arn $( LISTENER_RULE_ARN ) \ --actions '[{ "Type": "forward", "ForwardConfig": { "TargetGroups": [ { "TargetGroupArn": "$(BLUE_TG_ARN)", "Weight": ' $$ (( 100 - $( GREEN_WEIGHT ))) ' }, { "TargetGroupArn": "$(GREEN_TG_ARN)", "Weight": ' $( GREEN_WEIGHT ) ' } ] } }]' デプロイフロー 実際のデプロイでは、以下のような手順で段階的にトラフィックを移行します: # 1. Green 環境へのデプロイ make app-green.deploy TAG= $( NEW_VERSION ) # 2. トラフィックを段階的に移行 make set-weight GREEN_WEIGHT= 10 # 10% を Green へ make set-weight GREEN_WEIGHT= 50 # 50% を Green へ make set-weight GREEN_WEIGHT= 100 # 100% を Green へ(切り替え完了) # 3. 問題が発生した場合のロールバック make set-weight GREEN_WEIGHT= 0 # すべてのトラフィックを Blue へ戻す この実装により、段階的なリリースと問題発生時の迅速なロールバックが可能になっています。 導入後の効果 Blue/Green デプロイメント導入後、深夜のリリース作業がなくなり、運用チームの負担が大幅に軽減されました。また日中であってもリスクを最小限に抑えたリリースが実現でき、問題が発生した場合にも迅速にロールバックできるようになったため、システムの安定性を犠牲にすることなくリリースを行えるようになりました。 まとめ 今回は、医療システム統合基盤における Blue/Green デプロイメントの導入事例を紹介しました。 医療システムには高い可用性が求められる中、従来のローリングデプロイでは段階的なリリースや迅速なロールバックが困難で、深夜作業が必須となっていました。 Blue/Green デプロイメントの導入により、以下を実現しました: ALB の weighted target groups を活用した段階的トラフィック移行(10%→50%→100%)により品質を確認しながらのリリース進行 Worker サービス用の SQS キューを環境ごとに分離し、データ不整合を防止 深夜作業を廃止し、日中でも安全にリリース作業を実施可能に 今後も医療システムに求められる高可用性を維持しながら、開発効率の向上を目指し、継続的な改善を進めていきます。 We’re hiring 統合基盤チームでは、医療システムの根幹を支える重要な基盤の開発・運用を行っています。 今回紹介したような Blue/Green デプロイメントの実装をはじめ、高可用性が求められるシステムにおいて、技術的な課題に向き合いながら医療現場と患者体験の向上に貢献できる環境があります。 医療の未来をエンジニアリングで支えることに興味がある方のご応募をお待ちしています。 患者統合基盤 Webエンジニア / 株式会社メドレー 株式会社メドレーは患者統合基盤 Webエンジニアを採用しています。 open.talentio.com 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
こんにちは。医療プラットフォーム本部の日下( @mkusaka )です。 私の所属する統合基盤チームでは、医療プラットフォームの複数のシステムを支えるサービス群を運用しています。 これまではユーザーへの影響を最小限に抑えるため、リリース作業を深夜や早朝に限定していましたが、その結果として運用チームへの負担増やリリースタイミングの制約といった課題が生じていました。 こうした課題を解決し、日中でも安全かつ段階的にリリースを行うため、統合基盤に Blue/Green デプロイメントを導入しました。今回はその詳細を紹介します。 統合基盤コンポーネントの紹介 統合基盤チームでは、患者と医療機関の双方に使われる医療システムの根幹を支える重要な基盤の開発・運用を行っています。 管理するコンポーネントは、「医療機関向け」と「患者向け」の 2 つに分類できます。 「医療機関向け」機能は、 Pharms 、 Dentis 、 CLINICS などの各サービス間でコミュニケーションのハブとして機能し、イベントの配信を行います。 一方、「患者向け」機能としては、 総合医療アプリ CLINICS から送られたリクエストを適切なサービスに振り分けるゲートウェイの役割を担っています。 このようなアーキテクチャを採用した背景には、医療プラットフォームが複数のプロダクト(Pharms、Dentis、CLINICS など)を統合的に運用する必要があるという背景があります。 医療機関向けには、いずれか一方のシステムの状態に他方が引きずられない構成とすることで、各医療機関の業務システムの可用性を最大限高めるという狙いがあります。 一方、患者向けには、複数の医療機関向けシステムに対して共通のアプリからアクセスできるようにするため、患者情報の一元管理や統一された認証基盤の整備が不可欠です。これにより、患者にとってシームレスな体験を実現し、より効率的で質の高い医療サービスの提供を目指しています。 つまり、統合基盤が管理するこれらのコンポーネントに障害が発生すると、サービス間にまたがる業務だけでなく、患者向けのサービス提供にも影響が及ぶため、高い可用性が求められています。 Blue/Green デプロイメント導入の目的 統合基盤の各サービスは、AWS ECS(Elastic Container Service)上で運用されています。これまでは、運用のシンプルさからローリングデプロイを採用していました。しかしこの手法では、デプロイが開始されると新バージョンが一斉に展開されてしまうため、問題発生時の影響範囲が広く、迅速なロールバックも難しいという課題がありました。 こうした課題への対策として、Blue/Green デプロイメントを導入することを決定しました。 Blue/Green デプロイメントとは、新旧 2 つの環境(Blue と Green)を用意し、新バージョンを片方に展開した後、徐々にトラフィックを移行していく方法です。これにより、致命的な問題やパフォーマンスの劣化を早期に発見し、迅速なロールバックが可能になります。 Blue/Green デプロイメントの要件と独自実装の選択 システムを Blue/Green デプロイする際に、大きく分けて 2 つの考慮事項がありました。 1 つ目は 2 つの明確な検証フェーズを設けることです。 致命的なエラー検出フェーズ :新環境に約 10%のトラフィックを流し、システムの安定性を確認。 負荷時のパフォーマンス検証フェーズ :新環境に約 50%のトラフィックを流し、負荷によるパフォーマンスの劣化を確認。 これら 2 つのフェーズを明確に設け、十分な検証時間を取ることで、より高い自信をもってリリース作業を進めることが可能になると考えました。 2 つ目は非同期処理を担当する Worker サービスを新旧の環境それぞれで用意することです。 Worker サービスでは、ジョブが SQS(Simple Queue Service)キューに投入されてから実際に処理されるまでタイムラグがあります。その間に新しい環境へ切り替えが行われると、旧環境のジョブが新環境の Worker によって処理され、データの不整合が起きる可能性があります。このリスクを防ぐため、Blue と Green の環境それぞれに独立した SQS キューを用意し、それぞれのジョブが確実に自環境の Worker で処理を完了する形式を取ることとしました。 ECS には CodeDeploy を利用した標準的な Blue/Green の段階的デプロイ戦略として、Canary デプロイ(一部のトラフィックで検証後、一気に切り替え)や Linear デプロイ(一定割合ずつ徐々に適用)が提供されています。しかし、私たちが求めるようなトラフィック調整にはこれらの手法が十分に適しておらず、また SQS を分離するための Blue 環境/Green 環境どちらかを判定するような仕組みが整備されていないようだったので、内製化することとしました。 採用した構成 自前の Blue/Green デプロイを実現するため、以下の構成を採用しました。 ALB(Application Load Balancer)の weighted target groups を利用して、トラフィックを柔軟に調整しています。 ECS 環境は Blue/Green のそれぞれが並行稼働し、SQS は各環境ごとに独立したキューを設けます。 新バージョンは Green 環境として起動し、初期状態では Blue 環境(現行バージョン)がすべてのトラフィックを処理します。その後、Green 環境へのトラフィックを 10%、50%と段階的に増やしながら、各段階でメトリクスの監視を行います。 10%の段階で致命的なエラーがないことを確認し、50%の段階で負荷時のパフォーマンスに問題ないと判断されたら、最終的にすべてのトラフィックを Green 環境へ切り替えます(100%)。 また、各種ステップは承認操作やロールバックも含めて CI 上で完結するように整備を行うことで、デプロイの複雑性も抑えています。 実装サンプル Terraform と AWS CLI を組み合わせて実装しています。 ALB の weighted routing 設定(Terraform) resource "aws_lb_listener_rule" "weighted_routing" { listener_arn = aws_lb_listener. https . arn action { type = "forward" forward { target_group { arn = aws_lb_target_group. app_blue . arn weight = 100 # 初期状態では Blue が 100% } target_group { arn = aws_lb_target_group. app_green . arn weight = 0 # Green は 0% } } } # AWS CLI で weight を動的に調整するため、Terraform での変更を無視 lifecycle { ignore_changes = [ action ] } } トラフィック配分の変更(AWS CLI) デプロイ時のトラフィック配分は、AWS CLI を使用して動的に変更します。実際の運用では、Green の weight を指定すると Blue が自動的に 100 - Green になるように調整しています: # Green の weight を指定して Blue/Green の配分を設定 # 例:GREEN_WEIGHT=10 の場合、Blue=90、Green=10 に自動計算 set-weight: @aws elbv2 modify-rule \ --rule-arn $( LISTENER_RULE_ARN ) \ --actions '[{ "Type": "forward", "ForwardConfig": { "TargetGroups": [ { "TargetGroupArn": "$(BLUE_TG_ARN)", "Weight": ' $$ (( 100 - $( GREEN_WEIGHT ))) ' }, { "TargetGroupArn": "$(GREEN_TG_ARN)", "Weight": ' $( GREEN_WEIGHT ) ' } ] } }]' デプロイフロー 実際のデプロイでは、以下のような手順で段階的にトラフィックを移行します: # 1. Green 環境へのデプロイ make app-green.deploy TAG= $( NEW_VERSION ) # 2. トラフィックを段階的に移行 make set-weight GREEN_WEIGHT= 10 # 10% を Green へ make set-weight GREEN_WEIGHT= 50 # 50% を Green へ make set-weight GREEN_WEIGHT= 100 # 100% を Green へ(切り替え完了) # 3. 問題が発生した場合のロールバック make set-weight GREEN_WEIGHT= 0 # すべてのトラフィックを Blue へ戻す この実装により、段階的なリリースと問題発生時の迅速なロールバックが可能になっています。 導入後の効果 Blue/Green デプロイメント導入後、深夜のリリース作業がなくなり、運用チームの負担が大幅に軽減されました。また日中であってもリスクを最小限に抑えたリリースが実現でき、問題が発生した場合にも迅速にロールバックできるようになったため、システムの安定性を犠牲にすることなくリリースを行えるようになりました。 まとめ 今回は、医療システム統合基盤における Blue/Green デプロイメントの導入事例を紹介しました。 医療システムには高い可用性が求められる中、従来のローリングデプロイでは段階的なリリースや迅速なロールバックが困難で、深夜作業が必須となっていました。 Blue/Green デプロイメントの導入により、以下を実現しました: ALB の weighted target groups を活用した段階的トラフィック移行(10%→50%→100%)により品質を確認しながらのリリース進行 Worker サービス用の SQS キューを環境ごとに分離し、データ不整合を防止 深夜作業を廃止し、日中でも安全にリリース作業を実施可能に 今後も医療システムに求められる高可用性を維持しながら、開発効率の向上を目指し、継続的な改善を進めていきます。 We’re hiring 統合基盤チームでは、医療システムの根幹を支える重要な基盤の開発・運用を行っています。 今回紹介したような Blue/Green デプロイメントの実装をはじめ、高可用性が求められるシステムにおいて、技術的な課題に向き合いながら医療現場と患者体験の向上に貢献できる環境があります。 医療の未来をエンジニアリングで支えることに興味がある方のご応募をお待ちしています。 患者統合基盤 Webエンジニア / 株式会社メドレー 株式会社メドレーは患者統合基盤 Webエンジニアを採用しています。 open.talentio.com 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに こんにちは! 人材プラットフォーム本部プロダクト統括部プロダクト開発部アカデミー開発グループ所属の城間(シロマ)です。 私は 2024 年 4 月に新卒エンジニアとして入社し、現在はオンライン動画研修サービス「 ジョブメドレーアカデミー 」の新規プロダクト開発に携わっています。 先日、5 月 23 日、24 日に東京都千代田区のベルサール神田にて開催された TSKaigi 2025 にメドレーは Silver Sponsor として協賛しました! TypeScript をテーマにしたこの大規模なカンファレンスには、多様なバックグラウンドを持つ TypeScript エンジニアが全国から集結。 惜しくもブースの抽選には外れてしまいましたが、私も含め弊社からは数名のエンジニアが現地参加し、多くの素晴らしい出会いと学びがありました。 今回は、当日の会場の様子や印象に残ったセッション、そして今、私が感じていることを中心にご紹介します! 会場の熱気と「型にとらわれない」エンジニアたちの交流 今年の TSKaigi 2025 は 2 日間とも天候に恵まれ、 61 もの企業スポンサーと「TypeScript を扱う型にとらわれないエンジニア」が集結し、会場は終日、熱気と活気に満ち溢れていました。 場内の様子 スポンサーボード 2 日間に渡り、合計 3 会場でセッションや LT が盛んに行われ、時には立ち見の人が出るほどに盛況していました。 参加者同士でテーマに対してトークする OST(Open Space Technology) や、夜の豪華な懇親会では、セッションでは聞けないような深掘りした技術の話や、各社の開発文化に関する交流が盛んに行われ、当日はおよそ 50 名以上のエンジニアの方々と色々なお話をさせていただきました。 OST のテーマ 懇親会の様子 TypeScript ケーキ また、今回参加者に配布された公式グッズは、TSKaigi のオリジナル T シャツやトートバッグに加え、各企業が工夫を凝らした遊び心あふれるグッズで盛りだくさんでした。 特に、今回ブース出展のあった 19 社全てを周るスタンプラリー では、全てのスタンプを集めることで豪華景品が当たる抽選に参加でき、会場の賑わいに一役買っていました。 公式ノベルティ(一部) スタンプラリー 印象に残ったセッション:AI エージェント全盛の時代と TypeScript の可能性 どのセッションも非常に興味深く、時には理解が追いつかないほど最先端の内容でしたが、特に今の私が行う開発と照らし合わせて印象に残ったセッションをいくつかご紹介します。 Day1: AI Coding Agent Enablement in TypeScript 引用元: AI Coding Agents Enablement in TypeScript - Speaker Deck 概要 このセッションでは、人間による介入を最小限に抑え、AI エージェントが大規模な作業をどのように「自走」して実行させるかについて深く掘り下げられました。 なぜ自走が難しいのか?その主な要因は AI エージェントが「任意の TypeScript」のようなあまりにも広い「解空間」で動くため、精度が低くなってしまうためです。 すなわち AI エージェントの精度を高めるためには 「可能な限り解空間を絞る」 ことが基本方針であり、会社やプロジェクト固有の規約、ドメイン知識、デザインなどに基づいて適切に制約を与える重要性が何度も強調されていました。 その解空間を絞る具体的なアプローチとして 「コンテキスト注入」 と 「機械的検査とフィードバック」 の 2 つが挙げられました。 コンテキスト注入 : ドキュメント、規約、ドメイン知識などを LLM にインプットし、「解空間の定義」を与えること。特に TypeScript コード自体をドキュメントとして育てることが、コンテキスト注入に役立つ。 機械的検査とフィードバック : LLM が生成したコードが定義された解空間から外れていないか、型チェック、Linter、自動テストといった古典的な手法で検査し、NG の場合はエージェントにフィードバックして解空間へと押し戻すように促す。Linter は次にやるべきアクションを提示しやすく、決定的である点が非常に有効である。 また、TypeScript 開発における型の役割についても興味深い見解がありました。現状、型によってコード生成の精度が上がるという定量的な根拠はまだないものの、「解空間に押し戻す」ためのエージェントへの入力(フィードバック)としては役立つ可能性は大いにあるとのこと。AI はまだ高度な型解決が苦手なので、人間がドメインモデルや関数のシグネチャ(型定義)をしっかり書き、その後の実装をエージェントに任せるアプローチが有効であるという話は、非常に現実的で納得感がありました。 最後に、 「エコシステムの未来 - Speed is King」 という言葉が印象的でした。これは、AI エージェントのコード生成が加速すると、開発プロセス全体の速度がボトルネックとなることを示唆しています。具体的には、人間がコードを 30 分かけて書く時代には 1 分程度の静的解析は許容範囲でしたが、AI が同じコードをわずか 30 秒で書くようになると同じ 1 分間の静的解析は AI の作業時間に対して相対的に非常に長く、無視できないボトルネックになるということです。さらにクラウド型エージェントではチャットごとにコンテナが作成される仕組みが主流であり、その際にパッケージマネージャの速度がボトルネックになります。また、AI エージェントによるコード生産量が爆発的に増加すれば、デプロイの機会も増えるためビルド(バンドラー)の速度もボトルネックとして顕在化し、加えて将来的に LLM がコードを生成するタイミング(デコーディング時)ごとに型制約を守ろうとする「制約付きデコーディング」のようなアプローチが進めば、型チェッカーがボトルネックになる可能性も指摘されていました。故に、ツールチェイン全体の高速化が今後より重要になるという示唆は、将来的な開発環境を考える上で非常に重要な視点だと感じました。 Day2: TS 特化 Cline プログラミング 引用元: tskaigi.mizchi.workers.dev 概要 このセッションでは、LLM を活用した「Cline Agentic Coding」の全般について、特に TypeScript に特化した実践的なプロンプトのコツが紹介されました。 まず冒頭に うまくいくうまくいくプロンプトのコツ として、以下が挙げられていました。 書きすぎない(再現性ある範囲で詠唱破棄) 執拗に出力例を例示する 両立条件の矛盾を避ける 規模感に合わせて厳しくする また特に強調されていたのは、 テスト駆動開発(Test Driven Development) の重要性です。コード生成時に対応するユニットテストを常に生成し、コード修正時にはテストがパスすることを確認することで、AI エージェントの自己修復能力を高め、放置しても完成する高品質なコードに繋がるとのことでした。 その他にも、次のような実践的なプロンプトの一例が紹介されていました。 コメントによる自己記述 : 各ファイルの冒頭にコメントで仕様を記述することで、再修正時のコード解釈の一貫性を保つ In Source Testing : 実装と同じファイルにユニットテストを書くことで、コメント・実装・テストを三位一体で管理する types.ts にドメイン型を集約 : 中規模以上のプロジェクトでは src/types.ts にドメインモデルを集約し、SSoT(Single Source of Truth)とすることで、ファイル間の整合性を保ち read_file の頻度を減らす TS + 関数型ドメインモデリング : 状態の発散を抑えるために class を使わず関数による実装を優先し、代数的データ型でドメインをモデリングする ファイル配置規則の明記 : モノレポなどのファイル配置規則を明記することで、タスクごとのエージェントの推測コストを減らす 詳細指示を docs/*.md に分割 : 大規模プロジェクトでは無関係な指示によるノイズを減らすために、詳細な指示をドキュメントファイルに分割し、必要に応じて参照させる カバレッジに基づくテストの自動生成 : vitest でカバレッジを計測し、最もカバレッジが上がるテストコードを AI に考察・追加させる 機械的なマイグレーション : lodash の削除や類似 API を持つライブラリへの置き換えなど、面倒なマイグレーション作業を AI に自動化させる URL を読む能力 : URL の内容を読み込ませ、要約・保存させることで、Deep Research 的な挙動を可能にする 一方で、 うまくいかないプロンプト のパターンもいくつか示唆されました。 型だけで設計しようとする : ほぼ確実に無視され、抽象的な設計能力は期待できない 非同期例外処理が下手 : 思考停止気味に try-catch で握り潰しがちで、大規模開発で破綻の原因となる 環境構築が下手 : ゼロショットでの環境構築は発散しやすく、手数が仇となり環境を破壊する可能性がある モジュールインターフェースが発散 : 実装次第で全て export してしまい、モジュール間の契約が肥大化・破綻する 「ある」のがよくない(チェーホフの銃の法則) : 無関係なリソースを読み込ませると、それを使うことに固執し、大規模コードではノイズになる デバッグログを食いすぎる : 自身が生成したプリントデバッグでコンテキストウィンドウを消費し、デバッグコードを放置しがちである 結論として、現状の LLM はコーディングが下手であり、低品質なコードで設計が破綻し自滅する傾向があるとの見解でした。特に、リファクタリングの指針がなく、不要コードの判定やモジュール視点での API 設計が苦手で、ユーザー側でリファクタリングしても元の低品質なコードに書き戻すこともあるという少し厳しい評価が下されていました。 しかし TypeScript と LLM の組み合わせには良い点も多く、GitHub の公開コードが豊富で学習量が多いこと、安全性よりも表現力を選んだピーキーな型システムが自然言語と対応した型のモデリングをしやすいこと、豊富な静的解析手段とユーザー層の厚さがあることなどが挙げられていました。 現時点でのベストプラクティスとしては、PoC/プロトタイプのコード生成(1 ファイル完結 800 行以内が目安)に活用し、人間によるインターフェース設計、失敗パターンのプロンプトへの反映、成功するパターンのドキュメント化、そしてこちらでも Lint ルールの整備についての言及がありました。 そして最後にテスト駆動開発や LLM の得意領域・発達段階を予測する技術、プロンプトエンジニアリングの重要性の増加など Agentic Coding によるプログラミング自体の変質と不変である点について説明され締めくくられました。 感想 これらのセッションを通して、私が携わる新規プロダクト開発において、 実践できている部分 と 改善の余地がある部分 が明確になりました。 実践できている点として、全社的に積極的に利用している AI エディタ「Cursor」のプロジェクトルールや自立型 AI エージェント「Devin」の knowledge には、プロジェクトごとに解空間を明確にするコンテキストを与えています。これは、「AI Coding Agent Enablement in TypeScript」で述べられていた「コンテキスト注入」と「解空間を絞る」という考え方に合致していると感じました。また、コードと仕様を記述したドキュメントを同じリポジトリ内で管理し、開発を進めている点も、AI へのコンテキスト注入に貢献していると言えるでしょう。 開発フローにおいても、AI の特性を踏まえた工夫を凝らしています。「AI Coding Agent Enablement in TypeScript」で言及があった「3 回くらいループした末に any とかキャストで誤魔化しがち」という点に対し、例えば API 開発では、まず仕様やテストケースのドキュメントをコードベースと同じファイル内に記述し、次に Request や Response Body を zod を用いて厳密に型を記述します。これらをエンジニアがレビューした後、テストデータのセットアップや記法などをエンジニアが整え、残りを AI で実装を進めます。さらにアプリケーションコードの実装においても、用意したユニットテストが通過するという制約の元 AI に実装させることにより、型解決などに AI エージェントが費やす時間が最小化され、 実装フェーズの開発工数を大幅に短縮することに成功 しています。 mizchi さんのセッションで言及のあったテスト駆動開発(Test Driven Development)のスキルは今後もより重要になっていくでしょう。しかし、もう少し先の未来では、弊社メドレーが大切にしている価値観(Our Essential)の一つである「 ドキュメントドリブン 」に倣い、 ドキュメントドリブン開発(Document Driven Development) のスキルがより重要になっていくのではないか、と私は考えています。 具体的なイメージ さらに「Speed is King」の思想を体現するために、Linter と Formatter には Biome、パッケージマネージャーには Bun を採用するなどツールチェインの速度にはかなりシビアな意思決定をしており、より AI エージェントが生産的に活動できるような体制を整えています。 一方で、 改善の余地がある点 も明確になりました。特に双方のセッションで強調されていた「機械的検査とフィードバック」の継続的な改善、すなわち Linter については開発初期から現在に至るまであまり積極的に更新できていないと痛感しています。AI に与えるフィードバックをその場での単発のもので終わらせるのではなく、(人間と同じように)再発防止策を考えさせるように Linter を定期的にアップデートし、より決定的で高速に AI エージェントが機能するような環境をより一層整備していきます。 さいごに TypeScript の可能性を肌で感じ、熱意ある仲間たちと出会い、技術への情熱を改めて確認できた素晴らしいイベントでした。 メドレーは今後も TSKaigi だけでなく、他の技術イベントやコミュニティの発展を積極的に支援し、参加、貢献していきます。 過去にスポンサーとして協賛した技術カンファレンスの参加レポート記事はこちら! RubyKaigi 2025 参加レポート - Platinum Sponsor として協賛しました! | MEDLEY Developer Portal はじめに こんにちは! 人材プラットフォーム本部プロダクト開発室 第一開発グループ所属の山下です。 メドレーには今年2月に入社したエンジニアで、日本最大級の医療介護求人サイト ジョブメドレー の開発を担当しています。 メドレーは 4 月 1... developer.medley.jp pmconf 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。医療プラットフォーム本部で Product Manager をしている佐藤です。2024 年 5 月にメドレーにジョインし、医療機関向けプロダクト開発に奔走しています。社内では ”papa”、家では”おじさん”と呼ばれ可愛がら... developer.medley.jp JSConf JP 2024に プレミアムスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは!人材プラットフォーム本部で技術広報兼エンジニア採用をしている重田(@Shige0096)です。2024 年 11 月にメドレーにジョインし、初の社外イベントに参加してきました。 今回、メドレーは 2024/11/23 に九段坂上... developer.medley.jp DroidKaigi 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。人材プラットフォーム本部でエンジニアをしている山河です。2023 年 4 月に新卒として入社し、徐々に業務の幅を広げています! さて、メドレーは 2024/9/11 〜 9/13 の 3 日間にベルサール渋谷ガーデンにて開催され... developer.medley.jp TSKaigi 2025 の運営スタッフの皆さん、登壇者の皆さん、一緒に盛り上がった参加者の皆さん、本当にありがとうございました! メドレーではエンジニアを積極採用中です! メドレーでは領域を問わず、TypeScript を積極的に活用して医療ヘルスケアの未来をつくるプロダクトを開発しています。 TypeScript を活用した医療ヘルスケア領域の課題解決に興味がある方は、ぜひお気軽にご連絡ください! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp ※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください
はじめに こんにちは! 人材プラットフォーム本部プロダクト統括部プロダクト開発部アカデミー開発グループ所属の城間(シロマ)です。 私は 2024 年 4 月に新卒エンジニアとして入社し、現在はオンライン動画研修サービス「 ジョブメドレーアカデミー 」の新規プロダクト開発に携わっています。 先日、5 月 23 日、24 日に東京都千代田区のベルサール神田にて開催された TSKaigi 2025 にメドレーは Silver Sponsor として協賛しました! TypeScript をテーマにしたこの大規模なカンファレンスには、多様なバックグラウンドを持つ TypeScript エンジニアが全国から集結。 惜しくもブースの抽選には外れてしまいましたが、私も含め弊社からは数名のエンジニアが現地参加し、多くの素晴らしい出会いと学びがありました。 今回は、当日の会場の様子や印象に残ったセッション、そして今、私が感じていることを中心にご紹介します! 会場の熱気と「型にとらわれない」エンジニアたちの交流 今年の TSKaigi 2025 は 2 日間とも天候に恵まれ、 61 もの企業スポンサーと「TypeScript を扱う型にとらわれないエンジニア」が集結し、会場は終日、熱気と活気に満ち溢れていました。 場内の様子 スポンサーボード 2 日間に渡り、合計 3 会場でセッションや LT が盛んに行われ、時には立ち見の人が出るほどに盛況していました。 参加者同士でテーマに対してトークする OST(Open Space Technology) や、夜の豪華な懇親会では、セッションでは聞けないような深掘りした技術の話や、各社の開発文化に関する交流が盛んに行われ、当日はおよそ 50 名以上のエンジニアの方々と色々なお話をさせていただきました。 OST のテーマ 懇親会の様子 TypeScript ケーキ また、今回参加者に配布された公式グッズは、TSKaigi のオリジナル T シャツやトートバッグに加え、各企業が工夫を凝らした遊び心あふれるグッズで盛りだくさんでした。 特に、今回ブース出展のあった 19 社全てを周るスタンプラリー では、全てのスタンプを集めることで豪華景品が当たる抽選に参加でき、会場の賑わいに一役買っていました。 公式ノベルティ(一部) スタンプラリー 印象に残ったセッション:AI エージェント全盛の時代と TypeScript の可能性 どのセッションも非常に興味深く、時には理解が追いつかないほど最先端の内容でしたが、特に今の私が行う開発と照らし合わせて印象に残ったセッションをいくつかご紹介します。 Day1: AI Coding Agent Enablement in TypeScript 引用元: AI Coding Agents Enablement in TypeScript - Speaker Deck 概要 このセッションでは、人間による介入を最小限に抑え、AI エージェントが大規模な作業をどのように「自走」して実行させるかについて深く掘り下げられました。 なぜ自走が難しいのか?その主な要因は AI エージェントが「任意の TypeScript」のようなあまりにも広い「解空間」で動くため、精度が低くなってしまうためです。 すなわち AI エージェントの精度を高めるためには 「可能な限り解空間を絞る」 ことが基本方針であり、会社やプロジェクト固有の規約、ドメイン知識、デザインなどに基づいて適切に制約を与える重要性が何度も強調されていました。 その解空間を絞る具体的なアプローチとして 「コンテキスト注入」 と 「機械的検査とフィードバック」 の 2 つが挙げられました。 コンテキスト注入 : ドキュメント、規約、ドメイン知識などを LLM にインプットし、「解空間の定義」を与えること。特に TypeScript コード自体をドキュメントとして育てることが、コンテキスト注入に役立つ。 機械的検査とフィードバック : LLM が生成したコードが定義された解空間から外れていないか、型チェック、Linter、自動テストといった古典的な手法で検査し、NG の場合はエージェントにフィードバックして解空間へと押し戻すように促す。Linter は次にやるべきアクションを提示しやすく、決定的である点が非常に有効である。 また、TypeScript 開発における型の役割についても興味深い見解がありました。現状、型によってコード生成の精度が上がるという定量的な根拠はまだないものの、「解空間に押し戻す」ためのエージェントへの入力(フィードバック)としては役立つ可能性は大いにあるとのこと。AI はまだ高度な型解決が苦手なので、人間がドメインモデルや関数のシグネチャ(型定義)をしっかり書き、その後の実装をエージェントに任せるアプローチが有効であるという話は、非常に現実的で納得感がありました。 最後に、 「エコシステムの未来 - Speed is King」 という言葉が印象的でした。これは、AI エージェントのコード生成が加速すると、開発プロセス全体の速度がボトルネックとなることを示唆しています。具体的には、人間がコードを 30 分かけて書く時代には 1 分程度の静的解析は許容範囲でしたが、AI が同じコードをわずか 30 秒で書くようになると同じ 1 分間の静的解析は AI の作業時間に対して相対的に非常に長く、無視できないボトルネックになるということです。さらにクラウド型エージェントではチャットごとにコンテナが作成される仕組みが主流であり、その際にパッケージマネージャの速度がボトルネックになります。また、AI エージェントによるコード生産量が爆発的に増加すれば、デプロイの機会も増えるためビルド(バンドラー)の速度もボトルネックとして顕在化し、加えて将来的に LLM がコードを生成するタイミング(デコーディング時)ごとに型制約を守ろうとする「制約付きデコーディング」のようなアプローチが進めば、型チェッカーがボトルネックになる可能性も指摘されていました。故に、ツールチェイン全体の高速化が今後より重要になるという示唆は、将来的な開発環境を考える上で非常に重要な視点だと感じました。 Day2: TS 特化 Cline プログラミング 引用元: tskaigi.mizchi.workers.dev 概要 このセッションでは、LLM を活用した「Cline Agentic Coding」の全般について、特に TypeScript に特化した実践的なプロンプトのコツが紹介されました。 まず冒頭に うまくいくうまくいくプロンプトのコツ として、以下が挙げられていました。 書きすぎない(再現性ある範囲で詠唱破棄) 執拗に出力例を例示する 両立条件の矛盾を避ける 規模感に合わせて厳しくする また特に強調されていたのは、 テスト駆動開発(Test Driven Development) の重要性です。コード生成時に対応するユニットテストを常に生成し、コード修正時にはテストがパスすることを確認することで、AI エージェントの自己修復能力を高め、放置しても完成する高品質なコードに繋がるとのことでした。 その他にも、次のような実践的なプロンプトの一例が紹介されていました。 コメントによる自己記述 : 各ファイルの冒頭にコメントで仕様を記述することで、再修正時のコード解釈の一貫性を保つ In Source Testing : 実装と同じファイルにユニットテストを書くことで、コメント・実装・テストを三位一体で管理する types.ts にドメイン型を集約 : 中規模以上のプロジェクトでは src/types.ts にドメインモデルを集約し、SSoT(Single Source of Truth)とすることで、ファイル間の整合性を保ち read_file の頻度を減らす TS + 関数型ドメインモデリング : 状態の発散を抑えるために class を使わず関数による実装を優先し、代数的データ型でドメインをモデリングする ファイル配置規則の明記 : モノレポなどのファイル配置規則を明記することで、タスクごとのエージェントの推測コストを減らす 詳細指示を docs/*.md に分割 : 大規模プロジェクトでは無関係な指示によるノイズを減らすために、詳細な指示をドキュメントファイルに分割し、必要に応じて参照させる カバレッジに基づくテストの自動生成 : vitest でカバレッジを計測し、最もカバレッジが上がるテストコードを AI に考察・追加させる 機械的なマイグレーション : lodash の削除や類似 API を持つライブラリへの置き換えなど、面倒なマイグレーション作業を AI に自動化させる URL を読む能力 : URL の内容を読み込ませ、要約・保存させることで、Deep Research 的な挙動を可能にする 一方で、 うまくいかないプロンプト のパターンもいくつか示唆されました。 型だけで設計しようとする : ほぼ確実に無視され、抽象的な設計能力は期待できない 非同期例外処理が下手 : 思考停止気味に try-catch で握り潰しがちで、大規模開発で破綻の原因となる 環境構築が下手 : ゼロショットでの環境構築は発散しやすく、手数が仇となり環境を破壊する可能性がある モジュールインターフェースが発散 : 実装次第で全て export してしまい、モジュール間の契約が肥大化・破綻する 「ある」のがよくない(チェーホフの銃の法則) : 無関係なリソースを読み込ませると、それを使うことに固執し、大規模コードではノイズになる デバッグログを食いすぎる : 自身が生成したプリントデバッグでコンテキストウィンドウを消費し、デバッグコードを放置しがちである 結論として、現状の LLM はコーディングが下手であり、低品質なコードで設計が破綻し自滅する傾向があるとの見解でした。特に、リファクタリングの指針がなく、不要コードの判定やモジュール視点での API 設計が苦手で、ユーザー側でリファクタリングしても元の低品質なコードに書き戻すこともあるという少し厳しい評価が下されていました。 しかし TypeScript と LLM の組み合わせには良い点も多く、GitHub の公開コードが豊富で学習量が多いこと、安全性よりも表現力を選んだピーキーな型システムが自然言語と対応した型のモデリングをしやすいこと、豊富な静的解析手段とユーザー層の厚さがあることなどが挙げられていました。 現時点でのベストプラクティスとしては、PoC/プロトタイプのコード生成(1 ファイル完結 800 行以内が目安)に活用し、人間によるインターフェース設計、失敗パターンのプロンプトへの反映、成功するパターンのドキュメント化、そしてこちらでも Lint ルールの整備についての言及がありました。 そして最後にテスト駆動開発や LLM の得意領域・発達段階を予測する技術、プロンプトエンジニアリングの重要性の増加など Agentic Coding によるプログラミング自体の変質と不変である点について説明され締めくくられました。 感想 これらのセッションを通して、私が携わる新規プロダクト開発において、 実践できている部分 と 改善の余地がある部分 が明確になりました。 実践できている点として、全社的に積極的に利用している AI エディタ「Cursor」のプロジェクトルールや自立型 AI エージェント「Devin」の knowledge には、プロジェクトごとに解空間を明確にするコンテキストを与えています。これは、「AI Coding Agent Enablement in TypeScript」で述べられていた「コンテキスト注入」と「解空間を絞る」という考え方に合致していると感じました。また、コードと仕様を記述したドキュメントを同じリポジトリ内で管理し、開発を進めている点も、AI へのコンテキスト注入に貢献していると言えるでしょう。 開発フローにおいても、AI の特性を踏まえた工夫を凝らしています。「AI Coding Agent Enablement in TypeScript」で言及があった「3 回くらいループした末に any とかキャストで誤魔化しがち」という点に対し、例えば API 開発では、まず仕様やテストケースのドキュメントをコードベースと同じファイル内に記述し、次に Request や Response Body を zod を用いて厳密に型を記述します。これらをエンジニアがレビューした後、テストデータのセットアップや記法などをエンジニアが整え、残りを AI で実装を進めます。さらにアプリケーションコードの実装においても、用意したユニットテストが通過するという制約の元 AI に実装させることにより、型解決などに AI エージェントが費やす時間が最小化され、 実装フェーズの開発工数を大幅に短縮することに成功 しています。 mizchi さんのセッションで言及のあったテスト駆動開発(Test Driven Development)のスキルは今後もより重要になっていくでしょう。しかし、もう少し先の未来では、弊社メドレーが大切にしている価値観(Our Essential)の一つである「 ドキュメントドリブン 」に倣い、 ドキュメントドリブン開発(Document Driven Development) のスキルがより重要になっていくのではないか、と私は考えています。 具体的なイメージ さらに「Speed is King」の思想を体現するために、Linter と Formatter には Biome、パッケージマネージャーには Bun を採用するなどツールチェインの速度にはかなりシビアな意思決定をしており、より AI エージェントが生産的に活動できるような体制を整えています。 一方で、 改善の余地がある点 も明確になりました。特に双方のセッションで強調されていた「機械的検査とフィードバック」の継続的な改善、すなわち Linter については開発初期から現在に至るまであまり積極的に更新できていないと痛感しています。AI に与えるフィードバックをその場での単発のもので終わらせるのではなく、(人間と同じように)再発防止策を考えさせるように Linter を定期的にアップデートし、より決定的で高速に AI エージェントが機能するような環境をより一層整備していきます。 さいごに TypeScript の可能性を肌で感じ、熱意ある仲間たちと出会い、技術への情熱を改めて確認できた素晴らしいイベントでした。 メドレーは今後も TSKaigi だけでなく、他の技術イベントやコミュニティの発展を積極的に支援し、参加、貢献していきます。 過去にスポンサーとして協賛した技術カンファレンスの参加レポート記事はこちら! RubyKaigi 2025 参加レポート - Platinum Sponsor として協賛しました! | MEDLEY Developer Portal はじめに こんにちは! 人材プラットフォーム本部プロダクト開発室 第一開発グループ所属の山下です。 メドレーには今年2月に入社したエンジニアで、日本最大級の医療介護求人サイト ジョブメドレー の開発を担当しています。 メドレーは 4 月 1... developer.medley.jp pmconf 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。医療プラットフォーム本部で Product Manager をしている佐藤です。2024 年 5 月にメドレーにジョインし、医療機関向けプロダクト開発に奔走しています。社内では ”papa”、家では”おじさん”と呼ばれ可愛がら... developer.medley.jp JSConf JP 2024に プレミアムスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは!人材プラットフォーム本部で技術広報兼エンジニア採用をしている重田(@Shige0096)です。2024 年 11 月にメドレーにジョインし、初の社外イベントに参加してきました。 今回、メドレーは 2024/11/23 に九段坂上... developer.medley.jp DroidKaigi 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。人材プラットフォーム本部でエンジニアをしている山河です。2023 年 4 月に新卒として入社し、徐々に業務の幅を広げています! さて、メドレーは 2024/9/11 〜 9/13 の 3 日間にベルサール渋谷ガーデンにて開催され... developer.medley.jp TSKaigi 2025 の運営スタッフの皆さん、登壇者の皆さん、一緒に盛り上がった参加者の皆さん、本当にありがとうございました! メドレーではエンジニアを積極採用中です! メドレーでは領域を問わず、TypeScript を積極的に活用して医療ヘルスケアの未来をつくるプロダクトを開発しています。 TypeScript を活用した医療ヘルスケア領域の課題解決に興味がある方は、ぜひお気軽にご連絡ください! メドレーで働く|株式会社メドレー メドレーでの働き方や人事制度、求人情報など、採用に関する情報をご紹介します。 www.medley.jp ※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください
はじめに こんにちは! 人材プラットフォーム本部プロダクト統括部プロダクト開発部アカデミー開発グループ所属の城間(シロマ)です。 私は 2024 年 4 月に新卒エンジニアとして入社し、現在はオンライン動画研修サービス「 ジョブメドレーアカデミー 」の新規プロダクト開発に携わっています。 先日、5 月 23 日、24 日に東京都千代田区のベルサール神田にて開催された TSKaigi 2025 にメドレーは Silver Sponsor として協賛しました! TypeScript をテーマにしたこの大規模なカンファレンスには、多様なバックグラウンドを持つ TypeScript エンジニアが全国から集結。 惜しくもブースの抽選には外れてしまいましたが、私も含め弊社からは数名のエンジニアが現地参加し、多くの素晴らしい出会いと学びがありました。 今回は、当日の会場の様子や印象に残ったセッション、そして今、私が感じていることを中心にご紹介します! 会場の熱気と「型にとらわれない」エンジニアたちの交流 今年の TSKaigi 2025 は 2 日間とも天候に恵まれ、 61 もの企業スポンサーと「TypeScript を扱う型にとらわれないエンジニア」が集結し、会場は終日、熱気と活気に満ち溢れていました。 場内の様子 スポンサーボード 2 日間に渡り、合計 3 会場でセッションや LT が盛んに行われ、時には立ち見の人が出るほどに盛況していました。 参加者同士でテーマに対してトークする OST(Open Space Technology) や、夜の豪華な懇親会では、セッションでは聞けないような深掘りした技術の話や、各社の開発文化に関する交流が盛んに行われ、当日はおよそ 50 名以上のエンジニアの方々と色々なお話をさせていただきました。 OST のテーマ 懇親会の様子 TypeScript ケーキ また、今回参加者に配布された公式グッズは、TSKaigi のオリジナル T シャツやトートバッグに加え、各企業が工夫を凝らした遊び心あふれるグッズで盛りだくさんでした。 特に、今回ブース出展のあった 19 社全てを周るスタンプラリー では、全てのスタンプを集めることで豪華景品が当たる抽選に参加でき、会場の賑わいに一役買っていました。 公式ノベルティ(一部) スタンプラリー 印象に残ったセッション:AI エージェント全盛の時代と TypeScript の可能性 どのセッションも非常に興味深く、時には理解が追いつかないほど最先端の内容でしたが、特に今の私が行う開発と照らし合わせて印象に残ったセッションをいくつかご紹介します。 Day1: AI Coding Agent Enablement in TypeScript 引用元: AI Coding Agents Enablement in TypeScript - Speaker Deck 概要 このセッションでは、人間による介入を最小限に抑え、AI エージェントが大規模な作業をどのように「自走」して実行させるかについて深く掘り下げられました。 なぜ自走が難しいのか?その主な要因は AI エージェントが「任意の TypeScript」のようなあまりにも広い「解空間」で動くため、精度が低くなってしまうためです。 すなわち AI エージェントの精度を高めるためには 「可能な限り解空間を絞る」 ことが基本方針であり、会社やプロジェクト固有の規約、ドメイン知識、デザインなどに基づいて適切に制約を与える重要性が何度も強調されていました。 その解空間を絞る具体的なアプローチとして 「コンテキスト注入」 と 「機械的検査とフィードバック」 の 2 つが挙げられました。 コンテキスト注入 : ドキュメント、規約、ドメイン知識などを LLM にインプットし、「解空間の定義」を与えること。特に TypeScript コード自体をドキュメントとして育てることが、コンテキスト注入に役立つ。 機械的検査とフィードバック : LLM が生成したコードが定義された解空間から外れていないか、型チェック、Linter、自動テストといった古典的な手法で検査し、NG の場合はエージェントにフィードバックして解空間へと押し戻すように促す。Linter は次にやるべきアクションを提示しやすく、決定的である点が非常に有効である。 また、TypeScript 開発における型の役割についても興味深い見解がありました。現状、型によってコード生成の精度が上がるという定量的な根拠はまだないものの、「解空間に押し戻す」ためのエージェントへの入力(フィードバック)としては役立つ可能性は大いにあるとのこと。AI はまだ高度な型解決が苦手なので、人間がドメインモデルや関数のシグネチャ(型定義)をしっかり書き、その後の実装をエージェントに任せるアプローチが有効であるという話は、非常に現実的で納得感がありました。 最後に、 「エコシステムの未来 - Speed is King」 という言葉が印象的でした。これは、AI エージェントのコード生成が加速すると、開発プロセス全体の速度がボトルネックとなることを示唆しています。具体的には、人間がコードを 30 分かけて書く時代には 1 分程度の静的解析は許容範囲でしたが、AI が同じコードをわずか 30 秒で書くようになると同じ 1 分間の静的解析は AI の作業時間に対して相対的に非常に長く、無視できないボトルネックになるということです。さらにクラウド型エージェントではチャットごとにコンテナが作成される仕組みが主流であり、その際にパッケージマネージャの速度がボトルネックになります。また、AI エージェントによるコード生産量が爆発的に増加すれば、デプロイの機会も増えるためビルド(バンドラー)の速度もボトルネックとして顕在化し、加えて将来的に LLM がコードを生成するタイミング(デコーディング時)ごとに型制約を守ろうとする「制約付きデコーディング」のようなアプローチが進めば、型チェッカーがボトルネックになる可能性も指摘されていました。故に、ツールチェイン全体の高速化が今後より重要になるという示唆は、将来的な開発環境を考える上で非常に重要な視点だと感じました。 Day2: TS 特化 Cline プログラミング 引用元: tskaigi.mizchi.workers.dev 概要 このセッションでは、LLM を活用した「Cline Agentic Coding」の全般について、特に TypeScript に特化した実践的なプロンプトのコツが紹介されました。 まず冒頭に うまくいくうまくいくプロンプトのコツ として、以下が挙げられていました。 書きすぎない(再現性ある範囲で詠唱破棄) 執拗に出力例を例示する 両立条件の矛盾を避ける 規模感に合わせて厳しくする また特に強調されていたのは、 テスト駆動開発(Test Driven Development) の重要性です。コード生成時に対応するユニットテストを常に生成し、コード修正時にはテストがパスすることを確認することで、AI エージェントの自己修復能力を高め、放置しても完成する高品質なコードに繋がるとのことでした。 その他にも、次のような実践的なプロンプトの一例が紹介されていました。 コメントによる自己記述 : 各ファイルの冒頭にコメントで仕様を記述することで、再修正時のコード解釈の一貫性を保つ In Source Testing : 実装と同じファイルにユニットテストを書くことで、コメント・実装・テストを三位一体で管理する types.ts にドメイン型を集約 : 中規模以上のプロジェクトでは src/types.ts にドメインモデルを集約し、SSoT(Single Source of Truth)とすることで、ファイル間の整合性を保ち read_file の頻度を減らす TS + 関数型ドメインモデリング : 状態の発散を抑えるために class を使わず関数による実装を優先し、代数的データ型でドメインをモデリングする ファイル配置規則の明記 : モノレポなどのファイル配置規則を明記することで、タスクごとのエージェントの推測コストを減らす 詳細指示を docs/*.md に分割 : 大規模プロジェクトでは無関係な指示によるノイズを減らすために、詳細な指示をドキュメントファイルに分割し、必要に応じて参照させる カバレッジに基づくテストの自動生成 : vitest でカバレッジを計測し、最もカバレッジが上がるテストコードを AI に考察・追加させる 機械的なマイグレーション : lodash の削除や類似 API を持つライブラリへの置き換えなど、面倒なマイグレーション作業を AI に自動化させる URL を読む能力 : URL の内容を読み込ませ、要約・保存させることで、Deep Research 的な挙動を可能にする 一方で、 うまくいかないプロンプト のパターンもいくつか示唆されました。 型だけで設計しようとする : ほぼ確実に無視され、抽象的な設計能力は期待できない 非同期例外処理が下手 : 思考停止気味に try-catch で握り潰しがちで、大規模開発で破綻の原因となる 環境構築が下手 : ゼロショットでの環境構築は発散しやすく、手数が仇となり環境を破壊する可能性がある モジュールインターフェースが発散 : 実装次第で全て export してしまい、モジュール間の契約が肥大化・破綻する 「ある」のがよくない(チェーホフの銃の法則) : 無関係なリソースを読み込ませると、それを使うことに固執し、大規模コードではノイズになる デバッグログを食いすぎる : 自身が生成したプリントデバッグでコンテキストウィンドウを消費し、デバッグコードを放置しがちである 結論として、現状の LLM はコーディングが下手であり、低品質なコードで設計が破綻し自滅する傾向があるとの見解でした。特に、リファクタリングの指針がなく、不要コードの判定やモジュール視点での API 設計が苦手で、ユーザー側でリファクタリングしても元の低品質なコードに書き戻すこともあるという少し厳しい評価が下されていました。 しかし TypeScript と LLM の組み合わせには良い点も多く、GitHub の公開コードが豊富で学習量が多いこと、安全性よりも表現力を選んだピーキーな型システムが自然言語と対応した型のモデリングをしやすいこと、豊富な静的解析手段とユーザー層の厚さがあることなどが挙げられていました。 現時点でのベストプラクティスとしては、PoC/プロトタイプのコード生成(1 ファイル完結 800 行以内が目安)に活用し、人間によるインターフェース設計、失敗パターンのプロンプトへの反映、成功するパターンのドキュメント化、そしてこちらでも Lint ルールの整備についての言及がありました。 そして最後にテスト駆動開発や LLM の得意領域・発達段階を予測する技術、プロンプトエンジニアリングの重要性の増加など Agentic Coding によるプログラミング自体の変質と不変である点について説明され締めくくられました。 感想 これらのセッションを通して、私が携わる新規プロダクト開発において、 実践できている部分 と 改善の余地がある部分 が明確になりました。 実践できている点として、全社的に積極的に利用している AI エディタ「Cursor」のプロジェクトルールや自立型 AI エージェント「Devin」の knowledge には、プロジェクトごとに解空間を明確にするコンテキストを与えています。これは、「AI Coding Agent Enablement in TypeScript」で述べられていた「コンテキスト注入」と「解空間を絞る」という考え方に合致していると感じました。また、コードと仕様を記述したドキュメントを同じリポジトリ内で管理し、開発を進めている点も、AI へのコンテキスト注入に貢献していると言えるでしょう。 開発フローにおいても、AI の特性を踏まえた工夫を凝らしています。「AI Coding Agent Enablement in TypeScript」で言及があった「3 回くらいループした末に any とかキャストで誤魔化しがち」という点に対し、例えば API 開発では、まず仕様やテストケースのドキュメントをコードベースと同じファイル内に記述し、次に Request や Response Body を zod を用いて厳密に型を記述します。これらをエンジニアがレビューした後、テストデータのセットアップや記法などをエンジニアが整え、残りを AI で実装を進めます。さらにアプリケーションコードの実装においても、用意したユニットテストが通過するという制約の元 AI に実装させることにより、型解決などに AI エージェントが費やす時間が最小化され、 実装フェーズの開発工数を大幅に短縮することに成功 しています。 mizchi さんのセッションで言及のあったテスト駆動開発(Test Driven Development)のスキルは今後もより重要になっていくでしょう。しかし、もう少し先の未来では、弊社メドレーが大切にしている価値観(Our Essential)の一つである「 ドキュメントドリブン 」に倣い、 ドキュメントドリブン開発(Document Driven Development) のスキルがより重要になっていくのではないか、と私は考えています。 具体的なイメージ さらに「Speed is King」の思想を体現するために、Linter と Formatter には Biome、パッケージマネージャーには Bun を採用するなどツールチェインの速度にはかなりシビアな意思決定をしており、より AI エージェントが生産的に活動できるような体制を整えています。 一方で、 改善の余地がある点 も明確になりました。特に双方のセッションで強調されていた「機械的検査とフィードバック」の継続的な改善、すなわち Linter については開発初期から現在に至るまであまり積極的に更新できていないと痛感しています。AI に与えるフィードバックをその場での単発のもので終わらせるのではなく、(人間と同じように)再発防止策を考えさせるように Linter を定期的にアップデートし、より決定的で高速に AI エージェントが機能するような環境をより一層整備していきます。 さいごに TypeScript の可能性を肌で感じ、熱意ある仲間たちと出会い、技術への情熱を改めて確認できた素晴らしいイベントでした。 メドレーは今後も TSKaigi だけでなく、他の技術イベントやコミュニティの発展を積極的に支援し、参加、貢献していきます。 過去にスポンサーとして協賛した技術カンファレンスの参加レポート記事はこちら! RubyKaigi 2025 参加レポート - Platinum Sponsor として協賛しました! | MEDLEY Developer Portal はじめに こんにちは! 人材プラットフォーム本部プロダクト開発室 第一開発グループ所属の山下です。 メドレーには今年2月に入社したエンジニアで、日本最大級の医療介護求人サイト ジョブメドレー の開発を担当しています。 メドレーは 4 月 1... developer.medley.jp pmconf 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。医療プラットフォーム本部で Product Manager をしている佐藤です。2024 年 5 月にメドレーにジョインし、医療機関向けプロダクト開発に奔走しています。社内では ”papa”、家では”おじさん”と呼ばれ可愛がら... developer.medley.jp JSConf JP 2024に プレミアムスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは!人材プラットフォーム本部で技術広報兼エンジニア採用をしている重田(@Shige0096)です。2024 年 11 月にメドレーにジョインし、初の社外イベントに参加してきました。 今回、メドレーは 2024/11/23 に九段坂上... developer.medley.jp DroidKaigi 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。人材プラットフォーム本部でエンジニアをしている山河です。2023 年 4 月に新卒として入社し、徐々に業務の幅を広げています! さて、メドレーは 2024/9/11 〜 9/13 の 3 日間にベルサール渋谷ガーデンにて開催され... developer.medley.jp TSKaigi 2025 の運営スタッフの皆さん、登壇者の皆さん、一緒に盛り上がった参加者の皆さん、本当にありがとうございました! メドレーではエンジニアを積極採用中です! メドレーでは領域を問わず、TypeScript を積極的に活用して医療ヘルスケアの未来をつくるプロダクトを開発しています。 TypeScript を活用した医療ヘルスケア領域の課題解決に興味がある方は、ぜひお気軽にご連絡ください! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp ※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください
はじめに こんにちは! 人材プラットフォーム本部プロダクト統括部プロダクト開発部アカデミー開発グループ所属の城間(シロマ)です。 私は 2024 年 4 月に新卒エンジニアとして入社し、現在はオンライン動画研修サービス「 ジョブメドレーアカデミー 」の新規プロダクト開発に携わっています。 先日、5 月 23 日、24 日に東京都千代田区のベルサール神田にて開催された TSKaigi 2025 にメドレーは Silver Sponsor として協賛しました! TypeScript をテーマにしたこの大規模なカンファレンスには、多様なバックグラウンドを持つ TypeScript エンジニアが全国から集結。 惜しくもブースの抽選には外れてしまいましたが、私も含め弊社からは数名のエンジニアが現地参加し、多くの素晴らしい出会いと学びがありました。 今回は、当日の会場の様子や印象に残ったセッション、そして今、私が感じていることを中心にご紹介します! 会場の熱気と「型にとらわれない」エンジニアたちの交流 今年の TSKaigi 2025 は 2 日間とも天候に恵まれ、 61 もの企業スポンサーと「TypeScript を扱う型にとらわれないエンジニア」が集結し、会場は終日、熱気と活気に満ち溢れていました。 場内の様子 スポンサーボード 2 日間に渡り、合計 3 会場でセッションや LT が盛んに行われ、時には立ち見の人が出るほどに盛況していました。 参加者同士でテーマに対してトークする OST(Open Space Technology) や、夜の豪華な懇親会では、セッションでは聞けないような深掘りした技術の話や、各社の開発文化に関する交流が盛んに行われ、当日はおよそ 50 名以上のエンジニアの方々と色々なお話をさせていただきました。 OST のテーマ 懇親会の様子 TypeScript ケーキ また、今回参加者に配布された公式グッズは、TSKaigi のオリジナル T シャツやトートバッグに加え、各企業が工夫を凝らした遊び心あふれるグッズで盛りだくさんでした。 特に、今回ブース出展のあった 19 社全てを周るスタンプラリー では、全てのスタンプを集めることで豪華景品が当たる抽選に参加でき、会場の賑わいに一役買っていました。 公式ノベルティ(一部) スタンプラリー 印象に残ったセッション:AI エージェント全盛の時代と TypeScript の可能性 どのセッションも非常に興味深く、時には理解が追いつかないほど最先端の内容でしたが、特に今の私が行う開発と照らし合わせて印象に残ったセッションをいくつかご紹介します。 Day1: AI Coding Agent Enablement in TypeScript 引用元: AI Coding Agents Enablement in TypeScript - Speaker Deck 概要 このセッションでは、人間による介入を最小限に抑え、AI エージェントが大規模な作業をどのように「自走」して実行させるかについて深く掘り下げられました。 なぜ自走が難しいのか?その主な要因は AI エージェントが「任意の TypeScript」のようなあまりにも広い「解空間」で動くため、精度が低くなってしまうためです。 すなわち AI エージェントの精度を高めるためには 「可能な限り解空間を絞る」 ことが基本方針であり、会社やプロジェクト固有の規約、ドメイン知識、デザインなどに基づいて適切に制約を与える重要性が何度も強調されていました。 その解空間を絞る具体的なアプローチとして 「コンテキスト注入」 と 「機械的検査とフィードバック」 の 2 つが挙げられました。 コンテキスト注入 : ドキュメント、規約、ドメイン知識などを LLM にインプットし、「解空間の定義」を与えること。特に TypeScript コード自体をドキュメントとして育てることが、コンテキスト注入に役立つ。 機械的検査とフィードバック : LLM が生成したコードが定義された解空間から外れていないか、型チェック、Linter、自動テストといった古典的な手法で検査し、NG の場合はエージェントにフィードバックして解空間へと押し戻すように促す。Linter は次にやるべきアクションを提示しやすく、決定的である点が非常に有効である。 また、TypeScript 開発における型の役割についても興味深い見解がありました。現状、型によってコード生成の精度が上がるという定量的な根拠はまだないものの、「解空間に押し戻す」ためのエージェントへの入力(フィードバック)としては役立つ可能性は大いにあるとのこと。AI はまだ高度な型解決が苦手なので、人間がドメインモデルや関数のシグネチャ(型定義)をしっかり書き、その後の実装をエージェントに任せるアプローチが有効であるという話は、非常に現実的で納得感がありました。 最後に、 「エコシステムの未来 - Speed is King」 という言葉が印象的でした。これは、AI エージェントのコード生成が加速すると、開発プロセス全体の速度がボトルネックとなることを示唆しています。具体的には、人間がコードを 30 分かけて書く時代には 1 分程度の静的解析は許容範囲でしたが、AI が同じコードをわずか 30 秒で書くようになると同じ 1 分間の静的解析は AI の作業時間に対して相対的に非常に長く、無視できないボトルネックになるということです。さらにクラウド型エージェントではチャットごとにコンテナが作成される仕組みが主流であり、その際にパッケージマネージャの速度がボトルネックになります。また、AI エージェントによるコード生産量が爆発的に増加すれば、デプロイの機会も増えるためビルド(バンドラー)の速度もボトルネックとして顕在化し、加えて将来的に LLM がコードを生成するタイミング(デコーディング時)ごとに型制約を守ろうとする「制約付きデコーディング」のようなアプローチが進めば、型チェッカーがボトルネックになる可能性も指摘されていました。故に、ツールチェイン全体の高速化が今後より重要になるという示唆は、将来的な開発環境を考える上で非常に重要な視点だと感じました。 Day2: TS 特化 Cline プログラミング 引用元: tskaigi.mizchi.workers.dev 概要 このセッションでは、LLM を活用した「Cline Agentic Coding」の全般について、特に TypeScript に特化した実践的なプロンプトのコツが紹介されました。 まず冒頭に うまくいくうまくいくプロンプトのコツ として、以下が挙げられていました。 書きすぎない(再現性ある範囲で詠唱破棄) 執拗に出力例を例示する 両立条件の矛盾を避ける 規模感に合わせて厳しくする また特に強調されていたのは、 テスト駆動開発(Test Driven Development) の重要性です。コード生成時に対応するユニットテストを常に生成し、コード修正時にはテストがパスすることを確認することで、AI エージェントの自己修復能力を高め、放置しても完成する高品質なコードに繋がるとのことでした。 その他にも、次のような実践的なプロンプトの一例が紹介されていました。 コメントによる自己記述 : 各ファイルの冒頭にコメントで仕様を記述することで、再修正時のコード解釈の一貫性を保つ In Source Testing : 実装と同じファイルにユニットテストを書くことで、コメント・実装・テストを三位一体で管理する types.ts にドメイン型を集約 : 中規模以上のプロジェクトでは src/types.ts にドメインモデルを集約し、SSoT(Single Source of Truth)とすることで、ファイル間の整合性を保ち read_file の頻度を減らす TS + 関数型ドメインモデリング : 状態の発散を抑えるために class を使わず関数による実装を優先し、代数的データ型でドメインをモデリングする ファイル配置規則の明記 : モノレポなどのファイル配置規則を明記することで、タスクごとのエージェントの推測コストを減らす 詳細指示を docs/*.md に分割 : 大規模プロジェクトでは無関係な指示によるノイズを減らすために、詳細な指示をドキュメントファイルに分割し、必要に応じて参照させる カバレッジに基づくテストの自動生成 : vitest でカバレッジを計測し、最もカバレッジが上がるテストコードを AI に考察・追加させる 機械的なマイグレーション : lodash の削除や類似 API を持つライブラリへの置き換えなど、面倒なマイグレーション作業を AI に自動化させる URL を読む能力 : URL の内容を読み込ませ、要約・保存させることで、Deep Research 的な挙動を可能にする 一方で、 うまくいかないプロンプト のパターンもいくつか示唆されました。 型だけで設計しようとする : ほぼ確実に無視され、抽象的な設計能力は期待できない 非同期例外処理が下手 : 思考停止気味に try-catch で握り潰しがちで、大規模開発で破綻の原因となる 環境構築が下手 : ゼロショットでの環境構築は発散しやすく、手数が仇となり環境を破壊する可能性がある モジュールインターフェースが発散 : 実装次第で全て export してしまい、モジュール間の契約が肥大化・破綻する 「ある」のがよくない(チェーホフの銃の法則) : 無関係なリソースを読み込ませると、それを使うことに固執し、大規模コードではノイズになる デバッグログを食いすぎる : 自身が生成したプリントデバッグでコンテキストウィンドウを消費し、デバッグコードを放置しがちである 結論として、現状の LLM はコーディングが下手であり、低品質なコードで設計が破綻し自滅する傾向があるとの見解でした。特に、リファクタリングの指針がなく、不要コードの判定やモジュール視点での API 設計が苦手で、ユーザー側でリファクタリングしても元の低品質なコードに書き戻すこともあるという少し厳しい評価が下されていました。 しかし TypeScript と LLM の組み合わせには良い点も多く、GitHub の公開コードが豊富で学習量が多いこと、安全性よりも表現力を選んだピーキーな型システムが自然言語と対応した型のモデリングをしやすいこと、豊富な静的解析手段とユーザー層の厚さがあることなどが挙げられていました。 現時点でのベストプラクティスとしては、PoC/プロトタイプのコード生成(1 ファイル完結 800 行以内が目安)に活用し、人間によるインターフェース設計、失敗パターンのプロンプトへの反映、成功するパターンのドキュメント化、そしてこちらでも Lint ルールの整備についての言及がありました。 そして最後にテスト駆動開発や LLM の得意領域・発達段階を予測する技術、プロンプトエンジニアリングの重要性の増加など Agentic Coding によるプログラミング自体の変質と不変である点について説明され締めくくられました。 感想 これらのセッションを通して、私が携わる新規プロダクト開発において、 実践できている部分 と 改善の余地がある部分 が明確になりました。 実践できている点として、全社的に積極的に利用している AI エディタ「Cursor」のプロジェクトルールや自立型 AI エージェント「Devin」の knowledge には、プロジェクトごとに解空間を明確にするコンテキストを与えています。これは、「AI Coding Agent Enablement in TypeScript」で述べられていた「コンテキスト注入」と「解空間を絞る」という考え方に合致していると感じました。また、コードと仕様を記述したドキュメントを同じリポジトリ内で管理し、開発を進めている点も、AI へのコンテキスト注入に貢献していると言えるでしょう。 開発フローにおいても、AI の特性を踏まえた工夫を凝らしています。「AI Coding Agent Enablement in TypeScript」で言及があった「3 回くらいループした末に any とかキャストで誤魔化しがち」という点に対し、例えば API 開発では、まず仕様やテストケースのドキュメントをコードベースと同じファイル内に記述し、次に Request や Response Body を zod を用いて厳密に型を記述します。これらをエンジニアがレビューした後、テストデータのセットアップや記法などをエンジニアが整え、残りを AI で実装を進めます。さらにアプリケーションコードの実装においても、用意したユニットテストが通過するという制約の元 AI に実装させることにより、型解決などに AI エージェントが費やす時間が最小化され、 実装フェーズの開発工数を大幅に短縮することに成功 しています。 mizchi さんのセッションで言及のあったテスト駆動開発(Test Driven Development)のスキルは今後もより重要になっていくでしょう。しかし、もう少し先の未来では、弊社メドレーが大切にしている価値観(Our Essential)の一つである「 ドキュメントドリブン 」に倣い、 ドキュメントドリブン開発(Document Driven Development) のスキルがより重要になっていくのではないか、と私は考えています。 具体的なイメージ さらに「Speed is King」の思想を体現するために、Linter と Formatter には Biome、パッケージマネージャーには Bun を採用するなどツールチェインの速度にはかなりシビアな意思決定をしており、より AI エージェントが生産的に活動できるような体制を整えています。 一方で、 改善の余地がある点 も明確になりました。特に双方のセッションで強調されていた「機械的検査とフィードバック」の継続的な改善、すなわち Linter については開発初期から現在に至るまであまり積極的に更新できていないと痛感しています。AI に与えるフィードバックをその場での単発のもので終わらせるのではなく、(人間と同じように)再発防止策を考えさせるように Linter を定期的にアップデートし、より決定的で高速に AI エージェントが機能するような環境をより一層整備していきます。 さいごに TypeScript の可能性を肌で感じ、熱意ある仲間たちと出会い、技術への情熱を改めて確認できた素晴らしいイベントでした。 メドレーは今後も TSKaigi だけでなく、他の技術イベントやコミュニティの発展を積極的に支援し、参加、貢献していきます。 過去にスポンサーとして協賛した技術カンファレンスの参加レポート記事はこちら! RubyKaigi 2025 参加レポート - Platinum Sponsor として協賛しました! | MEDLEY Developer Portal はじめに こんにちは! 人材プラットフォーム本部プロダクト開発室 第一開発グループ所属の山下です。 メドレーには今年2月に入社したエンジニアで、日本最大級の医療介護求人サイト ジョブメドレー の開発を担当しています。 メドレーは 4 月 1... developer.medley.jp pmconf 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。医療プラットフォーム本部で Product Manager をしている佐藤です。2024 年 5 月にメドレーにジョインし、医療機関向けプロダクト開発に奔走しています。社内では ”papa”、家では”おじさん”と呼ばれ可愛がら... developer.medley.jp JSConf JP 2024に プレミアムスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは!人材プラットフォーム本部で技術広報兼エンジニア採用をしている重田(@Shige0096)です。2024 年 11 月にメドレーにジョインし、初の社外イベントに参加してきました。 今回、メドレーは 2024/11/23 に九段坂上... developer.medley.jp DroidKaigi 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。人材プラットフォーム本部でエンジニアをしている山河です。2023 年 4 月に新卒として入社し、徐々に業務の幅を広げています! さて、メドレーは 2024/9/11 〜 9/13 の 3 日間にベルサール渋谷ガーデンにて開催され... developer.medley.jp TSKaigi 2025 の運営スタッフの皆さん、登壇者の皆さん、一緒に盛り上がった参加者の皆さん、本当にありがとうございました! メドレーではエンジニアを積極採用中です! メドレーでは領域を問わず、TypeScript を積極的に活用して医療ヘルスケアの未来をつくるプロダクトを開発しています。 TypeScript を活用した医療ヘルスケア領域の課題解決に興味がある方は、ぜひお気軽にご連絡ください! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp ※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください
はじめに こんにちは! 人材プラットフォーム本部プロダクト統括部プロダクト開発部アカデミー開発グループ所属の城間(シロマ)です。 私は 2024 年 4 月に新卒エンジニアとして入社し、現在はオンライン動画研修サービス「 ジョブメドレーアカデミー 」の新規プロダクト開発に携わっています。 先日、5 月 23 日、24 日に東京都千代田区のベルサール神田にて開催された TSKaigi 2025 にメドレーは Silver Sponsor として協賛しました! TypeScript をテーマにしたこの大規模なカンファレンスには、多様なバックグラウンドを持つ TypeScript エンジニアが全国から集結。 惜しくもブースの抽選には外れてしまいましたが、私も含め弊社からは数名のエンジニアが現地参加し、多くの素晴らしい出会いと学びがありました。 今回は、当日の会場の様子や印象に残ったセッション、そして今、私が感じていることを中心にご紹介します! 会場の熱気と「型にとらわれない」エンジニアたちの交流 今年の TSKaigi 2025 は 2 日間とも天候に恵まれ、 61 もの企業スポンサーと「TypeScript を扱う型にとらわれないエンジニア」が集結し、会場は終日、熱気と活気に満ち溢れていました。 場内の様子 スポンサーボード 2 日間に渡り、合計 3 会場でセッションや LT が盛んに行われ、時には立ち見の人が出るほどに盛況していました。 参加者同士でテーマに対してトークする OST(Open Space Technology) や、夜の豪華な懇親会では、セッションでは聞けないような深掘りした技術の話や、各社の開発文化に関する交流が盛んに行われ、当日はおよそ 50 名以上のエンジニアの方々と色々なお話をさせていただきました。 OST のテーマ 懇親会の様子 TypeScript ケーキ また、今回参加者に配布された公式グッズは、TSKaigi のオリジナル T シャツやトートバッグに加え、各企業が工夫を凝らした遊び心あふれるグッズで盛りだくさんでした。 特に、今回ブース出展のあった 19 社全てを周るスタンプラリー では、全てのスタンプを集めることで豪華景品が当たる抽選に参加でき、会場の賑わいに一役買っていました。 公式ノベルティ(一部) スタンプラリー 印象に残ったセッション:AI エージェント全盛の時代と TypeScript の可能性 どのセッションも非常に興味深く、時には理解が追いつかないほど最先端の内容でしたが、特に今の私が行う開発と照らし合わせて印象に残ったセッションをいくつかご紹介します。 Day1: AI Coding Agent Enablement in TypeScript 引用元: AI Coding Agents Enablement in TypeScript - Speaker Deck 概要 このセッションでは、人間による介入を最小限に抑え、AI エージェントが大規模な作業をどのように「自走」して実行させるかについて深く掘り下げられました。 なぜ自走が難しいのか?その主な要因は AI エージェントが「任意の TypeScript」のようなあまりにも広い「解空間」で動くため、精度が低くなってしまうためです。 すなわち AI エージェントの精度を高めるためには 「可能な限り解空間を絞る」 ことが基本方針であり、会社やプロジェクト固有の規約、ドメイン知識、デザインなどに基づいて適切に制約を与える重要性が何度も強調されていました。 その解空間を絞る具体的なアプローチとして 「コンテキスト注入」 と 「機械的検査とフィードバック」 の 2 つが挙げられました。 コンテキスト注入 : ドキュメント、規約、ドメイン知識などを LLM にインプットし、「解空間の定義」を与えること。特に TypeScript コード自体をドキュメントとして育てることが、コンテキスト注入に役立つ。 機械的検査とフィードバック : LLM が生成したコードが定義された解空間から外れていないか、型チェック、Linter、自動テストといった古典的な手法で検査し、NG の場合はエージェントにフィードバックして解空間へと押し戻すように促す。Linter は次にやるべきアクションを提示しやすく、決定的である点が非常に有効である。 また、TypeScript 開発における型の役割についても興味深い見解がありました。現状、型によってコード生成の精度が上がるという定量的な根拠はまだないものの、「解空間に押し戻す」ためのエージェントへの入力(フィードバック)としては役立つ可能性は大いにあるとのこと。AI はまだ高度な型解決が苦手なので、人間がドメインモデルや関数のシグネチャ(型定義)をしっかり書き、その後の実装をエージェントに任せるアプローチが有効であるという話は、非常に現実的で納得感がありました。 最後に、 「エコシステムの未来 - Speed is King」 という言葉が印象的でした。これは、AI エージェントのコード生成が加速すると、開発プロセス全体の速度がボトルネックとなることを示唆しています。具体的には、人間がコードを 30 分かけて書く時代には 1 分程度の静的解析は許容範囲でしたが、AI が同じコードをわずか 30 秒で書くようになると同じ 1 分間の静的解析は AI の作業時間に対して相対的に非常に長く、無視できないボトルネックになるということです。さらにクラウド型エージェントではチャットごとにコンテナが作成される仕組みが主流であり、その際にパッケージマネージャの速度がボトルネックになります。また、AI エージェントによるコード生産量が爆発的に増加すれば、デプロイの機会も増えるためビルド(バンドラー)の速度もボトルネックとして顕在化し、加えて将来的に LLM がコードを生成するタイミング(デコーディング時)ごとに型制約を守ろうとする「制約付きデコーディング」のようなアプローチが進めば、型チェッカーがボトルネックになる可能性も指摘されていました。故に、ツールチェイン全体の高速化が今後より重要になるという示唆は、将来的な開発環境を考える上で非常に重要な視点だと感じました。 Day2: TS 特化 Cline プログラミング 引用元: tskaigi.mizchi.workers.dev 概要 このセッションでは、LLM を活用した「Cline Agentic Coding」の全般について、特に TypeScript に特化した実践的なプロンプトのコツが紹介されました。 まず冒頭に うまくいくうまくいくプロンプトのコツ として、以下が挙げられていました。 書きすぎない(再現性ある範囲で詠唱破棄) 執拗に出力例を例示する 両立条件の矛盾を避ける 規模感に合わせて厳しくする また特に強調されていたのは、 テスト駆動開発(Test Driven Development) の重要性です。コード生成時に対応するユニットテストを常に生成し、コード修正時にはテストがパスすることを確認することで、AI エージェントの自己修復能力を高め、放置しても完成する高品質なコードに繋がるとのことでした。 その他にも、次のような実践的なプロンプトの一例が紹介されていました。 コメントによる自己記述 : 各ファイルの冒頭にコメントで仕様を記述することで、再修正時のコード解釈の一貫性を保つ In Source Testing : 実装と同じファイルにユニットテストを書くことで、コメント・実装・テストを三位一体で管理する types.ts にドメイン型を集約 : 中規模以上のプロジェクトでは src/types.ts にドメインモデルを集約し、SSoT(Single Source of Truth)とすることで、ファイル間の整合性を保ち read_file の頻度を減らす TS + 関数型ドメインモデリング : 状態の発散を抑えるために class を使わず関数による実装を優先し、代数的データ型でドメインをモデリングする ファイル配置規則の明記 : モノレポなどのファイル配置規則を明記することで、タスクごとのエージェントの推測コストを減らす 詳細指示を docs/*.md に分割 : 大規模プロジェクトでは無関係な指示によるノイズを減らすために、詳細な指示をドキュメントファイルに分割し、必要に応じて参照させる カバレッジに基づくテストの自動生成 : vitest でカバレッジを計測し、最もカバレッジが上がるテストコードを AI に考察・追加させる 機械的なマイグレーション : lodash の削除や類似 API を持つライブラリへの置き換えなど、面倒なマイグレーション作業を AI に自動化させる URL を読む能力 : URL の内容を読み込ませ、要約・保存させることで、Deep Research 的な挙動を可能にする 一方で、 うまくいかないプロンプト のパターンもいくつか示唆されました。 型だけで設計しようとする : ほぼ確実に無視され、抽象的な設計能力は期待できない 非同期例外処理が下手 : 思考停止気味に try-catch で握り潰しがちで、大規模開発で破綻の原因となる 環境構築が下手 : ゼロショットでの環境構築は発散しやすく、手数が仇となり環境を破壊する可能性がある モジュールインターフェースが発散 : 実装次第で全て export してしまい、モジュール間の契約が肥大化・破綻する 「ある」のがよくない(チェーホフの銃の法則) : 無関係なリソースを読み込ませると、それを使うことに固執し、大規模コードではノイズになる デバッグログを食いすぎる : 自身が生成したプリントデバッグでコンテキストウィンドウを消費し、デバッグコードを放置しがちである 結論として、現状の LLM はコーディングが下手であり、低品質なコードで設計が破綻し自滅する傾向があるとの見解でした。特に、リファクタリングの指針がなく、不要コードの判定やモジュール視点での API 設計が苦手で、ユーザー側でリファクタリングしても元の低品質なコードに書き戻すこともあるという少し厳しい評価が下されていました。 しかし TypeScript と LLM の組み合わせには良い点も多く、GitHub の公開コードが豊富で学習量が多いこと、安全性よりも表現力を選んだピーキーな型システムが自然言語と対応した型のモデリングをしやすいこと、豊富な静的解析手段とユーザー層の厚さがあることなどが挙げられていました。 現時点でのベストプラクティスとしては、PoC/プロトタイプのコード生成(1 ファイル完結 800 行以内が目安)に活用し、人間によるインターフェース設計、失敗パターンのプロンプトへの反映、成功するパターンのドキュメント化、そしてこちらでも Lint ルールの整備についての言及がありました。 そして最後にテスト駆動開発や LLM の得意領域・発達段階を予測する技術、プロンプトエンジニアリングの重要性の増加など Agentic Coding によるプログラミング自体の変質と不変である点について説明され締めくくられました。 感想 これらのセッションを通して、私が携わる新規プロダクト開発において、 実践できている部分 と 改善の余地がある部分 が明確になりました。 実践できている点として、全社的に積極的に利用している AI エディタ「Cursor」のプロジェクトルールや自立型 AI エージェント「Devin」の knowledge には、プロジェクトごとに解空間を明確にするコンテキストを与えています。これは、「AI Coding Agent Enablement in TypeScript」で述べられていた「コンテキスト注入」と「解空間を絞る」という考え方に合致していると感じました。また、コードと仕様を記述したドキュメントを同じリポジトリ内で管理し、開発を進めている点も、AI へのコンテキスト注入に貢献していると言えるでしょう。 開発フローにおいても、AI の特性を踏まえた工夫を凝らしています。「AI Coding Agent Enablement in TypeScript」で言及があった「3 回くらいループした末に any とかキャストで誤魔化しがち」という点に対し、例えば API 開発では、まず仕様やテストケースのドキュメントをコードベースと同じファイル内に記述し、次に Request や Response Body を zod を用いて厳密に型を記述します。これらをエンジニアがレビューした後、テストデータのセットアップや記法などをエンジニアが整え、残りを AI で実装を進めます。さらにアプリケーションコードの実装においても、用意したユニットテストが通過するという制約の元 AI に実装させることにより、型解決などに AI エージェントが費やす時間が最小化され、 実装フェーズの開発工数を大幅に短縮することに成功 しています。 mizchi さんのセッションで言及のあったテスト駆動開発(Test Driven Development)のスキルは今後もより重要になっていくでしょう。しかし、もう少し先の未来では、弊社メドレーが大切にしている価値観(Our Essential)の一つである「 ドキュメントドリブン 」に倣い、 ドキュメントドリブン開発(Document Driven Development) のスキルがより重要になっていくのではないか、と私は考えています。 具体的なイメージ さらに「Speed is King」の思想を体現するために、Linter と Formatter には Biome、パッケージマネージャーには Bun を採用するなどツールチェインの速度にはかなりシビアな意思決定をしており、より AI エージェントが生産的に活動できるような体制を整えています。 一方で、 改善の余地がある点 も明確になりました。特に双方のセッションで強調されていた「機械的検査とフィードバック」の継続的な改善、すなわち Linter については開発初期から現在に至るまであまり積極的に更新できていないと痛感しています。AI に与えるフィードバックをその場での単発のもので終わらせるのではなく、(人間と同じように)再発防止策を考えさせるように Linter を定期的にアップデートし、より決定的で高速に AI エージェントが機能するような環境をより一層整備していきます。 さいごに TypeScript の可能性を肌で感じ、熱意ある仲間たちと出会い、技術への情熱を改めて確認できた素晴らしいイベントでした。 メドレーは今後も TSKaigi だけでなく、他の技術イベントやコミュニティの発展を積極的に支援し、参加、貢献していきます。 過去にスポンサーとして協賛した技術カンファレンスの参加レポート記事はこちら! RubyKaigi 2025 参加レポート - Platinum Sponsor として協賛しました! | MEDLEY Developer Portal はじめに こんにちは! 人材プラットフォーム本部プロダクト開発室 第一開発グループ所属の山下です。 メドレーには今年2月に入社したエンジニアで、日本最大級の医療介護求人サイト ジョブメドレー の開発を担当しています。 メドレーは 4 月 1... developer.medley.jp pmconf 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。医療プラットフォーム本部で Product Manager をしている佐藤です。2024 年 5 月にメドレーにジョインし、医療機関向けプロダクト開発に奔走しています。社内では ”papa”、家では”おじさん”と呼ばれ可愛がら... developer.medley.jp JSConf JP 2024に プレミアムスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは!人材プラットフォーム本部で技術広報兼エンジニア採用をしている重田(@Shige0096)です。2024 年 11 月にメドレーにジョインし、初の社外イベントに参加してきました。 今回、メドレーは 2024/11/23 に九段坂上... developer.medley.jp DroidKaigi 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。人材プラットフォーム本部でエンジニアをしている山河です。2023 年 4 月に新卒として入社し、徐々に業務の幅を広げています! さて、メドレーは 2024/9/11 〜 9/13 の 3 日間にベルサール渋谷ガーデンにて開催され... developer.medley.jp TSKaigi 2025 の運営スタッフの皆さん、登壇者の皆さん、一緒に盛り上がった参加者の皆さん、本当にありがとうございました! メドレーではエンジニアを積極採用中です! メドレーでは領域を問わず、TypeScript を積極的に活用して医療ヘルスケアの未来をつくるプロダクトを開発しています。 TypeScript を活用した医療ヘルスケア領域の課題解決に興味がある方は、ぜひお気軽にご連絡ください! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp ※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください
はじめに こんにちは! 人材プラットフォーム本部プロダクト統括部プロダクト開発部アカデミー開発グループ所属の城間(シロマ)です。 私は 2024 年 4 月に新卒エンジニアとして入社し、現在はオンライン動画研修サービス「 ジョブメドレーアカデミー 」の新規プロダクト開発に携わっています。 先日、5 月 23 日、24 日に東京都千代田区のベルサール神田にて開催された TSKaigi 2025 にメドレーは Silver Sponsor として協賛しました! TypeScript をテーマにしたこの大規模なカンファレンスには、多様なバックグラウンドを持つ TypeScript エンジニアが全国から集結。 惜しくもブースの抽選には外れてしまいましたが、私も含め弊社からは数名のエンジニアが現地参加し、多くの素晴らしい出会いと学びがありました。 今回は、当日の会場の様子や印象に残ったセッション、そして今、私が感じていることを中心にご紹介します! 会場の熱気と「型にとらわれない」エンジニアたちの交流 今年の TSKaigi 2025 は 2 日間とも天候に恵まれ、 61 もの企業スポンサーと「TypeScript を扱う型にとらわれないエンジニア」が集結し、会場は終日、熱気と活気に満ち溢れていました。 場内の様子 スポンサーボード 2 日間に渡り、合計 3 会場でセッションや LT が盛んに行われ、時には立ち見の人が出るほどに盛況していました。 参加者同士でテーマに対してトークする OST(Open Space Technology) や、夜の豪華な懇親会では、セッションでは聞けないような深掘りした技術の話や、各社の開発文化に関する交流が盛んに行われ、当日はおよそ 50 名以上のエンジニアの方々と色々なお話をさせていただきました。 OST のテーマ 懇親会の様子 TypeScript ケーキ また、今回参加者に配布された公式グッズは、TSKaigi のオリジナル T シャツやトートバッグに加え、各企業が工夫を凝らした遊び心あふれるグッズで盛りだくさんでした。 特に、今回ブース出展のあった 19 社全てを周るスタンプラリー では、全てのスタンプを集めることで豪華景品が当たる抽選に参加でき、会場の賑わいに一役買っていました。 公式ノベルティ(一部) スタンプラリー 印象に残ったセッション:AI エージェント全盛の時代と TypeScript の可能性 どのセッションも非常に興味深く、時には理解が追いつかないほど最先端の内容でしたが、特に今の私が行う開発と照らし合わせて印象に残ったセッションをいくつかご紹介します。 Day1: AI Coding Agent Enablement in TypeScript 引用元: AI Coding Agents Enablement in TypeScript - Speaker Deck 概要 このセッションでは、人間による介入を最小限に抑え、AI エージェントが大規模な作業をどのように「自走」して実行させるかについて深く掘り下げられました。 なぜ自走が難しいのか?その主な要因は AI エージェントが「任意の TypeScript」のようなあまりにも広い「解空間」で動くため、精度が低くなってしまうためです。 すなわち AI エージェントの精度を高めるためには 「可能な限り解空間を絞る」 ことが基本方針であり、会社やプロジェクト固有の規約、ドメイン知識、デザインなどに基づいて適切に制約を与える重要性が何度も強調されていました。 その解空間を絞る具体的なアプローチとして 「コンテキスト注入」 と 「機械的検査とフィードバック」 の 2 つが挙げられました。 コンテキスト注入 : ドキュメント、規約、ドメイン知識などを LLM にインプットし、「解空間の定義」を与えること。特に TypeScript コード自体をドキュメントとして育てることが、コンテキスト注入に役立つ。 機械的検査とフィードバック : LLM が生成したコードが定義された解空間から外れていないか、型チェック、Linter、自動テストといった古典的な手法で検査し、NG の場合はエージェントにフィードバックして解空間へと押し戻すように促す。Linter は次にやるべきアクションを提示しやすく、決定的である点が非常に有効である。 また、TypeScript 開発における型の役割についても興味深い見解がありました。現状、型によってコード生成の精度が上がるという定量的な根拠はまだないものの、「解空間に押し戻す」ためのエージェントへの入力(フィードバック)としては役立つ可能性は大いにあるとのこと。AI はまだ高度な型解決が苦手なので、人間がドメインモデルや関数のシグネチャ(型定義)をしっかり書き、その後の実装をエージェントに任せるアプローチが有効であるという話は、非常に現実的で納得感がありました。 最後に、 「エコシステムの未来 - Speed is King」 という言葉が印象的でした。これは、AI エージェントのコード生成が加速すると、開発プロセス全体の速度がボトルネックとなることを示唆しています。具体的には、人間がコードを 30 分かけて書く時代には 1 分程度の静的解析は許容範囲でしたが、AI が同じコードをわずか 30 秒で書くようになると同じ 1 分間の静的解析は AI の作業時間に対して相対的に非常に長く、無視できないボトルネックになるということです。さらにクラウド型エージェントではチャットごとにコンテナが作成される仕組みが主流であり、その際にパッケージマネージャの速度がボトルネックになります。また、AI エージェントによるコード生産量が爆発的に増加すれば、デプロイの機会も増えるためビルド(バンドラー)の速度もボトルネックとして顕在化し、加えて将来的に LLM がコードを生成するタイミング(デコーディング時)ごとに型制約を守ろうとする「制約付きデコーディング」のようなアプローチが進めば、型チェッカーがボトルネックになる可能性も指摘されていました。故に、ツールチェイン全体の高速化が今後より重要になるという示唆は、将来的な開発環境を考える上で非常に重要な視点だと感じました。 Day2: TS 特化 Cline プログラミング 引用元: tskaigi.mizchi.workers.dev 概要 このセッションでは、LLM を活用した「Cline Agentic Coding」の全般について、特に TypeScript に特化した実践的なプロンプトのコツが紹介されました。 まず冒頭に うまくいくうまくいくプロンプトのコツ として、以下が挙げられていました。 書きすぎない(再現性ある範囲で詠唱破棄) 執拗に出力例を例示する 両立条件の矛盾を避ける 規模感に合わせて厳しくする また特に強調されていたのは、 テスト駆動開発(Test Driven Development) の重要性です。コード生成時に対応するユニットテストを常に生成し、コード修正時にはテストがパスすることを確認することで、AI エージェントの自己修復能力を高め、放置しても完成する高品質なコードに繋がるとのことでした。 その他にも、次のような実践的なプロンプトの一例が紹介されていました。 コメントによる自己記述 : 各ファイルの冒頭にコメントで仕様を記述することで、再修正時のコード解釈の一貫性を保つ In Source Testing : 実装と同じファイルにユニットテストを書くことで、コメント・実装・テストを三位一体で管理する types.ts にドメイン型を集約 : 中規模以上のプロジェクトでは src/types.ts にドメインモデルを集約し、SSoT(Single Source of Truth)とすることで、ファイル間の整合性を保ち read_file の頻度を減らす TS + 関数型ドメインモデリング : 状態の発散を抑えるために class を使わず関数による実装を優先し、代数的データ型でドメインをモデリングする ファイル配置規則の明記 : モノレポなどのファイル配置規則を明記することで、タスクごとのエージェントの推測コストを減らす 詳細指示を docs/*.md に分割 : 大規模プロジェクトでは無関係な指示によるノイズを減らすために、詳細な指示をドキュメントファイルに分割し、必要に応じて参照させる カバレッジに基づくテストの自動生成 : vitest でカバレッジを計測し、最もカバレッジが上がるテストコードを AI に考察・追加させる 機械的なマイグレーション : lodash の削除や類似 API を持つライブラリへの置き換えなど、面倒なマイグレーション作業を AI に自動化させる URL を読む能力 : URL の内容を読み込ませ、要約・保存させることで、Deep Research 的な挙動を可能にする 一方で、 うまくいかないプロンプト のパターンもいくつか示唆されました。 型だけで設計しようとする : ほぼ確実に無視され、抽象的な設計能力は期待できない 非同期例外処理が下手 : 思考停止気味に try-catch で握り潰しがちで、大規模開発で破綻の原因となる 環境構築が下手 : ゼロショットでの環境構築は発散しやすく、手数が仇となり環境を破壊する可能性がある モジュールインターフェースが発散 : 実装次第で全て export してしまい、モジュール間の契約が肥大化・破綻する 「ある」のがよくない(チェーホフの銃の法則) : 無関係なリソースを読み込ませると、それを使うことに固執し、大規模コードではノイズになる デバッグログを食いすぎる : 自身が生成したプリントデバッグでコンテキストウィンドウを消費し、デバッグコードを放置しがちである 結論として、現状の LLM はコーディングが下手であり、低品質なコードで設計が破綻し自滅する傾向があるとの見解でした。特に、リファクタリングの指針がなく、不要コードの判定やモジュール視点での API 設計が苦手で、ユーザー側でリファクタリングしても元の低品質なコードに書き戻すこともあるという少し厳しい評価が下されていました。 しかし TypeScript と LLM の組み合わせには良い点も多く、GitHub の公開コードが豊富で学習量が多いこと、安全性よりも表現力を選んだピーキーな型システムが自然言語と対応した型のモデリングをしやすいこと、豊富な静的解析手段とユーザー層の厚さがあることなどが挙げられていました。 現時点でのベストプラクティスとしては、PoC/プロトタイプのコード生成(1 ファイル完結 800 行以内が目安)に活用し、人間によるインターフェース設計、失敗パターンのプロンプトへの反映、成功するパターンのドキュメント化、そしてこちらでも Lint ルールの整備についての言及がありました。 そして最後にテスト駆動開発や LLM の得意領域・発達段階を予測する技術、プロンプトエンジニアリングの重要性の増加など Agentic Coding によるプログラミング自体の変質と不変である点について説明され締めくくられました。 感想 これらのセッションを通して、私が携わる新規プロダクト開発において、 実践できている部分 と 改善の余地がある部分 が明確になりました。 実践できている点として、全社的に積極的に利用している AI エディタ「Cursor」のプロジェクトルールや自立型 AI エージェント「Devin」の knowledge には、プロジェクトごとに解空間を明確にするコンテキストを与えています。これは、「AI Coding Agent Enablement in TypeScript」で述べられていた「コンテキスト注入」と「解空間を絞る」という考え方に合致していると感じました。また、コードと仕様を記述したドキュメントを同じリポジトリ内で管理し、開発を進めている点も、AI へのコンテキスト注入に貢献していると言えるでしょう。 開発フローにおいても、AI の特性を踏まえた工夫を凝らしています。「AI Coding Agent Enablement in TypeScript」で言及があった「3 回くらいループした末に any とかキャストで誤魔化しがち」という点に対し、例えば API 開発では、まず仕様やテストケースのドキュメントをコードベースと同じファイル内に記述し、次に Request や Response Body を zod を用いて厳密に型を記述します。これらをエンジニアがレビューした後、テストデータのセットアップや記法などをエンジニアが整え、残りを AI で実装を進めます。さらにアプリケーションコードの実装においても、用意したユニットテストが通過するという制約の元 AI に実装させることにより、型解決などに AI エージェントが費やす時間が最小化され、 実装フェーズの開発工数を大幅に短縮することに成功 しています。 mizchi さんのセッションで言及のあったテスト駆動開発(Test Driven Development)のスキルは今後もより重要になっていくでしょう。しかし、もう少し先の未来では、弊社メドレーが大切にしている価値観(Our Essential)の一つである「 ドキュメントドリブン 」に倣い、 ドキュメントドリブン開発(Document Driven Development) のスキルがより重要になっていくのではないか、と私は考えています。 具体的なイメージ さらに「Speed is King」の思想を体現するために、Linter と Formatter には Biome、パッケージマネージャーには Bun を採用するなどツールチェインの速度にはかなりシビアな意思決定をしており、より AI エージェントが生産的に活動できるような体制を整えています。 一方で、 改善の余地がある点 も明確になりました。特に双方のセッションで強調されていた「機械的検査とフィードバック」の継続的な改善、すなわち Linter については開発初期から現在に至るまであまり積極的に更新できていないと痛感しています。AI に与えるフィードバックをその場での単発のもので終わらせるのではなく、(人間と同じように)再発防止策を考えさせるように Linter を定期的にアップデートし、より決定的で高速に AI エージェントが機能するような環境をより一層整備していきます。 さいごに TypeScript の可能性を肌で感じ、熱意ある仲間たちと出会い、技術への情熱を改めて確認できた素晴らしいイベントでした。 メドレーは今後も TSKaigi だけでなく、他の技術イベントやコミュニティの発展を積極的に支援し、参加、貢献していきます。 過去にスポンサーとして協賛した技術カンファレンスの参加レポート記事はこちら! RubyKaigi 2025 参加レポート - Platinum Sponsor として協賛しました! | MEDLEY Developer Portal はじめに こんにちは! 人材プラットフォーム本部プロダクト開発室 第一開発グループ所属の山下です。 メドレーには今年2月に入社したエンジニアで、日本最大級の医療介護求人サイト ジョブメドレー の開発を担当しています。 メドレーは 4 月 1... developer.medley.jp pmconf 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。医療プラットフォーム本部で Product Manager をしている佐藤です。2024 年 5 月にメドレーにジョインし、医療機関向けプロダクト開発に奔走しています。社内では ”papa”、家では”おじさん”と呼ばれ可愛がら... developer.medley.jp JSConf JP 2024に プレミアムスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは!人材プラットフォーム本部で技術広報兼エンジニア採用をしている重田(@Shige0096)です。2024 年 11 月にメドレーにジョインし、初の社外イベントに参加してきました。 今回、メドレーは 2024/11/23 に九段坂上... developer.medley.jp DroidKaigi 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。人材プラットフォーム本部でエンジニアをしている山河です。2023 年 4 月に新卒として入社し、徐々に業務の幅を広げています! さて、メドレーは 2024/9/11 〜 9/13 の 3 日間にベルサール渋谷ガーデンにて開催され... developer.medley.jp TSKaigi 2025 の運営スタッフの皆さん、登壇者の皆さん、一緒に盛り上がった参加者の皆さん、本当にありがとうございました! メドレーではエンジニアを積極採用中です! メドレーでは領域を問わず、TypeScript を積極的に活用して医療ヘルスケアの未来をつくるプロダクトを開発しています。 TypeScript を活用した医療ヘルスケア領域の課題解決に興味がある方は、ぜひお気軽にご連絡ください! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp ※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください
はじめに こんにちは! 人材プラットフォーム本部プロダクト統括部プロダクト開発部アカデミー開発グループ所属の城間(シロマ)です。 私は 2024 年 4 月に新卒エンジニアとして入社し、現在はオンライン動画研修サービス「 ジョブメドレーアカデミー 」の新規プロダクト開発に携わっています。 先日、5 月 23 日、24 日に東京都千代田区のベルサール神田にて開催された TSKaigi 2025 にメドレーは Silver Sponsor として協賛しました! TypeScript をテーマにしたこの大規模なカンファレンスには、多様なバックグラウンドを持つ TypeScript エンジニアが全国から集結。 惜しくもブースの抽選には外れてしまいましたが、私も含め弊社からは数名のエンジニアが現地参加し、多くの素晴らしい出会いと学びがありました。 今回は、当日の会場の様子や印象に残ったセッション、そして今、私が感じていることを中心にご紹介します! 会場の熱気と「型にとらわれない」エンジニアたちの交流 今年の TSKaigi 2025 は 2 日間とも天候に恵まれ、 61 もの企業スポンサーと「TypeScript を扱う型にとらわれないエンジニア」が集結し、会場は終日、熱気と活気に満ち溢れていました。 場内の様子 スポンサーボード 2 日間に渡り、合計 3 会場でセッションや LT が盛んに行われ、時には立ち見の人が出るほどに盛況していました。 参加者同士でテーマに対してトークする OST(Open Space Technology) や、夜の豪華な懇親会では、セッションでは聞けないような深掘りした技術の話や、各社の開発文化に関する交流が盛んに行われ、当日はおよそ 50 名以上のエンジニアの方々と色々なお話をさせていただきました。 OST のテーマ 懇親会の様子 TypeScript ケーキ また、今回参加者に配布された公式グッズは、TSKaigi のオリジナル T シャツやトートバッグに加え、各企業が工夫を凝らした遊び心あふれるグッズで盛りだくさんでした。 特に、今回ブース出展のあった 19 社全てを周るスタンプラリー では、全てのスタンプを集めることで豪華景品が当たる抽選に参加でき、会場の賑わいに一役買っていました。 公式ノベルティ(一部) スタンプラリー 印象に残ったセッション:AI エージェント全盛の時代と TypeScript の可能性 どのセッションも非常に興味深く、時には理解が追いつかないほど最先端の内容でしたが、特に今の私が行う開発と照らし合わせて印象に残ったセッションをいくつかご紹介します。 Day1: AI Coding Agent Enablement in TypeScript 引用元: AI Coding Agents Enablement in TypeScript - Speaker Deck 概要 このセッションでは、人間による介入を最小限に抑え、AI エージェントが大規模な作業をどのように「自走」して実行させるかについて深く掘り下げられました。 なぜ自走が難しいのか?その主な要因は AI エージェントが「任意の TypeScript」のようなあまりにも広い「解空間」で動くため、精度が低くなってしまうためです。 すなわち AI エージェントの精度を高めるためには 「可能な限り解空間を絞る」 ことが基本方針であり、会社やプロジェクト固有の規約、ドメイン知識、デザインなどに基づいて適切に制約を与える重要性が何度も強調されていました。 その解空間を絞る具体的なアプローチとして 「コンテキスト注入」 と 「機械的検査とフィードバック」 の 2 つが挙げられました。 コンテキスト注入 : ドキュメント、規約、ドメイン知識などを LLM にインプットし、「解空間の定義」を与えること。特に TypeScript コード自体をドキュメントとして育てることが、コンテキスト注入に役立つ。 機械的検査とフィードバック : LLM が生成したコードが定義された解空間から外れていないか、型チェック、Linter、自動テストといった古典的な手法で検査し、NG の場合はエージェントにフィードバックして解空間へと押し戻すように促す。Linter は次にやるべきアクションを提示しやすく、決定的である点が非常に有効である。 また、TypeScript 開発における型の役割についても興味深い見解がありました。現状、型によってコード生成の精度が上がるという定量的な根拠はまだないものの、「解空間に押し戻す」ためのエージェントへの入力(フィードバック)としては役立つ可能性は大いにあるとのこと。AI はまだ高度な型解決が苦手なので、人間がドメインモデルや関数のシグネチャ(型定義)をしっかり書き、その後の実装をエージェントに任せるアプローチが有効であるという話は、非常に現実的で納得感がありました。 最後に、 「エコシステムの未来 - Speed is King」 という言葉が印象的でした。これは、AI エージェントのコード生成が加速すると、開発プロセス全体の速度がボトルネックとなることを示唆しています。具体的には、人間がコードを 30 分かけて書く時代には 1 分程度の静的解析は許容範囲でしたが、AI が同じコードをわずか 30 秒で書くようになると同じ 1 分間の静的解析は AI の作業時間に対して相対的に非常に長く、無視できないボトルネックになるということです。さらにクラウド型エージェントではチャットごとにコンテナが作成される仕組みが主流であり、その際にパッケージマネージャの速度がボトルネックになります。また、AI エージェントによるコード生産量が爆発的に増加すれば、デプロイの機会も増えるためビルド(バンドラー)の速度もボトルネックとして顕在化し、加えて将来的に LLM がコードを生成するタイミング(デコーディング時)ごとに型制約を守ろうとする「制約付きデコーディング」のようなアプローチが進めば、型チェッカーがボトルネックになる可能性も指摘されていました。故に、ツールチェイン全体の高速化が今後より重要になるという示唆は、将来的な開発環境を考える上で非常に重要な視点だと感じました。 Day2: TS 特化 Cline プログラミング 引用元: tskaigi.mizchi.workers.dev 概要 このセッションでは、LLM を活用した「Cline Agentic Coding」の全般について、特に TypeScript に特化した実践的なプロンプトのコツが紹介されました。 まず冒頭に うまくいくうまくいくプロンプトのコツ として、以下が挙げられていました。 書きすぎない(再現性ある範囲で詠唱破棄) 執拗に出力例を例示する 両立条件の矛盾を避ける 規模感に合わせて厳しくする また特に強調されていたのは、 テスト駆動開発(Test Driven Development) の重要性です。コード生成時に対応するユニットテストを常に生成し、コード修正時にはテストがパスすることを確認することで、AI エージェントの自己修復能力を高め、放置しても完成する高品質なコードに繋がるとのことでした。 その他にも、次のような実践的なプロンプトの一例が紹介されていました。 コメントによる自己記述 : 各ファイルの冒頭にコメントで仕様を記述することで、再修正時のコード解釈の一貫性を保つ In Source Testing : 実装と同じファイルにユニットテストを書くことで、コメント・実装・テストを三位一体で管理する types.ts にドメイン型を集約 : 中規模以上のプロジェクトでは src/types.ts にドメインモデルを集約し、SSoT(Single Source of Truth)とすることで、ファイル間の整合性を保ち read_file の頻度を減らす TS + 関数型ドメインモデリング : 状態の発散を抑えるために class を使わず関数による実装を優先し、代数的データ型でドメインをモデリングする ファイル配置規則の明記 : モノレポなどのファイル配置規則を明記することで、タスクごとのエージェントの推測コストを減らす 詳細指示を docs/*.md に分割 : 大規模プロジェクトでは無関係な指示によるノイズを減らすために、詳細な指示をドキュメントファイルに分割し、必要に応じて参照させる カバレッジに基づくテストの自動生成 : vitest でカバレッジを計測し、最もカバレッジが上がるテストコードを AI に考察・追加させる 機械的なマイグレーション : lodash の削除や類似 API を持つライブラリへの置き換えなど、面倒なマイグレーション作業を AI に自動化させる URL を読む能力 : URL の内容を読み込ませ、要約・保存させることで、Deep Research 的な挙動を可能にする 一方で、 うまくいかないプロンプト のパターンもいくつか示唆されました。 型だけで設計しようとする : ほぼ確実に無視され、抽象的な設計能力は期待できない 非同期例外処理が下手 : 思考停止気味に try-catch で握り潰しがちで、大規模開発で破綻の原因となる 環境構築が下手 : ゼロショットでの環境構築は発散しやすく、手数が仇となり環境を破壊する可能性がある モジュールインターフェースが発散 : 実装次第で全て export してしまい、モジュール間の契約が肥大化・破綻する 「ある」のがよくない(チェーホフの銃の法則) : 無関係なリソースを読み込ませると、それを使うことに固執し、大規模コードではノイズになる デバッグログを食いすぎる : 自身が生成したプリントデバッグでコンテキストウィンドウを消費し、デバッグコードを放置しがちである 結論として、現状の LLM はコーディングが下手であり、低品質なコードで設計が破綻し自滅する傾向があるとの見解でした。特に、リファクタリングの指針がなく、不要コードの判定やモジュール視点での API 設計が苦手で、ユーザー側でリファクタリングしても元の低品質なコードに書き戻すこともあるという少し厳しい評価が下されていました。 しかし TypeScript と LLM の組み合わせには良い点も多く、GitHub の公開コードが豊富で学習量が多いこと、安全性よりも表現力を選んだピーキーな型システムが自然言語と対応した型のモデリングをしやすいこと、豊富な静的解析手段とユーザー層の厚さがあることなどが挙げられていました。 現時点でのベストプラクティスとしては、PoC/プロトタイプのコード生成(1 ファイル完結 800 行以内が目安)に活用し、人間によるインターフェース設計、失敗パターンのプロンプトへの反映、成功するパターンのドキュメント化、そしてこちらでも Lint ルールの整備についての言及がありました。 そして最後にテスト駆動開発や LLM の得意領域・発達段階を予測する技術、プロンプトエンジニアリングの重要性の増加など Agentic Coding によるプログラミング自体の変質と不変である点について説明され締めくくられました。 感想 これらのセッションを通して、私が携わる新規プロダクト開発において、 実践できている部分 と 改善の余地がある部分 が明確になりました。 実践できている点として、全社的に積極的に利用している AI エディタ「Cursor」のプロジェクトルールや自立型 AI エージェント「Devin」の knowledge には、プロジェクトごとに解空間を明確にするコンテキストを与えています。これは、「AI Coding Agent Enablement in TypeScript」で述べられていた「コンテキスト注入」と「解空間を絞る」という考え方に合致していると感じました。また、コードと仕様を記述したドキュメントを同じリポジトリ内で管理し、開発を進めている点も、AI へのコンテキスト注入に貢献していると言えるでしょう。 開発フローにおいても、AI の特性を踏まえた工夫を凝らしています。「AI Coding Agent Enablement in TypeScript」で言及があった「3 回くらいループした末に any とかキャストで誤魔化しがち」という点に対し、例えば API 開発では、まず仕様やテストケースのドキュメントをコードベースと同じファイル内に記述し、次に Request や Response Body を zod を用いて厳密に型を記述します。これらをエンジニアがレビューした後、テストデータのセットアップや記法などをエンジニアが整え、残りを AI で実装を進めます。さらにアプリケーションコードの実装においても、用意したユニットテストが通過するという制約の元 AI に実装させることにより、型解決などに AI エージェントが費やす時間が最小化され、 実装フェーズの開発工数を大幅に短縮することに成功 しています。 mizchi さんのセッションで言及のあったテスト駆動開発(Test Driven Development)のスキルは今後もより重要になっていくでしょう。しかし、もう少し先の未来では、弊社メドレーが大切にしている価値観(Our Essential)の一つである「 ドキュメントドリブン 」に倣い、 ドキュメントドリブン開発(Document Driven Development) のスキルがより重要になっていくのではないか、と私は考えています。 具体的なイメージ さらに「Speed is King」の思想を体現するために、Linter と Formatter には Biome、パッケージマネージャーには Bun を採用するなどツールチェインの速度にはかなりシビアな意思決定をしており、より AI エージェントが生産的に活動できるような体制を整えています。 一方で、 改善の余地がある点 も明確になりました。特に双方のセッションで強調されていた「機械的検査とフィードバック」の継続的な改善、すなわち Linter については開発初期から現在に至るまであまり積極的に更新できていないと痛感しています。AI に与えるフィードバックをその場での単発のもので終わらせるのではなく、(人間と同じように)再発防止策を考えさせるように Linter を定期的にアップデートし、より決定的で高速に AI エージェントが機能するような環境をより一層整備していきます。 さいごに TypeScript の可能性を肌で感じ、熱意ある仲間たちと出会い、技術への情熱を改めて確認できた素晴らしいイベントでした。 メドレーは今後も TSKaigi だけでなく、他の技術イベントやコミュニティの発展を積極的に支援し、参加、貢献していきます。 過去にスポンサーとして協賛した技術カンファレンスの参加レポート記事はこちら! RubyKaigi 2025 参加レポート - Platinum Sponsor として協賛しました! | MEDLEY Developer Portal はじめに こんにちは! 人材プラットフォーム本部プロダクト開発室 第一開発グループ所属の山下です。 メドレーには今年2月に入社したエンジニアで、日本最大級の医療介護求人サイト ジョブメドレー の開発を担当しています。 メドレーは 4 月 1... developer.medley.jp pmconf 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。医療プラットフォーム本部で Product Manager をしている佐藤です。2024 年 5 月にメドレーにジョインし、医療機関向けプロダクト開発に奔走しています。社内では ”papa”、家では”おじさん”と呼ばれ可愛がら... developer.medley.jp JSConf JP 2024に プレミアムスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは!人材プラットフォーム本部で技術広報兼エンジニア採用をしている重田(@Shige0096)です。2024 年 11 月にメドレーにジョインし、初の社外イベントに参加してきました。 今回、メドレーは 2024/11/23 に九段坂上... developer.medley.jp DroidKaigi 2024にゴールドスポンサーとして協賛しました! | MEDLEY Developer Portal こんにちは。人材プラットフォーム本部でエンジニアをしている山河です。2023 年 4 月に新卒として入社し、徐々に業務の幅を広げています! さて、メドレーは 2024/9/11 〜 9/13 の 3 日間にベルサール渋谷ガーデンにて開催され... developer.medley.jp TSKaigi 2025 の運営スタッフの皆さん、登壇者の皆さん、一緒に盛り上がった参加者の皆さん、本当にありがとうございました! メドレーではエンジニアを積極採用中です! メドレーでは領域を問わず、TypeScript を積極的に活用して医療ヘルスケアの未来をつくるプロダクトを開発しています。 TypeScript を活用した医療ヘルスケア領域の課題解決に興味がある方は、ぜひお気軽にご連絡ください! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp ※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください
はじめに こんにちは。医療プラットフォーム本部 CLINICS 開発グループの吉岡( @yuya333_ )と村上( @yuporonM )です。 吉岡は 2022 年に、村上は 2024 年に新卒でエンジニアとしてメドレーに入社しました。 私たちは、医科診療所向けの電子カルテである CLINICS カルテ を開発しています。CLINICS カルテは 2025 年 3 月に iPad 手書き機能をリリースしました。iPad 手書き機能とは、iPad を使って主訴・所見やシェーマ(身体の部位の絵)を手書きできる機能です。 主訴・所見の手書き シェーマの手書き 本記事では iPad 手書き機能を開発するにあたって工夫した点を紹介します。 ライブラリ選定 手書き機能を開発するにあたって、手書き処理すべてを実装するのは工数の観点から現実的ではありませんでした。 そのため、要件を満たせるかつメンテナンス面から以下のライブラリが候補となりました。 fabric konva メドレーでは、歯科診療所向けに電子カルテ Dentis を開発しており、Dentis では既に手書き機能を提供しています。そこで、手書き機能の実装に関して知見を持っている Dentis のエンジニアにライブラリ選定について相談したところ、以下の理由で fabric を採用することに決めました。 konva では線が荒くなってしまうことがある https://github.com/konvajs/konva/issues/1144 で解消方法も紹介されていましたが、細かく線を書きたいケースに対応できない konva では線を描くたびに再レンダリングが発生してしまう mousemove や touchevent のデータをキャッシュすることで解決できるが、遅延が気になる また、fabric を使用した手書き機能の実装方法に関しては、2024 年 12 月にメドレーのアドベントカレンダーで吉岡が書いた以下の記事を参考にしてみてください。 React+fabric.jsで作る手書きアプリ zenn.dev 手書き機能で工夫したポイント 4 選 工夫 1. 手を画面につけながらペンで手書きできるようにした 画面に手をつけながらペンで書いたときに、以下のように線が乱れてしまうことがありました。 画面に接触した手はペンや指で書いたときよりも接触面積が大きくなるので、接触部分が一定よりも大きくなった場合には線を書けないように実装しました。具体的には、 radiusX プロパティを使用して、radiusX がある値より大きいときには、 isDrawingMode が false になるようにしました。 canvas . on ( "mouse:down:before" , ( e ) => { if ( "TouchEvent" in window && e . e instanceof TouchEvent ) { const touch = e . e . touches . item ( 0 ); if (! touch ) { return ; } // 接触面積がペンや指よりも大きいときに線を書けなくする if ( touch . radiusX > 35 ) { canvas . isDrawingMode = false ; } } }); // 画面から手を離す度にisDrawingModeをリセット canvas . on ( "mouse:up:before" , () => { canvas . isDrawingMode = true ; }); 工夫 2. 消しゴムで線を消すときのラグを解消した 消しゴム機能は erase2d ライブラリを使って実装しました。 fabric v5 までは消しゴム機能が内包されていましたが、fabric v6 から erase2d に切り出されました。 erase2d を使って消しゴムを実装すると、次の動画のようにユーザビリティを大きく損なうほどのラグが発生していました。 このラグは PC では発生せず、iPad でのみ発生していました。 この現象について原因を調査したところ、以下の 2 点が原因であることが分かりました。 原因 1. Retina ディスプレイによるピクセル密度の違い iPad は Retina ディスプレイを搭載しており、ピクセル密度が通常のディスプレイよりも高くなっていました。 検証に利用していた 11 インチ iPad Air (M2) では、PC と比較してピクセル密度が 4 倍にもなっていました。 原因 2. ピクセル密度に依存した消しゴム機能特有の処理 消しゴム機能は、ペン機能と比較して複雑な処理が行われていました。 ペンは 1 つのキャンバスに線を描くだけであるのに対して、消しゴムは複数のキャンバスを扱い、それらを合成する処理が必要でした。 具体的には、消しゴムの軌跡を一時的なキャンバスに描画し、それをメインキャンバスに合成するといった処理等が行われていました。 これは、ピクセル密度に依存しており、ピクセル密度が高い環境では負荷が高くなっていました。 // https://github.com/ShaMan123/erase2d/blob/7ee2b789b4c9161d662ea8c8aaf87af4c37bab13/packages/core/src/erase.ts#L15-L40 export const erase = ( destination : CanvasRenderingContext2D , // メインキャンバス source : CanvasRenderingContext2D , // 消しゴムの軌跡が描かれたキャンバス erasingEffect ?: CanvasRenderingContext2D // 消したくないものを保護するためのキャンバス ) => { // メインキャンバスから、消しゴムの軌跡部分を消去 drawImage ( destination , source , "destination-out" ); if ( erasingEffect ) { // 消しゴムの軌跡に、消したくないものがあれば、消さずに保護 drawImage ( source , erasingEffect , "source-in" ); } else { // 消しゴムの軌跡を描いたキャンバスを初期化 source . save (); source . resetTransform (); source . clearRect ( 0 , 0 , source . canvas . width , source . canvas . height ); source . restore (); } }; これらの原因によって、Retina ディスプレイの高解像度環境では消しゴムで線を消すときにラグが発生していました。 解決策として Canvas の初期化時に enableRetinaScaling オプションを false に設定し、Retina ディスプレイの高解像度環境においても同じピクセル密度で処理を行うようにしました。 const canvas = new fabric . Canvas ( canvasElement , { enableRetinaScaling: false , }); この設定により、一定の解像度を担保しつつ、iPad でも滑らかな消しゴム操作を実現できました。 工夫 3. iPad で手書きして保存した画像を PC に即時反映されるようにした iPad で手書きしながら PC でカルテを使うということを想定しているため、iPad で保存した手書き画像は PC に即時反映される必要があります。こちらを実現するために、以下が候補に挙げられました。 SSE(Server-Sent Events) WebSocket Firebase Realtime Database Firebase Realtime Database を既に使用しており、今回保存するのは更新時間のみで、保存容量が大幅に増えることもないため、今回は Firebase Realtime Database を使用することにしました。 以下が、Realtime Database で変更の監視及び通知を行う実装です。 import { initializeApp } from "firebase/app" ; import { getDatabase , off , onValue , ref , set } from "firebase/database" ; const app = initializeApp ( { /* 省略’*/ }, "firebaseapp" ); const db = () => { return getDatabase ( app ); }; const PATH = "medicalRecordChiefComplaintImages" ; // refを生成 const medicalRecordChiefComplaintImagesRef = ( medicalRecordId : string ) => { return ref ( db (), `/ ${ PATH } / ${ medicalRecordId } ` ); }; // 主訴所見の手書き画像が更新されたことを通知する export const updateMedicalRecordChiefComplaintImageIds = async ({ medicalRecordId , }: { medicalRecordId : string ; }) => { await set ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), Date . now ()); }; // 主訴所見の手書き画像の変更を監視する export const watchMedicalRecordChiefComplaintImages = ({ medicalRecordId , refreshMedicalRecordChiefComplaintImages , }: { medicalRecordId : string ; refreshMedicalRecordChiefComplaintImages : () => void ; }) => { onValue ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), () => { // Realtime Databaseが更新されたときに実行する処理 refreshMedicalRecordChiefComplaintImages (); }); }; // 主訴所見の手書き画像の変更の監視を解除する export const unwatchMedicalRecordChiefComplaintImages = ({ medicalRecordId , }: { medicalRecordId : string ; }) => { off ( medicalRecordChiefComplaintImagesRef ( medicalRecordId )); }; フロントエンドでは、データフェッチに TanStack Query を使用しています。以下のように Realtime Database によって手書き画像の更新を監視して refetch しています。 export const useMedicalChiefComplaintImagesWatchQuery = ({ medicalRecordId , initialChiefComplaintImages , }: UseMedicalChiefComplaintImagesWatchQueryProps ) => { const medicalChiefComplaintImagesQuery = useQuery ({ queryKey: medicalChiefComplaintImageKey . list ( medicalRecordId ), queryFn : () => query . get ( request . get ( medicalRecordId )), }); useEffect (() => { watchMedicalRecordChiefComplaintImages ({ medicalRecordId: medicalRecordId , // Realtime Databaseの更新時にrefetchする refreshMedicalRecordChiefComplaintImages: medicalChiefComplaintImagesQuery . refetch , }); return () => unwatchMedicalRecordChiefComplaintImages ({ medicalRecordId }); }, [ medicalRecordId , medicalChiefComplaintImagesQuery . refetch ]); return medicalChiefComplaintImagesQuery ; }; 最後に、iPad で手書き画像を更新したタイミングで updateMedicalRecordChiefComplaintImageIds を呼び出せば、以下のように iPad での変更が PC に即時に反映されるようになります。 工夫 4. 手書きツールバーを自由に配置できるようにした 手書き機能では太さや色の変更、ペンと消しゴムの切り替え、履歴管理ができるようにツールバーを実装しています。ツールバーは頻繁に使われるため、ユーザーが使用しやすい位置に移動できるようにしました。こちらは dnd-kit を使用して実装しました。 動画にあるようにツールバーが親要素からはみ出ないようになっています。こちらは restrictToParentElement を使用することで実現できます。 また、CSS ライブラリに emotion を使用しており、今回の Draggable 対応では動的にスタイルを変更する必要があるため、 style prop を使うように注意する必要があります( https://emotion.sh/docs/best-practices#use-the-style-prop-for-dynamic-styles )。 ライブラリの不具合 上記のようにユーザーが使いやすいように手書き機能を開発しましたが、消しゴムで線を消すと消えて欲しくない背景画像まで消えてしまうという不具合がライブラリに存在していました。こちらは同じ fabric を使用している Dentis のエンジニア大岡に相談したところ、ライブラリに PR を出して解消していただけました! fix(): backgroundImage/overlayImage erasable by yasupeke · Pull Request #49 · ShaMan123/erase2d I always appreciate the convenience of using this tool. I have fixed an issue where the erasable setting for backgroundImage/overlayImage was not being applied. Please review the changes. github.com まとめ 本記事では、Web でユーザビリティの高い手書きアプリケーションを実装するための工夫点について紹介しました。手書き機能の実装においては、実装速度とユーザビリティを両立するために適切なライブラリ選定が重要です。私達は fabric を採用することで滑らかな手書きアプリケーションを実現できました。 実装にあたり、以下の 4 つを工夫しました。 手を画面に置きながら書ける機能: 接触面積の大きさを検出してペン入力と区別することで、自然な書き心地を実現しました 消しゴム操作のラグ解消: Retina ディスプレイ対応を最適化し、高負荷な消しゴム処理でもスムーズな操作感を実現しました リアルタイム同期: Firebase Realtime Database を活用し、複数デバイス間での即時反映を実現しました 柔軟な UI: dnd-kit によるドラッグ可能なツールバーで、ユーザー体験を向上しました また、ライブラリの不具合に対してはプロダクトを横断したコミュニケーションやコミュニティと協力することで解決しました。これらの工夫により、医科診療所で快適に使用できる手書き機能を開発することができました。 今後もユーザビリティを重視しながら、より使いやすい機能の開発を続けていきます。 We’re hiring メドレーでは一緒に働く仲間を大募集しています! カジュアル面談も実施しておりますので、「お話だけでも聞いてみたい!」「ちょっと雑談してみたい!」でも構いませんので、お気軽にお問い合わせください! 募集の一覧 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp 医療エンジニアリング領域盛り上がっています!メドレーについてお話します! メドレーの人材プラットフォーム事業についてお話しします! 「メドレーの人材プラットフォーム事業についてお話しします!」- 倉林 昭和さん pitta.me メドレーの開発チームについて知りたい方!ぜひお話ししましょう! メドレーの開発チームについて知りたい方!ぜひお話ししましょう! 「メドレーの開発チームについて知りたい方!ぜひお話ししましょう!」- 德永 諒介さん pitta.me
はじめに こんにちは。医療プラットフォーム本部 CLINICS 開発グループの吉岡( @yuya333_ )と村上( @yuporonM )です。 吉岡は 2022 年に、村上は 2024 年に新卒でエンジニアとしてメドレーに入社しました。 私たちは、医科診療所向けの電子カルテである CLINICS カルテ を開発しています。CLINICS カルテは 2025 年 3 月に iPad 手書き機能をリリースしました。iPad 手書き機能とは、iPad を使って主訴・所見やシェーマ(身体の部位の絵)を手書きできる機能です。 主訴・所見の手書き シェーマの手書き 本記事では iPad 手書き機能を開発するにあたって工夫した点を紹介します。 ライブラリ選定 手書き機能を開発するにあたって、手書き処理すべてを実装するのは工数の観点から現実的ではありませんでした。 そのため、要件を満たせるかつメンテナンス面から以下のライブラリが候補となりました。 fabric konva メドレーでは、歯科診療所向けに電子カルテ Dentis を開発しており、Dentis では既に手書き機能を提供しています。そこで、手書き機能の実装に関して知見を持っている Dentis のエンジニアにライブラリ選定について相談したところ、以下の理由で fabric を採用することに決めました。 konva では線が荒くなってしまうことがある https://github.com/konvajs/konva/issues/1144 で解消方法も紹介されていましたが、細かく線を書きたいケースに対応できない konva では線を描くたびに再レンダリングが発生してしまう mousemove や touchevent のデータをキャッシュすることで解決できるが、遅延が気になる また、fabric を使用した手書き機能の実装方法に関しては、2024 年 12 月にメドレーのアドベントカレンダーで吉岡が書いた以下の記事を参考にしてみてください。 React+fabric.jsで作る手書きアプリ zenn.dev 手書き機能で工夫したポイント 4 選 工夫 1. 手を画面につけながらペンで手書きできるようにした 画面に手をつけながらペンで書いたときに、以下のように線が乱れてしまうことがありました。 画面に接触した手はペンや指で書いたときよりも接触面積が大きくなるので、接触部分が一定よりも大きくなった場合には線を書けないように実装しました。具体的には、 radiusX プロパティを使用して、radiusX がある値より大きいときには、 isDrawingMode が false になるようにしました。 canvas . on ( "mouse:down:before" , ( e ) => { if ( "TouchEvent" in window && e . e instanceof TouchEvent ) { const touch = e . e . touches . item ( 0 ); if (! touch ) { return ; } // 接触面積がペンや指よりも大きいときに線を書けなくする if ( touch . radiusX > 35 ) { canvas . isDrawingMode = false ; } } }); // 画面から手を離す度にisDrawingModeをリセット canvas . on ( "mouse:up:before" , () => { canvas . isDrawingMode = true ; }); 工夫 2. 消しゴムで線を消すときのラグを解消した 消しゴム機能は erase2d ライブラリを使って実装しました。 fabric v5 までは消しゴム機能が内包されていましたが、fabric v6 から erase2d に切り出されました。 erase2d を使って消しゴムを実装すると、次の動画のようにユーザビリティを大きく損なうほどのラグが発生していました。 このラグは PC では発生せず、iPad でのみ発生していました。 この現象について原因を調査したところ、以下の 2 点が原因であることが分かりました。 原因 1. Retina ディスプレイによるピクセル密度の違い iPad は Retina ディスプレイを搭載しており、ピクセル密度が通常のディスプレイよりも高くなっていました。 検証に利用していた 11 インチ iPad Air (M2) では、PC と比較してピクセル密度が 4 倍にもなっていました。 原因 2. ピクセル密度に依存した消しゴム機能特有の処理 消しゴム機能は、ペン機能と比較して複雑な処理が行われていました。 ペンは 1 つのキャンバスに線を描くだけであるのに対して、消しゴムは複数のキャンバスを扱い、それらを合成する処理が必要でした。 具体的には、消しゴムの軌跡を一時的なキャンバスに描画し、それをメインキャンバスに合成するといった処理等が行われていました。 これは、ピクセル密度に依存しており、ピクセル密度が高い環境では負荷が高くなっていました。 // https://github.com/ShaMan123/erase2d/blob/7ee2b789b4c9161d662ea8c8aaf87af4c37bab13/packages/core/src/erase.ts#L15-L40 export const erase = ( destination : CanvasRenderingContext2D , // メインキャンバス source : CanvasRenderingContext2D , // 消しゴムの軌跡が描かれたキャンバス erasingEffect ?: CanvasRenderingContext2D // 消したくないものを保護するためのキャンバス ) => { // メインキャンバスから、消しゴムの軌跡部分を消去 drawImage ( destination , source , "destination-out" ); if ( erasingEffect ) { // 消しゴムの軌跡に、消したくないものがあれば、消さずに保護 drawImage ( source , erasingEffect , "source-in" ); } else { // 消しゴムの軌跡を描いたキャンバスを初期化 source . save (); source . resetTransform (); source . clearRect ( 0 , 0 , source . canvas . width , source . canvas . height ); source . restore (); } }; これらの原因によって、Retina ディスプレイの高解像度環境では消しゴムで線を消すときにラグが発生していました。 解決策として Canvas の初期化時に enableRetinaScaling オプションを false に設定し、Retina ディスプレイの高解像度環境においても同じピクセル密度で処理を行うようにしました。 const canvas = new fabric . Canvas ( canvasElement , { enableRetinaScaling: false , }); この設定により、一定の解像度を担保しつつ、iPad でも滑らかな消しゴム操作を実現できました。 工夫 3. iPad で手書きして保存した画像を PC に即時反映されるようにした iPad で手書きしながら PC でカルテを使うということを想定しているため、iPad で保存した手書き画像は PC に即時反映される必要があります。こちらを実現するために、以下が候補に挙げられました。 SSE(Server-Sent Events) WebSocket Firebase Realtime Database Firebase Realtime Database を既に使用しており、今回保存するのは更新時間のみで、保存容量が大幅に増えることもないため、今回は Firebase Realtime Database を使用することにしました。 以下が、Realtime Database で変更の監視及び通知を行う実装です。 import { initializeApp } from "firebase/app" ; import { getDatabase , off , onValue , ref , set } from "firebase/database" ; const app = initializeApp ( { /* 省略’*/ }, "firebaseapp" ); const db = () => { return getDatabase ( app ); }; const PATH = "medicalRecordChiefComplaintImages" ; // refを生成 const medicalRecordChiefComplaintImagesRef = ( medicalRecordId : string ) => { return ref ( db (), `/ ${ PATH } / ${ medicalRecordId } ` ); }; // 主訴所見の手書き画像が更新されたことを通知する export const updateMedicalRecordChiefComplaintImageIds = async ({ medicalRecordId , }: { medicalRecordId : string ; }) => { await set ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), Date . now ()); }; // 主訴所見の手書き画像の変更を監視する export const watchMedicalRecordChiefComplaintImages = ({ medicalRecordId , refreshMedicalRecordChiefComplaintImages , }: { medicalRecordId : string ; refreshMedicalRecordChiefComplaintImages : () => void ; }) => { onValue ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), () => { // Realtime Databaseが更新されたときに実行する処理 refreshMedicalRecordChiefComplaintImages (); }); }; // 主訴所見の手書き画像の変更の監視を解除する export const unwatchMedicalRecordChiefComplaintImages = ({ medicalRecordId , }: { medicalRecordId : string ; }) => { off ( medicalRecordChiefComplaintImagesRef ( medicalRecordId )); }; フロントエンドでは、データフェッチに TanStack Query を使用しています。以下のように Realtime Database によって手書き画像の更新を監視して refetch しています。 export const useMedicalChiefComplaintImagesWatchQuery = ({ medicalRecordId , initialChiefComplaintImages , }: UseMedicalChiefComplaintImagesWatchQueryProps ) => { const medicalChiefComplaintImagesQuery = useQuery ({ queryKey: medicalChiefComplaintImageKey . list ( medicalRecordId ), queryFn : () => query . get ( request . get ( medicalRecordId )), }); useEffect (() => { watchMedicalRecordChiefComplaintImages ({ medicalRecordId: medicalRecordId , // Realtime Databaseの更新時にrefetchする refreshMedicalRecordChiefComplaintImages: medicalChiefComplaintImagesQuery . refetch , }); return () => unwatchMedicalRecordChiefComplaintImages ({ medicalRecordId }); }, [ medicalRecordId , medicalChiefComplaintImagesQuery . refetch ]); return medicalChiefComplaintImagesQuery ; }; 最後に、iPad で手書き画像を更新したタイミングで updateMedicalRecordChiefComplaintImageIds を呼び出せば、以下のように iPad での変更が PC に即時に反映されるようになります。 工夫 4. 手書きツールバーを自由に配置できるようにした 手書き機能では太さや色の変更、ペンと消しゴムの切り替え、履歴管理ができるようにツールバーを実装しています。ツールバーは頻繁に使われるため、ユーザーが使用しやすい位置に移動できるようにしました。こちらは dnd-kit を使用して実装しました。 動画にあるようにツールバーが親要素からはみ出ないようになっています。こちらは restrictToParentElement を使用することで実現できます。 また、CSS ライブラリに emotion を使用しており、今回の Draggable 対応では動的にスタイルを変更する必要があるため、 style prop を使うように注意する必要があります( https://emotion.sh/docs/best-practices#use-the-style-prop-for-dynamic-styles )。 ライブラリの不具合 上記のようにユーザーが使いやすいように手書き機能を開発しましたが、消しゴムで線を消すと消えて欲しくない背景画像まで消えてしまうという不具合がライブラリに存在していました。こちらは同じ fabric を使用している Dentis のエンジニア大岡に相談したところ、ライブラリに PR を出して解消していただけました! fix(): backgroundImage/overlayImage erasable by yasupeke · Pull Request #49 · ShaMan123/erase2d I always appreciate the convenience of using this tool. I have fixed an issue where the erasable setting for backgroundImage/overlayImage was not being applied. Please review the changes. github.com まとめ 本記事では、Web でユーザビリティの高い手書きアプリケーションを実装するための工夫点について紹介しました。手書き機能の実装においては、実装速度とユーザビリティを両立するために適切なライブラリ選定が重要です。私達は fabric を採用することで滑らかな手書きアプリケーションを実現できました。 実装にあたり、以下の 4 つを工夫しました。 手を画面に置きながら書ける機能: 接触面積の大きさを検出してペン入力と区別することで、自然な書き心地を実現しました 消しゴム操作のラグ解消: Retina ディスプレイ対応を最適化し、高負荷な消しゴム処理でもスムーズな操作感を実現しました リアルタイム同期: Firebase Realtime Database を活用し、複数デバイス間での即時反映を実現しました 柔軟な UI: dnd-kit によるドラッグ可能なツールバーで、ユーザー体験を向上しました また、ライブラリの不具合に対してはプロダクトを横断したコミュニケーションやコミュニティと協力することで解決しました。これらの工夫により、医科診療所で快適に使用できる手書き機能を開発することができました。 今後もユーザビリティを重視しながら、より使いやすい機能の開発を続けていきます。 We’re hiring メドレーでは一緒に働く仲間を大募集しています! カジュアル面談も実施しておりますので、「お話だけでも聞いてみたい!」「ちょっと雑談してみたい!」でも構いませんので、お気軽にお問い合わせください! 募集の一覧 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp 医療エンジニアリング領域盛り上がっています!メドレーについてお話します! メドレーの人材プラットフォーム事業についてお話しします! 「メドレーの人材プラットフォーム事業についてお話しします!」- 倉林 昭和さん pitta.me メドレーの開発チームについて知りたい方!ぜひお話ししましょう! メドレーの開発チームについて知りたい方!ぜひお話ししましょう! 「メドレーの開発チームについて知りたい方!ぜひお話ししましょう!」- 德永 諒介さん pitta.me
はじめに こんにちは。医療プラットフォーム本部 CLINICS 開発グループの吉岡( @yuya333_ )と村上( @yuporonM )です。 吉岡は 2022 年に、村上は 2024 年に新卒でエンジニアとしてメドレーに入社しました。 私たちは、医科診療所向けの電子カルテである CLINICS カルテ を開発しています。CLINICS カルテは 2025 年 3 月に iPad 手書き機能をリリースしました。iPad 手書き機能とは、iPad を使って主訴・所見やシェーマ(身体の部位の絵)を手書きできる機能です。 主訴・所見の手書き シェーマの手書き 本記事では iPad 手書き機能を開発するにあたって工夫した点を紹介します。 ライブラリ選定 手書き機能を開発するにあたって、手書き処理すべてを実装するのは工数の観点から現実的ではありませんでした。 そのため、要件を満たせるかつメンテナンス面から以下のライブラリが候補となりました。 fabric konva メドレーでは、歯科診療所向けに電子カルテ Dentis を開発しており、Dentis では既に手書き機能を提供しています。そこで、手書き機能の実装に関して知見を持っている Dentis のエンジニアにライブラリ選定について相談したところ、以下の理由で fabric を採用することに決めました。 konva では線が荒くなってしまうことがある https://github.com/konvajs/konva/issues/1144 で解消方法も紹介されていましたが、細かく線を書きたいケースに対応できない konva では線を描くたびに再レンダリングが発生してしまう mousemove や touchevent のデータをキャッシュすることで解決できるが、遅延が気になる また、fabric を使用した手書き機能の実装方法に関しては、2024 年 12 月にメドレーのアドベントカレンダーで吉岡が書いた以下の記事を参考にしてみてください。 React+fabric.jsで作る手書きアプリ zenn.dev 手書き機能で工夫したポイント 4 選 工夫 1. 手を画面につけながらペンで手書きできるようにした 画面に手をつけながらペンで書いたときに、以下のように線が乱れてしまうことがありました。 画面に接触した手はペンや指で書いたときよりも接触面積が大きくなるので、接触部分が一定よりも大きくなった場合には線を書けないように実装しました。具体的には、 radiusX プロパティを使用して、radiusX がある値より大きいときには、 isDrawingMode が false になるようにしました。 canvas . on ( "mouse:down:before" , ( e ) => { if ( "TouchEvent" in window && e . e instanceof TouchEvent ) { const touch = e . e . touches . item ( 0 ); if (! touch ) { return ; } // 接触面積がペンや指よりも大きいときに線を書けなくする if ( touch . radiusX > 35 ) { canvas . isDrawingMode = false ; } } }); // 画面から手を離す度にisDrawingModeをリセット canvas . on ( "mouse:up:before" , () => { canvas . isDrawingMode = true ; }); 工夫 2. 消しゴムで線を消すときのラグを解消した 消しゴム機能は erase2d ライブラリを使って実装しました。 fabric v5 までは消しゴム機能が内包されていましたが、fabric v6 から erase2d に切り出されました。 erase2d を使って消しゴムを実装すると、次の動画のようにユーザビリティを大きく損なうほどのラグが発生していました。 このラグは PC では発生せず、iPad でのみ発生していました。 この現象について原因を調査したところ、以下の 2 点が原因であることが分かりました。 原因 1. Retina ディスプレイによるピクセル密度の違い iPad は Retina ディスプレイを搭載しており、ピクセル密度が通常のディスプレイよりも高くなっていました。 検証に利用していた 11 インチ iPad Air (M2) では、PC と比較してピクセル密度が 4 倍にもなっていました。 原因 2. ピクセル密度に依存した消しゴム機能特有の処理 消しゴム機能は、ペン機能と比較して複雑な処理が行われていました。 ペンは 1 つのキャンバスに線を描くだけであるのに対して、消しゴムは複数のキャンバスを扱い、それらを合成する処理が必要でした。 具体的には、消しゴムの軌跡を一時的なキャンバスに描画し、それをメインキャンバスに合成するといった処理等が行われていました。 これは、ピクセル密度に依存しており、ピクセル密度が高い環境では負荷が高くなっていました。 // https://github.com/ShaMan123/erase2d/blob/7ee2b789b4c9161d662ea8c8aaf87af4c37bab13/packages/core/src/erase.ts#L15-L40 export const erase = ( destination : CanvasRenderingContext2D , // メインキャンバス source : CanvasRenderingContext2D , // 消しゴムの軌跡が描かれたキャンバス erasingEffect ?: CanvasRenderingContext2D // 消したくないものを保護するためのキャンバス ) => { // メインキャンバスから、消しゴムの軌跡部分を消去 drawImage ( destination , source , "destination-out" ); if ( erasingEffect ) { // 消しゴムの軌跡に、消したくないものがあれば、消さずに保護 drawImage ( source , erasingEffect , "source-in" ); } else { // 消しゴムの軌跡を描いたキャンバスを初期化 source . save (); source . resetTransform (); source . clearRect ( 0 , 0 , source . canvas . width , source . canvas . height ); source . restore (); } }; これらの原因によって、Retina ディスプレイの高解像度環境では消しゴムで線を消すときにラグが発生していました。 解決策として Canvas の初期化時に enableRetinaScaling オプションを false に設定し、Retina ディスプレイの高解像度環境においても同じピクセル密度で処理を行うようにしました。 const canvas = new fabric . Canvas ( canvasElement , { enableRetinaScaling: false , }); この設定により、一定の解像度を担保しつつ、iPad でも滑らかな消しゴム操作を実現できました。 工夫 3. iPad で手書きして保存した画像を PC に即時反映されるようにした iPad で手書きしながら PC でカルテを使うということを想定しているため、iPad で保存した手書き画像は PC に即時反映される必要があります。こちらを実現するために、以下が候補に挙げられました。 SSE(Server-Sent Events) WebSocket Firebase Realtime Database Firebase Realtime Database を既に使用しており、今回保存するのは更新時間のみで、保存容量が大幅に増えることもないため、今回は Firebase Realtime Database を使用することにしました。 以下が、Realtime Database で変更の監視及び通知を行う実装です。 import { initializeApp } from "firebase/app" ; import { getDatabase , off , onValue , ref , set } from "firebase/database" ; const app = initializeApp ( { /* 省略’*/ }, "firebaseapp" ); const db = () => { return getDatabase ( app ); }; const PATH = "medicalRecordChiefComplaintImages" ; // refを生成 const medicalRecordChiefComplaintImagesRef = ( medicalRecordId : string ) => { return ref ( db (), `/ ${ PATH } / ${ medicalRecordId } ` ); }; // 主訴所見の手書き画像が更新されたことを通知する export const updateMedicalRecordChiefComplaintImageIds = async ({ medicalRecordId , }: { medicalRecordId : string ; }) => { await set ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), Date . now ()); }; // 主訴所見の手書き画像の変更を監視する export const watchMedicalRecordChiefComplaintImages = ({ medicalRecordId , refreshMedicalRecordChiefComplaintImages , }: { medicalRecordId : string ; refreshMedicalRecordChiefComplaintImages : () => void ; }) => { onValue ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), () => { // Realtime Databaseが更新されたときに実行する処理 refreshMedicalRecordChiefComplaintImages (); }); }; // 主訴所見の手書き画像の変更の監視を解除する export const unwatchMedicalRecordChiefComplaintImages = ({ medicalRecordId , }: { medicalRecordId : string ; }) => { off ( medicalRecordChiefComplaintImagesRef ( medicalRecordId )); }; フロントエンドでは、データフェッチに TanStack Query を使用しています。以下のように Realtime Database によって手書き画像の更新を監視して refetch しています。 export const useMedicalChiefComplaintImagesWatchQuery = ({ medicalRecordId , initialChiefComplaintImages , }: UseMedicalChiefComplaintImagesWatchQueryProps ) => { const medicalChiefComplaintImagesQuery = useQuery ({ queryKey: medicalChiefComplaintImageKey . list ( medicalRecordId ), queryFn : () => query . get ( request . get ( medicalRecordId )), }); useEffect (() => { watchMedicalRecordChiefComplaintImages ({ medicalRecordId: medicalRecordId , // Realtime Databaseの更新時にrefetchする refreshMedicalRecordChiefComplaintImages: medicalChiefComplaintImagesQuery . refetch , }); return () => unwatchMedicalRecordChiefComplaintImages ({ medicalRecordId }); }, [ medicalRecordId , medicalChiefComplaintImagesQuery . refetch ]); return medicalChiefComplaintImagesQuery ; }; 最後に、iPad で手書き画像を更新したタイミングで updateMedicalRecordChiefComplaintImageIds を呼び出せば、以下のように iPad での変更が PC に即時に反映されるようになります。 工夫 4. 手書きツールバーを自由に配置できるようにした 手書き機能では太さや色の変更、ペンと消しゴムの切り替え、履歴管理ができるようにツールバーを実装しています。ツールバーは頻繁に使われるため、ユーザーが使用しやすい位置に移動できるようにしました。こちらは dnd-kit を使用して実装しました。 動画にあるようにツールバーが親要素からはみ出ないようになっています。こちらは restrictToParentElement を使用することで実現できます。 また、CSS ライブラリに emotion を使用しており、今回の Draggable 対応では動的にスタイルを変更する必要があるため、 style prop を使うように注意する必要があります( https://emotion.sh/docs/best-practices#use-the-style-prop-for-dynamic-styles )。 ライブラリの不具合 上記のようにユーザーが使いやすいように手書き機能を開発しましたが、消しゴムで線を消すと消えて欲しくない背景画像まで消えてしまうという不具合がライブラリに存在していました。こちらは同じ fabric を使用している Dentis のエンジニア大岡に相談したところ、ライブラリに PR を出して解消していただけました! fix(): backgroundImage/overlayImage erasable by yasupeke · Pull Request #49 · ShaMan123/erase2d I always appreciate the convenience of using this tool. I have fixed an issue where the erasable setting for backgroundImage/overlayImage was not being applied. Please review the changes. github.com まとめ 本記事では、Web でユーザビリティの高い手書きアプリケーションを実装するための工夫点について紹介しました。手書き機能の実装においては、実装速度とユーザビリティを両立するために適切なライブラリ選定が重要です。私達は fabric を採用することで滑らかな手書きアプリケーションを実現できました。 実装にあたり、以下の 4 つを工夫しました。 手を画面に置きながら書ける機能: 接触面積の大きさを検出してペン入力と区別することで、自然な書き心地を実現しました 消しゴム操作のラグ解消: Retina ディスプレイ対応を最適化し、高負荷な消しゴム処理でもスムーズな操作感を実現しました リアルタイム同期: Firebase Realtime Database を活用し、複数デバイス間での即時反映を実現しました 柔軟な UI: dnd-kit によるドラッグ可能なツールバーで、ユーザー体験を向上しました また、ライブラリの不具合に対してはプロダクトを横断したコミュニケーションやコミュニティと協力することで解決しました。これらの工夫により、医科診療所で快適に使用できる手書き機能を開発することができました。 今後もユーザビリティを重視しながら、より使いやすい機能の開発を続けていきます。 We’re hiring メドレーでは一緒に働く仲間を大募集しています! カジュアル面談も実施しておりますので、「お話だけでも聞いてみたい!」「ちょっと雑談してみたい!」でも構いませんので、お気軽にお問い合わせください! 募集の一覧 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp 医療エンジニアリング領域盛り上がっています!メドレーについてお話します! メドレーの人材プラットフォーム事業についてお話しします! 「メドレーの人材プラットフォーム事業についてお話しします!」- 倉林 昭和さん pitta.me メドレーの開発チームについて知りたい方!ぜひお話ししましょう! メドレーの開発チームについて知りたい方!ぜひお話ししましょう! 「メドレーの開発チームについて知りたい方!ぜひお話ししましょう!」- 德永 諒介さん pitta.me
はじめに こんにちは。医療プラットフォーム本部 CLINICS 開発グループの吉岡( @yuya333_ )と村上( @yuporonM )です。 吉岡は 2022 年に、村上は 2024 年に新卒でエンジニアとしてメドレーに入社しました。 私たちは、医科診療所向けの電子カルテである CLINICS カルテ を開発しています。CLINICS カルテは 2025 年 3 月に iPad 手書き機能をリリースしました。iPad 手書き機能とは、iPad を使って主訴・所見やシェーマ(身体の部位の絵)を手書きできる機能です。 主訴・所見の手書き シェーマの手書き 本記事では iPad 手書き機能を開発するにあたって工夫した点を紹介します。 ライブラリ選定 手書き機能を開発するにあたって、手書き処理すべてを実装するのは工数の観点から現実的ではありませんでした。 そのため、要件を満たせるかつメンテナンス面から以下のライブラリが候補となりました。 fabric konva メドレーでは、歯科診療所向けに電子カルテ Dentis を開発しており、Dentis では既に手書き機能を提供しています。そこで、手書き機能の実装に関して知見を持っている Dentis のエンジニアにライブラリ選定について相談したところ、以下の理由で fabric を採用することに決めました。 konva では線が荒くなってしまうことがある https://github.com/konvajs/konva/issues/1144 で解消方法も紹介されていましたが、細かく線を書きたいケースに対応できない konva では線を描くたびに再レンダリングが発生してしまう mousemove や touchevent のデータをキャッシュすることで解決できるが、遅延が気になる また、fabric を使用した手書き機能の実装方法に関しては、2024 年 12 月にメドレーのアドベントカレンダーで吉岡が書いた以下の記事を参考にしてみてください。 React+fabric.jsで作る手書きアプリ zenn.dev 手書き機能で工夫したポイント 4 選 工夫 1. 手を画面につけながらペンで手書きできるようにした 画面に手をつけながらペンで書いたときに、以下のように線が乱れてしまうことがありました。 画面に接触した手はペンや指で書いたときよりも接触面積が大きくなるので、接触部分が一定よりも大きくなった場合には線を書けないように実装しました。具体的には、 radiusX プロパティを使用して、radiusX がある値より大きいときには、 isDrawingMode が false になるようにしました。 canvas . on ( "mouse:down:before" , ( e ) => { if ( "TouchEvent" in window && e . e instanceof TouchEvent ) { const touch = e . e . touches . item ( 0 ); if (! touch ) { return ; } // 接触面積がペンや指よりも大きいときに線を書けなくする if ( touch . radiusX > 35 ) { canvas . isDrawingMode = false ; } } }); // 画面から手を離す度にisDrawingModeをリセット canvas . on ( "mouse:up:before" , () => { canvas . isDrawingMode = true ; }); 工夫 2. 消しゴムで線を消すときのラグを解消した 消しゴム機能は erase2d ライブラリを使って実装しました。 fabric v5 までは消しゴム機能が内包されていましたが、fabric v6 から erase2d に切り出されました。 erase2d を使って消しゴムを実装すると、次の動画のようにユーザビリティを大きく損なうほどのラグが発生していました。 このラグは PC では発生せず、iPad でのみ発生していました。 この現象について原因を調査したところ、以下の 2 点が原因であることが分かりました。 原因 1. Retina ディスプレイによるピクセル密度の違い iPad は Retina ディスプレイを搭載しており、ピクセル密度が通常のディスプレイよりも高くなっていました。 検証に利用していた 11 インチ iPad Air (M2) では、PC と比較してピクセル密度が 4 倍にもなっていました。 原因 2. ピクセル密度に依存した消しゴム機能特有の処理 消しゴム機能は、ペン機能と比較して複雑な処理が行われていました。 ペンは 1 つのキャンバスに線を描くだけであるのに対して、消しゴムは複数のキャンバスを扱い、それらを合成する処理が必要でした。 具体的には、消しゴムの軌跡を一時的なキャンバスに描画し、それをメインキャンバスに合成するといった処理等が行われていました。 これは、ピクセル密度に依存しており、ピクセル密度が高い環境では負荷が高くなっていました。 // https://github.com/ShaMan123/erase2d/blob/7ee2b789b4c9161d662ea8c8aaf87af4c37bab13/packages/core/src/erase.ts#L15-L40 export const erase = ( destination : CanvasRenderingContext2D , // メインキャンバス source : CanvasRenderingContext2D , // 消しゴムの軌跡が描かれたキャンバス erasingEffect ?: CanvasRenderingContext2D // 消したくないものを保護するためのキャンバス ) => { // メインキャンバスから、消しゴムの軌跡部分を消去 drawImage ( destination , source , "destination-out" ); if ( erasingEffect ) { // 消しゴムの軌跡に、消したくないものがあれば、消さずに保護 drawImage ( source , erasingEffect , "source-in" ); } else { // 消しゴムの軌跡を描いたキャンバスを初期化 source . save (); source . resetTransform (); source . clearRect ( 0 , 0 , source . canvas . width , source . canvas . height ); source . restore (); } }; これらの原因によって、Retina ディスプレイの高解像度環境では消しゴムで線を消すときにラグが発生していました。 解決策として Canvas の初期化時に enableRetinaScaling オプションを false に設定し、Retina ディスプレイの高解像度環境においても同じピクセル密度で処理を行うようにしました。 const canvas = new fabric . Canvas ( canvasElement , { enableRetinaScaling: false , }); この設定により、一定の解像度を担保しつつ、iPad でも滑らかな消しゴム操作を実現できました。 工夫 3. iPad で手書きして保存した画像を PC に即時反映されるようにした iPad で手書きしながら PC でカルテを使うということを想定しているため、iPad で保存した手書き画像は PC に即時反映される必要があります。こちらを実現するために、以下が候補に挙げられました。 SSE(Server-Sent Events) WebSocket Firebase Realtime Database Firebase Realtime Database を既に使用しており、今回保存するのは更新時間のみで、保存容量が大幅に増えることもないため、今回は Firebase Realtime Database を使用することにしました。 以下が、Realtime Database で変更の監視及び通知を行う実装です。 import { initializeApp } from "firebase/app" ; import { getDatabase , off , onValue , ref , set } from "firebase/database" ; const app = initializeApp ( { /* 省略’*/ }, "firebaseapp" ); const db = () => { return getDatabase ( app ); }; const PATH = "medicalRecordChiefComplaintImages" ; // refを生成 const medicalRecordChiefComplaintImagesRef = ( medicalRecordId : string ) => { return ref ( db (), `/ ${ PATH } / ${ medicalRecordId } ` ); }; // 主訴所見の手書き画像が更新されたことを通知する export const updateMedicalRecordChiefComplaintImageIds = async ({ medicalRecordId , }: { medicalRecordId : string ; }) => { await set ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), Date . now ()); }; // 主訴所見の手書き画像の変更を監視する export const watchMedicalRecordChiefComplaintImages = ({ medicalRecordId , refreshMedicalRecordChiefComplaintImages , }: { medicalRecordId : string ; refreshMedicalRecordChiefComplaintImages : () => void ; }) => { onValue ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), () => { // Realtime Databaseが更新されたときに実行する処理 refreshMedicalRecordChiefComplaintImages (); }); }; // 主訴所見の手書き画像の変更の監視を解除する export const unwatchMedicalRecordChiefComplaintImages = ({ medicalRecordId , }: { medicalRecordId : string ; }) => { off ( medicalRecordChiefComplaintImagesRef ( medicalRecordId )); }; フロントエンドでは、データフェッチに TanStack Query を使用しています。以下のように Realtime Database によって手書き画像の更新を監視して refetch しています。 export const useMedicalChiefComplaintImagesWatchQuery = ({ medicalRecordId , initialChiefComplaintImages , }: UseMedicalChiefComplaintImagesWatchQueryProps ) => { const medicalChiefComplaintImagesQuery = useQuery ({ queryKey: medicalChiefComplaintImageKey . list ( medicalRecordId ), queryFn : () => query . get ( request . get ( medicalRecordId )), }); useEffect (() => { watchMedicalRecordChiefComplaintImages ({ medicalRecordId: medicalRecordId , // Realtime Databaseの更新時にrefetchする refreshMedicalRecordChiefComplaintImages: medicalChiefComplaintImagesQuery . refetch , }); return () => unwatchMedicalRecordChiefComplaintImages ({ medicalRecordId }); }, [ medicalRecordId , medicalChiefComplaintImagesQuery . refetch ]); return medicalChiefComplaintImagesQuery ; }; 最後に、iPad で手書き画像を更新したタイミングで updateMedicalRecordChiefComplaintImageIds を呼び出せば、以下のように iPad での変更が PC に即時に反映されるようになります。 工夫 4. 手書きツールバーを自由に配置できるようにした 手書き機能では太さや色の変更、ペンと消しゴムの切り替え、履歴管理ができるようにツールバーを実装しています。ツールバーは頻繁に使われるため、ユーザーが使用しやすい位置に移動できるようにしました。こちらは dnd-kit を使用して実装しました。 動画にあるようにツールバーが親要素からはみ出ないようになっています。こちらは restrictToParentElement を使用することで実現できます。 また、CSS ライブラリに emotion を使用しており、今回の Draggable 対応では動的にスタイルを変更する必要があるため、 style prop を使うように注意する必要があります( https://emotion.sh/docs/best-practices#use-the-style-prop-for-dynamic-styles )。 ライブラリの不具合 上記のようにユーザーが使いやすいように手書き機能を開発しましたが、消しゴムで線を消すと消えて欲しくない背景画像まで消えてしまうという不具合がライブラリに存在していました。こちらは同じ fabric を使用している Dentis のエンジニア大岡に相談したところ、ライブラリに PR を出して解消していただけました! fix(): backgroundImage/overlayImage erasable by yasupeke · Pull Request #49 · ShaMan123/erase2d I always appreciate the convenience of using this tool. I have fixed an issue where the erasable setting for backgroundImage/overlayImage was not being applied. Please review the changes. github.com まとめ 本記事では、Web でユーザビリティの高い手書きアプリケーションを実装するための工夫点について紹介しました。手書き機能の実装においては、実装速度とユーザビリティを両立するために適切なライブラリ選定が重要です。私達は fabric を採用することで滑らかな手書きアプリケーションを実現できました。 実装にあたり、以下の 4 つを工夫しました。 手を画面に置きながら書ける機能: 接触面積の大きさを検出してペン入力と区別することで、自然な書き心地を実現しました 消しゴム操作のラグ解消: Retina ディスプレイ対応を最適化し、高負荷な消しゴム処理でもスムーズな操作感を実現しました リアルタイム同期: Firebase Realtime Database を活用し、複数デバイス間での即時反映を実現しました 柔軟な UI: dnd-kit によるドラッグ可能なツールバーで、ユーザー体験を向上しました また、ライブラリの不具合に対してはプロダクトを横断したコミュニケーションやコミュニティと協力することで解決しました。これらの工夫により、医科診療所で快適に使用できる手書き機能を開発することができました。 今後もユーザビリティを重視しながら、より使いやすい機能の開発を続けていきます。 We’re hiring メドレーでは一緒に働く仲間を大募集しています! カジュアル面談も実施しておりますので、「お話だけでも聞いてみたい!」「ちょっと雑談してみたい!」でも構いませんので、お気軽にお問い合わせください! 募集の一覧 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp 医療エンジニアリング領域盛り上がっています!メドレーについてお話します! メドレーの人材プラットフォーム事業についてお話しします! 「メドレーの人材プラットフォーム事業についてお話しします!」- 倉林 昭和さん pitta.me メドレーの開発チームについて知りたい方!ぜひお話ししましょう! メドレーの開発チームについて知りたい方!ぜひお話ししましょう! 「メドレーの開発チームについて知りたい方!ぜひお話ししましょう!」- 德永 諒介さん pitta.me
はじめに こんにちは。医療プラットフォーム本部 CLINICS 開発グループの吉岡( @yuya333_ )と村上( @yuporonM )です。 吉岡は 2022 年に、村上は 2024 年に新卒でエンジニアとしてメドレーに入社しました。 私たちは、医科診療所向けの電子カルテである CLINICS カルテ を開発しています。CLINICS カルテは 2025 年 3 月に iPad 手書き機能をリリースしました。iPad 手書き機能とは、iPad を使って主訴・所見やシェーマ(身体の部位の絵)を手書きできる機能です。 主訴・所見の手書き シェーマの手書き 本記事では iPad 手書き機能を開発するにあたって工夫した点を紹介します。 ライブラリ選定 手書き機能を開発するにあたって、手書き処理すべてを実装するのは工数の観点から現実的ではありませんでした。 そのため、要件を満たせるかつメンテナンス面から以下のライブラリが候補となりました。 fabric konva メドレーでは、歯科診療所向けに電子カルテ Dentis を開発しており、Dentis では既に手書き機能を提供しています。そこで、手書き機能の実装に関して知見を持っている Dentis のエンジニアにライブラリ選定について相談したところ、以下の理由で fabric を採用することに決めました。 konva では線が荒くなってしまうことがある https://github.com/konvajs/konva/issues/1144 で解消方法も紹介されていましたが、細かく線を書きたいケースに対応できない konva では線を描くたびに再レンダリングが発生してしまう mousemove や touchevent のデータをキャッシュすることで解決できるが、遅延が気になる また、fabric を使用した手書き機能の実装方法に関しては、2024 年 12 月にメドレーのアドベントカレンダーで吉岡が書いた以下の記事を参考にしてみてください。 React+fabric.jsで作る手書きアプリ zenn.dev 手書き機能で工夫したポイント 4 選 工夫 1. 手を画面につけながらペンで手書きできるようにした 画面に手をつけながらペンで書いたときに、以下のように線が乱れてしまうことがありました。 画面に接触した手はペンや指で書いたときよりも接触面積が大きくなるので、接触部分が一定よりも大きくなった場合には線を書けないように実装しました。具体的には、 radiusX プロパティを使用して、radiusX がある値より大きいときには、 isDrawingMode が false になるようにしました。 canvas . on ( "mouse:down:before" , ( e ) => { if ( "TouchEvent" in window && e . e instanceof TouchEvent ) { const touch = e . e . touches . item ( 0 ); if (! touch ) { return ; } // 接触面積がペンや指よりも大きいときに線を書けなくする if ( touch . radiusX > 35 ) { canvas . isDrawingMode = false ; } } }); // 画面から手を離す度にisDrawingModeをリセット canvas . on ( "mouse:up:before" , () => { canvas . isDrawingMode = true ; }); 工夫 2. 消しゴムで線を消すときのラグを解消した 消しゴム機能は erase2d ライブラリを使って実装しました。 fabric v5 までは消しゴム機能が内包されていましたが、fabric v6 から erase2d に切り出されました。 erase2d を使って消しゴムを実装すると、次の動画のようにユーザビリティを大きく損なうほどのラグが発生していました。 このラグは PC では発生せず、iPad でのみ発生していました。 この現象について原因を調査したところ、以下の 2 点が原因であることが分かりました。 原因 1. Retina ディスプレイによるピクセル密度の違い iPad は Retina ディスプレイを搭載しており、ピクセル密度が通常のディスプレイよりも高くなっていました。 検証に利用していた 11 インチ iPad Air (M2) では、PC と比較してピクセル密度が 4 倍にもなっていました。 原因 2. ピクセル密度に依存した消しゴム機能特有の処理 消しゴム機能は、ペン機能と比較して複雑な処理が行われていました。 ペンは 1 つのキャンバスに線を描くだけであるのに対して、消しゴムは複数のキャンバスを扱い、それらを合成する処理が必要でした。 具体的には、消しゴムの軌跡を一時的なキャンバスに描画し、それをメインキャンバスに合成するといった処理等が行われていました。 これは、ピクセル密度に依存しており、ピクセル密度が高い環境では負荷が高くなっていました。 // https://github.com/ShaMan123/erase2d/blob/7ee2b789b4c9161d662ea8c8aaf87af4c37bab13/packages/core/src/erase.ts#L15-L40 export const erase = ( destination : CanvasRenderingContext2D , // メインキャンバス source : CanvasRenderingContext2D , // 消しゴムの軌跡が描かれたキャンバス erasingEffect ?: CanvasRenderingContext2D // 消したくないものを保護するためのキャンバス ) => { // メインキャンバスから、消しゴムの軌跡部分を消去 drawImage ( destination , source , "destination-out" ); if ( erasingEffect ) { // 消しゴムの軌跡に、消したくないものがあれば、消さずに保護 drawImage ( source , erasingEffect , "source-in" ); } else { // 消しゴムの軌跡を描いたキャンバスを初期化 source . save (); source . resetTransform (); source . clearRect ( 0 , 0 , source . canvas . width , source . canvas . height ); source . restore (); } }; これらの原因によって、Retina ディスプレイの高解像度環境では消しゴムで線を消すときにラグが発生していました。 解決策として Canvas の初期化時に enableRetinaScaling オプションを false に設定し、Retina ディスプレイの高解像度環境においても同じピクセル密度で処理を行うようにしました。 const canvas = new fabric . Canvas ( canvasElement , { enableRetinaScaling: false , }); この設定により、一定の解像度を担保しつつ、iPad でも滑らかな消しゴム操作を実現できました。 工夫 3. iPad で手書きして保存した画像を PC に即時反映されるようにした iPad で手書きしながら PC でカルテを使うということを想定しているため、iPad で保存した手書き画像は PC に即時反映される必要があります。こちらを実現するために、以下が候補に挙げられました。 SSE(Server-Sent Events) WebSocket Firebase Realtime Database Firebase Realtime Database を既に使用しており、今回保存するのは更新時間のみで、保存容量が大幅に増えることもないため、今回は Firebase Realtime Database を使用することにしました。 以下が、Realtime Database で変更の監視及び通知を行う実装です。 import { initializeApp } from "firebase/app" ; import { getDatabase , off , onValue , ref , set } from "firebase/database" ; const app = initializeApp ( { /* 省略’*/ }, "firebaseapp" ); const db = () => { return getDatabase ( app ); }; const PATH = "medicalRecordChiefComplaintImages" ; // refを生成 const medicalRecordChiefComplaintImagesRef = ( medicalRecordId : string ) => { return ref ( db (), `/ ${ PATH } / ${ medicalRecordId } ` ); }; // 主訴所見の手書き画像が更新されたことを通知する export const updateMedicalRecordChiefComplaintImageIds = async ({ medicalRecordId , }: { medicalRecordId : string ; }) => { await set ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), Date . now ()); }; // 主訴所見の手書き画像の変更を監視する export const watchMedicalRecordChiefComplaintImages = ({ medicalRecordId , refreshMedicalRecordChiefComplaintImages , }: { medicalRecordId : string ; refreshMedicalRecordChiefComplaintImages : () => void ; }) => { onValue ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), () => { // Realtime Databaseが更新されたときに実行する処理 refreshMedicalRecordChiefComplaintImages (); }); }; // 主訴所見の手書き画像の変更の監視を解除する export const unwatchMedicalRecordChiefComplaintImages = ({ medicalRecordId , }: { medicalRecordId : string ; }) => { off ( medicalRecordChiefComplaintImagesRef ( medicalRecordId )); }; フロントエンドでは、データフェッチに TanStack Query を使用しています。以下のように Realtime Database によって手書き画像の更新を監視して refetch しています。 export const useMedicalChiefComplaintImagesWatchQuery = ({ medicalRecordId , initialChiefComplaintImages , }: UseMedicalChiefComplaintImagesWatchQueryProps ) => { const medicalChiefComplaintImagesQuery = useQuery ({ queryKey: medicalChiefComplaintImageKey . list ( medicalRecordId ), queryFn : () => query . get ( request . get ( medicalRecordId )), }); useEffect (() => { watchMedicalRecordChiefComplaintImages ({ medicalRecordId: medicalRecordId , // Realtime Databaseの更新時にrefetchする refreshMedicalRecordChiefComplaintImages: medicalChiefComplaintImagesQuery . refetch , }); return () => unwatchMedicalRecordChiefComplaintImages ({ medicalRecordId }); }, [ medicalRecordId , medicalChiefComplaintImagesQuery . refetch ]); return medicalChiefComplaintImagesQuery ; }; 最後に、iPad で手書き画像を更新したタイミングで updateMedicalRecordChiefComplaintImageIds を呼び出せば、以下のように iPad での変更が PC に即時に反映されるようになります。 工夫 4. 手書きツールバーを自由に配置できるようにした 手書き機能では太さや色の変更、ペンと消しゴムの切り替え、履歴管理ができるようにツールバーを実装しています。ツールバーは頻繁に使われるため、ユーザーが使用しやすい位置に移動できるようにしました。こちらは dnd-kit を使用して実装しました。 動画にあるようにツールバーが親要素からはみ出ないようになっています。こちらは restrictToParentElement を使用することで実現できます。 また、CSS ライブラリに emotion を使用しており、今回の Draggable 対応では動的にスタイルを変更する必要があるため、 style prop を使うように注意する必要があります( https://emotion.sh/docs/best-practices#use-the-style-prop-for-dynamic-styles )。 ライブラリの不具合 上記のようにユーザーが使いやすいように手書き機能を開発しましたが、消しゴムで線を消すと消えて欲しくない背景画像まで消えてしまうという不具合がライブラリに存在していました。こちらは同じ fabric を使用している Dentis のエンジニア大岡に相談したところ、ライブラリに PR を出して解消していただけました! fix(): backgroundImage/overlayImage erasable by yasupeke · Pull Request #49 · ShaMan123/erase2d I always appreciate the convenience of using this tool. I have fixed an issue where the erasable setting for backgroundImage/overlayImage was not being applied. Please review the changes. github.com まとめ 本記事では、Web でユーザビリティの高い手書きアプリケーションを実装するための工夫点について紹介しました。手書き機能の実装においては、実装速度とユーザビリティを両立するために適切なライブラリ選定が重要です。私達は fabric を採用することで滑らかな手書きアプリケーションを実現できました。 実装にあたり、以下の 4 つを工夫しました。 手を画面に置きながら書ける機能: 接触面積の大きさを検出してペン入力と区別することで、自然な書き心地を実現しました 消しゴム操作のラグ解消: Retina ディスプレイ対応を最適化し、高負荷な消しゴム処理でもスムーズな操作感を実現しました リアルタイム同期: Firebase Realtime Database を活用し、複数デバイス間での即時反映を実現しました 柔軟な UI: dnd-kit によるドラッグ可能なツールバーで、ユーザー体験を向上しました また、ライブラリの不具合に対してはプロダクトを横断したコミュニケーションやコミュニティと協力することで解決しました。これらの工夫により、医科診療所で快適に使用できる手書き機能を開発することができました。 今後もユーザビリティを重視しながら、より使いやすい機能の開発を続けていきます。 We’re hiring メドレーでは一緒に働く仲間を大募集しています! カジュアル面談も実施しておりますので、「お話だけでも聞いてみたい!」「ちょっと雑談してみたい!」でも構いませんので、お気軽にお問い合わせください! 募集の一覧 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp 医療エンジニアリング領域盛り上がっています!メドレーについてお話します! メドレーの人材プラットフォーム事業についてお話しします! 「メドレーの人材プラットフォーム事業についてお話しします!」- 倉林 昭和さん pitta.me メドレーの開発チームについて知りたい方!ぜひお話ししましょう! メドレーの開発チームについて知りたい方!ぜひお話ししましょう! 「メドレーの開発チームについて知りたい方!ぜひお話ししましょう!」- 德永 諒介さん pitta.me
はじめに こんにちは。医療プラットフォーム本部 CLINICS 開発グループの吉岡( @yuya333_ )と村上( @yuporonM )です。 吉岡は 2022 年に、村上は 2024 年に新卒でエンジニアとしてメドレーに入社しました。 私たちは、医科診療所向けの電子カルテである CLINICS カルテ を開発しています。CLINICS カルテは 2025 年 3 月に iPad 手書き機能をリリースしました。iPad 手書き機能とは、iPad を使って主訴・所見やシェーマ(身体の部位の絵)を手書きできる機能です。 主訴・所見の手書き シェーマの手書き 本記事では iPad 手書き機能を開発するにあたって工夫した点を紹介します。 ライブラリ選定 手書き機能を開発するにあたって、手書き処理すべてを実装するのは工数の観点から現実的ではありませんでした。 そのため、要件を満たせるかつメンテナンス面から以下のライブラリが候補となりました。 fabric konva メドレーでは、歯科診療所向けに電子カルテ Dentis を開発しており、Dentis では既に手書き機能を提供しています。そこで、手書き機能の実装に関して知見を持っている Dentis のエンジニアにライブラリ選定について相談したところ、以下の理由で fabric を採用することに決めました。 konva では線が荒くなってしまうことがある https://github.com/konvajs/konva/issues/1144 で解消方法も紹介されていましたが、細かく線を書きたいケースに対応できない konva では線を描くたびに再レンダリングが発生してしまう mousemove や touchevent のデータをキャッシュすることで解決できるが、遅延が気になる また、fabric を使用した手書き機能の実装方法に関しては、2024 年 12 月にメドレーのアドベントカレンダーで吉岡が書いた以下の記事を参考にしてみてください。 React+fabric.jsで作る手書きアプリ zenn.dev 手書き機能で工夫したポイント 4 選 工夫 1. 手を画面につけながらペンで手書きできるようにした 画面に手をつけながらペンで書いたときに、以下のように線が乱れてしまうことがありました。 画面に接触した手はペンや指で書いたときよりも接触面積が大きくなるので、接触部分が一定よりも大きくなった場合には線を書けないように実装しました。具体的には、 radiusX プロパティを使用して、radiusX がある値より大きいときには、 isDrawingMode が false になるようにしました。 canvas . on ( "mouse:down:before" , ( e ) => { if ( "TouchEvent" in window && e . e instanceof TouchEvent ) { const touch = e . e . touches . item ( 0 ); if (! touch ) { return ; } // 接触面積がペンや指よりも大きいときに線を書けなくする if ( touch . radiusX > 35 ) { canvas . isDrawingMode = false ; } } }); // 画面から手を離す度にisDrawingModeをリセット canvas . on ( "mouse:up:before" , () => { canvas . isDrawingMode = true ; }); 工夫 2. 消しゴムで線を消すときのラグを解消した 消しゴム機能は erase2d ライブラリを使って実装しました。 fabric v5 までは消しゴム機能が内包されていましたが、fabric v6 から erase2d に切り出されました。 erase2d を使って消しゴムを実装すると、次の動画のようにユーザビリティを大きく損なうほどのラグが発生していました。 このラグは PC では発生せず、iPad でのみ発生していました。 この現象について原因を調査したところ、以下の 2 点が原因であることが分かりました。 原因 1. Retina ディスプレイによるピクセル密度の違い iPad は Retina ディスプレイを搭載しており、ピクセル密度が通常のディスプレイよりも高くなっていました。 検証に利用していた 11 インチ iPad Air (M2) では、PC と比較してピクセル密度が 4 倍にもなっていました。 原因 2. ピクセル密度に依存した消しゴム機能特有の処理 消しゴム機能は、ペン機能と比較して複雑な処理が行われていました。 ペンは 1 つのキャンバスに線を描くだけであるのに対して、消しゴムは複数のキャンバスを扱い、それらを合成する処理が必要でした。 具体的には、消しゴムの軌跡を一時的なキャンバスに描画し、それをメインキャンバスに合成するといった処理等が行われていました。 これは、ピクセル密度に依存しており、ピクセル密度が高い環境では負荷が高くなっていました。 // https://github.com/ShaMan123/erase2d/blob/7ee2b789b4c9161d662ea8c8aaf87af4c37bab13/packages/core/src/erase.ts#L15-L40 export const erase = ( destination : CanvasRenderingContext2D , // メインキャンバス source : CanvasRenderingContext2D , // 消しゴムの軌跡が描かれたキャンバス erasingEffect ?: CanvasRenderingContext2D // 消したくないものを保護するためのキャンバス ) => { // メインキャンバスから、消しゴムの軌跡部分を消去 drawImage ( destination , source , "destination-out" ); if ( erasingEffect ) { // 消しゴムの軌跡に、消したくないものがあれば、消さずに保護 drawImage ( source , erasingEffect , "source-in" ); } else { // 消しゴムの軌跡を描いたキャンバスを初期化 source . save (); source . resetTransform (); source . clearRect ( 0 , 0 , source . canvas . width , source . canvas . height ); source . restore (); } }; これらの原因によって、Retina ディスプレイの高解像度環境では消しゴムで線を消すときにラグが発生していました。 解決策として Canvas の初期化時に enableRetinaScaling オプションを false に設定し、Retina ディスプレイの高解像度環境においても同じピクセル密度で処理を行うようにしました。 const canvas = new fabric . Canvas ( canvasElement , { enableRetinaScaling: false , }); この設定により、一定の解像度を担保しつつ、iPad でも滑らかな消しゴム操作を実現できました。 工夫 3. iPad で手書きして保存した画像を PC に即時反映されるようにした iPad で手書きしながら PC でカルテを使うということを想定しているため、iPad で保存した手書き画像は PC に即時反映される必要があります。こちらを実現するために、以下が候補に挙げられました。 SSE(Server-Sent Events) WebSocket Firebase Realtime Database Firebase Realtime Database を既に使用しており、今回保存するのは更新時間のみで、保存容量が大幅に増えることもないため、今回は Firebase Realtime Database を使用することにしました。 以下が、Realtime Database で変更の監視及び通知を行う実装です。 import { initializeApp } from "firebase/app" ; import { getDatabase , off , onValue , ref , set } from "firebase/database" ; const app = initializeApp ( { /* 省略’*/ }, "firebaseapp" ); const db = () => { return getDatabase ( app ); }; const PATH = "medicalRecordChiefComplaintImages" ; // refを生成 const medicalRecordChiefComplaintImagesRef = ( medicalRecordId : string ) => { return ref ( db (), `/ ${ PATH } / ${ medicalRecordId } ` ); }; // 主訴所見の手書き画像が更新されたことを通知する export const updateMedicalRecordChiefComplaintImageIds = async ({ medicalRecordId , }: { medicalRecordId : string ; }) => { await set ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), Date . now ()); }; // 主訴所見の手書き画像の変更を監視する export const watchMedicalRecordChiefComplaintImages = ({ medicalRecordId , refreshMedicalRecordChiefComplaintImages , }: { medicalRecordId : string ; refreshMedicalRecordChiefComplaintImages : () => void ; }) => { onValue ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), () => { // Realtime Databaseが更新されたときに実行する処理 refreshMedicalRecordChiefComplaintImages (); }); }; // 主訴所見の手書き画像の変更の監視を解除する export const unwatchMedicalRecordChiefComplaintImages = ({ medicalRecordId , }: { medicalRecordId : string ; }) => { off ( medicalRecordChiefComplaintImagesRef ( medicalRecordId )); }; フロントエンドでは、データフェッチに TanStack Query を使用しています。以下のように Realtime Database によって手書き画像の更新を監視して refetch しています。 export const useMedicalChiefComplaintImagesWatchQuery = ({ medicalRecordId , initialChiefComplaintImages , }: UseMedicalChiefComplaintImagesWatchQueryProps ) => { const medicalChiefComplaintImagesQuery = useQuery ({ queryKey: medicalChiefComplaintImageKey . list ( medicalRecordId ), queryFn : () => query . get ( request . get ( medicalRecordId )), }); useEffect (() => { watchMedicalRecordChiefComplaintImages ({ medicalRecordId: medicalRecordId , // Realtime Databaseの更新時にrefetchする refreshMedicalRecordChiefComplaintImages: medicalChiefComplaintImagesQuery . refetch , }); return () => unwatchMedicalRecordChiefComplaintImages ({ medicalRecordId }); }, [ medicalRecordId , medicalChiefComplaintImagesQuery . refetch ]); return medicalChiefComplaintImagesQuery ; }; 最後に、iPad で手書き画像を更新したタイミングで updateMedicalRecordChiefComplaintImageIds を呼び出せば、以下のように iPad での変更が PC に即時に反映されるようになります。 工夫 4. 手書きツールバーを自由に配置できるようにした 手書き機能では太さや色の変更、ペンと消しゴムの切り替え、履歴管理ができるようにツールバーを実装しています。ツールバーは頻繁に使われるため、ユーザーが使用しやすい位置に移動できるようにしました。こちらは dnd-kit を使用して実装しました。 動画にあるようにツールバーが親要素からはみ出ないようになっています。こちらは restrictToParentElement を使用することで実現できます。 また、CSS ライブラリに emotion を使用しており、今回の Draggable 対応では動的にスタイルを変更する必要があるため、 style prop を使うように注意する必要があります( https://emotion.sh/docs/best-practices#use-the-style-prop-for-dynamic-styles )。 ライブラリの不具合 上記のようにユーザーが使いやすいように手書き機能を開発しましたが、消しゴムで線を消すと消えて欲しくない背景画像まで消えてしまうという不具合がライブラリに存在していました。こちらは同じ fabric を使用している Dentis のエンジニア大岡に相談したところ、ライブラリに PR を出して解消していただけました! fix(): backgroundImage/overlayImage erasable by yasupeke · Pull Request #49 · ShaMan123/erase2d I always appreciate the convenience of using this tool. I have fixed an issue where the erasable setting for backgroundImage/overlayImage was not being applied. Please review the changes. github.com まとめ 本記事では、Web でユーザビリティの高い手書きアプリケーションを実装するための工夫点について紹介しました。手書き機能の実装においては、実装速度とユーザビリティを両立するために適切なライブラリ選定が重要です。私達は fabric を採用することで滑らかな手書きアプリケーションを実現できました。 実装にあたり、以下の 4 つを工夫しました。 手を画面に置きながら書ける機能: 接触面積の大きさを検出してペン入力と区別することで、自然な書き心地を実現しました 消しゴム操作のラグ解消: Retina ディスプレイ対応を最適化し、高負荷な消しゴム処理でもスムーズな操作感を実現しました リアルタイム同期: Firebase Realtime Database を活用し、複数デバイス間での即時反映を実現しました 柔軟な UI: dnd-kit によるドラッグ可能なツールバーで、ユーザー体験を向上しました また、ライブラリの不具合に対してはプロダクトを横断したコミュニケーションやコミュニティと協力することで解決しました。これらの工夫により、医科診療所で快適に使用できる手書き機能を開発することができました。 今後もユーザビリティを重視しながら、より使いやすい機能の開発を続けていきます。 We’re hiring メドレーでは一緒に働く仲間を大募集しています! カジュアル面談も実施しておりますので、「お話だけでも聞いてみたい!」「ちょっと雑談してみたい!」でも構いませんので、お気軽にお問い合わせください! 募集の一覧 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp 医療エンジニアリング領域盛り上がっています!メドレーについてお話します! メドレーの人材プラットフォーム事業についてお話しします! 「メドレーの人材プラットフォーム事業についてお話しします!」- 倉林 昭和さん pitta.me メドレーの開発チームについて知りたい方!ぜひお話ししましょう! メドレーの開発チームについて知りたい方!ぜひお話ししましょう! 「メドレーの開発チームについて知りたい方!ぜひお話ししましょう!」- 德永 諒介さん pitta.me
はじめに こんにちは。医療プラットフォーム本部 CLINICS 開発グループの吉岡( @yuya333_ )と村上( @yuporonM )です。 吉岡は 2022 年に、村上は 2024 年に新卒でエンジニアとしてメドレーに入社しました。 私たちは、医科診療所向けの電子カルテである CLINICS カルテ を開発しています。CLINICS カルテは 2025 年 3 月に iPad 手書き機能をリリースしました。iPad 手書き機能とは、iPad を使って主訴・所見やシェーマ(身体の部位の絵)を手書きできる機能です。 主訴・所見の手書き シェーマの手書き 本記事では iPad 手書き機能を開発するにあたって工夫した点を紹介します。 ライブラリ選定 手書き機能を開発するにあたって、手書き処理すべてを実装するのは工数の観点から現実的ではありませんでした。 そのため、要件を満たせるかつメンテナンス面から以下のライブラリが候補となりました。 fabric konva メドレーでは、歯科診療所向けに電子カルテ Dentis を開発しており、Dentis では既に手書き機能を提供しています。そこで、手書き機能の実装に関して知見を持っている Dentis のエンジニアにライブラリ選定について相談したところ、以下の理由で fabric を採用することに決めました。 konva では線が荒くなってしまうことがある https://github.com/konvajs/konva/issues/1144 で解消方法も紹介されていましたが、細かく線を書きたいケースに対応できない konva では線を描くたびに再レンダリングが発生してしまう mousemove や touchevent のデータをキャッシュすることで解決できるが、遅延が気になる また、fabric を使用した手書き機能の実装方法に関しては、2024 年 12 月にメドレーのアドベントカレンダーで吉岡が書いた以下の記事を参考にしてみてください。 React+fabric.jsで作る手書きアプリ zenn.dev 手書き機能で工夫したポイント 4 選 工夫 1. 手を画面につけながらペンで手書きできるようにした 画面に手をつけながらペンで書いたときに、以下のように線が乱れてしまうことがありました。 画面に接触した手はペンや指で書いたときよりも接触面積が大きくなるので、接触部分が一定よりも大きくなった場合には線を書けないように実装しました。具体的には、 radiusX プロパティを使用して、radiusX がある値より大きいときには、 isDrawingMode が false になるようにしました。 canvas . on ( "mouse:down:before" , ( e ) => { if ( "TouchEvent" in window && e . e instanceof TouchEvent ) { const touch = e . e . touches . item ( 0 ); if (! touch ) { return ; } // 接触面積がペンや指よりも大きいときに線を書けなくする if ( touch . radiusX > 35 ) { canvas . isDrawingMode = false ; } } }); // 画面から手を離す度にisDrawingModeをリセット canvas . on ( "mouse:up:before" , () => { canvas . isDrawingMode = true ; }); 工夫 2. 消しゴムで線を消すときのラグを解消した 消しゴム機能は erase2d ライブラリを使って実装しました。 fabric v5 までは消しゴム機能が内包されていましたが、fabric v6 から erase2d に切り出されました。 erase2d を使って消しゴムを実装すると、次の動画のようにユーザビリティを大きく損なうほどのラグが発生していました。 このラグは PC では発生せず、iPad でのみ発生していました。 この現象について原因を調査したところ、以下の 2 点が原因であることが分かりました。 原因 1. Retina ディスプレイによるピクセル密度の違い iPad は Retina ディスプレイを搭載しており、ピクセル密度が通常のディスプレイよりも高くなっていました。 検証に利用していた 11 インチ iPad Air (M2) では、PC と比較してピクセル密度が 4 倍にもなっていました。 原因 2. ピクセル密度に依存した消しゴム機能特有の処理 消しゴム機能は、ペン機能と比較して複雑な処理が行われていました。 ペンは 1 つのキャンバスに線を描くだけであるのに対して、消しゴムは複数のキャンバスを扱い、それらを合成する処理が必要でした。 具体的には、消しゴムの軌跡を一時的なキャンバスに描画し、それをメインキャンバスに合成するといった処理等が行われていました。 これは、ピクセル密度に依存しており、ピクセル密度が高い環境では負荷が高くなっていました。 // https://github.com/ShaMan123/erase2d/blob/7ee2b789b4c9161d662ea8c8aaf87af4c37bab13/packages/core/src/erase.ts#L15-L40 export const erase = ( destination : CanvasRenderingContext2D , // メインキャンバス source : CanvasRenderingContext2D , // 消しゴムの軌跡が描かれたキャンバス erasingEffect ?: CanvasRenderingContext2D // 消したくないものを保護するためのキャンバス ) => { // メインキャンバスから、消しゴムの軌跡部分を消去 drawImage ( destination , source , "destination-out" ); if ( erasingEffect ) { // 消しゴムの軌跡に、消したくないものがあれば、消さずに保護 drawImage ( source , erasingEffect , "source-in" ); } else { // 消しゴムの軌跡を描いたキャンバスを初期化 source . save (); source . resetTransform (); source . clearRect ( 0 , 0 , source . canvas . width , source . canvas . height ); source . restore (); } }; これらの原因によって、Retina ディスプレイの高解像度環境では消しゴムで線を消すときにラグが発生していました。 解決策として Canvas の初期化時に enableRetinaScaling オプションを false に設定し、Retina ディスプレイの高解像度環境においても同じピクセル密度で処理を行うようにしました。 const canvas = new fabric . Canvas ( canvasElement , { enableRetinaScaling: false , }); この設定により、一定の解像度を担保しつつ、iPad でも滑らかな消しゴム操作を実現できました。 工夫 3. iPad で手書きして保存した画像を PC に即時反映されるようにした iPad で手書きしながら PC でカルテを使うということを想定しているため、iPad で保存した手書き画像は PC に即時反映される必要があります。こちらを実現するために、以下が候補に挙げられました。 SSE(Server-Sent Events) WebSocket Firebase Realtime Database Firebase Realtime Database を既に使用しており、今回保存するのは更新時間のみで、保存容量が大幅に増えることもないため、今回は Firebase Realtime Database を使用することにしました。 以下が、Realtime Database で変更の監視及び通知を行う実装です。 import { initializeApp } from "firebase/app" ; import { getDatabase , off , onValue , ref , set } from "firebase/database" ; const app = initializeApp ( { /* 省略’*/ }, "firebaseapp" ); const db = () => { return getDatabase ( app ); }; const PATH = "medicalRecordChiefComplaintImages" ; // refを生成 const medicalRecordChiefComplaintImagesRef = ( medicalRecordId : string ) => { return ref ( db (), `/ ${ PATH } / ${ medicalRecordId } ` ); }; // 主訴所見の手書き画像が更新されたことを通知する export const updateMedicalRecordChiefComplaintImageIds = async ({ medicalRecordId , }: { medicalRecordId : string ; }) => { await set ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), Date . now ()); }; // 主訴所見の手書き画像の変更を監視する export const watchMedicalRecordChiefComplaintImages = ({ medicalRecordId , refreshMedicalRecordChiefComplaintImages , }: { medicalRecordId : string ; refreshMedicalRecordChiefComplaintImages : () => void ; }) => { onValue ( medicalRecordChiefComplaintImagesRef ( medicalRecordId ), () => { // Realtime Databaseが更新されたときに実行する処理 refreshMedicalRecordChiefComplaintImages (); }); }; // 主訴所見の手書き画像の変更の監視を解除する export const unwatchMedicalRecordChiefComplaintImages = ({ medicalRecordId , }: { medicalRecordId : string ; }) => { off ( medicalRecordChiefComplaintImagesRef ( medicalRecordId )); }; フロントエンドでは、データフェッチに TanStack Query を使用しています。以下のように Realtime Database によって手書き画像の更新を監視して refetch しています。 export const useMedicalChiefComplaintImagesWatchQuery = ({ medicalRecordId , initialChiefComplaintImages , }: UseMedicalChiefComplaintImagesWatchQueryProps ) => { const medicalChiefComplaintImagesQuery = useQuery ({ queryKey: medicalChiefComplaintImageKey . list ( medicalRecordId ), queryFn : () => query . get ( request . get ( medicalRecordId )), }); useEffect (() => { watchMedicalRecordChiefComplaintImages ({ medicalRecordId: medicalRecordId , // Realtime Databaseの更新時にrefetchする refreshMedicalRecordChiefComplaintImages: medicalChiefComplaintImagesQuery . refetch , }); return () => unwatchMedicalRecordChiefComplaintImages ({ medicalRecordId }); }, [ medicalRecordId , medicalChiefComplaintImagesQuery . refetch ]); return medicalChiefComplaintImagesQuery ; }; 最後に、iPad で手書き画像を更新したタイミングで updateMedicalRecordChiefComplaintImageIds を呼び出せば、以下のように iPad での変更が PC に即時に反映されるようになります。 工夫 4. 手書きツールバーを自由に配置できるようにした 手書き機能では太さや色の変更、ペンと消しゴムの切り替え、履歴管理ができるようにツールバーを実装しています。ツールバーは頻繁に使われるため、ユーザーが使用しやすい位置に移動できるようにしました。こちらは dnd-kit を使用して実装しました。 動画にあるようにツールバーが親要素からはみ出ないようになっています。こちらは restrictToParentElement を使用することで実現できます。 また、CSS ライブラリに emotion を使用しており、今回の Draggable 対応では動的にスタイルを変更する必要があるため、 style prop を使うように注意する必要があります( https://emotion.sh/docs/best-practices#use-the-style-prop-for-dynamic-styles )。 ライブラリの不具合 上記のようにユーザーが使いやすいように手書き機能を開発しましたが、消しゴムで線を消すと消えて欲しくない背景画像まで消えてしまうという不具合がライブラリに存在していました。こちらは同じ fabric を使用している Dentis のエンジニア大岡に相談したところ、ライブラリに PR を出して解消していただけました! fix(): backgroundImage/overlayImage erasable by yasupeke · Pull Request #49 · ShaMan123/erase2d I always appreciate the convenience of using this tool. I have fixed an issue where the erasable setting for backgroundImage/overlayImage was not being applied. Please review the changes. github.com まとめ 本記事では、Web でユーザビリティの高い手書きアプリケーションを実装するための工夫点について紹介しました。手書き機能の実装においては、実装速度とユーザビリティを両立するために適切なライブラリ選定が重要です。私達は fabric を採用することで滑らかな手書きアプリケーションを実現できました。 実装にあたり、以下の 4 つを工夫しました。 手を画面に置きながら書ける機能: 接触面積の大きさを検出してペン入力と区別することで、自然な書き心地を実現しました 消しゴム操作のラグ解消: Retina ディスプレイ対応を最適化し、高負荷な消しゴム処理でもスムーズな操作感を実現しました リアルタイム同期: Firebase Realtime Database を活用し、複数デバイス間での即時反映を実現しました 柔軟な UI: dnd-kit によるドラッグ可能なツールバーで、ユーザー体験を向上しました また、ライブラリの不具合に対してはプロダクトを横断したコミュニケーションやコミュニティと協力することで解決しました。これらの工夫により、医科診療所で快適に使用できる手書き機能を開発することができました。 今後もユーザビリティを重視しながら、より使いやすい機能の開発を続けていきます。 We’re hiring メドレーでは一緒に働く仲間を大募集しています! カジュアル面談も実施しておりますので、「お話だけでも聞いてみたい!」「ちょっと雑談してみたい!」でも構いませんので、お気軽にお問い合わせください! 募集の一覧 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp 医療エンジニアリング領域盛り上がっています!メドレーについてお話します! メドレーの人材プラットフォーム事業についてお話しします! 「メドレーの人材プラットフォーム事業についてお話しします!」- 倉林 昭和さん pitta.me メドレーの開発チームについて知りたい方!ぜひお話ししましょう! メドレーの開発チームについて知りたい方!ぜひお話ししましょう! 「メドレーの開発チームについて知りたい方!ぜひお話ししましょう!」- 德永 諒介さん pitta.me