概要
こんにちは!私はカケハシにて薬局と患者様の関係を向上させるためのオペレーションツールである 患者リスト
の開発を担当している小室と申します。患者リスト
の立ち上げから担当させて頂いており、技術選定などのお話は先日はTechPlayでも登壇させて頂きました。( 登壇資料 )
本記事ではそんな 患者リスト
から、弊社のメインプロダクトとなる Musubi へDeepLinkで連携した際の知見をまとめたいと思います。



背景
そもそもなぜDeepLinkが必要になったかというと、患者リスト
では患者様をリストアップする機能があるのですが、その患者様が薬局に来局された際の処方内容や服薬指導内容等の詳細なログは薬歴業務システムである Musubi
で閲覧する事ができます。Musubi
は、Electronで作成されたDesktopアプリケーションになりますので、Musubi
の患者詳細ページにDeepLinkで遷移させることができれば、ユーザである薬剤師様の操作効率を大きく向上させる事ができると考えられました。
制約
Musubi
は弊社の基幹ツールであり、非常に多くの顧客と開発ロードマップを抱えています。そんな中、新興プロダクトである患者リスト
でにわかに高まったDeepLink機能の実装にすぐに取り組む事はプランニングとして難しい状況でした。
そこで、Musubi
の開発責任者 とご相談した所、私がDeepLinkの機能を実装しMusubi
にPRを送らせて頂くことで快諾して頂けました。
レビュー工数などは捻出して頂く前提なので、組織全体の価値貢献に向けて柔軟に対応できる点がカケハシの開発組織の強みだと改めて実感しました。
DeepLinkの基本
今回は Webアプリケーション => Electron製のDesktopアプリケーション
へのDeepLinkになります。
まず、仕組みから説明すると、Custom URL Scheme
で該当するアプリ(Musubi
)を起動してから、アプリ内でURLを解析してパラメータに従ったページ遷移や状態の復元を処理する流れになります。
Custom URL Scheme
というのはSlackのDeepLinkだと slack://~~~
という様なURLになりますが、カスタムスキーマに対して起動するアプリケーションをOSに事前に登録しておくことで、カスタムURLをクリックした時にOSが処理して該当のアプリケーションを起動してくれます。また、OSへの登録部分で、Electron場合はアプリケーションの初回起動時にOSに登録してくれる機能があります。
今回はMusubi
を起動したいので、musubi://
というカスタムスキーマを作成しました。
また、Electronは基本的に Main Process と Renderer Process の 2種類のプロセス が存在します。 詳細は省略しますが、Main ProcessはWindowを立ち上げ、Renderer ProcessはWindow内のアプリケーションを実行します。DeepLinkなどのOSからの制御は Main Process で受け取って、プロセス間通信 の仕組みで Renderer Process に送信する事で動作します。
考慮点
今回考慮が必要だった点としては、上記で説明した Main Process が複数存在する存在する場合に、DeepLinkでの遷移先のプロセス(window)をどの様に選択すればよいか?という部分です。
Electronが起動する Main Process の数はOSによっても挙動に違いがあります。
Macの場合 はFinderなどから起動した際には、Main Process がシングルインスタンスになる様にOSが制約をかけてアプリケーションを起動してくれます。そのため、Finderからアイコンを押しても、既存のwindowがある場合は、フォーカスが映るだけで複数のwindowが立ち上がることはありません(コマンドラインから実行した場合など例外があります)。
一方、Windowsの場合はシングルインスタンスを強制する仕様はないので、アプリを起動する際にwindowが都度立ち上がります。
複数の Main Process が乱立すると考慮点が増えてしまいますが、Electronにはシングルインスタンスロックを実装するために 二重で開いている事を検知する仕組 もあります。
今回対象になるMusubiではシングルインスタンスロックをかけていない為、メインプロセスをどう選択するのか?を考慮する必要がありました。
Main Process の実装
- Macの場合
// src/index.ts app.on('open-url', (e, url) => { mainWindow.webContents.send(CustomProtocol.ipcEventName, { ...パラメータ類 }); });
Macの場合はもともとシングルインスタンスで運用されている前提なので、DeepLinkを押した場合は、既に開いている Main Process の open-url
イベントが呼ばれます。この中で、mainWindow.webContents.send
を利用して、Main Process => Renderer Process
にプロセス間通信の仕組みでイベントを送ります。(mainWindowは Renderer Process です。)
- Windowsの場合
// DeepLinkで起動されているかを判定する const deepLinkExp = new RegExp(` expression `); const isDeepLinkCommand = process.argv.some(cmd => deepLinkExp.test(cmd)); // ロックを取得できているか確認する const lock = app.requestSingleInstanceLock(); // ① if (isDeepLinkCommand && !lock) app.quit();
WindowsではDeepLinkを押した時に新しい window(=Main Process) が立ち上がろうとします。しかしながら、既存の動作を尊重する場合、複数windowを開いてもよい仕様なので、①部分ではDeepLinkによって新規windowが立ち上がろうとしたときだけ、そのwindowをapp.quit()
で閉じています。すると、今度はロックを持っているインスタンス側で second-instance
イベントが呼ばれます。
app.on('second-instance', (e, commandLine) => { const isDeepLinkCommand = commandLine.find(cmd => deepLinkExp.test(cmd)); if (isDeepLinkCommand) { mainWindow.webContents.send(CustomProtocol.ipcEventName, { ...パラメータ類 }); // ② ウィンドウにフォーカスする if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); } });
second-instance
イベントには直前で終了したインスタンスの起動パラメータが渡されます。ここでは、Macのときと同様に Renderer Process にイベントを送ります。その後、windowsの画面上で複数のwindowが重なっている可能性がるので、②の部分でイベントを送ったwindowにフォーカスを移します。
まとめ
以上により、シングルインスタンスロックをしていないElectronのDesktopアプリケーションに、DeepLinkを実装することができました。
最後に
この機能を実装した当時私はまだ入社して6ヶ月程の少し手探りな時期で、初めて弊社の基幹システムであるMusubi
との連携案件だったので正直身構えてしまう気持ちもありました。しかし、Musubi
の開発責任者の方を始め多くの方にサポート頂いた結果、当初懸念していた部分な組織的なあれこれはなく順調に機能リリースをする事ができました。この事から、改めてですがシステム間連携の様なチームを跨ぐ施策は、個々のエンジニアがお客様への価値貢献や全体最適を優先していく高潔さが推進していくのだと実感しました。
カケハシでは現在エンジニアの積極な採用を行っておりますので、価値貢献を重視するエンジニア組織に興味のある方がいましたら、是非採用ページの方からエントリーをお待ちしております。