株式会社メドレーのブログ - TECH PLAY

TECH PLAY

株式会社メドレー

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

1411

はじめに こんにちは。コーポレートデザイン部でコーポレート IT を担当している清水です。 コーポレート IT では全従業員が利用するネットワークインフラや SaaS といった IT 全般を統括しており、社内で発生している課題について IT を駆使して解決しています。 今回は、こうした課題解決の一例として、「 呼び鈴アプリ 」の開発について背景と実装のご紹介をし、最後に コーポレート IT チーム の業務内容や理念についてご紹介したいと思います。 呼び鈴アプリの開発背景 弊社では、チャットツールである「 Slack 」と「 Google Workspace 」や「 TeamSpirit 」等を連携して、来客者受付システムや 稟議ワークフローシステム 、オフィス空調最適化システム等、チャットをベースにした業務運用(ChatOps)を数多く行っています。 今回はその中でも、サポートを受けたい場合にコーポレート部門の担当者を呼び出しできるアプリ、 通称:呼び鈴アプリ をご紹介しようと思います。 呼び鈴アプリ導入前の困りごと IT 備品や健康保険証等の物の受け渡しや PC トラブルへの対応など、従業員へのサポート対応の際、サポートを依頼した従業員とコーポレート部門との間で、以下のような困りごとが発生していました。 ■ 従業員側の困りごと ・誰に話しかけたらよいかわからない IT 備品や健康保険証等を受け取りにコーポレート部門の座席に来たが、誰に話しかけたらよいかわからず、ウロウロしている方が結構いました。結果、別の部門に話しかけて、別の部門の方が正しい部門に案内するという時間も発生していました。 ・時間調整に手間がかかる PC トラブルへの対応の際は、コーポレート IT の担当者が依頼者の席まで行って作業をしていたため、担当者と依頼者で時間の調整が必要でした。直前で都合が悪くなり、リスケが発生することもしばしばありました。 ■ コーポレート部門の困りごと ・依頼者が見つけづらい 座席表を見て依頼者のところまで行くが、直近で座席変更があって居ると思っていたところに居ないことや、指定の時間に席に居ないといったことが度々発生していました。 ・個人情報が見えると困る 従業員が人事部から健康保険証等の書類を受け取る際、人事部の方は個人情報を扱うため、PC 画面の見える位置まで来られると困るという事情があり、特定の位置で待ってもらいたいというニーズがありました。 ・従業員の氏名確認に手間がかかる 全従業員分の氏名は把握できないため、コーポレート部門に来た従業員本人に名前を聞いて確認し、一旦席に戻って、貸与する IT 備品や健康保険証等を探して渡すという、席を行ったり来たりする状況でした。 ・周りの業務状況に配慮が必要 依頼者の席で PC トラブル対応をすると、周りの人が業務に集中していたり、顧客と電話していたりするため、業務に支障がでないようになるべく小声で話す必要がありました。 ・依頼者への対応が属人化しがち コーポレート部門の担当者と依頼者の間で作業の時間を調整していましたが、担当者が急な打ち合わせやお休みで不在になってしまったときは、他の担当者への引き継ぎ対応が漏れてしまうことがありました。 上記の課題を解決するために、 従業員の方向けにコーポレート部門の担当者を呼び出すアプリ を作りました! どういったアプリか 呼び鈴アプリは、従業員とコーポレート部門で対面での作業/受け渡しが発生すること(PC トラブルへの対応/落とし物の受け渡し/健康保険証のお渡し等)に関して、iPad で依頼内容に応じた コーポレート部門の担当者を呼び出すことができるアプリ です。 コーポレート部門の近くのカウンターの上に iPad が置いてあり、そこから呼び出してもらいます。 ちなみに、カウンターはこんな感じのスペースで、従業員の方がセルフサービスで IT 備品交換を行えるブースもあります。 アプリの利用イメージ まず、従業員はカウンターまで行き、iPad の呼び鈴アプリから呼び出し依頼を行います。 呼び出し依頼は専用の Slack チャンネルに通知が来るので、各コーポレート部門の担当者が通知内容を確認して、従業員が居るカウンターに行き、対応という流れになります。 画面イメージ 利用方法は、自身の名前を選択してもらい、コーポレート部門への依頼内容を選択するという形です。 導入後の効果 呼び鈴アプリの導入によって、 従業員がコーポレート部門からサポートを受けたり、物を受け渡ししたいときは、 自身が都合の良いタイミングで カウンターの呼び鈴アプリに来れば良く、 利便性が向上した 。 Slack 通知に依頼者氏名が表示されるので、 氏名を確認する必要がなくなった。 コーポレート部門の担当者が 依頼者の席を探して尋ねなくてよくなった。 というように困りごとが改善されました。 また、特定のメンバーが対応するのではなく、呼び出されたタイミングで席に居る全メンバーが対応できるような運用にすることで、PC/IT 備品や健康保険証等の物の受け渡し、PC のトラブル対応等のサポート対応が属人的にならず、離席していても、休暇をとっていても誰でも対応できる体制になりました。 呼び鈴アプリの構成 呼び鈴アプリの開発にあたっては 1. 先述の課題を解決すること 2. コーポレート IT 内でプロジェクトが複数動いているため、他のプロジェクトの進捗に影響がでないように、工数をさほどかけずにクイックに開発できること 3. なるべくランニングコストをかけないこと という要件から、 AppSheet と Google App Script(以降 GAS) で実装することを決めました。 機能検証期間を含めて 1 週間程度で実装できています。 AppSheet は、Google から提供されている、ノーコードでアプリケーション開発ができるツールです。 AppSheet は、エンジニアではなく「現場で働く人が作る」という思想をもとにサービスが開始されました。そのため、プログラミング知識がない人でも、マウス操作のみで簡単にアプリケーションを開発できるようになっています。 サービス連携図 UI を AppSheet で、Slack への通知を GAS で実装しています。 画面構成 画面は依頼者選択画面、依頼内容一覧画面、依頼詳細一覧画面の3画面で構成しています。 呼び鈴アプリの実装 呼び鈴アプリをどのように実装しているか解説していきます。 AppSheet の設定 AppSheet を利用したアプリの作成方法については、Google 公式の How to create an app や Google で AppSheet を検索するといくつか作成例のサイトが表示されますので、今回の機能を実装するにあたり重要な 「依頼詳細一覧画面から Slack 通知」 設定の部分にフォーカスして解説します。 1. Data の設定 Tables と Slices について、それぞれ設定します。 Tables では、アプリケーションの元となるデータを格納する「テーブル」を作成します。 Slices では、テーブルから必要な行と列のみを表示させたり、条件にあうデータのみを表示させたりして、必要なデータを抽出した「テーブルの一部分」を作成します。 1-1. Tables の設定 今回 Table は以下の 5 つを用意しました。 テーブルは、スプレッドシートにテーブルとなるデータを作成した後、AppSheet と連携します。連携方法は Connect your data to AppSheet の動画を参考にしてください。 テーブル名 利用用途 Client 依頼者選択画面で選択されたユーザー名を一時的に保存するテーブル Doorbell 依頼内容一覧画面に表示するテーブル DoorbellDetails 依頼詳細一覧画面に表示するテーブル CallHistory 呼出履歴を保存するテーブル UserList 依頼者選択画面に表示するテーブル 依頼詳細一覧画面と呼び出し機能に関連する、「DoorbellDetails」と「CallHistory」について詳しく説明します。 ・ DoorbellDetails 依頼詳細一覧画面に表示したい内容を一覧で記載します。 分類 ID 列は Doorbell でも同名の列を作成し、そこで設定した値と合わせてください。また、対応 ID は分類 ID 内でユニークになるように設定してください。 対応説明に関しては、任意で入力していただければ大丈夫です。 SlackChannel 列に Slack チャンネルに投稿する用の Webhook URL を設定してください。 Incoming Webhooks 等を使用すると、 Webhook URL が取得できます。 3 行目以降のデータは、 2 行目を参考に設定してください。 スプレッドシートへのデータ入力例 行番号 A 列 B 列 C 列 D 列 E 列 1 分類 ID 対応 ID 対応名 対応説明 SlackChannel 2 receipt 1 入社にあたって貸与物を受け取りたい (通知先チャンネルの Webhook URL を設定) Table での設定例 分類 ID と対応 ID を複合キー(分類 ID と対応 ID に「REQUIRE」を設定)としています。 ・ CallHistory 呼出履歴を保存するテーブルのため、ヘッダー以外の入力は不要です。 スプレッドシートへのデータ入力例 行番号 A 列 B 列 C 列 D 列 E 列 F 列 G 列 1 CH_id CH_分類 ID CH_対応 ID CH_対応名 CH_SlackChannel CH_呼び出し日時 CH_依頼者 Table での設定例 1-2. Slices の設定 次に、Slice の設定を行います。Slice は依頼内容ごとに画面表示を切り替えるために「DoorbellDetails」のデータを元に作成します。 貸与物受け取りに関する Slice の場合、以下のように設定します。 設定項目名 設定値 Slice name 貸与物受け取り Source table DoorbellDetails Row filter condition [分類 ID]="receipt" 2. App の設定 Views について、それぞれ設定します。 2-1. Views の設定 依頼詳細一覧画面について詳しく説明します。 ・依頼詳細一覧画面 依頼内容一覧画面から遷移できるように、依頼詳細一覧画面は依頼内容ごとに準備します。ここでは貸与物受け取り画面を例として設定しています。 設定項目名 設定値 View name 貸与物受け取り画面 For this data 貸与物受け取り View type deck Position ref Primary header _RowNumber Secondary header 対応名 Event Actions(Row Selected) SaveAndMoveToClientForm(Actions の設定後、設定可能になります) 3. Actions の設定 各ボタンを押したときの動作を定義します。 実際には他にもいくつか Action を設定していますが、Slack 通知に関係する Action は以下の 3 つです。 Action 名 利用用途 MoveToClientForm 依頼者選択画面に遷移する SaveCallHistory CallHistory にデータを保存する SaveAndMoveToClientForm CallHistory に保存後、依頼者選択画面に遷移する ・ MoveToClientForm 依頼者選択画面に遷移する Action です。 設定項目名 設定値 Action name MoveToClientForm For a record of this table DoorbellDetails Do this App: go to another view within this app Target ”#view=依頼者選択” ・ SaveCallHistory 「CallHistory」テーブルにデータを保存する Action です。 選択された氏名については「Client」テーブルからデータを取得しています。 また、SlackChannel 列の記載がない場合は、画面に「呼び出し」ボタンを表示させないように設定しています。 設定項目名 設定値 Action name SaveCallHistory For a record of this table DoorbellDetails Do this Data: add a new row to another table using values from this row Set these columns CH_id = UNIQUEID() CH_分類 ID = [分類 ID] CH_対応 ID = [対応 ID] CH_対応名 = [対応名] CH_SlackChannel = [SlackChannel] CH_呼び出し日次 = TODAY() & ” ” & TIMENOW() CH_依頼者 = Client[ご自身の氏名] Display name 呼び出し Only if this condition is true ISNOTBLANK([SlackChannel]) Needs confirmation? True Confirmation Message CONCATENATE(“依頼者:“,Client[ご自身の氏名],"",“呼び出し内容:”,[対応名],"",“担当者を呼び出しますか?”) ・ SaveAndMoveToClientForm 呼出履歴に保存後、依頼者選択画面に遷移する Action です。この Action を依頼詳細一覧画面の Event Actions に紐付けます。 設定項目名 設定値 Action name SaveAndMoveToClientForm For a record of this table DoorbellDetails Do this Grouped: execute a sequence of actions Actions SaveCallHistory MoveToClientForm スプレッドシート の設定 AppSheet からスプレッドシートの「CallHistory」シートに呼出履歴データが登録(行追加)されたら、Slack 通知 GAS を実行するように設定します。 GAS の記述 先程テーブルを作成したスプレッドシートの拡張機能 →Apps Script を開きます。 App Script 上で新しく「.gs」ファイルを作成し、「CallHistory」 のシートが更新されたときだけ、Slack に通知するように GAS を記述します。 function onChange ( e ) { // イベントが"EDIT"の場合のみ実行。 if ( e . changeType === "EDIT" ) { const ar = e . source . getActiveRange (); const sheet = e . source . getActiveSheet (); // CallHistory シートが更新された場合のみ Slack に通知する if ( e . source . getSheetName () == "CallHistory" ) { const inquiry = sheet . getRange ( ar . getRow (), 4 ). getValue (); const slackWebHook = sheet . getRange ( ar . getRow (), 5 ). getValue (); const clientName = sheet . getRange ( ar . getRow (), 7 ). getValue (); notifyToSlack ( slackWebHook , "コーポカウンター呼び鈴" , ":notification_bell:" , // お好きな Slack の絵文字を設定して下さい。 `<!here> ${ clientName } さんが ${ inquiry } で呼び出ししています。` ); } } } function notifyToSlack ( url , username , icon , message ) { const jsonData = { username: username , icon_emoji: icon , text: message , }; const payload = JSON . stringify ( jsonData ); const options = { method: "post" , contentType: "application/json" , payload: payload , }; UrlFetchApp . fetch ( url , options ); } トリガーの設定 スプレッドシートに呼出履歴が登録(行追加)されたら、 onChange() を実行するように設定します。 問題なく動作すると、Slack に以下のようなメッセージが通知されます。 実装する上で難しかったこと AppSheet でアプリを開発するにあたり、ノーコード開発の制約でかゆいところに手が届かず、いくつか工夫して実装する必要がありました。 1. 氏名の選択結果を保持したまま、依頼内容選択の結果を「CallHistory」に書き出すこと AppSheet が複数の選択結果を保持できるような仕組みになっておらず、依頼者選択画面から依頼内容一覧画面に遷移すると選択結果が消えるため、依頼者選択結果をデータとしてどこかに保持する必要がありました。 → 氏名の選択結果を格納する一行だけのテーブル「Client」を作成し、「CallHistory」への書き出し時に「Client」の一行目のデータを読むように設定しました。 2. ボタンの配置と表示が変更できないこと 基本的にボタンに表示されているテキストは変更できず、ボタン位置も自由に変更できないため(画面の最上部か最下部のみ)、画面の設計には苦労しました。 → テキストに関しては、Settings → Views → Localization で「Save」を「次へ」に変更して対応しました。 ボタン位置に関してはどうにもならなかったため、なるべく導線がわかりやすいように画面上に説明を入れるようにしています。 ノーコードがゆえの制約はありますが、AppSheet での開発は簡単かつ迅速にできるため、小規模なアプリケーションであれば、おすすめの環境です。 以上、呼び鈴アプリの開発についてお話しました。 ここからは、コーポレート IT についてご紹介していきます。 コーポレート IT について コーポレートデザイン部は、 「未来志向で社内の IT インフラを含めたオフィス環境をデザインすること」 をミッションに業務しており、現在 2 つのチームがあります。 一つはワークプレイスチームで、ペン一本からオフィス施工に関することまで、オフィス環境全般を担当しています。 そして、もう一つがコーポレート IT チームで、全社で利用する SaaS の管理やオフィスのネットワークインフラ、社内アプリの開発等、全従業員が利用する IT ソリューション全般の企画・開発・運用を担当しています。 コーポレート IT の役割 コーポレート IT としてのミッションは、**「テクノロジーと創意工夫で従業員のパフォーマンスの最大化を図ること」**です。 そのために、単に上流・下流というフェーズ分担ではなく、システム導入だけでもない、全社の組織カルチャーづくりも見据えた全従業員が利用するコーポレート基盤をデザインし、そのデザインに基づいて IT ソリューションを実装、運用していきます。 逆に、各部門のミッション達成、業務目的達成のための個別の IT ソリューションは、ツールゆえ各部門が主管です。コーポレート IT は IT プロフェッショナルとしてアドバイスしたり、システム設計や実装する形で、技術的実現を担うものとして各部門とコラボします。 各部門の IT ソリューションに対する PDCA を各部門がオーナーシップを持つことで、利用時の要望や改善に対して、非常に早いサイクルで実施することができています。 業務内容紹介 コーポレート IT では、日々の従業員への IT サポート以外に、複数のプロジェクトが同時に走っています。以下に記載している業務はほんの一例です。 端末管理アプリケーションおよびセキュリティ製品の企画/導入/運用 オフィス移転でのネットワーク構築(ネットワークの設計、機器選定〜運用まで) 社外来客用受付システムの改修/運用 呼び鈴アプリの企画/開発/運用 従業員向けの情報セキュリティ研修 人事基幹システムの導入支援/各システムとの連携機能構築 etc また、メンバー自ら提案して企画化、予算を確保することもあり、アイデアに対して柔軟に対応しています。 業務を行う上で常に意識していること・大事にしていること コーポレート IT として業務を行う上で大事にしている考え方が、3 つあります。従業員の生産性が向上する環境の構築に必要不可欠と考えており、これらをもとに意思決定しています。 1. システム選定/技術選定の考え方 システム選定/技術選定の原則としては、グローバルトップシェアで、かつ、北米急成長企業が選定しているものを未来志向で活用していくことを意識していますが、その技術やツールを導入して、得たい本来の目的が達成できるかどうかを、広い視点から判断して意思決定しています。 2. ユーザー IT サポートの考え方 老子が言っている『授人以魚 不如授人以漁(人に魚を授けることは、漁の仕方を教えるに及ばない)』ということを大切にしており、「わからない」という人に単に答えを「教える」ことをせず、組織の未来のために、組織全体の IT リテラシーを上げること、スムーズに自己解決できる環境を整えることに努めています。 「ユーザー IT サポートが増え続け、サポート人件費が増大し、高コストな生産性の低いコーポレート IT チーム」とならないよう心がけています。 3. ルールを作らず、仕組み/仕掛けで解決する ルールを覚えなくても、意識しなくとも、狙ったことが自然に進むよう、仕組み/仕掛けによって目的を成す方法を作ることを意識しています。 例えば、高速道路のサービスエリアにある駐車場の逆走を禁止するために斜めに駐車位置ラインを引くといった、意識せずとも行動を一定方向に促す行動設計が望ましく、呼び鈴アプリもそういった仕掛けやプロセス設計の一環です。 さいごに コーポレート IT は全従業員が利用する IT 基盤の維持・改善が求められるため、非常に重要な役割です。また、急拡大する組織の中においては、今までの仕組みが通用しなくなり、未来志向で新たな仕組みを設計し続けていく必要があり、常にコーポレート IT チームの活躍の余地がたくさん存在しています。 そのため、未来志向で #革新と改善を主導 できる方を絶賛募集しております。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しております。様々な業種でスペシャリストを募集しています。興味がある方は是非ご連絡ください。医療という社会貢献性の高い領域へ、一緒に挑戦しませんか? 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回はジョブメドレーアカデミー(以下、アカデミー)の開発の皆さんに集まってもらい、 TypeScript と NestJS を使ったバックエンド開発をどのように行なっているのかをインタビューしました。 以前、アカデミーがリニューアルした際にチームメンバーにインタビューした note もあるので、未読の方はぜひそちらもご覧いただければと思います。 インタビュイー紹介 岸田さん SES 会社で業務システム開発、バングラデシュに支社があるオフショア開発をしている会社でブリッジエンジニアという経験を経て、2019 年メドレー入社。 その後ジョブメドレーで開発をリード。現在はアカデミーのプロダクト責任者を務める。 熊本さん 2019 年メドレーに新卒入社。半年の研修を経てジョブメドレーの開発に携わる。現在はアカデミーの開発に携わる。大学では機械学習を用いた自動車の自動運転の研究をしていた。 牧野さん 2021 年 11 月にメドレー入社。不動産業界の SaaS を開発している会社でのマネージャー・エンジニアを経験。現在はアカデミーの開発に携わる。 徳永さん 2022 年メドレーに新卒入社。 OJT 期間中からアカデミーの開発に携わる。大学では、対話 AI システム・ AI との対話用プラットフォームの研究開発をしていた。 アカデミーのアーキテクチャ 山田 : 早速ですが、現在のアカデミーのアーキテクチャについて話を伺いたいと思います。全体の構成としては現在どんな形になっているんでしょうか。 岸田 : 以前話をしたところから大幅に変わったところはないですが、大まかにはこんな感じになっています。 山田 : ありがとうございます、分かりやすいですね。 (手前)岸田さん・(奥)牧野さん 山田 : Web フロントエンドに関しては Next.js とグローバルの状態管理として Apollo Client を使っていると伺っています。バックエンドは色々あると思うのですが、DB に関しては PostgreSQL を使っているんですよね? 岸田 : はい、ORM としては Prisma を使っています。 山田 : こうした開発環境はどのように決まったんでしょうか?特に、開発言語を全て TypeScript で統一する、という意思決定が先にあってバックエンド側のフレームワークは NestJS にしようとなったのか、逆に NestJS を使おうとして TypeScript になったのかをお聞きできれば。 岸田 : 細かいところまでは記憶にないのですが、TypeScript を使って型で開発をしたいというのが一番大きい理由でした。 山田 : まずは TypeScript からだったんですね。そこから Web フレームワークとしては Express など他の物もあったと思いますが、NestJS を採用した理由は何だったのでしょうか? 岸田 : そうですね、色々選択肢はあったと思うんですが、現実的にサービスを運用するとなると Express と NestJS のどちらかかな、という感じになっていました。 どちらも実開発での経験を持っているメンバーはいなかったんですが、Web 開発で良く使われる Web フレームワークは、基本的な機能やコンセプトについては共通する部分が多いことに加えて、アカデミーも特殊な仕様があるプロダクトではないので、どちらを選んだとしてもメンバーが困ることはないだろうと考えました。 その上で細かい使い勝手などを検討したり GraphQL との親和性の高さや Express という枯れた技術をベースにしていて、 フレームワーク自体の信頼感もあるので NestJS に決めた という感じでした。 熊本 : 絞り込んだ段階で各メンバーが NestJS を試験的に触り、使用感を見てみましたが、大きく問題になる点もなかったので全員一致で決まった感じですね。 岸田 : 前述のように Express をベースにしているので何か困ったら最悪 Express にすれば良いなというのもありました。 牧野 : ここまで実際大きな問題というのは無かったです。 山田 : 今は GraphQL を使って API を作っていると思いますが、REST で作るという選択肢は最初から無かったんですか? 岸田 : はい、GraphQL を使えるメンバーが揃っているという状況であえて REST にするメリットは無いなという事で考えていませんでした。外部公開する API があるとか、パフォーマンスがすごく重要な重い API 呼び出しというのがあったら別だったでしょうけども。 山田 : そもそもの話になってしまうんですが、弊社のプロダクト開発でよく使われている Ruby on Rails を使う選択肢は無かったんでしょうか? 岸田 : 選択肢としてはあるにはあったんですが、プロダクトのフェイズとして現在既に成長をしているサービスであることや、メンバーのスキルセットとして Rails に慣れたメンバーがそこまで多くなかったことを考えると、いかに技術的負債を少なく保ちつつ、機能開発をしていけるかという点が重要になるので、現在の技術構成にしました。 これが既存サービスのリニューアルではなくて、新規で一から全部作っていかないといけないという状況であれば Rails を使うかもしれませんが、やっぱり型があってある程度開発効率が高くなるというのは魅力的だったので、 これからどんどんと機能開発をするには今の構成が良い と判断しました。 (手前)徳永さん・(奥)熊本さん 山田 : DB に関して PostgreSQL ですが、 MySQL などにするということは考えなかったですか? 熊本 : そうですね、リニューアル前のアカデミーが使っていたということがあり、大きく変えるメリットはあまり無かったのが大きいです。 岸田 : あとこれは自分の感覚なのですが、PostgreSQL の方がスマートに処理できることが多いという印象があるというのもありました。 山田 : 元々サービスで使っていたというのは、やっぱり大きいですね。ORM は Prisma ですよね。例えば他に TypeORM などの選択肢もあったと思いますが、こちらはどういった経緯で使うことになりましたか? 熊本 : 色々と触ってみた結果、Prisma が 自分たちの使い方にマッチしてた のが大きかったです。がっつりと SQL を自動生成するなどの手厚い機能は、自分達の使い方では必要もなかったですし。キャッチアップのためにペアプロなどもしましたが、 メンバーも使いやすい ということが確認できました。 山田 : 今ペアプロの話が出ましたが、どのような目的でやっていたんでしょうか。 熊本 : 初期の頃はメンバー間の知識などに偏りもあったので、そういったものを実際にコードを触りながら埋めていく という目的でした。実際に、GraphQL を使ったことがなかったメンバーも早く慣れてくれました。 山田 : なるほど。ある種学習も兼ねていたんですね。最終的に現在このアーキテクチャの使い勝手としては感想としてどうでしょうか? 岸田 : 言葉は違うかもしれないですが「 普通に使い勝手が良い 」という感じです。 もちろんメンバーが習熟してきたので、色々とコード的に改善しようという部分はあったりもしますが、今のところ本当に困った問題というのは無いので、非常に良い選択だったかと思います。 NestJS のテストコードについて 山田 : テストコードは基本書いているんですよね? 熊本 : はい、バックエンドは基本的に Jest で全部書いています。フロントエンドに関しては今まさに E2E テストをどのように実施していくかを試行錯誤しているところですね。 山田 : NestJS でテストコード書くのに何か困ったこととかありましたか? 岸田 : 困っているというわけではないですが、Rails でほぼ必ず使われている Factorybot に相当する機能が欲しいですね…。 山田 : 確かに Node.js だとそういったライブラリを見かけないですね…。 岸田 : 皆さんあんまり困ってないのかな?ということで今は簡易的なものを自作して使っています。 山田 : OSS で作ったら需要があるかもしれませんね。 現状の NestJS での開発の課題感 山田 : ここまで NestJS での開発について聞いてきましたが、プロダクト開発での課題は何かあったりしますか? 岸田 : 先ほども少し話をしたメンバーの習熟度が高くなったことによるコード改善の他には、NestJS というか Node.js が Rails に比べると定番ライブラリが少なめなのがあります。 例えば、 Sidekiq のようなライブラリって便利だと思うんですが、相当するライブラリでデファクトスタンダードというようなものが Node.js だと少ない気がしています。ライブラリが一杯あるのですが、デファクトまでいかないというものが多いという感想です。 山田 : 確かに Node.js だと同じ目的のライブラリが複数あって機能がちょっとずつ違うという感じがありますね。 牧野 : あとは機能開発にリソースを取っているので、細かい部分でのライブラリのアップデートが遅れ気味というのが課題感としてあります。 今のところはとてつもなく遅れているというわけではないのですが、積り積っていくとこういった部分が負債になっていくので、対応したいと思っています。 岸田 : こういったものも含めてプロダクト開発を円滑に回すために、自分も含めた各メンバーのスキルを広げて深くしていかないといけないなというのもありますね。 チームへのオンボーディングについて 山田 : さて、話が変わって現在アカデミーで一番最後にジョインしたのが徳永さんだと思いますが、オンボーディングなどはどのような感じだったんでしょうか。学生時代に NestJS はがっつり触っていたんでしたっけ? 徳永 : いえ、学生時代はチュートリアルをしたくらいで、深く使っていたということはなかったです。ですので、最初は全然プロダクトのコードが分からないというレベルで、ほとんど未経験でした。 山田 : なるほど。ほぼ未経験で新卒研修を経て、アカデミーの開発チームにジョインして困った部分などはありましたか? 徳永 : TypeScript を使っているというのは凄く自分にとって良くて、言語でつまづくということはほとんどありませんでした。 やはり 公式ドキュメントがすごく充実していますし、コミュニティの質の高い情報も多い ので。プロダクトに関しては過去の Pull Request や Issue 上のやり取りなどで、 どういう意図で実装されたものなのかを参照できたのが大きかった です。一方でバックエンドのデザインパターンなどは経験が少なく苦労した部分がありました。その部分はドキュメントで学習したり、周りのメンバーに直接質問しながらキャッチアップしていきました。 山田 : 今年は入社後研修も TypeScript で書く割合が Ruby よりも多かったので、それがもし逆だったら、もっと苦労していたかもしれないですね。 徳永 : そうですね、その可能性は十分にあり得ます。 山田 : あとアカデミーチームは火水木に勉強会を開催していますよね。ジョブメドレーの開発チームでも噂になっています。 岸田 : これは全員で集まる勉強会というわけではなく、 月によって各自テーマを決めて火水木 1 時間ずつスキルアップのために時間を確保し、学んだことなどをアウトプットしていく という形式です。アウトプットしたものは、お互いに確認して知識を深めていこうというものですね。 山田 : それは素敵な取り組みですね。あとは Figma で開発ドキュメントをまとめているのが印象的ですよね。 牧野 : 最初は岸田さんが率先して、開発した部分のドキュメントなんかを書いていたんですが、最近はチームで共有するようなものもほとんど Figma でまとめるようになっています。 熊本 : もちろん永続化しないといけないドキュメントは README なりコンフルエンスなりに書いていくのですが、ちょっとしたものを気軽に Figma にまとめられるのはすごく良いです。 岸田 : 他の人がやっていることなんかも絶対に目につく ので、そういった意味でもドキュメントが散らばらなくて今は良く機能していると思っています。 現状の課題感とアカデミーの開発に向く人 山田 : 最後になりますが、アカデミーに来てほしい人材はどういった方でしょうか? 徳永 : この プロダクトにわくわくできる方に来てほしい と思います。福祉業界に明るい方なんかは大歓迎です! 山田 : なるほど。プロダクトのビジネス面の理解をして楽しんで開発できる方ですね。技術的な部分では何かありますか? 岸田 : 今まで TypeScript と NestJS を中心に話していましたが、実はこれらができる人というのは必須条件ではありません。どの言語でもフレームワークでも ちゃんと Web 開発が分かっている方 であればという感じです。 他には 情報感度が高い人が良い と思います。言語化が難しいのですが、開発環境や言語、ライブラリなどできちんと最新の情報が取捨選択できる方がいると、チームがより活性化すると考えています。 バックエンドでいうとベース部分などもきちんと手を入れることができて、なおかつ人に教えるのが好きな方が入ってもらえると、チームへの知識伝播などもより上手くいくと考えています。 あるとより良いなという所だと、現在アプリは React Native で作られているんですが、React Native でのアプリ開発に強い方だと嬉しいですね。ユーザーに一番使われているのはアプリなので、さらにそのユーザビリティなどを高めるためには、アプリ開発に強い方に入ってもらえるとありがたいです。 山田 : よりアカデミーというプロダクトをドライブさせてくれる方にぜひ来てほしいところですね。本日はありがとうございました! おわりに メドレーの開発チームの中でも野心的なアーキテクチャで開発をしているアカデミーチームについてお伝えしました。 サービスを伸ばすために、適切に言語やフレームワークを選択していき、柔軟性を持って開発していってるんだなというのが伝わってきたインタビューでした。少しでも興味が出た方はぜひ、下のリンクからお話できればと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回はジョブメドレーアカデミー(以下、アカデミー)の開発の皆さんに集まってもらい、 TypeScript と NestJS を使ったバックエンド開発をどのように行なっているのかをインタビューしました。 以前、アカデミーがリニューアルした際にチームメンバーにインタビューした note もあるので、未読の方はぜひそちらもご覧いただければと思います。 インタビュイー紹介 岸田さん SES 会社で業務システム開発、バングラデシュに支社があるオフショア開発をしている会社でブリッジエンジニアという経験を経て、2019 年メドレー入社。 その後ジョブメドレーで開発をリード。現在はアカデミーのプロダクト責任者を務める。 熊本さん 2019 年メドレーに新卒入社。半年の研修を経てジョブメドレーの開発に携わる。現在はアカデミーの開発に携わる。大学では機械学習を用いた自動車の自動運転の研究をしていた。 牧野さん 2021 年 11 月にメドレー入社。不動産業界の SaaS を開発している会社でのマネージャー・エンジニアを経験。現在はアカデミーの開発に携わる。 徳永さん 2022 年メドレーに新卒入社。 OJT 期間中からアカデミーの開発に携わる。大学では、対話 AI システム・ AI との対話用プラットフォームの研究開発をしていた。 アカデミーのアーキテクチャ 山田 : 早速ですが、現在のアカデミーのアーキテクチャについて話を伺いたいと思います。全体の構成としては現在どんな形になっているんでしょうか。 岸田 : 以前話をしたところから大幅に変わったところはないですが、大まかにはこんな感じになっています。 山田 : ありがとうございます、分かりやすいですね。 (手前)岸田さん・(奥)牧野さん 山田 : Web フロントエンドに関しては Next.js とグローバルの状態管理として Apollo Client を使っていると伺っています。バックエンドは色々あると思うのですが、DB に関しては PostgreSQL を使っているんですよね? 岸田 : はい、ORM としては Prisma を使っています。 山田 : こうした開発環境はどのように決まったんでしょうか?特に、開発言語を全て TypeScript で統一する、という意思決定が先にあってバックエンド側のフレームワークは NestJS にしようとなったのか、逆に NestJS を使おうとして TypeScript になったのかをお聞きできれば。 岸田 : 細かいところまでは記憶にないのですが、TypeScript を使って型で開発をしたいというのが一番大きい理由でした。 山田 : まずは TypeScript からだったんですね。そこから Web フレームワークとしては Express など他の物もあったと思いますが、NestJS を採用した理由は何だったのでしょうか? 岸田 : そうですね、色々選択肢はあったと思うんですが、現実的にサービスを運用するとなると Express と NestJS のどちらかかな、という感じになっていました。 どちらも実開発での経験を持っているメンバーはいなかったんですが、Web 開発で良く使われる Web フレームワークは、基本的な機能やコンセプトについては共通する部分が多いことに加えて、アカデミーも特殊な仕様があるプロダクトではないので、どちらを選んだとしてもメンバーが困ることはないだろうと考えました。 その上で細かい使い勝手などを検討したり GraphQL との親和性の高さや Express という枯れた技術をベースにしていて、 フレームワーク自体の信頼感もあるので NestJS に決めた という感じでした。 熊本 : 絞り込んだ段階で各メンバーが NestJS を試験的に触り、使用感を見てみましたが、大きく問題になる点もなかったので全員一致で決まった感じですね。 岸田 : 前述のように Express をベースにしているので何か困ったら最悪 Express にすれば良いなというのもありました。 牧野 : ここまで実際大きな問題というのは無かったです。 山田 : 今は GraphQL を使って API を作っていると思いますが、REST で作るという選択肢は最初から無かったんですか? 岸田 : はい、GraphQL を使えるメンバーが揃っているという状況であえて REST にするメリットは無いなという事で考えていませんでした。外部公開する API があるとか、パフォーマンスがすごく重要な重い API 呼び出しというのがあったら別だったでしょうけども。 山田 : そもそもの話になってしまうんですが、弊社のプロダクト開発でよく使われている Ruby on Rails を使う選択肢は無かったんでしょうか? 岸田 : 選択肢としてはあるにはあったんですが、プロダクトのフェイズとして現在既に成長をしているサービスであることや、メンバーのスキルセットとして Rails に慣れたメンバーがそこまで多くなかったことを考えると、いかに技術的負債を少なく保ちつつ、機能開発をしていけるかという点が重要になるので、現在の技術構成にしました。 これが既存サービスのリニューアルではなくて、新規で一から全部作っていかないといけないという状況であれば Rails を使うかもしれませんが、やっぱり型があってある程度開発効率が高くなるというのは魅力的だったので、 これからどんどんと機能開発をするには今の構成が良い と判断しました。 (手前)徳永さん・(奥)熊本さん 山田 : DB に関して PostgreSQL ですが、 MySQL などにするということは考えなかったですか? 熊本 : そうですね、リニューアル前のアカデミーが使っていたということがあり、大きく変えるメリットはあまり無かったのが大きいです。 岸田 : あとこれは自分の感覚なのですが、PostgreSQL の方がスマートに処理できることが多いという印象があるというのもありました。 山田 : 元々サービスで使っていたというのは、やっぱり大きいですね。ORM は Prisma ですよね。例えば他に TypeORM などの選択肢もあったと思いますが、こちらはどういった経緯で使うことになりましたか? 熊本 : 色々と触ってみた結果、Prisma が 自分たちの使い方にマッチしてた のが大きかったです。がっつりと SQL を自動生成するなどの手厚い機能は、自分達の使い方では必要もなかったですし。キャッチアップのためにペアプロなどもしましたが、 メンバーも使いやすい ということが確認できました。 山田 : 今ペアプロの話が出ましたが、どのような目的でやっていたんでしょうか。 熊本 : 初期の頃はメンバー間の知識などに偏りもあったので、そういったものを実際にコードを触りながら埋めていく という目的でした。実際に、GraphQL を使ったことがなかったメンバーも早く慣れてくれました。 山田 : なるほど。ある種学習も兼ねていたんですね。最終的に現在このアーキテクチャの使い勝手としては感想としてどうでしょうか? 岸田 : 言葉は違うかもしれないですが「 普通に使い勝手が良い 」という感じです。 もちろんメンバーが習熟してきたので、色々とコード的に改善しようという部分はあったりもしますが、今のところ本当に困った問題というのは無いので、非常に良い選択だったかと思います。 NestJS のテストコードについて 山田 : テストコードは基本書いているんですよね? 熊本 : はい、バックエンドは基本的に Jest で全部書いています。フロントエンドに関しては今まさに E2E テストをどのように実施していくかを試行錯誤しているところですね。 山田 : NestJS でテストコード書くのに何か困ったこととかありましたか? 岸田 : 困っているというわけではないですが、Rails でほぼ必ず使われている Factorybot に相当する機能が欲しいですね…。 山田 : 確かに Node.js だとそういったライブラリを見かけないですね…。 岸田 : 皆さんあんまり困ってないのかな?ということで今は簡易的なものを自作して使っています。 山田 : OSS で作ったら需要があるかもしれませんね。 現状の NestJS での開発の課題感 山田 : ここまで NestJS での開発について聞いてきましたが、プロダクト開発での課題は何かあったりしますか? 岸田 : 先ほども少し話をしたメンバーの習熟度が高くなったことによるコード改善の他には、NestJS というか Node.js が Rails に比べると定番ライブラリが少なめなのがあります。 例えば、 Sidekiq のようなライブラリって便利だと思うんですが、相当するライブラリでデファクトスタンダードというようなものが Node.js だと少ない気がしています。ライブラリが一杯あるのですが、デファクトまでいかないというものが多いという感想です。 山田 : 確かに Node.js だと同じ目的のライブラリが複数あって機能がちょっとずつ違うという感じがありますね。 牧野 : あとは機能開発にリソースを取っているので、細かい部分でのライブラリのアップデートが遅れ気味というのが課題感としてあります。 今のところはとてつもなく遅れているというわけではないのですが、積り積っていくとこういった部分が負債になっていくので、対応したいと思っています。 岸田 : こういったものも含めてプロダクト開発を円滑に回すために、自分も含めた各メンバーのスキルを広げて深くしていかないといけないなというのもありますね。 チームへのオンボーディングについて 山田 : さて、話が変わって現在アカデミーで一番最後にジョインしたのが徳永さんだと思いますが、オンボーディングなどはどのような感じだったんでしょうか。学生時代に NestJS はがっつり触っていたんでしたっけ? 徳永 : いえ、学生時代はチュートリアルをしたくらいで、深く使っていたということはなかったです。ですので、最初は全然プロダクトのコードが分からないというレベルで、ほとんど未経験でした。 山田 : なるほど。ほぼ未経験で新卒研修を経て、アカデミーの開発チームにジョインして困った部分などはありましたか? 徳永 : TypeScript を使っているというのは凄く自分にとって良くて、言語でつまづくということはほとんどありませんでした。 やはり 公式ドキュメントがすごく充実していますし、コミュニティの質の高い情報も多い ので。プロダクトに関しては過去の Pull Request や Issue 上のやり取りなどで、 どういう意図で実装されたものなのかを参照できたのが大きかった です。一方でバックエンドのデザインパターンなどは経験が少なく苦労した部分がありました。その部分はドキュメントで学習したり、周りのメンバーに直接質問しながらキャッチアップしていきました。 山田 : 今年は入社後研修も TypeScript で書く割合が Ruby よりも多かったので、それがもし逆だったら、もっと苦労していたかもしれないですね。 徳永 : そうですね、その可能性は十分にあり得ます。 山田 : あとアカデミーチームは火水木に勉強会を開催していますよね。ジョブメドレーの開発チームでも噂になっています。 岸田 : これは全員で集まる勉強会というわけではなく、 月によって各自テーマを決めて火水木 1 時間ずつスキルアップのために時間を確保し、学んだことなどをアウトプットしていく という形式です。アウトプットしたものは、お互いに確認して知識を深めていこうというものですね。 山田 : それは素敵な取り組みですね。あとは Figma で開発ドキュメントをまとめているのが印象的ですよね。 牧野 : 最初は岸田さんが率先して、開発した部分のドキュメントなんかを書いていたんですが、最近はチームで共有するようなものもほとんど Figma でまとめるようになっています。 熊本 : もちろん永続化しないといけないドキュメントは README なりコンフルエンスなりに書いていくのですが、ちょっとしたものを気軽に Figma にまとめられるのはすごく良いです。 岸田 : 他の人がやっていることなんかも絶対に目につく ので、そういった意味でもドキュメントが散らばらなくて今は良く機能していると思っています。 現状の課題感とアカデミーの開発に向く人 山田 : 最後になりますが、アカデミーに来てほしい人材はどういった方でしょうか? 徳永 : この プロダクトにわくわくできる方に来てほしい と思います。福祉業界に明るい方なんかは大歓迎です! 山田 : なるほど。プロダクトのビジネス面の理解をして楽しんで開発できる方ですね。技術的な部分では何かありますか? 岸田 : 今まで TypeScript と NestJS を中心に話していましたが、実はこれらができる人というのは必須条件ではありません。どの言語でもフレームワークでも ちゃんと Web 開発が分かっている方 であればという感じです。 他には 情報感度が高い人が良い と思います。言語化が難しいのですが、開発環境や言語、ライブラリなどできちんと最新の情報が取捨選択できる方がいると、チームがより活性化すると考えています。 バックエンドでいうとベース部分などもきちんと手を入れることができて、なおかつ人に教えるのが好きな方が入ってもらえると、チームへの知識伝播などもより上手くいくと考えています。 あるとより良いなという所だと、現在アプリは React Native で作られているんですが、React Native でのアプリ開発に強い方だと嬉しいですね。ユーザーに一番使われているのはアプリなので、さらにそのユーザビリティなどを高めるためには、アプリ開発に強い方に入ってもらえるとありがたいです。 山田 : よりアカデミーというプロダクトをドライブさせてくれる方にぜひ来てほしいところですね。本日はありがとうございました! おわりに メドレーの開発チームの中でも野心的なアーキテクチャで開発をしているアカデミーチームについてお伝えしました。 サービスを伸ばすために、適切に言語やフレームワークを選択していき、柔軟性を持って開発していってるんだなというのが伝わってきたインタビューでした。少しでも興味が出た方はぜひ、下のリンクからお話できればと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回はジョブメドレーアカデミー(以下、アカデミー)の開発の皆さんに集まってもらい、 TypeScript と NestJS を使ったバックエンド開発をどのように行なっているのかをインタビューしました。 以前、アカデミーがリニューアルした際にチームメンバーにインタビューした note もあるので、未読の方はぜひそちらもご覧いただければと思います。 インタビュイー紹介 岸田さん SES 会社で業務システム開発、バングラデシュに支社があるオフショア開発をしている会社でブリッジエンジニアという経験を経て、2019 年メドレー入社。 その後ジョブメドレーで開発をリード。現在はアカデミーのプロダクト責任者を務める。 熊本さん 2019 年メドレーに新卒入社。半年の研修を経てジョブメドレーの開発に携わる。現在はアカデミーの開発に携わる。大学では機械学習を用いた自動車の自動運転の研究をしていた。 牧野さん 2021 年 11 月にメドレー入社。不動産業界の SaaS を開発している会社でのマネージャー・エンジニアを経験。現在はアカデミーの開発に携わる。 徳永さん 2022 年メドレーに新卒入社。 OJT 期間中からアカデミーの開発に携わる。大学では、対話 AI システム・ AI との対話用プラットフォームの研究開発をしていた。 アカデミーのアーキテクチャ 山田 : 早速ですが、現在のアカデミーのアーキテクチャについて話を伺いたいと思います。全体の構成としては現在どんな形になっているんでしょうか。 岸田 : 以前話をしたところから大幅に変わったところはないですが、大まかにはこんな感じになっています。 山田 : ありがとうございます、分かりやすいですね。 (手前)岸田さん・(奥)牧野さん 山田 : Web フロントエンドに関しては Next.js とグローバルの状態管理として Apollo Client を使っていると伺っています。バックエンドは色々あると思うのですが、DB に関しては PostgreSQL を使っているんですよね? 岸田 : はい、ORM としては Prisma を使っています。 山田 : こうした開発環境はどのように決まったんでしょうか?特に、開発言語を全て TypeScript で統一する、という意思決定が先にあってバックエンド側のフレームワークは NestJS にしようとなったのか、逆に NestJS を使おうとして TypeScript になったのかをお聞きできれば。 岸田 : 細かいところまでは記憶にないのですが、TypeScript を使って型で開発をしたいというのが一番大きい理由でした。 山田 : まずは TypeScript からだったんですね。そこから Web フレームワークとしては Express など他の物もあったと思いますが、NestJS を採用した理由は何だったのでしょうか? 岸田 : そうですね、色々選択肢はあったと思うんですが、現実的にサービスを運用するとなると Express と NestJS のどちらかかな、という感じになっていました。 どちらも実開発での経験を持っているメンバーはいなかったんですが、Web 開発で良く使われる Web フレームワークは、基本的な機能やコンセプトについては共通する部分が多いことに加えて、アカデミーも特殊な仕様があるプロダクトではないので、どちらを選んだとしてもメンバーが困ることはないだろうと考えました。 その上で細かい使い勝手などを検討したり GraphQL との親和性の高さや Express という枯れた技術をベースにしていて、 フレームワーク自体の信頼感もあるので NestJS に決めた という感じでした。 熊本 : 絞り込んだ段階で各メンバーが NestJS を試験的に触り、使用感を見てみましたが、大きく問題になる点もなかったので全員一致で決まった感じですね。 岸田 : 前述のように Express をベースにしているので何か困ったら最悪 Express にすれば良いなというのもありました。 牧野 : ここまで実際大きな問題というのは無かったです。 山田 : 今は GraphQL を使って API を作っていると思いますが、REST で作るという選択肢は最初から無かったんですか? 岸田 : はい、GraphQL を使えるメンバーが揃っているという状況であえて REST にするメリットは無いなという事で考えていませんでした。外部公開する API があるとか、パフォーマンスがすごく重要な重い API 呼び出しというのがあったら別だったでしょうけども。 山田 : そもそもの話になってしまうんですが、弊社のプロダクト開発でよく使われている Ruby on Rails を使う選択肢は無かったんでしょうか? 岸田 : 選択肢としてはあるにはあったんですが、プロダクトのフェイズとして現在既に成長をしているサービスであることや、メンバーのスキルセットとして Rails に慣れたメンバーがそこまで多くなかったことを考えると、いかに技術的負債を少なく保ちつつ、機能開発をしていけるかという点が重要になるので、現在の技術構成にしました。 これが既存サービスのリニューアルではなくて、新規で一から全部作っていかないといけないという状況であれば Rails を使うかもしれませんが、やっぱり型があってある程度開発効率が高くなるというのは魅力的だったので、 これからどんどんと機能開発をするには今の構成が良い と判断しました。 (手前)徳永さん・(奥)熊本さん 山田 : DB に関して PostgreSQL ですが、 MySQL などにするということは考えなかったですか? 熊本 : そうですね、リニューアル前のアカデミーが使っていたということがあり、大きく変えるメリットはあまり無かったのが大きいです。 岸田 : あとこれは自分の感覚なのですが、PostgreSQL の方がスマートに処理できることが多いという印象があるというのもありました。 山田 : 元々サービスで使っていたというのは、やっぱり大きいですね。ORM は Prisma ですよね。例えば他に TypeORM などの選択肢もあったと思いますが、こちらはどういった経緯で使うことになりましたか? 熊本 : 色々と触ってみた結果、Prisma が 自分たちの使い方にマッチしてた のが大きかったです。がっつりと SQL を自動生成するなどの手厚い機能は、自分達の使い方では必要もなかったですし。キャッチアップのためにペアプロなどもしましたが、 メンバーも使いやすい ということが確認できました。 山田 : 今ペアプロの話が出ましたが、どのような目的でやっていたんでしょうか。 熊本 : 初期の頃はメンバー間の知識などに偏りもあったので、そういったものを実際にコードを触りながら埋めていく という目的でした。実際に、GraphQL を使ったことがなかったメンバーも早く慣れてくれました。 山田 : なるほど。ある種学習も兼ねていたんですね。最終的に現在このアーキテクチャの使い勝手としては感想としてどうでしょうか? 岸田 : 言葉は違うかもしれないですが「 普通に使い勝手が良い 」という感じです。 もちろんメンバーが習熟してきたので、色々とコード的に改善しようという部分はあったりもしますが、今のところ本当に困った問題というのは無いので、非常に良い選択だったかと思います。 NestJS のテストコードについて 山田 : テストコードは基本書いているんですよね? 熊本 : はい、バックエンドは基本的に Jest で全部書いています。フロントエンドに関しては今まさに E2E テストをどのように実施していくかを試行錯誤しているところですね。 山田 : NestJS でテストコード書くのに何か困ったこととかありましたか? 岸田 : 困っているというわけではないですが、Rails でほぼ必ず使われている Factorybot に相当する機能が欲しいですね…。 山田 : 確かに Node.js だとそういったライブラリを見かけないですね…。 岸田 : 皆さんあんまり困ってないのかな?ということで今は簡易的なものを自作して使っています。 山田 : OSS で作ったら需要があるかもしれませんね。 現状の NestJS での開発の課題感 山田 : ここまで NestJS での開発について聞いてきましたが、プロダクト開発での課題は何かあったりしますか? 岸田 : 先ほども少し話をしたメンバーの習熟度が高くなったことによるコード改善の他には、NestJS というか Node.js が Rails に比べると定番ライブラリが少なめなのがあります。 例えば、 Sidekiq のようなライブラリって便利だと思うんですが、相当するライブラリでデファクトスタンダードというようなものが Node.js だと少ない気がしています。ライブラリが一杯あるのですが、デファクトまでいかないというものが多いという感想です。 山田 : 確かに Node.js だと同じ目的のライブラリが複数あって機能がちょっとずつ違うという感じがありますね。 牧野 : あとは機能開発にリソースを取っているので、細かい部分でのライブラリのアップデートが遅れ気味というのが課題感としてあります。 今のところはとてつもなく遅れているというわけではないのですが、積り積っていくとこういった部分が負債になっていくので、対応したいと思っています。 岸田 : こういったものも含めてプロダクト開発を円滑に回すために、自分も含めた各メンバーのスキルを広げて深くしていかないといけないなというのもありますね。 チームへのオンボーディングについて 山田 : さて、話が変わって現在アカデミーで一番最後にジョインしたのが徳永さんだと思いますが、オンボーディングなどはどのような感じだったんでしょうか。学生時代に NestJS はがっつり触っていたんでしたっけ? 徳永 : いえ、学生時代はチュートリアルをしたくらいで、深く使っていたということはなかったです。ですので、最初は全然プロダクトのコードが分からないというレベルで、ほとんど未経験でした。 山田 : なるほど。ほぼ未経験で新卒研修を経て、アカデミーの開発チームにジョインして困った部分などはありましたか? 徳永 : TypeScript を使っているというのは凄く自分にとって良くて、言語でつまづくということはほとんどありませんでした。 やはり 公式ドキュメントがすごく充実していますし、コミュニティの質の高い情報も多い ので。プロダクトに関しては過去の Pull Request や Issue 上のやり取りなどで、 どういう意図で実装されたものなのかを参照できたのが大きかった です。一方でバックエンドのデザインパターンなどは経験が少なく苦労した部分がありました。その部分はドキュメントで学習したり、周りのメンバーに直接質問しながらキャッチアップしていきました。 山田 : 今年は入社後研修も TypeScript で書く割合が Ruby よりも多かったので、それがもし逆だったら、もっと苦労していたかもしれないですね。 徳永 : そうですね、その可能性は十分にあり得ます。 山田 : あとアカデミーチームは火水木に勉強会を開催していますよね。ジョブメドレーの開発チームでも噂になっています。 岸田 : これは全員で集まる勉強会というわけではなく、 月によって各自テーマを決めて火水木 1 時間ずつスキルアップのために時間を確保し、学んだことなどをアウトプットしていく という形式です。アウトプットしたものは、お互いに確認して知識を深めていこうというものですね。 山田 : それは素敵な取り組みですね。あとは Figma で開発ドキュメントをまとめているのが印象的ですよね。 牧野 : 最初は岸田さんが率先して、開発した部分のドキュメントなんかを書いていたんですが、最近はチームで共有するようなものもほとんど Figma でまとめるようになっています。 熊本 : もちろん永続化しないといけないドキュメントは README なりコンフルエンスなりに書いていくのですが、ちょっとしたものを気軽に Figma にまとめられるのはすごく良いです。 岸田 : 他の人がやっていることなんかも絶対に目につく ので、そういった意味でもドキュメントが散らばらなくて今は良く機能していると思っています。 現状の課題感とアカデミーの開発に向く人 山田 : 最後になりますが、アカデミーに来てほしい人材はどういった方でしょうか? 徳永 : この プロダクトにわくわくできる方に来てほしい と思います。福祉業界に明るい方なんかは大歓迎です! 山田 : なるほど。プロダクトのビジネス面の理解をして楽しんで開発できる方ですね。技術的な部分では何かありますか? 岸田 : 今まで TypeScript と NestJS を中心に話していましたが、実はこれらができる人というのは必須条件ではありません。どの言語でもフレームワークでも ちゃんと Web 開発が分かっている方 であればという感じです。 他には 情報感度が高い人が良い と思います。言語化が難しいのですが、開発環境や言語、ライブラリなどできちんと最新の情報が取捨選択できる方がいると、チームがより活性化すると考えています。 バックエンドでいうとベース部分などもきちんと手を入れることができて、なおかつ人に教えるのが好きな方が入ってもらえると、チームへの知識伝播などもより上手くいくと考えています。 あるとより良いなという所だと、現在アプリは React Native で作られているんですが、React Native でのアプリ開発に強い方だと嬉しいですね。ユーザーに一番使われているのはアプリなので、さらにそのユーザビリティなどを高めるためには、アプリ開発に強い方に入ってもらえるとありがたいです。 山田 : よりアカデミーというプロダクトをドライブさせてくれる方にぜひ来てほしいところですね。本日はありがとうございました! おわりに メドレーの開発チームの中でも野心的なアーキテクチャで開発をしているアカデミーチームについてお伝えしました。 サービスを伸ばすために、適切に言語やフレームワークを選択していき、柔軟性を持って開発していってるんだなというのが伝わってきたインタビューでした。少しでも興味が出た方はぜひ、下のリンクからお話できればと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回はジョブメドレーアカデミー(以下、アカデミー)の開発の皆さんに集まってもらい、 TypeScript と NestJS を使ったバックエンド開発をどのように行なっているのかをインタビューしました。 以前、アカデミーがリニューアルした際にチームメンバーにインタビューした note もあるので、未読の方はぜひそちらもご覧いただければと思います。 インタビュイー紹介 岸田さん SES 会社で業務システム開発、バングラデシュに支社があるオフショア開発をしている会社でブリッジエンジニアという経験を経て、2019 年メドレー入社。 その後ジョブメドレーで開発をリード。現在はアカデミーのプロダクト責任者を務める。 熊本さん 2019 年メドレーに新卒入社。半年の研修を経てジョブメドレーの開発に携わる。現在はアカデミーの開発に携わる。大学では機械学習を用いた自動車の自動運転の研究をしていた。 牧野さん 2021 年 11 月にメドレー入社。不動産業界の SaaS を開発している会社でのマネージャー・エンジニアを経験。現在はアカデミーの開発に携わる。 徳永さん 2022 年メドレーに新卒入社。 OJT 期間中からアカデミーの開発に携わる。大学では、対話 AI システム・ AI との対話用プラットフォームの研究開発をしていた。 アカデミーのアーキテクチャ 山田 : 早速ですが、現在のアカデミーのアーキテクチャについて話を伺いたいと思います。全体の構成としては現在どんな形になっているんでしょうか。 岸田 : 以前話をしたところから大幅に変わったところはないですが、大まかにはこんな感じになっています。 山田 : ありがとうございます、分かりやすいですね。 (手前)岸田さん・(奥)牧野さん 山田 : Web フロントエンドに関しては Next.js とグローバルの状態管理として Apollo Client を使っていると伺っています。バックエンドは色々あると思うのですが、DB に関しては PostgreSQL を使っているんですよね? 岸田 : はい、ORM としては Prisma を使っています。 山田 : こうした開発環境はどのように決まったんでしょうか?特に、開発言語を全て TypeScript で統一する、という意思決定が先にあってバックエンド側のフレームワークは NestJS にしようとなったのか、逆に NestJS を使おうとして TypeScript になったのかをお聞きできれば。 岸田 : 細かいところまでは記憶にないのですが、TypeScript を使って型で開発をしたいというのが一番大きい理由でした。 山田 : まずは TypeScript からだったんですね。そこから Web フレームワークとしては Express など他の物もあったと思いますが、NestJS を採用した理由は何だったのでしょうか? 岸田 : そうですね、色々選択肢はあったと思うんですが、現実的にサービスを運用するとなると Express と NestJS のどちらかかな、という感じになっていました。 どちらも実開発での経験を持っているメンバーはいなかったんですが、Web 開発で良く使われる Web フレームワークは、基本的な機能やコンセプトについては共通する部分が多いことに加えて、アカデミーも特殊な仕様があるプロダクトではないので、どちらを選んだとしてもメンバーが困ることはないだろうと考えました。 その上で細かい使い勝手などを検討したり GraphQL との親和性の高さや Express という枯れた技術をベースにしていて、 フレームワーク自体の信頼感もあるので NestJS に決めた という感じでした。 熊本 : 絞り込んだ段階で各メンバーが NestJS を試験的に触り、使用感を見てみましたが、大きく問題になる点もなかったので全員一致で決まった感じですね。 岸田 : 前述のように Express をベースにしているので何か困ったら最悪 Express にすれば良いなというのもありました。 牧野 : ここまで実際大きな問題というのは無かったです。 山田 : 今は GraphQL を使って API を作っていると思いますが、REST で作るという選択肢は最初から無かったんですか? 岸田 : はい、GraphQL を使えるメンバーが揃っているという状況であえて REST にするメリットは無いなという事で考えていませんでした。外部公開する API があるとか、パフォーマンスがすごく重要な重い API 呼び出しというのがあったら別だったでしょうけども。 山田 : そもそもの話になってしまうんですが、弊社のプロダクト開発でよく使われている Ruby on Rails を使う選択肢は無かったんでしょうか? 岸田 : 選択肢としてはあるにはあったんですが、プロダクトのフェイズとして現在既に成長をしているサービスであることや、メンバーのスキルセットとして Rails に慣れたメンバーがそこまで多くなかったことを考えると、いかに技術的負債を少なく保ちつつ、機能開発をしていけるかという点が重要になるので、現在の技術構成にしました。 これが既存サービスのリニューアルではなくて、新規で一から全部作っていかないといけないという状況であれば Rails を使うかもしれませんが、やっぱり型があってある程度開発効率が高くなるというのは魅力的だったので、 これからどんどんと機能開発をするには今の構成が良い と判断しました。 (手前)徳永さん・(奥)熊本さん 山田 : DB に関して PostgreSQL ですが、 MySQL などにするということは考えなかったですか? 熊本 : そうですね、リニューアル前のアカデミーが使っていたということがあり、大きく変えるメリットはあまり無かったのが大きいです。 岸田 : あとこれは自分の感覚なのですが、PostgreSQL の方がスマートに処理できることが多いという印象があるというのもありました。 山田 : 元々サービスで使っていたというのは、やっぱり大きいですね。ORM は Prisma ですよね。例えば他に TypeORM などの選択肢もあったと思いますが、こちらはどういった経緯で使うことになりましたか? 熊本 : 色々と触ってみた結果、Prisma が 自分たちの使い方にマッチしてた のが大きかったです。がっつりと SQL を自動生成するなどの手厚い機能は、自分達の使い方では必要もなかったですし。キャッチアップのためにペアプロなどもしましたが、 メンバーも使いやすい ということが確認できました。 山田 : 今ペアプロの話が出ましたが、どのような目的でやっていたんでしょうか。 熊本 : 初期の頃はメンバー間の知識などに偏りもあったので、そういったものを実際にコードを触りながら埋めていく という目的でした。実際に、GraphQL を使ったことがなかったメンバーも早く慣れてくれました。 山田 : なるほど。ある種学習も兼ねていたんですね。最終的に現在このアーキテクチャの使い勝手としては感想としてどうでしょうか? 岸田 : 言葉は違うかもしれないですが「 普通に使い勝手が良い 」という感じです。 もちろんメンバーが習熟してきたので、色々とコード的に改善しようという部分はあったりもしますが、今のところ本当に困った問題というのは無いので、非常に良い選択だったかと思います。 NestJS のテストコードについて 山田 : テストコードは基本書いているんですよね? 熊本 : はい、バックエンドは基本的に Jest で全部書いています。フロントエンドに関しては今まさに E2E テストをどのように実施していくかを試行錯誤しているところですね。 山田 : NestJS でテストコード書くのに何か困ったこととかありましたか? 岸田 : 困っているというわけではないですが、Rails でほぼ必ず使われている Factorybot に相当する機能が欲しいですね…。 山田 : 確かに Node.js だとそういったライブラリを見かけないですね…。 岸田 : 皆さんあんまり困ってないのかな?ということで今は簡易的なものを自作して使っています。 山田 : OSS で作ったら需要があるかもしれませんね。 現状の NestJS での開発の課題感 山田 : ここまで NestJS での開発について聞いてきましたが、プロダクト開発での課題は何かあったりしますか? 岸田 : 先ほども少し話をしたメンバーの習熟度が高くなったことによるコード改善の他には、NestJS というか Node.js が Rails に比べると定番ライブラリが少なめなのがあります。 例えば、 Sidekiq のようなライブラリって便利だと思うんですが、相当するライブラリでデファクトスタンダードというようなものが Node.js だと少ない気がしています。ライブラリが一杯あるのですが、デファクトまでいかないというものが多いという感想です。 山田 : 確かに Node.js だと同じ目的のライブラリが複数あって機能がちょっとずつ違うという感じがありますね。 牧野 : あとは機能開発にリソースを取っているので、細かい部分でのライブラリのアップデートが遅れ気味というのが課題感としてあります。 今のところはとてつもなく遅れているというわけではないのですが、積り積っていくとこういった部分が負債になっていくので、対応したいと思っています。 岸田 : こういったものも含めてプロダクト開発を円滑に回すために、自分も含めた各メンバーのスキルを広げて深くしていかないといけないなというのもありますね。 チームへのオンボーディングについて 山田 : さて、話が変わって現在アカデミーで一番最後にジョインしたのが徳永さんだと思いますが、オンボーディングなどはどのような感じだったんでしょうか。学生時代に NestJS はがっつり触っていたんでしたっけ? 徳永 : いえ、学生時代はチュートリアルをしたくらいで、深く使っていたということはなかったです。ですので、最初は全然プロダクトのコードが分からないというレベルで、ほとんど未経験でした。 山田 : なるほど。ほぼ未経験で新卒研修を経て、アカデミーの開発チームにジョインして困った部分などはありましたか? 徳永 : TypeScript を使っているというのは凄く自分にとって良くて、言語でつまづくということはほとんどありませんでした。 やはり 公式ドキュメントがすごく充実していますし、コミュニティの質の高い情報も多い ので。プロダクトに関しては過去の Pull Request や Issue 上のやり取りなどで、 どういう意図で実装されたものなのかを参照できたのが大きかった です。一方でバックエンドのデザインパターンなどは経験が少なく苦労した部分がありました。その部分はドキュメントで学習したり、周りのメンバーに直接質問しながらキャッチアップしていきました。 山田 : 今年は入社後研修も TypeScript で書く割合が Ruby よりも多かったので、それがもし逆だったら、もっと苦労していたかもしれないですね。 徳永 : そうですね、その可能性は十分にあり得ます。 山田 : あとアカデミーチームは火水木に勉強会を開催していますよね。ジョブメドレーの開発チームでも噂になっています。 岸田 : これは全員で集まる勉強会というわけではなく、 月によって各自テーマを決めて火水木 1 時間ずつスキルアップのために時間を確保し、学んだことなどをアウトプットしていく という形式です。アウトプットしたものは、お互いに確認して知識を深めていこうというものですね。 山田 : それは素敵な取り組みですね。あとは Figma で開発ドキュメントをまとめているのが印象的ですよね。 牧野 : 最初は岸田さんが率先して、開発した部分のドキュメントなんかを書いていたんですが、最近はチームで共有するようなものもほとんど Figma でまとめるようになっています。 熊本 : もちろん永続化しないといけないドキュメントは README なりコンフルエンスなりに書いていくのですが、ちょっとしたものを気軽に Figma にまとめられるのはすごく良いです。 岸田 : 他の人がやっていることなんかも絶対に目につく ので、そういった意味でもドキュメントが散らばらなくて今は良く機能していると思っています。 現状の課題感とアカデミーの開発に向く人 山田 : 最後になりますが、アカデミーに来てほしい人材はどういった方でしょうか? 徳永 : この プロダクトにわくわくできる方に来てほしい と思います。福祉業界に明るい方なんかは大歓迎です! 山田 : なるほど。プロダクトのビジネス面の理解をして楽しんで開発できる方ですね。技術的な部分では何かありますか? 岸田 : 今まで TypeScript と NestJS を中心に話していましたが、実はこれらができる人というのは必須条件ではありません。どの言語でもフレームワークでも ちゃんと Web 開発が分かっている方 であればという感じです。 他には 情報感度が高い人が良い と思います。言語化が難しいのですが、開発環境や言語、ライブラリなどできちんと最新の情報が取捨選択できる方がいると、チームがより活性化すると考えています。 バックエンドでいうとベース部分などもきちんと手を入れることができて、なおかつ人に教えるのが好きな方が入ってもらえると、チームへの知識伝播などもより上手くいくと考えています。 あるとより良いなという所だと、現在アプリは React Native で作られているんですが、React Native でのアプリ開発に強い方だと嬉しいですね。ユーザーに一番使われているのはアプリなので、さらにそのユーザビリティなどを高めるためには、アプリ開発に強い方に入ってもらえるとありがたいです。 山田 : よりアカデミーというプロダクトをドライブさせてくれる方にぜひ来てほしいところですね。本日はありがとうございました! おわりに メドレーの開発チームの中でも野心的なアーキテクチャで開発をしているアカデミーチームについてお伝えしました。 サービスを伸ばすために、適切に言語やフレームワークを選択していき、柔軟性を持って開発していってるんだなというのが伝わってきたインタビューでした。少しでも興味が出た方はぜひ、下のリンクからお話できればと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回はジョブメドレーアカデミー(以下、アカデミー)の開発の皆さんに集まってもらい、 TypeScript と NestJS を使ったバックエンド開発をどのように行なっているのかをインタビューしました。 以前、アカデミーがリニューアルした際にチームメンバーにインタビューした note もあるので、未読の方はぜひそちらもご覧いただければと思います。 インタビュイー紹介 岸田さん SES 会社で業務システム開発、バングラデシュに支社があるオフショア開発をしている会社でブリッジエンジニアという経験を経て、2019 年メドレー入社。 その後ジョブメドレーで開発をリード。現在はアカデミーのプロダクト責任者を務める。 熊本さん 2019 年メドレーに新卒入社。半年の研修を経てジョブメドレーの開発に携わる。現在はアカデミーの開発に携わる。大学では機械学習を用いた自動車の自動運転の研究をしていた。 牧野さん 2021 年 11 月にメドレー入社。不動産業界の SaaS を開発している会社でのマネージャー・エンジニアを経験。現在はアカデミーの開発に携わる。 徳永さん 2022 年メドレーに新卒入社。 OJT 期間中からアカデミーの開発に携わる。大学では、対話 AI システム・ AI との対話用プラットフォームの研究開発をしていた。 アカデミーのアーキテクチャ 山田 : 早速ですが、現在のアカデミーのアーキテクチャについて話を伺いたいと思います。全体の構成としては現在どんな形になっているんでしょうか。 岸田 : 以前話をしたところから大幅に変わったところはないですが、大まかにはこんな感じになっています。 山田 : ありがとうございます、分かりやすいですね。 (手前)岸田さん・(奥)牧野さん 山田 : Web フロントエンドに関しては Next.js とグローバルの状態管理として Apollo Client を使っていると伺っています。バックエンドは色々あると思うのですが、DB に関しては PostgreSQL を使っているんですよね? 岸田 : はい、ORM としては Prisma を使っています。 山田 : こうした開発環境はどのように決まったんでしょうか?特に、開発言語を全て TypeScript で統一する、という意思決定が先にあってバックエンド側のフレームワークは NestJS にしようとなったのか、逆に NestJS を使おうとして TypeScript になったのかをお聞きできれば。 岸田 : 細かいところまでは記憶にないのですが、TypeScript を使って型で開発をしたいというのが一番大きい理由でした。 山田 : まずは TypeScript からだったんですね。そこから Web フレームワークとしては Express など他の物もあったと思いますが、NestJS を採用した理由は何だったのでしょうか? 岸田 : そうですね、色々選択肢はあったと思うんですが、現実的にサービスを運用するとなると Express と NestJS のどちらかかな、という感じになっていました。 どちらも実開発での経験を持っているメンバーはいなかったんですが、Web 開発で良く使われる Web フレームワークは、基本的な機能やコンセプトについては共通する部分が多いことに加えて、アカデミーも特殊な仕様があるプロダクトではないので、どちらを選んだとしてもメンバーが困ることはないだろうと考えました。 その上で細かい使い勝手などを検討したり GraphQL との親和性の高さや Express という枯れた技術をベースにしていて、 フレームワーク自体の信頼感もあるので NestJS に決めた という感じでした。 熊本 : 絞り込んだ段階で各メンバーが NestJS を試験的に触り、使用感を見てみましたが、大きく問題になる点もなかったので全員一致で決まった感じですね。 岸田 : 前述のように Express をベースにしているので何か困ったら最悪 Express にすれば良いなというのもありました。 牧野 : ここまで実際大きな問題というのは無かったです。 山田 : 今は GraphQL を使って API を作っていると思いますが、REST で作るという選択肢は最初から無かったんですか? 岸田 : はい、GraphQL を使えるメンバーが揃っているという状況であえて REST にするメリットは無いなという事で考えていませんでした。外部公開する API があるとか、パフォーマンスがすごく重要な重い API 呼び出しというのがあったら別だったでしょうけども。 山田 : そもそもの話になってしまうんですが、弊社のプロダクト開発でよく使われている Ruby on Rails を使う選択肢は無かったんでしょうか? 岸田 : 選択肢としてはあるにはあったんですが、プロダクトのフェイズとして現在既に成長をしているサービスであることや、メンバーのスキルセットとして Rails に慣れたメンバーがそこまで多くなかったことを考えると、いかに技術的負債を少なく保ちつつ、機能開発をしていけるかという点が重要になるので、現在の技術構成にしました。 これが既存サービスのリニューアルではなくて、新規で一から全部作っていかないといけないという状況であれば Rails を使うかもしれませんが、やっぱり型があってある程度開発効率が高くなるというのは魅力的だったので、 これからどんどんと機能開発をするには今の構成が良い と判断しました。 (手前)徳永さん・(奥)熊本さん 山田 : DB に関して PostgreSQL ですが、 MySQL などにするということは考えなかったですか? 熊本 : そうですね、リニューアル前のアカデミーが使っていたということがあり、大きく変えるメリットはあまり無かったのが大きいです。 岸田 : あとこれは自分の感覚なのですが、PostgreSQL の方がスマートに処理できることが多いという印象があるというのもありました。 山田 : 元々サービスで使っていたというのは、やっぱり大きいですね。ORM は Prisma ですよね。例えば他に TypeORM などの選択肢もあったと思いますが、こちらはどういった経緯で使うことになりましたか? 熊本 : 色々と触ってみた結果、Prisma が 自分たちの使い方にマッチしてた のが大きかったです。がっつりと SQL を自動生成するなどの手厚い機能は、自分達の使い方では必要もなかったですし。キャッチアップのためにペアプロなどもしましたが、 メンバーも使いやすい ということが確認できました。 山田 : 今ペアプロの話が出ましたが、どのような目的でやっていたんでしょうか。 熊本 : 初期の頃はメンバー間の知識などに偏りもあったので、そういったものを実際にコードを触りながら埋めていく という目的でした。実際に、GraphQL を使ったことがなかったメンバーも早く慣れてくれました。 山田 : なるほど。ある種学習も兼ねていたんですね。最終的に現在このアーキテクチャの使い勝手としては感想としてどうでしょうか? 岸田 : 言葉は違うかもしれないですが「 普通に使い勝手が良い 」という感じです。 もちろんメンバーが習熟してきたので、色々とコード的に改善しようという部分はあったりもしますが、今のところ本当に困った問題というのは無いので、非常に良い選択だったかと思います。 NestJS のテストコードについて 山田 : テストコードは基本書いているんですよね? 熊本 : はい、バックエンドは基本的に Jest で全部書いています。フロントエンドに関しては今まさに E2E テストをどのように実施していくかを試行錯誤しているところですね。 山田 : NestJS でテストコード書くのに何か困ったこととかありましたか? 岸田 : 困っているというわけではないですが、Rails でほぼ必ず使われている Factorybot に相当する機能が欲しいですね…。 山田 : 確かに Node.js だとそういったライブラリを見かけないですね…。 岸田 : 皆さんあんまり困ってないのかな?ということで今は簡易的なものを自作して使っています。 山田 : OSS で作ったら需要があるかもしれませんね。 現状の NestJS での開発の課題感 山田 : ここまで NestJS での開発について聞いてきましたが、プロダクト開発での課題は何かあったりしますか? 岸田 : 先ほども少し話をしたメンバーの習熟度が高くなったことによるコード改善の他には、NestJS というか Node.js が Rails に比べると定番ライブラリが少なめなのがあります。 例えば、 Sidekiq のようなライブラリって便利だと思うんですが、相当するライブラリでデファクトスタンダードというようなものが Node.js だと少ない気がしています。ライブラリが一杯あるのですが、デファクトまでいかないというものが多いという感想です。 山田 : 確かに Node.js だと同じ目的のライブラリが複数あって機能がちょっとずつ違うという感じがありますね。 牧野 : あとは機能開発にリソースを取っているので、細かい部分でのライブラリのアップデートが遅れ気味というのが課題感としてあります。 今のところはとてつもなく遅れているというわけではないのですが、積り積っていくとこういった部分が負債になっていくので、対応したいと思っています。 岸田 : こういったものも含めてプロダクト開発を円滑に回すために、自分も含めた各メンバーのスキルを広げて深くしていかないといけないなというのもありますね。 チームへのオンボーディングについて 山田 : さて、話が変わって現在アカデミーで一番最後にジョインしたのが徳永さんだと思いますが、オンボーディングなどはどのような感じだったんでしょうか。学生時代に NestJS はがっつり触っていたんでしたっけ? 徳永 : いえ、学生時代はチュートリアルをしたくらいで、深く使っていたということはなかったです。ですので、最初は全然プロダクトのコードが分からないというレベルで、ほとんど未経験でした。 山田 : なるほど。ほぼ未経験で新卒研修を経て、アカデミーの開発チームにジョインして困った部分などはありましたか? 徳永 : TypeScript を使っているというのは凄く自分にとって良くて、言語でつまづくということはほとんどありませんでした。 やはり 公式ドキュメントがすごく充実していますし、コミュニティの質の高い情報も多い ので。プロダクトに関しては過去の Pull Request や Issue 上のやり取りなどで、 どういう意図で実装されたものなのかを参照できたのが大きかった です。一方でバックエンドのデザインパターンなどは経験が少なく苦労した部分がありました。その部分はドキュメントで学習したり、周りのメンバーに直接質問しながらキャッチアップしていきました。 山田 : 今年は入社後研修も TypeScript で書く割合が Ruby よりも多かったので、それがもし逆だったら、もっと苦労していたかもしれないですね。 徳永 : そうですね、その可能性は十分にあり得ます。 山田 : あとアカデミーチームは火水木に勉強会を開催していますよね。ジョブメドレーの開発チームでも噂になっています。 岸田 : これは全員で集まる勉強会というわけではなく、 月によって各自テーマを決めて火水木 1 時間ずつスキルアップのために時間を確保し、学んだことなどをアウトプットしていく という形式です。アウトプットしたものは、お互いに確認して知識を深めていこうというものですね。 山田 : それは素敵な取り組みですね。あとは Figma で開発ドキュメントをまとめているのが印象的ですよね。 牧野 : 最初は岸田さんが率先して、開発した部分のドキュメントなんかを書いていたんですが、最近はチームで共有するようなものもほとんど Figma でまとめるようになっています。 熊本 : もちろん永続化しないといけないドキュメントは README なりコンフルエンスなりに書いていくのですが、ちょっとしたものを気軽に Figma にまとめられるのはすごく良いです。 岸田 : 他の人がやっていることなんかも絶対に目につく ので、そういった意味でもドキュメントが散らばらなくて今は良く機能していると思っています。 現状の課題感とアカデミーの開発に向く人 山田 : 最後になりますが、アカデミーに来てほしい人材はどういった方でしょうか? 徳永 : この プロダクトにわくわくできる方に来てほしい と思います。福祉業界に明るい方なんかは大歓迎です! 山田 : なるほど。プロダクトのビジネス面の理解をして楽しんで開発できる方ですね。技術的な部分では何かありますか? 岸田 : 今まで TypeScript と NestJS を中心に話していましたが、実はこれらができる人というのは必須条件ではありません。どの言語でもフレームワークでも ちゃんと Web 開発が分かっている方 であればという感じです。 他には 情報感度が高い人が良い と思います。言語化が難しいのですが、開発環境や言語、ライブラリなどできちんと最新の情報が取捨選択できる方がいると、チームがより活性化すると考えています。 バックエンドでいうとベース部分などもきちんと手を入れることができて、なおかつ人に教えるのが好きな方が入ってもらえると、チームへの知識伝播などもより上手くいくと考えています。 あるとより良いなという所だと、現在アプリは React Native で作られているんですが、React Native でのアプリ開発に強い方だと嬉しいですね。ユーザーに一番使われているのはアプリなので、さらにそのユーザビリティなどを高めるためには、アプリ開発に強い方に入ってもらえるとありがたいです。 山田 : よりアカデミーというプロダクトをドライブさせてくれる方にぜひ来てほしいところですね。本日はありがとうございました! おわりに メドレーの開発チームの中でも野心的なアーキテクチャで開発をしているアカデミーチームについてお伝えしました。 サービスを伸ばすために、適切に言語やフレームワークを選択していき、柔軟性を持って開発していってるんだなというのが伝わってきたインタビューでした。少しでも興味が出た方はぜひ、下のリンクからお話できればと思います! https://www.medley.jp/jobs
はじめに みなさん、こんにちは。エンジニアの山田です。今回はジョブメドレーアカデミー(以下、アカデミー)の開発の皆さんに集まってもらい、 TypeScript と NestJS を使ったバックエンド開発をどのように行なっているのかをインタビューしました。 以前、アカデミーがリニューアルした際にチームメンバーにインタビューした note もあるので、未読の方はぜひそちらもご覧いただければと思います。 インタビュイー紹介 岸田さん SES 会社で業務システム開発、バングラデシュに支社があるオフショア開発をしている会社でブリッジエンジニアという経験を経て、2019 年メドレー入社。 その後ジョブメドレーで開発をリード。現在はアカデミーのプロダクト責任者を務める。 熊本さん 2019 年メドレーに新卒入社。半年の研修を経てジョブメドレーの開発に携わる。現在はアカデミーの開発に携わる。大学では機械学習を用いた自動車の自動運転の研究をしていた。 牧野さん 2021 年 11 月にメドレー入社。不動産業界の SaaS を開発している会社でのマネージャー・エンジニアを経験。現在はアカデミーの開発に携わる。 徳永さん 2022 年メドレーに新卒入社。 OJT 期間中からアカデミーの開発に携わる。大学では、対話 AI システム・ AI との対話用プラットフォームの研究開発をしていた。 アカデミーのアーキテクチャ 山田 : 早速ですが、現在のアカデミーのアーキテクチャについて話を伺いたいと思います。全体の構成としては現在どんな形になっているんでしょうか。 岸田 : 以前話をしたところから大幅に変わったところはないですが、大まかにはこんな感じになっています。 山田 : ありがとうございます、分かりやすいですね。 (手前)岸田さん・(奥)牧野さん 山田 : Web フロントエンドに関しては Next.js とグローバルの状態管理として Apollo Client を使っていると伺っています。バックエンドは色々あると思うのですが、DB に関しては PostgreSQL を使っているんですよね? 岸田 : はい、ORM としては Prisma を使っています。 山田 : こうした開発環境はどのように決まったんでしょうか?特に、開発言語を全て TypeScript で統一する、という意思決定が先にあってバックエンド側のフレームワークは NestJS にしようとなったのか、逆に NestJS を使おうとして TypeScript になったのかをお聞きできれば。 岸田 : 細かいところまでは記憶にないのですが、TypeScript を使って型で開発をしたいというのが一番大きい理由でした。 山田 : まずは TypeScript からだったんですね。そこから Web フレームワークとしては Express など他の物もあったと思いますが、NestJS を採用した理由は何だったのでしょうか? 岸田 : そうですね、色々選択肢はあったと思うんですが、現実的にサービスを運用するとなると Express と NestJS のどちらかかな、という感じになっていました。 どちらも実開発での経験を持っているメンバーはいなかったんですが、Web 開発で良く使われる Web フレームワークは、基本的な機能やコンセプトについては共通する部分が多いことに加えて、アカデミーも特殊な仕様があるプロダクトではないので、どちらを選んだとしてもメンバーが困ることはないだろうと考えました。 その上で細かい使い勝手などを検討したり GraphQL との親和性の高さや Express という枯れた技術をベースにしていて、 フレームワーク自体の信頼感もあるので NestJS に決めた という感じでした。 熊本 : 絞り込んだ段階で各メンバーが NestJS を試験的に触り、使用感を見てみましたが、大きく問題になる点もなかったので全員一致で決まった感じですね。 岸田 : 前述のように Express をベースにしているので何か困ったら最悪 Express にすれば良いなというのもありました。 牧野 : ここまで実際大きな問題というのは無かったです。 山田 : 今は GraphQL を使って API を作っていると思いますが、REST で作るという選択肢は最初から無かったんですか? 岸田 : はい、GraphQL を使えるメンバーが揃っているという状況であえて REST にするメリットは無いなという事で考えていませんでした。外部公開する API があるとか、パフォーマンスがすごく重要な重い API 呼び出しというのがあったら別だったでしょうけども。 山田 : そもそもの話になってしまうんですが、弊社のプロダクト開発でよく使われている Ruby on Rails を使う選択肢は無かったんでしょうか? 岸田 : 選択肢としてはあるにはあったんですが、プロダクトのフェイズとして現在既に成長をしているサービスであることや、メンバーのスキルセットとして Rails に慣れたメンバーがそこまで多くなかったことを考えると、いかに技術的負債を少なく保ちつつ、機能開発をしていけるかという点が重要になるので、現在の技術構成にしました。 これが既存サービスのリニューアルではなくて、新規で一から全部作っていかないといけないという状況であれば Rails を使うかもしれませんが、やっぱり型があってある程度開発効率が高くなるというのは魅力的だったので、 これからどんどんと機能開発をするには今の構成が良い と判断しました。 (手前)徳永さん・(奥)熊本さん 山田 : DB に関して PostgreSQL ですが、 MySQL などにするということは考えなかったですか? 熊本 : そうですね、リニューアル前のアカデミーが使っていたということがあり、大きく変えるメリットはあまり無かったのが大きいです。 岸田 : あとこれは自分の感覚なのですが、PostgreSQL の方がスマートに処理できることが多いという印象があるというのもありました。 山田 : 元々サービスで使っていたというのは、やっぱり大きいですね。ORM は Prisma ですよね。例えば他に TypeORM などの選択肢もあったと思いますが、こちらはどういった経緯で使うことになりましたか? 熊本 : 色々と触ってみた結果、Prisma が 自分たちの使い方にマッチしてた のが大きかったです。がっつりと SQL を自動生成するなどの手厚い機能は、自分達の使い方では必要もなかったですし。キャッチアップのためにペアプロなどもしましたが、 メンバーも使いやすい ということが確認できました。 山田 : 今ペアプロの話が出ましたが、どのような目的でやっていたんでしょうか。 熊本 : 初期の頃はメンバー間の知識などに偏りもあったので、そういったものを実際にコードを触りながら埋めていく という目的でした。実際に、GraphQL を使ったことがなかったメンバーも早く慣れてくれました。 山田 : なるほど。ある種学習も兼ねていたんですね。最終的に現在このアーキテクチャの使い勝手としては感想としてどうでしょうか? 岸田 : 言葉は違うかもしれないですが「 普通に使い勝手が良い 」という感じです。 もちろんメンバーが習熟してきたので、色々とコード的に改善しようという部分はあったりもしますが、今のところ本当に困った問題というのは無いので、非常に良い選択だったかと思います。 NestJS のテストコードについて 山田 : テストコードは基本書いているんですよね? 熊本 : はい、バックエンドは基本的に Jest で全部書いています。フロントエンドに関しては今まさに E2E テストをどのように実施していくかを試行錯誤しているところですね。 山田 : NestJS でテストコード書くのに何か困ったこととかありましたか? 岸田 : 困っているというわけではないですが、Rails でほぼ必ず使われている Factorybot に相当する機能が欲しいですね…。 山田 : 確かに Node.js だとそういったライブラリを見かけないですね…。 岸田 : 皆さんあんまり困ってないのかな?ということで今は簡易的なものを自作して使っています。 山田 : OSS で作ったら需要があるかもしれませんね。 現状の NestJS での開発の課題感 山田 : ここまで NestJS での開発について聞いてきましたが、プロダクト開発での課題は何かあったりしますか? 岸田 : 先ほども少し話をしたメンバーの習熟度が高くなったことによるコード改善の他には、NestJS というか Node.js が Rails に比べると定番ライブラリが少なめなのがあります。 例えば、 Sidekiq のようなライブラリって便利だと思うんですが、相当するライブラリでデファクトスタンダードというようなものが Node.js だと少ない気がしています。ライブラリが一杯あるのですが、デファクトまでいかないというものが多いという感想です。 山田 : 確かに Node.js だと同じ目的のライブラリが複数あって機能がちょっとずつ違うという感じがありますね。 牧野 : あとは機能開発にリソースを取っているので、細かい部分でのライブラリのアップデートが遅れ気味というのが課題感としてあります。 今のところはとてつもなく遅れているというわけではないのですが、積り積っていくとこういった部分が負債になっていくので、対応したいと思っています。 岸田 : こういったものも含めてプロダクト開発を円滑に回すために、自分も含めた各メンバーのスキルを広げて深くしていかないといけないなというのもありますね。 チームへのオンボーディングについて 山田 : さて、話が変わって現在アカデミーで一番最後にジョインしたのが徳永さんだと思いますが、オンボーディングなどはどのような感じだったんでしょうか。学生時代に NestJS はがっつり触っていたんでしたっけ? 徳永 : いえ、学生時代はチュートリアルをしたくらいで、深く使っていたということはなかったです。ですので、最初は全然プロダクトのコードが分からないというレベルで、ほとんど未経験でした。 山田 : なるほど。ほぼ未経験で新卒研修を経て、アカデミーの開発チームにジョインして困った部分などはありましたか? 徳永 : TypeScript を使っているというのは凄く自分にとって良くて、言語でつまづくということはほとんどありませんでした。 やはり 公式ドキュメントがすごく充実していますし、コミュニティの質の高い情報も多い ので。プロダクトに関しては過去の Pull Request や Issue 上のやり取りなどで、 どういう意図で実装されたものなのかを参照できたのが大きかった です。一方でバックエンドのデザインパターンなどは経験が少なく苦労した部分がありました。その部分はドキュメントで学習したり、周りのメンバーに直接質問しながらキャッチアップしていきました。 山田 : 今年は入社後研修も TypeScript で書く割合が Ruby よりも多かったので、それがもし逆だったら、もっと苦労していたかもしれないですね。 徳永 : そうですね、その可能性は十分にあり得ます。 山田 : あとアカデミーチームは火水木に勉強会を開催していますよね。ジョブメドレーの開発チームでも噂になっています。 岸田 : これは全員で集まる勉強会というわけではなく、 月によって各自テーマを決めて火水木 1 時間ずつスキルアップのために時間を確保し、学んだことなどをアウトプットしていく という形式です。アウトプットしたものは、お互いに確認して知識を深めていこうというものですね。 山田 : それは素敵な取り組みですね。あとは Figma で開発ドキュメントをまとめているのが印象的ですよね。 牧野 : 最初は岸田さんが率先して、開発した部分のドキュメントなんかを書いていたんですが、最近はチームで共有するようなものもほとんど Figma でまとめるようになっています。 熊本 : もちろん永続化しないといけないドキュメントは README なりコンフルエンスなりに書いていくのですが、ちょっとしたものを気軽に Figma にまとめられるのはすごく良いです。 岸田 : 他の人がやっていることなんかも絶対に目につく ので、そういった意味でもドキュメントが散らばらなくて今は良く機能していると思っています。 現状の課題感とアカデミーの開発に向く人 山田 : 最後になりますが、アカデミーに来てほしい人材はどういった方でしょうか? 徳永 : この プロダクトにわくわくできる方に来てほしい と思います。福祉業界に明るい方なんかは大歓迎です! 山田 : なるほど。プロダクトのビジネス面の理解をして楽しんで開発できる方ですね。技術的な部分では何かありますか? 岸田 : 今まで TypeScript と NestJS を中心に話していましたが、実はこれらができる人というのは必須条件ではありません。どの言語でもフレームワークでも ちゃんと Web 開発が分かっている方 であればという感じです。 他には 情報感度が高い人が良い と思います。言語化が難しいのですが、開発環境や言語、ライブラリなどできちんと最新の情報が取捨選択できる方がいると、チームがより活性化すると考えています。 バックエンドでいうとベース部分などもきちんと手を入れることができて、なおかつ人に教えるのが好きな方が入ってもらえると、チームへの知識伝播などもより上手くいくと考えています。 あるとより良いなという所だと、現在アプリは React Native で作られているんですが、React Native でのアプリ開発に強い方だと嬉しいですね。ユーザーに一番使われているのはアプリなので、さらにそのユーザビリティなどを高めるためには、アプリ開発に強い方に入ってもらえるとありがたいです。 山田 : よりアカデミーというプロダクトをドライブさせてくれる方にぜひ来てほしいところですね。本日はありがとうございました! おわりに メドレーの開発チームの中でも野心的なアーキテクチャで開発をしているアカデミーチームについてお伝えしました。 サービスを伸ばすために、適切に言語やフレームワークを選択していき、柔軟性を持って開発していってるんだなというのが伝わってきたインタビューでした。少しでも興味が出た方はぜひ、下のリンクからお話できればと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回はジョブメドレーアカデミー(以下、アカデミー)の開発の皆さんに集まってもらい、 TypeScript と NestJS を使ったバックエンド開発をどのように行なっているのかをインタビューしました。 以前、アカデミーがリニューアルした際にチームメンバーにインタビューした note もあるので、未読の方はぜひそちらもご覧いただければと思います。 インタビュイー紹介 岸田さん SES 会社で業務システム開発、バングラデシュに支社があるオフショア開発をしている会社でブリッジエンジニアという経験を経て、2019 年メドレー入社。 その後ジョブメドレーで開発をリード。現在はアカデミーのプロダクト責任者を務める。 熊本さん 2019 年メドレーに新卒入社。半年の研修を経てジョブメドレーの開発に携わる。現在はアカデミーの開発に携わる。大学では機械学習を用いた自動車の自動運転の研究をしていた。 牧野さん 2021 年 11 月にメドレー入社。不動産業界の SaaS を開発している会社でのマネージャー・エンジニアを経験。現在はアカデミーの開発に携わる。 徳永さん 2022 年メドレーに新卒入社。 OJT 期間中からアカデミーの開発に携わる。大学では、対話 AI システム・ AI との対話用プラットフォームの研究開発をしていた。 アカデミーのアーキテクチャ 山田 : 早速ですが、現在のアカデミーのアーキテクチャについて話を伺いたいと思います。全体の構成としては現在どんな形になっているんでしょうか。 岸田 : 以前話をしたところから大幅に変わったところはないですが、大まかにはこんな感じになっています。 山田 : ありがとうございます、分かりやすいですね。 (手前)岸田さん・(奥)牧野さん 山田 : Web フロントエンドに関しては Next.js とグローバルの状態管理として Apollo Client を使っていると伺っています。バックエンドは色々あると思うのですが、DB に関しては PostgreSQL を使っているんですよね? 岸田 : はい、ORM としては Prisma を使っています。 山田 : こうした開発環境はどのように決まったんでしょうか?特に、開発言語を全て TypeScript で統一する、という意思決定が先にあってバックエンド側のフレームワークは NestJS にしようとなったのか、逆に NestJS を使おうとして TypeScript になったのかをお聞きできれば。 岸田 : 細かいところまでは記憶にないのですが、TypeScript を使って型で開発をしたいというのが一番大きい理由でした。 山田 : まずは TypeScript からだったんですね。そこから Web フレームワークとしては Express など他の物もあったと思いますが、NestJS を採用した理由は何だったのでしょうか? 岸田 : そうですね、色々選択肢はあったと思うんですが、現実的にサービスを運用するとなると Express と NestJS のどちらかかな、という感じになっていました。 どちらも実開発での経験を持っているメンバーはいなかったんですが、Web 開発で良く使われる Web フレームワークは、基本的な機能やコンセプトについては共通する部分が多いことに加えて、アカデミーも特殊な仕様があるプロダクトではないので、どちらを選んだとしてもメンバーが困ることはないだろうと考えました。 その上で細かい使い勝手などを検討したり GraphQL との親和性の高さや Express という枯れた技術をベースにしていて、 フレームワーク自体の信頼感もあるので NestJS に決めた という感じでした。 熊本 : 絞り込んだ段階で各メンバーが NestJS を試験的に触り、使用感を見てみましたが、大きく問題になる点もなかったので全員一致で決まった感じですね。 岸田 : 前述のように Express をベースにしているので何か困ったら最悪 Express にすれば良いなというのもありました。 牧野 : ここまで実際大きな問題というのは無かったです。 山田 : 今は GraphQL を使って API を作っていると思いますが、REST で作るという選択肢は最初から無かったんですか? 岸田 : はい、GraphQL を使えるメンバーが揃っているという状況であえて REST にするメリットは無いなという事で考えていませんでした。外部公開する API があるとか、パフォーマンスがすごく重要な重い API 呼び出しというのがあったら別だったでしょうけども。 山田 : そもそもの話になってしまうんですが、弊社のプロダクト開発でよく使われている Ruby on Rails を使う選択肢は無かったんでしょうか? 岸田 : 選択肢としてはあるにはあったんですが、プロダクトのフェイズとして現在既に成長をしているサービスであることや、メンバーのスキルセットとして Rails に慣れたメンバーがそこまで多くなかったことを考えると、いかに技術的負債を少なく保ちつつ、機能開発をしていけるかという点が重要になるので、現在の技術構成にしました。 これが既存サービスのリニューアルではなくて、新規で一から全部作っていかないといけないという状況であれば Rails を使うかもしれませんが、やっぱり型があってある程度開発効率が高くなるというのは魅力的だったので、 これからどんどんと機能開発をするには今の構成が良い と判断しました。 (手前)徳永さん・(奥)熊本さん 山田 : DB に関して PostgreSQL ですが、 MySQL などにするということは考えなかったですか? 熊本 : そうですね、リニューアル前のアカデミーが使っていたということがあり、大きく変えるメリットはあまり無かったのが大きいです。 岸田 : あとこれは自分の感覚なのですが、PostgreSQL の方がスマートに処理できることが多いという印象があるというのもありました。 山田 : 元々サービスで使っていたというのは、やっぱり大きいですね。ORM は Prisma ですよね。例えば他に TypeORM などの選択肢もあったと思いますが、こちらはどういった経緯で使うことになりましたか? 熊本 : 色々と触ってみた結果、Prisma が 自分たちの使い方にマッチしてた のが大きかったです。がっつりと SQL を自動生成するなどの手厚い機能は、自分達の使い方では必要もなかったですし。キャッチアップのためにペアプロなどもしましたが、 メンバーも使いやすい ということが確認できました。 山田 : 今ペアプロの話が出ましたが、どのような目的でやっていたんでしょうか。 熊本 : 初期の頃はメンバー間の知識などに偏りもあったので、そういったものを実際にコードを触りながら埋めていく という目的でした。実際に、GraphQL を使ったことがなかったメンバーも早く慣れてくれました。 山田 : なるほど。ある種学習も兼ねていたんですね。最終的に現在このアーキテクチャの使い勝手としては感想としてどうでしょうか? 岸田 : 言葉は違うかもしれないですが「 普通に使い勝手が良い 」という感じです。 もちろんメンバーが習熟してきたので、色々とコード的に改善しようという部分はあったりもしますが、今のところ本当に困った問題というのは無いので、非常に良い選択だったかと思います。 NestJS のテストコードについて 山田 : テストコードは基本書いているんですよね? 熊本 : はい、バックエンドは基本的に Jest で全部書いています。フロントエンドに関しては今まさに E2E テストをどのように実施していくかを試行錯誤しているところですね。 山田 : NestJS でテストコード書くのに何か困ったこととかありましたか? 岸田 : 困っているというわけではないですが、Rails でほぼ必ず使われている Factorybot に相当する機能が欲しいですね…。 山田 : 確かに Node.js だとそういったライブラリを見かけないですね…。 岸田 : 皆さんあんまり困ってないのかな?ということで今は簡易的なものを自作して使っています。 山田 : OSS で作ったら需要があるかもしれませんね。 現状の NestJS での開発の課題感 山田 : ここまで NestJS での開発について聞いてきましたが、プロダクト開発での課題は何かあったりしますか? 岸田 : 先ほども少し話をしたメンバーの習熟度が高くなったことによるコード改善の他には、NestJS というか Node.js が Rails に比べると定番ライブラリが少なめなのがあります。 例えば、 Sidekiq のようなライブラリって便利だと思うんですが、相当するライブラリでデファクトスタンダードというようなものが Node.js だと少ない気がしています。ライブラリが一杯あるのですが、デファクトまでいかないというものが多いという感想です。 山田 : 確かに Node.js だと同じ目的のライブラリが複数あって機能がちょっとずつ違うという感じがありますね。 牧野 : あとは機能開発にリソースを取っているので、細かい部分でのライブラリのアップデートが遅れ気味というのが課題感としてあります。 今のところはとてつもなく遅れているというわけではないのですが、積り積っていくとこういった部分が負債になっていくので、対応したいと思っています。 岸田 : こういったものも含めてプロダクト開発を円滑に回すために、自分も含めた各メンバーのスキルを広げて深くしていかないといけないなというのもありますね。 チームへのオンボーディングについて 山田 : さて、話が変わって現在アカデミーで一番最後にジョインしたのが徳永さんだと思いますが、オンボーディングなどはどのような感じだったんでしょうか。学生時代に NestJS はがっつり触っていたんでしたっけ? 徳永 : いえ、学生時代はチュートリアルをしたくらいで、深く使っていたということはなかったです。ですので、最初は全然プロダクトのコードが分からないというレベルで、ほとんど未経験でした。 山田 : なるほど。ほぼ未経験で新卒研修を経て、アカデミーの開発チームにジョインして困った部分などはありましたか? 徳永 : TypeScript を使っているというのは凄く自分にとって良くて、言語でつまづくということはほとんどありませんでした。 やはり 公式ドキュメントがすごく充実していますし、コミュニティの質の高い情報も多い ので。プロダクトに関しては過去の Pull Request や Issue 上のやり取りなどで、 どういう意図で実装されたものなのかを参照できたのが大きかった です。一方でバックエンドのデザインパターンなどは経験が少なく苦労した部分がありました。その部分はドキュメントで学習したり、周りのメンバーに直接質問しながらキャッチアップしていきました。 山田 : 今年は入社後研修も TypeScript で書く割合が Ruby よりも多かったので、それがもし逆だったら、もっと苦労していたかもしれないですね。 徳永 : そうですね、その可能性は十分にあり得ます。 山田 : あとアカデミーチームは火水木に勉強会を開催していますよね。ジョブメドレーの開発チームでも噂になっています。 岸田 : これは全員で集まる勉強会というわけではなく、 月によって各自テーマを決めて火水木 1 時間ずつスキルアップのために時間を確保し、学んだことなどをアウトプットしていく という形式です。アウトプットしたものは、お互いに確認して知識を深めていこうというものですね。 山田 : それは素敵な取り組みですね。あとは Figma で開発ドキュメントをまとめているのが印象的ですよね。 牧野 : 最初は岸田さんが率先して、開発した部分のドキュメントなんかを書いていたんですが、最近はチームで共有するようなものもほとんど Figma でまとめるようになっています。 熊本 : もちろん永続化しないといけないドキュメントは README なりコンフルエンスなりに書いていくのですが、ちょっとしたものを気軽に Figma にまとめられるのはすごく良いです。 岸田 : 他の人がやっていることなんかも絶対に目につく ので、そういった意味でもドキュメントが散らばらなくて今は良く機能していると思っています。 現状の課題感とアカデミーの開発に向く人 山田 : 最後になりますが、アカデミーに来てほしい人材はどういった方でしょうか? 徳永 : この プロダクトにわくわくできる方に来てほしい と思います。福祉業界に明るい方なんかは大歓迎です! 山田 : なるほど。プロダクトのビジネス面の理解をして楽しんで開発できる方ですね。技術的な部分では何かありますか? 岸田 : 今まで TypeScript と NestJS を中心に話していましたが、実はこれらができる人というのは必須条件ではありません。どの言語でもフレームワークでも ちゃんと Web 開発が分かっている方 であればという感じです。 他には 情報感度が高い人が良い と思います。言語化が難しいのですが、開発環境や言語、ライブラリなどできちんと最新の情報が取捨選択できる方がいると、チームがより活性化すると考えています。 バックエンドでいうとベース部分などもきちんと手を入れることができて、なおかつ人に教えるのが好きな方が入ってもらえると、チームへの知識伝播などもより上手くいくと考えています。 あるとより良いなという所だと、現在アプリは React Native で作られているんですが、React Native でのアプリ開発に強い方だと嬉しいですね。ユーザーに一番使われているのはアプリなので、さらにそのユーザビリティなどを高めるためには、アプリ開発に強い方に入ってもらえるとありがたいです。 山田 : よりアカデミーというプロダクトをドライブさせてくれる方にぜひ来てほしいところですね。本日はありがとうございました! おわりに メドレーの開発チームの中でも野心的なアーキテクチャで開発をしているアカデミーチームについてお伝えしました。 サービスを伸ばすために、適切に言語やフレームワークを選択していき、柔軟性を持って開発していってるんだなというのが伝わってきたインタビューでした。少しでも興味が出た方はぜひ、下のリンクからお話できればと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの山田です。今回はジョブメドレーアカデミー(以下、アカデミー)の開発の皆さんに集まってもらい、 TypeScript と NestJS を使ったバックエンド開発をどのように行なっているのかをインタビューしました。 以前、アカデミーがリニューアルした際にチームメンバーにインタビューした note もあるので、未読の方はぜひそちらもご覧いただければと思います。 インタビュイー紹介 岸田さん SES 会社で業務システム開発、バングラデシュに支社があるオフショア開発をしている会社でブリッジエンジニアという経験を経て、2019 年メドレー入社。 その後ジョブメドレーで開発をリード。現在はアカデミーのプロダクト責任者を務める。 熊本さん 2019 年メドレーに新卒入社。半年の研修を経てジョブメドレーの開発に携わる。現在はアカデミーの開発に携わる。大学では機械学習を用いた自動車の自動運転の研究をしていた。 牧野さん 2021 年 11 月にメドレー入社。不動産業界の SaaS を開発している会社でのマネージャー・エンジニアを経験。現在はアカデミーの開発に携わる。 徳永さん 2022 年メドレーに新卒入社。 OJT 期間中からアカデミーの開発に携わる。大学では、対話 AI システム・ AI との対話用プラットフォームの研究開発をしていた。 アカデミーのアーキテクチャ 山田 : 早速ですが、現在のアカデミーのアーキテクチャについて話を伺いたいと思います。全体の構成としては現在どんな形になっているんでしょうか。 岸田 : 以前話をしたところから大幅に変わったところはないですが、大まかにはこんな感じになっています。 山田 : ありがとうございます、分かりやすいですね。 (手前)岸田さん・(奥)牧野さん 山田 : Web フロントエンドに関しては Next.js とグローバルの状態管理として Apollo Client を使っていると伺っています。バックエンドは色々あると思うのですが、DB に関しては PostgreSQL を使っているんですよね? 岸田 : はい、ORM としては Prisma を使っています。 山田 : こうした開発環境はどのように決まったんでしょうか?特に、開発言語を全て TypeScript で統一する、という意思決定が先にあってバックエンド側のフレームワークは NestJS にしようとなったのか、逆に NestJS を使おうとして TypeScript になったのかをお聞きできれば。 岸田 : 細かいところまでは記憶にないのですが、TypeScript を使って型で開発をしたいというのが一番大きい理由でした。 山田 : まずは TypeScript からだったんですね。そこから Web フレームワークとしては Express など他の物もあったと思いますが、NestJS を採用した理由は何だったのでしょうか? 岸田 : そうですね、色々選択肢はあったと思うんですが、現実的にサービスを運用するとなると Express と NestJS のどちらかかな、という感じになっていました。 どちらも実開発での経験を持っているメンバーはいなかったんですが、Web 開発で良く使われる Web フレームワークは、基本的な機能やコンセプトについては共通する部分が多いことに加えて、アカデミーも特殊な仕様があるプロダクトではないので、どちらを選んだとしてもメンバーが困ることはないだろうと考えました。 その上で細かい使い勝手などを検討したり GraphQL との親和性の高さや Express という枯れた技術をベースにしていて、 フレームワーク自体の信頼感もあるので NestJS に決めた という感じでした。 熊本 : 絞り込んだ段階で各メンバーが NestJS を試験的に触り、使用感を見てみましたが、大きく問題になる点もなかったので全員一致で決まった感じですね。 岸田 : 前述のように Express をベースにしているので何か困ったら最悪 Express にすれば良いなというのもありました。 牧野 : ここまで実際大きな問題というのは無かったです。 山田 : 今は GraphQL を使って API を作っていると思いますが、REST で作るという選択肢は最初から無かったんですか? 岸田 : はい、GraphQL を使えるメンバーが揃っているという状況であえて REST にするメリットは無いなという事で考えていませんでした。外部公開する API があるとか、パフォーマンスがすごく重要な重い API 呼び出しというのがあったら別だったでしょうけども。 山田 : そもそもの話になってしまうんですが、弊社のプロダクト開発でよく使われている Ruby on Rails を使う選択肢は無かったんでしょうか? 岸田 : 選択肢としてはあるにはあったんですが、プロダクトのフェイズとして現在既に成長をしているサービスであることや、メンバーのスキルセットとして Rails に慣れたメンバーがそこまで多くなかったことを考えると、いかに技術的負債を少なく保ちつつ、機能開発をしていけるかという点が重要になるので、現在の技術構成にしました。 これが既存サービスのリニューアルではなくて、新規で一から全部作っていかないといけないという状況であれば Rails を使うかもしれませんが、やっぱり型があってある程度開発効率が高くなるというのは魅力的だったので、 これからどんどんと機能開発をするには今の構成が良い と判断しました。 (手前)徳永さん・(奥)熊本さん 山田 : DB に関して PostgreSQL ですが、 MySQL などにするということは考えなかったですか? 熊本 : そうですね、リニューアル前のアカデミーが使っていたということがあり、大きく変えるメリットはあまり無かったのが大きいです。 岸田 : あとこれは自分の感覚なのですが、PostgreSQL の方がスマートに処理できることが多いという印象があるというのもありました。 山田 : 元々サービスで使っていたというのは、やっぱり大きいですね。ORM は Prisma ですよね。例えば他に TypeORM などの選択肢もあったと思いますが、こちらはどういった経緯で使うことになりましたか? 熊本 : 色々と触ってみた結果、Prisma が 自分たちの使い方にマッチしてた のが大きかったです。がっつりと SQL を自動生成するなどの手厚い機能は、自分達の使い方では必要もなかったですし。キャッチアップのためにペアプロなどもしましたが、 メンバーも使いやすい ということが確認できました。 山田 : 今ペアプロの話が出ましたが、どのような目的でやっていたんでしょうか。 熊本 : 初期の頃はメンバー間の知識などに偏りもあったので、そういったものを実際にコードを触りながら埋めていく という目的でした。実際に、GraphQL を使ったことがなかったメンバーも早く慣れてくれました。 山田 : なるほど。ある種学習も兼ねていたんですね。最終的に現在このアーキテクチャの使い勝手としては感想としてどうでしょうか? 岸田 : 言葉は違うかもしれないですが「 普通に使い勝手が良い 」という感じです。 もちろんメンバーが習熟してきたので、色々とコード的に改善しようという部分はあったりもしますが、今のところ本当に困った問題というのは無いので、非常に良い選択だったかと思います。 NestJS のテストコードについて 山田 : テストコードは基本書いているんですよね? 熊本 : はい、バックエンドは基本的に Jest で全部書いています。フロントエンドに関しては今まさに E2E テストをどのように実施していくかを試行錯誤しているところですね。 山田 : NestJS でテストコード書くのに何か困ったこととかありましたか? 岸田 : 困っているというわけではないですが、Rails でほぼ必ず使われている Factorybot に相当する機能が欲しいですね…。 山田 : 確かに Node.js だとそういったライブラリを見かけないですね…。 岸田 : 皆さんあんまり困ってないのかな?ということで今は簡易的なものを自作して使っています。 山田 : OSS で作ったら需要があるかもしれませんね。 現状の NestJS での開発の課題感 山田 : ここまで NestJS での開発について聞いてきましたが、プロダクト開発での課題は何かあったりしますか? 岸田 : 先ほども少し話をしたメンバーの習熟度が高くなったことによるコード改善の他には、NestJS というか Node.js が Rails に比べると定番ライブラリが少なめなのがあります。 例えば、 Sidekiq のようなライブラリって便利だと思うんですが、相当するライブラリでデファクトスタンダードというようなものが Node.js だと少ない気がしています。ライブラリが一杯あるのですが、デファクトまでいかないというものが多いという感想です。 山田 : 確かに Node.js だと同じ目的のライブラリが複数あって機能がちょっとずつ違うという感じがありますね。 牧野 : あとは機能開発にリソースを取っているので、細かい部分でのライブラリのアップデートが遅れ気味というのが課題感としてあります。 今のところはとてつもなく遅れているというわけではないのですが、積り積っていくとこういった部分が負債になっていくので、対応したいと思っています。 岸田 : こういったものも含めてプロダクト開発を円滑に回すために、自分も含めた各メンバーのスキルを広げて深くしていかないといけないなというのもありますね。 チームへのオンボーディングについて 山田 : さて、話が変わって現在アカデミーで一番最後にジョインしたのが徳永さんだと思いますが、オンボーディングなどはどのような感じだったんでしょうか。学生時代に NestJS はがっつり触っていたんでしたっけ? 徳永 : いえ、学生時代はチュートリアルをしたくらいで、深く使っていたということはなかったです。ですので、最初は全然プロダクトのコードが分からないというレベルで、ほとんど未経験でした。 山田 : なるほど。ほぼ未経験で新卒研修を経て、アカデミーの開発チームにジョインして困った部分などはありましたか? 徳永 : TypeScript を使っているというのは凄く自分にとって良くて、言語でつまづくということはほとんどありませんでした。 やはり 公式ドキュメントがすごく充実していますし、コミュニティの質の高い情報も多い ので。プロダクトに関しては過去の Pull Request や Issue 上のやり取りなどで、 どういう意図で実装されたものなのかを参照できたのが大きかった です。一方でバックエンドのデザインパターンなどは経験が少なく苦労した部分がありました。その部分はドキュメントで学習したり、周りのメンバーに直接質問しながらキャッチアップしていきました。 山田 : 今年は入社後研修も TypeScript で書く割合が Ruby よりも多かったので、それがもし逆だったら、もっと苦労していたかもしれないですね。 徳永 : そうですね、その可能性は十分にあり得ます。 山田 : あとアカデミーチームは火水木に勉強会を開催していますよね。ジョブメドレーの開発チームでも噂になっています。 岸田 : これは全員で集まる勉強会というわけではなく、 月によって各自テーマを決めて火水木 1 時間ずつスキルアップのために時間を確保し、学んだことなどをアウトプットしていく という形式です。アウトプットしたものは、お互いに確認して知識を深めていこうというものですね。 山田 : それは素敵な取り組みですね。あとは Figma で開発ドキュメントをまとめているのが印象的ですよね。 牧野 : 最初は岸田さんが率先して、開発した部分のドキュメントなんかを書いていたんですが、最近はチームで共有するようなものもほとんど Figma でまとめるようになっています。 熊本 : もちろん永続化しないといけないドキュメントは README なりコンフルエンスなりに書いていくのですが、ちょっとしたものを気軽に Figma にまとめられるのはすごく良いです。 岸田 : 他の人がやっていることなんかも絶対に目につく ので、そういった意味でもドキュメントが散らばらなくて今は良く機能していると思っています。 現状の課題感とアカデミーの開発に向く人 山田 : 最後になりますが、アカデミーに来てほしい人材はどういった方でしょうか? 徳永 : この プロダクトにわくわくできる方に来てほしい と思います。福祉業界に明るい方なんかは大歓迎です! 山田 : なるほど。プロダクトのビジネス面の理解をして楽しんで開発できる方ですね。技術的な部分では何かありますか? 岸田 : 今まで TypeScript と NestJS を中心に話していましたが、実はこれらができる人というのは必須条件ではありません。どの言語でもフレームワークでも ちゃんと Web 開発が分かっている方 であればという感じです。 他には 情報感度が高い人が良い と思います。言語化が難しいのですが、開発環境や言語、ライブラリなどできちんと最新の情報が取捨選択できる方がいると、チームがより活性化すると考えています。 バックエンドでいうとベース部分などもきちんと手を入れることができて、なおかつ人に教えるのが好きな方が入ってもらえると、チームへの知識伝播などもより上手くいくと考えています。 あるとより良いなという所だと、現在アプリは React Native で作られているんですが、React Native でのアプリ開発に強い方だと嬉しいですね。ユーザーに一番使われているのはアプリなので、さらにそのユーザビリティなどを高めるためには、アプリ開発に強い方に入ってもらえるとありがたいです。 山田 : よりアカデミーというプロダクトをドライブさせてくれる方にぜひ来てほしいところですね。本日はありがとうございました! おわりに メドレーの開発チームの中でも野心的なアーキテクチャで開発をしているアカデミーチームについてお伝えしました。 サービスを伸ばすために、適切に言語やフレームワークを選択していき、柔軟性を持って開発していってるんだなというのが伝わってきたインタビューでした。少しでも興味が出た方はぜひ、下のリンクからお話できればと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
こんにちは。医療プラットフォーム本部プロダクト開発室エンジニアの中畑です。主にオンライン診療・服薬指導アプリ CLINICS の開発を担当しています。 今回は CLINICS アプリ内で扱う処方箋プレビューに透かし(watermark)を入れた話を紹介したいと思います。なぜ実施したのか、実装方法、パフォーマンスチューニングの 3 本立てでお送りしたいと思います。 課題と解決方針 まず、なぜ処方箋プレビューに透かしを入れることにしたのか。 CLINICS では診察後に患者が希望すると、かかりつけ薬局支援システム Pharms を導入している調剤薬局にてオンライン服薬指導を受けることができます。その際に医療機関から処方箋の画像ファイルや PDF をアップロードし、患者は CLINICS アプリを通じて、オンライン服薬指導を受けたい調剤薬局に処方箋データを送る必要があります。 患者はオンライン服薬指導を予約する前に医療機関からアップロードされた処方箋画像をプレビューできます。この処方箋プレビューは原本とは扱いが異なるため、患者がアプリ内でそれぞれを明確に区別できるような対応を入れる必要があり、その手段として処方箋プレビューに透かしを入れることにしたというのが理由となります。 実現・実装方法 まずは画像に透かしを入れる方法を考え、それからシステム上でどのタイミングで透かしを入れるか検討しました。前提条件ですが、処方箋プレビュー画像は以下のような形で保存されています。 フォーマット: JPEG, PNG, GIF, PDF(画像以外もある) 保存場所: S3 画像に透かしを入れる方法 透かしは技術的には 2 枚の画像をアルファブレンド 1 を使って合成した画像となります。 アルファブレンドの実現方法は言語やライブラリによっていろいろありますが、今回は処方箋を扱うシステムが Go を利用しているので、Go の以下の標準ライブラリを使って実装しました。また、画像には向き( orientation )の情報が保持されているのですが、向きを意識しないと合成時におかしくなることがあります。端末やツールによって取り扱いが異なる部分が多いので、それを解消してくれる disintegration/imaging パッケージを利用しました。 image パッケージ : ベースライブラリ ximage パッケージ : 描画処理 disintegration/imaging パッケージ : 読み込み時に向きを補正 詳細は省きますが、以下のようなコードで合成できます func Watermark ( srcFile string ) { // 元画像 imgb , _ := os . Open ( srcFile ) var img image . Image img , _ = imaging . Decode ( imgb , imaging . AutoOrientation ( true )) defer imgb . Close () // 上に重ねる透かし画像 wmb , _ := os . Open ( "watermark.png" ) watermark , _ , _ := image . Decode ( wmb ) defer wmb . Close () // 透かしを配置する場所 (画像サイズによって異なるため、実際は計算が必要) watermarkRect := image . Rectangle { image . Point { 100 , 100 }, image . Point { 200 , 300 }} // 合成処理(ここではバイリニア補間で実施) b := img . Bounds () m := image . NewNRGBA ( b ) draw . Draw ( m , b , img , image . ZP , draw . Src ) draw . BiLinear . Scale ( m , watermarkRect , watermark , watermark . Bounds (), draw . Over , nil ) // 合成画像の出力(JPEG 出力。他のエンコーダーを利用すれば他の画像形式も可) imgw , _ := os . Create ( "/tmp/dest.jpg" ) jpeg . Encode ( imgw , m , & jpeg . Options { Quality : jpeg . DefaultQuality }) defer imgw . Close () } PDF に透かしを入れる方法 また、今回の要件には PDF ファイルにも透かしを入れる必要がありました。PDF にはレイヤーという概念があり、元の PDF に対して透かしを入れるレイヤーを追加すれば実現できます。Go には PDF を標準で扱えるパッケージがなかったので、今回は pdfcpu を利用しました。 透かしのレイヤーを追加する関数が用意されており、以下のコードだけで実現できて大変便利です。 2 wm , _ := api . ImageWatermark ( "watermark.jpg" , "sc:.8 rel, rot:0" , onTop , update , pdfcpu . POINTS ) api . AddWatermarksFile ( "src.pdf" , "dest.pdf" , nil , wm , nil ) S3 画像に透かしを入れる方法 AWS を利用していると S3 に各種ファイルを保存することも多いと思います。 基本的には S3 画像を読み込んで透かし処理をすれば実現できますが、AWS には S3 のレスポンスを加工するマネージメントサービスがいくつかあり、今回は S3 Object Lambda を利用しました。 S3 Object Lambda を利用することで S3 からファイルを取得する際に Lambda による処理を実行したうえで返却することができます。これを利用して、元の画像に Lambda で透かしを入れて返却することができます。 処方箋プレビューに透かしを入れる前の構成は以下のようなイメージです。 アプリサーバーにて処方箋プレビューの S3 presigned URL(署名付き URL)を発行 クライアントに返す クライアントは受け取った URL にアクセス。処方箋プレビューが表示される これに S3 Object Lambda を利用して透かしを入れると以下のようになります。 アプリサーバーにて処方箋プレビューの S3 Object Lambda Access Point の presigned URL(署名付き URL)を発行 クライアントは受け取った URL にアクセス S3 Object Lambda Access Point は透かし処理を行う Lambda 関数を実行。その際に S3 Access Point の署名付き URL を発行し、リクエストパラメータに設定 Lambda Function は受け取った URL にアクセスし処方箋プレビューの元画像を取得して透かしを入れて返却 透かし入りの処方箋プレビューが表示される 新たに S3 Object Lambda Access Point , Lambda Function , S3 Access Point を用意する必要があって少しややこしいですが簡単に説明すると以下のような役割のものとなります。 icon title description S3 Access Point S3 Bucket に対する alias。S3 Object Lambda は必ずこれを経由して S3 にアクセスする必要がある Lambda Function S3 のファイルに対してなんらかの処理を実施する関数 S3 Object Lambda Access Point S3 Access Point に対して Lambda 関数を実行するためのアクセスポイント 透かし処理を実際呼び出す際は、アプリケーション側からは S3 の presigned URL から S3 Object Lambda Access Point の presigned URL に切り替えるだけなので、透かしありなしの切り替えも簡単です。 S3 presigned URL: https://[bucket-name].s3.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] ↓ S3 Object Lambda Access Point presigned URL: https://[s3-object-lambda-access-point]-[account-id].s3-object-lambda.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] S3 Object Lambda を利用することで以下のようなメリットがあります。 追加のインフラ構築が不要 既存サーバーのリソース追加や、新たに透かし処理用のサーバーを用意する必要がない。既存のアプリサーバーへの影響を軽微にできる Lambda が自動的にスケールを実施してくれるので負荷対策が容易 利用した分だけの課金となる(S3 Get + Lambda + S3 Object Lambda の利用料) 透かしを入れた処方箋プレビュー画像を事前に用意する必要がない 事前に用意するとリリース前に保持した画像に対しても処理する必要が出てくる (ただし、今回は特定のユーザーのみのアクセスなので不特定多数のアクセスが見込まれる場合は事前生成したほうがよいかもしれません) もちろん画像だけではなく、テキストデータなど他のものにも利用できるので、S3 に保管した機密情報等をフィルタリングして返したい用途などにもマッチします。 AWS 公式ドキュメントに詳細な解説と様々なユースケースのチュートリアルがありますので、興味がある方は是非御覧ください。 S3 Object Lambda を使用したオブジェクトの変換 パフォーマンス・チューニング ここまでで、処方箋プレビューに透かしを入れる処理自体は完成しました。ここで気になってくるのがパフォーマンスについてです。今までは S3 の画像を表示するだけでしたが透かし処理に S3 Object Lambda を使うことになったため、速度と料金が気になるところです。 様々なチューニングポイントがありますが、今回は Lambda のメモリ設定に焦点を絞ってお話したいと思います。 Lambda の CPU パフォーマンス Lambda はメモリ設定しかできませんが、メモリ量に比例して CPU 性能が向上する仕組みとなっています。 公式ドキュメント 上では 1769MB あたり 1vCPU であるとされていて、MAX の 10240MB で 6vCPU 使えるとしています。実際に Lambda の vCPU の設定を調べたブログ ( SENTIA tech blog ) では以下のようになっていたそうです。 Memory vCPUs 128 - 3008 MB 2 3009 - 5307 MB 3 5308 - 7076 MB 4 7077 - 8845 MB 5 8846 - 10240 MB 6 ただし、メモリをいくら増やしたところで関数自体がマルチスレッド・マルチプロセス化されていない場合はパフォーマンス向上が見込めないということにもなります。適切に並列処理が実装されていれば、vCPU が増える境目で大きな性能向上が期待できます。 Lambda の料金 東京リージョン(ap-northeast-1)において、2022 年 10 月現在は以下のような料金となります。 3 無料枠: 1 ヶ月あたり 100 万リクエスト、40 万 GB-秒(仮に 1 GB で動かしたとして 40 万秒使える) 128 MB だと 320 万秒、 10240 MB だと 4 万秒 有料枠: 100 万リクエストあたり 0.20 USD, GB-秒あたり 0.00001666667 USD 1 億リクエストあたり 20 USD 128MB で 1 万秒 使うと 0.02083 USD 1024MB で 1 万秒 使うと 0.16666 USD 上記は x86(amd64) の値段、arm だと 2 割引 arm は CPU パフォーマンスも 2 割向上すると言われているので、可能であれば arm を選択すると更にコスパ向上が期待できる このように見てみると、サーバーやコンテナを利用した場合はどんなに最小構成でも冗長化を考えると数十ドルはかかるので、特に頻繁に実行されない機能は Lambda によるサーバーレス化を検討すると良さそうです。 また、時間あたりで料金がかかるということは、API などネットワークアクセスで時間を使った場合も料金がかかることに注意が必要です。外部 API のパフォーマンスに依存してしまうので、なるべく Lambda は CPU を使った処理が支配的になるとコスパよく利用することができます。 ベンチマーク Lambda のパフォーマンス計測には、 AWS Lambda Power Tuning を利用しました。AWS Lambda Power Tuning は Step Functions と Lambda を使って Lambda の速度とコストを計測できるツールです。 Step Functions と Lambda をデプロイした上で、Step Functions の state machine に計測したい Lambda の ARN, memory 設定, 実行回数, payload を入力することでベンチマーク結果を取得できます。 実行すると最終結果に URL が出力され、その URL をブラウザで表示することで、グラフで結果とスコアボードで結果が可視化されます。また 2 つの結果を比較して表示することもできますので改善後に比較することも容易です。 4 AWS Lambda Power Tuning の導入 リポジトリのドキュメント に従い導入していきます。 今回は AWS Serverless Application Repository(SAR) を使って導入しました。AWS Serverless Application Repository(SAR) とはその名の通り、サーバーレスアプリケーションを管理するリポジトリですが、組織内に限らず公開することも可能で、他の人も再利用可能になっています。また、導入する際は公開ページの Deploy ボタンをポチるだけで自身の AWS アカウント上にデプロイされ、 CloudFormation の Stack として管理されます。削除も CloudFormation の Stack を削除するだけで実施できます。 AWS Lambda Power Tuning は こちらから 自身のアカウントにデプロイできます。 また、AWS や個人だけでなく Datadog や New Relic など他のパブリッシャーもサーバーレスアプリケーションを公開しているので、他にも使いたいものがないか探してみるとよいかもしれません。 AWS Lambda Power Tuning の実行 デプロイが完了すると、AWS Step Functions 上に state machine があるので、state machine にパラメータを入力することで計測ができます。 リポジトリのドキュメント にいくつかやり方が記載されています。ここでは 実行パラメータを定義した json ファイルを用意してスクリプトから実行する方法を紹介します。 まずは、 AWS Lambda Power Tuning リポジトリ を git clone し、 scripts/sample-execution-input.json を編集します。 { "lambdaARN" : "arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-performance-tuning" , "powerValues" : [ 128 , 256 , 512 , 832 , 1024 , 1536 , 1769 , 1770 , 2048 , 3008 ], "num" : 100 , "payload" : { "body" : "{ \" id \" : \" 123 \" , \" name \" : \" performance \" }" , "path" : "/api/performance" , "httpMethod" : "GET" , "isBase64Encoded" : false , "multiValueHeaders" : { "Accept" : [ "application/json" ] } }, "parallelInvocation" : false } 入力値の詳細は以下の通りです。 5 lambdaARN: Lambda Function の ARN powerValues: 計測したい Lambda のメモリを指定 num: メモリごとの Lambda 実行回数 payload: Lambda Function にわたす Event JSON の内容 parallelInvocation: ベンチマークを並列実行する 次に、 scripts/execute.sh を実行すると、ベンチマークが開始され、最後に結果 URL が出力されます。デプロイ時の設定によって、CloudFormation の Stack Name が違う場合もありますので、必要に応じて書き換えてください。 $ sh ./scripts/execute.sh -n Execution started... -n . : -n . SUCCEEDED Execution output: { "power" :128, "cost" :8.4E-9, "duration" :3.6706666666666674, "stateMachine" : { " executionCost ":4.0E-4," lambdaCost ":1.0178708203125003E-4," visualization ":" https://lambda-power-tuning.show/#gAAAAQACQAMABAAG6QbqBgAIwAs = ; NOxqQP9GmkDFII5AbxKBQLErfECGXW1AWDmGQNNNjkBpSo1ARf1nQA = = ; l08QMn1jtDJ9YzQz1pCSM5dPkDNjd9gzb9AbNPzmGzR9YzQ05vRTNA = ="}} 計測方法は以上、簡単ですね 👌 AWS Lambda Power Tuning の結果確認 計測結果を確認します。関数によって傾向は異なりますが、ここでは 2 種類のパターンを紹介します。 DB や API アクセスなど外部処理が支配的なケース(Network-intensive) Lambda 内の処理が外部処理で占めている場合は、 以下のようなグラフ になります。 赤色の実行時間がどのメモリ設定でも 4ms 前後と大きな変化がなく、青色のコストだけが右肩上がりになっています。 この場合は、Lambda の最低メモリである 128MB が最適ということになります。 CPU 実行時間が支配的なケース(CPU-intensive) Lambda 内の処理が内部処理で占めている場合は、 以下のようなグラフ になります。透かし処理は画像合成処理に多くの CPU を利用するので、こちらのパターンとなりました。 メモリが少ないと速度が出ておらず、メモリを増やすに連れて速度も改善しており、コストの増加も緩やかです。ただし、今回はプログラムがマルチスレッド・マルチプロセス対応はしていないため、コア数が増える 1770MB 以降は大きな速度改善は見られず、コストが上がる結果となっています。 よって、このケースではコストだけを考えたら低メモリが有利ですが、速度も考えたら 1024MB ~ 1770MB あたりにすることを検討すると良さそうです。 まとめ 処方箋プレビューに透かしを入れることになった背景、実現方法を紹介いたしました。 透かし処理はアルファブレンド処理を実施することで実現でき、PDF については透かし用のレイヤーを重ねることで実現できます。 S3 Object Lambda を利用することで、追加のサーバー構築をすることなく、S3 のファイルに対してフィルタリングをかけることができます。 Lambda のパフォーマンス・チューニングについては、AWS Lambda Power Tuning を利用して可視化できます。外部 API などネットワーク依存(Network-intensive)ではなく、CPU 処理が中心(CPU-intensive)になると、コスパよく利用することができます。 S3 のファイルに対してなんらかの処理を実施したいと考えてる方は、一度 S3 Object Lambda の利用を検討してみてはいかがでしょうか? また、Lambda のチューニングポイントは色々ありますが、AWS Summit Online で紹介された以下のセッションがおすすめなので、もっと深くチューニングしたい方は是非ご覧ください! AWS Lambda Performance Tuning Deep Dive〜本当に知りたいのは”ここ”だった〜 さいごに メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しております。医療という分野においては、機微な情報を扱ったり診療という大事な業務を止めないよう、可用性、パフォーマンス、セキュリティともに高いサービスレベルを求められます。興味がある方は是非ご連絡ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp Footnotes 元画像に直接文字や図形などを描画することももちろん可能ですが、透かしを画像データとしてデザイン、管理したいこともあり、今回はアルファブレンドを選択した。 アルファブレンド - wikipedia ↩ pdfcpu API document: watermark example ↩ ただし最初の 60 億 GB 秒/月。昨今は円安なのでドル建てで利用しているインフラコストも上がっており、USD 表記だとコストが変わらないようにみえるので要注意。 AWS Lambda 料金 ↩ この UI は AWS Lambda Power Tuning UI というツールで別途提供されており、 lambda-power-tuning.show ドメインでアクセスできるので、自前で用意する必要はない。URL は https://lambda-power-tuning.show/#AAEAAgADAAQ=;xKDwRUqaakU5+RtFcXLqRA==;UqkHOPJCBDjA6AM46DAEOA==;AAEAAgADAAQ=;aYP6RdyeeUUQKiVFuC76RA==;ZDoNOJy3DDiJrQs4zhENOA==;100;50 のような形式でデータが URL のパスパラメータにエンコードされている。 ↩ この他にも strategy など様々なパラメータがある: aws-lambda-power-tuning: README-INPUT-OUTPUT.md ↩
こんにちは。医療プラットフォーム本部プロダクト開発室エンジニアの中畑です。主にオンライン診療・服薬指導アプリ CLINICS の開発を担当しています。 今回は CLINICS アプリ内で扱う処方箋プレビューに透かし(watermark)を入れた話を紹介したいと思います。なぜ実施したのか、実装方法、パフォーマンスチューニングの 3 本立てでお送りしたいと思います。 課題と解決方針 まず、なぜ処方箋プレビューに透かしを入れることにしたのか。 CLINICS では診察後に患者が希望すると、かかりつけ薬局支援システム Pharms を導入している調剤薬局にてオンライン服薬指導を受けることができます。その際に医療機関から処方箋の画像ファイルや PDF をアップロードし、患者は CLINICS アプリを通じて、オンライン服薬指導を受けたい調剤薬局に処方箋データを送る必要があります。 患者はオンライン服薬指導を予約する前に医療機関からアップロードされた処方箋画像をプレビューできます。この処方箋プレビューは原本とは扱いが異なるため、患者がアプリ内でそれぞれを明確に区別できるような対応を入れる必要があり、その手段として処方箋プレビューに透かしを入れることにしたというのが理由となります。 実現・実装方法 まずは画像に透かしを入れる方法を考え、それからシステム上でどのタイミングで透かしを入れるか検討しました。前提条件ですが、処方箋プレビュー画像は以下のような形で保存されています。 フォーマット: JPEG, PNG, GIF, PDF(画像以外もある) 保存場所: S3 画像に透かしを入れる方法 透かしは技術的には 2 枚の画像をアルファブレンド 1 を使って合成した画像となります。 アルファブレンドの実現方法は言語やライブラリによっていろいろありますが、今回は処方箋を扱うシステムが Go を利用しているので、Go の以下の標準ライブラリを使って実装しました。また、画像には向き( orientation )の情報が保持されているのですが、向きを意識しないと合成時におかしくなることがあります。端末やツールによって取り扱いが異なる部分が多いので、それを解消してくれる disintegration/imaging パッケージを利用しました。 image パッケージ : ベースライブラリ ximage パッケージ : 描画処理 disintegration/imaging パッケージ : 読み込み時に向きを補正 詳細は省きますが、以下のようなコードで合成できます func Watermark ( srcFile string ) { // 元画像 imgb , _ := os . Open ( srcFile ) var img image . Image img , _ = imaging . Decode ( imgb , imaging . AutoOrientation ( true )) defer imgb . Close () // 上に重ねる透かし画像 wmb , _ := os . Open ( "watermark.png" ) watermark , _ , _ := image . Decode ( wmb ) defer wmb . Close () // 透かしを配置する場所 (画像サイズによって異なるため、実際は計算が必要) watermarkRect := image . Rectangle { image . Point { 100 , 100 }, image . Point { 200 , 300 }} // 合成処理(ここではバイリニア補間で実施) b := img . Bounds () m := image . NewNRGBA ( b ) draw . Draw ( m , b , img , image . ZP , draw . Src ) draw . BiLinear . Scale ( m , watermarkRect , watermark , watermark . Bounds (), draw . Over , nil ) // 合成画像の出力(JPEG 出力。他のエンコーダーを利用すれば他の画像形式も可) imgw , _ := os . Create ( "/tmp/dest.jpg" ) jpeg . Encode ( imgw , m , & jpeg . Options { Quality : jpeg . DefaultQuality }) defer imgw . Close () } PDF に透かしを入れる方法 また、今回の要件には PDF ファイルにも透かしを入れる必要がありました。PDF にはレイヤーという概念があり、元の PDF に対して透かしを入れるレイヤーを追加すれば実現できます。Go には PDF を標準で扱えるパッケージがなかったので、今回は pdfcpu を利用しました。 透かしのレイヤーを追加する関数が用意されており、以下のコードだけで実現できて大変便利です。 2 wm , _ := api . ImageWatermark ( "watermark.jpg" , "sc:.8 rel, rot:0" , onTop , update , pdfcpu . POINTS ) api . AddWatermarksFile ( "src.pdf" , "dest.pdf" , nil , wm , nil ) S3 画像に透かしを入れる方法 AWS を利用していると S3 に各種ファイルを保存することも多いと思います。 基本的には S3 画像を読み込んで透かし処理をすれば実現できますが、AWS には S3 のレスポンスを加工するマネージメントサービスがいくつかあり、今回は S3 Object Lambda を利用しました。 S3 Object Lambda を利用することで S3 からファイルを取得する際に Lambda による処理を実行したうえで返却することができます。これを利用して、元の画像に Lambda で透かしを入れて返却することができます。 処方箋プレビューに透かしを入れる前の構成は以下のようなイメージです。 アプリサーバーにて処方箋プレビューの S3 presigned URL(署名付き URL)を発行 クライアントに返す クライアントは受け取った URL にアクセス。処方箋プレビューが表示される これに S3 Object Lambda を利用して透かしを入れると以下のようになります。 アプリサーバーにて処方箋プレビューの S3 Object Lambda Access Point の presigned URL(署名付き URL)を発行 クライアントは受け取った URL にアクセス S3 Object Lambda Access Point は透かし処理を行う Lambda 関数を実行。その際に S3 Access Point の署名付き URL を発行し、リクエストパラメータに設定 Lambda Function は受け取った URL にアクセスし処方箋プレビューの元画像を取得して透かしを入れて返却 透かし入りの処方箋プレビューが表示される 新たに S3 Object Lambda Access Point , Lambda Function , S3 Access Point を用意する必要があって少しややこしいですが簡単に説明すると以下のような役割のものとなります。 icon title description S3 Access Point S3 Bucket に対する alias。S3 Object Lambda は必ずこれを経由して S3 にアクセスする必要がある Lambda Function S3 のファイルに対してなんらかの処理を実施する関数 S3 Object Lambda Access Point S3 Access Point に対して Lambda 関数を実行するためのアクセスポイント 透かし処理を実際呼び出す際は、アプリケーション側からは S3 の presigned URL から S3 Object Lambda Access Point の presigned URL に切り替えるだけなので、透かしありなしの切り替えも簡単です。 S3 presigned URL: https://[bucket-name].s3.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] ↓ S3 Object Lambda Access Point presigned URL: https://[s3-object-lambda-access-point]-[account-id].s3-object-lambda.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] S3 Object Lambda を利用することで以下のようなメリットがあります。 追加のインフラ構築が不要 既存サーバーのリソース追加や、新たに透かし処理用のサーバーを用意する必要がない。既存のアプリサーバーへの影響を軽微にできる Lambda が自動的にスケールを実施してくれるので負荷対策が容易 利用した分だけの課金となる(S3 Get + Lambda + S3 Object Lambda の利用料) 透かしを入れた処方箋プレビュー画像を事前に用意する必要がない 事前に用意するとリリース前に保持した画像に対しても処理する必要が出てくる (ただし、今回は特定のユーザーのみのアクセスなので不特定多数のアクセスが見込まれる場合は事前生成したほうがよいかもしれません) もちろん画像だけではなく、テキストデータなど他のものにも利用できるので、S3 に保管した機密情報等をフィルタリングして返したい用途などにもマッチします。 AWS 公式ドキュメントに詳細な解説と様々なユースケースのチュートリアルがありますので、興味がある方は是非御覧ください。 S3 Object Lambda を使用したオブジェクトの変換 パフォーマンス・チューニング ここまでで、処方箋プレビューに透かしを入れる処理自体は完成しました。ここで気になってくるのがパフォーマンスについてです。今までは S3 の画像を表示するだけでしたが透かし処理に S3 Object Lambda を使うことになったため、速度と料金が気になるところです。 様々なチューニングポイントがありますが、今回は Lambda のメモリ設定に焦点を絞ってお話したいと思います。 Lambda の CPU パフォーマンス Lambda はメモリ設定しかできませんが、メモリ量に比例して CPU 性能が向上する仕組みとなっています。 公式ドキュメント 上では 1769MB あたり 1vCPU であるとされていて、MAX の 10240MB で 6vCPU 使えるとしています。実際に Lambda の vCPU の設定を調べたブログ ( SENTIA tech blog ) では以下のようになっていたそうです。 Memory vCPUs 128 - 3008 MB 2 3009 - 5307 MB 3 5308 - 7076 MB 4 7077 - 8845 MB 5 8846 - 10240 MB 6 ただし、メモリをいくら増やしたところで関数自体がマルチスレッド・マルチプロセス化されていない場合はパフォーマンス向上が見込めないということにもなります。適切に並列処理が実装されていれば、vCPU が増える境目で大きな性能向上が期待できます。 Lambda の料金 東京リージョン(ap-northeast-1)において、2022 年 10 月現在は以下のような料金となります。 3 無料枠: 1 ヶ月あたり 100 万リクエスト、40 万 GB-秒(仮に 1 GB で動かしたとして 40 万秒使える) 128 MB だと 320 万秒、 10240 MB だと 4 万秒 有料枠: 100 万リクエストあたり 0.20 USD, GB-秒あたり 0.00001666667 USD 1 億リクエストあたり 20 USD 128MB で 1 万秒 使うと 0.02083 USD 1024MB で 1 万秒 使うと 0.16666 USD 上記は x86(amd64) の値段、arm だと 2 割引 arm は CPU パフォーマンスも 2 割向上すると言われているので、可能であれば arm を選択すると更にコスパ向上が期待できる このように見てみると、サーバーやコンテナを利用した場合はどんなに最小構成でも冗長化を考えると数十ドルはかかるので、特に頻繁に実行されない機能は Lambda によるサーバーレス化を検討すると良さそうです。 また、時間あたりで料金がかかるということは、API などネットワークアクセスで時間を使った場合も料金がかかることに注意が必要です。外部 API のパフォーマンスに依存してしまうので、なるべく Lambda は CPU を使った処理が支配的になるとコスパよく利用することができます。 ベンチマーク Lambda のパフォーマンス計測には、 AWS Lambda Power Tuning を利用しました。AWS Lambda Power Tuning は Step Functions と Lambda を使って Lambda の速度とコストを計測できるツールです。 Step Functions と Lambda をデプロイした上で、Step Functions の state machine に計測したい Lambda の ARN, memory 設定, 実行回数, payload を入力することでベンチマーク結果を取得できます。 実行すると最終結果に URL が出力され、その URL をブラウザで表示することで、グラフで結果とスコアボードで結果が可視化されます。また 2 つの結果を比較して表示することもできますので改善後に比較することも容易です。 4 AWS Lambda Power Tuning の導入 リポジトリのドキュメント に従い導入していきます。 今回は AWS Serverless Application Repository(SAR) を使って導入しました。AWS Serverless Application Repository(SAR) とはその名の通り、サーバーレスアプリケーションを管理するリポジトリですが、組織内に限らず公開することも可能で、他の人も再利用可能になっています。また、導入する際は公開ページの Deploy ボタンをポチるだけで自身の AWS アカウント上にデプロイされ、 CloudFormation の Stack として管理されます。削除も CloudFormation の Stack を削除するだけで実施できます。 AWS Lambda Power Tuning は こちらから 自身のアカウントにデプロイできます。 また、AWS や個人だけでなく Datadog や New Relic など他のパブリッシャーもサーバーレスアプリケーションを公開しているので、他にも使いたいものがないか探してみるとよいかもしれません。 AWS Lambda Power Tuning の実行 デプロイが完了すると、AWS Step Functions 上に state machine があるので、state machine にパラメータを入力することで計測ができます。 リポジトリのドキュメント にいくつかやり方が記載されています。ここでは 実行パラメータを定義した json ファイルを用意してスクリプトから実行する方法を紹介します。 まずは、 AWS Lambda Power Tuning リポジトリ を git clone し、 scripts/sample-execution-input.json を編集します。 { "lambdaARN" : "arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-performance-tuning" , "powerValues" : [ 128 , 256 , 512 , 832 , 1024 , 1536 , 1769 , 1770 , 2048 , 3008 ], "num" : 100 , "payload" : { "body" : "{ \" id \" : \" 123 \" , \" name \" : \" performance \" }" , "path" : "/api/performance" , "httpMethod" : "GET" , "isBase64Encoded" : false , "multiValueHeaders" : { "Accept" : [ "application/json" ] } }, "parallelInvocation" : false } 入力値の詳細は以下の通りです。 5 lambdaARN: Lambda Function の ARN powerValues: 計測したい Lambda のメモリを指定 num: メモリごとの Lambda 実行回数 payload: Lambda Function にわたす Event JSON の内容 parallelInvocation: ベンチマークを並列実行する 次に、 scripts/execute.sh を実行すると、ベンチマークが開始され、最後に結果 URL が出力されます。デプロイ時の設定によって、CloudFormation の Stack Name が違う場合もありますので、必要に応じて書き換えてください。 $ sh ./scripts/execute.sh -n Execution started... -n . : -n . SUCCEEDED Execution output: { "power" :128, "cost" :8.4E-9, "duration" :3.6706666666666674, "stateMachine" : { " executionCost ":4.0E-4," lambdaCost ":1.0178708203125003E-4," visualization ":" https://lambda-power-tuning.show/#gAAAAQACQAMABAAG6QbqBgAIwAs = ; NOxqQP9GmkDFII5AbxKBQLErfECGXW1AWDmGQNNNjkBpSo1ARf1nQA = = ; l08QMn1jtDJ9YzQz1pCSM5dPkDNjd9gzb9AbNPzmGzR9YzQ05vRTNA = ="}} 計測方法は以上、簡単ですね 👌 AWS Lambda Power Tuning の結果確認 計測結果を確認します。関数によって傾向は異なりますが、ここでは 2 種類のパターンを紹介します。 DB や API アクセスなど外部処理が支配的なケース(Network-intensive) Lambda 内の処理が外部処理で占めている場合は、 以下のようなグラフ になります。 赤色の実行時間がどのメモリ設定でも 4ms 前後と大きな変化がなく、青色のコストだけが右肩上がりになっています。 この場合は、Lambda の最低メモリである 128MB が最適ということになります。 CPU 実行時間が支配的なケース(CPU-intensive) Lambda 内の処理が内部処理で占めている場合は、 以下のようなグラフ になります。透かし処理は画像合成処理に多くの CPU を利用するので、こちらのパターンとなりました。 メモリが少ないと速度が出ておらず、メモリを増やすに連れて速度も改善しており、コストの増加も緩やかです。ただし、今回はプログラムがマルチスレッド・マルチプロセス対応はしていないため、コア数が増える 1770MB 以降は大きな速度改善は見られず、コストが上がる結果となっています。 よって、このケースではコストだけを考えたら低メモリが有利ですが、速度も考えたら 1024MB ~ 1770MB あたりにすることを検討すると良さそうです。 まとめ 処方箋プレビューに透かしを入れることになった背景、実現方法を紹介いたしました。 透かし処理はアルファブレンド処理を実施することで実現でき、PDF については透かし用のレイヤーを重ねることで実現できます。 S3 Object Lambda を利用することで、追加のサーバー構築をすることなく、S3 のファイルに対してフィルタリングをかけることができます。 Lambda のパフォーマンス・チューニングについては、AWS Lambda Power Tuning を利用して可視化できます。外部 API などネットワーク依存(Network-intensive)ではなく、CPU 処理が中心(CPU-intensive)になると、コスパよく利用することができます。 S3 のファイルに対してなんらかの処理を実施したいと考えてる方は、一度 S3 Object Lambda の利用を検討してみてはいかがでしょうか? また、Lambda のチューニングポイントは色々ありますが、AWS Summit Online で紹介された以下のセッションがおすすめなので、もっと深くチューニングしたい方は是非ご覧ください! AWS Lambda Performance Tuning Deep Dive〜本当に知りたいのは”ここ”だった〜 さいごに メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しております。医療という分野においては、機微な情報を扱ったり診療という大事な業務を止めないよう、可用性、パフォーマンス、セキュリティともに高いサービスレベルを求められます。興味がある方は是非ご連絡ください。 https://www.medley.jp/jobs/ Footnotes 元画像に直接文字や図形などを描画することももちろん可能ですが、透かしを画像データとしてデザイン、管理したいこともあり、今回はアルファブレンドを選択した。 アルファブレンド - wikipedia ↩ pdfcpu API document: watermark example ↩ ただし最初の 60 億 GB 秒/月。昨今は円安なのでドル建てで利用しているインフラコストも上がっており、USD 表記だとコストが変わらないようにみえるので要注意。 AWS Lambda 料金 ↩ この UI は AWS Lambda Power Tuning UI というツールで別途提供されており、 lambda-power-tuning.show ドメインでアクセスできるので、自前で用意する必要はない。URL は https://lambda-power-tuning.show/#AAEAAgADAAQ=;xKDwRUqaakU5+RtFcXLqRA==;UqkHOPJCBDjA6AM46DAEOA==;AAEAAgADAAQ=;aYP6RdyeeUUQKiVFuC76RA==;ZDoNOJy3DDiJrQs4zhENOA==;100;50 のような形式でデータが URL のパスパラメータにエンコードされている。 ↩ この他にも strategy など様々なパラメータがある: aws-lambda-power-tuning: README-INPUT-OUTPUT.md ↩
こんにちは。医療プラットフォーム本部プロダクト開発室エンジニアの中畑です。主にオンライン診療・服薬指導アプリ CLINICS の開発を担当しています。 今回は CLINICS アプリ内で扱う処方箋プレビューに透かし(watermark)を入れた話を紹介したいと思います。なぜ実施したのか、実装方法、パフォーマンスチューニングの 3 本立てでお送りしたいと思います。 課題と解決方針 まず、なぜ処方箋プレビューに透かしを入れることにしたのか。 CLINICS では診察後に患者が希望すると、かかりつけ薬局支援システム Pharms を導入している調剤薬局にてオンライン服薬指導を受けることができます。その際に医療機関から処方箋の画像ファイルや PDF をアップロードし、患者は CLINICS アプリを通じて、オンライン服薬指導を受けたい調剤薬局に処方箋データを送る必要があります。 患者はオンライン服薬指導を予約する前に医療機関からアップロードされた処方箋画像をプレビューできます。この処方箋プレビューは原本とは扱いが異なるため、患者がアプリ内でそれぞれを明確に区別できるような対応を入れる必要があり、その手段として処方箋プレビューに透かしを入れることにしたというのが理由となります。 実現・実装方法 まずは画像に透かしを入れる方法を考え、それからシステム上でどのタイミングで透かしを入れるか検討しました。前提条件ですが、処方箋プレビュー画像は以下のような形で保存されています。 フォーマット: JPEG, PNG, GIF, PDF(画像以外もある) 保存場所: S3 画像に透かしを入れる方法 透かしは技術的には 2 枚の画像をアルファブレンド 1 を使って合成した画像となります。 アルファブレンドの実現方法は言語やライブラリによっていろいろありますが、今回は処方箋を扱うシステムが Go を利用しているので、Go の以下の標準ライブラリを使って実装しました。また、画像には向き( orientation )の情報が保持されているのですが、向きを意識しないと合成時におかしくなることがあります。端末やツールによって取り扱いが異なる部分が多いので、それを解消してくれる disintegration/imaging パッケージを利用しました。 image パッケージ : ベースライブラリ ximage パッケージ : 描画処理 disintegration/imaging パッケージ : 読み込み時に向きを補正 詳細は省きますが、以下のようなコードで合成できます func Watermark ( srcFile string ) { // 元画像 imgb , _ := os . Open ( srcFile ) var img image . Image img , _ = imaging . Decode ( imgb , imaging . AutoOrientation ( true )) defer imgb . Close () // 上に重ねる透かし画像 wmb , _ := os . Open ( "watermark.png" ) watermark , _ , _ := image . Decode ( wmb ) defer wmb . Close () // 透かしを配置する場所 (画像サイズによって異なるため、実際は計算が必要) watermarkRect := image . Rectangle { image . Point { 100 , 100 }, image . Point { 200 , 300 }} // 合成処理(ここではバイリニア補間で実施) b := img . Bounds () m := image . NewNRGBA ( b ) draw . Draw ( m , b , img , image . ZP , draw . Src ) draw . BiLinear . Scale ( m , watermarkRect , watermark , watermark . Bounds (), draw . Over , nil ) // 合成画像の出力(JPEG 出力。他のエンコーダーを利用すれば他の画像形式も可) imgw , _ := os . Create ( "/tmp/dest.jpg" ) jpeg . Encode ( imgw , m , & jpeg . Options { Quality : jpeg . DefaultQuality }) defer imgw . Close () } PDF に透かしを入れる方法 また、今回の要件には PDF ファイルにも透かしを入れる必要がありました。PDF にはレイヤーという概念があり、元の PDF に対して透かしを入れるレイヤーを追加すれば実現できます。Go には PDF を標準で扱えるパッケージがなかったので、今回は pdfcpu を利用しました。 透かしのレイヤーを追加する関数が用意されており、以下のコードだけで実現できて大変便利です。 2 wm , _ := api . ImageWatermark ( "watermark.jpg" , "sc:.8 rel, rot:0" , onTop , update , pdfcpu . POINTS ) api . AddWatermarksFile ( "src.pdf" , "dest.pdf" , nil , wm , nil ) S3 画像に透かしを入れる方法 AWS を利用していると S3 に各種ファイルを保存することも多いと思います。 基本的には S3 画像を読み込んで透かし処理をすれば実現できますが、AWS には S3 のレスポンスを加工するマネージメントサービスがいくつかあり、今回は S3 Object Lambda を利用しました。 S3 Object Lambda を利用することで S3 からファイルを取得する際に Lambda による処理を実行したうえで返却することができます。これを利用して、元の画像に Lambda で透かしを入れて返却することができます。 処方箋プレビューに透かしを入れる前の構成は以下のようなイメージです。 アプリサーバーにて処方箋プレビューの S3 presigned URL(署名付き URL)を発行 クライアントに返す クライアントは受け取った URL にアクセス。処方箋プレビューが表示される これに S3 Object Lambda を利用して透かしを入れると以下のようになります。 アプリサーバーにて処方箋プレビューの S3 Object Lambda Access Point の presigned URL(署名付き URL)を発行 クライアントは受け取った URL にアクセス S3 Object Lambda Access Point は透かし処理を行う Lambda 関数を実行。その際に S3 Access Point の署名付き URL を発行し、リクエストパラメータに設定 Lambda Function は受け取った URL にアクセスし処方箋プレビューの元画像を取得して透かしを入れて返却 透かし入りの処方箋プレビューが表示される 新たに S3 Object Lambda Access Point , Lambda Function , S3 Access Point を用意する必要があって少しややこしいですが簡単に説明すると以下のような役割のものとなります。 icon title description S3 Access Point S3 Bucket に対する alias。S3 Object Lambda は必ずこれを経由して S3 にアクセスする必要がある Lambda Function S3 のファイルに対してなんらかの処理を実施する関数 S3 Object Lambda Access Point S3 Access Point に対して Lambda 関数を実行するためのアクセスポイント 透かし処理を実際呼び出す際は、アプリケーション側からは S3 の presigned URL から S3 Object Lambda Access Point の presigned URL に切り替えるだけなので、透かしありなしの切り替えも簡単です。 S3 presigned URL: https://[bucket-name].s3.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] ↓ S3 Object Lambda Access Point presigned URL: https://[s3-object-lambda-access-point]-[account-id].s3-object-lambda.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] S3 Object Lambda を利用することで以下のようなメリットがあります。 追加のインフラ構築が不要 既存サーバーのリソース追加や、新たに透かし処理用のサーバーを用意する必要がない。既存のアプリサーバーへの影響を軽微にできる Lambda が自動的にスケールを実施してくれるので負荷対策が容易 利用した分だけの課金となる(S3 Get + Lambda + S3 Object Lambda の利用料) 透かしを入れた処方箋プレビュー画像を事前に用意する必要がない 事前に用意するとリリース前に保持した画像に対しても処理する必要が出てくる (ただし、今回は特定のユーザーのみのアクセスなので不特定多数のアクセスが見込まれる場合は事前生成したほうがよいかもしれません) もちろん画像だけではなく、テキストデータなど他のものにも利用できるので、S3 に保管した機密情報等をフィルタリングして返したい用途などにもマッチします。 AWS 公式ドキュメントに詳細な解説と様々なユースケースのチュートリアルがありますので、興味がある方は是非御覧ください。 S3 Object Lambda を使用したオブジェクトの変換 パフォーマンス・チューニング ここまでで、処方箋プレビューに透かしを入れる処理自体は完成しました。ここで気になってくるのがパフォーマンスについてです。今までは S3 の画像を表示するだけでしたが透かし処理に S3 Object Lambda を使うことになったため、速度と料金が気になるところです。 様々なチューニングポイントがありますが、今回は Lambda のメモリ設定に焦点を絞ってお話したいと思います。 Lambda の CPU パフォーマンス Lambda はメモリ設定しかできませんが、メモリ量に比例して CPU 性能が向上する仕組みとなっています。 公式ドキュメント 上では 1769MB あたり 1vCPU であるとされていて、MAX の 10240MB で 6vCPU 使えるとしています。実際に Lambda の vCPU の設定を調べたブログ ( SENTIA tech blog ) では以下のようになっていたそうです。 Memory vCPUs 128 - 3008 MB 2 3009 - 5307 MB 3 5308 - 7076 MB 4 7077 - 8845 MB 5 8846 - 10240 MB 6 ただし、メモリをいくら増やしたところで関数自体がマルチスレッド・マルチプロセス化されていない場合はパフォーマンス向上が見込めないということにもなります。適切に並列処理が実装されていれば、vCPU が増える境目で大きな性能向上が期待できます。 Lambda の料金 東京リージョン(ap-northeast-1)において、2022 年 10 月現在は以下のような料金となります。 3 無料枠: 1 ヶ月あたり 100 万リクエスト、40 万 GB-秒(仮に 1 GB で動かしたとして 40 万秒使える) 128 MB だと 320 万秒、 10240 MB だと 4 万秒 有料枠: 100 万リクエストあたり 0.20 USD, GB-秒あたり 0.00001666667 USD 1 億リクエストあたり 20 USD 128MB で 1 万秒 使うと 0.02083 USD 1024MB で 1 万秒 使うと 0.16666 USD 上記は x86(amd64) の値段、arm だと 2 割引 arm は CPU パフォーマンスも 2 割向上すると言われているので、可能であれば arm を選択すると更にコスパ向上が期待できる このように見てみると、サーバーやコンテナを利用した場合はどんなに最小構成でも冗長化を考えると数十ドルはかかるので、特に頻繁に実行されない機能は Lambda によるサーバーレス化を検討すると良さそうです。 また、時間あたりで料金がかかるということは、API などネットワークアクセスで時間を使った場合も料金がかかることに注意が必要です。外部 API のパフォーマンスに依存してしまうので、なるべく Lambda は CPU を使った処理が支配的になるとコスパよく利用することができます。 ベンチマーク Lambda のパフォーマンス計測には、 AWS Lambda Power Tuning を利用しました。AWS Lambda Power Tuning は Step Functions と Lambda を使って Lambda の速度とコストを計測できるツールです。 Step Functions と Lambda をデプロイした上で、Step Functions の state machine に計測したい Lambda の ARN, memory 設定, 実行回数, payload を入力することでベンチマーク結果を取得できます。 実行すると最終結果に URL が出力され、その URL をブラウザで表示することで、グラフで結果とスコアボードで結果が可視化されます。また 2 つの結果を比較して表示することもできますので改善後に比較することも容易です。 4 AWS Lambda Power Tuning の導入 リポジトリのドキュメント に従い導入していきます。 今回は AWS Serverless Application Repository(SAR) を使って導入しました。AWS Serverless Application Repository(SAR) とはその名の通り、サーバーレスアプリケーションを管理するリポジトリですが、組織内に限らず公開することも可能で、他の人も再利用可能になっています。また、導入する際は公開ページの Deploy ボタンをポチるだけで自身の AWS アカウント上にデプロイされ、 CloudFormation の Stack として管理されます。削除も CloudFormation の Stack を削除するだけで実施できます。 AWS Lambda Power Tuning は こちらから 自身のアカウントにデプロイできます。 また、AWS や個人だけでなく Datadog や New Relic など他のパブリッシャーもサーバーレスアプリケーションを公開しているので、他にも使いたいものがないか探してみるとよいかもしれません。 AWS Lambda Power Tuning の実行 デプロイが完了すると、AWS Step Functions 上に state machine があるので、state machine にパラメータを入力することで計測ができます。 リポジトリのドキュメント にいくつかやり方が記載されています。ここでは 実行パラメータを定義した json ファイルを用意してスクリプトから実行する方法を紹介します。 まずは、 AWS Lambda Power Tuning リポジトリ を git clone し、 scripts/sample-execution-input.json を編集します。 { "lambdaARN" : "arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-performance-tuning" , "powerValues" : [ 128 , 256 , 512 , 832 , 1024 , 1536 , 1769 , 1770 , 2048 , 3008 ], "num" : 100 , "payload" : { "body" : "{ \" id \" : \" 123 \" , \" name \" : \" performance \" }" , "path" : "/api/performance" , "httpMethod" : "GET" , "isBase64Encoded" : false , "multiValueHeaders" : { "Accept" : [ "application/json" ] } }, "parallelInvocation" : false } 入力値の詳細は以下の通りです。 5 lambdaARN: Lambda Function の ARN powerValues: 計測したい Lambda のメモリを指定 num: メモリごとの Lambda 実行回数 payload: Lambda Function にわたす Event JSON の内容 parallelInvocation: ベンチマークを並列実行する 次に、 scripts/execute.sh を実行すると、ベンチマークが開始され、最後に結果 URL が出力されます。デプロイ時の設定によって、CloudFormation の Stack Name が違う場合もありますので、必要に応じて書き換えてください。 $ sh ./scripts/execute.sh -n Execution started... -n . : -n . SUCCEEDED Execution output: { "power" :128, "cost" :8.4E-9, "duration" :3.6706666666666674, "stateMachine" : { " executionCost ":4.0E-4," lambdaCost ":1.0178708203125003E-4," visualization ":" https://lambda-power-tuning.show/#gAAAAQACQAMABAAG6QbqBgAIwAs = ; NOxqQP9GmkDFII5AbxKBQLErfECGXW1AWDmGQNNNjkBpSo1ARf1nQA = = ; l08QMn1jtDJ9YzQz1pCSM5dPkDNjd9gzb9AbNPzmGzR9YzQ05vRTNA = ="}} 計測方法は以上、簡単ですね 👌 AWS Lambda Power Tuning の結果確認 計測結果を確認します。関数によって傾向は異なりますが、ここでは 2 種類のパターンを紹介します。 DB や API アクセスなど外部処理が支配的なケース(Network-intensive) Lambda 内の処理が外部処理で占めている場合は、 以下のようなグラフ になります。 赤色の実行時間がどのメモリ設定でも 4ms 前後と大きな変化がなく、青色のコストだけが右肩上がりになっています。 この場合は、Lambda の最低メモリである 128MB が最適ということになります。 CPU 実行時間が支配的なケース(CPU-intensive) Lambda 内の処理が内部処理で占めている場合は、 以下のようなグラフ になります。透かし処理は画像合成処理に多くの CPU を利用するので、こちらのパターンとなりました。 メモリが少ないと速度が出ておらず、メモリを増やすに連れて速度も改善しており、コストの増加も緩やかです。ただし、今回はプログラムがマルチスレッド・マルチプロセス対応はしていないため、コア数が増える 1770MB 以降は大きな速度改善は見られず、コストが上がる結果となっています。 よって、このケースではコストだけを考えたら低メモリが有利ですが、速度も考えたら 1024MB ~ 1770MB あたりにすることを検討すると良さそうです。 まとめ 処方箋プレビューに透かしを入れることになった背景、実現方法を紹介いたしました。 透かし処理はアルファブレンド処理を実施することで実現でき、PDF については透かし用のレイヤーを重ねることで実現できます。 S3 Object Lambda を利用することで、追加のサーバー構築をすることなく、S3 のファイルに対してフィルタリングをかけることができます。 Lambda のパフォーマンス・チューニングについては、AWS Lambda Power Tuning を利用して可視化できます。外部 API などネットワーク依存(Network-intensive)ではなく、CPU 処理が中心(CPU-intensive)になると、コスパよく利用することができます。 S3 のファイルに対してなんらかの処理を実施したいと考えてる方は、一度 S3 Object Lambda の利用を検討してみてはいかがでしょうか? また、Lambda のチューニングポイントは色々ありますが、AWS Summit Online で紹介された以下のセッションがおすすめなので、もっと深くチューニングしたい方は是非ご覧ください! AWS Lambda Performance Tuning Deep Dive〜本当に知りたいのは”ここ”だった〜 さいごに メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しております。医療という分野においては、機微な情報を扱ったり診療という大事な業務を止めないよう、可用性、パフォーマンス、セキュリティともに高いサービスレベルを求められます。興味がある方は是非ご連絡ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp Footnotes 元画像に直接文字や図形などを描画することももちろん可能ですが、透かしを画像データとしてデザイン、管理したいこともあり、今回はアルファブレンドを選択した。 アルファブレンド - wikipedia ↩ pdfcpu API document: watermark example ↩ ただし最初の 60 億 GB 秒/月。昨今は円安なのでドル建てで利用しているインフラコストも上がっており、USD 表記だとコストが変わらないようにみえるので要注意。 AWS Lambda 料金 ↩ この UI は AWS Lambda Power Tuning UI というツールで別途提供されており、 lambda-power-tuning.show ドメインでアクセスできるので、自前で用意する必要はない。URL は https://lambda-power-tuning.show/#AAEAAgADAAQ=;xKDwRUqaakU5+RtFcXLqRA==;UqkHOPJCBDjA6AM46DAEOA==;AAEAAgADAAQ=;aYP6RdyeeUUQKiVFuC76RA==;ZDoNOJy3DDiJrQs4zhENOA==;100;50 のような形式でデータが URL のパスパラメータにエンコードされている。 ↩ この他にも strategy など様々なパラメータがある: aws-lambda-power-tuning: README-INPUT-OUTPUT.md ↩
こんにちは。医療プラットフォーム本部プロダクト開発室エンジニアの中畑です。主にオンライン診療・服薬指導アプリ CLINICS の開発を担当しています。 今回は CLINICS アプリ内で扱う処方箋プレビューに透かし(watermark)を入れた話を紹介したいと思います。なぜ実施したのか、実装方法、パフォーマンスチューニングの 3 本立てでお送りしたいと思います。 課題と解決方針 まず、なぜ処方箋プレビューに透かしを入れることにしたのか。 CLINICS では診察後に患者が希望すると、かかりつけ薬局支援システム Pharms を導入している調剤薬局にてオンライン服薬指導を受けることができます。その際に医療機関から処方箋の画像ファイルや PDF をアップロードし、患者は CLINICS アプリを通じて、オンライン服薬指導を受けたい調剤薬局に処方箋データを送る必要があります。 患者はオンライン服薬指導を予約する前に医療機関からアップロードされた処方箋画像をプレビューできます。この処方箋プレビューは原本とは扱いが異なるため、患者がアプリ内でそれぞれを明確に区別できるような対応を入れる必要があり、その手段として処方箋プレビューに透かしを入れることにしたというのが理由となります。 実現・実装方法 まずは画像に透かしを入れる方法を考え、それからシステム上でどのタイミングで透かしを入れるか検討しました。前提条件ですが、処方箋プレビュー画像は以下のような形で保存されています。 フォーマット: JPEG, PNG, GIF, PDF(画像以外もある) 保存場所: S3 画像に透かしを入れる方法 透かしは技術的には 2 枚の画像をアルファブレンド 1 を使って合成した画像となります。 アルファブレンドの実現方法は言語やライブラリによっていろいろありますが、今回は処方箋を扱うシステムが Go を利用しているので、Go の以下の標準ライブラリを使って実装しました。また、画像には向き( orientation )の情報が保持されているのですが、向きを意識しないと合成時におかしくなることがあります。端末やツールによって取り扱いが異なる部分が多いので、それを解消してくれる disintegration/imaging パッケージを利用しました。 image パッケージ : ベースライブラリ ximage パッケージ : 描画処理 disintegration/imaging パッケージ : 読み込み時に向きを補正 詳細は省きますが、以下のようなコードで合成できます func Watermark ( srcFile string ) { // 元画像 imgb , _ := os . Open ( srcFile ) var img image . Image img , _ = imaging . Decode ( imgb , imaging . AutoOrientation ( true )) defer imgb . Close () // 上に重ねる透かし画像 wmb , _ := os . Open ( "watermark.png" ) watermark , _ , _ := image . Decode ( wmb ) defer wmb . Close () // 透かしを配置する場所 (画像サイズによって異なるため、実際は計算が必要) watermarkRect := image . Rectangle { image . Point { 100 , 100 }, image . Point { 200 , 300 }} // 合成処理(ここではバイリニア補間で実施) b := img . Bounds () m := image . NewNRGBA ( b ) draw . Draw ( m , b , img , image . ZP , draw . Src ) draw . BiLinear . Scale ( m , watermarkRect , watermark , watermark . Bounds (), draw . Over , nil ) // 合成画像の出力(JPEG 出力。他のエンコーダーを利用すれば他の画像形式も可) imgw , _ := os . Create ( "/tmp/dest.jpg" ) jpeg . Encode ( imgw , m , & jpeg . Options { Quality : jpeg . DefaultQuality }) defer imgw . Close () } PDF に透かしを入れる方法 また、今回の要件には PDF ファイルにも透かしを入れる必要がありました。PDF にはレイヤーという概念があり、元の PDF に対して透かしを入れるレイヤーを追加すれば実現できます。Go には PDF を標準で扱えるパッケージがなかったので、今回は pdfcpu を利用しました。 透かしのレイヤーを追加する関数が用意されており、以下のコードだけで実現できて大変便利です。 2 wm , _ := api . ImageWatermark ( "watermark.jpg" , "sc:.8 rel, rot:0" , onTop , update , pdfcpu . POINTS ) api . AddWatermarksFile ( "src.pdf" , "dest.pdf" , nil , wm , nil ) S3 画像に透かしを入れる方法 AWS を利用していると S3 に各種ファイルを保存することも多いと思います。 基本的には S3 画像を読み込んで透かし処理をすれば実現できますが、AWS には S3 のレスポンスを加工するマネージメントサービスがいくつかあり、今回は S3 Object Lambda を利用しました。 S3 Object Lambda を利用することで S3 からファイルを取得する際に Lambda による処理を実行したうえで返却することができます。これを利用して、元の画像に Lambda で透かしを入れて返却することができます。 処方箋プレビューに透かしを入れる前の構成は以下のようなイメージです。 アプリサーバーにて処方箋プレビューの S3 presigned URL(署名付き URL)を発行 クライアントに返す クライアントは受け取った URL にアクセス。処方箋プレビューが表示される これに S3 Object Lambda を利用して透かしを入れると以下のようになります。 アプリサーバーにて処方箋プレビューの S3 Object Lambda Access Point の presigned URL(署名付き URL)を発行 クライアントは受け取った URL にアクセス S3 Object Lambda Access Point は透かし処理を行う Lambda 関数を実行。その際に S3 Access Point の署名付き URL を発行し、リクエストパラメータに設定 Lambda Function は受け取った URL にアクセスし処方箋プレビューの元画像を取得して透かしを入れて返却 透かし入りの処方箋プレビューが表示される 新たに S3 Object Lambda Access Point , Lambda Function , S3 Access Point を用意する必要があって少しややこしいですが簡単に説明すると以下のような役割のものとなります。 icon title description S3 Access Point S3 Bucket に対する alias。S3 Object Lambda は必ずこれを経由して S3 にアクセスする必要がある Lambda Function S3 のファイルに対してなんらかの処理を実施する関数 S3 Object Lambda Access Point S3 Access Point に対して Lambda 関数を実行するためのアクセスポイント 透かし処理を実際呼び出す際は、アプリケーション側からは S3 の presigned URL から S3 Object Lambda Access Point の presigned URL に切り替えるだけなので、透かしありなしの切り替えも簡単です。 S3 presigned URL: https://[bucket-name].s3.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] ↓ S3 Object Lambda Access Point presigned URL: https://[s3-object-lambda-access-point]-[account-id].s3-object-lambda.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] S3 Object Lambda を利用することで以下のようなメリットがあります。 追加のインフラ構築が不要 既存サーバーのリソース追加や、新たに透かし処理用のサーバーを用意する必要がない。既存のアプリサーバーへの影響を軽微にできる Lambda が自動的にスケールを実施してくれるので負荷対策が容易 利用した分だけの課金となる(S3 Get + Lambda + S3 Object Lambda の利用料) 透かしを入れた処方箋プレビュー画像を事前に用意する必要がない 事前に用意するとリリース前に保持した画像に対しても処理する必要が出てくる (ただし、今回は特定のユーザーのみのアクセスなので不特定多数のアクセスが見込まれる場合は事前生成したほうがよいかもしれません) もちろん画像だけではなく、テキストデータなど他のものにも利用できるので、S3 に保管した機密情報等をフィルタリングして返したい用途などにもマッチします。 AWS 公式ドキュメントに詳細な解説と様々なユースケースのチュートリアルがありますので、興味がある方は是非御覧ください。 S3 Object Lambda を使用したオブジェクトの変換 パフォーマンス・チューニング ここまでで、処方箋プレビューに透かしを入れる処理自体は完成しました。ここで気になってくるのがパフォーマンスについてです。今までは S3 の画像を表示するだけでしたが透かし処理に S3 Object Lambda を使うことになったため、速度と料金が気になるところです。 様々なチューニングポイントがありますが、今回は Lambda のメモリ設定に焦点を絞ってお話したいと思います。 Lambda の CPU パフォーマンス Lambda はメモリ設定しかできませんが、メモリ量に比例して CPU 性能が向上する仕組みとなっています。 公式ドキュメント 上では 1769MB あたり 1vCPU であるとされていて、MAX の 10240MB で 6vCPU 使えるとしています。実際に Lambda の vCPU の設定を調べたブログ ( SENTIA tech blog ) では以下のようになっていたそうです。 Memory vCPUs 128 - 3008 MB 2 3009 - 5307 MB 3 5308 - 7076 MB 4 7077 - 8845 MB 5 8846 - 10240 MB 6 ただし、メモリをいくら増やしたところで関数自体がマルチスレッド・マルチプロセス化されていない場合はパフォーマンス向上が見込めないということにもなります。適切に並列処理が実装されていれば、vCPU が増える境目で大きな性能向上が期待できます。 Lambda の料金 東京リージョン(ap-northeast-1)において、2022 年 10 月現在は以下のような料金となります。 3 無料枠: 1 ヶ月あたり 100 万リクエスト、40 万 GB-秒(仮に 1 GB で動かしたとして 40 万秒使える) 128 MB だと 320 万秒、 10240 MB だと 4 万秒 有料枠: 100 万リクエストあたり 0.20 USD, GB-秒あたり 0.00001666667 USD 1 億リクエストあたり 20 USD 128MB で 1 万秒 使うと 0.02083 USD 1024MB で 1 万秒 使うと 0.16666 USD 上記は x86(amd64) の値段、arm だと 2 割引 arm は CPU パフォーマンスも 2 割向上すると言われているので、可能であれば arm を選択すると更にコスパ向上が期待できる このように見てみると、サーバーやコンテナを利用した場合はどんなに最小構成でも冗長化を考えると数十ドルはかかるので、特に頻繁に実行されない機能は Lambda によるサーバーレス化を検討すると良さそうです。 また、時間あたりで料金がかかるということは、API などネットワークアクセスで時間を使った場合も料金がかかることに注意が必要です。外部 API のパフォーマンスに依存してしまうので、なるべく Lambda は CPU を使った処理が支配的になるとコスパよく利用することができます。 ベンチマーク Lambda のパフォーマンス計測には、 AWS Lambda Power Tuning を利用しました。AWS Lambda Power Tuning は Step Functions と Lambda を使って Lambda の速度とコストを計測できるツールです。 Step Functions と Lambda をデプロイした上で、Step Functions の state machine に計測したい Lambda の ARN, memory 設定, 実行回数, payload を入力することでベンチマーク結果を取得できます。 実行すると最終結果に URL が出力され、その URL をブラウザで表示することで、グラフで結果とスコアボードで結果が可視化されます。また 2 つの結果を比較して表示することもできますので改善後に比較することも容易です。 4 AWS Lambda Power Tuning の導入 リポジトリのドキュメント に従い導入していきます。 今回は AWS Serverless Application Repository(SAR) を使って導入しました。AWS Serverless Application Repository(SAR) とはその名の通り、サーバーレスアプリケーションを管理するリポジトリですが、組織内に限らず公開することも可能で、他の人も再利用可能になっています。また、導入する際は公開ページの Deploy ボタンをポチるだけで自身の AWS アカウント上にデプロイされ、 CloudFormation の Stack として管理されます。削除も CloudFormation の Stack を削除するだけで実施できます。 AWS Lambda Power Tuning は こちらから 自身のアカウントにデプロイできます。 また、AWS や個人だけでなく Datadog や New Relic など他のパブリッシャーもサーバーレスアプリケーションを公開しているので、他にも使いたいものがないか探してみるとよいかもしれません。 AWS Lambda Power Tuning の実行 デプロイが完了すると、AWS Step Functions 上に state machine があるので、state machine にパラメータを入力することで計測ができます。 リポジトリのドキュメント にいくつかやり方が記載されています。ここでは 実行パラメータを定義した json ファイルを用意してスクリプトから実行する方法を紹介します。 まずは、 AWS Lambda Power Tuning リポジトリ を git clone し、 scripts/sample-execution-input.json を編集します。 { "lambdaARN" : "arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-performance-tuning" , "powerValues" : [ 128 , 256 , 512 , 832 , 1024 , 1536 , 1769 , 1770 , 2048 , 3008 ], "num" : 100 , "payload" : { "body" : "{ \" id \" : \" 123 \" , \" name \" : \" performance \" }" , "path" : "/api/performance" , "httpMethod" : "GET" , "isBase64Encoded" : false , "multiValueHeaders" : { "Accept" : [ "application/json" ] } }, "parallelInvocation" : false } 入力値の詳細は以下の通りです。 5 lambdaARN: Lambda Function の ARN powerValues: 計測したい Lambda のメモリを指定 num: メモリごとの Lambda 実行回数 payload: Lambda Function にわたす Event JSON の内容 parallelInvocation: ベンチマークを並列実行する 次に、 scripts/execute.sh を実行すると、ベンチマークが開始され、最後に結果 URL が出力されます。デプロイ時の設定によって、CloudFormation の Stack Name が違う場合もありますので、必要に応じて書き換えてください。 $ sh ./scripts/execute.sh -n Execution started... -n . : -n . SUCCEEDED Execution output: { "power" :128, "cost" :8.4E-9, "duration" :3.6706666666666674, "stateMachine" : { " executionCost ":4.0E-4," lambdaCost ":1.0178708203125003E-4," visualization ":" https://lambda-power-tuning.show/#gAAAAQACQAMABAAG6QbqBgAIwAs = ; NOxqQP9GmkDFII5AbxKBQLErfECGXW1AWDmGQNNNjkBpSo1ARf1nQA = = ; l08QMn1jtDJ9YzQz1pCSM5dPkDNjd9gzb9AbNPzmGzR9YzQ05vRTNA = ="}} 計測方法は以上、簡単ですね 👌 AWS Lambda Power Tuning の結果確認 計測結果を確認します。関数によって傾向は異なりますが、ここでは 2 種類のパターンを紹介します。 DB や API アクセスなど外部処理が支配的なケース(Network-intensive) Lambda 内の処理が外部処理で占めている場合は、 以下のようなグラフ になります。 赤色の実行時間がどのメモリ設定でも 4ms 前後と大きな変化がなく、青色のコストだけが右肩上がりになっています。 この場合は、Lambda の最低メモリである 128MB が最適ということになります。 CPU 実行時間が支配的なケース(CPU-intensive) Lambda 内の処理が内部処理で占めている場合は、 以下のようなグラフ になります。透かし処理は画像合成処理に多くの CPU を利用するので、こちらのパターンとなりました。 メモリが少ないと速度が出ておらず、メモリを増やすに連れて速度も改善しており、コストの増加も緩やかです。ただし、今回はプログラムがマルチスレッド・マルチプロセス対応はしていないため、コア数が増える 1770MB 以降は大きな速度改善は見られず、コストが上がる結果となっています。 よって、このケースではコストだけを考えたら低メモリが有利ですが、速度も考えたら 1024MB ~ 1770MB あたりにすることを検討すると良さそうです。 まとめ 処方箋プレビューに透かしを入れることになった背景、実現方法を紹介いたしました。 透かし処理はアルファブレンド処理を実施することで実現でき、PDF については透かし用のレイヤーを重ねることで実現できます。 S3 Object Lambda を利用することで、追加のサーバー構築をすることなく、S3 のファイルに対してフィルタリングをかけることができます。 Lambda のパフォーマンス・チューニングについては、AWS Lambda Power Tuning を利用して可視化できます。外部 API などネットワーク依存(Network-intensive)ではなく、CPU 処理が中心(CPU-intensive)になると、コスパよく利用することができます。 S3 のファイルに対してなんらかの処理を実施したいと考えてる方は、一度 S3 Object Lambda の利用を検討してみてはいかがでしょうか? また、Lambda のチューニングポイントは色々ありますが、AWS Summit Online で紹介された以下のセッションがおすすめなので、もっと深くチューニングしたい方は是非ご覧ください! AWS Lambda Performance Tuning Deep Dive〜本当に知りたいのは”ここ”だった〜 さいごに メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しております。医療という分野においては、機微な情報を扱ったり診療という大事な業務を止めないよう、可用性、パフォーマンス、セキュリティともに高いサービスレベルを求められます。興味がある方は是非ご連絡ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp Footnotes 元画像に直接文字や図形などを描画することももちろん可能ですが、透かしを画像データとしてデザイン、管理したいこともあり、今回はアルファブレンドを選択した。 アルファブレンド - wikipedia ↩ pdfcpu API document: watermark example ↩ ただし最初の 60 億 GB 秒/月。昨今は円安なのでドル建てで利用しているインフラコストも上がっており、USD 表記だとコストが変わらないようにみえるので要注意。 AWS Lambda 料金 ↩ この UI は AWS Lambda Power Tuning UI というツールで別途提供されており、 lambda-power-tuning.show ドメインでアクセスできるので、自前で用意する必要はない。URL は https://lambda-power-tuning.show/#AAEAAgADAAQ=;xKDwRUqaakU5+RtFcXLqRA==;UqkHOPJCBDjA6AM46DAEOA==;AAEAAgADAAQ=;aYP6RdyeeUUQKiVFuC76RA==;ZDoNOJy3DDiJrQs4zhENOA==;100;50 のような形式でデータが URL のパスパラメータにエンコードされている。 ↩ この他にも strategy など様々なパラメータがある: aws-lambda-power-tuning: README-INPUT-OUTPUT.md ↩
こんにちは。医療プラットフォーム本部プロダクト開発室エンジニアの中畑です。主にオンライン診療・服薬指導アプリ CLINICS の開発を担当しています。 今回は CLINICS アプリ内で扱う処方箋プレビューに透かし(watermark)を入れた話を紹介したいと思います。なぜ実施したのか、実装方法、パフォーマンスチューニングの 3 本立てでお送りしたいと思います。 課題と解決方針 まず、なぜ処方箋プレビューに透かしを入れることにしたのか。 CLINICS では診察後に患者が希望すると、かかりつけ薬局支援システム Pharms を導入している調剤薬局にてオンライン服薬指導を受けることができます。その際に医療機関から処方箋の画像ファイルや PDF をアップロードし、患者は CLINICS アプリを通じて、オンライン服薬指導を受けたい調剤薬局に処方箋データを送る必要があります。 患者はオンライン服薬指導を予約する前に医療機関からアップロードされた処方箋画像をプレビューできます。この処方箋プレビューは原本とは扱いが異なるため、患者がアプリ内でそれぞれを明確に区別できるような対応を入れる必要があり、その手段として処方箋プレビューに透かしを入れることにしたというのが理由となります。 実現・実装方法 まずは画像に透かしを入れる方法を考え、それからシステム上でどのタイミングで透かしを入れるか検討しました。前提条件ですが、処方箋プレビュー画像は以下のような形で保存されています。 フォーマット: JPEG, PNG, GIF, PDF(画像以外もある) 保存場所: S3 画像に透かしを入れる方法 透かしは技術的には 2 枚の画像をアルファブレンド 1 を使って合成した画像となります。 アルファブレンドの実現方法は言語やライブラリによっていろいろありますが、今回は処方箋を扱うシステムが Go を利用しているので、Go の以下の標準ライブラリを使って実装しました。また、画像には向き( orientation )の情報が保持されているのですが、向きを意識しないと合成時におかしくなることがあります。端末やツールによって取り扱いが異なる部分が多いので、それを解消してくれる disintegration/imaging パッケージを利用しました。 image パッケージ : ベースライブラリ ximage パッケージ : 描画処理 disintegration/imaging パッケージ : 読み込み時に向きを補正 詳細は省きますが、以下のようなコードで合成できます func Watermark ( srcFile string ) { // 元画像 imgb , _ := os . Open ( srcFile ) var img image . Image img , _ = imaging . Decode ( imgb , imaging . AutoOrientation ( true )) defer imgb . Close () // 上に重ねる透かし画像 wmb , _ := os . Open ( "watermark.png" ) watermark , _ , _ := image . Decode ( wmb ) defer wmb . Close () // 透かしを配置する場所 (画像サイズによって異なるため、実際は計算が必要) watermarkRect := image . Rectangle { image . Point { 100 , 100 }, image . Point { 200 , 300 }} // 合成処理(ここではバイリニア補間で実施) b := img . Bounds () m := image . NewNRGBA ( b ) draw . Draw ( m , b , img , image . ZP , draw . Src ) draw . BiLinear . Scale ( m , watermarkRect , watermark , watermark . Bounds (), draw . Over , nil ) // 合成画像の出力(JPEG 出力。他のエンコーダーを利用すれば他の画像形式も可) imgw , _ := os . Create ( "/tmp/dest.jpg" ) jpeg . Encode ( imgw , m , & jpeg . Options { Quality : jpeg . DefaultQuality }) defer imgw . Close () } PDF に透かしを入れる方法 また、今回の要件には PDF ファイルにも透かしを入れる必要がありました。PDF にはレイヤーという概念があり、元の PDF に対して透かしを入れるレイヤーを追加すれば実現できます。Go には PDF を標準で扱えるパッケージがなかったので、今回は pdfcpu を利用しました。 透かしのレイヤーを追加する関数が用意されており、以下のコードだけで実現できて大変便利です。 2 wm , _ := api . ImageWatermark ( "watermark.jpg" , "sc:.8 rel, rot:0" , onTop , update , pdfcpu . POINTS ) api . AddWatermarksFile ( "src.pdf" , "dest.pdf" , nil , wm , nil ) S3 画像に透かしを入れる方法 AWS を利用していると S3 に各種ファイルを保存することも多いと思います。 基本的には S3 画像を読み込んで透かし処理をすれば実現できますが、AWS には S3 のレスポンスを加工するマネージメントサービスがいくつかあり、今回は S3 Object Lambda を利用しました。 S3 Object Lambda を利用することで S3 からファイルを取得する際に Lambda による処理を実行したうえで返却することができます。これを利用して、元の画像に Lambda で透かしを入れて返却することができます。 処方箋プレビューに透かしを入れる前の構成は以下のようなイメージです。 アプリサーバーにて処方箋プレビューの S3 presigned URL(署名付き URL)を発行 クライアントに返す クライアントは受け取った URL にアクセス。処方箋プレビューが表示される これに S3 Object Lambda を利用して透かしを入れると以下のようになります。 アプリサーバーにて処方箋プレビューの S3 Object Lambda Access Point の presigned URL(署名付き URL)を発行 クライアントは受け取った URL にアクセス S3 Object Lambda Access Point は透かし処理を行う Lambda 関数を実行。その際に S3 Access Point の署名付き URL を発行し、リクエストパラメータに設定 Lambda Function は受け取った URL にアクセスし処方箋プレビューの元画像を取得して透かしを入れて返却 透かし入りの処方箋プレビューが表示される 新たに S3 Object Lambda Access Point , Lambda Function , S3 Access Point を用意する必要があって少しややこしいですが簡単に説明すると以下のような役割のものとなります。 icon title description S3 Access Point S3 Bucket に対する alias。S3 Object Lambda は必ずこれを経由して S3 にアクセスする必要がある Lambda Function S3 のファイルに対してなんらかの処理を実施する関数 S3 Object Lambda Access Point S3 Access Point に対して Lambda 関数を実行するためのアクセスポイント 透かし処理を実際呼び出す際は、アプリケーション側からは S3 の presigned URL から S3 Object Lambda Access Point の presigned URL に切り替えるだけなので、透かしありなしの切り替えも簡単です。 S3 presigned URL: https://[bucket-name].s3.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] ↓ S3 Object Lambda Access Point presigned URL: https://[s3-object-lambda-access-point]-[account-id].s3-object-lambda.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] S3 Object Lambda を利用することで以下のようなメリットがあります。 追加のインフラ構築が不要 既存サーバーのリソース追加や、新たに透かし処理用のサーバーを用意する必要がない。既存のアプリサーバーへの影響を軽微にできる Lambda が自動的にスケールを実施してくれるので負荷対策が容易 利用した分だけの課金となる(S3 Get + Lambda + S3 Object Lambda の利用料) 透かしを入れた処方箋プレビュー画像を事前に用意する必要がない 事前に用意するとリリース前に保持した画像に対しても処理する必要が出てくる (ただし、今回は特定のユーザーのみのアクセスなので不特定多数のアクセスが見込まれる場合は事前生成したほうがよいかもしれません) もちろん画像だけではなく、テキストデータなど他のものにも利用できるので、S3 に保管した機密情報等をフィルタリングして返したい用途などにもマッチします。 AWS 公式ドキュメントに詳細な解説と様々なユースケースのチュートリアルがありますので、興味がある方は是非御覧ください。 S3 Object Lambda を使用したオブジェクトの変換 パフォーマンス・チューニング ここまでで、処方箋プレビューに透かしを入れる処理自体は完成しました。ここで気になってくるのがパフォーマンスについてです。今までは S3 の画像を表示するだけでしたが透かし処理に S3 Object Lambda を使うことになったため、速度と料金が気になるところです。 様々なチューニングポイントがありますが、今回は Lambda のメモリ設定に焦点を絞ってお話したいと思います。 Lambda の CPU パフォーマンス Lambda はメモリ設定しかできませんが、メモリ量に比例して CPU 性能が向上する仕組みとなっています。 公式ドキュメント 上では 1769MB あたり 1vCPU であるとされていて、MAX の 10240MB で 6vCPU 使えるとしています。実際に Lambda の vCPU の設定を調べたブログ ( SENTIA tech blog ) では以下のようになっていたそうです。 Memory vCPUs 128 - 3008 MB 2 3009 - 5307 MB 3 5308 - 7076 MB 4 7077 - 8845 MB 5 8846 - 10240 MB 6 ただし、メモリをいくら増やしたところで関数自体がマルチスレッド・マルチプロセス化されていない場合はパフォーマンス向上が見込めないということにもなります。適切に並列処理が実装されていれば、vCPU が増える境目で大きな性能向上が期待できます。 Lambda の料金 東京リージョン(ap-northeast-1)において、2022 年 10 月現在は以下のような料金となります。 3 無料枠: 1 ヶ月あたり 100 万リクエスト、40 万 GB-秒(仮に 1 GB で動かしたとして 40 万秒使える) 128 MB だと 320 万秒、 10240 MB だと 4 万秒 有料枠: 100 万リクエストあたり 0.20 USD, GB-秒あたり 0.00001666667 USD 1 億リクエストあたり 20 USD 128MB で 1 万秒 使うと 0.02083 USD 1024MB で 1 万秒 使うと 0.16666 USD 上記は x86(amd64) の値段、arm だと 2 割引 arm は CPU パフォーマンスも 2 割向上すると言われているので、可能であれば arm を選択すると更にコスパ向上が期待できる このように見てみると、サーバーやコンテナを利用した場合はどんなに最小構成でも冗長化を考えると数十ドルはかかるので、特に頻繁に実行されない機能は Lambda によるサーバーレス化を検討すると良さそうです。 また、時間あたりで料金がかかるということは、API などネットワークアクセスで時間を使った場合も料金がかかることに注意が必要です。外部 API のパフォーマンスに依存してしまうので、なるべく Lambda は CPU を使った処理が支配的になるとコスパよく利用することができます。 ベンチマーク Lambda のパフォーマンス計測には、 AWS Lambda Power Tuning を利用しました。AWS Lambda Power Tuning は Step Functions と Lambda を使って Lambda の速度とコストを計測できるツールです。 Step Functions と Lambda をデプロイした上で、Step Functions の state machine に計測したい Lambda の ARN, memory 設定, 実行回数, payload を入力することでベンチマーク結果を取得できます。 実行すると最終結果に URL が出力され、その URL をブラウザで表示することで、グラフで結果とスコアボードで結果が可視化されます。また 2 つの結果を比較して表示することもできますので改善後に比較することも容易です。 4 AWS Lambda Power Tuning の導入 リポジトリのドキュメント に従い導入していきます。 今回は AWS Serverless Application Repository(SAR) を使って導入しました。AWS Serverless Application Repository(SAR) とはその名の通り、サーバーレスアプリケーションを管理するリポジトリですが、組織内に限らず公開することも可能で、他の人も再利用可能になっています。また、導入する際は公開ページの Deploy ボタンをポチるだけで自身の AWS アカウント上にデプロイされ、 CloudFormation の Stack として管理されます。削除も CloudFormation の Stack を削除するだけで実施できます。 AWS Lambda Power Tuning は こちらから 自身のアカウントにデプロイできます。 また、AWS や個人だけでなく Datadog や New Relic など他のパブリッシャーもサーバーレスアプリケーションを公開しているので、他にも使いたいものがないか探してみるとよいかもしれません。 AWS Lambda Power Tuning の実行 デプロイが完了すると、AWS Step Functions 上に state machine があるので、state machine にパラメータを入力することで計測ができます。 リポジトリのドキュメント にいくつかやり方が記載されています。ここでは 実行パラメータを定義した json ファイルを用意してスクリプトから実行する方法を紹介します。 まずは、 AWS Lambda Power Tuning リポジトリ を git clone し、 scripts/sample-execution-input.json を編集します。 { "lambdaARN" : "arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-performance-tuning" , "powerValues" : [ 128 , 256 , 512 , 832 , 1024 , 1536 , 1769 , 1770 , 2048 , 3008 ], "num" : 100 , "payload" : { "body" : "{ \" id \" : \" 123 \" , \" name \" : \" performance \" }" , "path" : "/api/performance" , "httpMethod" : "GET" , "isBase64Encoded" : false , "multiValueHeaders" : { "Accept" : [ "application/json" ] } }, "parallelInvocation" : false } 入力値の詳細は以下の通りです。 5 lambdaARN: Lambda Function の ARN powerValues: 計測したい Lambda のメモリを指定 num: メモリごとの Lambda 実行回数 payload: Lambda Function にわたす Event JSON の内容 parallelInvocation: ベンチマークを並列実行する 次に、 scripts/execute.sh を実行すると、ベンチマークが開始され、最後に結果 URL が出力されます。デプロイ時の設定によって、CloudFormation の Stack Name が違う場合もありますので、必要に応じて書き換えてください。 $ sh ./scripts/execute.sh -n Execution started... -n . : -n . SUCCEEDED Execution output: { "power" :128, "cost" :8.4E-9, "duration" :3.6706666666666674, "stateMachine" : { " executionCost ":4.0E-4," lambdaCost ":1.0178708203125003E-4," visualization ":" https://lambda-power-tuning.show/#gAAAAQACQAMABAAG6QbqBgAIwAs = ; NOxqQP9GmkDFII5AbxKBQLErfECGXW1AWDmGQNNNjkBpSo1ARf1nQA = = ; l08QMn1jtDJ9YzQz1pCSM5dPkDNjd9gzb9AbNPzmGzR9YzQ05vRTNA = ="}} 計測方法は以上、簡単ですね 👌 AWS Lambda Power Tuning の結果確認 計測結果を確認します。関数によって傾向は異なりますが、ここでは 2 種類のパターンを紹介します。 DB や API アクセスなど外部処理が支配的なケース(Network-intensive) Lambda 内の処理が外部処理で占めている場合は、 以下のようなグラフ になります。 赤色の実行時間がどのメモリ設定でも 4ms 前後と大きな変化がなく、青色のコストだけが右肩上がりになっています。 この場合は、Lambda の最低メモリである 128MB が最適ということになります。 CPU 実行時間が支配的なケース(CPU-intensive) Lambda 内の処理が内部処理で占めている場合は、 以下のようなグラフ になります。透かし処理は画像合成処理に多くの CPU を利用するので、こちらのパターンとなりました。 メモリが少ないと速度が出ておらず、メモリを増やすに連れて速度も改善しており、コストの増加も緩やかです。ただし、今回はプログラムがマルチスレッド・マルチプロセス対応はしていないため、コア数が増える 1770MB 以降は大きな速度改善は見られず、コストが上がる結果となっています。 よって、このケースではコストだけを考えたら低メモリが有利ですが、速度も考えたら 1024MB ~ 1770MB あたりにすることを検討すると良さそうです。 まとめ 処方箋プレビューに透かしを入れることになった背景、実現方法を紹介いたしました。 透かし処理はアルファブレンド処理を実施することで実現でき、PDF については透かし用のレイヤーを重ねることで実現できます。 S3 Object Lambda を利用することで、追加のサーバー構築をすることなく、S3 のファイルに対してフィルタリングをかけることができます。 Lambda のパフォーマンス・チューニングについては、AWS Lambda Power Tuning を利用して可視化できます。外部 API などネットワーク依存(Network-intensive)ではなく、CPU 処理が中心(CPU-intensive)になると、コスパよく利用することができます。 S3 のファイルに対してなんらかの処理を実施したいと考えてる方は、一度 S3 Object Lambda の利用を検討してみてはいかがでしょうか? また、Lambda のチューニングポイントは色々ありますが、AWS Summit Online で紹介された以下のセッションがおすすめなので、もっと深くチューニングしたい方は是非ご覧ください! AWS Lambda Performance Tuning Deep Dive〜本当に知りたいのは”ここ”だった〜 さいごに メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しております。医療という分野においては、機微な情報を扱ったり診療という大事な業務を止めないよう、可用性、パフォーマンス、セキュリティともに高いサービスレベルを求められます。興味がある方は是非ご連絡ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp Footnotes 元画像に直接文字や図形などを描画することももちろん可能ですが、透かしを画像データとしてデザイン、管理したいこともあり、今回はアルファブレンドを選択した。 アルファブレンド - wikipedia ↩ pdfcpu API document: watermark example ↩ ただし最初の 60 億 GB 秒/月。昨今は円安なのでドル建てで利用しているインフラコストも上がっており、USD 表記だとコストが変わらないようにみえるので要注意。 AWS Lambda 料金 ↩ この UI は AWS Lambda Power Tuning UI というツールで別途提供されており、 lambda-power-tuning.show ドメインでアクセスできるので、自前で用意する必要はない。URL は https://lambda-power-tuning.show/#AAEAAgADAAQ=;xKDwRUqaakU5+RtFcXLqRA==;UqkHOPJCBDjA6AM46DAEOA==;AAEAAgADAAQ=;aYP6RdyeeUUQKiVFuC76RA==;ZDoNOJy3DDiJrQs4zhENOA==;100;50 のような形式でデータが URL のパスパラメータにエンコードされている。 ↩ この他にも strategy など様々なパラメータがある: aws-lambda-power-tuning: README-INPUT-OUTPUT.md ↩
こんにちは。医療プラットフォーム本部プロダクト開発室エンジニアの中畑です。主にオンライン診療・服薬指導アプリ CLINICS の開発を担当しています。 今回は CLINICS アプリ内で扱う処方箋プレビューに透かし(watermark)を入れた話を紹介したいと思います。なぜ実施したのか、実装方法、パフォーマンスチューニングの 3 本立てでお送りしたいと思います。 課題と解決方針 まず、なぜ処方箋プレビューに透かしを入れることにしたのか。 CLINICS では診察後に患者が希望すると、かかりつけ薬局支援システム Pharms を導入している調剤薬局にてオンライン服薬指導を受けることができます。その際に医療機関から処方箋の画像ファイルや PDF をアップロードし、患者は CLINICS アプリを通じて、オンライン服薬指導を受けたい調剤薬局に処方箋データを送る必要があります。 患者はオンライン服薬指導を予約する前に医療機関からアップロードされた処方箋画像をプレビューできます。この処方箋プレビューは原本とは扱いが異なるため、患者がアプリ内でそれぞれを明確に区別できるような対応を入れる必要があり、その手段として処方箋プレビューに透かしを入れることにしたというのが理由となります。 実現・実装方法 まずは画像に透かしを入れる方法を考え、それからシステム上でどのタイミングで透かしを入れるか検討しました。前提条件ですが、処方箋プレビュー画像は以下のような形で保存されています。 フォーマット: JPEG, PNG, GIF, PDF(画像以外もある) 保存場所: S3 画像に透かしを入れる方法 透かしは技術的には 2 枚の画像をアルファブレンド 1 を使って合成した画像となります。 アルファブレンドの実現方法は言語やライブラリによっていろいろありますが、今回は処方箋を扱うシステムが Go を利用しているので、Go の以下の標準ライブラリを使って実装しました。また、画像には向き( orientation )の情報が保持されているのですが、向きを意識しないと合成時におかしくなることがあります。端末やツールによって取り扱いが異なる部分が多いので、それを解消してくれる disintegration/imaging パッケージを利用しました。 image パッケージ : ベースライブラリ ximage パッケージ : 描画処理 disintegration/imaging パッケージ : 読み込み時に向きを補正 詳細は省きますが、以下のようなコードで合成できます func Watermark ( srcFile string ) { // 元画像 imgb , _ := os . Open ( srcFile ) var img image . Image img , _ = imaging . Decode ( imgb , imaging . AutoOrientation ( true )) defer imgb . Close () // 上に重ねる透かし画像 wmb , _ := os . Open ( "watermark.png" ) watermark , _ , _ := image . Decode ( wmb ) defer wmb . Close () // 透かしを配置する場所 (画像サイズによって異なるため、実際は計算が必要) watermarkRect := image . Rectangle { image . Point { 100 , 100 }, image . Point { 200 , 300 }} // 合成処理(ここではバイリニア補間で実施) b := img . Bounds () m := image . NewNRGBA ( b ) draw . Draw ( m , b , img , image . ZP , draw . Src ) draw . BiLinear . Scale ( m , watermarkRect , watermark , watermark . Bounds (), draw . Over , nil ) // 合成画像の出力(JPEG 出力。他のエンコーダーを利用すれば他の画像形式も可) imgw , _ := os . Create ( "/tmp/dest.jpg" ) jpeg . Encode ( imgw , m , & jpeg . Options { Quality : jpeg . DefaultQuality }) defer imgw . Close () } PDF に透かしを入れる方法 また、今回の要件には PDF ファイルにも透かしを入れる必要がありました。PDF にはレイヤーという概念があり、元の PDF に対して透かしを入れるレイヤーを追加すれば実現できます。Go には PDF を標準で扱えるパッケージがなかったので、今回は pdfcpu を利用しました。 透かしのレイヤーを追加する関数が用意されており、以下のコードだけで実現できて大変便利です。 2 wm , _ := api . ImageWatermark ( "watermark.jpg" , "sc:.8 rel, rot:0" , onTop , update , pdfcpu . POINTS ) api . AddWatermarksFile ( "src.pdf" , "dest.pdf" , nil , wm , nil ) S3 画像に透かしを入れる方法 AWS を利用していると S3 に各種ファイルを保存することも多いと思います。 基本的には S3 画像を読み込んで透かし処理をすれば実現できますが、AWS には S3 のレスポンスを加工するマネージメントサービスがいくつかあり、今回は S3 Object Lambda を利用しました。 S3 Object Lambda を利用することで S3 からファイルを取得する際に Lambda による処理を実行したうえで返却することができます。これを利用して、元の画像に Lambda で透かしを入れて返却することができます。 処方箋プレビューに透かしを入れる前の構成は以下のようなイメージです。 アプリサーバーにて処方箋プレビューの S3 presigned URL(署名付き URL)を発行 クライアントに返す クライアントは受け取った URL にアクセス。処方箋プレビューが表示される これに S3 Object Lambda を利用して透かしを入れると以下のようになります。 アプリサーバーにて処方箋プレビューの S3 Object Lambda Access Point の presigned URL(署名付き URL)を発行 クライアントは受け取った URL にアクセス S3 Object Lambda Access Point は透かし処理を行う Lambda 関数を実行。その際に S3 Access Point の署名付き URL を発行し、リクエストパラメータに設定 Lambda Function は受け取った URL にアクセスし処方箋プレビューの元画像を取得して透かしを入れて返却 透かし入りの処方箋プレビューが表示される 新たに S3 Object Lambda Access Point , Lambda Function , S3 Access Point を用意する必要があって少しややこしいですが簡単に説明すると以下のような役割のものとなります。 icon title description S3 Access Point S3 Bucket に対する alias。S3 Object Lambda は必ずこれを経由して S3 にアクセスする必要がある Lambda Function S3 のファイルに対してなんらかの処理を実施する関数 S3 Object Lambda Access Point S3 Access Point に対して Lambda 関数を実行するためのアクセスポイント 透かし処理を実際呼び出す際は、アプリケーション側からは S3 の presigned URL から S3 Object Lambda Access Point の presigned URL に切り替えるだけなので、透かしありなしの切り替えも簡単です。 S3 presigned URL: https://[bucket-name].s3.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] ↓ S3 Object Lambda Access Point presigned URL: https://[s3-object-lambda-access-point]-[account-id].s3-object-lambda.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] S3 Object Lambda を利用することで以下のようなメリットがあります。 追加のインフラ構築が不要 既存サーバーのリソース追加や、新たに透かし処理用のサーバーを用意する必要がない。既存のアプリサーバーへの影響を軽微にできる Lambda が自動的にスケールを実施してくれるので負荷対策が容易 利用した分だけの課金となる(S3 Get + Lambda + S3 Object Lambda の利用料) 透かしを入れた処方箋プレビュー画像を事前に用意する必要がない 事前に用意するとリリース前に保持した画像に対しても処理する必要が出てくる (ただし、今回は特定のユーザーのみのアクセスなので不特定多数のアクセスが見込まれる場合は事前生成したほうがよいかもしれません) もちろん画像だけではなく、テキストデータなど他のものにも利用できるので、S3 に保管した機密情報等をフィルタリングして返したい用途などにもマッチします。 AWS 公式ドキュメントに詳細な解説と様々なユースケースのチュートリアルがありますので、興味がある方は是非御覧ください。 S3 Object Lambda を使用したオブジェクトの変換 パフォーマンス・チューニング ここまでで、処方箋プレビューに透かしを入れる処理自体は完成しました。ここで気になってくるのがパフォーマンスについてです。今までは S3 の画像を表示するだけでしたが透かし処理に S3 Object Lambda を使うことになったため、速度と料金が気になるところです。 様々なチューニングポイントがありますが、今回は Lambda のメモリ設定に焦点を絞ってお話したいと思います。 Lambda の CPU パフォーマンス Lambda はメモリ設定しかできませんが、メモリ量に比例して CPU 性能が向上する仕組みとなっています。 公式ドキュメント 上では 1769MB あたり 1vCPU であるとされていて、MAX の 10240MB で 6vCPU 使えるとしています。実際に Lambda の vCPU の設定を調べたブログ ( SENTIA tech blog ) では以下のようになっていたそうです。 Memory vCPUs 128 - 3008 MB 2 3009 - 5307 MB 3 5308 - 7076 MB 4 7077 - 8845 MB 5 8846 - 10240 MB 6 ただし、メモリをいくら増やしたところで関数自体がマルチスレッド・マルチプロセス化されていない場合はパフォーマンス向上が見込めないということにもなります。適切に並列処理が実装されていれば、vCPU が増える境目で大きな性能向上が期待できます。 Lambda の料金 東京リージョン(ap-northeast-1)において、2022 年 10 月現在は以下のような料金となります。 3 無料枠: 1 ヶ月あたり 100 万リクエスト、40 万 GB-秒(仮に 1 GB で動かしたとして 40 万秒使える) 128 MB だと 320 万秒、 10240 MB だと 4 万秒 有料枠: 100 万リクエストあたり 0.20 USD, GB-秒あたり 0.00001666667 USD 1 億リクエストあたり 20 USD 128MB で 1 万秒 使うと 0.02083 USD 1024MB で 1 万秒 使うと 0.16666 USD 上記は x86(amd64) の値段、arm だと 2 割引 arm は CPU パフォーマンスも 2 割向上すると言われているので、可能であれば arm を選択すると更にコスパ向上が期待できる このように見てみると、サーバーやコンテナを利用した場合はどんなに最小構成でも冗長化を考えると数十ドルはかかるので、特に頻繁に実行されない機能は Lambda によるサーバーレス化を検討すると良さそうです。 また、時間あたりで料金がかかるということは、API などネットワークアクセスで時間を使った場合も料金がかかることに注意が必要です。外部 API のパフォーマンスに依存してしまうので、なるべく Lambda は CPU を使った処理が支配的になるとコスパよく利用することができます。 ベンチマーク Lambda のパフォーマンス計測には、 AWS Lambda Power Tuning を利用しました。AWS Lambda Power Tuning は Step Functions と Lambda を使って Lambda の速度とコストを計測できるツールです。 Step Functions と Lambda をデプロイした上で、Step Functions の state machine に計測したい Lambda の ARN, memory 設定, 実行回数, payload を入力することでベンチマーク結果を取得できます。 実行すると最終結果に URL が出力され、その URL をブラウザで表示することで、グラフで結果とスコアボードで結果が可視化されます。また 2 つの結果を比較して表示することもできますので改善後に比較することも容易です。 4 AWS Lambda Power Tuning の導入 リポジトリのドキュメント に従い導入していきます。 今回は AWS Serverless Application Repository(SAR) を使って導入しました。AWS Serverless Application Repository(SAR) とはその名の通り、サーバーレスアプリケーションを管理するリポジトリですが、組織内に限らず公開することも可能で、他の人も再利用可能になっています。また、導入する際は公開ページの Deploy ボタンをポチるだけで自身の AWS アカウント上にデプロイされ、 CloudFormation の Stack として管理されます。削除も CloudFormation の Stack を削除するだけで実施できます。 AWS Lambda Power Tuning は こちらから 自身のアカウントにデプロイできます。 また、AWS や個人だけでなく Datadog や New Relic など他のパブリッシャーもサーバーレスアプリケーションを公開しているので、他にも使いたいものがないか探してみるとよいかもしれません。 AWS Lambda Power Tuning の実行 デプロイが完了すると、AWS Step Functions 上に state machine があるので、state machine にパラメータを入力することで計測ができます。 リポジトリのドキュメント にいくつかやり方が記載されています。ここでは 実行パラメータを定義した json ファイルを用意してスクリプトから実行する方法を紹介します。 まずは、 AWS Lambda Power Tuning リポジトリ を git clone し、 scripts/sample-execution-input.json を編集します。 { "lambdaARN" : "arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-performance-tuning" , "powerValues" : [ 128 , 256 , 512 , 832 , 1024 , 1536 , 1769 , 1770 , 2048 , 3008 ], "num" : 100 , "payload" : { "body" : "{ \" id \" : \" 123 \" , \" name \" : \" performance \" }" , "path" : "/api/performance" , "httpMethod" : "GET" , "isBase64Encoded" : false , "multiValueHeaders" : { "Accept" : [ "application/json" ] } }, "parallelInvocation" : false } 入力値の詳細は以下の通りです。 5 lambdaARN: Lambda Function の ARN powerValues: 計測したい Lambda のメモリを指定 num: メモリごとの Lambda 実行回数 payload: Lambda Function にわたす Event JSON の内容 parallelInvocation: ベンチマークを並列実行する 次に、 scripts/execute.sh を実行すると、ベンチマークが開始され、最後に結果 URL が出力されます。デプロイ時の設定によって、CloudFormation の Stack Name が違う場合もありますので、必要に応じて書き換えてください。 $ sh ./scripts/execute.sh -n Execution started... -n . : -n . SUCCEEDED Execution output: { "power" :128, "cost" :8.4E-9, "duration" :3.6706666666666674, "stateMachine" : { " executionCost ":4.0E-4," lambdaCost ":1.0178708203125003E-4," visualization ":" https://lambda-power-tuning.show/#gAAAAQACQAMABAAG6QbqBgAIwAs = ; NOxqQP9GmkDFII5AbxKBQLErfECGXW1AWDmGQNNNjkBpSo1ARf1nQA = = ; l08QMn1jtDJ9YzQz1pCSM5dPkDNjd9gzb9AbNPzmGzR9YzQ05vRTNA = ="}} 計測方法は以上、簡単ですね 👌 AWS Lambda Power Tuning の結果確認 計測結果を確認します。関数によって傾向は異なりますが、ここでは 2 種類のパターンを紹介します。 DB や API アクセスなど外部処理が支配的なケース(Network-intensive) Lambda 内の処理が外部処理で占めている場合は、 以下のようなグラフ になります。 赤色の実行時間がどのメモリ設定でも 4ms 前後と大きな変化がなく、青色のコストだけが右肩上がりになっています。 この場合は、Lambda の最低メモリである 128MB が最適ということになります。 CPU 実行時間が支配的なケース(CPU-intensive) Lambda 内の処理が内部処理で占めている場合は、 以下のようなグラフ になります。透かし処理は画像合成処理に多くの CPU を利用するので、こちらのパターンとなりました。 メモリが少ないと速度が出ておらず、メモリを増やすに連れて速度も改善しており、コストの増加も緩やかです。ただし、今回はプログラムがマルチスレッド・マルチプロセス対応はしていないため、コア数が増える 1770MB 以降は大きな速度改善は見られず、コストが上がる結果となっています。 よって、このケースではコストだけを考えたら低メモリが有利ですが、速度も考えたら 1024MB ~ 1770MB あたりにすることを検討すると良さそうです。 まとめ 処方箋プレビューに透かしを入れることになった背景、実現方法を紹介いたしました。 透かし処理はアルファブレンド処理を実施することで実現でき、PDF については透かし用のレイヤーを重ねることで実現できます。 S3 Object Lambda を利用することで、追加のサーバー構築をすることなく、S3 のファイルに対してフィルタリングをかけることができます。 Lambda のパフォーマンス・チューニングについては、AWS Lambda Power Tuning を利用して可視化できます。外部 API などネットワーク依存(Network-intensive)ではなく、CPU 処理が中心(CPU-intensive)になると、コスパよく利用することができます。 S3 のファイルに対してなんらかの処理を実施したいと考えてる方は、一度 S3 Object Lambda の利用を検討してみてはいかがでしょうか? また、Lambda のチューニングポイントは色々ありますが、AWS Summit Online で紹介された以下のセッションがおすすめなので、もっと深くチューニングしたい方は是非ご覧ください! AWS Lambda Performance Tuning Deep Dive〜本当に知りたいのは”ここ”だった〜 さいごに メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しております。医療という分野においては、機微な情報を扱ったり診療という大事な業務を止めないよう、可用性、パフォーマンス、セキュリティともに高いサービスレベルを求められます。興味がある方は是非ご連絡ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp Footnotes 元画像に直接文字や図形などを描画することももちろん可能ですが、透かしを画像データとしてデザイン、管理したいこともあり、今回はアルファブレンドを選択した。 アルファブレンド - wikipedia ↩ pdfcpu API document: watermark example ↩ ただし最初の 60 億 GB 秒/月。昨今は円安なのでドル建てで利用しているインフラコストも上がっており、USD 表記だとコストが変わらないようにみえるので要注意。 AWS Lambda 料金 ↩ この UI は AWS Lambda Power Tuning UI というツールで別途提供されており、 lambda-power-tuning.show ドメインでアクセスできるので、自前で用意する必要はない。URL は https://lambda-power-tuning.show/#AAEAAgADAAQ=;xKDwRUqaakU5+RtFcXLqRA==;UqkHOPJCBDjA6AM46DAEOA==;AAEAAgADAAQ=;aYP6RdyeeUUQKiVFuC76RA==;ZDoNOJy3DDiJrQs4zhENOA==;100;50 のような形式でデータが URL のパスパラメータにエンコードされている。 ↩ この他にも strategy など様々なパラメータがある: aws-lambda-power-tuning: README-INPUT-OUTPUT.md ↩
こんにちは。医療プラットフォーム本部プロダクト開発室エンジニアの中畑です。主にオンライン診療・服薬指導アプリ CLINICS の開発を担当しています。 今回は CLINICS アプリ内で扱う処方箋プレビューに透かし(watermark)を入れた話を紹介したいと思います。なぜ実施したのか、実装方法、パフォーマンスチューニングの 3 本立てでお送りしたいと思います。 課題と解決方針 まず、なぜ処方箋プレビューに透かしを入れることにしたのか。 CLINICS では診察後に患者が希望すると、かかりつけ薬局支援システム Pharms を導入している調剤薬局にてオンライン服薬指導を受けることができます。その際に医療機関から処方箋の画像ファイルや PDF をアップロードし、患者は CLINICS アプリを通じて、オンライン服薬指導を受けたい調剤薬局に処方箋データを送る必要があります。 患者はオンライン服薬指導を予約する前に医療機関からアップロードされた処方箋画像をプレビューできます。この処方箋プレビューは原本とは扱いが異なるため、患者がアプリ内でそれぞれを明確に区別できるような対応を入れる必要があり、その手段として処方箋プレビューに透かしを入れることにしたというのが理由となります。 実現・実装方法 まずは画像に透かしを入れる方法を考え、それからシステム上でどのタイミングで透かしを入れるか検討しました。前提条件ですが、処方箋プレビュー画像は以下のような形で保存されています。 フォーマット: JPEG, PNG, GIF, PDF(画像以外もある) 保存場所: S3 画像に透かしを入れる方法 透かしは技術的には 2 枚の画像をアルファブレンド 1 を使って合成した画像となります。 アルファブレンドの実現方法は言語やライブラリによっていろいろありますが、今回は処方箋を扱うシステムが Go を利用しているので、Go の以下の標準ライブラリを使って実装しました。また、画像には向き( orientation )の情報が保持されているのですが、向きを意識しないと合成時におかしくなることがあります。端末やツールによって取り扱いが異なる部分が多いので、それを解消してくれる disintegration/imaging パッケージを利用しました。 image パッケージ : ベースライブラリ ximage パッケージ : 描画処理 disintegration/imaging パッケージ : 読み込み時に向きを補正 詳細は省きますが、以下のようなコードで合成できます func Watermark ( srcFile string ) { // 元画像 imgb , _ := os . Open ( srcFile ) var img image . Image img , _ = imaging . Decode ( imgb , imaging . AutoOrientation ( true )) defer imgb . Close () // 上に重ねる透かし画像 wmb , _ := os . Open ( "watermark.png" ) watermark , _ , _ := image . Decode ( wmb ) defer wmb . Close () // 透かしを配置する場所 (画像サイズによって異なるため、実際は計算が必要) watermarkRect := image . Rectangle { image . Point { 100 , 100 }, image . Point { 200 , 300 }} // 合成処理(ここではバイリニア補間で実施) b := img . Bounds () m := image . NewNRGBA ( b ) draw . Draw ( m , b , img , image . ZP , draw . Src ) draw . BiLinear . Scale ( m , watermarkRect , watermark , watermark . Bounds (), draw . Over , nil ) // 合成画像の出力(JPEG 出力。他のエンコーダーを利用すれば他の画像形式も可) imgw , _ := os . Create ( "/tmp/dest.jpg" ) jpeg . Encode ( imgw , m , & jpeg . Options { Quality : jpeg . DefaultQuality }) defer imgw . Close () } PDF に透かしを入れる方法 また、今回の要件には PDF ファイルにも透かしを入れる必要がありました。PDF にはレイヤーという概念があり、元の PDF に対して透かしを入れるレイヤーを追加すれば実現できます。Go には PDF を標準で扱えるパッケージがなかったので、今回は pdfcpu を利用しました。 透かしのレイヤーを追加する関数が用意されており、以下のコードだけで実現できて大変便利です。 2 wm , _ := api . ImageWatermark ( "watermark.jpg" , "sc:.8 rel, rot:0" , onTop , update , pdfcpu . POINTS ) api . AddWatermarksFile ( "src.pdf" , "dest.pdf" , nil , wm , nil ) S3 画像に透かしを入れる方法 AWS を利用していると S3 に各種ファイルを保存することも多いと思います。 基本的には S3 画像を読み込んで透かし処理をすれば実現できますが、AWS には S3 のレスポンスを加工するマネージメントサービスがいくつかあり、今回は S3 Object Lambda を利用しました。 S3 Object Lambda を利用することで S3 からファイルを取得する際に Lambda による処理を実行したうえで返却することができます。これを利用して、元の画像に Lambda で透かしを入れて返却することができます。 処方箋プレビューに透かしを入れる前の構成は以下のようなイメージです。 アプリサーバーにて処方箋プレビューの S3 presigned URL(署名付き URL)を発行 クライアントに返す クライアントは受け取った URL にアクセス。処方箋プレビューが表示される これに S3 Object Lambda を利用して透かしを入れると以下のようになります。 アプリサーバーにて処方箋プレビューの S3 Object Lambda Access Point の presigned URL(署名付き URL)を発行 クライアントは受け取った URL にアクセス S3 Object Lambda Access Point は透かし処理を行う Lambda 関数を実行。その際に S3 Access Point の署名付き URL を発行し、リクエストパラメータに設定 Lambda Function は受け取った URL にアクセスし処方箋プレビューの元画像を取得して透かしを入れて返却 透かし入りの処方箋プレビューが表示される 新たに S3 Object Lambda Access Point , Lambda Function , S3 Access Point を用意する必要があって少しややこしいですが簡単に説明すると以下のような役割のものとなります。 icon title description S3 Access Point S3 Bucket に対する alias。S3 Object Lambda は必ずこれを経由して S3 にアクセスする必要がある Lambda Function S3 のファイルに対してなんらかの処理を実施する関数 S3 Object Lambda Access Point S3 Access Point に対して Lambda 関数を実行するためのアクセスポイント 透かし処理を実際呼び出す際は、アプリケーション側からは S3 の presigned URL から S3 Object Lambda Access Point の presigned URL に切り替えるだけなので、透かしありなしの切り替えも簡単です。 S3 presigned URL: https://[bucket-name].s3.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] ↓ S3 Object Lambda Access Point presigned URL: https://[s3-object-lambda-access-point]-[account-id].s3-object-lambda.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] S3 Object Lambda を利用することで以下のようなメリットがあります。 追加のインフラ構築が不要 既存サーバーのリソース追加や、新たに透かし処理用のサーバーを用意する必要がない。既存のアプリサーバーへの影響を軽微にできる Lambda が自動的にスケールを実施してくれるので負荷対策が容易 利用した分だけの課金となる(S3 Get + Lambda + S3 Object Lambda の利用料) 透かしを入れた処方箋プレビュー画像を事前に用意する必要がない 事前に用意するとリリース前に保持した画像に対しても処理する必要が出てくる (ただし、今回は特定のユーザーのみのアクセスなので不特定多数のアクセスが見込まれる場合は事前生成したほうがよいかもしれません) もちろん画像だけではなく、テキストデータなど他のものにも利用できるので、S3 に保管した機密情報等をフィルタリングして返したい用途などにもマッチします。 AWS 公式ドキュメントに詳細な解説と様々なユースケースのチュートリアルがありますので、興味がある方は是非御覧ください。 S3 Object Lambda を使用したオブジェクトの変換 パフォーマンス・チューニング ここまでで、処方箋プレビューに透かしを入れる処理自体は完成しました。ここで気になってくるのがパフォーマンスについてです。今までは S3 の画像を表示するだけでしたが透かし処理に S3 Object Lambda を使うことになったため、速度と料金が気になるところです。 様々なチューニングポイントがありますが、今回は Lambda のメモリ設定に焦点を絞ってお話したいと思います。 Lambda の CPU パフォーマンス Lambda はメモリ設定しかできませんが、メモリ量に比例して CPU 性能が向上する仕組みとなっています。 公式ドキュメント 上では 1769MB あたり 1vCPU であるとされていて、MAX の 10240MB で 6vCPU 使えるとしています。実際に Lambda の vCPU の設定を調べたブログ ( SENTIA tech blog ) では以下のようになっていたそうです。 Memory vCPUs 128 - 3008 MB 2 3009 - 5307 MB 3 5308 - 7076 MB 4 7077 - 8845 MB 5 8846 - 10240 MB 6 ただし、メモリをいくら増やしたところで関数自体がマルチスレッド・マルチプロセス化されていない場合はパフォーマンス向上が見込めないということにもなります。適切に並列処理が実装されていれば、vCPU が増える境目で大きな性能向上が期待できます。 Lambda の料金 東京リージョン(ap-northeast-1)において、2022 年 10 月現在は以下のような料金となります。 3 無料枠: 1 ヶ月あたり 100 万リクエスト、40 万 GB-秒(仮に 1 GB で動かしたとして 40 万秒使える) 128 MB だと 320 万秒、 10240 MB だと 4 万秒 有料枠: 100 万リクエストあたり 0.20 USD, GB-秒あたり 0.00001666667 USD 1 億リクエストあたり 20 USD 128MB で 1 万秒 使うと 0.02083 USD 1024MB で 1 万秒 使うと 0.16666 USD 上記は x86(amd64) の値段、arm だと 2 割引 arm は CPU パフォーマンスも 2 割向上すると言われているので、可能であれば arm を選択すると更にコスパ向上が期待できる このように見てみると、サーバーやコンテナを利用した場合はどんなに最小構成でも冗長化を考えると数十ドルはかかるので、特に頻繁に実行されない機能は Lambda によるサーバーレス化を検討すると良さそうです。 また、時間あたりで料金がかかるということは、API などネットワークアクセスで時間を使った場合も料金がかかることに注意が必要です。外部 API のパフォーマンスに依存してしまうので、なるべく Lambda は CPU を使った処理が支配的になるとコスパよく利用することができます。 ベンチマーク Lambda のパフォーマンス計測には、 AWS Lambda Power Tuning を利用しました。AWS Lambda Power Tuning は Step Functions と Lambda を使って Lambda の速度とコストを計測できるツールです。 Step Functions と Lambda をデプロイした上で、Step Functions の state machine に計測したい Lambda の ARN, memory 設定, 実行回数, payload を入力することでベンチマーク結果を取得できます。 実行すると最終結果に URL が出力され、その URL をブラウザで表示することで、グラフで結果とスコアボードで結果が可視化されます。また 2 つの結果を比較して表示することもできますので改善後に比較することも容易です。 4 AWS Lambda Power Tuning の導入 リポジトリのドキュメント に従い導入していきます。 今回は AWS Serverless Application Repository(SAR) を使って導入しました。AWS Serverless Application Repository(SAR) とはその名の通り、サーバーレスアプリケーションを管理するリポジトリですが、組織内に限らず公開することも可能で、他の人も再利用可能になっています。また、導入する際は公開ページの Deploy ボタンをポチるだけで自身の AWS アカウント上にデプロイされ、 CloudFormation の Stack として管理されます。削除も CloudFormation の Stack を削除するだけで実施できます。 AWS Lambda Power Tuning は こちらから 自身のアカウントにデプロイできます。 また、AWS や個人だけでなく Datadog や New Relic など他のパブリッシャーもサーバーレスアプリケーションを公開しているので、他にも使いたいものがないか探してみるとよいかもしれません。 AWS Lambda Power Tuning の実行 デプロイが完了すると、AWS Step Functions 上に state machine があるので、state machine にパラメータを入力することで計測ができます。 リポジトリのドキュメント にいくつかやり方が記載されています。ここでは 実行パラメータを定義した json ファイルを用意してスクリプトから実行する方法を紹介します。 まずは、 AWS Lambda Power Tuning リポジトリ を git clone し、 scripts/sample-execution-input.json を編集します。 { "lambdaARN" : "arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-performance-tuning" , "powerValues" : [ 128 , 256 , 512 , 832 , 1024 , 1536 , 1769 , 1770 , 2048 , 3008 ], "num" : 100 , "payload" : { "body" : "{ \" id \" : \" 123 \" , \" name \" : \" performance \" }" , "path" : "/api/performance" , "httpMethod" : "GET" , "isBase64Encoded" : false , "multiValueHeaders" : { "Accept" : [ "application/json" ] } }, "parallelInvocation" : false } 入力値の詳細は以下の通りです。 5 lambdaARN: Lambda Function の ARN powerValues: 計測したい Lambda のメモリを指定 num: メモリごとの Lambda 実行回数 payload: Lambda Function にわたす Event JSON の内容 parallelInvocation: ベンチマークを並列実行する 次に、 scripts/execute.sh を実行すると、ベンチマークが開始され、最後に結果 URL が出力されます。デプロイ時の設定によって、CloudFormation の Stack Name が違う場合もありますので、必要に応じて書き換えてください。 $ sh ./scripts/execute.sh -n Execution started... -n . : -n . SUCCEEDED Execution output: { "power" :128, "cost" :8.4E-9, "duration" :3.6706666666666674, "stateMachine" : { " executionCost ":4.0E-4," lambdaCost ":1.0178708203125003E-4," visualization ":" https://lambda-power-tuning.show/#gAAAAQACQAMABAAG6QbqBgAIwAs = ; NOxqQP9GmkDFII5AbxKBQLErfECGXW1AWDmGQNNNjkBpSo1ARf1nQA = = ; l08QMn1jtDJ9YzQz1pCSM5dPkDNjd9gzb9AbNPzmGzR9YzQ05vRTNA = ="}} 計測方法は以上、簡単ですね 👌 AWS Lambda Power Tuning の結果確認 計測結果を確認します。関数によって傾向は異なりますが、ここでは 2 種類のパターンを紹介します。 DB や API アクセスなど外部処理が支配的なケース(Network-intensive) Lambda 内の処理が外部処理で占めている場合は、 以下のようなグラフ になります。 赤色の実行時間がどのメモリ設定でも 4ms 前後と大きな変化がなく、青色のコストだけが右肩上がりになっています。 この場合は、Lambda の最低メモリである 128MB が最適ということになります。 CPU 実行時間が支配的なケース(CPU-intensive) Lambda 内の処理が内部処理で占めている場合は、 以下のようなグラフ になります。透かし処理は画像合成処理に多くの CPU を利用するので、こちらのパターンとなりました。 メモリが少ないと速度が出ておらず、メモリを増やすに連れて速度も改善しており、コストの増加も緩やかです。ただし、今回はプログラムがマルチスレッド・マルチプロセス対応はしていないため、コア数が増える 1770MB 以降は大きな速度改善は見られず、コストが上がる結果となっています。 よって、このケースではコストだけを考えたら低メモリが有利ですが、速度も考えたら 1024MB ~ 1770MB あたりにすることを検討すると良さそうです。 まとめ 処方箋プレビューに透かしを入れることになった背景、実現方法を紹介いたしました。 透かし処理はアルファブレンド処理を実施することで実現でき、PDF については透かし用のレイヤーを重ねることで実現できます。 S3 Object Lambda を利用することで、追加のサーバー構築をすることなく、S3 のファイルに対してフィルタリングをかけることができます。 Lambda のパフォーマンス・チューニングについては、AWS Lambda Power Tuning を利用して可視化できます。外部 API などネットワーク依存(Network-intensive)ではなく、CPU 処理が中心(CPU-intensive)になると、コスパよく利用することができます。 S3 のファイルに対してなんらかの処理を実施したいと考えてる方は、一度 S3 Object Lambda の利用を検討してみてはいかがでしょうか? また、Lambda のチューニングポイントは色々ありますが、AWS Summit Online で紹介された以下のセッションがおすすめなので、もっと深くチューニングしたい方は是非ご覧ください! AWS Lambda Performance Tuning Deep Dive〜本当に知りたいのは”ここ”だった〜 さいごに メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しております。医療という分野においては、機微な情報を扱ったり診療という大事な業務を止めないよう、可用性、パフォーマンス、セキュリティともに高いサービスレベルを求められます。興味がある方は是非ご連絡ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp Footnotes 元画像に直接文字や図形などを描画することももちろん可能ですが、透かしを画像データとしてデザイン、管理したいこともあり、今回はアルファブレンドを選択した。 アルファブレンド - wikipedia ↩ pdfcpu API document: watermark example ↩ ただし最初の 60 億 GB 秒/月。昨今は円安なのでドル建てで利用しているインフラコストも上がっており、USD 表記だとコストが変わらないようにみえるので要注意。 AWS Lambda 料金 ↩ この UI は AWS Lambda Power Tuning UI というツールで別途提供されており、 lambda-power-tuning.show ドメインでアクセスできるので、自前で用意する必要はない。URL は https://lambda-power-tuning.show/#AAEAAgADAAQ=;xKDwRUqaakU5+RtFcXLqRA==;UqkHOPJCBDjA6AM46DAEOA==;AAEAAgADAAQ=;aYP6RdyeeUUQKiVFuC76RA==;ZDoNOJy3DDiJrQs4zhENOA==;100;50 のような形式でデータが URL のパスパラメータにエンコードされている。 ↩ この他にも strategy など様々なパラメータがある: aws-lambda-power-tuning: README-INPUT-OUTPUT.md ↩
こんにちは。医療プラットフォーム本部プロダクト開発室エンジニアの中畑です。主にオンライン診療・服薬指導アプリ CLINICS の開発を担当しています。 今回は CLINICS アプリ内で扱う処方箋プレビューに透かし(watermark)を入れた話を紹介したいと思います。なぜ実施したのか、実装方法、パフォーマンスチューニングの 3 本立てでお送りしたいと思います。 課題と解決方針 まず、なぜ処方箋プレビューに透かしを入れることにしたのか。 CLINICS では診察後に患者が希望すると、かかりつけ薬局支援システム Pharms を導入している調剤薬局にてオンライン服薬指導を受けることができます。その際に医療機関から処方箋の画像ファイルや PDF をアップロードし、患者は CLINICS アプリを通じて、オンライン服薬指導を受けたい調剤薬局に処方箋データを送る必要があります。 患者はオンライン服薬指導を予約する前に医療機関からアップロードされた処方箋画像をプレビューできます。この処方箋プレビューは原本とは扱いが異なるため、患者がアプリ内でそれぞれを明確に区別できるような対応を入れる必要があり、その手段として処方箋プレビューに透かしを入れることにしたというのが理由となります。 実現・実装方法 まずは画像に透かしを入れる方法を考え、それからシステム上でどのタイミングで透かしを入れるか検討しました。前提条件ですが、処方箋プレビュー画像は以下のような形で保存されています。 フォーマット: JPEG, PNG, GIF, PDF(画像以外もある) 保存場所: S3 画像に透かしを入れる方法 透かしは技術的には 2 枚の画像をアルファブレンド 1 を使って合成した画像となります。 アルファブレンドの実現方法は言語やライブラリによっていろいろありますが、今回は処方箋を扱うシステムが Go を利用しているので、Go の以下の標準ライブラリを使って実装しました。また、画像には向き( orientation )の情報が保持されているのですが、向きを意識しないと合成時におかしくなることがあります。端末やツールによって取り扱いが異なる部分が多いので、それを解消してくれる disintegration/imaging パッケージを利用しました。 image パッケージ : ベースライブラリ ximage パッケージ : 描画処理 disintegration/imaging パッケージ : 読み込み時に向きを補正 詳細は省きますが、以下のようなコードで合成できます func Watermark ( srcFile string ) { // 元画像 imgb , _ := os . Open ( srcFile ) var img image . Image img , _ = imaging . Decode ( imgb , imaging . AutoOrientation ( true )) defer imgb . Close () // 上に重ねる透かし画像 wmb , _ := os . Open ( "watermark.png" ) watermark , _ , _ := image . Decode ( wmb ) defer wmb . Close () // 透かしを配置する場所 (画像サイズによって異なるため、実際は計算が必要) watermarkRect := image . Rectangle { image . Point { 100 , 100 }, image . Point { 200 , 300 }} // 合成処理(ここではバイリニア補間で実施) b := img . Bounds () m := image . NewNRGBA ( b ) draw . Draw ( m , b , img , image . ZP , draw . Src ) draw . BiLinear . Scale ( m , watermarkRect , watermark , watermark . Bounds (), draw . Over , nil ) // 合成画像の出力(JPEG 出力。他のエンコーダーを利用すれば他の画像形式も可) imgw , _ := os . Create ( "/tmp/dest.jpg" ) jpeg . Encode ( imgw , m , & jpeg . Options { Quality : jpeg . DefaultQuality }) defer imgw . Close () } PDF に透かしを入れる方法 また、今回の要件には PDF ファイルにも透かしを入れる必要がありました。PDF にはレイヤーという概念があり、元の PDF に対して透かしを入れるレイヤーを追加すれば実現できます。Go には PDF を標準で扱えるパッケージがなかったので、今回は pdfcpu を利用しました。 透かしのレイヤーを追加する関数が用意されており、以下のコードだけで実現できて大変便利です。 2 wm , _ := api . ImageWatermark ( "watermark.jpg" , "sc:.8 rel, rot:0" , onTop , update , pdfcpu . POINTS ) api . AddWatermarksFile ( "src.pdf" , "dest.pdf" , nil , wm , nil ) S3 画像に透かしを入れる方法 AWS を利用していると S3 に各種ファイルを保存することも多いと思います。 基本的には S3 画像を読み込んで透かし処理をすれば実現できますが、AWS には S3 のレスポンスを加工するマネージメントサービスがいくつかあり、今回は S3 Object Lambda を利用しました。 S3 Object Lambda を利用することで S3 からファイルを取得する際に Lambda による処理を実行したうえで返却することができます。これを利用して、元の画像に Lambda で透かしを入れて返却することができます。 処方箋プレビューに透かしを入れる前の構成は以下のようなイメージです。 アプリサーバーにて処方箋プレビューの S3 presigned URL(署名付き URL)を発行 クライアントに返す クライアントは受け取った URL にアクセス。処方箋プレビューが表示される これに S3 Object Lambda を利用して透かしを入れると以下のようになります。 アプリサーバーにて処方箋プレビューの S3 Object Lambda Access Point の presigned URL(署名付き URL)を発行 クライアントは受け取った URL にアクセス S3 Object Lambda Access Point は透かし処理を行う Lambda 関数を実行。その際に S3 Access Point の署名付き URL を発行し、リクエストパラメータに設定 Lambda Function は受け取った URL にアクセスし処方箋プレビューの元画像を取得して透かしを入れて返却 透かし入りの処方箋プレビューが表示される 新たに S3 Object Lambda Access Point , Lambda Function , S3 Access Point を用意する必要があって少しややこしいですが簡単に説明すると以下のような役割のものとなります。 icon title description S3 Access Point S3 Bucket に対する alias。S3 Object Lambda は必ずこれを経由して S3 にアクセスする必要がある Lambda Function S3 のファイルに対してなんらかの処理を実施する関数 S3 Object Lambda Access Point S3 Access Point に対して Lambda 関数を実行するためのアクセスポイント 透かし処理を実際呼び出す際は、アプリケーション側からは S3 の presigned URL から S3 Object Lambda Access Point の presigned URL に切り替えるだけなので、透かしありなしの切り替えも簡単です。 S3 presigned URL: https://[bucket-name].s3.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] ↓ S3 Object Lambda Access Point presigned URL: https://[s3-object-lambda-access-point]-[account-id].s3-object-lambda.ap-northeast-1.amazonaws.com/prescription.jpg?[signature] S3 Object Lambda を利用することで以下のようなメリットがあります。 追加のインフラ構築が不要 既存サーバーのリソース追加や、新たに透かし処理用のサーバーを用意する必要がない。既存のアプリサーバーへの影響を軽微にできる Lambda が自動的にスケールを実施してくれるので負荷対策が容易 利用した分だけの課金となる(S3 Get + Lambda + S3 Object Lambda の利用料) 透かしを入れた処方箋プレビュー画像を事前に用意する必要がない 事前に用意するとリリース前に保持した画像に対しても処理する必要が出てくる (ただし、今回は特定のユーザーのみのアクセスなので不特定多数のアクセスが見込まれる場合は事前生成したほうがよいかもしれません) もちろん画像だけではなく、テキストデータなど他のものにも利用できるので、S3 に保管した機密情報等をフィルタリングして返したい用途などにもマッチします。 AWS 公式ドキュメントに詳細な解説と様々なユースケースのチュートリアルがありますので、興味がある方は是非御覧ください。 S3 Object Lambda を使用したオブジェクトの変換 パフォーマンス・チューニング ここまでで、処方箋プレビューに透かしを入れる処理自体は完成しました。ここで気になってくるのがパフォーマンスについてです。今までは S3 の画像を表示するだけでしたが透かし処理に S3 Object Lambda を使うことになったため、速度と料金が気になるところです。 様々なチューニングポイントがありますが、今回は Lambda のメモリ設定に焦点を絞ってお話したいと思います。 Lambda の CPU パフォーマンス Lambda はメモリ設定しかできませんが、メモリ量に比例して CPU 性能が向上する仕組みとなっています。 公式ドキュメント 上では 1769MB あたり 1vCPU であるとされていて、MAX の 10240MB で 6vCPU 使えるとしています。実際に Lambda の vCPU の設定を調べたブログ ( SENTIA tech blog ) では以下のようになっていたそうです。 Memory vCPUs 128 - 3008 MB 2 3009 - 5307 MB 3 5308 - 7076 MB 4 7077 - 8845 MB 5 8846 - 10240 MB 6 ただし、メモリをいくら増やしたところで関数自体がマルチスレッド・マルチプロセス化されていない場合はパフォーマンス向上が見込めないということにもなります。適切に並列処理が実装されていれば、vCPU が増える境目で大きな性能向上が期待できます。 Lambda の料金 東京リージョン(ap-northeast-1)において、2022 年 10 月現在は以下のような料金となります。 3 無料枠: 1 ヶ月あたり 100 万リクエスト、40 万 GB-秒(仮に 1 GB で動かしたとして 40 万秒使える) 128 MB だと 320 万秒、 10240 MB だと 4 万秒 有料枠: 100 万リクエストあたり 0.20 USD, GB-秒あたり 0.00001666667 USD 1 億リクエストあたり 20 USD 128MB で 1 万秒 使うと 0.02083 USD 1024MB で 1 万秒 使うと 0.16666 USD 上記は x86(amd64) の値段、arm だと 2 割引 arm は CPU パフォーマンスも 2 割向上すると言われているので、可能であれば arm を選択すると更にコスパ向上が期待できる このように見てみると、サーバーやコンテナを利用した場合はどんなに最小構成でも冗長化を考えると数十ドルはかかるので、特に頻繁に実行されない機能は Lambda によるサーバーレス化を検討すると良さそうです。 また、時間あたりで料金がかかるということは、API などネットワークアクセスで時間を使った場合も料金がかかることに注意が必要です。外部 API のパフォーマンスに依存してしまうので、なるべく Lambda は CPU を使った処理が支配的になるとコスパよく利用することができます。 ベンチマーク Lambda のパフォーマンス計測には、 AWS Lambda Power Tuning を利用しました。AWS Lambda Power Tuning は Step Functions と Lambda を使って Lambda の速度とコストを計測できるツールです。 Step Functions と Lambda をデプロイした上で、Step Functions の state machine に計測したい Lambda の ARN, memory 設定, 実行回数, payload を入力することでベンチマーク結果を取得できます。 実行すると最終結果に URL が出力され、その URL をブラウザで表示することで、グラフで結果とスコアボードで結果が可視化されます。また 2 つの結果を比較して表示することもできますので改善後に比較することも容易です。 4 AWS Lambda Power Tuning の導入 リポジトリのドキュメント に従い導入していきます。 今回は AWS Serverless Application Repository(SAR) を使って導入しました。AWS Serverless Application Repository(SAR) とはその名の通り、サーバーレスアプリケーションを管理するリポジトリですが、組織内に限らず公開することも可能で、他の人も再利用可能になっています。また、導入する際は公開ページの Deploy ボタンをポチるだけで自身の AWS アカウント上にデプロイされ、 CloudFormation の Stack として管理されます。削除も CloudFormation の Stack を削除するだけで実施できます。 AWS Lambda Power Tuning は こちらから 自身のアカウントにデプロイできます。 また、AWS や個人だけでなく Datadog や New Relic など他のパブリッシャーもサーバーレスアプリケーションを公開しているので、他にも使いたいものがないか探してみるとよいかもしれません。 AWS Lambda Power Tuning の実行 デプロイが完了すると、AWS Step Functions 上に state machine があるので、state machine にパラメータを入力することで計測ができます。 リポジトリのドキュメント にいくつかやり方が記載されています。ここでは 実行パラメータを定義した json ファイルを用意してスクリプトから実行する方法を紹介します。 まずは、 AWS Lambda Power Tuning リポジトリ を git clone し、 scripts/sample-execution-input.json を編集します。 { "lambdaARN" : "arn:aws:lambda:ap-northeast-1:123456789012:function:lambda-performance-tuning" , "powerValues" : [ 128 , 256 , 512 , 832 , 1024 , 1536 , 1769 , 1770 , 2048 , 3008 ], "num" : 100 , "payload" : { "body" : "{ \" id \" : \" 123 \" , \" name \" : \" performance \" }" , "path" : "/api/performance" , "httpMethod" : "GET" , "isBase64Encoded" : false , "multiValueHeaders" : { "Accept" : [ "application/json" ] } }, "parallelInvocation" : false } 入力値の詳細は以下の通りです。 5 lambdaARN: Lambda Function の ARN powerValues: 計測したい Lambda のメモリを指定 num: メモリごとの Lambda 実行回数 payload: Lambda Function にわたす Event JSON の内容 parallelInvocation: ベンチマークを並列実行する 次に、 scripts/execute.sh を実行すると、ベンチマークが開始され、最後に結果 URL が出力されます。デプロイ時の設定によって、CloudFormation の Stack Name が違う場合もありますので、必要に応じて書き換えてください。 $ sh ./scripts/execute.sh -n Execution started... -n . : -n . SUCCEEDED Execution output: { "power" :128, "cost" :8.4E-9, "duration" :3.6706666666666674, "stateMachine" : { " executionCost ":4.0E-4," lambdaCost ":1.0178708203125003E-4," visualization ":" https://lambda-power-tuning.show/#gAAAAQACQAMABAAG6QbqBgAIwAs = ; NOxqQP9GmkDFII5AbxKBQLErfECGXW1AWDmGQNNNjkBpSo1ARf1nQA = = ; l08QMn1jtDJ9YzQz1pCSM5dPkDNjd9gzb9AbNPzmGzR9YzQ05vRTNA = ="}} 計測方法は以上、簡単ですね 👌 AWS Lambda Power Tuning の結果確認 計測結果を確認します。関数によって傾向は異なりますが、ここでは 2 種類のパターンを紹介します。 DB や API アクセスなど外部処理が支配的なケース(Network-intensive) Lambda 内の処理が外部処理で占めている場合は、 以下のようなグラフ になります。 赤色の実行時間がどのメモリ設定でも 4ms 前後と大きな変化がなく、青色のコストだけが右肩上がりになっています。 この場合は、Lambda の最低メモリである 128MB が最適ということになります。 CPU 実行時間が支配的なケース(CPU-intensive) Lambda 内の処理が内部処理で占めている場合は、 以下のようなグラフ になります。透かし処理は画像合成処理に多くの CPU を利用するので、こちらのパターンとなりました。 メモリが少ないと速度が出ておらず、メモリを増やすに連れて速度も改善しており、コストの増加も緩やかです。ただし、今回はプログラムがマルチスレッド・マルチプロセス対応はしていないため、コア数が増える 1770MB 以降は大きな速度改善は見られず、コストが上がる結果となっています。 よって、このケースではコストだけを考えたら低メモリが有利ですが、速度も考えたら 1024MB ~ 1770MB あたりにすることを検討すると良さそうです。 まとめ 処方箋プレビューに透かしを入れることになった背景、実現方法を紹介いたしました。 透かし処理はアルファブレンド処理を実施することで実現でき、PDF については透かし用のレイヤーを重ねることで実現できます。 S3 Object Lambda を利用することで、追加のサーバー構築をすることなく、S3 のファイルに対してフィルタリングをかけることができます。 Lambda のパフォーマンス・チューニングについては、AWS Lambda Power Tuning を利用して可視化できます。外部 API などネットワーク依存(Network-intensive)ではなく、CPU 処理が中心(CPU-intensive)になると、コスパよく利用することができます。 S3 のファイルに対してなんらかの処理を実施したいと考えてる方は、一度 S3 Object Lambda の利用を検討してみてはいかがでしょうか? また、Lambda のチューニングポイントは色々ありますが、AWS Summit Online で紹介された以下のセッションがおすすめなので、もっと深くチューニングしたい方は是非ご覧ください! AWS Lambda Performance Tuning Deep Dive〜本当に知りたいのは”ここ”だった〜 さいごに メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しております。医療という分野においては、機微な情報を扱ったり診療という大事な業務を止めないよう、可用性、パフォーマンス、セキュリティともに高いサービスレベルを求められます。興味がある方は是非ご連絡ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp Footnotes 元画像に直接文字や図形などを描画することももちろん可能ですが、透かしを画像データとしてデザイン、管理したいこともあり、今回はアルファブレンドを選択した。 アルファブレンド - wikipedia ↩ pdfcpu API document: watermark example ↩ ただし最初の 60 億 GB 秒/月。昨今は円安なのでドル建てで利用しているインフラコストも上がっており、USD 表記だとコストが変わらないようにみえるので要注意。 AWS Lambda 料金 ↩ この UI は AWS Lambda Power Tuning UI というツールで別途提供されており、 lambda-power-tuning.show ドメインでアクセスできるので、自前で用意する必要はない。URL は https://lambda-power-tuning.show/#AAEAAgADAAQ=;xKDwRUqaakU5+RtFcXLqRA==;UqkHOPJCBDjA6AM46DAEOA==;AAEAAgADAAQ=;aYP6RdyeeUUQKiVFuC76RA==;ZDoNOJy3DDiJrQs4zhENOA==;100;50 のような形式でデータが URL のパスパラメータにエンコードされている。 ↩ この他にも strategy など様々なパラメータがある: aws-lambda-power-tuning: README-INPUT-OUTPUT.md ↩
はじめに みなさん、こんにちは。エンジニアの新居です。クラウド診療支援システム CLINICS が 8 月から大幅に アップデート して、より患者と医療機関が密接に繋がるようになりました。 今回のアップデートで中心になっている機能領域として PRM(Patient Relationship Management) というものがあります。 この PRM が一体どういったものなのか、プロジェクトはどのように進んでいったのかを関係者にインタビューしていきました。 メドレーで大規模なプロジェクト開発をどのように行なっているかの一端をお見せできればと考えています! インタビュイー紹介 宍戸さん 医療プラットフォーム第一本部 プロダクト開発室第二グループに所属。クラウド診療支援システム CLINICS の開発責任者。前職は株式会社サイバーエージェントでサーバサイドエンジニアとして従事。2017 年メドレー入社。 酒井さん 医療プラットフォーム第一本部 プロダクト開発室第二グループに所属。クラウド診療支援システム CLINICS の UI/UX 全般を担当。本プロジェクトのリード。前職は受託開発会社で様々なサイトやシステムのデザインに従事。2019 年メドレー入社。 田中さん 医療プラットフォーム CTO。医療プラットフォームの開発統括をしながら、自身は患者統合基盤の開発を担当。前職までは SIer や IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとして従事。2016 年メドレー入社。 来田さん 前職まではクリニックで精神科医として従事。CLINCS オンライン診療の立ち上げ時から関わる。現在は CLINICS のディレクションを中心として事業部と開発の架け橋となっている。 PRM とはどういうものなのか? 新居 : では今回中心となった PRM とはいったい何なのかというところから解説していただいて良いですか? 一般的にはそこまで知られてない言葉なのではないかと思うのですが…。 宍戸 : 元々 CRM(Customer Relationship Management)というマーケティングなどの文脈の用語があります。これは「自社サービスや商品を使っている顧客」と「サービスや商品を提供している企業」との関係性を管理することを指しますが、それをもとに適切なアクションをおこなうことで最終的には利益向上を目指すものと考えられます。 PRM は CRM と同様に医療機関と患者さんの関係性を管理することで必要なサービスを提供する ためのもの、と考えています。 CLINICS で基幹システムである電子カルテなどではなく、 オンライン診療・予約・問診など患者接点となる機能をターゲットにしてそれらを強化・改善 していったのが、CLINICS の今回の PRM プロジェクトになります。 CLINICS 開発責任者 宍戸さん 新居 : 確かに CRM は最近良く聞く言葉でしたが、PRM は患者と医療サービスを使う医療機関との関係を深めるためのものなんですね。 PRM プロジェクトの始まり CLINICS に感じていた課題とは 新居 : このプロジェクトは半年ほどかけていると聞きましたが、「PRM をやっていこう」となったのが半年前からだったんでしょうか? 宍戸 : 元々 CLINICS はオンライン診療のシステムとして 2016 年からサービスを提供してきました。2018 年からは CLINICS カルテとして電子カルテシステムも提供を開始しましたが、そのタイミング以降はオンライン診療部分に関してはそこまで大きくアップデートはしてこなかったんです。 現在はクラウド診療支援システムとして販売していますが、これまでは電子カルテなど基幹部分の強化がメインになっていたからです。しかし、数年開発・運用してきた中で、本来医療機関の診察のメインである対面診療の予約については課題が積み残されていたり、昨今の状況から来る オンライン診療のニーズなどを始めとして、患者接点を強化していく必要がある ということになり、今回取り組むことになりました。 酒井 : PRM というと本当にここ最近の潮流なのですが、 CLINICS はもともと「患者とつながる」をコンセプト としており、今回の機能強化などもこのコンセプトに基づき実施しています。その上で、患者とのタッチポイントで抜けていた部分や強化したほうが良い部分をより作り込んでいったという形です。 例えば、CLINICS は元々オンライン診療のシステムとして始まっており、対面診療予約に関して使いづらさを感じるケースがあったため、対面診療予約をより行いやすくするよう改修を行うなどです。 CLINICS デザイナー 酒井さん 新居 : 元々あったものをさらに患者とのタッチポイントを強化するために、改修・改善していったんですね。 プロジェクトの背景 どのくらいの規模のプロジェクトだったのか 新居 : 半年のプロジェクトということですが、規模としてはどの位の大きさだったんでしょうか? 酒井 : 私が経験したプロジェクトの中でも関わる部署など考えると一番大きかったと思います。開発はもちろんですが、CLINICS チームの セールス・カスタマーサクセス・マーケティング、広報もなのでほぼ CLINICS の全部署と関わっていました 。 田中 : 他にも、サービスとしては患者統合基盤・ 患者向けアプリ・ Dentis が関係しています。 宍戸 : QA チームにもしっかり入ってもらって QA を行なったりしてもらったので、本当に 医療プラットフォームのほぼ全てのチームと関わって開発していった 感じです。最終的にプロダクトの見せ方や売り方なども含めて調整していったので事業部とも密接に関わって作り上げていきました。 新しい PRM 機能をどうやって、プロダクトに融合させたのか 新居 : そこまで大きい規模のプロジェクトだということで、PRM の各種機能をどうやって上手くプロダクトに落としこむのか、実際にはどのようにみなさん推進されていったんでしょう。 宍戸 : プロジェクトの推進という意味では、最初は来田さんや( Dentis の運営を行なっている)プロダクト戦略室の平山さんと スモールスタートで意見を出し合いながらモックを作っていき、まずはビジュアル面から機能のイメージなどを刷り合わせ ていくのに、酒井さんに手を動かしてもらいながら進めていました。 その過程でこのプロジェクトにおける重要な意思決定がありました。CLINICS ではログイン不要のシンプルな予約機能を以前より提供していました。この機能は患者アプリとは元々違うドメインで運営していたものでしたが、今回のタイミングでこの両者のアプリケーションを患者アプリと同じドメインに統合して運営していこうという決定をしました。 患者アプリは CLINICS チームとは別チームで運営しているという背景もあり、チーム間の連携が格段に増え 、考えなければいけない事柄も多くなります。プロジェクトとして関係者が多くなってきたので、 今までの考え方を 1 度リセットしてプロジェクトの進め方を新しく模索する必要がありました 。 この点がかなり自分の中では大変でした。来田さんも大変だったと思います。こうした全体像を描いたり、全体スケジュールを決めたりというところでは、田中さんにアドバイスをもらいながらプロジェクトを進めていきました。 プロジェクトを推進するために必要だったこと 難しい意思決定をどのようにしていた? 新居 : 途中から一気にプロジェクトがスケールアップしたということなんですね。今回のプロジェクトは色々な部署も関わるし、たくさんの機能や改修が入っています。開発側と事業部側との意識合わせが、かなり必要だったんではないかと思うのですが、その辺はいかがだったでしょうか。 来田 : 例えば、今まで CLINICS では対面診療予約の際に患者のクレジットカード登録を必須としていたのですが、今回の改修でクレジットカード登録を不要に変更できるように対応を行いました。この対応は、CLINICS オンライン診療の初期から念願の機能だったりします。自分は事業部長である田中大介さんと今年の年明けくらいから話をして、メンバーに「よし、このプロジェクトをやっていこう」という話を持っていくようにしていました。やっぱり、大変なプロジェクトだというのはあったのでスタートを切れる状態に徐々にしていった感じです。 プロジェクトが佳境に入ってからは自分は各部署との調整がメインの業務でした。開発側と事業側とか CLINICS と Dentis だとかそういった立場の違いから来る要求の違いを納得できる仕様に落とし込む作業です。やっぱり、それぞれの立場でベストを尽くそうとみんな考えているんですが、全部を取り入れることはできないので。私が調整するときには それぞれ反対の立場の人たちの考えをきちんと伝えるようにということをしながら、仕様を決めていく ことを念頭に置いていました。 具体的な調整の例としては、開発する機能とセールスする際のプランの刷り合わせだったりですね。「機能としてはこうしたほうが使いやすいよね」というだけだと、セールス側としては「どこをウリにして売っていくのかというのが見えてこない」という話になったんですね。これはどちらの言い分も一理あるものなんですが、こういうところを時間をかけて 双方納得行くように決めていきました 。 CLINICS ディレクター・医師 来田さん 酒井 : この例でやったことは、まず現在のプランと機能を全部書き出して、組み合わせを整理するということでした。これをベースにして話を刷り合わせていったんですが、文字ベースだと人間分かった気になるんですが、実はそこまでピンと来ない。ですので、 LP のモックを作ってこれを見た顧客が導入したくなるようなウリになっているのかをすぐに判断できる状態 にしました。 宍戸 : こうしてちゃんと機能を解いていって細かい部分も含めて、時には CLINICS のコンセプトに立ち返って事業部・開発双方で時間をかけて認識を合わせることができたのは、大変でしたが良かったですね。 リリースまでのプロセスで顧客の声を反映 新居 : やはりここまでのプロジェクトだと最初の段階でちゃんとリリースまでを見据えた意識合わせをチーム内で行なっていたんですね。開発が進行していって、リリースするまではどのようにプロジェクトは進んでいったんでしょう。 宍戸 : リリースをする直前も、単純に仕様や機能について文書で説明しても理解するのは難しいので、酒井さんにディレクションをしてもらいながら、開発環境を使って QA チームやカスタマーサポートチームと一緒に認識合わせやリリースに向けた準備を行なっていきました。 来田 : あとは関係が構築できている 顧客に実際機能を見てもらって反応を伺う ということもしていきました。色々な使い方をしている顧客 10 件くらいにモックをベースにヒアリングをかけていきました。価格や導入時期なども含めた話を聞いていって、そこをすぐにプロジェクトにフィードバックする体制を構築していました。 酒井 : 実際に顧客の声をこうして聞けるというのは本当にプロダクトにとってありがたいことでした。特にこういう使い勝手などに直接関わる部分では、こうしたフィードバックの有無で完成度が違ってきます。 機能の正式リリース前から機能評価に協力していただける医療機関がいるのは、カスタマーサポートの皆さんが顧客と良好な関係を築けているおかげ だと思っています。こういった点はメドレーの強みだと思います。 プロジェクトで大切にしたこと 新居 : 開発段階からユーザーの声を吸い上げていけるのは、本当に良いですね。プロダクトの改善スピードなんかも段違いに早くなりそうです。他に大切にしていた部分などありますか? 酒井 : 今回の機能開発においては、プロジェクトの責任者として、開発以外のメンバーともしっかりコミュニケーションし合意形成を図ることを意識してプロジェクトを推進しました。 先ほどの話にも出ましたが 1 つの機能を実装するとなっても、実際の医療現場での使い勝手や、メドレーでのサポート、売り方など色々な視点で考えないといけないプロジェクトでした。そのため、そういったところをカスタマーサクセスのメンバー含む 10 人ほどと毎週 1 つ 1 つ納得がいくように詰めていくという作業をしていったりしました。 来田さんのように現役の医師の意見、顧客の意見なども取り入れられますし、カスタマーサポートには医療事務経験者の方もいらっしゃるので、多方面の視点を総合的に取り入れられるコミュニケーション を心がけていました。 手段はテキストや Figma でのデザイン、実際の実装を見せるなど適切な手段を選んでコミュニケーションコストを低くするようにして進行していました。 実際の機能実装にあたってはエンジニアも自身で考えて細かいボールを拾ったりしながら、実装 していってもらいました。この辺りはこのプロジェクトだからというよりも、メドレーの開発のベースになっている部分なので良いですよね。 メンバーのイメージ合わせがプロジェクト成功の鍵 新居 : 先ほどもお話がありましたが、プロジェクト途中の意思決定で関わるプロダクトが多くなったのでスイッチングが大変だったということでしたが、実際にそのタイミングになったときに全体のシステム設計なんかも変わったりしたんでしょうか。 田中 : 私はプロジェクトの途中から入ったんですが、そのタイミングで、宍戸さんや来田さんがちょうどシステム設計を考え直していたり、プロジェクト計画の引き直しをしているところでした。そこでまずは全体像を把握したいと考えてドキュメントなどを見せてもらったんです。各機能の細かい要件は分かったのですが、このプロジェクト全体としての各システムの責務や依存関係などがいまいち分かりづらく、メンバー間の認識も若干ズレが発生しているように感じました。なのでまずは**メンバーの認識を合わせ、各チームが何をするべきかを明確化するために、各システムの責務など全体感の整理整頓をして「見える化」**することから始めました。 このプロジェクトに限らずですが、プロジェクトとして抽象度の高い段階でのドキュメントは、文章のみの記載だと実は関係者にしっかりと伝わっていなかったということが多々あります。各自が頭の中でイメージした図に差異が発生している状態です。自分はそういう際に良く言っているのが**「大枠で良いので、まずは整理結果を図にしてみんなのイメージを合わせる**」ということです。こうして認識を合わせることで各チームが進めやすくなり後戻りリスクも軽減されるので、この段階でしっかりとイメージをすりあわせる事がとても重要だと考えています。 医療プラットフォーム CTO 田中さん 新居 : たしかにテキストで仕様なんかを書きながら説明しても、実は認識に齟齬があった…という経験があります。その後はどのようなプロセスでプロジェクト推進したんですか? 田中 : その後は各プロダクトにて実装予定の機能で、プロダクト間で共通化できそうな機能を共通サービスとして切り出すかどうかをみんなと考えていきました。結果として大きな機能では「ビデオ通話」部分を共通化するという意思決定をしました。他にも「問診票の提供」機能が候補としてあがっていましたが、共通化せずに各プロダクトの責務としてそれぞれ実装するという結論となりました。同じような機能に見えても、医科と歯科という業務ドメイン毎のコンテキスト差異を十分に検討した上での判断でした。 その差異がプロダクト機能としての強みとなり、安易な共通化はプロダクト成長の足枷となるリスクも存在 するためです。この判断も先ほどの全体の依存関係などが整理されたからこそ、意思決定できるものでした。 酒井 : 機能実装時に実感しましたが、やはり医科と歯科では細かい部分の要件などが異なる部分が多かったので、この段階で共通化の判断ができたのは大変助かりました。 PRM プロジェクトの大きな成果 新居 : 最後になりますが、このプロジェクトが終わっての成果はどのように考えていますか? 宍戸 : 冒頭に話した CLINICS の課題に関してはある程度、解決ができたと思います。 医療機関の業務に沿った形でプロダクトが進化 できた点はよかったと思います。特に問診周りなどは発熱外来などニーズの多様化に即して使い勝手が良くなったと思っています。 酒井 : 他にもビデオ通話部分など「オンライン診療」を提供するプロダクトとして「対面診療」だけではなく、もっと有用なプロダクトにできたかと思っています。 来田 : まだリリースしたばかりですが、初月の販売実績も期待していたよりも良く、そうした意味でも マーケットフィットした機能を出せたなという手応え が実感としてもありますね。 田中 : 予約やオンライン診療もドメインを統一した結果、会員登録数が増えていますので、そうした点も成果と言えます。 酒井 : あとは直接の成果というわけではないのですが、今回のプロジェクトの副産物として CLINICS の機能を整理整頓していった結果、 改めて「一気通貫で医療機関の業務をサポートできる」というトータルでの機能提供と各機能のシームレスな連携は CLINICS の強み だ、というのが再確認できたのは大きいです。 新居 : これからのさらなる CLINICS の発展に期待できますね!本日は長い時間お話ありがとうございました! おわりに メドレーとしてもかなり大規模なプロジェクトだった今回の PRM プロジェクトでしたが、みなさんのお話を聞いて各部署・各プロダクトで綿密に連携しながらプロジェクトを成功させようとしている様子が感じられるインタビューでした。 特にプロジェクト進行の話は非常に参考になりました。皆さんのご参考になる部分があれば幸いです。 こうしたプロジェクトで自分の力を発揮したい!と思った方はぜひお気軽に下記からご応募ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp
はじめに みなさん、こんにちは。エンジニアの新居です。クラウド診療支援システム CLINICS が 8 月から大幅に アップデート して、より患者と医療機関が密接に繋がるようになりました。 今回のアップデートで中心になっている機能領域として PRM(Patient Relationship Management) というものがあります。 この PRM が一体どういったものなのか、プロジェクトはどのように進んでいったのかを関係者にインタビューしていきました。 メドレーで大規模なプロジェクト開発をどのように行なっているかの一端をお見せできればと考えています! インタビュイー紹介 宍戸さん 医療プラットフォーム第一本部 プロダクト開発室第二グループに所属。クラウド診療支援システム CLINICS の開発責任者。前職は株式会社サイバーエージェントでサーバサイドエンジニアとして従事。2017 年メドレー入社。 酒井さん 医療プラットフォーム第一本部 プロダクト開発室第二グループに所属。クラウド診療支援システム CLINICS の UI/UX 全般を担当。本プロジェクトのリード。前職は受託開発会社で様々なサイトやシステムのデザインに従事。2019 年メドレー入社。 田中さん 医療プラットフォーム CTO。医療プラットフォームの開発統括をしながら、自身は患者統合基盤の開発を担当。前職までは SIer や IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとして従事。2016 年メドレー入社。 来田さん 前職まではクリニックで精神科医として従事。CLINCS オンライン診療の立ち上げ時から関わる。現在は CLINICS のディレクションを中心として事業部と開発の架け橋となっている。 PRM とはどういうものなのか? 新居 : では今回中心となった PRM とはいったい何なのかというところから解説していただいて良いですか? 一般的にはそこまで知られてない言葉なのではないかと思うのですが…。 宍戸 : 元々 CRM(Customer Relationship Management)というマーケティングなどの文脈の用語があります。これは「自社サービスや商品を使っている顧客」と「サービスや商品を提供している企業」との関係性を管理することを指しますが、それをもとに適切なアクションをおこなうことで最終的には利益向上を目指すものと考えられます。 PRM は CRM と同様に医療機関と患者さんの関係性を管理することで必要なサービスを提供する ためのもの、と考えています。 CLINICS で基幹システムである電子カルテなどではなく、 オンライン診療・予約・問診など患者接点となる機能をターゲットにしてそれらを強化・改善 していったのが、CLINICS の今回の PRM プロジェクトになります。 CLINICS 開発責任者 宍戸さん 新居 : 確かに CRM は最近良く聞く言葉でしたが、PRM は患者と医療サービスを使う医療機関との関係を深めるためのものなんですね。 PRM プロジェクトの始まり CLINICS に感じていた課題とは 新居 : このプロジェクトは半年ほどかけていると聞きましたが、「PRM をやっていこう」となったのが半年前からだったんでしょうか? 宍戸 : 元々 CLINICS はオンライン診療のシステムとして 2016 年からサービスを提供してきました。2018 年からは CLINICS カルテとして電子カルテシステムも提供を開始しましたが、そのタイミング以降はオンライン診療部分に関してはそこまで大きくアップデートはしてこなかったんです。 現在はクラウド診療支援システムとして販売していますが、これまでは電子カルテなど基幹部分の強化がメインになっていたからです。しかし、数年開発・運用してきた中で、本来医療機関の診察のメインである対面診療の予約については課題が積み残されていたり、昨今の状況から来る オンライン診療のニーズなどを始めとして、患者接点を強化していく必要がある ということになり、今回取り組むことになりました。 酒井 : PRM というと本当にここ最近の潮流なのですが、 CLINICS はもともと「患者とつながる」をコンセプト としており、今回の機能強化などもこのコンセプトに基づき実施しています。その上で、患者とのタッチポイントで抜けていた部分や強化したほうが良い部分をより作り込んでいったという形です。 例えば、CLINICS は元々オンライン診療のシステムとして始まっており、対面診療予約に関して使いづらさを感じるケースがあったため、対面診療予約をより行いやすくするよう改修を行うなどです。 CLINICS デザイナー 酒井さん 新居 : 元々あったものをさらに患者とのタッチポイントを強化するために、改修・改善していったんですね。 プロジェクトの背景 どのくらいの規模のプロジェクトだったのか 新居 : 半年のプロジェクトということですが、規模としてはどの位の大きさだったんでしょうか? 酒井 : 私が経験したプロジェクトの中でも関わる部署など考えると一番大きかったと思います。開発はもちろんですが、CLINICS チームの セールス・カスタマーサクセス・マーケティング、広報もなのでほぼ CLINICS の全部署と関わっていました 。 田中 : 他にも、サービスとしては患者統合基盤・ 患者向けアプリ・ Dentis が関係しています。 宍戸 : QA チームにもしっかり入ってもらって QA を行なったりしてもらったので、本当に 医療プラットフォームのほぼ全てのチームと関わって開発していった 感じです。最終的にプロダクトの見せ方や売り方なども含めて調整していったので事業部とも密接に関わって作り上げていきました。 新しい PRM 機能をどうやって、プロダクトに融合させたのか 新居 : そこまで大きい規模のプロジェクトだということで、PRM の各種機能をどうやって上手くプロダクトに落としこむのか、実際にはどのようにみなさん推進されていったんでしょう。 宍戸 : プロジェクトの推進という意味では、最初は来田さんや( Dentis の運営を行なっている)プロダクト戦略室の平山さんと スモールスタートで意見を出し合いながらモックを作っていき、まずはビジュアル面から機能のイメージなどを刷り合わせ ていくのに、酒井さんに手を動かしてもらいながら進めていました。 その過程でこのプロジェクトにおける重要な意思決定がありました。CLINICS ではログイン不要のシンプルな予約機能を以前より提供していました。この機能は患者アプリとは元々違うドメインで運営していたものでしたが、今回のタイミングでこの両者のアプリケーションを患者アプリと同じドメインに統合して運営していこうという決定をしました。 患者アプリは CLINICS チームとは別チームで運営しているという背景もあり、チーム間の連携が格段に増え 、考えなければいけない事柄も多くなります。プロジェクトとして関係者が多くなってきたので、 今までの考え方を 1 度リセットしてプロジェクトの進め方を新しく模索する必要がありました 。 この点がかなり自分の中では大変でした。来田さんも大変だったと思います。こうした全体像を描いたり、全体スケジュールを決めたりというところでは、田中さんにアドバイスをもらいながらプロジェクトを進めていきました。 プロジェクトを推進するために必要だったこと 難しい意思決定をどのようにしていた? 新居 : 途中から一気にプロジェクトがスケールアップしたということなんですね。今回のプロジェクトは色々な部署も関わるし、たくさんの機能や改修が入っています。開発側と事業部側との意識合わせが、かなり必要だったんではないかと思うのですが、その辺はいかがだったでしょうか。 来田 : 例えば、今まで CLINICS では対面診療予約の際に患者のクレジットカード登録を必須としていたのですが、今回の改修でクレジットカード登録を不要に変更できるように対応を行いました。この対応は、CLINICS オンライン診療の初期から念願の機能だったりします。自分は事業部長である田中大介さんと今年の年明けくらいから話をして、メンバーに「よし、このプロジェクトをやっていこう」という話を持っていくようにしていました。やっぱり、大変なプロジェクトだというのはあったのでスタートを切れる状態に徐々にしていった感じです。 プロジェクトが佳境に入ってからは自分は各部署との調整がメインの業務でした。開発側と事業側とか CLINICS と Dentis だとかそういった立場の違いから来る要求の違いを納得できる仕様に落とし込む作業です。やっぱり、それぞれの立場でベストを尽くそうとみんな考えているんですが、全部を取り入れることはできないので。私が調整するときには それぞれ反対の立場の人たちの考えをきちんと伝えるようにということをしながら、仕様を決めていく ことを念頭に置いていました。 具体的な調整の例としては、開発する機能とセールスする際のプランの刷り合わせだったりですね。「機能としてはこうしたほうが使いやすいよね」というだけだと、セールス側としては「どこをウリにして売っていくのかというのが見えてこない」という話になったんですね。これはどちらの言い分も一理あるものなんですが、こういうところを時間をかけて 双方納得行くように決めていきました 。 CLINICS ディレクター・医師 来田さん 酒井 : この例でやったことは、まず現在のプランと機能を全部書き出して、組み合わせを整理するということでした。これをベースにして話を刷り合わせていったんですが、文字ベースだと人間分かった気になるんですが、実はそこまでピンと来ない。ですので、 LP のモックを作ってこれを見た顧客が導入したくなるようなウリになっているのかをすぐに判断できる状態 にしました。 宍戸 : こうしてちゃんと機能を解いていって細かい部分も含めて、時には CLINICS のコンセプトに立ち返って事業部・開発双方で時間をかけて認識を合わせることができたのは、大変でしたが良かったですね。 リリースまでのプロセスで顧客の声を反映 新居 : やはりここまでのプロジェクトだと最初の段階でちゃんとリリースまでを見据えた意識合わせをチーム内で行なっていたんですね。開発が進行していって、リリースするまではどのようにプロジェクトは進んでいったんでしょう。 宍戸 : リリースをする直前も、単純に仕様や機能について文書で説明しても理解するのは難しいので、酒井さんにディレクションをしてもらいながら、開発環境を使って QA チームやカスタマーサポートチームと一緒に認識合わせやリリースに向けた準備を行なっていきました。 来田 : あとは関係が構築できている 顧客に実際機能を見てもらって反応を伺う ということもしていきました。色々な使い方をしている顧客 10 件くらいにモックをベースにヒアリングをかけていきました。価格や導入時期なども含めた話を聞いていって、そこをすぐにプロジェクトにフィードバックする体制を構築していました。 酒井 : 実際に顧客の声をこうして聞けるというのは本当にプロダクトにとってありがたいことでした。特にこういう使い勝手などに直接関わる部分では、こうしたフィードバックの有無で完成度が違ってきます。 機能の正式リリース前から機能評価に協力していただける医療機関がいるのは、カスタマーサポートの皆さんが顧客と良好な関係を築けているおかげ だと思っています。こういった点はメドレーの強みだと思います。 プロジェクトで大切にしたこと 新居 : 開発段階からユーザーの声を吸い上げていけるのは、本当に良いですね。プロダクトの改善スピードなんかも段違いに早くなりそうです。他に大切にしていた部分などありますか? 酒井 : 今回の機能開発においては、プロジェクトの責任者として、開発以外のメンバーともしっかりコミュニケーションし合意形成を図ることを意識してプロジェクトを推進しました。 先ほどの話にも出ましたが 1 つの機能を実装するとなっても、実際の医療現場での使い勝手や、メドレーでのサポート、売り方など色々な視点で考えないといけないプロジェクトでした。そのため、そういったところをカスタマーサクセスのメンバー含む 10 人ほどと毎週 1 つ 1 つ納得がいくように詰めていくという作業をしていったりしました。 来田さんのように現役の医師の意見、顧客の意見なども取り入れられますし、カスタマーサポートには医療事務経験者の方もいらっしゃるので、多方面の視点を総合的に取り入れられるコミュニケーション を心がけていました。 手段はテキストや Figma でのデザイン、実際の実装を見せるなど適切な手段を選んでコミュニケーションコストを低くするようにして進行していました。 実際の機能実装にあたってはエンジニアも自身で考えて細かいボールを拾ったりしながら、実装 していってもらいました。この辺りはこのプロジェクトだからというよりも、メドレーの開発のベースになっている部分なので良いですよね。 メンバーのイメージ合わせがプロジェクト成功の鍵 新居 : 先ほどもお話がありましたが、プロジェクト途中の意思決定で関わるプロダクトが多くなったのでスイッチングが大変だったということでしたが、実際にそのタイミングになったときに全体のシステム設計なんかも変わったりしたんでしょうか。 田中 : 私はプロジェクトの途中から入ったんですが、そのタイミングで、宍戸さんや来田さんがちょうどシステム設計を考え直していたり、プロジェクト計画の引き直しをしているところでした。そこでまずは全体像を把握したいと考えてドキュメントなどを見せてもらったんです。各機能の細かい要件は分かったのですが、このプロジェクト全体としての各システムの責務や依存関係などがいまいち分かりづらく、メンバー間の認識も若干ズレが発生しているように感じました。なのでまずは**メンバーの認識を合わせ、各チームが何をするべきかを明確化するために、各システムの責務など全体感の整理整頓をして「見える化」**することから始めました。 このプロジェクトに限らずですが、プロジェクトとして抽象度の高い段階でのドキュメントは、文章のみの記載だと実は関係者にしっかりと伝わっていなかったということが多々あります。各自が頭の中でイメージした図に差異が発生している状態です。自分はそういう際に良く言っているのが**「大枠で良いので、まずは整理結果を図にしてみんなのイメージを合わせる**」ということです。こうして認識を合わせることで各チームが進めやすくなり後戻りリスクも軽減されるので、この段階でしっかりとイメージをすりあわせる事がとても重要だと考えています。 医療プラットフォーム CTO 田中さん 新居 : たしかにテキストで仕様なんかを書きながら説明しても、実は認識に齟齬があった…という経験があります。その後はどのようなプロセスでプロジェクト推進したんですか? 田中 : その後は各プロダクトにて実装予定の機能で、プロダクト間で共通化できそうな機能を共通サービスとして切り出すかどうかをみんなと考えていきました。結果として大きな機能では「ビデオ通話」部分を共通化するという意思決定をしました。他にも「問診票の提供」機能が候補としてあがっていましたが、共通化せずに各プロダクトの責務としてそれぞれ実装するという結論となりました。同じような機能に見えても、医科と歯科という業務ドメイン毎のコンテキスト差異を十分に検討した上での判断でした。 その差異がプロダクト機能としての強みとなり、安易な共通化はプロダクト成長の足枷となるリスクも存在 するためです。この判断も先ほどの全体の依存関係などが整理されたからこそ、意思決定できるものでした。 酒井 : 機能実装時に実感しましたが、やはり医科と歯科では細かい部分の要件などが異なる部分が多かったので、この段階で共通化の判断ができたのは大変助かりました。 PRM プロジェクトの大きな成果 新居 : 最後になりますが、このプロジェクトが終わっての成果はどのように考えていますか? 宍戸 : 冒頭に話した CLINICS の課題に関してはある程度、解決ができたと思います。 医療機関の業務に沿った形でプロダクトが進化 できた点はよかったと思います。特に問診周りなどは発熱外来などニーズの多様化に即して使い勝手が良くなったと思っています。 酒井 : 他にもビデオ通話部分など「オンライン診療」を提供するプロダクトとして「対面診療」だけではなく、もっと有用なプロダクトにできたかと思っています。 来田 : まだリリースしたばかりですが、初月の販売実績も期待していたよりも良く、そうした意味でも マーケットフィットした機能を出せたなという手応え が実感としてもありますね。 田中 : 予約やオンライン診療もドメインを統一した結果、会員登録数が増えていますので、そうした点も成果と言えます。 酒井 : あとは直接の成果というわけではないのですが、今回のプロジェクトの副産物として CLINICS の機能を整理整頓していった結果、 改めて「一気通貫で医療機関の業務をサポートできる」というトータルでの機能提供と各機能のシームレスな連携は CLINICS の強み だ、というのが再確認できたのは大きいです。 新居 : これからのさらなる CLINICS の発展に期待できますね!本日は長い時間お話ありがとうございました! おわりに メドレーとしてもかなり大規模なプロジェクトだった今回の PRM プロジェクトでしたが、みなさんのお話を聞いて各部署・各プロダクトで綿密に連携しながらプロジェクトを成功させようとしている様子が感じられるインタビューでした。 特にプロジェクト進行の話は非常に参考になりました。皆さんのご参考になる部分があれば幸いです。 こうしたプロジェクトで自分の力を発揮したい!と思った方はぜひお気軽に下記からご応募ください。 https://www.medley.jp/jobs/
はじめに みなさん、こんにちは。エンジニアの新居です。クラウド診療支援システム CLINICS が 8 月から大幅に アップデート して、より患者と医療機関が密接に繋がるようになりました。 今回のアップデートで中心になっている機能領域として PRM(Patient Relationship Management) というものがあります。 この PRM が一体どういったものなのか、プロジェクトはどのように進んでいったのかを関係者にインタビューしていきました。 メドレーで大規模なプロジェクト開発をどのように行なっているかの一端をお見せできればと考えています! インタビュイー紹介 宍戸さん 医療プラットフォーム第一本部 プロダクト開発室第二グループに所属。クラウド診療支援システム CLINICS の開発責任者。前職は株式会社サイバーエージェントでサーバサイドエンジニアとして従事。2017 年メドレー入社。 酒井さん 医療プラットフォーム第一本部 プロダクト開発室第二グループに所属。クラウド診療支援システム CLINICS の UI/UX 全般を担当。本プロジェクトのリード。前職は受託開発会社で様々なサイトやシステムのデザインに従事。2019 年メドレー入社。 田中さん 医療プラットフォーム CTO。医療プラットフォームの開発統括をしながら、自身は患者統合基盤の開発を担当。前職までは SIer や IT コンサルタントを経て、株式会社サイバーエージェントでサーバサイドエンジニアとして従事。2016 年メドレー入社。 来田さん 前職まではクリニックで精神科医として従事。CLINCS オンライン診療の立ち上げ時から関わる。現在は CLINICS のディレクションを中心として事業部と開発の架け橋となっている。 PRM とはどういうものなのか? 新居 : では今回中心となった PRM とはいったい何なのかというところから解説していただいて良いですか? 一般的にはそこまで知られてない言葉なのではないかと思うのですが…。 宍戸 : 元々 CRM(Customer Relationship Management)というマーケティングなどの文脈の用語があります。これは「自社サービスや商品を使っている顧客」と「サービスや商品を提供している企業」との関係性を管理することを指しますが、それをもとに適切なアクションをおこなうことで最終的には利益向上を目指すものと考えられます。 PRM は CRM と同様に医療機関と患者さんの関係性を管理することで必要なサービスを提供する ためのもの、と考えています。 CLINICS で基幹システムである電子カルテなどではなく、 オンライン診療・予約・問診など患者接点となる機能をターゲットにしてそれらを強化・改善 していったのが、CLINICS の今回の PRM プロジェクトになります。 CLINICS 開発責任者 宍戸さん 新居 : 確かに CRM は最近良く聞く言葉でしたが、PRM は患者と医療サービスを使う医療機関との関係を深めるためのものなんですね。 PRM プロジェクトの始まり CLINICS に感じていた課題とは 新居 : このプロジェクトは半年ほどかけていると聞きましたが、「PRM をやっていこう」となったのが半年前からだったんでしょうか? 宍戸 : 元々 CLINICS はオンライン診療のシステムとして 2016 年からサービスを提供してきました。2018 年からは CLINICS カルテとして電子カルテシステムも提供を開始しましたが、そのタイミング以降はオンライン診療部分に関してはそこまで大きくアップデートはしてこなかったんです。 現在はクラウド診療支援システムとして販売していますが、これまでは電子カルテなど基幹部分の強化がメインになっていたからです。しかし、数年開発・運用してきた中で、本来医療機関の診察のメインである対面診療の予約については課題が積み残されていたり、昨今の状況から来る オンライン診療のニーズなどを始めとして、患者接点を強化していく必要がある ということになり、今回取り組むことになりました。 酒井 : PRM というと本当にここ最近の潮流なのですが、 CLINICS はもともと「患者とつながる」をコンセプト としており、今回の機能強化などもこのコンセプトに基づき実施しています。その上で、患者とのタッチポイントで抜けていた部分や強化したほうが良い部分をより作り込んでいったという形です。 例えば、CLINICS は元々オンライン診療のシステムとして始まっており、対面診療予約に関して使いづらさを感じるケースがあったため、対面診療予約をより行いやすくするよう改修を行うなどです。 CLINICS デザイナー 酒井さん 新居 : 元々あったものをさらに患者とのタッチポイントを強化するために、改修・改善していったんですね。 プロジェクトの背景 どのくらいの規模のプロジェクトだったのか 新居 : 半年のプロジェクトということですが、規模としてはどの位の大きさだったんでしょうか? 酒井 : 私が経験したプロジェクトの中でも関わる部署など考えると一番大きかったと思います。開発はもちろんですが、CLINICS チームの セールス・カスタマーサクセス・マーケティング、広報もなのでほぼ CLINICS の全部署と関わっていました 。 田中 : 他にも、サービスとしては患者統合基盤・ 患者向けアプリ・ Dentis が関係しています。 宍戸 : QA チームにもしっかり入ってもらって QA を行なったりしてもらったので、本当に 医療プラットフォームのほぼ全てのチームと関わって開発していった 感じです。最終的にプロダクトの見せ方や売り方なども含めて調整していったので事業部とも密接に関わって作り上げていきました。 新しい PRM 機能をどうやって、プロダクトに融合させたのか 新居 : そこまで大きい規模のプロジェクトだということで、PRM の各種機能をどうやって上手くプロダクトに落としこむのか、実際にはどのようにみなさん推進されていったんでしょう。 宍戸 : プロジェクトの推進という意味では、最初は来田さんや( Dentis の運営を行なっている)プロダクト戦略室の平山さんと スモールスタートで意見を出し合いながらモックを作っていき、まずはビジュアル面から機能のイメージなどを刷り合わせ ていくのに、酒井さんに手を動かしてもらいながら進めていました。 その過程でこのプロジェクトにおける重要な意思決定がありました。CLINICS ではログイン不要のシンプルな予約機能を以前より提供していました。この機能は患者アプリとは元々違うドメインで運営していたものでしたが、今回のタイミングでこの両者のアプリケーションを患者アプリと同じドメインに統合して運営していこうという決定をしました。 患者アプリは CLINICS チームとは別チームで運営しているという背景もあり、チーム間の連携が格段に増え 、考えなければいけない事柄も多くなります。プロジェクトとして関係者が多くなってきたので、 今までの考え方を 1 度リセットしてプロジェクトの進め方を新しく模索する必要がありました 。 この点がかなり自分の中では大変でした。来田さんも大変だったと思います。こうした全体像を描いたり、全体スケジュールを決めたりというところでは、田中さんにアドバイスをもらいながらプロジェクトを進めていきました。 プロジェクトを推進するために必要だったこと 難しい意思決定をどのようにしていた? 新居 : 途中から一気にプロジェクトがスケールアップしたということなんですね。今回のプロジェクトは色々な部署も関わるし、たくさんの機能や改修が入っています。開発側と事業部側との意識合わせが、かなり必要だったんではないかと思うのですが、その辺はいかがだったでしょうか。 来田 : 例えば、今まで CLINICS では対面診療予約の際に患者のクレジットカード登録を必須としていたのですが、今回の改修でクレジットカード登録を不要に変更できるように対応を行いました。この対応は、CLINICS オンライン診療の初期から念願の機能だったりします。自分は事業部長である田中大介さんと今年の年明けくらいから話をして、メンバーに「よし、このプロジェクトをやっていこう」という話を持っていくようにしていました。やっぱり、大変なプロジェクトだというのはあったのでスタートを切れる状態に徐々にしていった感じです。 プロジェクトが佳境に入ってからは自分は各部署との調整がメインの業務でした。開発側と事業側とか CLINICS と Dentis だとかそういった立場の違いから来る要求の違いを納得できる仕様に落とし込む作業です。やっぱり、それぞれの立場でベストを尽くそうとみんな考えているんですが、全部を取り入れることはできないので。私が調整するときには それぞれ反対の立場の人たちの考えをきちんと伝えるようにということをしながら、仕様を決めていく ことを念頭に置いていました。 具体的な調整の例としては、開発する機能とセールスする際のプランの刷り合わせだったりですね。「機能としてはこうしたほうが使いやすいよね」というだけだと、セールス側としては「どこをウリにして売っていくのかというのが見えてこない」という話になったんですね。これはどちらの言い分も一理あるものなんですが、こういうところを時間をかけて 双方納得行くように決めていきました 。 CLINICS ディレクター・医師 来田さん 酒井 : この例でやったことは、まず現在のプランと機能を全部書き出して、組み合わせを整理するということでした。これをベースにして話を刷り合わせていったんですが、文字ベースだと人間分かった気になるんですが、実はそこまでピンと来ない。ですので、 LP のモックを作ってこれを見た顧客が導入したくなるようなウリになっているのかをすぐに判断できる状態 にしました。 宍戸 : こうしてちゃんと機能を解いていって細かい部分も含めて、時には CLINICS のコンセプトに立ち返って事業部・開発双方で時間をかけて認識を合わせることができたのは、大変でしたが良かったですね。 リリースまでのプロセスで顧客の声を反映 新居 : やはりここまでのプロジェクトだと最初の段階でちゃんとリリースまでを見据えた意識合わせをチーム内で行なっていたんですね。開発が進行していって、リリースするまではどのようにプロジェクトは進んでいったんでしょう。 宍戸 : リリースをする直前も、単純に仕様や機能について文書で説明しても理解するのは難しいので、酒井さんにディレクションをしてもらいながら、開発環境を使って QA チームやカスタマーサポートチームと一緒に認識合わせやリリースに向けた準備を行なっていきました。 来田 : あとは関係が構築できている 顧客に実際機能を見てもらって反応を伺う ということもしていきました。色々な使い方をしている顧客 10 件くらいにモックをベースにヒアリングをかけていきました。価格や導入時期なども含めた話を聞いていって、そこをすぐにプロジェクトにフィードバックする体制を構築していました。 酒井 : 実際に顧客の声をこうして聞けるというのは本当にプロダクトにとってありがたいことでした。特にこういう使い勝手などに直接関わる部分では、こうしたフィードバックの有無で完成度が違ってきます。 機能の正式リリース前から機能評価に協力していただける医療機関がいるのは、カスタマーサポートの皆さんが顧客と良好な関係を築けているおかげ だと思っています。こういった点はメドレーの強みだと思います。 プロジェクトで大切にしたこと 新居 : 開発段階からユーザーの声を吸い上げていけるのは、本当に良いですね。プロダクトの改善スピードなんかも段違いに早くなりそうです。他に大切にしていた部分などありますか? 酒井 : 今回の機能開発においては、プロジェクトの責任者として、開発以外のメンバーともしっかりコミュニケーションし合意形成を図ることを意識してプロジェクトを推進しました。 先ほどの話にも出ましたが 1 つの機能を実装するとなっても、実際の医療現場での使い勝手や、メドレーでのサポート、売り方など色々な視点で考えないといけないプロジェクトでした。そのため、そういったところをカスタマーサクセスのメンバー含む 10 人ほどと毎週 1 つ 1 つ納得がいくように詰めていくという作業をしていったりしました。 来田さんのように現役の医師の意見、顧客の意見なども取り入れられますし、カスタマーサポートには医療事務経験者の方もいらっしゃるので、多方面の視点を総合的に取り入れられるコミュニケーション を心がけていました。 手段はテキストや Figma でのデザイン、実際の実装を見せるなど適切な手段を選んでコミュニケーションコストを低くするようにして進行していました。 実際の機能実装にあたってはエンジニアも自身で考えて細かいボールを拾ったりしながら、実装 していってもらいました。この辺りはこのプロジェクトだからというよりも、メドレーの開発のベースになっている部分なので良いですよね。 メンバーのイメージ合わせがプロジェクト成功の鍵 新居 : 先ほどもお話がありましたが、プロジェクト途中の意思決定で関わるプロダクトが多くなったのでスイッチングが大変だったということでしたが、実際にそのタイミングになったときに全体のシステム設計なんかも変わったりしたんでしょうか。 田中 : 私はプロジェクトの途中から入ったんですが、そのタイミングで、宍戸さんや来田さんがちょうどシステム設計を考え直していたり、プロジェクト計画の引き直しをしているところでした。そこでまずは全体像を把握したいと考えてドキュメントなどを見せてもらったんです。各機能の細かい要件は分かったのですが、このプロジェクト全体としての各システムの責務や依存関係などがいまいち分かりづらく、メンバー間の認識も若干ズレが発生しているように感じました。なのでまずは**メンバーの認識を合わせ、各チームが何をするべきかを明確化するために、各システムの責務など全体感の整理整頓をして「見える化」**することから始めました。 このプロジェクトに限らずですが、プロジェクトとして抽象度の高い段階でのドキュメントは、文章のみの記載だと実は関係者にしっかりと伝わっていなかったということが多々あります。各自が頭の中でイメージした図に差異が発生している状態です。自分はそういう際に良く言っているのが**「大枠で良いので、まずは整理結果を図にしてみんなのイメージを合わせる**」ということです。こうして認識を合わせることで各チームが進めやすくなり後戻りリスクも軽減されるので、この段階でしっかりとイメージをすりあわせる事がとても重要だと考えています。 医療プラットフォーム CTO 田中さん 新居 : たしかにテキストで仕様なんかを書きながら説明しても、実は認識に齟齬があった…という経験があります。その後はどのようなプロセスでプロジェクト推進したんですか? 田中 : その後は各プロダクトにて実装予定の機能で、プロダクト間で共通化できそうな機能を共通サービスとして切り出すかどうかをみんなと考えていきました。結果として大きな機能では「ビデオ通話」部分を共通化するという意思決定をしました。他にも「問診票の提供」機能が候補としてあがっていましたが、共通化せずに各プロダクトの責務としてそれぞれ実装するという結論となりました。同じような機能に見えても、医科と歯科という業務ドメイン毎のコンテキスト差異を十分に検討した上での判断でした。 その差異がプロダクト機能としての強みとなり、安易な共通化はプロダクト成長の足枷となるリスクも存在 するためです。この判断も先ほどの全体の依存関係などが整理されたからこそ、意思決定できるものでした。 酒井 : 機能実装時に実感しましたが、やはり医科と歯科では細かい部分の要件などが異なる部分が多かったので、この段階で共通化の判断ができたのは大変助かりました。 PRM プロジェクトの大きな成果 新居 : 最後になりますが、このプロジェクトが終わっての成果はどのように考えていますか? 宍戸 : 冒頭に話した CLINICS の課題に関してはある程度、解決ができたと思います。 医療機関の業務に沿った形でプロダクトが進化 できた点はよかったと思います。特に問診周りなどは発熱外来などニーズの多様化に即して使い勝手が良くなったと思っています。 酒井 : 他にもビデオ通話部分など「オンライン診療」を提供するプロダクトとして「対面診療」だけではなく、もっと有用なプロダクトにできたかと思っています。 来田 : まだリリースしたばかりですが、初月の販売実績も期待していたよりも良く、そうした意味でも マーケットフィットした機能を出せたなという手応え が実感としてもありますね。 田中 : 予約やオンライン診療もドメインを統一した結果、会員登録数が増えていますので、そうした点も成果と言えます。 酒井 : あとは直接の成果というわけではないのですが、今回のプロジェクトの副産物として CLINICS の機能を整理整頓していった結果、 改めて「一気通貫で医療機関の業務をサポートできる」というトータルでの機能提供と各機能のシームレスな連携は CLINICS の強み だ、というのが再確認できたのは大きいです。 新居 : これからのさらなる CLINICS の発展に期待できますね!本日は長い時間お話ありがとうございました! おわりに メドレーとしてもかなり大規模なプロジェクトだった今回の PRM プロジェクトでしたが、みなさんのお話を聞いて各部署・各プロダクトで綿密に連携しながらプロジェクトを成功させようとしている様子が感じられるインタビューでした。 特にプロジェクト進行の話は非常に参考になりました。皆さんのご参考になる部分があれば幸いです。 こうしたプロジェクトで自分の力を発揮したい!と思った方はぜひお気軽に下記からご応募ください。 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 www.medley.jp