こんにちは!メドレーで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/ ※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください
こんにちは。医療プラットフォーム本部の日下( @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 ※カジュアル面談ご希望の際は、<その他> にてその旨をご記載ください
はじめに こんにちは。医療プラットフォーム本部 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
はじめに こんにちは、医療プラットフォーム本部 SRE グループの大塚です。入社から 3 年ほど CLINICS のソフトウェアエンジニアとして開発に携わり、その中でプロダクトの安定稼働のための信頼性向上の技術的な取り込みも担当していました。 このたび、医療プラットフォームにおけるシステムの安定稼働と信頼性の確保がこれまで以上に求められる状況になり、正式に SRE チームを立ち上げました。医療機関向け SaaS である CLINICS の信頼性向上を主眼に発足し、立ち上げから SLO の策定や運用効率化などの成果が出ています。 この記事では、以下の内容についてお話しします。 医療プラットフォームにおける SRE チーム立ち上げの経緯 医療プラットフォームの中核である CLINICS について 医療プラットフォーム SRE チームの組織体制と役割 医療プラットフォーム SRE チームとしての展望 医療系 SaaS に興味があるエンジニアや、SRE としてのキャリアに関心がある方に向けて書いています。医療の発展にテクノロジーで貢献したいと考える方にとって参考になれば幸いです。 なぜ、医療プラットフォームで SRE チーム立ち上げたのか? 医療プラットフォームの中核を担う CLINICS 医療プラットフォームは「医療ヘルスケアの未来をつくる」、「納得できる医療」の実現のために、様々な医療領域のプロダクトを提供しています。 その中でも、医療機関向け SaaS である CLINICS は高い信頼性が求められています。CLINICS は電子カルテ、予約管理、会計システム、オンライン診療など、診療所の基幹業務をトータルでサポートするクラウドサービスです。診療所の日々の運営に不可欠な業務を担うシステムであるため、高い信頼性と安定性が求められます。 また、CLINICS は医療機関と患者をつなぐ接点でもあり、システムには患者向けの API なども含まれます。そのため、システムの信頼性は患者の観点から見ても重要です。 CLINICS に求められる非機能要件 CLINICS は診療業務の基幹システムであり、診療所の業務をリアルタイムでサポートします。機能面の品質はもちろん、非機能要件(信頼性、可用性、パフォーマンス、保守性など)も極めて重要です。 例えば、システムの応答速度が遅くなり患者が受付や会計で長時間待たされると、医療機関だけでなく患者の体験も悪化します。そして、システム停止は診療所の業務が遂行できなくなることを意味します。 その結果、CLINICS サポートへの負荷増大や医療機関の信頼性低下などの影響を招きます。障害復旧対応で開発計画の変更が余儀なくされ、開発速度の低下を生じさせる一因になります。 しかし、CLINICS においてはこれらの非機能要件を満たすことは簡単ではありません。CLINICS は医療プラットフォームの中で最も歴史が長いプロダクトです。契約医療機関数は数千を超え、診療所の業務時間中は常に大量のデータとトラフィックを処理します。また、 CLINICS はレセコン(診療報酬請求事務を管理・自動化するシステム) などの複数のシステムで構成され、インフラなどの運用コストも高くなっています。 CLINICS を含む医療プラットフォームの成長に伴う技術課題 私自身も CLINICS のソフトウェアエンジニアとして開発に携わり、インフラに詳しいメンバー数人と共に可用性や信頼性に関するタスクを担当してきました。しかし、事業の成長に伴いシステム規模が拡大する中で、機能開発とシステム信頼性の両立が困難になっていました。 システムが深刻な状態には至っていませんでしたが、概ね以下のような課題を抱えていました。 長年の運用の中で非効率な DB へのクエリが蓄積し、処理に時間がかかる箇所が散見される トイル(SRE における反復的な運用作業)が増大し、運用業務の負荷が大きくなり、さらに悪循環を生む ライブラリ対応の優先度が下がり、 EOL 対応に追われている アラートの振り返りと暫定的な再発防止策は実施するが、根本対応までやりきれない また、これらの課題は、 Dentis や Pharms などの医療プラットフォーム内の他のプロダクトでも発生する可能性が高いです。他のプロダクトも CLINICS と同様の体制で開発と運用されており、すでに同じ課題が顕在化しつつあります。 CLINICS の医療機関数は日々増加しており、今後も事業拡大が見込まれます。それは他のプロダクトでも同様です。CLINICS に限らず医療プラットフォーム全体の成長を加速させるには、 信頼性の高い基盤構築に向き合えるチーム体制の構築が不可欠です。 医療プラットフォーム SRE チームの発足 信頼性課題などの中長期的な課題解決を目指す医療プラットフォーム SRE チームを発足しました。CLINICS に限らず、医療プラットフォーム全体を対応範囲としています。先程述べたように、システム信頼性の課題は医療プラットフォーム全体の課題であるためです。SRE チームが医療プラットフォームの横断組織として機能することで、システム信頼性に関する知見を集約します。 まずは、医療プラットフォームの中でも 中心的な役割を担う大規模なシステムである CLINICS の信頼性向上を最優先課題としています。SRE チームは CLINICS での取り組みの中で得た知見を医療プラットフォーム全体に還元します。 2025 年 4 月現在、SRE チームは CLINICS プロダクトに専任する形で活動しています。一般に知られている Embedded SRE です。この形態では、SRE チームメンバーが開発チームに直接入り込み、密に連携しながら信頼性向上に取り組みます。 CLINICS の信頼性向上を目指した SRE の取り組み ここからは CLINICS に対して現在行っている取り組みを説明します。 SRE チームの役割とミッション CLINICS における SRE チームのミッションは 「事業成長を支え、安定した開発基盤と顧客の信頼性を維持する文化、仕組みを構築すること」 です。SRE チームは信頼性向上に専念することで、開発チームは機能開発に集中できる状態を目指し、CLINICS プロダクトは事業と品質の両輪での成長を図ります。 前半で述べた課題に対応するために、SRE チームの立ち上げ時にまず 3 つの重点目標を設定しました。それぞれの目標に対する取り組みと一部の成果を紹介します。 目標 1: トイル排除と中長期課題へのシフト 課題: 日常的な運用作業(トイル)を自動化し、少人数での複数システムの運用ができ、足元の課題から中長期的な課題へ時間を使えるようにする 主な取り組み: 既存インフラリソースの Terraform import を実施 : 新しいインフラリソースは Terraform で作成されていました。しかし、リリース初期から存在するリソースは違い、インフラ運用の属人化を引き起こしていました。Terraform import を定常的に実施し、誰でも実装可能で、レビューしやすい運用を実現しました。 定常的に発生する運用業務の自動化 : CLINICS は運用するシステムも多く、月次の更新処理や定常的なスケールアップ対応など、数多くの運用タスクがありました。 AWS Step Functions などにより定常的な業務を自動化し、運用負荷軽減を実現しました。 目標 2: 改善文化の浸透 課題: ポストモーテム 文化(障害後の振り返り文化)を確立し、再発防止が適切に実行され、改善課題が積み上がり続ける状態から脱する。 主な取り組み: ポストモーテムの標準化: 以前から振り返りの文化はありましたが、定式化されておらず、各々が各自の形式で振り返りを実施していました。 チームでのナレッジ共有がなされず、障害対応での属人化の要因となっていました。体系的に障害を振り返る仕組みを作り、CLINICS のチーム全体に展開することで、チームでの障害対応力を向上させました。 アラート改善を定常的に回す仕組みの策定 : 緊急性が高い事象や顧客とのコミュニケーションが必要な事象はアラートの検知時に迅速に解決できていました。しかし、タイミングの問題で発生する瑣末なフロントエンドのエラーなどは「オオカミ少年」になり、アラートを定期的に検知していました。開発チーム全体で根本対応できるものは実施する、すぐに解決が難しいものは一定期間無視するなど、対応ルールを精緻化しました。 目標 3: SLO による信頼性可視化 課題: CLINICS の関係者が納得できる 信頼性の指標(SLO) を構築し、改善施策はこれらの指標改善を優先する 主な取り組み: 重要な顧客体験 (Critical User Journey) の策定と SLO ダッシュボードの作成: CUJ(Critical User Journey) を特定し、Datadog 上に SLO のダッシュボードを作成しました。フロントエンドのパフォーマンスモニタリングを目的とした Datadog RUM(Realtime User Monitoring) などを導入することで、オブザーバビリティの強化も実現し、顧客体験をより厳密にトラッキングできるようにしました。 SLO モニタリングの実施: 作成したダッシュボードをもとに受付、診察、会計など 3 つの顧客体験について、可用性、レイテンシー、エラー率の目標値を設定しました。閾値の調整やエラーバジェットのモニタリングを定期実施し、アラート改善に役立てる取り組みを実現しました。 SRE チームにおけるその他の取り組み 上記の取り組み以外にも、SRE の一般的な以下の業務にも取り組んでいます。 事業成長を見越したキャパシティプランニング システムのモニタリングによる事前の課題発見 障害対応プロセスの改善 インフラコストの削減 DB 等のパフォーマンス問題の改善 ライブラリ対応やセキュリティ対応の効率化 アーキテクチャとリリースフローの改善…など SRE チームの取り組みの成果が実を結び始める これらの取り組みにより、CLINICS 開発メンバー全体(開発チームを含む)の運用負担が軽減されました。 信頼性の維持をしつつ、目の前の課題だけでなく将来を見据えた取り組みに注力する余裕が生まれています。開発チームとの協業を強化し、機能開発時に SLO の組み込みを進め、改善目標の指標としても活用する計画を立てています。他にも障害時の信頼性向上を目的とした Sidekiq Pro の導入など、信頼性向上にも取り組んでいます。 このように私たちの取り組みが徐々に成果を上げ始めています。 開発チームと SRE チームの協働により、当初実現したかった CLINICS の機能開発とシステム信頼性の両立が実現しつつあります 。今後もこの連携と SRE の取り組みを深め、より安定したサービス提供を目指してまいります。 医療プラットフォーム SRE チームとしての展望 ここまでは、CLINICS における SRE の取り組みを書いてきました。ここからは、今後医療プラットフォーム全体に SRE としてどのように貢献しようとしているのかの展望を述べます。 医療プラットフォーム全体への SRE ノウハウ展開 CLINICS での知見を活かし、今後、 Dentis や Pharms といった医療プラットフォームの他のプロダクトにも SRE のノウハウを展開していく予定です。 CLINICS は規模が大きく、信頼性要求も高いため、培ったノウハウは他のプロダクトにも十分応用できると考えています。ノウハウを展開することで、医療プラットフォーム全体での信頼性の底上げを実現します。 プロダクトの拡充に対して柔軟に対応できる仕組みづくり 医療プラットフォームは、「医療ヘルスケアの未来をつくる」というミッション実現のために、今後も新規開発や M&A によるプロダクト増加も見込まれます。 新たなプロダクトが加わった際にも、医療プラットフォーム標準の信頼性水準に迅速に引き上げることが重要です。 SRE チームは、医療プラットフォームに属する全てのプロダクトが高い信頼性を維持できる体制を目指します。 事業運営に関わる全関係者が納得する SRE 文化の醸成 SRE の取り組みは開発チームだけのものではありません。ポストモーテムや開発優先度の判断において、非開発メンバーの視点は不可欠です。プロダクトマネージャーや QA メンバーはもちろん、カスタマーサクセス・サポートチームも信頼性の考え方を共有することで、「なぜ機能開発より安定性向上を優先すべきか」といった判断に事業全体の納得感が生まれます。 SLO などの指標は単なる技術的な数値ではなく、顧客体験と事業成長を支える重要な要素です。医療プラットフォーム全体で信頼性と機能開発のバランスを適切に取りながら前進できる文化を育むこと、これも SRE チームの重要なミッションの一つです。 まとめ:医療ヘルスケアの未来を支える SRE 医療プラットフォームの SRE 立ち上げについてお伝えしました。医療プラットフォームの成長と「医療ヘルスケアの未来」創造のために、SRE による信頼性確保は欠かせないミッションです。 SRE としても医療プラットフォームとしても、挑戦したいことは山積みです。事業成長を加速し、医療機関と患者の体験を向上させるために、SRE の活動は不可欠です。 何より、「医療ヘルスケアの未来をつくる」というミッションに共感し、技術で医療の課題解決に貢献したいという情熱を持つ方をお待ちしています。私たちと一緒にこのミッションを実現しませんか? 興味を持たれた方は、以下のリンクより、ぜひカジュアル面談の応募をお願いします。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに こんにちは、医療プラットフォーム本部 SRE グループの大塚です。入社から 3 年ほど CLINICS のソフトウェアエンジニアとして開発に携わり、その中でプロダクトの安定稼働のための信頼性向上の技術的な取り込みも担当していました。 このたび、医療プラットフォームにおけるシステムの安定稼働と信頼性の確保がこれまで以上に求められる状況になり、正式に SRE チームを立ち上げました。医療機関向け SaaS である CLINICS の信頼性向上を主眼に発足し、立ち上げから SLO の策定や運用効率化などの成果が出ています。 この記事では、以下の内容についてお話しします。 医療プラットフォームにおける SRE チーム立ち上げの経緯 医療プラットフォームの中核である CLINICS について 医療プラットフォーム SRE チームの組織体制と役割 医療プラットフォーム SRE チームとしての展望 医療系 SaaS に興味があるエンジニアや、SRE としてのキャリアに関心がある方に向けて書いています。医療の発展にテクノロジーで貢献したいと考える方にとって参考になれば幸いです。 なぜ、医療プラットフォームで SRE チーム立ち上げたのか? 医療プラットフォームの中核を担う CLINICS 医療プラットフォームは「医療ヘルスケアの未来をつくる」、「納得できる医療」の実現のために、様々な医療領域のプロダクトを提供しています。 その中でも、医療機関向け SaaS である CLINICS は高い信頼性が求められています。CLINICS は電子カルテ、予約管理、会計システム、オンライン診療など、診療所の基幹業務をトータルでサポートするクラウドサービスです。診療所の日々の運営に不可欠な業務を担うシステムであるため、高い信頼性と安定性が求められます。 また、CLINICS は医療機関と患者をつなぐ接点でもあり、システムには患者向けの API なども含まれます。そのため、システムの信頼性は患者の観点から見ても重要です。 CLINICS に求められる非機能要件 CLINICS は診療業務の基幹システムであり、診療所の業務をリアルタイムでサポートします。機能面の品質はもちろん、非機能要件(信頼性、可用性、パフォーマンス、保守性など)も極めて重要です。 例えば、システムの応答速度が遅くなり患者が受付や会計で長時間待たされると、医療機関だけでなく患者の体験も悪化します。そして、システム停止は診療所の業務が遂行できなくなることを意味します。 その結果、CLINICS サポートへの負荷増大や医療機関の信頼性低下などの影響を招きます。障害復旧対応で開発計画の変更が余儀なくされ、開発速度の低下を生じさせる一因になります。 しかし、CLINICS においてはこれらの非機能要件を満たすことは簡単ではありません。CLINICS は医療プラットフォームの中で最も歴史が長いプロダクトです。契約医療機関数は数千を超え、診療所の業務時間中は常に大量のデータとトラフィックを処理します。また、 CLINICS はレセコン(診療報酬請求事務を管理・自動化するシステム) などの複数のシステムで構成され、インフラなどの運用コストも高くなっています。 CLINICS を含む医療プラットフォームの成長に伴う技術課題 私自身も CLINICS のソフトウェアエンジニアとして開発に携わり、インフラに詳しいメンバー数人と共に可用性や信頼性に関するタスクを担当してきました。しかし、事業の成長に伴いシステム規模が拡大する中で、機能開発とシステム信頼性の両立が困難になっていました。 システムが深刻な状態には至っていませんでしたが、概ね以下のような課題を抱えていました。 長年の運用の中で非効率な DB へのクエリが蓄積し、処理に時間がかかる箇所が散見される トイル(SRE における反復的な運用作業)が増大し、運用業務の負荷が大きくなり、さらに悪循環を生む ライブラリ対応の優先度が下がり、 EOL 対応に追われている アラートの振り返りと暫定的な再発防止策は実施するが、根本対応までやりきれない また、これらの課題は、 Dentis や Pharms などの医療プラットフォーム内の他のプロダクトでも発生する可能性が高いです。他のプロダクトも CLINICS と同様の体制で開発と運用されており、すでに同じ課題が顕在化しつつあります。 CLINICS の医療機関数は日々増加しており、今後も事業拡大が見込まれます。それは他のプロダクトでも同様です。CLINICS に限らず医療プラットフォーム全体の成長を加速させるには、 信頼性の高い基盤構築に向き合えるチーム体制の構築が不可欠です。 医療プラットフォーム SRE チームの発足 信頼性課題などの中長期的な課題解決を目指す医療プラットフォーム SRE チームを発足しました。CLINICS に限らず、医療プラットフォーム全体を対応範囲としています。先程述べたように、システム信頼性の課題は医療プラットフォーム全体の課題であるためです。SRE チームが医療プラットフォームの横断組織として機能することで、システム信頼性に関する知見を集約します。 まずは、医療プラットフォームの中でも 中心的な役割を担う大規模なシステムである CLINICS の信頼性向上を最優先課題としています。SRE チームは CLINICS での取り組みの中で得た知見を医療プラットフォーム全体に還元します。 2025 年 4 月現在、SRE チームは CLINICS プロダクトに専任する形で活動しています。一般に知られている Embedded SRE です。この形態では、SRE チームメンバーが開発チームに直接入り込み、密に連携しながら信頼性向上に取り組みます。 CLINICS の信頼性向上を目指した SRE の取り組み ここからは CLINICS に対して現在行っている取り組みを説明します。 SRE チームの役割とミッション CLINICS における SRE チームのミッションは 「事業成長を支え、安定した開発基盤と顧客の信頼性を維持する文化、仕組みを構築すること」 です。SRE チームは信頼性向上に専念することで、開発チームは機能開発に集中できる状態を目指し、CLINICS プロダクトは事業と品質の両輪での成長を図ります。 前半で述べた課題に対応するために、SRE チームの立ち上げ時にまず 3 つの重点目標を設定しました。それぞれの目標に対する取り組みと一部の成果を紹介します。 目標 1: トイル排除と中長期課題へのシフト 課題: 日常的な運用作業(トイル)を自動化し、少人数での複数システムの運用ができ、足元の課題から中長期的な課題へ時間を使えるようにする 主な取り組み: 既存インフラリソースの Terraform import を実施 : 新しいインフラリソースは Terraform で作成されていました。しかし、リリース初期から存在するリソースは違い、インフラ運用の属人化を引き起こしていました。Terraform import を定常的に実施し、誰でも実装可能で、レビューしやすい運用を実現しました。 定常的に発生する運用業務の自動化 : CLINICS は運用するシステムも多く、月次の更新処理や定常的なスケールアップ対応など、数多くの運用タスクがありました。 AWS Step Functions などにより定常的な業務を自動化し、運用負荷軽減を実現しました。 目標 2: 改善文化の浸透 課題: ポストモーテム 文化(障害後の振り返り文化)を確立し、再発防止が適切に実行され、改善課題が積み上がり続ける状態から脱する。 主な取り組み: ポストモーテムの標準化: 以前から振り返りの文化はありましたが、定式化されておらず、各々が各自の形式で振り返りを実施していました。 チームでのナレッジ共有がなされず、障害対応での属人化の要因となっていました。体系的に障害を振り返る仕組みを作り、CLINICS のチーム全体に展開することで、チームでの障害対応力を向上させました。 アラート改善を定常的に回す仕組みの策定 : 緊急性が高い事象や顧客とのコミュニケーションが必要な事象はアラートの検知時に迅速に解決できていました。しかし、タイミングの問題で発生する瑣末なフロントエンドのエラーなどは「オオカミ少年」になり、アラートを定期的に検知していました。開発チーム全体で根本対応できるものは実施する、すぐに解決が難しいものは一定期間無視するなど、対応ルールを精緻化しました。 目標 3: SLO による信頼性可視化 課題: CLINICS の関係者が納得できる 信頼性の指標(SLO) を構築し、改善施策はこれらの指標改善を優先する 主な取り組み: 重要な顧客体験 (Critical User Journey) の策定と SLO ダッシュボードの作成: CUJ(Critical User Journey) を特定し、Datadog 上に SLO のダッシュボードを作成しました。フロントエンドのパフォーマンスモニタリングを目的とした Datadog RUM(Realtime User Monitoring) などを導入することで、オブザーバビリティの強化も実現し、顧客体験をより厳密にトラッキングできるようにしました。 SLO モニタリングの実施: 作成したダッシュボードをもとに受付、診察、会計など 3 つの顧客体験について、可用性、レイテンシー、エラー率の目標値を設定しました。閾値の調整やエラーバジェットのモニタリングを定期実施し、アラート改善に役立てる取り組みを実現しました。 SRE チームにおけるその他の取り組み 上記の取り組み以外にも、SRE の一般的な以下の業務にも取り組んでいます。 事業成長を見越したキャパシティプランニング システムのモニタリングによる事前の課題発見 障害対応プロセスの改善 インフラコストの削減 DB 等のパフォーマンス問題の改善 ライブラリ対応やセキュリティ対応の効率化 アーキテクチャとリリースフローの改善…など SRE チームの取り組みの成果が実を結び始める これらの取り組みにより、CLINICS 開発メンバー全体(開発チームを含む)の運用負担が軽減されました。 信頼性の維持をしつつ、目の前の課題だけでなく将来を見据えた取り組みに注力する余裕が生まれています。開発チームとの協業を強化し、機能開発時に SLO の組み込みを進め、改善目標の指標としても活用する計画を立てています。他にも障害時の信頼性向上を目的とした Sidekiq Pro の導入など、信頼性向上にも取り組んでいます。 このように私たちの取り組みが徐々に成果を上げ始めています。 開発チームと SRE チームの協働により、当初実現したかった CLINICS の機能開発とシステム信頼性の両立が実現しつつあります 。今後もこの連携と SRE の取り組みを深め、より安定したサービス提供を目指してまいります。 医療プラットフォーム SRE チームとしての展望 ここまでは、CLINICS における SRE の取り組みを書いてきました。ここからは、今後医療プラットフォーム全体に SRE としてどのように貢献しようとしているのかの展望を述べます。 医療プラットフォーム全体への SRE ノウハウ展開 CLINICS での知見を活かし、今後、 Dentis や Pharms といった医療プラットフォームの他のプロダクトにも SRE のノウハウを展開していく予定です。 CLINICS は規模が大きく、信頼性要求も高いため、培ったノウハウは他のプロダクトにも十分応用できると考えています。ノウハウを展開することで、医療プラットフォーム全体での信頼性の底上げを実現します。 プロダクトの拡充に対して柔軟に対応できる仕組みづくり 医療プラットフォームは、「医療ヘルスケアの未来をつくる」というミッション実現のために、今後も新規開発や M&A によるプロダクト増加も見込まれます。 新たなプロダクトが加わった際にも、医療プラットフォーム標準の信頼性水準に迅速に引き上げることが重要です。 SRE チームは、医療プラットフォームに属する全てのプロダクトが高い信頼性を維持できる体制を目指します。 事業運営に関わる全関係者が納得する SRE 文化の醸成 SRE の取り組みは開発チームだけのものではありません。ポストモーテムや開発優先度の判断において、非開発メンバーの視点は不可欠です。プロダクトマネージャーや QA メンバーはもちろん、カスタマーサクセス・サポートチームも信頼性の考え方を共有することで、「なぜ機能開発より安定性向上を優先すべきか」といった判断に事業全体の納得感が生まれます。 SLO などの指標は単なる技術的な数値ではなく、顧客体験と事業成長を支える重要な要素です。医療プラットフォーム全体で信頼性と機能開発のバランスを適切に取りながら前進できる文化を育むこと、これも SRE チームの重要なミッションの一つです。 まとめ:医療ヘルスケアの未来を支える SRE 医療プラットフォームの SRE 立ち上げについてお伝えしました。医療プラットフォームの成長と「医療ヘルスケアの未来」創造のために、SRE による信頼性確保は欠かせないミッションです。 SRE としても医療プラットフォームとしても、挑戦したいことは山積みです。事業成長を加速し、医療機関と患者の体験を向上させるために、SRE の活動は不可欠です。 何より、「医療ヘルスケアの未来をつくる」というミッションに共感し、技術で医療の課題解決に貢献したいという情熱を持つ方をお待ちしています。私たちと一緒にこのミッションを実現しませんか? 興味を持たれた方は、以下のリンクより、ぜひカジュアル面談の応募をお願いします。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに こんにちは、医療プラットフォーム本部 SRE グループの大塚です。入社から 3 年ほど CLINICS のソフトウェアエンジニアとして開発に携わり、その中でプロダクトの安定稼働のための信頼性向上の技術的な取り込みも担当していました。 このたび、医療プラットフォームにおけるシステムの安定稼働と信頼性の確保がこれまで以上に求められる状況になり、正式に SRE チームを立ち上げました。医療機関向け SaaS である CLINICS の信頼性向上を主眼に発足し、立ち上げから SLO の策定や運用効率化などの成果が出ています。 この記事では、以下の内容についてお話しします。 医療プラットフォームにおける SRE チーム立ち上げの経緯 医療プラットフォームの中核である CLINICS について 医療プラットフォーム SRE チームの組織体制と役割 医療プラットフォーム SRE チームとしての展望 医療系 SaaS に興味があるエンジニアや、SRE としてのキャリアに関心がある方に向けて書いています。医療の発展にテクノロジーで貢献したいと考える方にとって参考になれば幸いです。 なぜ、医療プラットフォームで SRE チーム立ち上げたのか? 医療プラットフォームの中核を担う CLINICS 医療プラットフォームは「医療ヘルスケアの未来をつくる」、「納得できる医療」の実現のために、様々な医療領域のプロダクトを提供しています。 その中でも、医療機関向け SaaS である CLINICS は高い信頼性が求められています。CLINICS は電子カルテ、予約管理、会計システム、オンライン診療など、診療所の基幹業務をトータルでサポートするクラウドサービスです。診療所の日々の運営に不可欠な業務を担うシステムであるため、高い信頼性と安定性が求められます。 また、CLINICS は医療機関と患者をつなぐ接点でもあり、システムには患者向けの API なども含まれます。そのため、システムの信頼性は患者の観点から見ても重要です。 CLINICS に求められる非機能要件 CLINICS は診療業務の基幹システムであり、診療所の業務をリアルタイムでサポートします。機能面の品質はもちろん、非機能要件(信頼性、可用性、パフォーマンス、保守性など)も極めて重要です。 例えば、システムの応答速度が遅くなり患者が受付や会計で長時間待たされると、医療機関だけでなく患者の体験も悪化します。そして、システム停止は診療所の業務が遂行できなくなることを意味します。 その結果、CLINICS サポートへの負荷増大や医療機関の信頼性低下などの影響を招きます。障害復旧対応で開発計画の変更が余儀なくされ、開発速度の低下を生じさせる一因になります。 しかし、CLINICS においてはこれらの非機能要件を満たすことは簡単ではありません。CLINICS は医療プラットフォームの中で最も歴史が長いプロダクトです。契約医療機関数は数千を超え、診療所の業務時間中は常に大量のデータとトラフィックを処理します。また、 CLINICS はレセコン(診療報酬請求事務を管理・自動化するシステム) などの複数のシステムで構成され、インフラなどの運用コストも高くなっています。 CLINICS を含む医療プラットフォームの成長に伴う技術課題 私自身も CLINICS のソフトウェアエンジニアとして開発に携わり、インフラに詳しいメンバー数人と共に可用性や信頼性に関するタスクを担当してきました。しかし、事業の成長に伴いシステム規模が拡大する中で、機能開発とシステム信頼性の両立が困難になっていました。 システムが深刻な状態には至っていませんでしたが、概ね以下のような課題を抱えていました。 長年の運用の中で非効率な DB へのクエリが蓄積し、処理に時間がかかる箇所が散見される トイル(SRE における反復的な運用作業)が増大し、運用業務の負荷が大きくなり、さらに悪循環を生む ライブラリ対応の優先度が下がり、 EOL 対応に追われている アラートの振り返りと暫定的な再発防止策は実施するが、根本対応までやりきれない また、これらの課題は、 Dentis や Pharms などの医療プラットフォーム内の他のプロダクトでも発生する可能性が高いです。他のプロダクトも CLINICS と同様の体制で開発と運用されており、すでに同じ課題が顕在化しつつあります。 CLINICS の医療機関数は日々増加しており、今後も事業拡大が見込まれます。それは他のプロダクトでも同様です。CLINICS に限らず医療プラットフォーム全体の成長を加速させるには、 信頼性の高い基盤構築に向き合えるチーム体制の構築が不可欠です。 医療プラットフォーム SRE チームの発足 信頼性課題などの中長期的な課題解決を目指す医療プラットフォーム SRE チームを発足しました。CLINICS に限らず、医療プラットフォーム全体を対応範囲としています。先程述べたように、システム信頼性の課題は医療プラットフォーム全体の課題であるためです。SRE チームが医療プラットフォームの横断組織として機能することで、システム信頼性に関する知見を集約します。 まずは、医療プラットフォームの中でも 中心的な役割を担う大規模なシステムである CLINICS の信頼性向上を最優先課題としています。SRE チームは CLINICS での取り組みの中で得た知見を医療プラットフォーム全体に還元します。 2025 年 4 月現在、SRE チームは CLINICS プロダクトに専任する形で活動しています。一般に知られている Embedded SRE です。この形態では、SRE チームメンバーが開発チームに直接入り込み、密に連携しながら信頼性向上に取り組みます。 CLINICS の信頼性向上を目指した SRE の取り組み ここからは CLINICS に対して現在行っている取り組みを説明します。 SRE チームの役割とミッション CLINICS における SRE チームのミッションは 「事業成長を支え、安定した開発基盤と顧客の信頼性を維持する文化、仕組みを構築すること」 です。SRE チームは信頼性向上に専念することで、開発チームは機能開発に集中できる状態を目指し、CLINICS プロダクトは事業と品質の両輪での成長を図ります。 前半で述べた課題に対応するために、SRE チームの立ち上げ時にまず 3 つの重点目標を設定しました。それぞれの目標に対する取り組みと一部の成果を紹介します。 目標 1: トイル排除と中長期課題へのシフト 課題: 日常的な運用作業(トイル)を自動化し、少人数での複数システムの運用ができ、足元の課題から中長期的な課題へ時間を使えるようにする 主な取り組み: 既存インフラリソースの Terraform import を実施 : 新しいインフラリソースは Terraform で作成されていました。しかし、リリース初期から存在するリソースは違い、インフラ運用の属人化を引き起こしていました。Terraform import を定常的に実施し、誰でも実装可能で、レビューしやすい運用を実現しました。 定常的に発生する運用業務の自動化 : CLINICS は運用するシステムも多く、月次の更新処理や定常的なスケールアップ対応など、数多くの運用タスクがありました。 AWS Step Functions などにより定常的な業務を自動化し、運用負荷軽減を実現しました。 目標 2: 改善文化の浸透 課題: ポストモーテム 文化(障害後の振り返り文化)を確立し、再発防止が適切に実行され、改善課題が積み上がり続ける状態から脱する。 主な取り組み: ポストモーテムの標準化: 以前から振り返りの文化はありましたが、定式化されておらず、各々が各自の形式で振り返りを実施していました。 チームでのナレッジ共有がなされず、障害対応での属人化の要因となっていました。体系的に障害を振り返る仕組みを作り、CLINICS のチーム全体に展開することで、チームでの障害対応力を向上させました。 アラート改善を定常的に回す仕組みの策定 : 緊急性が高い事象や顧客とのコミュニケーションが必要な事象はアラートの検知時に迅速に解決できていました。しかし、タイミングの問題で発生する瑣末なフロントエンドのエラーなどは「オオカミ少年」になり、アラートを定期的に検知していました。開発チーム全体で根本対応できるものは実施する、すぐに解決が難しいものは一定期間無視するなど、対応ルールを精緻化しました。 目標 3: SLO による信頼性可視化 課題: CLINICS の関係者が納得できる 信頼性の指標(SLO) を構築し、改善施策はこれらの指標改善を優先する 主な取り組み: 重要な顧客体験 (Critical User Journey) の策定と SLO ダッシュボードの作成: CUJ(Critical User Journey) を特定し、Datadog 上に SLO のダッシュボードを作成しました。フロントエンドのパフォーマンスモニタリングを目的とした Datadog RUM(Realtime User Monitoring) などを導入することで、オブザーバビリティの強化も実現し、顧客体験をより厳密にトラッキングできるようにしました。 SLO モニタリングの実施: 作成したダッシュボードをもとに受付、診察、会計など 3 つの顧客体験について、可用性、レイテンシー、エラー率の目標値を設定しました。閾値の調整やエラーバジェットのモニタリングを定期実施し、アラート改善に役立てる取り組みを実現しました。 SRE チームにおけるその他の取り組み 上記の取り組み以外にも、SRE の一般的な以下の業務にも取り組んでいます。 事業成長を見越したキャパシティプランニング システムのモニタリングによる事前の課題発見 障害対応プロセスの改善 インフラコストの削減 DB 等のパフォーマンス問題の改善 ライブラリ対応やセキュリティ対応の効率化 アーキテクチャとリリースフローの改善…など SRE チームの取り組みの成果が実を結び始める これらの取り組みにより、CLINICS 開発メンバー全体(開発チームを含む)の運用負担が軽減されました。 信頼性の維持をしつつ、目の前の課題だけでなく将来を見据えた取り組みに注力する余裕が生まれています。開発チームとの協業を強化し、機能開発時に SLO の組み込みを進め、改善目標の指標としても活用する計画を立てています。他にも障害時の信頼性向上を目的とした Sidekiq Pro の導入など、信頼性向上にも取り組んでいます。 このように私たちの取り組みが徐々に成果を上げ始めています。 開発チームと SRE チームの協働により、当初実現したかった CLINICS の機能開発とシステム信頼性の両立が実現しつつあります 。今後もこの連携と SRE の取り組みを深め、より安定したサービス提供を目指してまいります。 医療プラットフォーム SRE チームとしての展望 ここまでは、CLINICS における SRE の取り組みを書いてきました。ここからは、今後医療プラットフォーム全体に SRE としてどのように貢献しようとしているのかの展望を述べます。 医療プラットフォーム全体への SRE ノウハウ展開 CLINICS での知見を活かし、今後、 Dentis や Pharms といった医療プラットフォームの他のプロダクトにも SRE のノウハウを展開していく予定です。 CLINICS は規模が大きく、信頼性要求も高いため、培ったノウハウは他のプロダクトにも十分応用できると考えています。ノウハウを展開することで、医療プラットフォーム全体での信頼性の底上げを実現します。 プロダクトの拡充に対して柔軟に対応できる仕組みづくり 医療プラットフォームは、「医療ヘルスケアの未来をつくる」というミッション実現のために、今後も新規開発や M&A によるプロダクト増加も見込まれます。 新たなプロダクトが加わった際にも、医療プラットフォーム標準の信頼性水準に迅速に引き上げることが重要です。 SRE チームは、医療プラットフォームに属する全てのプロダクトが高い信頼性を維持できる体制を目指します。 事業運営に関わる全関係者が納得する SRE 文化の醸成 SRE の取り組みは開発チームだけのものではありません。ポストモーテムや開発優先度の判断において、非開発メンバーの視点は不可欠です。プロダクトマネージャーや QA メンバーはもちろん、カスタマーサクセス・サポートチームも信頼性の考え方を共有することで、「なぜ機能開発より安定性向上を優先すべきか」といった判断に事業全体の納得感が生まれます。 SLO などの指標は単なる技術的な数値ではなく、顧客体験と事業成長を支える重要な要素です。医療プラットフォーム全体で信頼性と機能開発のバランスを適切に取りながら前進できる文化を育むこと、これも SRE チームの重要なミッションの一つです。 まとめ:医療ヘルスケアの未来を支える SRE 医療プラットフォームの SRE 立ち上げについてお伝えしました。医療プラットフォームの成長と「医療ヘルスケアの未来」創造のために、SRE による信頼性確保は欠かせないミッションです。 SRE としても医療プラットフォームとしても、挑戦したいことは山積みです。事業成長を加速し、医療機関と患者の体験を向上させるために、SRE の活動は不可欠です。 何より、「医療ヘルスケアの未来をつくる」というミッションに共感し、技術で医療の課題解決に貢献したいという情熱を持つ方をお待ちしています。私たちと一緒にこのミッションを実現しませんか? 興味を持たれた方は、以下のリンクより、ぜひカジュアル面談の応募をお願いします。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp