システムの発行済PDF書類を後からなんとか検索できるようにする話
こんにちは。
ECシステム開発チームのいまづです。
人材募集していることからもおわかりかと思いますが、人手不足です。
インボイス対応やら電子帳簿保存法やらでECシステムをあずかっているみなさまは、やはりドタバタされているのでしょうか。
インボイス対応で税理士先生に相談しながらPDF書類の作成機能の改造をすすめたのですが、同時に電子帳簿保存法のことも考えないとならないという中で、「発行済PDF書類を検索できるようにする」を最低限実現するために、どんなふうに考えて対応したのか(しようとしているのか)について紹介します。
要件を考える
状況
社内のPDF書類発行機能を含む業務システムは、当然お客様のご注文を検索する機能を持ってはいますし、発行したPDF書類のファイルは(お客様が直接ダウンロードされるものも含めて)全てAWS S3上に保存しています。
とはいえ、これらPDFのファイルを検索する仕組みは持っていませんでしたし、発行済書類の内容を保存する仕組みもありませんでした。
インボイス対応は(機会があれば書きたいと思いますが)、特に仕様を詰めるところでかなり時間を食われてしまって、2023/10開始に間に合わせようとすると、同時に書類検索の仕組みを用意することは開発リソース的にちょっと無理な状況でした。
求められるものを整理する
そもそも、発行済PDF書類を検索できるようにする、というのは業務上で常用する機能というわけではなく、電子帳簿保存法の「電子帳簿の保存要件」として、取引年月日や金額を条件に指定して検索できること、という検索要件によるものです。
そしてこれは、「必要なときに」検索できればよいのであって、常時稼働するような検索サービスが求められるわけではないなと。
開発・運用コストのことを考えても、常時稼働するウェブアプリとなると、それなりの規模になってしまいます。
ということで、欲しいものと都合を考えるとこんな感じに。
- 発行済PDF書類を検索するために書類情報のデータベースは欲しい。
- 検索のためのGUIが必要だけど、プラットフォーム考えたくないのでウェブアプリとして実現したい。
- インボイス対応後に発行する書類だけを対象にする。
- RDSにデータベースを作るようなことはやりたくない(コスト的に)。
- インボイス対応しながら、検索のための仕組みをどうこうする余裕はないので、後回しにしたい。
- とはいえインボイス対応後の発行書類は対象にするので、データは何かしら残したい。
インボイス対応の中でできることを考えて実装する
インボイス対応しながら、発行済PDF書類検索の仕組みを考えつつ対応する余裕はないので、とりあえずPDFファイルと並べて、PDF書類の内容と発行する元データ(注文情報など)を記載したJSONファイルを残すようにだけしようと決めました。
そうすれば後からJSONファイルからデータベースに登録するようにすればなんとかなるでしょう。
もともとPDF書類の発行機能は、書類の種類ごとにテンプレートを用意していて、JSONで内容を指定してPDFを生成するつくりになっています。
このPDF発行時の情報と書類発行に使っているJSONを合体させたファイルを保存することは、それほどの手間をかけずに実装できました。
(インボイス対応後)書類情報JSONファイルの扱い方を考える
さて、インボイス対応の中でとりあえず書類の情報を持ったJSONファイルは作成されるようになりました。
日々バンバンファイルが溜まっています。
これをどうにかする方法を考えたますが、AWSにはAthenaなど便利な機能はもちろんあるのですが、ちょっとコストが見合わない。
単純にデータベースに登録して検索する方が良さそうです。
お手軽に使えるデータベースがないものか、と考えましたが、最もお手軽なものとなるとやはりSQLiteが良さそうに思います。
とはいえ、JSONファイルが作成されたらPUTイベントを通知してAWS Lambdaでゴニョゴニョ、と考えるとSQLiteが向いているとは思えません。
そこで、(古いFlask + SQLIteなウェブアプリ "EAGLE ".brd" file panelizer" を移設した話 は、ちょうどこの辺りを検討していたときに入ってきた話だったのですが、そこでも紹介した) Litesteam を検討してみました。
基本的には、
- (既存があれば)リストアする。
- レプリケーションしながら、更新系のプログラムを実行する。
という使い方ですが、更新するインスタンスとしては単独に制限する必要がありそう。なのでやはりLambdaで使うには向かなさそう、と判断しました。
しかし、要件的にはリアルタイムで登録する必要は必ずしもありません。
なので、
- S3のPUTイベントで、処理対象JSONをキューに積んでおく。
- 定期的に実行するバッチ処理でSQLiteに登録する。
- 検索ウェブアプリを実行する場合は、SQLiteのファイルを元にする。
なら、更新するインスタンスは一つでよさそうです。
これなら、バッチの開始時・実行時にLitestreamを使ってリストア・レプリケーション、がハマるように思います。
ウェブアプリの実行時には、実行前にリストアするだけで良さそう。
キューについては、バッチ処理を一日に数回実行する想定であれば、SQSに積んでおけばいいかな、というところです。処理順は関係ないですし。
実装
JSONファイルのハンドリング
JSONファイルのハンドリングはこんな感じにします。
- PDFやJSONファイルを格納しているS3のPUTイベントをSNSに通知。
- SNSをサブスクライブするSQSを追加。
- SQSのメッセージを順次処理するECSタスクを作成。
- プログラム自体はPython。
- Litestreamを含むDockerイメージにする。
- 起動前にLitestreamのリストアを実行。
- バッチ処理中はLitestreamのレプリケーションを実行。
- 日に数度くらいの頻度でスケジュール実行するように設定。
試してみたところ、うまく動作しているようでした。
ただ、バッチ処理終了後すぐにタスクを終了してしまうと、最後のSQLiteの更新がレプリケーションで反映されずに終了してしまうようなので、単純ですが終了時にPython側で数秒程度スリープするようにしています。
検索ウェブアプリ(API)
お試しの意味もあって gunicorn + uvicorn + FastAPI + SQLAlchemy構成で、条件を複数指定してSQLiteを検索するようなJSON APIを実装しました。
UIは後から何かで作る予定。
バッチ処理と同じく、Litestreamを含んだDockerイメージにしていて、起動時にリストアするようにしています。
gunicorn + uvicorn 環境でログが思ったように出力できなくてハマった話は gunicornのworker-classにuvicornを指定するときに、uvicornのlog-configを指定したい で紹介しています。
このDockerイメージは、GUIも含んだものにするつもりにしています。
AWS上でサーブしようと思うと、アクセス制限や認証が必要になってきますが、2024年2月に間に合わせるのであれば、とりあえずDockerイメージをPC上で実行して、検索・PDFのダウンロードを可能にしさせすれば、法律的な要件は満たせるだろうと考えています。
振り返り(終わってないですが)
SQLiteとLitestreamを採用したことで、運用コスト的にはかなり小さくできたのではないかと思っています。
最新の書類情報が検索対象にならない可能性はありますが、必要な検索要件も満たせたのではないかなと(UIが完成していませんが)。
いったん使える状態にできた後は、認証機構をつけて必要なときに cdk deploy
すればオンラインで使える、という形にしておきたいですね。
システムエンジニア募集中です!
興味のある方はぜひカジュアル面談へ!お待ちしています。