TECH PLAY

株式会社メドレー

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

1359

はじめに こんにちは。コーポレートデザイン部でコーポレート 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 チームの活躍の余地がたくさん存在しています。 そのため、未来志向で #革新と改善を主導 できる方を絶賛募集しております。 https://www.medley.jp/jobs/corporate-design.html メドレーでは、医療分野の社会課題を IT にて解決するために日々邁進しております。様々な業種でスペシャリストを募集しています。興味がある方は是非ご連絡ください。医療という社会貢献性の高い領域へ、一緒に挑戦しませんか? https://www.medley.jp/jobs/
アバター
はじめに こんにちは。コーポレートデザイン部でコーポレート 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
アバター
はじめに こんにちは。コーポレートデザイン部でコーポレート 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
アバター
はじめに こんにちは。コーポレートデザイン部でコーポレート 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
アバター
はじめに こんにちは。コーポレートデザイン部でコーポレート 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 でのアプリ開発に強い方だと嬉しいですね。ユーザーに一番使われているのはアプリなので、さらにそのユーザビリティなどを高めるためには、アプリ開発に強い方に入ってもらえるとありがたいです。 山田 : よりアカデミーというプロダクトをドライブさせてくれる方にぜひ来てほしいところですね。本日はありがとうございました! おわりに メドレーの開発チームの中でも野心的なアーキテクチャで開発をしているアカデミーチームについてお伝えしました。 サービスを伸ばすために、適切に言語やフレームワークを選択していき、柔軟性を持って開発していってるんだなというのが伝わってきたインタビューでした。少しでも興味が出た方はぜひ、下のリンクからお話できればと思います! 募集の一覧 | 株式会社メドレー メドレーの採用情報はこちらからご確認ください。 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
アバター
こんにちは。医療プラットフォーム本部プロダクト開発室エンジニアの中畑です。主にオンライン診療・服薬指導アプリ 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 にて解決するために日々邁進しております。医療という分野においては、機微な情報を扱ったり診療という大事な業務を止めないよう、可用性、パフォーマンス、セキュリティともに高いサービスレベルを求められます。興味がある方は是非ご連絡ください。 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 ↩
アバター