こんにちは。医療介護求人サイト「ジョブメドレー」の開発を担当しているエンジニアの山田です。 今年の新卒エンジニア研修において、メンターを担当しました。 メドレーでは 2019 年度から新卒採用を行なっており、今年 2021 年度は 5 名の新卒がエンジニアとして入社しました。 例年と同じく 4 月から 9 月にかけて、約 5 ヶ月間の新卒エンジニア研修を実施しましたので、その取り組みを、研修受講者である新卒からの声も交えてご紹介します。 新卒研修の概要 今年の新卒研修の最終ゴールは、「 メドレーのエンジニアとして、 Our Essentials (※) を体現し、顧客へ価値提供できるようになるための基礎を身につけ、経験を得ること 」として掲げました。 ※) メドレーの行動原則 メドレーの新卒エンジニア研修では、技術を身につけることだけではなく、ビジネスパーソンとしての基礎を身につけ、メドレーが大切にしている価値観を理解し、体現する意識をもって、顧客への価値提供について自分の言葉で話せるようになることまでを目指してもらいます。 研修は昨年同様、大きく分けて、4 つのフェーズに区切って行いました。 また、全研修期間を通じて、各新卒にはメンターを一人ずつ付けました。メンターは、一週間に 1~2 回のペースで新卒と 1on1 ミーティングを実施し、フィジカルとメンタルの両面を気遣い、個別にフォローを行いました。 新卒研修の内容 フェーズ 1:社会人&メドレー基礎研修 リスク研修 インサイダー取引防止研修 コンプライアンス研修 情報セキュリティ研修 ビジネス研修 ビジネスマナー研修 ビジネススキル研修 ビジネススタンス研修(外部研修) フェーズ 1 では、 成果を出し、価値を発揮するために必要なビジネスパーソンとしての基本的な仕事の型を身につけること をゴールとしました。 リスク研修では、メドレー社員として、社会人として、身の周りで起こりうるリスクについて考え、いかにそれらのリスクと向き合うかを講義形式で学んでもらいました。 ビジネス研修では、社会人としての最低限のマナーを学び、論理的思考力、コミュニケーション力など、エンジニア職に限らない課題解決力へつながるポータブルな知識を、座学とワークショップを通じて定着してもらうことを図りました。 また、社会人の基準で仕事と向き合い、適切な報連相によって周囲と協働していくことの重要性についても学んでもらいました。 新卒からの声 質の高い、多量のインプット・アウトプットができた 伝わるメールの書き方、名刺の渡し方など、社会人に必須のマナーやスキルを認識できた ワークを通じて、言葉では理解していても行動するとできないことを洗い出せた フェーズ 2:エンジニア基礎研修 開発基礎 1 メドレーエンジニアとして求めること 事業(ジョブメドレー ・ CLINICS ・介護のほんね)の概要説明 開発基礎研修(Ruby on Rails チュートリアル) 開発実践 (要件定義〜リリースまで) 開発基礎 2 技術書の輪読会 ドキュメンテーションスキル研修 プレゼンテーションスキル研修 中間レポート作成 中間報告会 フェーズ 2 では、新卒研修後に開発業務に入ってもらえるよう、 エンジニアとしての基礎を身につけること をゴールとしました。 メドレーエンジニアとして求めること 開発に関わるこのフェーズにおいても、要件定義を含む汎用的な技術的スキルは勿論のこと、メドレーエンジニアが共通して持つべき価値観などを共有するため、フェーズ 2 初日は「 メドレーエンジニアとして求めること 」と題して、エンジニアの執行役員 田中が講義を行いました。 講義では、「 エンジニア とは、 エンジニアの価値 とは、 プロエンジニア とはなんでしょうか?」という問いから始まり、講義の終わりにはもう一度同じ問いかけをして締めくくり、新卒がメドレーの求めるエンジニア像について自身の言葉で話せるように考えてもらいました。 メドレーが求めるエンジニア像については、CTO 平山の メドレー平山の中央突破: THE エンジニア にも書かれていますので、よろしければ、あわせてご覧ください。 さらに、メドレーが展開する各事業および関連するプロダクトの概要説明をプロダクトマネージャーが行い、メドレーで開発する意義をあらためて認識してもらいました。 開発基礎研修 2 日目より、 Ruby on Rails チュートリアル (以下、「Rails チュートリアル」)を教材とした、開発基礎研修に移りました。 メドレーのプロダクトは Rails で作られているものが多く、Web アプリケーションを開発するための基礎を身につけるためにも、Rails チュートリアルの内容を実施してもらいました。 単純に、Rails チュートリアルの内容に沿って、ダラダラと写経するのではなく、随時、学んだことは Confluence にまとめ、GitHub 上で Pull Request を作成する形で、ソースコードを共有してもらいました。 学んだことを自分の言葉に置き換えてアウトプットすることで反復学習を促し、Pull Request を作成してもらうことで GitHub の使い方に慣れてもらうことを図りました。 また、デイリーで朝会と夕会を実施しました。朝会は仕事のリズムを整えるための顔合わせ、夕会は新卒から質問・成果を共有してメンターがそれに対してフィードバックをする場としてそれぞれ実施しました。 研修前に既に Rails チュートリアルを一周していた新卒もいましたが、二周目を実施して新たな気付きを得たり、AWS を用いてクラウド上に環境構築し、作成した Web アプリケーションをデプロイするまでを実践してもらうなど、インフラに関しても理解を深めてもらうことができました。 新卒からの声 システムのパフォーマンスを上げるための工夫を知ることができた バグ発生〜原因特定〜修正、というデバッグのスピードが、研修序盤から飛躍的に上がった プロダクトで利用している AWS の各種サービスの概要を理解できたことに加え、サービス間の繋がりやネットワークの流れに関しても理解を深めることができた 開発実践 開発基礎研修にて Web アプリケーション開発の基礎を学んだ後は、「 メドレー/グループ会社で使う来訪者受付システム 」を開発題材として、開発実践研修を行いました。 開発業務全体の流れを把握することで、 チームで開発(課題解決)することを経験し、今後の仕事に役立たせること を目的としました。 本研修で達成すべきこととして掲げていたものは主に、次の通りです。 プロジェクト管理能力を身につけること 開発する対象を体系的に整理できる能力を養うこと システム設計に関する基礎的な物事を理解すること チーム開発を理解すること 品質を理解すること 既に決まりきった仕様書に沿って開発するのではなく、新卒自身が現状の問題把握や課題整理を行って、ユーザーへ価値提供するために何を作るべきかを考えることから始まり、リリース後の運用方法やランニングコストのことまで考え提案してもらいました。 開発実践研修は約 1 ヶ月の期間をかけて行いました。大まかな流れとしては、次の通りです。 要件定義(ヒアリング・現状把握・課題整理・要求分析・機能/非機能要件の洗い出し・ UI 草案) プロジェクト計画(役割分担・ WBS/ガントチャート作成) 設計(画面設計・機能設計・データモデリング・方式設計・インフラ設計) 開発(実装・コードレビュー) QA(テスト設計・テスト) 成果発表(成果物を関係者へプレゼン・リリース) 方式設計の一部として、開発に使用する言語などの選定も新卒自身が行いました。 今回作成した社員向け管理画面と来訪者向け画面はいずれも SPA(一部、PWA)のアーキテクチャを採用し、主なライブラリ/フレームワークに関して、フロントエンドは TypeScript , React , Next.js , Chakra UI , Ionic Framework 、バックエンドは Ruby on Rails(API モード)をそれぞれ利用することとなりました。 選定理由としては主に、次の通りです。 TypeScript, React(両画面共通) ロジックからテンプレートまでの全てのコードを静的型付けで書くことができ、堅牢性に優れているため Next.js, Chakra UI(社員向け管理画面) ゼロコンフィグでビルドやレンダリングを最適化できるため アクセシビリティに優れたリッチな UI を素早く構築できるため Ionic Framework(来訪者向け画面) iPad 上で、ネイティブアプリのような UI/UX を提供するため Ruby on Rails API モード フロントエンドとバックエンドを分離して疎結合にするため 短期間で構築するため 社内でもよく使われておりメンテナンスしやすいため インフラは AWS を採用し、EC2, S3, RDS, CloudFront, Route53, CloudWatch などのサービスを利用しました。 結果的に、本研修プログラムの成果物としてリリースされたシステムは「 Medley Entrance 」という名前で、社内ツールとして現在、毎日稼働しており、ユーザーとしてメドレー/グループ社員だけではなく、来訪者の方々にも使っていただいています。 Medley Entrance(上:社員向け管理画面、下:来訪者向け画面) チームで課題解決に臨み、価値提供までの実績を残せたことは自信につながり、開発実践研修のやりがいとして、感じてもらえたのではないでしょうか。 要件定義などの期間中、想定よりもスムーズに進められなかった時も他責にせず、各々がリーダーシップを発揮し、建設的に進めていく新卒の様子をメンターの一人として傍で見させてもらいました。 この 1 ヶ月間の開発実践研修を通じて、技術力はさることながら、課題解決に対する十分な熱意と主体性を新卒から感じられ、とても頼もしい印象として残りました。 新卒からの声 開発の中での方針を意識して設計/実装することができた(シンプルにする) QA とはそもそも何かというリサーチから入り、有識者の考え方を軸に方針を決めてから始められた 各々が最適なパフォーマンスを発揮できる環境づくりを意識して、高速な意思決定が可能な体制を整えることができた 要件が決まりきっていない中で設計するのは難しかった 開発タスクが集中していた時に、プロジェクト全体の現状を把握できていなかった 文章を作るスキルが足りていない 技術書の輪読会 フェーズ 2 の開発基礎 2 の輪読会では、 『Web を支える技術 -HTTP、URI、HTML、そして REST』 を題材書籍として、7 日間に渡って毎日、次の手順で実施しました。 参加者が同じパートをあらかじめ読んでおき、書籍から学んだこと、ネットなどで調べても解消しきれなかった疑問点などをまとめる その内容をもとに、夕方のミーティング時において、各自が発表してディスカッションを行う ディスカッションした内容は議事録にまとめる 輪読会は他者からの学びを共有してもらうことで、自分にはなかった視点・気付きを獲得し、その書籍への理解をより深められる効果があります。 本研修プログラムにおける輪読会の目的としては、 Web サービスを開発していく上で必要となる知識へ触れることにより、今後獲得していくべき知識のベースラインを理解すること でしたが、輪読会ならではのメリット・楽しさを新卒に実感してもらえたことも、副次的な効果としてあったと思います。 新卒からの声 Web の基本的な知識を「なぜ登場したのか」を理解しながら網羅的に学ぶことができた 文書でまとめた後に、口で説明することが学んだ内容の定着に良いと感じた これまでなんとなく実装していたことの仕組みを学ぶことで、知識として定着することができた 中間報告会 フェーズ 2:エンジニア基礎研修最後の研修プログラムである中間報告会に向けて、ドキュメンテーションスキル研修、プレゼンテーションスキル研修を実施しました。 上記スキルが必要となる背景は、次の通りです。 エンジニアリングを通じた課題解決とはプログラムを書くだけでは解決しない場面もある 背景、目的を正しくステークホルダーへ共有しながらチームとして取り組んでいくことになる 伝えたいことを文章として整理し、他者へ分かりやすく伝えていくことが求められる また、メドレーの行動原則 Our Essentials を構成する要素として、「ドキュメントドリブン」「全てを明確に」という項目が含まれており、これらを実現するためにも、とても重要なスキルとしてメドレーは考えています。 新卒研修が終わった後も、エンジニアとして技術的なスキルを身につける機会は日常的に多くありますが、上記のようなスキルをまとめて習得する機会は少ないため、このような研修を社会人のはじめから受けておくことで、その後の伸びしろが違ってくるのではないかと思います。 研修が終わった後は、各自で報告会用の資料を作成し、研修講師からの添削を受けました。 中間報告会は各部署の開発マネージャーを発表相手として、当日は程よい緊張感をもって、良い雰囲気で報告会を終えられました。 フェーズ 3:事業部 OJT 事業部研修 取締役豊田からの講義 CLINICS 事業部研修 ジョブメドレー事業部研修 開発 OJT システム全体像説明 環境構築 各プロダクトの開発チームでの OJT フェーズ 3 では、 顧客の課題と、顧客への価値提供のための各チームの連携を体感し、メドレーの顧客提供価値を自分の言葉で話せるようになること をゴールとしました。 フェーズ 3 のはじめに、取締役豊田による日本の医療の課題とメドレーの取り組みに関する講義を受講してもらいました。 持続可能な医療体制を構築していくためにメドレーが成すべきことなどの話を聞いた後に、新卒からの質問の受け答えによって理解を深め、メドレーの社会的意義をあらためて認識してもらい、エンジニアとしてだけではなく、 メドレー社員としての自覚 を強めてもらいました。 事業部研修 開発 OJT で手を動かす前に、自分たちが何のために開発するのかを具体的にイメージできるよう、次のように、各現場に参加してもらいました。 見込顧客への架電業務見学 商談前の社内ミーティング参加 商談現場同席 定例会議参加 事業部のスタッフが、顧客の課題に対して、どのような対応をしていて、どのようにプロダクトを説明しているのか、事業部の各チームが、どのように連携して最終的に顧客に価値を届けるのかの全体観を知ってもらうことを狙いとしていました。 開発チームのエンジニアは業務上、プロダクトのエンドユーザーである顧客の声などを商談のタイミングから聞ける機会はなかなか無いので、研修を通じて話を聞けたことは、今後の開発モチベーションにも影響する良い機会だったと思います。 新卒からの声 ユーザー、顧客、事業部が抱える課題を確認できたことで、開発以外にも目を向けるきっかけになり良かった 各部署が KPI として定めている数字を知ることができ、開発に降りてきている施策の影響箇所がどの部分かを理解できた上で、開発に取り組むことができるようになった 各部署のミーディングに参加することで、各部署がどのような考えで何を目指しているのかを理解でき、メドレー全体として目指している方向性が掴めた 開発 OJT 事業部研修に続く開発 OJT では、 ジョブメドレー 、 CLINICS 、 介護のほんね の開発チームに分かれて、研修を実施しました。 OJT 配属先では、メンターとは別に、トレーナーを付けて業務の進め方などをサポートしました。トレーナーは配属先の先輩エンジニアが担当しました。 OJT の流れとしては、初日に、プロダクトがどのように動いているのか、システム全体像を把握することから始まり、各自、ドキュメントに沿って、PC にローカル開発環境をセットアップしました。 その後は、他の先輩エンジニアと同様に、GitHub Issue で管理されている課題を解消することを日々の目標としてこなしてもらいました。ただし、単に Issue に書かれている課題をクリアしようとするのではなく、 そもそも、なぜそれをやるのか、Issue の背景や起票者の意図を十分に理解した上で、プロダクトのあるべき姿に導く ことを意識してもらいました。 Issue に書かれている内容の理解が不十分だったり、解決方針がうまく定まらない場合は随時、ミーティングの時間を設けて、Issue 起票者やトレーナーと認識合わせを行い、認識の相違から生まれるミスコミュニケーションを極力減らすよう、取り組みました。 技術的な質問に関しても、定期的に質問タイムを設けたり、複雑になりそうな実装や、つまずきポイントとなりそうな箇所に関しては、 画面共有を用いてレビュー を行い、疑問点に関してもその場で確認して、解消してもらいました。 緊急事態宣言期間中だったため、会社全体で原則、在宅勤務の体制となっており、対面でのコミュニケーションが希薄になりがちでしたが、朝会、夕会を含め、たとえ新卒から質問が無くても質問タイムでのミーティングは定期的に実施するなど、 できるだけ頻繁に顔合わせして、新卒本人の声と顔を確認する よう心がけました。 Issue へのアサインから始まって、実装 -> レビュー依頼 -> QA -> リリース -> Issue 起票者への報告まで、一連の開発フローを経験してもらい、チーム内での開発業務に慣れてもらうことができました。 フェーズ 4:最終報告 新卒研修最後のプログラムとして、メドレー役員陣に向けた最終報告会を実施しました。 最終報告会の目的としては、次の通りです。 学んだことの知識を深化させる 自らの得手・不得手を捉え、将来の成長計画を立てる 体系的に整理・文書化して他者へ伝えるスキルを向上させる 役員陣に向けてプレゼンすることで、本配属に向けた決意表明として区切りを付ける 役員陣への発表であることに加え、一人あたりの発表時間にも制限が設けられており、当日の緊張はかなりのものだったと思います。 前日に発表会場を下見して、リハーサルを入念に行うなど、当日の発表会を成功させるため、メドレーのエンジニアとしての自覚を持って、発表準備に取り組んでいました。 技術志向とプロダクト志向の両輪を目指すエンジニア募集中 メドレーの研修では、技術的な講義や実践だけで終わるのではなく、ビジネスパーソンとして必要な基礎も身につけ、なぜ開発するのかを追究し、プロダクトを通じた課題解決を実体験してもらうことを重視しています。 メドレーでは、医療ヘルスケア分野の課題解決に挑みたいエンジニアを募集しています。 新卒の学生に限らず、中途採用も行っているので、エンジニアの方で少しでも興味を持っていただけたら、是非、面談でお話ししましょう。 最後までお読みいただき、誠にありがとうございました。 P.S. 昨年、一昨年の新卒研修の様子はこちらより、それぞれご覧いただけます。 2020 年度新卒エンジニア研修について 2019 年度新卒エンジニア研修について 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめまして。メドレーのエンジニア熊本です。新卒で入社し今年で 3 年目になりまして、 2019 年度エンジニア新卒の研修 を終えてから早 2 年が経とうとしています。 そんな私ですが去年の 11 月頃から先月までの間、とあるプロジェクトのリーダーを任せてもらっていたので、そのお話をさせていただきます。 はじめに 私は新卒研修を終えてから医療介護求人サイト ジョブメドレー のチームで開発をしていましたが、そのジョブメドレーを支える社内管理システムのリニューアルプロジェクトに初期から携わっていました。 こちらのプロジェクトにつきましては、弊社デザイナーの酒井が デザイナーがデザインツールを使わずに、React を使ってデザインした話 を、弊社エンジニアの山田が GraphQL, TypeScript, React を用いて型安全に社内システムをリニューアルした話 を以前ブログにしていますので、よろしければあわせてご覧ください。 その社内管理システムをどのような流れでリニューアルし、その中で自分の役割がどう変化しどう対応したのかなどについて、次の章からお話ししていきます。 プロジェクトについて リニューアルの背景やシステムの概要については上に紹介した記事でも説明しているため割愛しますが、求職者や求人を掲載する顧客に関する業務を行っているシステムをおよそ 1 年半かけて刷新するという大きなプロジェクトでした。 システムの中でも求職者関連を「Phase1」、顧客関連を「Phase2」として分割し、リニューアルを進めました。 プロジェクト内での自分の役割の変遷 Phase1 の最初期は先輩方がアーキテクチャの設計やスケジューリングをしていました。当時まだ新卒 1 年目で未熟な私でしたが、権限管理のテーブル設計をするタスクをアサインしてもらいました。ここでは詳細を省きますが、初めてのテーブル設計で右も左も分からない状態から責任感を持って何とか形にすることができ、(もちろんリニューアル中に多少の見直しはありましたが)大きな達成感を得たことを覚えています。 各種設計、技術選定、開発の進め方などが大方固まり本格的に開発が始まるわけですが、Phase1 の際は先輩社員がプロジェクトリーダーとして引っ張っていただき、自分は開発メンバーの一員として API の作成などに奮闘していました。 GraphQL といった技術やスケジュールが厳密に引かれたプロジェクトでの開発など初めて経験することも多々ありましたが、先輩方にサポートをいただいたり、同期と切磋琢磨しながら取り組めたおかげで、Phase1 を乗り切ることができました。 さて、ここからが本題になりますが、Phase2 になるとプロジェクトメンバーの入れ替えや私自身の目標設定も重なり、プロジェクトリーダーを任せてもらうことになります。まずはプロジェクトリーダーに任命されてから、どういった仕事をしていたのかご紹介します。 プロジェクトリーダーの仕事 プロジェクトリーダーとして期待されていたことは以下の通りです。 プロジェクト管理 システム設計 開発 チームマネジメント これを更に細分化し、私の実業務と照らし合わせながら並べてみると、多少粒度にばらつきがあるかもしれませんが以下のようなことが挙げられます。 要件定義・画面設計(ディレクターとデザイナー主導で進めつつ、エンジニアも実データや既存ロジックを踏まえた観点を持ち合わせて参加しました) 開発方針の検討 開発タスクへの落とし込み 技術調査・選定 API 設計 工数算出・スケジューリング 実装・レビュー QA(Quality Assurance)テスト リリースマネジメント Phase2 は段階的にリリースを行ったため、その度に 1 から 9 までを繰り返していたような流れになります。また、上記に加え、定例ミーティングでの報告や開発メンバーのタスクマネジメントも随時行っていました。 もちろん苦労したことは多く、全部を挙げようとするとキリがないのですが、その中でもいくつかに絞った上で紹介したいと思います。 苦労と工夫 1. 「そもそも何をやればいいのか」 まず最初に苦労したことは「そもそも何をやればいいのかわからない」ということでした。初めから先ほど挙げたような動きをイメージできていたわけではなく、記事や本を読み漁ったり先輩との 1on1 で質問攻めにしたりと基本的な知識を叩き込むわけですが、実際にとった最初の動きとしては「できる部分を見つけてやっていく」ということだったと思います。 自分がリーダーに任命された時点でのプロジェクトの状況としては要件定義や画面設計が進んでいる最中でしたが、これらがまとまるのを待つのではなく「全部決まらないとやれないこと」と「現時点でやれること」を切り分けて動きました。こうしたところから少しずつリズムを作り、最終的に先ほど列挙したような一通りのことがイメージ・実行できるようになったのだと思います。 2. 工数見積もり 一般的に工数見積もりに関する記事は世の中に多く存在しますが、私の場合は工数見積もりの方法がわからなかったというよりも、「どういう思想で見積もったのか、どういう選択肢があるのか」を曖昧にしていたことが当初の問題でした。 初めて見積もった時は単に開発タスクを積み上げた工数を報告して満足してしまいましたが、様々な方のフィードバックを受けプロダクト価値を高めるためにどういう動きができるのかを考える必要があったことを痛感しました。単純に工数を積み上げる場合や事業的な都合を踏まえてミニマムで開発する場合など、いくつかの選択肢をそれぞれの軸で考える必要があったことを学びました(この時期は夜な夜な夢の中で工数見積もりをしていたのも今ではいい思い出です)。 3. 意思決定 これはいつになっても正解が存在する類のものではないのですが、特に意思決定には苦労しました。意思決定といっても開発方針から技術選定まで様々な粒度のものがありますが、特に最初から苦労したのは技術的な決定でした。 それまで先輩に頼ることの多かった私がプロジェクトリーダーになった直後から何もかもできるようになるわけではないことは明々白々ですが、「自分が決めないと」と焦ってしまっていた時期もあったと思います。 そこで一度立ち止まって意識したことは、「何ができて何ができないのかを他者に明示する」ことでした。はっきりと自分に足りていないことを他者に伝えることで、周りもサポートしやすくなると思いますし、自分自身なにがやれることなのか明確になるので単純なことですが効果的であったと思います。他にも開発メンバーの提案で、インセプションデッキを取り入れてみたことも効果的でした。 また、意思決定とは文脈が少し変わってきますが、モブプロやペアプロを実施してチーム力を高め属人化をなくしつつ開発効率を向上させる取り組みも、時間が経てば経つほど効果を実感できて良かったと思います。このようにアジャイル開発の手法からチームにフィットする手法をいくつか取り入れることもできました。 プロジェクトを通して成長したこと これまで小出しで色々とお話しさせていただきましたが、自分が特に成長したと感じていることをまとめさせていただきます。 一通りの経験を通して得られたリード力 「API 設計だけ」ではなく一通り全てを任せていただいたことはとても大きな経験になりました。初めて個人ではなくチーム・プロジェクト全体として効率が良くなる動きを考える経験もできたと思います。 技術力 もちろん実装を通じて得た技術は数えきれないほどありますが、その中でも特に責任を持って他者のコードをレビューしたり、自分が書くコードの影響範囲やスコープを意識し続けたことが大きな糧になっている気がします。 リスク管理力 スケジュール遅延のリスク、方向性がずれてしまうリスク、技術的なリスク、様々ありますがこれらのリスクヘッジを考える力がプロジェクトリーダーには必要です。 リスク管理において「先読みが大切」とよく言われますが、私の場合はある先輩社員から「常に 2 週間先を見据えておけ」という具体的な日数のアドバイスをいただきました。具体的にすることであらゆることが想像しやすくなりましたし、それを 1 年以上毎日意識し実行し続けたことが、プロジェクトをやり切ることができた要因にもなっていると思います。もちろんこの言葉は家宝にしようと思っています。 価値に対する視野 何よりも「プロダクトのユーザーに価値を提供すること」の意味を理解しました。ここまでに書いてきたようなスケジュール管理やリスク管理などは、あくまでプロジェクトを遂行する上で必要な仕事の一つでしかないはずです。プロジェクトを通してシステムを使っている社員、更にはその先の顧客・求職者へ如何に価値を提供できるか考えるべきですが、一時期は「どうやるのか・なにをやるのか」というプロジェクト自体を完遂させることしか考えられていない時期もありました。 視野が狭くなっていたことに周りからの指摘で気づくことができ、それ以降は「そもそも本当にこの機能はいるのか」などユーザーの立場からの観点も徐々に身に付けることができました。これがきっかけとなり、周りとも頻繁に「なぜやるのか」を議論できるようになったと思います。新卒 1 年目で口酸っぱく言われていた「目的意識」をようやく腹落ちさせ体現することができました。 最後に 最後となりますが、プロジェクトリーダーについて語ってきた私ですが、入社するまでは Web 開発未経験でして、メドレーでの成長を非常に実感しています。そんなメドレーではエンジニア・デザイナーをはじめ多くのポジションで新たなメンバーを募集していますので、少しでもご興味をお持ちいただけた方は、是非お気軽にお話しさせていただければと思います! ここまでお付き合いいただき、ありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめまして。メドレーのエンジニア熊本です。新卒で入社し今年で 3 年目になりまして、 2019 年度エンジニア新卒の研修 を終えてから早 2 年が経とうとしています。 そんな私ですが去年の 11 月頃から先月までの間、とあるプロジェクトのリーダーを任せてもらっていたので、そのお話をさせていただきます。 はじめに 私は新卒研修を終えてから医療介護求人サイト ジョブメドレー のチームで開発をしていましたが、そのジョブメドレーを支える社内管理システムのリニューアルプロジェクトに初期から携わっていました。 こちらのプロジェクトにつきましては、弊社デザイナーの酒井が デザイナーがデザインツールを使わずに、React を使ってデザインした話 を、弊社エンジニアの山田が GraphQL, TypeScript, React を用いて型安全に社内システムをリニューアルした話 を以前ブログにしていますので、よろしければあわせてご覧ください。 その社内管理システムをどのような流れでリニューアルし、その中で自分の役割がどう変化しどう対応したのかなどについて、次の章からお話ししていきます。 プロジェクトについて リニューアルの背景やシステムの概要については上に紹介した記事でも説明しているため割愛しますが、求職者や求人を掲載する顧客に関する業務を行っているシステムをおよそ 1 年半かけて刷新するという大きなプロジェクトでした。 システムの中でも求職者関連を「Phase1」、顧客関連を「Phase2」として分割し、リニューアルを進めました。 プロジェクト内での自分の役割の変遷 Phase1 の最初期は先輩方がアーキテクチャの設計やスケジューリングをしていました。当時まだ新卒 1 年目で未熟な私でしたが、権限管理のテーブル設計をするタスクをアサインしてもらいました。ここでは詳細を省きますが、初めてのテーブル設計で右も左も分からない状態から責任感を持って何とか形にすることができ、(もちろんリニューアル中に多少の見直しはありましたが)大きな達成感を得たことを覚えています。 各種設計、技術選定、開発の進め方などが大方固まり本格的に開発が始まるわけですが、Phase1 の際は先輩社員がプロジェクトリーダーとして引っ張っていただき、自分は開発メンバーの一員として API の作成などに奮闘していました。 GraphQL といった技術やスケジュールが厳密に引かれたプロジェクトでの開発など初めて経験することも多々ありましたが、先輩方にサポートをいただいたり、同期と切磋琢磨しながら取り組めたおかげで、Phase1 を乗り切ることができました。 さて、ここからが本題になりますが、Phase2 になるとプロジェクトメンバーの入れ替えや私自身の目標設定も重なり、プロジェクトリーダーを任せてもらうことになります。まずはプロジェクトリーダーに任命されてから、どういった仕事をしていたのかご紹介します。 プロジェクトリーダーの仕事 プロジェクトリーダーとして期待されていたことは以下の通りです。 プロジェクト管理 システム設計 開発 チームマネジメント これを更に細分化し、私の実業務と照らし合わせながら並べてみると、多少粒度にばらつきがあるかもしれませんが以下のようなことが挙げられます。 要件定義・画面設計(ディレクターとデザイナー主導で進めつつ、エンジニアも実データや既存ロジックを踏まえた観点を持ち合わせて参加しました) 開発方針の検討 開発タスクへの落とし込み 技術調査・選定 API 設計 工数算出・スケジューリング 実装・レビュー QA(Quality Assurance)テスト リリースマネジメント Phase2 は段階的にリリースを行ったため、その度に 1 から 9 までを繰り返していたような流れになります。また、上記に加え、定例ミーティングでの報告や開発メンバーのタスクマネジメントも随時行っていました。 もちろん苦労したことは多く、全部を挙げようとするとキリがないのですが、その中でもいくつかに絞った上で紹介したいと思います。 苦労と工夫 1. 「そもそも何をやればいいのか」 まず最初に苦労したことは「そもそも何をやればいいのかわからない」ということでした。初めから先ほど挙げたような動きをイメージできていたわけではなく、記事や本を読み漁ったり先輩との 1on1 で質問攻めにしたりと基本的な知識を叩き込むわけですが、実際にとった最初の動きとしては「できる部分を見つけてやっていく」ということだったと思います。 自分がリーダーに任命された時点でのプロジェクトの状況としては要件定義や画面設計が進んでいる最中でしたが、これらがまとまるのを待つのではなく「全部決まらないとやれないこと」と「現時点でやれること」を切り分けて動きました。こうしたところから少しずつリズムを作り、最終的に先ほど列挙したような一通りのことがイメージ・実行できるようになったのだと思います。 2. 工数見積もり 一般的に工数見積もりに関する記事は世の中に多く存在しますが、私の場合は工数見積もりの方法がわからなかったというよりも、「どういう思想で見積もったのか、どういう選択肢があるのか」を曖昧にしていたことが当初の問題でした。 初めて見積もった時は単に開発タスクを積み上げた工数を報告して満足してしまいましたが、様々な方のフィードバックを受けプロダクト価値を高めるためにどういう動きができるのかを考える必要があったことを痛感しました。単純に工数を積み上げる場合や事業的な都合を踏まえてミニマムで開発する場合など、いくつかの選択肢をそれぞれの軸で考える必要があったことを学びました(この時期は夜な夜な夢の中で工数見積もりをしていたのも今ではいい思い出です)。 3. 意思決定 これはいつになっても正解が存在する類のものではないのですが、特に意思決定には苦労しました。意思決定といっても開発方針から技術選定まで様々な粒度のものがありますが、特に最初から苦労したのは技術的な決定でした。 それまで先輩に頼ることの多かった私がプロジェクトリーダーになった直後から何もかもできるようになるわけではないことは明々白々ですが、「自分が決めないと」と焦ってしまっていた時期もあったと思います。 そこで一度立ち止まって意識したことは、「何ができて何ができないのかを他者に明示する」ことでした。はっきりと自分に足りていないことを他者に伝えることで、周りもサポートしやすくなると思いますし、自分自身なにがやれることなのか明確になるので単純なことですが効果的であったと思います。他にも開発メンバーの提案で、インセプションデッキを取り入れてみたことも効果的でした。 また、意思決定とは文脈が少し変わってきますが、モブプロやペアプロを実施してチーム力を高め属人化をなくしつつ開発効率を向上させる取り組みも、時間が経てば経つほど効果を実感できて良かったと思います。このようにアジャイル開発の手法からチームにフィットする手法をいくつか取り入れることもできました。 プロジェクトを通して成長したこと これまで小出しで色々とお話しさせていただきましたが、自分が特に成長したと感じていることをまとめさせていただきます。 一通りの経験を通して得られたリード力 「API 設計だけ」ではなく一通り全てを任せていただいたことはとても大きな経験になりました。初めて個人ではなくチーム・プロジェクト全体として効率が良くなる動きを考える経験もできたと思います。 技術力 もちろん実装を通じて得た技術は数えきれないほどありますが、その中でも特に責任を持って他者のコードをレビューしたり、自分が書くコードの影響範囲やスコープを意識し続けたことが大きな糧になっている気がします。 リスク管理力 スケジュール遅延のリスク、方向性がずれてしまうリスク、技術的なリスク、様々ありますがこれらのリスクヘッジを考える力がプロジェクトリーダーには必要です。 リスク管理において「先読みが大切」とよく言われますが、私の場合はある先輩社員から「常に 2 週間先を見据えておけ」という具体的な日数のアドバイスをいただきました。具体的にすることであらゆることが想像しやすくなりましたし、それを 1 年以上毎日意識し実行し続けたことが、プロジェクトをやり切ることができた要因にもなっていると思います。もちろんこの言葉は家宝にしようと思っています。 価値に対する視野 何よりも「プロダクトのユーザーに価値を提供すること」の意味を理解しました。ここまでに書いてきたようなスケジュール管理やリスク管理などは、あくまでプロジェクトを遂行する上で必要な仕事の一つでしかないはずです。プロジェクトを通してシステムを使っている社員、更にはその先の顧客・求職者へ如何に価値を提供できるか考えるべきですが、一時期は「どうやるのか・なにをやるのか」というプロジェクト自体を完遂させることしか考えられていない時期もありました。 視野が狭くなっていたことに周りからの指摘で気づくことができ、それ以降は「そもそも本当にこの機能はいるのか」などユーザーの立場からの観点も徐々に身に付けることができました。これがきっかけとなり、周りとも頻繁に「なぜやるのか」を議論できるようになったと思います。新卒 1 年目で口酸っぱく言われていた「目的意識」をようやく腹落ちさせ体現することができました。 最後に 最後となりますが、プロジェクトリーダーについて語ってきた私ですが、入社するまでは Web 開発未経験でして、メドレーでの成長を非常に実感しています。そんなメドレーではエンジニア・デザイナーをはじめ多くのポジションで新たなメンバーを募集していますので、少しでもご興味をお持ちいただけた方は、是非お気軽にお話しさせていただければと思います! ここまでお付き合いいただき、ありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめまして。メドレーのエンジニア熊本です。新卒で入社し今年で 3 年目になりまして、 2019 年度エンジニア新卒の研修 を終えてから早 2 年が経とうとしています。 そんな私ですが去年の 11 月頃から先月までの間、とあるプロジェクトのリーダーを任せてもらっていたので、そのお話をさせていただきます。 はじめに 私は新卒研修を終えてから医療介護求人サイト ジョブメドレー のチームで開発をしていましたが、そのジョブメドレーを支える社内管理システムのリニューアルプロジェクトに初期から携わっていました。 こちらのプロジェクトにつきましては、弊社デザイナーの酒井が デザイナーがデザインツールを使わずに、React を使ってデザインした話 を、弊社エンジニアの山田が GraphQL, TypeScript, React を用いて型安全に社内システムをリニューアルした話 を以前ブログにしていますので、よろしければあわせてご覧ください。 その社内管理システムをどのような流れでリニューアルし、その中で自分の役割がどう変化しどう対応したのかなどについて、次の章からお話ししていきます。 プロジェクトについて リニューアルの背景やシステムの概要については上に紹介した記事でも説明しているため割愛しますが、求職者や求人を掲載する顧客に関する業務を行っているシステムをおよそ 1 年半かけて刷新するという大きなプロジェクトでした。 システムの中でも求職者関連を「Phase1」、顧客関連を「Phase2」として分割し、リニューアルを進めました。 プロジェクト内での自分の役割の変遷 Phase1 の最初期は先輩方がアーキテクチャの設計やスケジューリングをしていました。当時まだ新卒 1 年目で未熟な私でしたが、権限管理のテーブル設計をするタスクをアサインしてもらいました。ここでは詳細を省きますが、初めてのテーブル設計で右も左も分からない状態から責任感を持って何とか形にすることができ、(もちろんリニューアル中に多少の見直しはありましたが)大きな達成感を得たことを覚えています。 各種設計、技術選定、開発の進め方などが大方固まり本格的に開発が始まるわけですが、Phase1 の際は先輩社員がプロジェクトリーダーとして引っ張っていただき、自分は開発メンバーの一員として API の作成などに奮闘していました。 GraphQL といった技術やスケジュールが厳密に引かれたプロジェクトでの開発など初めて経験することも多々ありましたが、先輩方にサポートをいただいたり、同期と切磋琢磨しながら取り組めたおかげで、Phase1 を乗り切ることができました。 さて、ここからが本題になりますが、Phase2 になるとプロジェクトメンバーの入れ替えや私自身の目標設定も重なり、プロジェクトリーダーを任せてもらうことになります。まずはプロジェクトリーダーに任命されてから、どういった仕事をしていたのかご紹介します。 プロジェクトリーダーの仕事 プロジェクトリーダーとして期待されていたことは以下の通りです。 プロジェクト管理 システム設計 開発 チームマネジメント これを更に細分化し、私の実業務と照らし合わせながら並べてみると、多少粒度にばらつきがあるかもしれませんが以下のようなことが挙げられます。 要件定義・画面設計(ディレクターとデザイナー主導で進めつつ、エンジニアも実データや既存ロジックを踏まえた観点を持ち合わせて参加しました) 開発方針の検討 開発タスクへの落とし込み 技術調査・選定 API 設計 工数算出・スケジューリング 実装・レビュー QA(Quality Assurance)テスト リリースマネジメント Phase2 は段階的にリリースを行ったため、その度に 1 から 9 までを繰り返していたような流れになります。また、上記に加え、定例ミーティングでの報告や開発メンバーのタスクマネジメントも随時行っていました。 もちろん苦労したことは多く、全部を挙げようとするとキリがないのですが、その中でもいくつかに絞った上で紹介したいと思います。 苦労と工夫 1. 「そもそも何をやればいいのか」 まず最初に苦労したことは「そもそも何をやればいいのかわからない」ということでした。初めから先ほど挙げたような動きをイメージできていたわけではなく、記事や本を読み漁ったり先輩との 1on1 で質問攻めにしたりと基本的な知識を叩き込むわけですが、実際にとった最初の動きとしては「できる部分を見つけてやっていく」ということだったと思います。 自分がリーダーに任命された時点でのプロジェクトの状況としては要件定義や画面設計が進んでいる最中でしたが、これらがまとまるのを待つのではなく「全部決まらないとやれないこと」と「現時点でやれること」を切り分けて動きました。こうしたところから少しずつリズムを作り、最終的に先ほど列挙したような一通りのことがイメージ・実行できるようになったのだと思います。 2. 工数見積もり 一般的に工数見積もりに関する記事は世の中に多く存在しますが、私の場合は工数見積もりの方法がわからなかったというよりも、「どういう思想で見積もったのか、どういう選択肢があるのか」を曖昧にしていたことが当初の問題でした。 初めて見積もった時は単に開発タスクを積み上げた工数を報告して満足してしまいましたが、様々な方のフィードバックを受けプロダクト価値を高めるためにどういう動きができるのかを考える必要があったことを痛感しました。単純に工数を積み上げる場合や事業的な都合を踏まえてミニマムで開発する場合など、いくつかの選択肢をそれぞれの軸で考える必要があったことを学びました(この時期は夜な夜な夢の中で工数見積もりをしていたのも今ではいい思い出です)。 3. 意思決定 これはいつになっても正解が存在する類のものではないのですが、特に意思決定には苦労しました。意思決定といっても開発方針から技術選定まで様々な粒度のものがありますが、特に最初から苦労したのは技術的な決定でした。 それまで先輩に頼ることの多かった私がプロジェクトリーダーになった直後から何もかもできるようになるわけではないことは明々白々ですが、「自分が決めないと」と焦ってしまっていた時期もあったと思います。 そこで一度立ち止まって意識したことは、「何ができて何ができないのかを他者に明示する」ことでした。はっきりと自分に足りていないことを他者に伝えることで、周りもサポートしやすくなると思いますし、自分自身なにがやれることなのか明確になるので単純なことですが効果的であったと思います。他にも開発メンバーの提案で、インセプションデッキを取り入れてみたことも効果的でした。 また、意思決定とは文脈が少し変わってきますが、モブプロやペアプロを実施してチーム力を高め属人化をなくしつつ開発効率を向上させる取り組みも、時間が経てば経つほど効果を実感できて良かったと思います。このようにアジャイル開発の手法からチームにフィットする手法をいくつか取り入れることもできました。 プロジェクトを通して成長したこと これまで小出しで色々とお話しさせていただきましたが、自分が特に成長したと感じていることをまとめさせていただきます。 一通りの経験を通して得られたリード力 「API 設計だけ」ではなく一通り全てを任せていただいたことはとても大きな経験になりました。初めて個人ではなくチーム・プロジェクト全体として効率が良くなる動きを考える経験もできたと思います。 技術力 もちろん実装を通じて得た技術は数えきれないほどありますが、その中でも特に責任を持って他者のコードをレビューしたり、自分が書くコードの影響範囲やスコープを意識し続けたことが大きな糧になっている気がします。 リスク管理力 スケジュール遅延のリスク、方向性がずれてしまうリスク、技術的なリスク、様々ありますがこれらのリスクヘッジを考える力がプロジェクトリーダーには必要です。 リスク管理において「先読みが大切」とよく言われますが、私の場合はある先輩社員から「常に 2 週間先を見据えておけ」という具体的な日数のアドバイスをいただきました。具体的にすることであらゆることが想像しやすくなりましたし、それを 1 年以上毎日意識し実行し続けたことが、プロジェクトをやり切ることができた要因にもなっていると思います。もちろんこの言葉は家宝にしようと思っています。 価値に対する視野 何よりも「プロダクトのユーザーに価値を提供すること」の意味を理解しました。ここまでに書いてきたようなスケジュール管理やリスク管理などは、あくまでプロジェクトを遂行する上で必要な仕事の一つでしかないはずです。プロジェクトを通してシステムを使っている社員、更にはその先の顧客・求職者へ如何に価値を提供できるか考えるべきですが、一時期は「どうやるのか・なにをやるのか」というプロジェクト自体を完遂させることしか考えられていない時期もありました。 視野が狭くなっていたことに周りからの指摘で気づくことができ、それ以降は「そもそも本当にこの機能はいるのか」などユーザーの立場からの観点も徐々に身に付けることができました。これがきっかけとなり、周りとも頻繁に「なぜやるのか」を議論できるようになったと思います。新卒 1 年目で口酸っぱく言われていた「目的意識」をようやく腹落ちさせ体現することができました。 最後に 最後となりますが、プロジェクトリーダーについて語ってきた私ですが、入社するまでは Web 開発未経験でして、メドレーでの成長を非常に実感しています。そんなメドレーではエンジニア・デザイナーをはじめ多くのポジションで新たなメンバーを募集していますので、少しでもご興味をお持ちいただけた方は、是非お気軽にお話しさせていただければと思います! ここまでお付き合いいただき、ありがとうございました。 https://www.medley.jp/jobs/
はじめまして。メドレーのエンジニア熊本です。新卒で入社し今年で 3 年目になりまして、 2019 年度エンジニア新卒の研修 を終えてから早 2 年が経とうとしています。 そんな私ですが去年の 11 月頃から先月までの間、とあるプロジェクトのリーダーを任せてもらっていたので、そのお話をさせていただきます。 はじめに 私は新卒研修を終えてから医療介護求人サイト ジョブメドレー のチームで開発をしていましたが、そのジョブメドレーを支える社内管理システムのリニューアルプロジェクトに初期から携わっていました。 こちらのプロジェクトにつきましては、弊社デザイナーの酒井が デザイナーがデザインツールを使わずに、React を使ってデザインした話 を、弊社エンジニアの山田が GraphQL, TypeScript, React を用いて型安全に社内システムをリニューアルした話 を以前ブログにしていますので、よろしければあわせてご覧ください。 その社内管理システムをどのような流れでリニューアルし、その中で自分の役割がどう変化しどう対応したのかなどについて、次の章からお話ししていきます。 プロジェクトについて リニューアルの背景やシステムの概要については上に紹介した記事でも説明しているため割愛しますが、求職者や求人を掲載する顧客に関する業務を行っているシステムをおよそ 1 年半かけて刷新するという大きなプロジェクトでした。 システムの中でも求職者関連を「Phase1」、顧客関連を「Phase2」として分割し、リニューアルを進めました。 プロジェクト内での自分の役割の変遷 Phase1 の最初期は先輩方がアーキテクチャの設計やスケジューリングをしていました。当時まだ新卒 1 年目で未熟な私でしたが、権限管理のテーブル設計をするタスクをアサインしてもらいました。ここでは詳細を省きますが、初めてのテーブル設計で右も左も分からない状態から責任感を持って何とか形にすることができ、(もちろんリニューアル中に多少の見直しはありましたが)大きな達成感を得たことを覚えています。 各種設計、技術選定、開発の進め方などが大方固まり本格的に開発が始まるわけですが、Phase1 の際は先輩社員がプロジェクトリーダーとして引っ張っていただき、自分は開発メンバーの一員として API の作成などに奮闘していました。 GraphQL といった技術やスケジュールが厳密に引かれたプロジェクトでの開発など初めて経験することも多々ありましたが、先輩方にサポートをいただいたり、同期と切磋琢磨しながら取り組めたおかげで、Phase1 を乗り切ることができました。 さて、ここからが本題になりますが、Phase2 になるとプロジェクトメンバーの入れ替えや私自身の目標設定も重なり、プロジェクトリーダーを任せてもらうことになります。まずはプロジェクトリーダーに任命されてから、どういった仕事をしていたのかご紹介します。 プロジェクトリーダーの仕事 プロジェクトリーダーとして期待されていたことは以下の通りです。 プロジェクト管理 システム設計 開発 チームマネジメント これを更に細分化し、私の実業務と照らし合わせながら並べてみると、多少粒度にばらつきがあるかもしれませんが以下のようなことが挙げられます。 要件定義・画面設計(ディレクターとデザイナー主導で進めつつ、エンジニアも実データや既存ロジックを踏まえた観点を持ち合わせて参加しました) 開発方針の検討 開発タスクへの落とし込み 技術調査・選定 API 設計 工数算出・スケジューリング 実装・レビュー QA(Quality Assurance)テスト リリースマネジメント Phase2 は段階的にリリースを行ったため、その度に 1 から 9 までを繰り返していたような流れになります。また、上記に加え、定例ミーティングでの報告や開発メンバーのタスクマネジメントも随時行っていました。 もちろん苦労したことは多く、全部を挙げようとするとキリがないのですが、その中でもいくつかに絞った上で紹介したいと思います。 苦労と工夫 1. 「そもそも何をやればいいのか」 まず最初に苦労したことは「そもそも何をやればいいのかわからない」ということでした。初めから先ほど挙げたような動きをイメージできていたわけではなく、記事や本を読み漁ったり先輩との 1on1 で質問攻めにしたりと基本的な知識を叩き込むわけですが、実際にとった最初の動きとしては「できる部分を見つけてやっていく」ということだったと思います。 自分がリーダーに任命された時点でのプロジェクトの状況としては要件定義や画面設計が進んでいる最中でしたが、これらがまとまるのを待つのではなく「全部決まらないとやれないこと」と「現時点でやれること」を切り分けて動きました。こうしたところから少しずつリズムを作り、最終的に先ほど列挙したような一通りのことがイメージ・実行できるようになったのだと思います。 2. 工数見積もり 一般的に工数見積もりに関する記事は世の中に多く存在しますが、私の場合は工数見積もりの方法がわからなかったというよりも、「どういう思想で見積もったのか、どういう選択肢があるのか」を曖昧にしていたことが当初の問題でした。 初めて見積もった時は単に開発タスクを積み上げた工数を報告して満足してしまいましたが、様々な方のフィードバックを受けプロダクト価値を高めるためにどういう動きができるのかを考える必要があったことを痛感しました。単純に工数を積み上げる場合や事業的な都合を踏まえてミニマムで開発する場合など、いくつかの選択肢をそれぞれの軸で考える必要があったことを学びました(この時期は夜な夜な夢の中で工数見積もりをしていたのも今ではいい思い出です)。 3. 意思決定 これはいつになっても正解が存在する類のものではないのですが、特に意思決定には苦労しました。意思決定といっても開発方針から技術選定まで様々な粒度のものがありますが、特に最初から苦労したのは技術的な決定でした。 それまで先輩に頼ることの多かった私がプロジェクトリーダーになった直後から何もかもできるようになるわけではないことは明々白々ですが、「自分が決めないと」と焦ってしまっていた時期もあったと思います。 そこで一度立ち止まって意識したことは、「何ができて何ができないのかを他者に明示する」ことでした。はっきりと自分に足りていないことを他者に伝えることで、周りもサポートしやすくなると思いますし、自分自身なにがやれることなのか明確になるので単純なことですが効果的であったと思います。他にも開発メンバーの提案で、インセプションデッキを取り入れてみたことも効果的でした。 また、意思決定とは文脈が少し変わってきますが、モブプロやペアプロを実施してチーム力を高め属人化をなくしつつ開発効率を向上させる取り組みも、時間が経てば経つほど効果を実感できて良かったと思います。このようにアジャイル開発の手法からチームにフィットする手法をいくつか取り入れることもできました。 プロジェクトを通して成長したこと これまで小出しで色々とお話しさせていただきましたが、自分が特に成長したと感じていることをまとめさせていただきます。 一通りの経験を通して得られたリード力 「API 設計だけ」ではなく一通り全てを任せていただいたことはとても大きな経験になりました。初めて個人ではなくチーム・プロジェクト全体として効率が良くなる動きを考える経験もできたと思います。 技術力 もちろん実装を通じて得た技術は数えきれないほどありますが、その中でも特に責任を持って他者のコードをレビューしたり、自分が書くコードの影響範囲やスコープを意識し続けたことが大きな糧になっている気がします。 リスク管理力 スケジュール遅延のリスク、方向性がずれてしまうリスク、技術的なリスク、様々ありますがこれらのリスクヘッジを考える力がプロジェクトリーダーには必要です。 リスク管理において「先読みが大切」とよく言われますが、私の場合はある先輩社員から「常に 2 週間先を見据えておけ」という具体的な日数のアドバイスをいただきました。具体的にすることであらゆることが想像しやすくなりましたし、それを 1 年以上毎日意識し実行し続けたことが、プロジェクトをやり切ることができた要因にもなっていると思います。もちろんこの言葉は家宝にしようと思っています。 価値に対する視野 何よりも「プロダクトのユーザーに価値を提供すること」の意味を理解しました。ここまでに書いてきたようなスケジュール管理やリスク管理などは、あくまでプロジェクトを遂行する上で必要な仕事の一つでしかないはずです。プロジェクトを通してシステムを使っている社員、更にはその先の顧客・求職者へ如何に価値を提供できるか考えるべきですが、一時期は「どうやるのか・なにをやるのか」というプロジェクト自体を完遂させることしか考えられていない時期もありました。 視野が狭くなっていたことに周りからの指摘で気づくことができ、それ以降は「そもそも本当にこの機能はいるのか」などユーザーの立場からの観点も徐々に身に付けることができました。これがきっかけとなり、周りとも頻繁に「なぜやるのか」を議論できるようになったと思います。新卒 1 年目で口酸っぱく言われていた「目的意識」をようやく腹落ちさせ体現することができました。 最後に 最後となりますが、プロジェクトリーダーについて語ってきた私ですが、入社するまでは Web 開発未経験でして、メドレーでの成長を非常に実感しています。そんなメドレーではエンジニア・デザイナーをはじめ多くのポジションで新たなメンバーを募集していますので、少しでもご興味をお持ちいただけた方は、是非お気軽にお話しさせていただければと思います! ここまでお付き合いいただき、ありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめまして。メドレーのエンジニア熊本です。新卒で入社し今年で 3 年目になりまして、 2019 年度エンジニア新卒の研修 を終えてから早 2 年が経とうとしています。 そんな私ですが去年の 11 月頃から先月までの間、とあるプロジェクトのリーダーを任せてもらっていたので、そのお話をさせていただきます。 はじめに 私は新卒研修を終えてから医療介護求人サイト ジョブメドレー のチームで開発をしていましたが、そのジョブメドレーを支える社内管理システムのリニューアルプロジェクトに初期から携わっていました。 こちらのプロジェクトにつきましては、弊社デザイナーの酒井が デザイナーがデザインツールを使わずに、React を使ってデザインした話 を、弊社エンジニアの山田が GraphQL, TypeScript, React を用いて型安全に社内システムをリニューアルした話 を以前ブログにしていますので、よろしければあわせてご覧ください。 その社内管理システムをどのような流れでリニューアルし、その中で自分の役割がどう変化しどう対応したのかなどについて、次の章からお話ししていきます。 プロジェクトについて リニューアルの背景やシステムの概要については上に紹介した記事でも説明しているため割愛しますが、求職者や求人を掲載する顧客に関する業務を行っているシステムをおよそ 1 年半かけて刷新するという大きなプロジェクトでした。 システムの中でも求職者関連を「Phase1」、顧客関連を「Phase2」として分割し、リニューアルを進めました。 プロジェクト内での自分の役割の変遷 Phase1 の最初期は先輩方がアーキテクチャの設計やスケジューリングをしていました。当時まだ新卒 1 年目で未熟な私でしたが、権限管理のテーブル設計をするタスクをアサインしてもらいました。ここでは詳細を省きますが、初めてのテーブル設計で右も左も分からない状態から責任感を持って何とか形にすることができ、(もちろんリニューアル中に多少の見直しはありましたが)大きな達成感を得たことを覚えています。 各種設計、技術選定、開発の進め方などが大方固まり本格的に開発が始まるわけですが、Phase1 の際は先輩社員がプロジェクトリーダーとして引っ張っていただき、自分は開発メンバーの一員として API の作成などに奮闘していました。 GraphQL といった技術やスケジュールが厳密に引かれたプロジェクトでの開発など初めて経験することも多々ありましたが、先輩方にサポートをいただいたり、同期と切磋琢磨しながら取り組めたおかげで、Phase1 を乗り切ることができました。 さて、ここからが本題になりますが、Phase2 になるとプロジェクトメンバーの入れ替えや私自身の目標設定も重なり、プロジェクトリーダーを任せてもらうことになります。まずはプロジェクトリーダーに任命されてから、どういった仕事をしていたのかご紹介します。 プロジェクトリーダーの仕事 プロジェクトリーダーとして期待されていたことは以下の通りです。 プロジェクト管理 システム設計 開発 チームマネジメント これを更に細分化し、私の実業務と照らし合わせながら並べてみると、多少粒度にばらつきがあるかもしれませんが以下のようなことが挙げられます。 要件定義・画面設計(ディレクターとデザイナー主導で進めつつ、エンジニアも実データや既存ロジックを踏まえた観点を持ち合わせて参加しました) 開発方針の検討 開発タスクへの落とし込み 技術調査・選定 API 設計 工数算出・スケジューリング 実装・レビュー QA(Quality Assurance)テスト リリースマネジメント Phase2 は段階的にリリースを行ったため、その度に 1 から 9 までを繰り返していたような流れになります。また、上記に加え、定例ミーティングでの報告や開発メンバーのタスクマネジメントも随時行っていました。 もちろん苦労したことは多く、全部を挙げようとするとキリがないのですが、その中でもいくつかに絞った上で紹介したいと思います。 苦労と工夫 1. 「そもそも何をやればいいのか」 まず最初に苦労したことは「そもそも何をやればいいのかわからない」ということでした。初めから先ほど挙げたような動きをイメージできていたわけではなく、記事や本を読み漁ったり先輩との 1on1 で質問攻めにしたりと基本的な知識を叩き込むわけですが、実際にとった最初の動きとしては「できる部分を見つけてやっていく」ということだったと思います。 自分がリーダーに任命された時点でのプロジェクトの状況としては要件定義や画面設計が進んでいる最中でしたが、これらがまとまるのを待つのではなく「全部決まらないとやれないこと」と「現時点でやれること」を切り分けて動きました。こうしたところから少しずつリズムを作り、最終的に先ほど列挙したような一通りのことがイメージ・実行できるようになったのだと思います。 2. 工数見積もり 一般的に工数見積もりに関する記事は世の中に多く存在しますが、私の場合は工数見積もりの方法がわからなかったというよりも、「どういう思想で見積もったのか、どういう選択肢があるのか」を曖昧にしていたことが当初の問題でした。 初めて見積もった時は単に開発タスクを積み上げた工数を報告して満足してしまいましたが、様々な方のフィードバックを受けプロダクト価値を高めるためにどういう動きができるのかを考える必要があったことを痛感しました。単純に工数を積み上げる場合や事業的な都合を踏まえてミニマムで開発する場合など、いくつかの選択肢をそれぞれの軸で考える必要があったことを学びました(この時期は夜な夜な夢の中で工数見積もりをしていたのも今ではいい思い出です)。 3. 意思決定 これはいつになっても正解が存在する類のものではないのですが、特に意思決定には苦労しました。意思決定といっても開発方針から技術選定まで様々な粒度のものがありますが、特に最初から苦労したのは技術的な決定でした。 それまで先輩に頼ることの多かった私がプロジェクトリーダーになった直後から何もかもできるようになるわけではないことは明々白々ですが、「自分が決めないと」と焦ってしまっていた時期もあったと思います。 そこで一度立ち止まって意識したことは、「何ができて何ができないのかを他者に明示する」ことでした。はっきりと自分に足りていないことを他者に伝えることで、周りもサポートしやすくなると思いますし、自分自身なにがやれることなのか明確になるので単純なことですが効果的であったと思います。他にも開発メンバーの提案で、インセプションデッキを取り入れてみたことも効果的でした。 また、意思決定とは文脈が少し変わってきますが、モブプロやペアプロを実施してチーム力を高め属人化をなくしつつ開発効率を向上させる取り組みも、時間が経てば経つほど効果を実感できて良かったと思います。このようにアジャイル開発の手法からチームにフィットする手法をいくつか取り入れることもできました。 プロジェクトを通して成長したこと これまで小出しで色々とお話しさせていただきましたが、自分が特に成長したと感じていることをまとめさせていただきます。 一通りの経験を通して得られたリード力 「API 設計だけ」ではなく一通り全てを任せていただいたことはとても大きな経験になりました。初めて個人ではなくチーム・プロジェクト全体として効率が良くなる動きを考える経験もできたと思います。 技術力 もちろん実装を通じて得た技術は数えきれないほどありますが、その中でも特に責任を持って他者のコードをレビューしたり、自分が書くコードの影響範囲やスコープを意識し続けたことが大きな糧になっている気がします。 リスク管理力 スケジュール遅延のリスク、方向性がずれてしまうリスク、技術的なリスク、様々ありますがこれらのリスクヘッジを考える力がプロジェクトリーダーには必要です。 リスク管理において「先読みが大切」とよく言われますが、私の場合はある先輩社員から「常に 2 週間先を見据えておけ」という具体的な日数のアドバイスをいただきました。具体的にすることであらゆることが想像しやすくなりましたし、それを 1 年以上毎日意識し実行し続けたことが、プロジェクトをやり切ることができた要因にもなっていると思います。もちろんこの言葉は家宝にしようと思っています。 価値に対する視野 何よりも「プロダクトのユーザーに価値を提供すること」の意味を理解しました。ここまでに書いてきたようなスケジュール管理やリスク管理などは、あくまでプロジェクトを遂行する上で必要な仕事の一つでしかないはずです。プロジェクトを通してシステムを使っている社員、更にはその先の顧客・求職者へ如何に価値を提供できるか考えるべきですが、一時期は「どうやるのか・なにをやるのか」というプロジェクト自体を完遂させることしか考えられていない時期もありました。 視野が狭くなっていたことに周りからの指摘で気づくことができ、それ以降は「そもそも本当にこの機能はいるのか」などユーザーの立場からの観点も徐々に身に付けることができました。これがきっかけとなり、周りとも頻繁に「なぜやるのか」を議論できるようになったと思います。新卒 1 年目で口酸っぱく言われていた「目的意識」をようやく腹落ちさせ体現することができました。 最後に 最後となりますが、プロジェクトリーダーについて語ってきた私ですが、入社するまでは Web 開発未経験でして、メドレーでの成長を非常に実感しています。そんなメドレーではエンジニア・デザイナーをはじめ多くのポジションで新たなメンバーを募集していますので、少しでもご興味をお持ちいただけた方は、是非お気軽にお話しさせていただければと思います! ここまでお付き合いいただき、ありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめまして。メドレーのエンジニア熊本です。新卒で入社し今年で 3 年目になりまして、 2019 年度エンジニア新卒の研修 を終えてから早 2 年が経とうとしています。 そんな私ですが去年の 11 月頃から先月までの間、とあるプロジェクトのリーダーを任せてもらっていたので、そのお話をさせていただきます。 はじめに 私は新卒研修を終えてから医療介護求人サイト ジョブメドレー のチームで開発をしていましたが、そのジョブメドレーを支える社内管理システムのリニューアルプロジェクトに初期から携わっていました。 こちらのプロジェクトにつきましては、弊社デザイナーの酒井が デザイナーがデザインツールを使わずに、React を使ってデザインした話 を、弊社エンジニアの山田が GraphQL, TypeScript, React を用いて型安全に社内システムをリニューアルした話 を以前ブログにしていますので、よろしければあわせてご覧ください。 その社内管理システムをどのような流れでリニューアルし、その中で自分の役割がどう変化しどう対応したのかなどについて、次の章からお話ししていきます。 プロジェクトについて リニューアルの背景やシステムの概要については上に紹介した記事でも説明しているため割愛しますが、求職者や求人を掲載する顧客に関する業務を行っているシステムをおよそ 1 年半かけて刷新するという大きなプロジェクトでした。 システムの中でも求職者関連を「Phase1」、顧客関連を「Phase2」として分割し、リニューアルを進めました。 プロジェクト内での自分の役割の変遷 Phase1 の最初期は先輩方がアーキテクチャの設計やスケジューリングをしていました。当時まだ新卒 1 年目で未熟な私でしたが、権限管理のテーブル設計をするタスクをアサインしてもらいました。ここでは詳細を省きますが、初めてのテーブル設計で右も左も分からない状態から責任感を持って何とか形にすることができ、(もちろんリニューアル中に多少の見直しはありましたが)大きな達成感を得たことを覚えています。 各種設計、技術選定、開発の進め方などが大方固まり本格的に開発が始まるわけですが、Phase1 の際は先輩社員がプロジェクトリーダーとして引っ張っていただき、自分は開発メンバーの一員として API の作成などに奮闘していました。 GraphQL といった技術やスケジュールが厳密に引かれたプロジェクトでの開発など初めて経験することも多々ありましたが、先輩方にサポートをいただいたり、同期と切磋琢磨しながら取り組めたおかげで、Phase1 を乗り切ることができました。 さて、ここからが本題になりますが、Phase2 になるとプロジェクトメンバーの入れ替えや私自身の目標設定も重なり、プロジェクトリーダーを任せてもらうことになります。まずはプロジェクトリーダーに任命されてから、どういった仕事をしていたのかご紹介します。 プロジェクトリーダーの仕事 プロジェクトリーダーとして期待されていたことは以下の通りです。 プロジェクト管理 システム設計 開発 チームマネジメント これを更に細分化し、私の実業務と照らし合わせながら並べてみると、多少粒度にばらつきがあるかもしれませんが以下のようなことが挙げられます。 要件定義・画面設計(ディレクターとデザイナー主導で進めつつ、エンジニアも実データや既存ロジックを踏まえた観点を持ち合わせて参加しました) 開発方針の検討 開発タスクへの落とし込み 技術調査・選定 API 設計 工数算出・スケジューリング 実装・レビュー QA(Quality Assurance)テスト リリースマネジメント Phase2 は段階的にリリースを行ったため、その度に 1 から 9 までを繰り返していたような流れになります。また、上記に加え、定例ミーティングでの報告や開発メンバーのタスクマネジメントも随時行っていました。 もちろん苦労したことは多く、全部を挙げようとするとキリがないのですが、その中でもいくつかに絞った上で紹介したいと思います。 苦労と工夫 1. 「そもそも何をやればいいのか」 まず最初に苦労したことは「そもそも何をやればいいのかわからない」ということでした。初めから先ほど挙げたような動きをイメージできていたわけではなく、記事や本を読み漁ったり先輩との 1on1 で質問攻めにしたりと基本的な知識を叩き込むわけですが、実際にとった最初の動きとしては「できる部分を見つけてやっていく」ということだったと思います。 自分がリーダーに任命された時点でのプロジェクトの状況としては要件定義や画面設計が進んでいる最中でしたが、これらがまとまるのを待つのではなく「全部決まらないとやれないこと」と「現時点でやれること」を切り分けて動きました。こうしたところから少しずつリズムを作り、最終的に先ほど列挙したような一通りのことがイメージ・実行できるようになったのだと思います。 2. 工数見積もり 一般的に工数見積もりに関する記事は世の中に多く存在しますが、私の場合は工数見積もりの方法がわからなかったというよりも、「どういう思想で見積もったのか、どういう選択肢があるのか」を曖昧にしていたことが当初の問題でした。 初めて見積もった時は単に開発タスクを積み上げた工数を報告して満足してしまいましたが、様々な方のフィードバックを受けプロダクト価値を高めるためにどういう動きができるのかを考える必要があったことを痛感しました。単純に工数を積み上げる場合や事業的な都合を踏まえてミニマムで開発する場合など、いくつかの選択肢をそれぞれの軸で考える必要があったことを学びました(この時期は夜な夜な夢の中で工数見積もりをしていたのも今ではいい思い出です)。 3. 意思決定 これはいつになっても正解が存在する類のものではないのですが、特に意思決定には苦労しました。意思決定といっても開発方針から技術選定まで様々な粒度のものがありますが、特に最初から苦労したのは技術的な決定でした。 それまで先輩に頼ることの多かった私がプロジェクトリーダーになった直後から何もかもできるようになるわけではないことは明々白々ですが、「自分が決めないと」と焦ってしまっていた時期もあったと思います。 そこで一度立ち止まって意識したことは、「何ができて何ができないのかを他者に明示する」ことでした。はっきりと自分に足りていないことを他者に伝えることで、周りもサポートしやすくなると思いますし、自分自身なにがやれることなのか明確になるので単純なことですが効果的であったと思います。他にも開発メンバーの提案で、インセプションデッキを取り入れてみたことも効果的でした。 また、意思決定とは文脈が少し変わってきますが、モブプロやペアプロを実施してチーム力を高め属人化をなくしつつ開発効率を向上させる取り組みも、時間が経てば経つほど効果を実感できて良かったと思います。このようにアジャイル開発の手法からチームにフィットする手法をいくつか取り入れることもできました。 プロジェクトを通して成長したこと これまで小出しで色々とお話しさせていただきましたが、自分が特に成長したと感じていることをまとめさせていただきます。 一通りの経験を通して得られたリード力 「API 設計だけ」ではなく一通り全てを任せていただいたことはとても大きな経験になりました。初めて個人ではなくチーム・プロジェクト全体として効率が良くなる動きを考える経験もできたと思います。 技術力 もちろん実装を通じて得た技術は数えきれないほどありますが、その中でも特に責任を持って他者のコードをレビューしたり、自分が書くコードの影響範囲やスコープを意識し続けたことが大きな糧になっている気がします。 リスク管理力 スケジュール遅延のリスク、方向性がずれてしまうリスク、技術的なリスク、様々ありますがこれらのリスクヘッジを考える力がプロジェクトリーダーには必要です。 リスク管理において「先読みが大切」とよく言われますが、私の場合はある先輩社員から「常に 2 週間先を見据えておけ」という具体的な日数のアドバイスをいただきました。具体的にすることであらゆることが想像しやすくなりましたし、それを 1 年以上毎日意識し実行し続けたことが、プロジェクトをやり切ることができた要因にもなっていると思います。もちろんこの言葉は家宝にしようと思っています。 価値に対する視野 何よりも「プロダクトのユーザーに価値を提供すること」の意味を理解しました。ここまでに書いてきたようなスケジュール管理やリスク管理などは、あくまでプロジェクトを遂行する上で必要な仕事の一つでしかないはずです。プロジェクトを通してシステムを使っている社員、更にはその先の顧客・求職者へ如何に価値を提供できるか考えるべきですが、一時期は「どうやるのか・なにをやるのか」というプロジェクト自体を完遂させることしか考えられていない時期もありました。 視野が狭くなっていたことに周りからの指摘で気づくことができ、それ以降は「そもそも本当にこの機能はいるのか」などユーザーの立場からの観点も徐々に身に付けることができました。これがきっかけとなり、周りとも頻繁に「なぜやるのか」を議論できるようになったと思います。新卒 1 年目で口酸っぱく言われていた「目的意識」をようやく腹落ちさせ体現することができました。 最後に 最後となりますが、プロジェクトリーダーについて語ってきた私ですが、入社するまでは Web 開発未経験でして、メドレーでの成長を非常に実感しています。そんなメドレーではエンジニア・デザイナーをはじめ多くのポジションで新たなメンバーを募集していますので、少しでもご興味をお持ちいただけた方は、是非お気軽にお話しさせていただければと思います! ここまでお付き合いいただき、ありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめまして。メドレーのエンジニア熊本です。新卒で入社し今年で 3 年目になりまして、 2019 年度エンジニア新卒の研修 を終えてから早 2 年が経とうとしています。 そんな私ですが去年の 11 月頃から先月までの間、とあるプロジェクトのリーダーを任せてもらっていたので、そのお話をさせていただきます。 はじめに 私は新卒研修を終えてから医療介護求人サイト ジョブメドレー のチームで開発をしていましたが、そのジョブメドレーを支える社内管理システムのリニューアルプロジェクトに初期から携わっていました。 こちらのプロジェクトにつきましては、弊社デザイナーの酒井が デザイナーがデザインツールを使わずに、React を使ってデザインした話 を、弊社エンジニアの山田が GraphQL, TypeScript, React を用いて型安全に社内システムをリニューアルした話 を以前ブログにしていますので、よろしければあわせてご覧ください。 その社内管理システムをどのような流れでリニューアルし、その中で自分の役割がどう変化しどう対応したのかなどについて、次の章からお話ししていきます。 プロジェクトについて リニューアルの背景やシステムの概要については上に紹介した記事でも説明しているため割愛しますが、求職者や求人を掲載する顧客に関する業務を行っているシステムをおよそ 1 年半かけて刷新するという大きなプロジェクトでした。 システムの中でも求職者関連を「Phase1」、顧客関連を「Phase2」として分割し、リニューアルを進めました。 プロジェクト内での自分の役割の変遷 Phase1 の最初期は先輩方がアーキテクチャの設計やスケジューリングをしていました。当時まだ新卒 1 年目で未熟な私でしたが、権限管理のテーブル設計をするタスクをアサインしてもらいました。ここでは詳細を省きますが、初めてのテーブル設計で右も左も分からない状態から責任感を持って何とか形にすることができ、(もちろんリニューアル中に多少の見直しはありましたが)大きな達成感を得たことを覚えています。 各種設計、技術選定、開発の進め方などが大方固まり本格的に開発が始まるわけですが、Phase1 の際は先輩社員がプロジェクトリーダーとして引っ張っていただき、自分は開発メンバーの一員として API の作成などに奮闘していました。 GraphQL といった技術やスケジュールが厳密に引かれたプロジェクトでの開発など初めて経験することも多々ありましたが、先輩方にサポートをいただいたり、同期と切磋琢磨しながら取り組めたおかげで、Phase1 を乗り切ることができました。 さて、ここからが本題になりますが、Phase2 になるとプロジェクトメンバーの入れ替えや私自身の目標設定も重なり、プロジェクトリーダーを任せてもらうことになります。まずはプロジェクトリーダーに任命されてから、どういった仕事をしていたのかご紹介します。 プロジェクトリーダーの仕事 プロジェクトリーダーとして期待されていたことは以下の通りです。 プロジェクト管理 システム設計 開発 チームマネジメント これを更に細分化し、私の実業務と照らし合わせながら並べてみると、多少粒度にばらつきがあるかもしれませんが以下のようなことが挙げられます。 要件定義・画面設計(ディレクターとデザイナー主導で進めつつ、エンジニアも実データや既存ロジックを踏まえた観点を持ち合わせて参加しました) 開発方針の検討 開発タスクへの落とし込み 技術調査・選定 API 設計 工数算出・スケジューリング 実装・レビュー QA(Quality Assurance)テスト リリースマネジメント Phase2 は段階的にリリースを行ったため、その度に 1 から 9 までを繰り返していたような流れになります。また、上記に加え、定例ミーティングでの報告や開発メンバーのタスクマネジメントも随時行っていました。 もちろん苦労したことは多く、全部を挙げようとするとキリがないのですが、その中でもいくつかに絞った上で紹介したいと思います。 苦労と工夫 1. 「そもそも何をやればいいのか」 まず最初に苦労したことは「そもそも何をやればいいのかわからない」ということでした。初めから先ほど挙げたような動きをイメージできていたわけではなく、記事や本を読み漁ったり先輩との 1on1 で質問攻めにしたりと基本的な知識を叩き込むわけですが、実際にとった最初の動きとしては「できる部分を見つけてやっていく」ということだったと思います。 自分がリーダーに任命された時点でのプロジェクトの状況としては要件定義や画面設計が進んでいる最中でしたが、これらがまとまるのを待つのではなく「全部決まらないとやれないこと」と「現時点でやれること」を切り分けて動きました。こうしたところから少しずつリズムを作り、最終的に先ほど列挙したような一通りのことがイメージ・実行できるようになったのだと思います。 2. 工数見積もり 一般的に工数見積もりに関する記事は世の中に多く存在しますが、私の場合は工数見積もりの方法がわからなかったというよりも、「どういう思想で見積もったのか、どういう選択肢があるのか」を曖昧にしていたことが当初の問題でした。 初めて見積もった時は単に開発タスクを積み上げた工数を報告して満足してしまいましたが、様々な方のフィードバックを受けプロダクト価値を高めるためにどういう動きができるのかを考える必要があったことを痛感しました。単純に工数を積み上げる場合や事業的な都合を踏まえてミニマムで開発する場合など、いくつかの選択肢をそれぞれの軸で考える必要があったことを学びました(この時期は夜な夜な夢の中で工数見積もりをしていたのも今ではいい思い出です)。 3. 意思決定 これはいつになっても正解が存在する類のものではないのですが、特に意思決定には苦労しました。意思決定といっても開発方針から技術選定まで様々な粒度のものがありますが、特に最初から苦労したのは技術的な決定でした。 それまで先輩に頼ることの多かった私がプロジェクトリーダーになった直後から何もかもできるようになるわけではないことは明々白々ですが、「自分が決めないと」と焦ってしまっていた時期もあったと思います。 そこで一度立ち止まって意識したことは、「何ができて何ができないのかを他者に明示する」ことでした。はっきりと自分に足りていないことを他者に伝えることで、周りもサポートしやすくなると思いますし、自分自身なにがやれることなのか明確になるので単純なことですが効果的であったと思います。他にも開発メンバーの提案で、インセプションデッキを取り入れてみたことも効果的でした。 また、意思決定とは文脈が少し変わってきますが、モブプロやペアプロを実施してチーム力を高め属人化をなくしつつ開発効率を向上させる取り組みも、時間が経てば経つほど効果を実感できて良かったと思います。このようにアジャイル開発の手法からチームにフィットする手法をいくつか取り入れることもできました。 プロジェクトを通して成長したこと これまで小出しで色々とお話しさせていただきましたが、自分が特に成長したと感じていることをまとめさせていただきます。 一通りの経験を通して得られたリード力 「API 設計だけ」ではなく一通り全てを任せていただいたことはとても大きな経験になりました。初めて個人ではなくチーム・プロジェクト全体として効率が良くなる動きを考える経験もできたと思います。 技術力 もちろん実装を通じて得た技術は数えきれないほどありますが、その中でも特に責任を持って他者のコードをレビューしたり、自分が書くコードの影響範囲やスコープを意識し続けたことが大きな糧になっている気がします。 リスク管理力 スケジュール遅延のリスク、方向性がずれてしまうリスク、技術的なリスク、様々ありますがこれらのリスクヘッジを考える力がプロジェクトリーダーには必要です。 リスク管理において「先読みが大切」とよく言われますが、私の場合はある先輩社員から「常に 2 週間先を見据えておけ」という具体的な日数のアドバイスをいただきました。具体的にすることであらゆることが想像しやすくなりましたし、それを 1 年以上毎日意識し実行し続けたことが、プロジェクトをやり切ることができた要因にもなっていると思います。もちろんこの言葉は家宝にしようと思っています。 価値に対する視野 何よりも「プロダクトのユーザーに価値を提供すること」の意味を理解しました。ここまでに書いてきたようなスケジュール管理やリスク管理などは、あくまでプロジェクトを遂行する上で必要な仕事の一つでしかないはずです。プロジェクトを通してシステムを使っている社員、更にはその先の顧客・求職者へ如何に価値を提供できるか考えるべきですが、一時期は「どうやるのか・なにをやるのか」というプロジェクト自体を完遂させることしか考えられていない時期もありました。 視野が狭くなっていたことに周りからの指摘で気づくことができ、それ以降は「そもそも本当にこの機能はいるのか」などユーザーの立場からの観点も徐々に身に付けることができました。これがきっかけとなり、周りとも頻繁に「なぜやるのか」を議論できるようになったと思います。新卒 1 年目で口酸っぱく言われていた「目的意識」をようやく腹落ちさせ体現することができました。 最後に 最後となりますが、プロジェクトリーダーについて語ってきた私ですが、入社するまでは Web 開発未経験でして、メドレーでの成長を非常に実感しています。そんなメドレーではエンジニア・デザイナーをはじめ多くのポジションで新たなメンバーを募集していますので、少しでもご興味をお持ちいただけた方は、是非お気軽にお話しさせていただければと思います! ここまでお付き合いいただき、ありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめまして。メドレーのエンジニア熊本です。新卒で入社し今年で 3 年目になりまして、 2019 年度エンジニア新卒の研修 を終えてから早 2 年が経とうとしています。 そんな私ですが去年の 11 月頃から先月までの間、とあるプロジェクトのリーダーを任せてもらっていたので、そのお話をさせていただきます。 はじめに 私は新卒研修を終えてから医療介護求人サイト ジョブメドレー のチームで開発をしていましたが、そのジョブメドレーを支える社内管理システムのリニューアルプロジェクトに初期から携わっていました。 こちらのプロジェクトにつきましては、弊社デザイナーの酒井が デザイナーがデザインツールを使わずに、React を使ってデザインした話 を、弊社エンジニアの山田が GraphQL, TypeScript, React を用いて型安全に社内システムをリニューアルした話 を以前ブログにしていますので、よろしければあわせてご覧ください。 その社内管理システムをどのような流れでリニューアルし、その中で自分の役割がどう変化しどう対応したのかなどについて、次の章からお話ししていきます。 プロジェクトについて リニューアルの背景やシステムの概要については上に紹介した記事でも説明しているため割愛しますが、求職者や求人を掲載する顧客に関する業務を行っているシステムをおよそ 1 年半かけて刷新するという大きなプロジェクトでした。 システムの中でも求職者関連を「Phase1」、顧客関連を「Phase2」として分割し、リニューアルを進めました。 プロジェクト内での自分の役割の変遷 Phase1 の最初期は先輩方がアーキテクチャの設計やスケジューリングをしていました。当時まだ新卒 1 年目で未熟な私でしたが、権限管理のテーブル設計をするタスクをアサインしてもらいました。ここでは詳細を省きますが、初めてのテーブル設計で右も左も分からない状態から責任感を持って何とか形にすることができ、(もちろんリニューアル中に多少の見直しはありましたが)大きな達成感を得たことを覚えています。 各種設計、技術選定、開発の進め方などが大方固まり本格的に開発が始まるわけですが、Phase1 の際は先輩社員がプロジェクトリーダーとして引っ張っていただき、自分は開発メンバーの一員として API の作成などに奮闘していました。 GraphQL といった技術やスケジュールが厳密に引かれたプロジェクトでの開発など初めて経験することも多々ありましたが、先輩方にサポートをいただいたり、同期と切磋琢磨しながら取り組めたおかげで、Phase1 を乗り切ることができました。 さて、ここからが本題になりますが、Phase2 になるとプロジェクトメンバーの入れ替えや私自身の目標設定も重なり、プロジェクトリーダーを任せてもらうことになります。まずはプロジェクトリーダーに任命されてから、どういった仕事をしていたのかご紹介します。 プロジェクトリーダーの仕事 プロジェクトリーダーとして期待されていたことは以下の通りです。 プロジェクト管理 システム設計 開発 チームマネジメント これを更に細分化し、私の実業務と照らし合わせながら並べてみると、多少粒度にばらつきがあるかもしれませんが以下のようなことが挙げられます。 要件定義・画面設計(ディレクターとデザイナー主導で進めつつ、エンジニアも実データや既存ロジックを踏まえた観点を持ち合わせて参加しました) 開発方針の検討 開発タスクへの落とし込み 技術調査・選定 API 設計 工数算出・スケジューリング 実装・レビュー QA(Quality Assurance)テスト リリースマネジメント Phase2 は段階的にリリースを行ったため、その度に 1 から 9 までを繰り返していたような流れになります。また、上記に加え、定例ミーティングでの報告や開発メンバーのタスクマネジメントも随時行っていました。 もちろん苦労したことは多く、全部を挙げようとするとキリがないのですが、その中でもいくつかに絞った上で紹介したいと思います。 苦労と工夫 1. 「そもそも何をやればいいのか」 まず最初に苦労したことは「そもそも何をやればいいのかわからない」ということでした。初めから先ほど挙げたような動きをイメージできていたわけではなく、記事や本を読み漁ったり先輩との 1on1 で質問攻めにしたりと基本的な知識を叩き込むわけですが、実際にとった最初の動きとしては「できる部分を見つけてやっていく」ということだったと思います。 自分がリーダーに任命された時点でのプロジェクトの状況としては要件定義や画面設計が進んでいる最中でしたが、これらがまとまるのを待つのではなく「全部決まらないとやれないこと」と「現時点でやれること」を切り分けて動きました。こうしたところから少しずつリズムを作り、最終的に先ほど列挙したような一通りのことがイメージ・実行できるようになったのだと思います。 2. 工数見積もり 一般的に工数見積もりに関する記事は世の中に多く存在しますが、私の場合は工数見積もりの方法がわからなかったというよりも、「どういう思想で見積もったのか、どういう選択肢があるのか」を曖昧にしていたことが当初の問題でした。 初めて見積もった時は単に開発タスクを積み上げた工数を報告して満足してしまいましたが、様々な方のフィードバックを受けプロダクト価値を高めるためにどういう動きができるのかを考える必要があったことを痛感しました。単純に工数を積み上げる場合や事業的な都合を踏まえてミニマムで開発する場合など、いくつかの選択肢をそれぞれの軸で考える必要があったことを学びました(この時期は夜な夜な夢の中で工数見積もりをしていたのも今ではいい思い出です)。 3. 意思決定 これはいつになっても正解が存在する類のものではないのですが、特に意思決定には苦労しました。意思決定といっても開発方針から技術選定まで様々な粒度のものがありますが、特に最初から苦労したのは技術的な決定でした。 それまで先輩に頼ることの多かった私がプロジェクトリーダーになった直後から何もかもできるようになるわけではないことは明々白々ですが、「自分が決めないと」と焦ってしまっていた時期もあったと思います。 そこで一度立ち止まって意識したことは、「何ができて何ができないのかを他者に明示する」ことでした。はっきりと自分に足りていないことを他者に伝えることで、周りもサポートしやすくなると思いますし、自分自身なにがやれることなのか明確になるので単純なことですが効果的であったと思います。他にも開発メンバーの提案で、インセプションデッキを取り入れてみたことも効果的でした。 また、意思決定とは文脈が少し変わってきますが、モブプロやペアプロを実施してチーム力を高め属人化をなくしつつ開発効率を向上させる取り組みも、時間が経てば経つほど効果を実感できて良かったと思います。このようにアジャイル開発の手法からチームにフィットする手法をいくつか取り入れることもできました。 プロジェクトを通して成長したこと これまで小出しで色々とお話しさせていただきましたが、自分が特に成長したと感じていることをまとめさせていただきます。 一通りの経験を通して得られたリード力 「API 設計だけ」ではなく一通り全てを任せていただいたことはとても大きな経験になりました。初めて個人ではなくチーム・プロジェクト全体として効率が良くなる動きを考える経験もできたと思います。 技術力 もちろん実装を通じて得た技術は数えきれないほどありますが、その中でも特に責任を持って他者のコードをレビューしたり、自分が書くコードの影響範囲やスコープを意識し続けたことが大きな糧になっている気がします。 リスク管理力 スケジュール遅延のリスク、方向性がずれてしまうリスク、技術的なリスク、様々ありますがこれらのリスクヘッジを考える力がプロジェクトリーダーには必要です。 リスク管理において「先読みが大切」とよく言われますが、私の場合はある先輩社員から「常に 2 週間先を見据えておけ」という具体的な日数のアドバイスをいただきました。具体的にすることであらゆることが想像しやすくなりましたし、それを 1 年以上毎日意識し実行し続けたことが、プロジェクトをやり切ることができた要因にもなっていると思います。もちろんこの言葉は家宝にしようと思っています。 価値に対する視野 何よりも「プロダクトのユーザーに価値を提供すること」の意味を理解しました。ここまでに書いてきたようなスケジュール管理やリスク管理などは、あくまでプロジェクトを遂行する上で必要な仕事の一つでしかないはずです。プロジェクトを通してシステムを使っている社員、更にはその先の顧客・求職者へ如何に価値を提供できるか考えるべきですが、一時期は「どうやるのか・なにをやるのか」というプロジェクト自体を完遂させることしか考えられていない時期もありました。 視野が狭くなっていたことに周りからの指摘で気づくことができ、それ以降は「そもそも本当にこの機能はいるのか」などユーザーの立場からの観点も徐々に身に付けることができました。これがきっかけとなり、周りとも頻繁に「なぜやるのか」を議論できるようになったと思います。新卒 1 年目で口酸っぱく言われていた「目的意識」をようやく腹落ちさせ体現することができました。 最後に 最後となりますが、プロジェクトリーダーについて語ってきた私ですが、入社するまでは Web 開発未経験でして、メドレーでの成長を非常に実感しています。そんなメドレーではエンジニア・デザイナーをはじめ多くのポジションで新たなメンバーを募集していますので、少しでもご興味をお持ちいただけた方は、是非お気軽にお話しさせていただければと思います! ここまでお付き合いいただき、ありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
事業本部 プロダクト開発室エンジニアの日下です。 オンライン診療・服薬指導・クラウド診療支援システム「CLINICS」 の、患者・医療機関に向けたアプリケーションの機能開発、開発基盤、インフラ周りを担当しております。 今回 CLINICS が提供するオンライン診療機能に「画面共有機能」を追加しましたので、その背景・技術的な話をまとめます。 画面共有機能実装の背景 CLINICS とオンライン診療 普段皆さんが病院にかかるとき、多くの場合は病院に行き、医師の診察を対面で受け、会計をして帰るといった流れになるかと思います。 CLINICS のオンライン診療はこの流れをインターネットを通して提供するサービスです。 ※ オンライン診療は、一度、初診等で対面診療を受けた際に医師が可能と判断した場合、次回以降の診察において可能になります。また、現在は新型コロナウイルス感染症対策時限措置として、初診からオンライン診療を受けることが可能となっています。 CLINICS を利用した場合、事前に予約した時間にスマホまたは PC で待機をする、医師の診察をビデオチャットで受け、会計はクレジットカードで行われるという流れとなっています。 クラウド診療支援システムとしての CLINICS は 2016 年に「オンライン診療のためのシステム」としてローンチ され、 2018 年にはクラウド型電子カルテ機能を 、 2019 年には予約管理システム機能を 、 2020 年にはかかりつけ薬局支援システム Pharms との連携機能も追加し 、患者向けアプリからオンライン服薬指導をシームレスに受けることができるようになりました。 プロダクト開発室ではこれらオンライン診療機能・電子カルテ機能・予約管理機能・連携機能の改善を日々行っています。 画面共有機能の需要の高まり、実装の決定 このように CLINICS の改善を日々行なっている中、昨年から始まった 新型コロナウイルス感染症(COVID-19) の流行に伴った需要の増加により、オンライン診療の件数が急増しました。 CLINICS も数多くの医療機関にご利用いただく中で、オンライン診療に関わるさまざまなご要望をいただくようになりました。その中でも特に多かったものが、今回紹介する画面共有機能です。 対面での診察の際に医師が検査結果などを患者に見せながら説明するように、オンラインで診察する場合でも資料をリアルタイムで共有しながら説明ができるようになれば、今まで以上にオンラインでも質の高い診察を行えるようになります。 こういったユースケース、要望などを検討した結果、CLINICS を利用するすべての医療機関及び患者にとって大きな恩恵が見込まれたため、オンライン診察(ビデオチャット)中に医師の PC 画面をリアルタイムで患者に共有する機能として実装をすることにしました。 画面共有機能の実装 画面共有をする医師側向けのコードでどういった実装方法があるのか、大まかな流れをまとめます。 ※ 以下に記載しているコードは説明のための疑似コードですので、このままでは動作しないことにご注意ください。また、医師側の実装例を掲載しているため、患者側(画面共有を受ける側)の実装は別途必要になります。 オンライン診察開始までの処理 オンライン診察を開始するには医師側のマイクとカメラで取得した情報を患者側に送付する必要があります。ここではそこまでの実装の流れを見ていきます。 カメラ・マイクのストリームの取得 オンライン診察開始時点で医師側のマイク・カメラの情報を共有するため、まずはそれらのストリームを取得する必要があります。こういったメディアコンテンツのストリームを司るインターフェイスとして MediaStream が定義されています。 マイク・カメラの MediaStream は、例えば MediaDevices.getUserMedia() を利用して取得できます。 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); SkyWay 経由でオンライン診察を始める WebRTC で P2P のビデオチャットを利用するためには、初期の接続のための処理及び接続の維持などの処理を行う必要があります。弊社ではこのあたりの処理を WebRTC SaaS の SkyWay 及びその SDK を利用することで簡略化しています。 オンライン診察開始時には、先程取得した医師側のマイク・カメラの MediaStream を SkyWay の SDK に渡すことで、一対一でのリアルタイムビデオチャットを実現できます。 import Peer , { MediaConnection } from "skyway-js" ; const peer = new Peer ({ key: "your-api-key" }); // 事前に患者と共有しておいた peer id に対して call メソッドと MediaStream を渡すことで診察を開始できる。 const mediaConnection : MediaConnection = peer . call ( "shared-peer-id" , userMediaStream ); // 注: 患者側は送付された処理をハンドリングする機能を実装する必要がある ここまでがオンライン診察を開始するまでの処理です。 ※ 詳細は SkyWay 公式の チュートリアル などを参照ください。 画面共有の処理 ここまでで患者に対して医師側のカメラ・マイクで取得された映像・音声が表示されている状態のため、これを切り替える処理が必要になります。今回は現在接続に利用している MediaStream を、画面共有用の MediaStream に入れ替えることで実現しました。 画面の MediaStream の取得 まずは共有する画面の MediaStream を取得する必要があります。これは MediaDevices.getDisplayMedia() を使うことで実現できます。 const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); 画面共有用の MediaStream を作る getDisplayMedia() から共有する画面の MediaStream を取得できるものの、そのまま利用するとマイクの音声が入りません。 これは getDisplayMedia() から取れる MediaStream にマイクの音声が含まれていないことが原因なので、必要な画像・音声の組み合わせを持った画面共有用の MediaStream を作成することで対処ができます。 MediaStreamTrack を組みあわせて画面共有用の MediaStream を作る 画面共有用の MediaStream を作成する前にまず、MediaStreamTrack と MediaStream の関係を理解する必要があります。 MediaStreamTrack はストリームに含まれる一つのメディアトラックを表現するものです。 kind という読み取り専用プロパティがあり、オーディオトラックであれば "audio" が、ビデオトラックであれば "video" が設定されています。 また、 MediaStream は複数の MediaStreamTrack から成り、オーディオトラック・ビデオトラックを取り出すメソッドがそれぞれ MediaStream.getAudioTracks() ・ MediaStream.getVideoTracks() として実装されています。 これらを組み合わせることで、マイクと画面の MediaStreamTrack を持つ MediaStream を作ることができ、これを SkyWay の SDK に渡すことで、画面共有を実現できます。 const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingMediaStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); MediaStream の入れ替え 最後に画面共有状態への切り替えです。マイク・カメラが共有されている状態からの切り替えにはいくつかの方法が考えられます。 例えば、多重化であれば MediaConnection( Skyway の SDK の単位で、「接続先 Peer へのメディアチャネル接続」を管理する)の多重化、MediaStream の多重化、MediaStreamTrack の多重化がそれぞれ考えられます。これらの方法はマイク・カメラの切り替え時のチラつき抑制など実装上の選択肢が増えるメリットがある一方で、通信量が多くなってしまう点がデメリットと言えます。 今回は多重化をせずに既存の MediaStream を切り替える実装を紹介します。この方法のメリットは、多重化に比べると通信量が少なく、またすでに MediaStream が一つである前提で作られている場合は、画面共有を受ける側の実装の変更が不要という点です。 この方法は、 SkyWay の SDK であれば MediaConnection の replaceStream というメソッド に対して新しい MediaStream を渡すことで実現ができます。 // 画面共有用の MediaStream を渡すことで、画面共有を開始する // MediaConnection は先程 `peer.call` した際の返り値として取れているため、それを利用する mediaConnection . replaceStream ( sharingMediaStream ); 実装前に懸念していたマイク・カメラの切り替え時のチラつきなども気になるほどはなく、実用に足るような品質を保つことができることを確認しています。 実装の全体概要 以上の流れを実装すると、次のようなコードになります。 import Peer , { MediaConnection } from "skyway-js" ; /** 医師側のマイク・カメラを共有してオンライン診察開始するところまで **/ // getUserMedia()でカメラ・マイクのストリームを取得 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); // Skyway sdk の初期化処理 const peer = new Peer ({ key: "your-api-key" }); // オンライン診察の開始 const mediaConnection : MediaConnection = peer . call ( "peerId" , userMediaStream ); /** 画面共有を開始する処理 **/ // 画面共有する画面の stream を取る const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); // 画面共有用の MediaStream を渡すことで、画面共有を開始する mediaConnection . replaceStream ( sharingStream ); 開発中に遭遇した問題への対応 スリープモード・共有を停止ボタンを押したときの対応 Google Chrome で画面共有の際に表示される「共有を停止」ボタンを押下したり、PC をスリープモードにすると、画面の MediaStreamTrack が途切れてしまいます。 これは該当の MediaStreamTrack に "ended" のイベントリスナを登録しておくことでハンドリングできます。 displayVideoTrack . addEventListener ( "ended" , handleEndedEvent , { once: true }); TypeScript の型の対応 現状 TypeScript の型が getDisplayMedia() に対応していなかったため、今回は実装の参考にしている skyway-conf で利用されている型 を流用する形で対応をしました。 declare global { interface MediaDevices { getDisplayMedia ( constraints : MediaStreamConstraints ): Promise < MediaStream >; } } これは根本的には TypeScript の dom.d.ts に型定義が入っていないことが起因していますが、 TypeScript4.4 で対応がされるようです 。 まとめ 昨今の状況により、オンライン診察のニーズが高まり、画面共有機能の重要性が高まりました。 診察中の画面共有機能は以下の api を組み合わせることで実現することができます。 PC 画面の MediaStream は getDisplayMedia() を使うことで取得 MediaStream に含める音声・画像ストリームを変更したい場合は MediaStreamTrack の組み合わせを変えることで作成 接続中の MediaStream の変更は SkyWay の SDK の MediaConnection.replaceStream() を使う 最後に CLINICS では本稿で紹介した画面共有などの新規機能の導入や日々の改善を通じて、医療機関・患者双方に支持されるプロダクトを目指し開発を行っています。興味を持たれたエンジニアの方がいらっしゃいましたらぜひ こちら にご連絡いただければと思います。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
事業本部 プロダクト開発室エンジニアの日下です。 オンライン診療・服薬指導・クラウド診療支援システム「CLINICS」 の、患者・医療機関に向けたアプリケーションの機能開発、開発基盤、インフラ周りを担当しております。 今回 CLINICS が提供するオンライン診療機能に「画面共有機能」を追加しましたので、その背景・技術的な話をまとめます。 画面共有機能実装の背景 CLINICS とオンライン診療 普段皆さんが病院にかかるとき、多くの場合は病院に行き、医師の診察を対面で受け、会計をして帰るといった流れになるかと思います。 CLINICS のオンライン診療はこの流れをインターネットを通して提供するサービスです。 ※ オンライン診療は、一度、初診等で対面診療を受けた際に医師が可能と判断した場合、次回以降の診察において可能になります。また、現在は新型コロナウイルス感染症対策時限措置として、初診からオンライン診療を受けることが可能となっています。 CLINICS を利用した場合、事前に予約した時間にスマホまたは PC で待機をする、医師の診察をビデオチャットで受け、会計はクレジットカードで行われるという流れとなっています。 クラウド診療支援システムとしての CLINICS は 2016 年に「オンライン診療のためのシステム」としてローンチ され、 2018 年にはクラウド型電子カルテ機能を 、 2019 年には予約管理システム機能を 、 2020 年にはかかりつけ薬局支援システム Pharms との連携機能も追加し 、患者向けアプリからオンライン服薬指導をシームレスに受けることができるようになりました。 プロダクト開発室ではこれらオンライン診療機能・電子カルテ機能・予約管理機能・連携機能の改善を日々行っています。 画面共有機能の需要の高まり、実装の決定 このように CLINICS の改善を日々行なっている中、昨年から始まった 新型コロナウイルス感染症(COVID-19) の流行に伴った需要の増加により、オンライン診療の件数が急増しました。 CLINICS も数多くの医療機関にご利用いただく中で、オンライン診療に関わるさまざまなご要望をいただくようになりました。その中でも特に多かったものが、今回紹介する画面共有機能です。 対面での診察の際に医師が検査結果などを患者に見せながら説明するように、オンラインで診察する場合でも資料をリアルタイムで共有しながら説明ができるようになれば、今まで以上にオンラインでも質の高い診察を行えるようになります。 こういったユースケース、要望などを検討した結果、CLINICS を利用するすべての医療機関及び患者にとって大きな恩恵が見込まれたため、オンライン診察(ビデオチャット)中に医師の PC 画面をリアルタイムで患者に共有する機能として実装をすることにしました。 画面共有機能の実装 画面共有をする医師側向けのコードでどういった実装方法があるのか、大まかな流れをまとめます。 ※ 以下に記載しているコードは説明のための疑似コードですので、このままでは動作しないことにご注意ください。また、医師側の実装例を掲載しているため、患者側(画面共有を受ける側)の実装は別途必要になります。 オンライン診察開始までの処理 オンライン診察を開始するには医師側のマイクとカメラで取得した情報を患者側に送付する必要があります。ここではそこまでの実装の流れを見ていきます。 カメラ・マイクのストリームの取得 オンライン診察開始時点で医師側のマイク・カメラの情報を共有するため、まずはそれらのストリームを取得する必要があります。こういったメディアコンテンツのストリームを司るインターフェイスとして MediaStream が定義されています。 マイク・カメラの MediaStream は、例えば MediaDevices.getUserMedia() を利用して取得できます。 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); SkyWay 経由でオンライン診察を始める WebRTC で P2P のビデオチャットを利用するためには、初期の接続のための処理及び接続の維持などの処理を行う必要があります。弊社ではこのあたりの処理を WebRTC SaaS の SkyWay 及びその SDK を利用することで簡略化しています。 オンライン診察開始時には、先程取得した医師側のマイク・カメラの MediaStream を SkyWay の SDK に渡すことで、一対一でのリアルタイムビデオチャットを実現できます。 import Peer , { MediaConnection } from "skyway-js" ; const peer = new Peer ({ key: "your-api-key" }); // 事前に患者と共有しておいた peer id に対して call メソッドと MediaStream を渡すことで診察を開始できる。 const mediaConnection : MediaConnection = peer . call ( "shared-peer-id" , userMediaStream ); // 注: 患者側は送付された処理をハンドリングする機能を実装する必要がある ここまでがオンライン診察を開始するまでの処理です。 ※ 詳細は SkyWay 公式の チュートリアル などを参照ください。 画面共有の処理 ここまでで患者に対して医師側のカメラ・マイクで取得された映像・音声が表示されている状態のため、これを切り替える処理が必要になります。今回は現在接続に利用している MediaStream を、画面共有用の MediaStream に入れ替えることで実現しました。 画面の MediaStream の取得 まずは共有する画面の MediaStream を取得する必要があります。これは MediaDevices.getDisplayMedia() を使うことで実現できます。 const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); 画面共有用の MediaStream を作る getDisplayMedia() から共有する画面の MediaStream を取得できるものの、そのまま利用するとマイクの音声が入りません。 これは getDisplayMedia() から取れる MediaStream にマイクの音声が含まれていないことが原因なので、必要な画像・音声の組み合わせを持った画面共有用の MediaStream を作成することで対処ができます。 MediaStreamTrack を組みあわせて画面共有用の MediaStream を作る 画面共有用の MediaStream を作成する前にまず、MediaStreamTrack と MediaStream の関係を理解する必要があります。 MediaStreamTrack はストリームに含まれる一つのメディアトラックを表現するものです。 kind という読み取り専用プロパティがあり、オーディオトラックであれば "audio" が、ビデオトラックであれば "video" が設定されています。 また、 MediaStream は複数の MediaStreamTrack から成り、オーディオトラック・ビデオトラックを取り出すメソッドがそれぞれ MediaStream.getAudioTracks() ・ MediaStream.getVideoTracks() として実装されています。 これらを組み合わせることで、マイクと画面の MediaStreamTrack を持つ MediaStream を作ることができ、これを SkyWay の SDK に渡すことで、画面共有を実現できます。 const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingMediaStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); MediaStream の入れ替え 最後に画面共有状態への切り替えです。マイク・カメラが共有されている状態からの切り替えにはいくつかの方法が考えられます。 例えば、多重化であれば MediaConnection( Skyway の SDK の単位で、「接続先 Peer へのメディアチャネル接続」を管理する)の多重化、MediaStream の多重化、MediaStreamTrack の多重化がそれぞれ考えられます。これらの方法はマイク・カメラの切り替え時のチラつき抑制など実装上の選択肢が増えるメリットがある一方で、通信量が多くなってしまう点がデメリットと言えます。 今回は多重化をせずに既存の MediaStream を切り替える実装を紹介します。この方法のメリットは、多重化に比べると通信量が少なく、またすでに MediaStream が一つである前提で作られている場合は、画面共有を受ける側の実装の変更が不要という点です。 この方法は、 SkyWay の SDK であれば MediaConnection の replaceStream というメソッド に対して新しい MediaStream を渡すことで実現ができます。 // 画面共有用の MediaStream を渡すことで、画面共有を開始する // MediaConnection は先程 `peer.call` した際の返り値として取れているため、それを利用する mediaConnection . replaceStream ( sharingMediaStream ); 実装前に懸念していたマイク・カメラの切り替え時のチラつきなども気になるほどはなく、実用に足るような品質を保つことができることを確認しています。 実装の全体概要 以上の流れを実装すると、次のようなコードになります。 import Peer , { MediaConnection } from "skyway-js" ; /** 医師側のマイク・カメラを共有してオンライン診察開始するところまで **/ // getUserMedia()でカメラ・マイクのストリームを取得 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); // Skyway sdk の初期化処理 const peer = new Peer ({ key: "your-api-key" }); // オンライン診察の開始 const mediaConnection : MediaConnection = peer . call ( "peerId" , userMediaStream ); /** 画面共有を開始する処理 **/ // 画面共有する画面の stream を取る const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); // 画面共有用の MediaStream を渡すことで、画面共有を開始する mediaConnection . replaceStream ( sharingStream ); 開発中に遭遇した問題への対応 スリープモード・共有を停止ボタンを押したときの対応 Google Chrome で画面共有の際に表示される「共有を停止」ボタンを押下したり、PC をスリープモードにすると、画面の MediaStreamTrack が途切れてしまいます。 これは該当の MediaStreamTrack に "ended" のイベントリスナを登録しておくことでハンドリングできます。 displayVideoTrack . addEventListener ( "ended" , handleEndedEvent , { once: true }); TypeScript の型の対応 現状 TypeScript の型が getDisplayMedia() に対応していなかったため、今回は実装の参考にしている skyway-conf で利用されている型 を流用する形で対応をしました。 declare global { interface MediaDevices { getDisplayMedia ( constraints : MediaStreamConstraints ): Promise < MediaStream >; } } これは根本的には TypeScript の dom.d.ts に型定義が入っていないことが起因していますが、 TypeScript4.4 で対応がされるようです 。 まとめ 昨今の状況により、オンライン診察のニーズが高まり、画面共有機能の重要性が高まりました。 診察中の画面共有機能は以下の api を組み合わせることで実現することができます。 PC 画面の MediaStream は getDisplayMedia() を使うことで取得 MediaStream に含める音声・画像ストリームを変更したい場合は MediaStreamTrack の組み合わせを変えることで作成 接続中の MediaStream の変更は SkyWay の SDK の MediaConnection.replaceStream() を使う 最後に CLINICS では本稿で紹介した画面共有などの新規機能の導入や日々の改善を通じて、医療機関・患者双方に支持されるプロダクトを目指し開発を行っています。興味を持たれたエンジニアの方がいらっしゃいましたらぜひ こちら にご連絡いただければと思います。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
事業本部 プロダクト開発室エンジニアの日下です。 オンライン診療・服薬指導・クラウド診療支援システム「CLINICS」 の、患者・医療機関に向けたアプリケーションの機能開発、開発基盤、インフラ周りを担当しております。 今回 CLINICS が提供するオンライン診療機能に「画面共有機能」を追加しましたので、その背景・技術的な話をまとめます。 画面共有機能実装の背景 CLINICS とオンライン診療 普段皆さんが病院にかかるとき、多くの場合は病院に行き、医師の診察を対面で受け、会計をして帰るといった流れになるかと思います。 CLINICS のオンライン診療はこの流れをインターネットを通して提供するサービスです。 ※ オンライン診療は、一度、初診等で対面診療を受けた際に医師が可能と判断した場合、次回以降の診察において可能になります。また、現在は新型コロナウイルス感染症対策時限措置として、初診からオンライン診療を受けることが可能となっています。 CLINICS を利用した場合、事前に予約した時間にスマホまたは PC で待機をする、医師の診察をビデオチャットで受け、会計はクレジットカードで行われるという流れとなっています。 クラウド診療支援システムとしての CLINICS は 2016 年に「オンライン診療のためのシステム」としてローンチ され、 2018 年にはクラウド型電子カルテ機能を 、 2019 年には予約管理システム機能を 、 2020 年にはかかりつけ薬局支援システム Pharms との連携機能も追加し 、患者向けアプリからオンライン服薬指導をシームレスに受けることができるようになりました。 プロダクト開発室ではこれらオンライン診療機能・電子カルテ機能・予約管理機能・連携機能の改善を日々行っています。 画面共有機能の需要の高まり、実装の決定 このように CLINICS の改善を日々行なっている中、昨年から始まった 新型コロナウイルス感染症(COVID-19) の流行に伴った需要の増加により、オンライン診療の件数が急増しました。 CLINICS も数多くの医療機関にご利用いただく中で、オンライン診療に関わるさまざまなご要望をいただくようになりました。その中でも特に多かったものが、今回紹介する画面共有機能です。 対面での診察の際に医師が検査結果などを患者に見せながら説明するように、オンラインで診察する場合でも資料をリアルタイムで共有しながら説明ができるようになれば、今まで以上にオンラインでも質の高い診察を行えるようになります。 こういったユースケース、要望などを検討した結果、CLINICS を利用するすべての医療機関及び患者にとって大きな恩恵が見込まれたため、オンライン診察(ビデオチャット)中に医師の PC 画面をリアルタイムで患者に共有する機能として実装をすることにしました。 画面共有機能の実装 画面共有をする医師側向けのコードでどういった実装方法があるのか、大まかな流れをまとめます。 ※ 以下に記載しているコードは説明のための疑似コードですので、このままでは動作しないことにご注意ください。また、医師側の実装例を掲載しているため、患者側(画面共有を受ける側)の実装は別途必要になります。 オンライン診察開始までの処理 オンライン診察を開始するには医師側のマイクとカメラで取得した情報を患者側に送付する必要があります。ここではそこまでの実装の流れを見ていきます。 カメラ・マイクのストリームの取得 オンライン診察開始時点で医師側のマイク・カメラの情報を共有するため、まずはそれらのストリームを取得する必要があります。こういったメディアコンテンツのストリームを司るインターフェイスとして MediaStream が定義されています。 マイク・カメラの MediaStream は、例えば MediaDevices.getUserMedia() を利用して取得できます。 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); SkyWay 経由でオンライン診察を始める WebRTC で P2P のビデオチャットを利用するためには、初期の接続のための処理及び接続の維持などの処理を行う必要があります。弊社ではこのあたりの処理を WebRTC SaaS の SkyWay 及びその SDK を利用することで簡略化しています。 オンライン診察開始時には、先程取得した医師側のマイク・カメラの MediaStream を SkyWay の SDK に渡すことで、一対一でのリアルタイムビデオチャットを実現できます。 import Peer , { MediaConnection } from "skyway-js" ; const peer = new Peer ({ key: "your-api-key" }); // 事前に患者と共有しておいた peer id に対して call メソッドと MediaStream を渡すことで診察を開始できる。 const mediaConnection : MediaConnection = peer . call ( "shared-peer-id" , userMediaStream ); // 注: 患者側は送付された処理をハンドリングする機能を実装する必要がある ここまでがオンライン診察を開始するまでの処理です。 ※ 詳細は SkyWay 公式の チュートリアル などを参照ください。 画面共有の処理 ここまでで患者に対して医師側のカメラ・マイクで取得された映像・音声が表示されている状態のため、これを切り替える処理が必要になります。今回は現在接続に利用している MediaStream を、画面共有用の MediaStream に入れ替えることで実現しました。 画面の MediaStream の取得 まずは共有する画面の MediaStream を取得する必要があります。これは MediaDevices.getDisplayMedia() を使うことで実現できます。 const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); 画面共有用の MediaStream を作る getDisplayMedia() から共有する画面の MediaStream を取得できるものの、そのまま利用するとマイクの音声が入りません。 これは getDisplayMedia() から取れる MediaStream にマイクの音声が含まれていないことが原因なので、必要な画像・音声の組み合わせを持った画面共有用の MediaStream を作成することで対処ができます。 MediaStreamTrack を組みあわせて画面共有用の MediaStream を作る 画面共有用の MediaStream を作成する前にまず、MediaStreamTrack と MediaStream の関係を理解する必要があります。 MediaStreamTrack はストリームに含まれる一つのメディアトラックを表現するものです。 kind という読み取り専用プロパティがあり、オーディオトラックであれば "audio" が、ビデオトラックであれば "video" が設定されています。 また、 MediaStream は複数の MediaStreamTrack から成り、オーディオトラック・ビデオトラックを取り出すメソッドがそれぞれ MediaStream.getAudioTracks() ・ MediaStream.getVideoTracks() として実装されています。 これらを組み合わせることで、マイクと画面の MediaStreamTrack を持つ MediaStream を作ることができ、これを SkyWay の SDK に渡すことで、画面共有を実現できます。 const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingMediaStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); MediaStream の入れ替え 最後に画面共有状態への切り替えです。マイク・カメラが共有されている状態からの切り替えにはいくつかの方法が考えられます。 例えば、多重化であれば MediaConnection( Skyway の SDK の単位で、「接続先 Peer へのメディアチャネル接続」を管理する)の多重化、MediaStream の多重化、MediaStreamTrack の多重化がそれぞれ考えられます。これらの方法はマイク・カメラの切り替え時のチラつき抑制など実装上の選択肢が増えるメリットがある一方で、通信量が多くなってしまう点がデメリットと言えます。 今回は多重化をせずに既存の MediaStream を切り替える実装を紹介します。この方法のメリットは、多重化に比べると通信量が少なく、またすでに MediaStream が一つである前提で作られている場合は、画面共有を受ける側の実装の変更が不要という点です。 この方法は、 SkyWay の SDK であれば MediaConnection の replaceStream というメソッド に対して新しい MediaStream を渡すことで実現ができます。 // 画面共有用の MediaStream を渡すことで、画面共有を開始する // MediaConnection は先程 `peer.call` した際の返り値として取れているため、それを利用する mediaConnection . replaceStream ( sharingMediaStream ); 実装前に懸念していたマイク・カメラの切り替え時のチラつきなども気になるほどはなく、実用に足るような品質を保つことができることを確認しています。 実装の全体概要 以上の流れを実装すると、次のようなコードになります。 import Peer , { MediaConnection } from "skyway-js" ; /** 医師側のマイク・カメラを共有してオンライン診察開始するところまで **/ // getUserMedia()でカメラ・マイクのストリームを取得 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); // Skyway sdk の初期化処理 const peer = new Peer ({ key: "your-api-key" }); // オンライン診察の開始 const mediaConnection : MediaConnection = peer . call ( "peerId" , userMediaStream ); /** 画面共有を開始する処理 **/ // 画面共有する画面の stream を取る const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); // 画面共有用の MediaStream を渡すことで、画面共有を開始する mediaConnection . replaceStream ( sharingStream ); 開発中に遭遇した問題への対応 スリープモード・共有を停止ボタンを押したときの対応 Google Chrome で画面共有の際に表示される「共有を停止」ボタンを押下したり、PC をスリープモードにすると、画面の MediaStreamTrack が途切れてしまいます。 これは該当の MediaStreamTrack に "ended" のイベントリスナを登録しておくことでハンドリングできます。 displayVideoTrack . addEventListener ( "ended" , handleEndedEvent , { once: true }); TypeScript の型の対応 現状 TypeScript の型が getDisplayMedia() に対応していなかったため、今回は実装の参考にしている skyway-conf で利用されている型 を流用する形で対応をしました。 declare global { interface MediaDevices { getDisplayMedia ( constraints : MediaStreamConstraints ): Promise < MediaStream >; } } これは根本的には TypeScript の dom.d.ts に型定義が入っていないことが起因していますが、 TypeScript4.4 で対応がされるようです 。 まとめ 昨今の状況により、オンライン診察のニーズが高まり、画面共有機能の重要性が高まりました。 診察中の画面共有機能は以下の api を組み合わせることで実現することができます。 PC 画面の MediaStream は getDisplayMedia() を使うことで取得 MediaStream に含める音声・画像ストリームを変更したい場合は MediaStreamTrack の組み合わせを変えることで作成 接続中の MediaStream の変更は SkyWay の SDK の MediaConnection.replaceStream() を使う 最後に CLINICS では本稿で紹介した画面共有などの新規機能の導入や日々の改善を通じて、医療機関・患者双方に支持されるプロダクトを目指し開発を行っています。興味を持たれたエンジニアの方がいらっしゃいましたらぜひ こちら にご連絡いただければと思います。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
事業本部 プロダクト開発室エンジニアの日下です。 オンライン診療・服薬指導・クラウド診療支援システム「CLINICS」 の、患者・医療機関に向けたアプリケーションの機能開発、開発基盤、インフラ周りを担当しております。 今回 CLINICS が提供するオンライン診療機能に「画面共有機能」を追加しましたので、その背景・技術的な話をまとめます。 画面共有機能実装の背景 CLINICS とオンライン診療 普段皆さんが病院にかかるとき、多くの場合は病院に行き、医師の診察を対面で受け、会計をして帰るといった流れになるかと思います。 CLINICS のオンライン診療はこの流れをインターネットを通して提供するサービスです。 ※ オンライン診療は、一度、初診等で対面診療を受けた際に医師が可能と判断した場合、次回以降の診察において可能になります。また、現在は新型コロナウイルス感染症対策時限措置として、初診からオンライン診療を受けることが可能となっています。 CLINICS を利用した場合、事前に予約した時間にスマホまたは PC で待機をする、医師の診察をビデオチャットで受け、会計はクレジットカードで行われるという流れとなっています。 クラウド診療支援システムとしての CLINICS は 2016 年に「オンライン診療のためのシステム」としてローンチ され、 2018 年にはクラウド型電子カルテ機能を 、 2019 年には予約管理システム機能を 、 2020 年にはかかりつけ薬局支援システム Pharms との連携機能も追加し 、患者向けアプリからオンライン服薬指導をシームレスに受けることができるようになりました。 プロダクト開発室ではこれらオンライン診療機能・電子カルテ機能・予約管理機能・連携機能の改善を日々行っています。 画面共有機能の需要の高まり、実装の決定 このように CLINICS の改善を日々行なっている中、昨年から始まった 新型コロナウイルス感染症(COVID-19) の流行に伴った需要の増加により、オンライン診療の件数が急増しました。 CLINICS も数多くの医療機関にご利用いただく中で、オンライン診療に関わるさまざまなご要望をいただくようになりました。その中でも特に多かったものが、今回紹介する画面共有機能です。 対面での診察の際に医師が検査結果などを患者に見せながら説明するように、オンラインで診察する場合でも資料をリアルタイムで共有しながら説明ができるようになれば、今まで以上にオンラインでも質の高い診察を行えるようになります。 こういったユースケース、要望などを検討した結果、CLINICS を利用するすべての医療機関及び患者にとって大きな恩恵が見込まれたため、オンライン診察(ビデオチャット)中に医師の PC 画面をリアルタイムで患者に共有する機能として実装をすることにしました。 画面共有機能の実装 画面共有をする医師側向けのコードでどういった実装方法があるのか、大まかな流れをまとめます。 ※ 以下に記載しているコードは説明のための疑似コードですので、このままでは動作しないことにご注意ください。また、医師側の実装例を掲載しているため、患者側(画面共有を受ける側)の実装は別途必要になります。 オンライン診察開始までの処理 オンライン診察を開始するには医師側のマイクとカメラで取得した情報を患者側に送付する必要があります。ここではそこまでの実装の流れを見ていきます。 カメラ・マイクのストリームの取得 オンライン診察開始時点で医師側のマイク・カメラの情報を共有するため、まずはそれらのストリームを取得する必要があります。こういったメディアコンテンツのストリームを司るインターフェイスとして MediaStream が定義されています。 マイク・カメラの MediaStream は、例えば MediaDevices.getUserMedia() を利用して取得できます。 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); SkyWay 経由でオンライン診察を始める WebRTC で P2P のビデオチャットを利用するためには、初期の接続のための処理及び接続の維持などの処理を行う必要があります。弊社ではこのあたりの処理を WebRTC SaaS の SkyWay 及びその SDK を利用することで簡略化しています。 オンライン診察開始時には、先程取得した医師側のマイク・カメラの MediaStream を SkyWay の SDK に渡すことで、一対一でのリアルタイムビデオチャットを実現できます。 import Peer , { MediaConnection } from "skyway-js" ; const peer = new Peer ({ key: "your-api-key" }); // 事前に患者と共有しておいた peer id に対して call メソッドと MediaStream を渡すことで診察を開始できる。 const mediaConnection : MediaConnection = peer . call ( "shared-peer-id" , userMediaStream ); // 注: 患者側は送付された処理をハンドリングする機能を実装する必要がある ここまでがオンライン診察を開始するまでの処理です。 ※ 詳細は SkyWay 公式の チュートリアル などを参照ください。 画面共有の処理 ここまでで患者に対して医師側のカメラ・マイクで取得された映像・音声が表示されている状態のため、これを切り替える処理が必要になります。今回は現在接続に利用している MediaStream を、画面共有用の MediaStream に入れ替えることで実現しました。 画面の MediaStream の取得 まずは共有する画面の MediaStream を取得する必要があります。これは MediaDevices.getDisplayMedia() を使うことで実現できます。 const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); 画面共有用の MediaStream を作る getDisplayMedia() から共有する画面の MediaStream を取得できるものの、そのまま利用するとマイクの音声が入りません。 これは getDisplayMedia() から取れる MediaStream にマイクの音声が含まれていないことが原因なので、必要な画像・音声の組み合わせを持った画面共有用の MediaStream を作成することで対処ができます。 MediaStreamTrack を組みあわせて画面共有用の MediaStream を作る 画面共有用の MediaStream を作成する前にまず、MediaStreamTrack と MediaStream の関係を理解する必要があります。 MediaStreamTrack はストリームに含まれる一つのメディアトラックを表現するものです。 kind という読み取り専用プロパティがあり、オーディオトラックであれば "audio" が、ビデオトラックであれば "video" が設定されています。 また、 MediaStream は複数の MediaStreamTrack から成り、オーディオトラック・ビデオトラックを取り出すメソッドがそれぞれ MediaStream.getAudioTracks() ・ MediaStream.getVideoTracks() として実装されています。 これらを組み合わせることで、マイクと画面の MediaStreamTrack を持つ MediaStream を作ることができ、これを SkyWay の SDK に渡すことで、画面共有を実現できます。 const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingMediaStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); MediaStream の入れ替え 最後に画面共有状態への切り替えです。マイク・カメラが共有されている状態からの切り替えにはいくつかの方法が考えられます。 例えば、多重化であれば MediaConnection( Skyway の SDK の単位で、「接続先 Peer へのメディアチャネル接続」を管理する)の多重化、MediaStream の多重化、MediaStreamTrack の多重化がそれぞれ考えられます。これらの方法はマイク・カメラの切り替え時のチラつき抑制など実装上の選択肢が増えるメリットがある一方で、通信量が多くなってしまう点がデメリットと言えます。 今回は多重化をせずに既存の MediaStream を切り替える実装を紹介します。この方法のメリットは、多重化に比べると通信量が少なく、またすでに MediaStream が一つである前提で作られている場合は、画面共有を受ける側の実装の変更が不要という点です。 この方法は、 SkyWay の SDK であれば MediaConnection の replaceStream というメソッド に対して新しい MediaStream を渡すことで実現ができます。 // 画面共有用の MediaStream を渡すことで、画面共有を開始する // MediaConnection は先程 `peer.call` した際の返り値として取れているため、それを利用する mediaConnection . replaceStream ( sharingMediaStream ); 実装前に懸念していたマイク・カメラの切り替え時のチラつきなども気になるほどはなく、実用に足るような品質を保つことができることを確認しています。 実装の全体概要 以上の流れを実装すると、次のようなコードになります。 import Peer , { MediaConnection } from "skyway-js" ; /** 医師側のマイク・カメラを共有してオンライン診察開始するところまで **/ // getUserMedia()でカメラ・マイクのストリームを取得 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); // Skyway sdk の初期化処理 const peer = new Peer ({ key: "your-api-key" }); // オンライン診察の開始 const mediaConnection : MediaConnection = peer . call ( "peerId" , userMediaStream ); /** 画面共有を開始する処理 **/ // 画面共有する画面の stream を取る const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); // 画面共有用の MediaStream を渡すことで、画面共有を開始する mediaConnection . replaceStream ( sharingStream ); 開発中に遭遇した問題への対応 スリープモード・共有を停止ボタンを押したときの対応 Google Chrome で画面共有の際に表示される「共有を停止」ボタンを押下したり、PC をスリープモードにすると、画面の MediaStreamTrack が途切れてしまいます。 これは該当の MediaStreamTrack に "ended" のイベントリスナを登録しておくことでハンドリングできます。 displayVideoTrack . addEventListener ( "ended" , handleEndedEvent , { once: true }); TypeScript の型の対応 現状 TypeScript の型が getDisplayMedia() に対応していなかったため、今回は実装の参考にしている skyway-conf で利用されている型 を流用する形で対応をしました。 declare global { interface MediaDevices { getDisplayMedia ( constraints : MediaStreamConstraints ): Promise < MediaStream >; } } これは根本的には TypeScript の dom.d.ts に型定義が入っていないことが起因していますが、 TypeScript4.4 で対応がされるようです 。 まとめ 昨今の状況により、オンライン診察のニーズが高まり、画面共有機能の重要性が高まりました。 診察中の画面共有機能は以下の api を組み合わせることで実現することができます。 PC 画面の MediaStream は getDisplayMedia() を使うことで取得 MediaStream に含める音声・画像ストリームを変更したい場合は MediaStreamTrack の組み合わせを変えることで作成 接続中の MediaStream の変更は SkyWay の SDK の MediaConnection.replaceStream() を使う 最後に CLINICS では本稿で紹介した画面共有などの新規機能の導入や日々の改善を通じて、医療機関・患者双方に支持されるプロダクトを目指し開発を行っています。興味を持たれたエンジニアの方がいらっしゃいましたらぜひ こちら にご連絡いただければと思います。 https://www.medley.jp/jobs/
事業本部 プロダクト開発室エンジニアの日下です。 オンライン診療・服薬指導・クラウド診療支援システム「CLINICS」 の、患者・医療機関に向けたアプリケーションの機能開発、開発基盤、インフラ周りを担当しております。 今回 CLINICS が提供するオンライン診療機能に「画面共有機能」を追加しましたので、その背景・技術的な話をまとめます。 画面共有機能実装の背景 CLINICS とオンライン診療 普段皆さんが病院にかかるとき、多くの場合は病院に行き、医師の診察を対面で受け、会計をして帰るといった流れになるかと思います。 CLINICS のオンライン診療はこの流れをインターネットを通して提供するサービスです。 ※ オンライン診療は、一度、初診等で対面診療を受けた際に医師が可能と判断した場合、次回以降の診察において可能になります。また、現在は新型コロナウイルス感染症対策時限措置として、初診からオンライン診療を受けることが可能となっています。 CLINICS を利用した場合、事前に予約した時間にスマホまたは PC で待機をする、医師の診察をビデオチャットで受け、会計はクレジットカードで行われるという流れとなっています。 クラウド診療支援システムとしての CLINICS は 2016 年に「オンライン診療のためのシステム」としてローンチ され、 2018 年にはクラウド型電子カルテ機能を 、 2019 年には予約管理システム機能を 、 2020 年にはかかりつけ薬局支援システム Pharms との連携機能も追加し 、患者向けアプリからオンライン服薬指導をシームレスに受けることができるようになりました。 プロダクト開発室ではこれらオンライン診療機能・電子カルテ機能・予約管理機能・連携機能の改善を日々行っています。 画面共有機能の需要の高まり、実装の決定 このように CLINICS の改善を日々行なっている中、昨年から始まった 新型コロナウイルス感染症(COVID-19) の流行に伴った需要の増加により、オンライン診療の件数が急増しました。 CLINICS も数多くの医療機関にご利用いただく中で、オンライン診療に関わるさまざまなご要望をいただくようになりました。その中でも特に多かったものが、今回紹介する画面共有機能です。 対面での診察の際に医師が検査結果などを患者に見せながら説明するように、オンラインで診察する場合でも資料をリアルタイムで共有しながら説明ができるようになれば、今まで以上にオンラインでも質の高い診察を行えるようになります。 こういったユースケース、要望などを検討した結果、CLINICS を利用するすべての医療機関及び患者にとって大きな恩恵が見込まれたため、オンライン診察(ビデオチャット)中に医師の PC 画面をリアルタイムで患者に共有する機能として実装をすることにしました。 画面共有機能の実装 画面共有をする医師側向けのコードでどういった実装方法があるのか、大まかな流れをまとめます。 ※ 以下に記載しているコードは説明のための疑似コードですので、このままでは動作しないことにご注意ください。また、医師側の実装例を掲載しているため、患者側(画面共有を受ける側)の実装は別途必要になります。 オンライン診察開始までの処理 オンライン診察を開始するには医師側のマイクとカメラで取得した情報を患者側に送付する必要があります。ここではそこまでの実装の流れを見ていきます。 カメラ・マイクのストリームの取得 オンライン診察開始時点で医師側のマイク・カメラの情報を共有するため、まずはそれらのストリームを取得する必要があります。こういったメディアコンテンツのストリームを司るインターフェイスとして MediaStream が定義されています。 マイク・カメラの MediaStream は、例えば MediaDevices.getUserMedia() を利用して取得できます。 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); SkyWay 経由でオンライン診察を始める WebRTC で P2P のビデオチャットを利用するためには、初期の接続のための処理及び接続の維持などの処理を行う必要があります。弊社ではこのあたりの処理を WebRTC SaaS の SkyWay 及びその SDK を利用することで簡略化しています。 オンライン診察開始時には、先程取得した医師側のマイク・カメラの MediaStream を SkyWay の SDK に渡すことで、一対一でのリアルタイムビデオチャットを実現できます。 import Peer , { MediaConnection } from "skyway-js" ; const peer = new Peer ({ key: "your-api-key" }); // 事前に患者と共有しておいた peer id に対して call メソッドと MediaStream を渡すことで診察を開始できる。 const mediaConnection : MediaConnection = peer . call ( "shared-peer-id" , userMediaStream ); // 注: 患者側は送付された処理をハンドリングする機能を実装する必要がある ここまでがオンライン診察を開始するまでの処理です。 ※ 詳細は SkyWay 公式の チュートリアル などを参照ください。 画面共有の処理 ここまでで患者に対して医師側のカメラ・マイクで取得された映像・音声が表示されている状態のため、これを切り替える処理が必要になります。今回は現在接続に利用している MediaStream を、画面共有用の MediaStream に入れ替えることで実現しました。 画面の MediaStream の取得 まずは共有する画面の MediaStream を取得する必要があります。これは MediaDevices.getDisplayMedia() を使うことで実現できます。 const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); 画面共有用の MediaStream を作る getDisplayMedia() から共有する画面の MediaStream を取得できるものの、そのまま利用するとマイクの音声が入りません。 これは getDisplayMedia() から取れる MediaStream にマイクの音声が含まれていないことが原因なので、必要な画像・音声の組み合わせを持った画面共有用の MediaStream を作成することで対処ができます。 MediaStreamTrack を組みあわせて画面共有用の MediaStream を作る 画面共有用の MediaStream を作成する前にまず、MediaStreamTrack と MediaStream の関係を理解する必要があります。 MediaStreamTrack はストリームに含まれる一つのメディアトラックを表現するものです。 kind という読み取り専用プロパティがあり、オーディオトラックであれば "audio" が、ビデオトラックであれば "video" が設定されています。 また、 MediaStream は複数の MediaStreamTrack から成り、オーディオトラック・ビデオトラックを取り出すメソッドがそれぞれ MediaStream.getAudioTracks() ・ MediaStream.getVideoTracks() として実装されています。 これらを組み合わせることで、マイクと画面の MediaStreamTrack を持つ MediaStream を作ることができ、これを SkyWay の SDK に渡すことで、画面共有を実現できます。 const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingMediaStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); MediaStream の入れ替え 最後に画面共有状態への切り替えです。マイク・カメラが共有されている状態からの切り替えにはいくつかの方法が考えられます。 例えば、多重化であれば MediaConnection( Skyway の SDK の単位で、「接続先 Peer へのメディアチャネル接続」を管理する)の多重化、MediaStream の多重化、MediaStreamTrack の多重化がそれぞれ考えられます。これらの方法はマイク・カメラの切り替え時のチラつき抑制など実装上の選択肢が増えるメリットがある一方で、通信量が多くなってしまう点がデメリットと言えます。 今回は多重化をせずに既存の MediaStream を切り替える実装を紹介します。この方法のメリットは、多重化に比べると通信量が少なく、またすでに MediaStream が一つである前提で作られている場合は、画面共有を受ける側の実装の変更が不要という点です。 この方法は、 SkyWay の SDK であれば MediaConnection の replaceStream というメソッド に対して新しい MediaStream を渡すことで実現ができます。 // 画面共有用の MediaStream を渡すことで、画面共有を開始する // MediaConnection は先程 `peer.call` した際の返り値として取れているため、それを利用する mediaConnection . replaceStream ( sharingMediaStream ); 実装前に懸念していたマイク・カメラの切り替え時のチラつきなども気になるほどはなく、実用に足るような品質を保つことができることを確認しています。 実装の全体概要 以上の流れを実装すると、次のようなコードになります。 import Peer , { MediaConnection } from "skyway-js" ; /** 医師側のマイク・カメラを共有してオンライン診察開始するところまで **/ // getUserMedia()でカメラ・マイクのストリームを取得 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); // Skyway sdk の初期化処理 const peer = new Peer ({ key: "your-api-key" }); // オンライン診察の開始 const mediaConnection : MediaConnection = peer . call ( "peerId" , userMediaStream ); /** 画面共有を開始する処理 **/ // 画面共有する画面の stream を取る const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); // 画面共有用の MediaStream を渡すことで、画面共有を開始する mediaConnection . replaceStream ( sharingStream ); 開発中に遭遇した問題への対応 スリープモード・共有を停止ボタンを押したときの対応 Google Chrome で画面共有の際に表示される「共有を停止」ボタンを押下したり、PC をスリープモードにすると、画面の MediaStreamTrack が途切れてしまいます。 これは該当の MediaStreamTrack に "ended" のイベントリスナを登録しておくことでハンドリングできます。 displayVideoTrack . addEventListener ( "ended" , handleEndedEvent , { once: true }); TypeScript の型の対応 現状 TypeScript の型が getDisplayMedia() に対応していなかったため、今回は実装の参考にしている skyway-conf で利用されている型 を流用する形で対応をしました。 declare global { interface MediaDevices { getDisplayMedia ( constraints : MediaStreamConstraints ): Promise < MediaStream >; } } これは根本的には TypeScript の dom.d.ts に型定義が入っていないことが起因していますが、 TypeScript4.4 で対応がされるようです 。 まとめ 昨今の状況により、オンライン診察のニーズが高まり、画面共有機能の重要性が高まりました。 診察中の画面共有機能は以下の api を組み合わせることで実現することができます。 PC 画面の MediaStream は getDisplayMedia() を使うことで取得 MediaStream に含める音声・画像ストリームを変更したい場合は MediaStreamTrack の組み合わせを変えることで作成 接続中の MediaStream の変更は SkyWay の SDK の MediaConnection.replaceStream() を使う 最後に CLINICS では本稿で紹介した画面共有などの新規機能の導入や日々の改善を通じて、医療機関・患者双方に支持されるプロダクトを目指し開発を行っています。興味を持たれたエンジニアの方がいらっしゃいましたらぜひ こちら にご連絡いただければと思います。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
事業本部 プロダクト開発室エンジニアの日下です。 オンライン診療・服薬指導・クラウド診療支援システム「CLINICS」 の、患者・医療機関に向けたアプリケーションの機能開発、開発基盤、インフラ周りを担当しております。 今回 CLINICS が提供するオンライン診療機能に「画面共有機能」を追加しましたので、その背景・技術的な話をまとめます。 画面共有機能実装の背景 CLINICS とオンライン診療 普段皆さんが病院にかかるとき、多くの場合は病院に行き、医師の診察を対面で受け、会計をして帰るといった流れになるかと思います。 CLINICS のオンライン診療はこの流れをインターネットを通して提供するサービスです。 ※ オンライン診療は、一度、初診等で対面診療を受けた際に医師が可能と判断した場合、次回以降の診察において可能になります。また、現在は新型コロナウイルス感染症対策時限措置として、初診からオンライン診療を受けることが可能となっています。 CLINICS を利用した場合、事前に予約した時間にスマホまたは PC で待機をする、医師の診察をビデオチャットで受け、会計はクレジットカードで行われるという流れとなっています。 クラウド診療支援システムとしての CLINICS は 2016 年に「オンライン診療のためのシステム」としてローンチ され、 2018 年にはクラウド型電子カルテ機能を 、 2019 年には予約管理システム機能を 、 2020 年にはかかりつけ薬局支援システム Pharms との連携機能も追加し 、患者向けアプリからオンライン服薬指導をシームレスに受けることができるようになりました。 プロダクト開発室ではこれらオンライン診療機能・電子カルテ機能・予約管理機能・連携機能の改善を日々行っています。 画面共有機能の需要の高まり、実装の決定 このように CLINICS の改善を日々行なっている中、昨年から始まった 新型コロナウイルス感染症(COVID-19) の流行に伴った需要の増加により、オンライン診療の件数が急増しました。 CLINICS も数多くの医療機関にご利用いただく中で、オンライン診療に関わるさまざまなご要望をいただくようになりました。その中でも特に多かったものが、今回紹介する画面共有機能です。 対面での診察の際に医師が検査結果などを患者に見せながら説明するように、オンラインで診察する場合でも資料をリアルタイムで共有しながら説明ができるようになれば、今まで以上にオンラインでも質の高い診察を行えるようになります。 こういったユースケース、要望などを検討した結果、CLINICS を利用するすべての医療機関及び患者にとって大きな恩恵が見込まれたため、オンライン診察(ビデオチャット)中に医師の PC 画面をリアルタイムで患者に共有する機能として実装をすることにしました。 画面共有機能の実装 画面共有をする医師側向けのコードでどういった実装方法があるのか、大まかな流れをまとめます。 ※ 以下に記載しているコードは説明のための疑似コードですので、このままでは動作しないことにご注意ください。また、医師側の実装例を掲載しているため、患者側(画面共有を受ける側)の実装は別途必要になります。 オンライン診察開始までの処理 オンライン診察を開始するには医師側のマイクとカメラで取得した情報を患者側に送付する必要があります。ここではそこまでの実装の流れを見ていきます。 カメラ・マイクのストリームの取得 オンライン診察開始時点で医師側のマイク・カメラの情報を共有するため、まずはそれらのストリームを取得する必要があります。こういったメディアコンテンツのストリームを司るインターフェイスとして MediaStream が定義されています。 マイク・カメラの MediaStream は、例えば MediaDevices.getUserMedia() を利用して取得できます。 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); SkyWay 経由でオンライン診察を始める WebRTC で P2P のビデオチャットを利用するためには、初期の接続のための処理及び接続の維持などの処理を行う必要があります。弊社ではこのあたりの処理を WebRTC SaaS の SkyWay 及びその SDK を利用することで簡略化しています。 オンライン診察開始時には、先程取得した医師側のマイク・カメラの MediaStream を SkyWay の SDK に渡すことで、一対一でのリアルタイムビデオチャットを実現できます。 import Peer , { MediaConnection } from "skyway-js" ; const peer = new Peer ({ key: "your-api-key" }); // 事前に患者と共有しておいた peer id に対して call メソッドと MediaStream を渡すことで診察を開始できる。 const mediaConnection : MediaConnection = peer . call ( "shared-peer-id" , userMediaStream ); // 注: 患者側は送付された処理をハンドリングする機能を実装する必要がある ここまでがオンライン診察を開始するまでの処理です。 ※ 詳細は SkyWay 公式の チュートリアル などを参照ください。 画面共有の処理 ここまでで患者に対して医師側のカメラ・マイクで取得された映像・音声が表示されている状態のため、これを切り替える処理が必要になります。今回は現在接続に利用している MediaStream を、画面共有用の MediaStream に入れ替えることで実現しました。 画面の MediaStream の取得 まずは共有する画面の MediaStream を取得する必要があります。これは MediaDevices.getDisplayMedia() を使うことで実現できます。 const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); 画面共有用の MediaStream を作る getDisplayMedia() から共有する画面の MediaStream を取得できるものの、そのまま利用するとマイクの音声が入りません。 これは getDisplayMedia() から取れる MediaStream にマイクの音声が含まれていないことが原因なので、必要な画像・音声の組み合わせを持った画面共有用の MediaStream を作成することで対処ができます。 MediaStreamTrack を組みあわせて画面共有用の MediaStream を作る 画面共有用の MediaStream を作成する前にまず、MediaStreamTrack と MediaStream の関係を理解する必要があります。 MediaStreamTrack はストリームに含まれる一つのメディアトラックを表現するものです。 kind という読み取り専用プロパティがあり、オーディオトラックであれば "audio" が、ビデオトラックであれば "video" が設定されています。 また、 MediaStream は複数の MediaStreamTrack から成り、オーディオトラック・ビデオトラックを取り出すメソッドがそれぞれ MediaStream.getAudioTracks() ・ MediaStream.getVideoTracks() として実装されています。 これらを組み合わせることで、マイクと画面の MediaStreamTrack を持つ MediaStream を作ることができ、これを SkyWay の SDK に渡すことで、画面共有を実現できます。 const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingMediaStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); MediaStream の入れ替え 最後に画面共有状態への切り替えです。マイク・カメラが共有されている状態からの切り替えにはいくつかの方法が考えられます。 例えば、多重化であれば MediaConnection( Skyway の SDK の単位で、「接続先 Peer へのメディアチャネル接続」を管理する)の多重化、MediaStream の多重化、MediaStreamTrack の多重化がそれぞれ考えられます。これらの方法はマイク・カメラの切り替え時のチラつき抑制など実装上の選択肢が増えるメリットがある一方で、通信量が多くなってしまう点がデメリットと言えます。 今回は多重化をせずに既存の MediaStream を切り替える実装を紹介します。この方法のメリットは、多重化に比べると通信量が少なく、またすでに MediaStream が一つである前提で作られている場合は、画面共有を受ける側の実装の変更が不要という点です。 この方法は、 SkyWay の SDK であれば MediaConnection の replaceStream というメソッド に対して新しい MediaStream を渡すことで実現ができます。 // 画面共有用の MediaStream を渡すことで、画面共有を開始する // MediaConnection は先程 `peer.call` した際の返り値として取れているため、それを利用する mediaConnection . replaceStream ( sharingMediaStream ); 実装前に懸念していたマイク・カメラの切り替え時のチラつきなども気になるほどはなく、実用に足るような品質を保つことができることを確認しています。 実装の全体概要 以上の流れを実装すると、次のようなコードになります。 import Peer , { MediaConnection } from "skyway-js" ; /** 医師側のマイク・カメラを共有してオンライン診察開始するところまで **/ // getUserMedia()でカメラ・マイクのストリームを取得 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); // Skyway sdk の初期化処理 const peer = new Peer ({ key: "your-api-key" }); // オンライン診察の開始 const mediaConnection : MediaConnection = peer . call ( "peerId" , userMediaStream ); /** 画面共有を開始する処理 **/ // 画面共有する画面の stream を取る const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); // 画面共有用の MediaStream を渡すことで、画面共有を開始する mediaConnection . replaceStream ( sharingStream ); 開発中に遭遇した問題への対応 スリープモード・共有を停止ボタンを押したときの対応 Google Chrome で画面共有の際に表示される「共有を停止」ボタンを押下したり、PC をスリープモードにすると、画面の MediaStreamTrack が途切れてしまいます。 これは該当の MediaStreamTrack に "ended" のイベントリスナを登録しておくことでハンドリングできます。 displayVideoTrack . addEventListener ( "ended" , handleEndedEvent , { once: true }); TypeScript の型の対応 現状 TypeScript の型が getDisplayMedia() に対応していなかったため、今回は実装の参考にしている skyway-conf で利用されている型 を流用する形で対応をしました。 declare global { interface MediaDevices { getDisplayMedia ( constraints : MediaStreamConstraints ): Promise < MediaStream >; } } これは根本的には TypeScript の dom.d.ts に型定義が入っていないことが起因していますが、 TypeScript4.4 で対応がされるようです 。 まとめ 昨今の状況により、オンライン診察のニーズが高まり、画面共有機能の重要性が高まりました。 診察中の画面共有機能は以下の api を組み合わせることで実現することができます。 PC 画面の MediaStream は getDisplayMedia() を使うことで取得 MediaStream に含める音声・画像ストリームを変更したい場合は MediaStreamTrack の組み合わせを変えることで作成 接続中の MediaStream の変更は SkyWay の SDK の MediaConnection.replaceStream() を使う 最後に CLINICS では本稿で紹介した画面共有などの新規機能の導入や日々の改善を通じて、医療機関・患者双方に支持されるプロダクトを目指し開発を行っています。興味を持たれたエンジニアの方がいらっしゃいましたらぜひ こちら にご連絡いただければと思います。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
事業本部 プロダクト開発室エンジニアの日下です。 オンライン診療・服薬指導・クラウド診療支援システム「CLINICS」 の、患者・医療機関に向けたアプリケーションの機能開発、開発基盤、インフラ周りを担当しております。 今回 CLINICS が提供するオンライン診療機能に「画面共有機能」を追加しましたので、その背景・技術的な話をまとめます。 画面共有機能実装の背景 CLINICS とオンライン診療 普段皆さんが病院にかかるとき、多くの場合は病院に行き、医師の診察を対面で受け、会計をして帰るといった流れになるかと思います。 CLINICS のオンライン診療はこの流れをインターネットを通して提供するサービスです。 ※ オンライン診療は、一度、初診等で対面診療を受けた際に医師が可能と判断した場合、次回以降の診察において可能になります。また、現在は新型コロナウイルス感染症対策時限措置として、初診からオンライン診療を受けることが可能となっています。 CLINICS を利用した場合、事前に予約した時間にスマホまたは PC で待機をする、医師の診察をビデオチャットで受け、会計はクレジットカードで行われるという流れとなっています。 クラウド診療支援システムとしての CLINICS は 2016 年に「オンライン診療のためのシステム」としてローンチ され、 2018 年にはクラウド型電子カルテ機能を 、 2019 年には予約管理システム機能を 、 2020 年にはかかりつけ薬局支援システム Pharms との連携機能も追加し 、患者向けアプリからオンライン服薬指導をシームレスに受けることができるようになりました。 プロダクト開発室ではこれらオンライン診療機能・電子カルテ機能・予約管理機能・連携機能の改善を日々行っています。 画面共有機能の需要の高まり、実装の決定 このように CLINICS の改善を日々行なっている中、昨年から始まった 新型コロナウイルス感染症(COVID-19) の流行に伴った需要の増加により、オンライン診療の件数が急増しました。 CLINICS も数多くの医療機関にご利用いただく中で、オンライン診療に関わるさまざまなご要望をいただくようになりました。その中でも特に多かったものが、今回紹介する画面共有機能です。 対面での診察の際に医師が検査結果などを患者に見せながら説明するように、オンラインで診察する場合でも資料をリアルタイムで共有しながら説明ができるようになれば、今まで以上にオンラインでも質の高い診察を行えるようになります。 こういったユースケース、要望などを検討した結果、CLINICS を利用するすべての医療機関及び患者にとって大きな恩恵が見込まれたため、オンライン診察(ビデオチャット)中に医師の PC 画面をリアルタイムで患者に共有する機能として実装をすることにしました。 画面共有機能の実装 画面共有をする医師側向けのコードでどういった実装方法があるのか、大まかな流れをまとめます。 ※ 以下に記載しているコードは説明のための疑似コードですので、このままでは動作しないことにご注意ください。また、医師側の実装例を掲載しているため、患者側(画面共有を受ける側)の実装は別途必要になります。 オンライン診察開始までの処理 オンライン診察を開始するには医師側のマイクとカメラで取得した情報を患者側に送付する必要があります。ここではそこまでの実装の流れを見ていきます。 カメラ・マイクのストリームの取得 オンライン診察開始時点で医師側のマイク・カメラの情報を共有するため、まずはそれらのストリームを取得する必要があります。こういったメディアコンテンツのストリームを司るインターフェイスとして MediaStream が定義されています。 マイク・カメラの MediaStream は、例えば MediaDevices.getUserMedia() を利用して取得できます。 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); SkyWay 経由でオンライン診察を始める WebRTC で P2P のビデオチャットを利用するためには、初期の接続のための処理及び接続の維持などの処理を行う必要があります。弊社ではこのあたりの処理を WebRTC SaaS の SkyWay 及びその SDK を利用することで簡略化しています。 オンライン診察開始時には、先程取得した医師側のマイク・カメラの MediaStream を SkyWay の SDK に渡すことで、一対一でのリアルタイムビデオチャットを実現できます。 import Peer , { MediaConnection } from "skyway-js" ; const peer = new Peer ({ key: "your-api-key" }); // 事前に患者と共有しておいた peer id に対して call メソッドと MediaStream を渡すことで診察を開始できる。 const mediaConnection : MediaConnection = peer . call ( "shared-peer-id" , userMediaStream ); // 注: 患者側は送付された処理をハンドリングする機能を実装する必要がある ここまでがオンライン診察を開始するまでの処理です。 ※ 詳細は SkyWay 公式の チュートリアル などを参照ください。 画面共有の処理 ここまでで患者に対して医師側のカメラ・マイクで取得された映像・音声が表示されている状態のため、これを切り替える処理が必要になります。今回は現在接続に利用している MediaStream を、画面共有用の MediaStream に入れ替えることで実現しました。 画面の MediaStream の取得 まずは共有する画面の MediaStream を取得する必要があります。これは MediaDevices.getDisplayMedia() を使うことで実現できます。 const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); 画面共有用の MediaStream を作る getDisplayMedia() から共有する画面の MediaStream を取得できるものの、そのまま利用するとマイクの音声が入りません。 これは getDisplayMedia() から取れる MediaStream にマイクの音声が含まれていないことが原因なので、必要な画像・音声の組み合わせを持った画面共有用の MediaStream を作成することで対処ができます。 MediaStreamTrack を組みあわせて画面共有用の MediaStream を作る 画面共有用の MediaStream を作成する前にまず、MediaStreamTrack と MediaStream の関係を理解する必要があります。 MediaStreamTrack はストリームに含まれる一つのメディアトラックを表現するものです。 kind という読み取り専用プロパティがあり、オーディオトラックであれば "audio" が、ビデオトラックであれば "video" が設定されています。 また、 MediaStream は複数の MediaStreamTrack から成り、オーディオトラック・ビデオトラックを取り出すメソッドがそれぞれ MediaStream.getAudioTracks() ・ MediaStream.getVideoTracks() として実装されています。 これらを組み合わせることで、マイクと画面の MediaStreamTrack を持つ MediaStream を作ることができ、これを SkyWay の SDK に渡すことで、画面共有を実現できます。 const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingMediaStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); MediaStream の入れ替え 最後に画面共有状態への切り替えです。マイク・カメラが共有されている状態からの切り替えにはいくつかの方法が考えられます。 例えば、多重化であれば MediaConnection( Skyway の SDK の単位で、「接続先 Peer へのメディアチャネル接続」を管理する)の多重化、MediaStream の多重化、MediaStreamTrack の多重化がそれぞれ考えられます。これらの方法はマイク・カメラの切り替え時のチラつき抑制など実装上の選択肢が増えるメリットがある一方で、通信量が多くなってしまう点がデメリットと言えます。 今回は多重化をせずに既存の MediaStream を切り替える実装を紹介します。この方法のメリットは、多重化に比べると通信量が少なく、またすでに MediaStream が一つである前提で作られている場合は、画面共有を受ける側の実装の変更が不要という点です。 この方法は、 SkyWay の SDK であれば MediaConnection の replaceStream というメソッド に対して新しい MediaStream を渡すことで実現ができます。 // 画面共有用の MediaStream を渡すことで、画面共有を開始する // MediaConnection は先程 `peer.call` した際の返り値として取れているため、それを利用する mediaConnection . replaceStream ( sharingMediaStream ); 実装前に懸念していたマイク・カメラの切り替え時のチラつきなども気になるほどはなく、実用に足るような品質を保つことができることを確認しています。 実装の全体概要 以上の流れを実装すると、次のようなコードになります。 import Peer , { MediaConnection } from "skyway-js" ; /** 医師側のマイク・カメラを共有してオンライン診察開始するところまで **/ // getUserMedia()でカメラ・マイクのストリームを取得 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); // Skyway sdk の初期化処理 const peer = new Peer ({ key: "your-api-key" }); // オンライン診察の開始 const mediaConnection : MediaConnection = peer . call ( "peerId" , userMediaStream ); /** 画面共有を開始する処理 **/ // 画面共有する画面の stream を取る const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); // 画面共有用の MediaStream を渡すことで、画面共有を開始する mediaConnection . replaceStream ( sharingStream ); 開発中に遭遇した問題への対応 スリープモード・共有を停止ボタンを押したときの対応 Google Chrome で画面共有の際に表示される「共有を停止」ボタンを押下したり、PC をスリープモードにすると、画面の MediaStreamTrack が途切れてしまいます。 これは該当の MediaStreamTrack に "ended" のイベントリスナを登録しておくことでハンドリングできます。 displayVideoTrack . addEventListener ( "ended" , handleEndedEvent , { once: true }); TypeScript の型の対応 現状 TypeScript の型が getDisplayMedia() に対応していなかったため、今回は実装の参考にしている skyway-conf で利用されている型 を流用する形で対応をしました。 declare global { interface MediaDevices { getDisplayMedia ( constraints : MediaStreamConstraints ): Promise < MediaStream >; } } これは根本的には TypeScript の dom.d.ts に型定義が入っていないことが起因していますが、 TypeScript4.4 で対応がされるようです 。 まとめ 昨今の状況により、オンライン診察のニーズが高まり、画面共有機能の重要性が高まりました。 診察中の画面共有機能は以下の api を組み合わせることで実現することができます。 PC 画面の MediaStream は getDisplayMedia() を使うことで取得 MediaStream に含める音声・画像ストリームを変更したい場合は MediaStreamTrack の組み合わせを変えることで作成 接続中の MediaStream の変更は SkyWay の SDK の MediaConnection.replaceStream() を使う 最後に CLINICS では本稿で紹介した画面共有などの新規機能の導入や日々の改善を通じて、医療機関・患者双方に支持されるプロダクトを目指し開発を行っています。興味を持たれたエンジニアの方がいらっしゃいましたらぜひ こちら にご連絡いただければと思います。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
事業本部 プロダクト開発室エンジニアの日下です。 オンライン診療・服薬指導・クラウド診療支援システム「CLINICS」 の、患者・医療機関に向けたアプリケーションの機能開発、開発基盤、インフラ周りを担当しております。 今回 CLINICS が提供するオンライン診療機能に「画面共有機能」を追加しましたので、その背景・技術的な話をまとめます。 画面共有機能実装の背景 CLINICS とオンライン診療 普段皆さんが病院にかかるとき、多くの場合は病院に行き、医師の診察を対面で受け、会計をして帰るといった流れになるかと思います。 CLINICS のオンライン診療はこの流れをインターネットを通して提供するサービスです。 ※ オンライン診療は、一度、初診等で対面診療を受けた際に医師が可能と判断した場合、次回以降の診察において可能になります。また、現在は新型コロナウイルス感染症対策時限措置として、初診からオンライン診療を受けることが可能となっています。 CLINICS を利用した場合、事前に予約した時間にスマホまたは PC で待機をする、医師の診察をビデオチャットで受け、会計はクレジットカードで行われるという流れとなっています。 クラウド診療支援システムとしての CLINICS は 2016 年に「オンライン診療のためのシステム」としてローンチ され、 2018 年にはクラウド型電子カルテ機能を 、 2019 年には予約管理システム機能を 、 2020 年にはかかりつけ薬局支援システム Pharms との連携機能も追加し 、患者向けアプリからオンライン服薬指導をシームレスに受けることができるようになりました。 プロダクト開発室ではこれらオンライン診療機能・電子カルテ機能・予約管理機能・連携機能の改善を日々行っています。 画面共有機能の需要の高まり、実装の決定 このように CLINICS の改善を日々行なっている中、昨年から始まった 新型コロナウイルス感染症(COVID-19) の流行に伴った需要の増加により、オンライン診療の件数が急増しました。 CLINICS も数多くの医療機関にご利用いただく中で、オンライン診療に関わるさまざまなご要望をいただくようになりました。その中でも特に多かったものが、今回紹介する画面共有機能です。 対面での診察の際に医師が検査結果などを患者に見せながら説明するように、オンラインで診察する場合でも資料をリアルタイムで共有しながら説明ができるようになれば、今まで以上にオンラインでも質の高い診察を行えるようになります。 こういったユースケース、要望などを検討した結果、CLINICS を利用するすべての医療機関及び患者にとって大きな恩恵が見込まれたため、オンライン診察(ビデオチャット)中に医師の PC 画面をリアルタイムで患者に共有する機能として実装をすることにしました。 画面共有機能の実装 画面共有をする医師側向けのコードでどういった実装方法があるのか、大まかな流れをまとめます。 ※ 以下に記載しているコードは説明のための疑似コードですので、このままでは動作しないことにご注意ください。また、医師側の実装例を掲載しているため、患者側(画面共有を受ける側)の実装は別途必要になります。 オンライン診察開始までの処理 オンライン診察を開始するには医師側のマイクとカメラで取得した情報を患者側に送付する必要があります。ここではそこまでの実装の流れを見ていきます。 カメラ・マイクのストリームの取得 オンライン診察開始時点で医師側のマイク・カメラの情報を共有するため、まずはそれらのストリームを取得する必要があります。こういったメディアコンテンツのストリームを司るインターフェイスとして MediaStream が定義されています。 マイク・カメラの MediaStream は、例えば MediaDevices.getUserMedia() を利用して取得できます。 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); SkyWay 経由でオンライン診察を始める WebRTC で P2P のビデオチャットを利用するためには、初期の接続のための処理及び接続の維持などの処理を行う必要があります。弊社ではこのあたりの処理を WebRTC SaaS の SkyWay 及びその SDK を利用することで簡略化しています。 オンライン診察開始時には、先程取得した医師側のマイク・カメラの MediaStream を SkyWay の SDK に渡すことで、一対一でのリアルタイムビデオチャットを実現できます。 import Peer , { MediaConnection } from "skyway-js" ; const peer = new Peer ({ key: "your-api-key" }); // 事前に患者と共有しておいた peer id に対して call メソッドと MediaStream を渡すことで診察を開始できる。 const mediaConnection : MediaConnection = peer . call ( "shared-peer-id" , userMediaStream ); // 注: 患者側は送付された処理をハンドリングする機能を実装する必要がある ここまでがオンライン診察を開始するまでの処理です。 ※ 詳細は SkyWay 公式の チュートリアル などを参照ください。 画面共有の処理 ここまでで患者に対して医師側のカメラ・マイクで取得された映像・音声が表示されている状態のため、これを切り替える処理が必要になります。今回は現在接続に利用している MediaStream を、画面共有用の MediaStream に入れ替えることで実現しました。 画面の MediaStream の取得 まずは共有する画面の MediaStream を取得する必要があります。これは MediaDevices.getDisplayMedia() を使うことで実現できます。 const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); 画面共有用の MediaStream を作る getDisplayMedia() から共有する画面の MediaStream を取得できるものの、そのまま利用するとマイクの音声が入りません。 これは getDisplayMedia() から取れる MediaStream にマイクの音声が含まれていないことが原因なので、必要な画像・音声の組み合わせを持った画面共有用の MediaStream を作成することで対処ができます。 MediaStreamTrack を組みあわせて画面共有用の MediaStream を作る 画面共有用の MediaStream を作成する前にまず、MediaStreamTrack と MediaStream の関係を理解する必要があります。 MediaStreamTrack はストリームに含まれる一つのメディアトラックを表現するものです。 kind という読み取り専用プロパティがあり、オーディオトラックであれば "audio" が、ビデオトラックであれば "video" が設定されています。 また、 MediaStream は複数の MediaStreamTrack から成り、オーディオトラック・ビデオトラックを取り出すメソッドがそれぞれ MediaStream.getAudioTracks() ・ MediaStream.getVideoTracks() として実装されています。 これらを組み合わせることで、マイクと画面の MediaStreamTrack を持つ MediaStream を作ることができ、これを SkyWay の SDK に渡すことで、画面共有を実現できます。 const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingMediaStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); MediaStream の入れ替え 最後に画面共有状態への切り替えです。マイク・カメラが共有されている状態からの切り替えにはいくつかの方法が考えられます。 例えば、多重化であれば MediaConnection( Skyway の SDK の単位で、「接続先 Peer へのメディアチャネル接続」を管理する)の多重化、MediaStream の多重化、MediaStreamTrack の多重化がそれぞれ考えられます。これらの方法はマイク・カメラの切り替え時のチラつき抑制など実装上の選択肢が増えるメリットがある一方で、通信量が多くなってしまう点がデメリットと言えます。 今回は多重化をせずに既存の MediaStream を切り替える実装を紹介します。この方法のメリットは、多重化に比べると通信量が少なく、またすでに MediaStream が一つである前提で作られている場合は、画面共有を受ける側の実装の変更が不要という点です。 この方法は、 SkyWay の SDK であれば MediaConnection の replaceStream というメソッド に対して新しい MediaStream を渡すことで実現ができます。 // 画面共有用の MediaStream を渡すことで、画面共有を開始する // MediaConnection は先程 `peer.call` した際の返り値として取れているため、それを利用する mediaConnection . replaceStream ( sharingMediaStream ); 実装前に懸念していたマイク・カメラの切り替え時のチラつきなども気になるほどはなく、実用に足るような品質を保つことができることを確認しています。 実装の全体概要 以上の流れを実装すると、次のようなコードになります。 import Peer , { MediaConnection } from "skyway-js" ; /** 医師側のマイク・カメラを共有してオンライン診察開始するところまで **/ // getUserMedia()でカメラ・マイクのストリームを取得 const userMediaStream : MediaStream = await navigator . mediaDevices . getUserMedia ({ video: true , audio: true , }); // Skyway sdk の初期化処理 const peer = new Peer ({ key: "your-api-key" }); // オンライン診察の開始 const mediaConnection : MediaConnection = peer . call ( "peerId" , userMediaStream ); /** 画面共有を開始する処理 **/ // 画面共有する画面の stream を取る const displayStream : MediaStream = await navigator . mediaDevices . getDisplayMedia ( { video: true } ); const [ displayVideoTrack ]: MediaStreamTrack [] = displayStream . getVideoTracks (); // 画面共有の音声はマイクの音声を利用したいので、userMediaStream から audioTrack を取り出しておく const [ userAudioTrack ]: MediaStreamTrack [] = userMediaStream . getAudioTracks (); // 画面共有するための MediaStream を作成する(画面の videoTrack、マイクの audioTrack を持つ MediaStream を作る) const sharingStream : MediaStream = new MediaStream ([ displayVideoTrack , userAudioTrack , ]); // 画面共有用の MediaStream を渡すことで、画面共有を開始する mediaConnection . replaceStream ( sharingStream ); 開発中に遭遇した問題への対応 スリープモード・共有を停止ボタンを押したときの対応 Google Chrome で画面共有の際に表示される「共有を停止」ボタンを押下したり、PC をスリープモードにすると、画面の MediaStreamTrack が途切れてしまいます。 これは該当の MediaStreamTrack に "ended" のイベントリスナを登録しておくことでハンドリングできます。 displayVideoTrack . addEventListener ( "ended" , handleEndedEvent , { once: true }); TypeScript の型の対応 現状 TypeScript の型が getDisplayMedia() に対応していなかったため、今回は実装の参考にしている skyway-conf で利用されている型 を流用する形で対応をしました。 declare global { interface MediaDevices { getDisplayMedia ( constraints : MediaStreamConstraints ): Promise < MediaStream >; } } これは根本的には TypeScript の dom.d.ts に型定義が入っていないことが起因していますが、 TypeScript4.4 で対応がされるようです 。 まとめ 昨今の状況により、オンライン診察のニーズが高まり、画面共有機能の重要性が高まりました。 診察中の画面共有機能は以下の api を組み合わせることで実現することができます。 PC 画面の MediaStream は getDisplayMedia() を使うことで取得 MediaStream に含める音声・画像ストリームを変更したい場合は MediaStreamTrack の組み合わせを変えることで作成 接続中の MediaStream の変更は SkyWay の SDK の MediaConnection.replaceStream() を使う 最後に CLINICS では本稿で紹介した画面共有などの新規機能の導入や日々の改善を通じて、医療機関・患者双方に支持されるプロダクトを目指し開発を行っています。興味を持たれたエンジニアの方がいらっしゃいましたらぜひ こちら にご連絡いただければと思います。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに はじめまして、コーポレートエンジニアの山下です。 2020 年に Slack を活用した ChatOps 稟議ワークフローを内製で開発したのですが、さらに、2021 年 4 月にこの Slack 稟議と電子契約システムである クラウドサイン を連携させて、電子契約をもっと便利に使い、生産性の向上を実現しましたのでお話しいたします。 まず、当社の稟議システムは 2020 年 12 月の当社の記事 のおさらいになりますが、稟議の作業が Slack 上で完結する、 ChatOps による稟議ワークフロー となっております。本稿については 2021 年 7 月に執筆しておりますので丁度導入から 1 年程経過し、その間大きなトラブルも無く、今も当社の極めて迅速な意思決定の一助になっています。ChatOps による稟議ワークフローについては、直近、2021 年 6 月に LayerX 社が LayerX ワークフローの新機能として発表 し、サービスとしても提供され、 日経新聞 でも取り上げられていることから、今現在のパラダイムとして、先進的で有効な一手法であったと再認識しております。 今回、新型コロナウイルス感染拡大防止に伴うリモートワークの加速という状況もあり、当社で 2021 年 4 月に電子契約システムとしてクラウドサインを導入しました。電子契約に限らず、契約押印作業は稟議の後続作業に当たるため、ただ導入して使用するのみならず、クラウドサインの API を利用して稟議上にあるデータを電子契約に送信させることでシームレスな連携を実現しています。本稿では当社が行ったシームレスな連携手法について詳細をご説明いたします。 TeamSpirit とクラウドサインの API 連携について 実装概要 弊社の稟議システムである TeamSpirit とクラウドサインとの連携についてお話しします。まず、本稿の開発部分とシステム構成は下記になっております。 処理内容の詳細は後ほど述べますが、概要としては TeamSprit(Apex)からクラウドサインの API をコールし、クラウドサイン上で作成した契約文書へ稟議に記載されている契約書ファイルや先方担当者等の情報を連携する仕組みとなっております。これにより契約担当者はクラウドサインにログイン後、下記の 3 ステップで先方に送信できるようになっています。 記載内容の確認 押印・署名箇所の設定 先方への送信 クラウドサインを使用して契約文書を一から作成する場合のユーザ作業と、当社で採用した API 連携行った場合のユーザ作業を比較したものが下記の表です。作業が半分程度削減されたことが分かります。 作業項番 一から作成する場合 API 連携を利用した当社の場合 1 ログイン ログイン 2 契約文書の作成(件名、契約文書としての宛名設定等) なし 3 契約書ファイルのアップロード なし 4 先方の送信先設定 なし 5 押印欄の設定 押印欄の設定 6 先方への送信 先方への送信 実装 今回の開発で使用した クラウドサイン API は下記の 5 つの API を使用しました (※以降、クラウドサイン API に倣い、変数を表現する場合は {} で括ります)。 API 種類 使用用途 post /token アクセストークンの取得 post /document 契約文書の作成 put /documents/{documentID}/attribute 契約文書の作成で設定できない、細かい項目の設定 post /documents/{documentID}/files ファイルのアップロード post /documents/{documentID}/participants 先方の送信先設定 全体像で記載したクラウドサインの連携部について、上記の API を織り交ぜて詳細化すると下図のようになります。 実装方法としてはクラウドサイン API のリファレンスを参照し、テスト実行時に出力される curl コマンドを参考に同様のレスポンスを得るように Apex で HTTP リクエストを実装しました。アクセストークンの取得を例にとると下記のようになります。 API リファレンスでの curl コマンド例 curl -X 'POST' \ 'https://api.cloudsign.jp/token' \ -H 'accept: application/json' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'client_id=xxxxxxyyyyyyzzzzzz' Apex でのリクエスト実装例 HttpRequest req = new HttpRequest (); req . setMethod ( 'POST' ); req . setEndpoint ( 'https://api.cloudsign.jp/token' ); req . setHeader ( 'accept' , 'application/json' ); req . setHeader ( 'Content-Type' , 'application/x-www-form-urlencoded' ); req . setBody ( 'client_id=' + 'xxxxxxyyyyyyzzzzzz' ); 私自身、Apex もクラウドサイン API もこの案件を担当するまで触ったことがありませんでしたが、リクエストの試行から実装まで 2 週間かからない程度で実装することができました。 ただし、実装や運用にあたっては下記 2 点について注意が必要になります。 Apex からクラウドサインへのファイルのアップロードは単純ではない アクセストークンの有効期限はクラウドサインでコントロールされる 1. Apex からクラウドサインへのファイルのアップロードは単純ではない ファイルのアップロードについては今回使用した API の中で、唯一、テスト実行の curl と Apex のリクエスト実装で差分が生まれます。まず、その差分を確認するために curl コマンド例と Apex のリクエスト実装例で header、body にセットしている値を比較してみます。 API リファレンスでの curl コマンド例 curl -X 'POST' \ 'https://api.cloudsign.jp/documents/{document_id}/files' \ -H 'accept: application/json' \ -H 'Authorization: AAAAAABBBBBBCCCCCC' \ -H 'Content-Type: multipart/form-data' \ -F 'name=テスト' \ -F 'uploadfile=@テスト.pdf;type=application/pdf' Apex でのリクエスト実装例 HttpRequest req = new HttpRequest (); req . setMethod ( 'POST' ); req . setEndpoint ( 'https://api.cloudsign.jp/documents/{document_id}/files' ); req . setHeader ( 'accept' , 'application/json' ); req . setHeader ( 'Authorization' , ‘AAAAAABBBBBBCCCCCC’); req . setHeader ( 'Content-Type' , 'multipart/form-data; boundary={boundary}' ); // ※1 req . setBodyAsBlob ({multipartBody}); // ※2 主な違いは ※1 , ※2 とコメントした部分になります。 Apex では HTTP リクエストの値を手で書いていくことになるので、テスト実行例のように curl がよしなに処理している部分(-F オプションの部分や Apex で記載している boundary)も実装しなければなりません。これが単純に実装できない理由になります。 boundary については multipart/form-data を送信する際に必要な境界でヘッダーでどの文字列が境界であるかを設定します。 curl の-F オプションで定義していた文字列とファイル指定部分は、Apex でファイル(バイナリ)を扱うため、その body に含まれる文字列も含めて Blob 型で扱う必要があります( Content-Transfer-Encoding: base64 に API 提供側が対応している場合は例外になります)。そのため、文字列とバイナリデータを結合し一つの Blob にする方法は下記になります。 「バイナリデータ」、「body の開始からバイナリデータまでの文字列」、「バイナリデータ以降から終端までの文字列」の 3 グループに分ける。 3 グループをそれぞれ Base64 で符号化する。 符号化した「バイナリデータ」と「body の開始からバイナリデータまでの文字列」について、Base64 のデータパディングを示す”=”が含まれないように改行コードで調整する。 「body の開始からバイナリデータまでの文字列」、「バイナリデータ」、「バイナリデータ以降から終端までの文字列」の順で結合する。 結合した Base64 のデータを復号して、一つの Blob とする。 2. アクセストークンの有効期限はクラウドサインでコントロールされる アクセストークンやその有効期限は token API を発行した際のレスポンスとしてクラウドサインから発行されます。 発行されたレスポンス例 { "access_token" : "AAAAAABBBBBBCCCCCC" , "token_type" : “xxxx” , "expires_in" : 3600 } このレスポンスの内、expires_in の値がトークンの有効期限になります。掲題の通り、有効期限の管理はクラウドサイン側で行われ、有効期限内に再度トークンのリクエストを行った場合、経過した時間だけ expires_in の値が小さくなった結果が返ってきて、access_token などは同じ値が取得されます。有効期限内に token API を再度実行した結果が下記になります。 有効期限切れ前に token API を発行した際のレスポンス例 { "access_token" : "AAAAAABBBBBBCCCCCC" , "token_type" : “xxxx” , "expires_in" : 762 } 一方、有効期限後にトークンのリクエストを実行すると、それまでと異なるアクセストークンを取得し、新しい有効期限が設定されます。 有効期限切れ後に token API を発行した際のレスポンス例 { "access_token" : "XXXXXXYYYYYYZZZZZZ" , "token_type" : "xxxx" , "expires_in" : 3600 } そのため、API 連携が一度動いた後、有効期限ぎりぎりでもう一度 API 連携が動いてしまった場合、タイミングが悪いと契約文書の作成から最終処理である先方の送信先設定までのプロセス内のどこかから、トークンの有効期限切れが発生する可能性が想定されます。実際に期限切れが発生した場合、発生時以降に発行したその回の API 連携処理が失敗します。 トークンの有効期限切れが発生した際、API リファレンスより HTTP ステータスコードが 401 かつエラー内容が”unauthorized”で応答されることから、当社ではこのエラーを受けた場合にトークンを再取得して処理をリトライするように実装しました。 押印文書作成を例にとると下記のような実装イメージになります。 //クラウドサイン上に押印文書を作成し、作成した文書 ID を取得する public String getDocumentId ( String authToken, String title, String message){ ・・・中略・・・ HTTPResponse res = http . send (req); if ( res . getStatusCode () == 200 ){ ・・・正常に終了した際の処理・・・ } //タイミングが悪く token がタイムアウトした場合、トークンを取得し直して、リトライする else if ( res . getStatusCode () == 401 ){ //レスポンスの内容を確認するため、エラーレスポンスの中身を取得する Map < String , Object > responseBody = new Map < String , Object >(); responseBody = ( Map <String, Object>) JSON . deserializeUntyped ( res . getBody ()); String errorVal = (String) responseBody . get ( 'error' ); //リファレンス上、アクセストークンが無効(有効期限切れ)の場合、'unauthorized’となる if ( errorVal . equals ( 'unauthorized' )){ //クラウドサインのアクセストークンの再取得 authToken = getAuthToken (); //単純再帰で再実行する。 documentId = getDocumentId (authToken, title, message); } ・・・中略・・・ } ・・・以下省略・・・ } 実装を終えて 上記を実装した結果、稟議と入力内容が同じ、または、稟議から生成できる内容は全てシステム連携で自動生成するため、押印担当は稟議とクラウドサインの画面を並べて転記するような煩雑な作業を必要としない環境になりました。また、契約書の製本、郵送等の紙媒体であるが故の事務の削減ができるようになる等の、電子契約を導入することのそもそものメリットも併せて享受しています。 当社では 2021 年 4 月後半からクラウドサインを導入しましたが、2021 年 6 月時点ではすでに 月間で締結した契約書の「3 割以上」が電子契約を活用しており 、押印担当の展望として今後も利用を拡大していく予定です。 コーポレートエンジニア募集中 メドレーのコーポレート部門では、本稿のように、SaaS の導入ひとつとっても検討を尽くし、既存のシステムと有機的に結合させることで「徹底的に合理性を追求した組織基盤や、仕掛けづくり」を行っています。 面白そう!興味がある!と感じた方は、ぜひ当社採用ページからご応募お願いします! 最後までお読みいただきありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに はじめまして、コーポレートエンジニアの山下です。 2020 年に Slack を活用した ChatOps 稟議ワークフローを内製で開発したのですが、さらに、2021 年 4 月にこの Slack 稟議と電子契約システムである クラウドサイン を連携させて、電子契約をもっと便利に使い、生産性の向上を実現しましたのでお話しいたします。 まず、当社の稟議システムは 2020 年 12 月の当社の記事 のおさらいになりますが、稟議の作業が Slack 上で完結する、 ChatOps による稟議ワークフロー となっております。本稿については 2021 年 7 月に執筆しておりますので丁度導入から 1 年程経過し、その間大きなトラブルも無く、今も当社の極めて迅速な意思決定の一助になっています。ChatOps による稟議ワークフローについては、直近、2021 年 6 月に LayerX 社が LayerX ワークフローの新機能として発表 し、サービスとしても提供され、 日経新聞 でも取り上げられていることから、今現在のパラダイムとして、先進的で有効な一手法であったと再認識しております。 今回、新型コロナウイルス感染拡大防止に伴うリモートワークの加速という状況もあり、当社で 2021 年 4 月に電子契約システムとしてクラウドサインを導入しました。電子契約に限らず、契約押印作業は稟議の後続作業に当たるため、ただ導入して使用するのみならず、クラウドサインの API を利用して稟議上にあるデータを電子契約に送信させることでシームレスな連携を実現しています。本稿では当社が行ったシームレスな連携手法について詳細をご説明いたします。 TeamSpirit とクラウドサインの API 連携について 実装概要 弊社の稟議システムである TeamSpirit とクラウドサインとの連携についてお話しします。まず、本稿の開発部分とシステム構成は下記になっております。 処理内容の詳細は後ほど述べますが、概要としては TeamSprit(Apex)からクラウドサインの API をコールし、クラウドサイン上で作成した契約文書へ稟議に記載されている契約書ファイルや先方担当者等の情報を連携する仕組みとなっております。これにより契約担当者はクラウドサインにログイン後、下記の 3 ステップで先方に送信できるようになっています。 記載内容の確認 押印・署名箇所の設定 先方への送信 クラウドサインを使用して契約文書を一から作成する場合のユーザ作業と、当社で採用した API 連携行った場合のユーザ作業を比較したものが下記の表です。作業が半分程度削減されたことが分かります。 作業項番 一から作成する場合 API 連携を利用した当社の場合 1 ログイン ログイン 2 契約文書の作成(件名、契約文書としての宛名設定等) なし 3 契約書ファイルのアップロード なし 4 先方の送信先設定 なし 5 押印欄の設定 押印欄の設定 6 先方への送信 先方への送信 実装 今回の開発で使用した クラウドサイン API は下記の 5 つの API を使用しました (※以降、クラウドサイン API に倣い、変数を表現する場合は {} で括ります)。 API 種類 使用用途 post /token アクセストークンの取得 post /document 契約文書の作成 put /documents/{documentID}/attribute 契約文書の作成で設定できない、細かい項目の設定 post /documents/{documentID}/files ファイルのアップロード post /documents/{documentID}/participants 先方の送信先設定 全体像で記載したクラウドサインの連携部について、上記の API を織り交ぜて詳細化すると下図のようになります。 実装方法としてはクラウドサイン API のリファレンスを参照し、テスト実行時に出力される curl コマンドを参考に同様のレスポンスを得るように Apex で HTTP リクエストを実装しました。アクセストークンの取得を例にとると下記のようになります。 API リファレンスでの curl コマンド例 curl -X 'POST' \ 'https://api.cloudsign.jp/token' \ -H 'accept: application/json' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'client_id=xxxxxxyyyyyyzzzzzz' Apex でのリクエスト実装例 HttpRequest req = new HttpRequest (); req . setMethod ( 'POST' ); req . setEndpoint ( 'https://api.cloudsign.jp/token' ); req . setHeader ( 'accept' , 'application/json' ); req . setHeader ( 'Content-Type' , 'application/x-www-form-urlencoded' ); req . setBody ( 'client_id=' + 'xxxxxxyyyyyyzzzzzz' ); 私自身、Apex もクラウドサイン API もこの案件を担当するまで触ったことがありませんでしたが、リクエストの試行から実装まで 2 週間かからない程度で実装することができました。 ただし、実装や運用にあたっては下記 2 点について注意が必要になります。 Apex からクラウドサインへのファイルのアップロードは単純ではない アクセストークンの有効期限はクラウドサインでコントロールされる 1. Apex からクラウドサインへのファイルのアップロードは単純ではない ファイルのアップロードについては今回使用した API の中で、唯一、テスト実行の curl と Apex のリクエスト実装で差分が生まれます。まず、その差分を確認するために curl コマンド例と Apex のリクエスト実装例で header、body にセットしている値を比較してみます。 API リファレンスでの curl コマンド例 curl -X 'POST' \ 'https://api.cloudsign.jp/documents/{document_id}/files' \ -H 'accept: application/json' \ -H 'Authorization: AAAAAABBBBBBCCCCCC' \ -H 'Content-Type: multipart/form-data' \ -F 'name=テスト' \ -F 'uploadfile=@テスト.pdf;type=application/pdf' Apex でのリクエスト実装例 HttpRequest req = new HttpRequest (); req . setMethod ( 'POST' ); req . setEndpoint ( 'https://api.cloudsign.jp/documents/{document_id}/files' ); req . setHeader ( 'accept' , 'application/json' ); req . setHeader ( 'Authorization' , ‘AAAAAABBBBBBCCCCCC’); req . setHeader ( 'Content-Type' , 'multipart/form-data; boundary={boundary}' ); // ※1 req . setBodyAsBlob ({multipartBody}); // ※2 主な違いは ※1 , ※2 とコメントした部分になります。 Apex では HTTP リクエストの値を手で書いていくことになるので、テスト実行例のように curl がよしなに処理している部分(-F オプションの部分や Apex で記載している boundary)も実装しなければなりません。これが単純に実装できない理由になります。 boundary については multipart/form-data を送信する際に必要な境界でヘッダーでどの文字列が境界であるかを設定します。 curl の-F オプションで定義していた文字列とファイル指定部分は、Apex でファイル(バイナリ)を扱うため、その body に含まれる文字列も含めて Blob 型で扱う必要があります( Content-Transfer-Encoding: base64 に API 提供側が対応している場合は例外になります)。そのため、文字列とバイナリデータを結合し一つの Blob にする方法は下記になります。 「バイナリデータ」、「body の開始からバイナリデータまでの文字列」、「バイナリデータ以降から終端までの文字列」の 3 グループに分ける。 3 グループをそれぞれ Base64 で符号化する。 符号化した「バイナリデータ」と「body の開始からバイナリデータまでの文字列」について、Base64 のデータパディングを示す”=”が含まれないように改行コードで調整する。 「body の開始からバイナリデータまでの文字列」、「バイナリデータ」、「バイナリデータ以降から終端までの文字列」の順で結合する。 結合した Base64 のデータを復号して、一つの Blob とする。 2. アクセストークンの有効期限はクラウドサインでコントロールされる アクセストークンやその有効期限は token API を発行した際のレスポンスとしてクラウドサインから発行されます。 発行されたレスポンス例 { "access_token" : "AAAAAABBBBBBCCCCCC" , "token_type" : “xxxx” , "expires_in" : 3600 } このレスポンスの内、expires_in の値がトークンの有効期限になります。掲題の通り、有効期限の管理はクラウドサイン側で行われ、有効期限内に再度トークンのリクエストを行った場合、経過した時間だけ expires_in の値が小さくなった結果が返ってきて、access_token などは同じ値が取得されます。有効期限内に token API を再度実行した結果が下記になります。 有効期限切れ前に token API を発行した際のレスポンス例 { "access_token" : "AAAAAABBBBBBCCCCCC" , "token_type" : “xxxx” , "expires_in" : 762 } 一方、有効期限後にトークンのリクエストを実行すると、それまでと異なるアクセストークンを取得し、新しい有効期限が設定されます。 有効期限切れ後に token API を発行した際のレスポンス例 { "access_token" : "XXXXXXYYYYYYZZZZZZ" , "token_type" : "xxxx" , "expires_in" : 3600 } そのため、API 連携が一度動いた後、有効期限ぎりぎりでもう一度 API 連携が動いてしまった場合、タイミングが悪いと契約文書の作成から最終処理である先方の送信先設定までのプロセス内のどこかから、トークンの有効期限切れが発生する可能性が想定されます。実際に期限切れが発生した場合、発生時以降に発行したその回の API 連携処理が失敗します。 トークンの有効期限切れが発生した際、API リファレンスより HTTP ステータスコードが 401 かつエラー内容が”unauthorized”で応答されることから、当社ではこのエラーを受けた場合にトークンを再取得して処理をリトライするように実装しました。 押印文書作成を例にとると下記のような実装イメージになります。 //クラウドサイン上に押印文書を作成し、作成した文書 ID を取得する public String getDocumentId ( String authToken, String title, String message){ ・・・中略・・・ HTTPResponse res = http . send (req); if ( res . getStatusCode () == 200 ){ ・・・正常に終了した際の処理・・・ } //タイミングが悪く token がタイムアウトした場合、トークンを取得し直して、リトライする else if ( res . getStatusCode () == 401 ){ //レスポンスの内容を確認するため、エラーレスポンスの中身を取得する Map < String , Object > responseBody = new Map < String , Object >(); responseBody = ( Map <String, Object>) JSON . deserializeUntyped ( res . getBody ()); String errorVal = (String) responseBody . get ( 'error' ); //リファレンス上、アクセストークンが無効(有効期限切れ)の場合、'unauthorized’となる if ( errorVal . equals ( 'unauthorized' )){ //クラウドサインのアクセストークンの再取得 authToken = getAuthToken (); //単純再帰で再実行する。 documentId = getDocumentId (authToken, title, message); } ・・・中略・・・ } ・・・以下省略・・・ } 実装を終えて 上記を実装した結果、稟議と入力内容が同じ、または、稟議から生成できる内容は全てシステム連携で自動生成するため、押印担当は稟議とクラウドサインの画面を並べて転記するような煩雑な作業を必要としない環境になりました。また、契約書の製本、郵送等の紙媒体であるが故の事務の削減ができるようになる等の、電子契約を導入することのそもそものメリットも併せて享受しています。 当社では 2021 年 4 月後半からクラウドサインを導入しましたが、2021 年 6 月時点ではすでに 月間で締結した契約書の「3 割以上」が電子契約を活用しており 、押印担当の展望として今後も利用を拡大していく予定です。 コーポレートエンジニア募集中 メドレーのコーポレート部門では、本稿のように、SaaS の導入ひとつとっても検討を尽くし、既存のシステムと有機的に結合させることで「徹底的に合理性を追求した組織基盤や、仕掛けづくり」を行っています。 面白そう!興味がある!と感じた方は、ぜひ当社採用ページからご応募お願いします! 最後までお読みいただきありがとうございました。 https://www.medley.jp/jobs/
はじめに はじめまして、コーポレートエンジニアの山下です。 2020 年に Slack を活用した ChatOps 稟議ワークフローを内製で開発したのですが、さらに、2021 年 4 月にこの Slack 稟議と電子契約システムである クラウドサイン を連携させて、電子契約をもっと便利に使い、生産性の向上を実現しましたのでお話しいたします。 まず、当社の稟議システムは 2020 年 12 月の当社の記事 のおさらいになりますが、稟議の作業が Slack 上で完結する、 ChatOps による稟議ワークフロー となっております。本稿については 2021 年 7 月に執筆しておりますので丁度導入から 1 年程経過し、その間大きなトラブルも無く、今も当社の極めて迅速な意思決定の一助になっています。ChatOps による稟議ワークフローについては、直近、2021 年 6 月に LayerX 社が LayerX ワークフローの新機能として発表 し、サービスとしても提供され、 日経新聞 でも取り上げられていることから、今現在のパラダイムとして、先進的で有効な一手法であったと再認識しております。 今回、新型コロナウイルス感染拡大防止に伴うリモートワークの加速という状況もあり、当社で 2021 年 4 月に電子契約システムとしてクラウドサインを導入しました。電子契約に限らず、契約押印作業は稟議の後続作業に当たるため、ただ導入して使用するのみならず、クラウドサインの API を利用して稟議上にあるデータを電子契約に送信させることでシームレスな連携を実現しています。本稿では当社が行ったシームレスな連携手法について詳細をご説明いたします。 TeamSpirit とクラウドサインの API 連携について 実装概要 弊社の稟議システムである TeamSpirit とクラウドサインとの連携についてお話しします。まず、本稿の開発部分とシステム構成は下記になっております。 処理内容の詳細は後ほど述べますが、概要としては TeamSprit(Apex)からクラウドサインの API をコールし、クラウドサイン上で作成した契約文書へ稟議に記載されている契約書ファイルや先方担当者等の情報を連携する仕組みとなっております。これにより契約担当者はクラウドサインにログイン後、下記の 3 ステップで先方に送信できるようになっています。 記載内容の確認 押印・署名箇所の設定 先方への送信 クラウドサインを使用して契約文書を一から作成する場合のユーザ作業と、当社で採用した API 連携行った場合のユーザ作業を比較したものが下記の表です。作業が半分程度削減されたことが分かります。 作業項番 一から作成する場合 API 連携を利用した当社の場合 1 ログイン ログイン 2 契約文書の作成(件名、契約文書としての宛名設定等) なし 3 契約書ファイルのアップロード なし 4 先方の送信先設定 なし 5 押印欄の設定 押印欄の設定 6 先方への送信 先方への送信 実装 今回の開発で使用した クラウドサイン API は下記の 5 つの API を使用しました (※以降、クラウドサイン API に倣い、変数を表現する場合は {} で括ります)。 API 種類 使用用途 post /token アクセストークンの取得 post /document 契約文書の作成 put /documents/{documentID}/attribute 契約文書の作成で設定できない、細かい項目の設定 post /documents/{documentID}/files ファイルのアップロード post /documents/{documentID}/participants 先方の送信先設定 全体像で記載したクラウドサインの連携部について、上記の API を織り交ぜて詳細化すると下図のようになります。 実装方法としてはクラウドサイン API のリファレンスを参照し、テスト実行時に出力される curl コマンドを参考に同様のレスポンスを得るように Apex で HTTP リクエストを実装しました。アクセストークンの取得を例にとると下記のようになります。 API リファレンスでの curl コマンド例 curl -X 'POST' \ 'https://api.cloudsign.jp/token' \ -H 'accept: application/json' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'client_id=xxxxxxyyyyyyzzzzzz' Apex でのリクエスト実装例 HttpRequest req = new HttpRequest (); req . setMethod ( 'POST' ); req . setEndpoint ( 'https://api.cloudsign.jp/token' ); req . setHeader ( 'accept' , 'application/json' ); req . setHeader ( 'Content-Type' , 'application/x-www-form-urlencoded' ); req . setBody ( 'client_id=' + 'xxxxxxyyyyyyzzzzzz' ); 私自身、Apex もクラウドサイン API もこの案件を担当するまで触ったことがありませんでしたが、リクエストの試行から実装まで 2 週間かからない程度で実装することができました。 ただし、実装や運用にあたっては下記 2 点について注意が必要になります。 Apex からクラウドサインへのファイルのアップロードは単純ではない アクセストークンの有効期限はクラウドサインでコントロールされる 1. Apex からクラウドサインへのファイルのアップロードは単純ではない ファイルのアップロードについては今回使用した API の中で、唯一、テスト実行の curl と Apex のリクエスト実装で差分が生まれます。まず、その差分を確認するために curl コマンド例と Apex のリクエスト実装例で header、body にセットしている値を比較してみます。 API リファレンスでの curl コマンド例 curl -X 'POST' \ 'https://api.cloudsign.jp/documents/{document_id}/files' \ -H 'accept: application/json' \ -H 'Authorization: AAAAAABBBBBBCCCCCC' \ -H 'Content-Type: multipart/form-data' \ -F 'name=テスト' \ -F 'uploadfile=@テスト.pdf;type=application/pdf' Apex でのリクエスト実装例 HttpRequest req = new HttpRequest (); req . setMethod ( 'POST' ); req . setEndpoint ( 'https://api.cloudsign.jp/documents/{document_id}/files' ); req . setHeader ( 'accept' , 'application/json' ); req . setHeader ( 'Authorization' , ‘AAAAAABBBBBBCCCCCC’); req . setHeader ( 'Content-Type' , 'multipart/form-data; boundary={boundary}' ); // ※1 req . setBodyAsBlob ({multipartBody}); // ※2 主な違いは ※1 , ※2 とコメントした部分になります。 Apex では HTTP リクエストの値を手で書いていくことになるので、テスト実行例のように curl がよしなに処理している部分(-F オプションの部分や Apex で記載している boundary)も実装しなければなりません。これが単純に実装できない理由になります。 boundary については multipart/form-data を送信する際に必要な境界でヘッダーでどの文字列が境界であるかを設定します。 curl の-F オプションで定義していた文字列とファイル指定部分は、Apex でファイル(バイナリ)を扱うため、その body に含まれる文字列も含めて Blob 型で扱う必要があります( Content-Transfer-Encoding: base64 に API 提供側が対応している場合は例外になります)。そのため、文字列とバイナリデータを結合し一つの Blob にする方法は下記になります。 「バイナリデータ」、「body の開始からバイナリデータまでの文字列」、「バイナリデータ以降から終端までの文字列」の 3 グループに分ける。 3 グループをそれぞれ Base64 で符号化する。 符号化した「バイナリデータ」と「body の開始からバイナリデータまでの文字列」について、Base64 のデータパディングを示す”=”が含まれないように改行コードで調整する。 「body の開始からバイナリデータまでの文字列」、「バイナリデータ」、「バイナリデータ以降から終端までの文字列」の順で結合する。 結合した Base64 のデータを復号して、一つの Blob とする。 2. アクセストークンの有効期限はクラウドサインでコントロールされる アクセストークンやその有効期限は token API を発行した際のレスポンスとしてクラウドサインから発行されます。 発行されたレスポンス例 { "access_token" : "AAAAAABBBBBBCCCCCC" , "token_type" : “xxxx” , "expires_in" : 3600 } このレスポンスの内、expires_in の値がトークンの有効期限になります。掲題の通り、有効期限の管理はクラウドサイン側で行われ、有効期限内に再度トークンのリクエストを行った場合、経過した時間だけ expires_in の値が小さくなった結果が返ってきて、access_token などは同じ値が取得されます。有効期限内に token API を再度実行した結果が下記になります。 有効期限切れ前に token API を発行した際のレスポンス例 { "access_token" : "AAAAAABBBBBBCCCCCC" , "token_type" : “xxxx” , "expires_in" : 762 } 一方、有効期限後にトークンのリクエストを実行すると、それまでと異なるアクセストークンを取得し、新しい有効期限が設定されます。 有効期限切れ後に token API を発行した際のレスポンス例 { "access_token" : "XXXXXXYYYYYYZZZZZZ" , "token_type" : "xxxx" , "expires_in" : 3600 } そのため、API 連携が一度動いた後、有効期限ぎりぎりでもう一度 API 連携が動いてしまった場合、タイミングが悪いと契約文書の作成から最終処理である先方の送信先設定までのプロセス内のどこかから、トークンの有効期限切れが発生する可能性が想定されます。実際に期限切れが発生した場合、発生時以降に発行したその回の API 連携処理が失敗します。 トークンの有効期限切れが発生した際、API リファレンスより HTTP ステータスコードが 401 かつエラー内容が”unauthorized”で応答されることから、当社ではこのエラーを受けた場合にトークンを再取得して処理をリトライするように実装しました。 押印文書作成を例にとると下記のような実装イメージになります。 //クラウドサイン上に押印文書を作成し、作成した文書 ID を取得する public String getDocumentId ( String authToken, String title, String message){ ・・・中略・・・ HTTPResponse res = http . send (req); if ( res . getStatusCode () == 200 ){ ・・・正常に終了した際の処理・・・ } //タイミングが悪く token がタイムアウトした場合、トークンを取得し直して、リトライする else if ( res . getStatusCode () == 401 ){ //レスポンスの内容を確認するため、エラーレスポンスの中身を取得する Map < String , Object > responseBody = new Map < String , Object >(); responseBody = ( Map <String, Object>) JSON . deserializeUntyped ( res . getBody ()); String errorVal = (String) responseBody . get ( 'error' ); //リファレンス上、アクセストークンが無効(有効期限切れ)の場合、'unauthorized’となる if ( errorVal . equals ( 'unauthorized' )){ //クラウドサインのアクセストークンの再取得 authToken = getAuthToken (); //単純再帰で再実行する。 documentId = getDocumentId (authToken, title, message); } ・・・中略・・・ } ・・・以下省略・・・ } 実装を終えて 上記を実装した結果、稟議と入力内容が同じ、または、稟議から生成できる内容は全てシステム連携で自動生成するため、押印担当は稟議とクラウドサインの画面を並べて転記するような煩雑な作業を必要としない環境になりました。また、契約書の製本、郵送等の紙媒体であるが故の事務の削減ができるようになる等の、電子契約を導入することのそもそものメリットも併せて享受しています。 当社では 2021 年 4 月後半からクラウドサインを導入しましたが、2021 年 6 月時点ではすでに 月間で締結した契約書の「3 割以上」が電子契約を活用しており 、押印担当の展望として今後も利用を拡大していく予定です。 コーポレートエンジニア募集中 メドレーのコーポレート部門では、本稿のように、SaaS の導入ひとつとっても検討を尽くし、既存のシステムと有機的に結合させることで「徹底的に合理性を追求した組織基盤や、仕掛けづくり」を行っています。 面白そう!興味がある!と感じた方は、ぜひ当社採用ページからご応募お願いします! 最後までお読みいただきありがとうございました。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp