バックエンドでのデータ管理
Apache Camelのおおよその流れがわかったところで、実際のデータを登録する処理を説明します。また、今回は利用する郵便番号データは、今回は日本郵便のHPからダウンロードしたものを利用しました。
[Note]日本郵便のHPからダウンロードできるデータについて
日本郵便のHPからダウンロードできる郵便番号データは、自由に利用できますが、その場合には多少の注意が必要です。データ内の住所項目を見ると、「次のビルを除く」や、「以下に掲載がない場合」、複数の丁目「(1~3丁目)」や「(その他)」などのデータがあるので、これらのデータを実際に利用できる形に修正する必要があります。
また、今回のサンプルのダウンロードURLに直接、日本郵便のHPのダウンロードURLを指定しないようにお願いします。
サンプルのアプリケーションとコードの関係を示したものが図5になります。
また、データ管理を行っているクラスは表2の機能が実装されています。
クラス名 | 概要 |
---|---|
ZipcodeImportRouteBuilder | Apache Camelのルート定義をするクラス |
WebDownloadTask | Springのスケジュール機能を利用した定期ファイル取得処理 |
AdminController | ファイルアップロードを受け付けるRest API処理 |
フォルダを利用したデータ登録・更新
指定したフォルダにファイルが存在すれば、そのファイルを検知し自動的に郵便番号データとしてデータベースに登録する流れにします。その処理のサンプルコードがリスト3です。
この処理は、FTPやSCPなどによってデータが送信されてきて、指定のフォルダにアップロードが終わったタイミングで処理が実行されることを期待しています。
@Component public class ZipcodeImportRouteBuilder extends RouteBuilder { : (省略) @Override public void configure() throws Exception { File file = new File("."); // (1) 実行しているフォルダ以下のimportフォルダをチェックする from("file://" + file.getAbsolutePath() + File.pathSeparator + "import").process((Exchange exchange) -> { File importFile = exchange.getIn().getBody(File.class); InputStream is = new FileInputStream(importFile); // (2) 次処理の入力データを設定する Message in = exchange.getIn(); in.setBody(is); }).to("direct:import"); // (3) InputStreamの入力を受けてデータを登録するルート from("direct:import").process((Exchange exchange) -> { Message msg = exchange.getIn(); Object body = msg.getBody(); if(body instanceof InputStream){ InputStream is = (InputStream)body; try { importData(is); exchange.getOut().setHeader("Status","OK"); } // (省略) } }); } // (3) protected void importData(InputStream is) throws UnsupportedEncodingException, IOException{ // (省略) InputStreamからCSVデータを読んで、DBに追加、または更新する } }
(1)の"file://..."というエンドポイントのURI内に検知する際のフォルダを指定します。指定したフォルダにファイルが存在すれば自動で処理が実行されます。
(2)では、次の処理の入力データとしてInputStreamのインスタンスを設定しています。入力時のオブジェクト型は自由なので、データを受ける処理側で型を意識する必要があります。
(3)の"direct:<任意の名前>"というエンドポイントは、何も処理が行われないまま、次の処理に進みます。この名前を使って他の処理から自由にこの処理を呼び出すことができます。また、(3)は実際のCSVデータを読み取り、データベースに登録する処理です。実際のコードは添付するサンプルコードを参照してください。
定期スケジュールでデータを取得する
続いて、定期的なスケジュールで外部のWebサイトにあるデータを取得し、データベースに登録・更新する処理を想定します。定期的なスケジュールや、外部のWebサイトからデータを取得することもApache Camelで記述が行えますが、今回はSpringでのスケジュール機能を利用します。
Spring Bootでスケジュール機能を利用する場合には、リスト4のように@EnableSchedulingアノテーションを宣言する必要があります。
import org.springframework.scheduling.annotation.EnableScheduling; @EnableScheduling public class MainApplication { // (省略) }
また、スケジュールとして実行される処理は@Scheduledアノテーションをメソッドに宣言し処理を記述します。
リスト5は、Springのスケジュール機能を利用し、Apache Camelのルート処理を実行する際のサンプルコードです。
: (省略) import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class WebDownloadTask { // (1) Apache CamelのContextオブジェクトを取得する @Autowired CamelContext camelContext; // (2) Springのスケジュール機能を使った実行 @Scheduled(cron = "0 0 1 * * *") public void downloadFile(){ // (3) 指定したURLからファイルをダウンロードする HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder(URI.create(downloadUrl)).build(); try { Path path = Files.createTempFile("download-",".zip"); HttpResponse<Path> response = client.send(request, HttpResponse.BodyHandlers.ofFile(path)); File file = path.toFile(); if(file.exists()){ ZipFile zipFile = new ZipFile(file); ZipEntry entry = zipFile.entries().nextElement(); InputStream is = zipFile.getInputStream(entry); // (4) ダウンロードしたZipファイル内のデータ(1つのみ)を入力とし、"direct:import"のエンドポイントに処理をつなげる ProducerTemplate template = camelContext.createProducerTemplate(); Exchange exchange = template.send("direct:import", (Exchange obj) -> { obj.getIn().setBody(is,InputStream.class); }); // (5) 処理結果を取得する Message out = exchange.getOut(); String status = out.getHeader("status",String.class); if(status.equals("OK")){ log.info("import OK"); } // (省略) } } // (省略) } }
(1)では、Apache CamelのContextオブジェクトを取得します。Apache Camelの外から処理を実行する場合にはこのオブジェクトを取得します。また、このオブジェクトはSpring Boot側で自動的に作成されています。
(2)では実行するスケジュール処理を記述しています。また、サンプルではスケジュールはcron形式での記述が可能であり、ここでは毎月1日の00:00に処理を実行するように指定しています。今回使用したcron表記方法以外にも表3の表記方法があり、cron以外の表記方法はTimerクラスを使ったスケジュール実行と同じルールになります。
(3)では指定したURLからファイルをダウンロードしています。ダウンロードするファイルはZip圧縮されている前提としています。続いて、(4)では(3)で取得したデータを利用し、Apache Camel側で作成した"direct:import"というエンドポイントに入力を渡します。
このようにApache Camelの"direct"というスキームは、外部のプログラムからも実行できるので非常に便利です。
処理の結果を取得したい場合には、(5)のようにして結果を取得することもできます。
クラス名 | 概要 |
---|---|
cron | cronでの記述方法と同じように記述可能 |
fixedDelay | 前の処理と次の処理の間隔が指定したミリ秒になるように処理が実行される |
fixedRate | 指定したミリ秒間隔で処理が定期的に実行される。処理開始の間隔になるので処理自体が長い場合には、同時に2つ以上の処理が実行される場合がある |
initialDelay | 最初の処理を行うための待機時間 |
HTTPを使った、ファイルのアップロード処理からのデータ登録
HTTPのPUTを使ったファイルアップロードを使ってデータを登録する場合では、これまでの例と前回までのRestControllerの知識があれば実装可能です。
詳しい説明は割愛しますが、リスト5のコードで実装できます。
@RequestMapping("/admin/zipcode") public class AdminController { // PUTでのファイルアップロード処理 // curlを使ったファイルアップロード例 : curl -u -X PUT http://127.0.0.1:8082/admin/zipcode/import -T import.csv @PutMapping("/import") public Response importAction(InputStream is){ // 受信したデータを使ってApache Camelのルートを実行する ProducerTemplate template = camelContext.createProducerTemplate(); Exchange exchange = template.send("direct:import", (Exchange obj) -> { obj.getIn().setBody(is,InputStream.class); }); // (省略) } }
最後に
Spring Bootを使う理由の1つとして、TomcatなどのWebコンテナとSpring Frameworkという使い方だけではなく、今回のサンプルアプリケーションのように、Webだけではない別のフレームワークとWebフレームワークを混在させた1つのアプリケーションが作りやすい点があげられます。
また、Webシステムだけであれば、PHPやRuby、Pythonといったスクリプト系の言語の方が使いやすい場合がありますが、今回のようなHTTP以外での入出力データをカバーする必要が生じた場合にJavaという言語の強みがより一層引き立つはずです。
次回は、今回作成したサンプルアプリケーションに認証をつける方法や、細かなSpring Bootの設定カスタマイズなどを紹介します。
参考資料
本連載の書籍が発売されました!
Javaによる高速Webアプリケーション開発のためのSpring Boot入門
著者:WINGSプロジェクト 小林昌弘
発売日:2020年5月31日(水)
価格(POD):2,200円(税込)
価格(電書):1,760円(税込)