フロントエンドエンジニアの小林和弘です。
kakari という薬局向けに提供しているサービスで Electron を使って Windows アプリケーションを作成したので、そのことについてお話しようかと思います。
事の発端
これまで、「kakari」では、患者さまから薬局へ処方せんが送信されたときに薬剤師さまが処方せんの受信に気づけるように FAX で処方せんを送信していました。
しかし、FAX 送信に利用していた Twilio Programmable Fax サービスが 2021 年 12 月 17 日をもってサービス終了になってしまいました。
このサービス終了に対応すべく、薬局向けに処方せんが送信されたらプリンターで処方せん印刷ができる Windows アプリケーションを Electron で作成することになりました。
そもそもElectronとは
HTML, CSS, JS のフロントエンドの技術だけで Windows, Mac, Linux 向けにアプリケーションを作成できるツールです。
アプリケーションの中身は Node.js と Chromium ブラウザになっています。画面表示には Chromium ブラウザが使われ、OS 側の処理は Node.js で行えるようになっています。
有名なアプリケーションだと Slack や Discord、VSCode なども Electron で作成されています。
なぜ Electron なのか
ブラウザにも印刷機能は搭載されていますが、JavaScript 上で window.print()
を実行すると印刷ダイアログが表示されて、ダイアログ内の OK ボタンを押す必要があります。薬局の業務中に薬剤師さまにこの作業をしていただくのは現実的ではありません。
これを回避する方法として、--kiosk-printing
オプションを付けた状態で Chrome ブラウザを起動するという突破口はありますが、すべての薬局が必ず Chrome を導入しているわけではないのと、すでに導入いただいている多くの店舗に導入支援することのコストの高さから、Web アプリは選択肢から外れています。
外部の会社に Windows アプリを開発していただくという選択肢もありました(実際に何社かに概算見積もりを出していただきました)が、運用も視野に入れるとフロントエンド技術で保守できる Electron が良いだろうという結論に至りました。
Electron の世界
Electron は先ほども述べた通り、ブラウザと Node.js で実装されています。Node.js 側からブラウザに命令を飛ばすことも、ブラウザ側から Node.js 側に命令を飛ばすこともできます。
当然、データのやりとりもできるわけですが、OS 側の処理を担う Node.js にブラウザ側から直接アクセスできる状態というのは非常に危険な状態です。
この危険な状態を避けるために、Electron ではコンテキストの分離というものを行っています。
コンテキスト分離を説明する前に Electron のプロセスモデルについて軽く説明します。
Electron には 2 つのプロセスが存在します。1 つ目が Node.js のランタイム上で動いているメインプロセス、2 つ目が Chromium ブラウザのランタイム上で動いているレンダラープロセスです。
Electron のデフォルトのセキュリティ設定ではメインプロセスとレンダラープロセス間でデータのやり取りができないようになっています。
ではどのようにしてアプリ画面のブラウザと Node.js でデータのやり取りを行うかというと、Electron に用意されているプリロードスクリプトというものを利用します。プリロードスクリプトはレンダラープロセス内で実行されるコードで、レンダラープロセスからメインプロセスにアクセスするための許可リストを定義しています。レンダラープロセスからメインプロセスにアクセスできる部分をプリロードスクリプトというレイヤーに分離することで、マシンを操作するメインプロセスの強力な API が無闇にレンダラープロセスに露出するのを防いでいます。
前準備
メインプロセス、レンダラープロセスという 2 つのプロセスがあることと、そのプロセス間を安全に接合するためのレイヤーとしてプリロードスクリプトが存在するというお話をしました。
Electron の知見が乏しい状態で、この三者のスクリプトをビルドする環境を用意するのは骨が折れます。
Electron の公式サイトではボイラープレートと CLI の利用を提案していますが、CLI の electron-forge は必要最低限のファイルを出力するだけで正直ほとんど役に立ちませんでした(簡単なツールをローカルでサクッとつくるだけならこれでもいいかもですが)。electron-forge は簡易的な開発環境は提供してくれますが、アプリのビルドや配布などの CI/CD の設定は一切行われていません。
公式サイトからリンクが貼られているボイラープレートには GitHub Actions の CI/CD も事前に準備されたものがいくつか用意されていました。
メドピア内では Vue.js がよく使われており、別プロジェクトから Vue ファイルを流用することも可能であろうと判断して vite-electron-builder を選択しました。
vite-electron-builder はメインプロセス、レンダラープロセス、プリロードスクリプトがそれぞれ別のディレクトリに切り出されており、Electron の構造を意識した、見通しのよい開発を行うことができています。
どのように自動印刷を実現しているのか
実際に自動印刷アプリがどのように動いているかは、ざっくりと下記のような流れになっています(薬局のログイン等はもろもろ省略)。
- 患者さまが kakari のモバイルアプリから処方せんを送信
- 処方せんを受け取った Rails サーバーは Pusher (リアルタイム通知の SaaS)にリクエスト
- Electron で処方せん到着の通知を受け取って、印刷用 URL を取得する API にリクエスト
- レスポンスに含まれた印刷用 URL をレンダラープロセスでレンダリングして印刷処理
蛇足ですが、リリース当初の Electron のバージョンだと Electron 側からプリンターの選択ができない(OS でデフォルト設定したプリンターが選択される)状態でした。しかし、Electron v17 でバグが修正されて Electron のアプリ画面に OS に接続されたプリンターを選択できる UI を追加してよりアプリらしさが増しています。
印刷回数 100 万回突破
今回つくった Electron アプリから印刷された処方せんの印刷回数が 100 万回を突破しました。
仮に 1 回で 1 枚の処方せんが印刷されるとすると、A4 用紙の厚さが約 0.1mm なので積み上げると 10万 mm = 100 m になります。ビルでいうと 30 階建てくらいです。これだけの規模の患者さまと薬剤師さまのお役に立てたと思うと感慨深いものがあります。
丁度いい節目なのでテックブログを書こうと思ったんですが、ネタを温めていたら直に 130 万回を超えようとしていました(レビュー通過して公開されたときには絶対に超えてる)。
まとめ
Electron の 2 つのプロセスと、そのプロセスを安全に使うコンテキスト分離を理解した上でボイラーテンプレート使えば比較的簡単にデスクトップアプリがつくれてしまいます。
ブラウザでは対処できない課題を解決するための選択肢のひとつとして Electron を検討してみるのも面白いかもしれません。
メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!
■募集ポジションはこちら
https://medpeer.co.jp/recruit/entry/
■開発環境はこちら