- 辻大志郎
- 約 3,700 文字
- 300 View
はじめに
製造・エネルギーグループの辻です。Web系のシステム開発やデータ連携においてデータフォーマットにJSONを利用することは一般的ですが、製造系の基幹システムとのデータ連携においてEDIFACTを利用するケースがあります。EDIFACTはあまり馴染みのない、あるいは聞きなれないデータフォーマットかと思いますが、製造業あるいは他業種においても歴史あるデータフォーマットです。またEDIFACTデータを処理するにあたってはエンタープライズ向けのETL製品やEDIミドルウェアが導入されているケースがよくあると思います。ただ、今回、EDIFACTを扱うJavaアプリケーションをスクラッチ開発するニーズがありました。
そこで本記事では、まずEDIFACTとは何か?ということを簡単に触れたのち、JavaでEDIFACTデータを処理するためのライブラリ選定や実装アプローチの概要を紹介します。
そもそもEDIFACTとは?
EDIFACTとは、国連が定めた電子データ交換の国際標準規格です。正式名称はUN/EDIFACT(the United Nations rules for Electronic Data Interchange for Administration, Commerce and Transport)1と呼ばれます。テキストベースのデータ構造ですが、JSONのようにキー名を持たず、位置と順序でデータの意味が決まります。' 、 + 、 : などの文字を用いて文字列を区切り、メッセージ仕様に基づいて階層構造やデータの詳細がわかるデータフォーマットです。
EDIFACTのサンプルデータを以下に記載しました。なお、実際の伝送データは改行されていませんが、可視性向上のために改行しています。
UNB+UNOA:2+SENDER_ID+RECEIVER_ID+251228:1500+1234' |
UNB や UNH、 DTM といった謎の単語(EDIFACTの用語でセグメントと呼びます)と付随する文字列が現れました。何を意味しているか一見、理解不能のように見えます。
ところが、JSONフォーマットに変換するとなんとなくイメージがわく方も多いのではないでしょうか?以下は先ほどのEDIFACTメッセージを擬似的にJSONに変換したデータです。
{ |
EDIFACTの文字列を、上記のように階層構造に変換できる理由は、メッセージタイプ(業務プロセスごとに定義されたデータ構造の仕様)により構造が定義されているためです。上記で紹介した例は、UN/EDIFACTの ORDERS (発注)というメッセージタイプに基づくサンプルメッセージです。
https://service.unece.org/trade/untdid/d96a/trmd/orders_t.htm
その他にも DESADV(出荷通知)、INVOIC(請求)など、様々な業務プロセスに対応するメッセージタイプが定義されています。それぞれのメッセージタイプごとに、どのセグメントを、どの順序で、何回繰り返すかというデータ構造が定義されています。
ただ、実務上はUN/EDIFACTの国際標準をベースにしつつも、業界ごとに独自の規約(サブセット)を定義して運用することが一般的です。たとえば自動車業界では JAMA(日本自動車工業会) 2 がEDIFACT導入ガイドラインを制定しています。さらに、業界内でも個社ごとに仕様がカスタマイズされているケースは少なくありません。
JavaのEDIFACTライブラリ選定
EDIFACTの概要を紹介しました。次は、EDIFACTメッセージをJavaアプリケーションで処理するにあたって、ベースとなるライブラリ選定プロセスを紹介します。Javaライブラリをざっくりと調査&簡易検証した結果、候補として以下の2つに絞られました(ただ調査&簡易検証範囲は限定的です。他にもこのライブラリが便利だよ、という知見をお持ちの方、こっそりと教えて下さい)。
| 案 | ライブラリ名 | リポジトリ | 特徴 |
|---|---|---|---|
| 案1 | Smooks | https://github.com/smooks/smooks | ・高機能なデータ変換フレームワーク ・EDIFACTメッセージの仕様を定義したXMLファイルを用意することで、メッセージのパースとBeanへデータマッピングが可能 |
| 案2 | Staedi | https://github.com/xlate/staedi | ・ストリームベースの軽量EDIパーサ ・処理フローをアプリケーションコードで制御する必要あり ・Beanへのマッピング機能はない |
結論としては、案2のStaediを採用しました。当初、案1のSmooksを主として検討していましたが、EDIFACTメッセージの定義ファイルを用意することが困難であると判明しました。UN/EDIFACTの標準規格の定義ファイル自体はSmooksの標準機能として提供されており、当初はよさそうに見えていました3。
しかし、実務上は個社別にカスタマイズされているEDIFACTの定義ファイルを用意する必要がありました。いくつか試行錯誤しましたが、この個社別にカスタマイズされている定義ファイルを作成することが困難であるとわかりました。これがノックアウトファクターとなり、代替案であるStaediを採用することにしました。
実装アプローチ
Staediは、EDIFACTデータの読み書きに特化したストリームベースのパーサライブラリです。メッセージ構造の解釈や処理フローはアプリケーションコードに委ねられています。実装方針は大きく2通り考えられました。
- StaediのREADMEにあるような、イベント駆動(ステートマシン)的なアプローチ
- 全データをメモリ上のツリー構造として扱う、構文解析的なアプローチ
今回は2の方針を採用しました。実務で採用されている複雑なEDIFACT仕様にそって処理することを考えたとき、主に以下の理由から2のアプローチのほうがコードの保守性が高いと判断したためです。
- コードの構造からメッセージ仕様を直接的に読み取れる
- 入力データの並び順とシステムが必要とする出力データの順序が異なる場合でも、ツリー構造であれば柔軟にデータを取り出せる
- 構文木としてデバッグできるため、構造定義とデータ抽出定義のどちらに問題があるかを特定しやすい
2のアプローチではEDIFACTのセグメントを一度JSONノードのリストとして展開し、そのリストをカーソルを使って走査しながら、階層構造を構築します。EDIFACTのメッセージは ' や + や : などの文字により区切られていますが、このような字句解析はStaediにまかせます。字句解析結果のJSONノードをアプリケーションコードで構造解析し、ビジネスロジックで必要なデータを抽出します(なお、本記事ではデータの抽出部分は割愛します)。
Java処理フロー概要
StaediとJacksonを用いたセグメントのリスト化
まず、Staediの EDIStreamReader とJacksonの ObjectMapper を組み合わせて、EDIFACTメッセージを List<JsonNode> に変換します。EDIFACTの各セグメントを扱いやすいJSONオブジェクトのリストにします。
private static final ObjectMapper MAPPER = new ObjectMapper(); |
カーソルの用意
次に現在どのセグメントを参照していているか?を管理するカーソルクラスを用意します。パース処理中に、次のセグメントが UNH メッセージであれば UNH メッセージ処理をする、といった先読みをできるようにしています。
/** |
パース処理
カーソルを用いて、メッセージをパースする処理を実装します。複数回登場しうるセグメント(NAD、LIN など)は先読みしながらループを回して処理します。コードの構造から、セグメントの流れ(UNH の次に BGM が登場するなど)や登場回数(1回なのか複数回)が、わかることが嬉しいポイントです。
/** |
実行クラスや toString() などの実装は省略しますが、これらを組み合わせると、以下のようにEDIFACTメッセージを構造化して扱えます。
[Interchange] |
まとめ
JavaによるEDIFACT処理のスクラッチ開発について、ライブラリ選定から実装までの一連の流れを紹介しました。ストリームパーサであるStaediで字句解析、アプリケーション側で構文解析するアプローチを採用することで、EDIFACTの複雑なネスト構造を直感的なコードで表現できるようになりました。少々ニッチな領域ではありますが、モダンな開発環境でレガシーな仕様と向き合う際の参考になれば幸いです。