Asakusa Direct I/O formatted textの紹介

はじめに

このエントリは Asakusa Framework Advent Calendar 2017 の20日目のエントリです。実は 2 年ぶりくらいにお仕事で Asakusa Framework を使った開発をしているので、今年は参加することにしました。

Asakusa Framework は 6 年前の初期リリースから継続して機能の追加を積極的に行っています。先ほど 2 年ぶりに Asakusa で開発していると書きましたが、その間にも色々進化していて、確実に便利になっていると感じました。

今回はその中の Direct I/O formatted text について紹介したいと思います。これはバージョン 0.9.1 になって追加された、かなり新しい機能です。

Direct I/O は HDFSAWS S3、GCS といった分散ファイルシステム上に保存されたファイルを透過的に読み書きする機能ですが、これまで次のようなファイルの種類に応じてデータモデルへのマッピングを行う機能が用意されていました。

  • Direct I/O CSV
  • Direct I/O TSV
  • Direct I/O line

DirectI/O formatted text はこれまでファイルの種類に応じて別々の API を用いていたところに統一的な機能を提供し、さらに機能追加も行われた強力なライブラリになっています。次のような強みがあります。

  • より多様なデータ形式に対応できる
  • 不整合データに対して柔軟な操作設定ができる
  • 読み込み時にファイルについてのメタ情報を入れられる

詳細についてはドキュメントをじっくり読んでもらうとして、ここでは個人的に嬉しかった点を挙げていきたいと思います。

嬉しかった点その1

「きちんとしていないデータに対してかなり無理が利く」という点です。

例えば、複数のシステム間で連携をするとします。各システムにサービスインターフェースが用意されていればいいですが、そうも行かず、いわゆるファイル連携をすることも多いと思います。

このファイル連携、とにかく泥臭い対応が必要になることが多いですよね。連携先の各システムに「CSV ファイルで連携しましょう」って声を掛けても次のように綺麗なデータが来るとは限らないことが多かったりしますよね。

  • 改行コード、エンコーディングがばらばら
  • 同じデータに対応したものなのにヘッダがあったりなかったり
  • 固定長ファイルを単純に CSV 化したのか、各項目が空白埋めでご丁寧に桁を揃えられているとか
  • 行によってカラム数が異なる CSV 的なものが出てくるとか
    • 本体のデータ部分以外に見出しや合計行なんかも 1 つのファイルに混じっている例を見たことがあります...

Direct I/O formatted text はかなり柔軟な読み込みオプションがあり、このような汚いファイルに対してもかなり無理が利くようになっています。

Direct I/O formatted text を使うためには DMDL のモデル定義に @directio.text.tabular (CSV以外の形式のファイルを扱う場合に使い、区切り文字を指定する) もしくは @directio.text.csv 属性を指定します。

@directio.text.csv(
    charset = "Windows-31J",
    header = skip,
    trim_input = true,
    true_format = "1",
    false_format = "0",
    date_format = "yyyy/MM/dd",
    datetime_format = "yyyy/MM/dd HH:mm:ss",
    on_more_input = report,
    on_less_input = ignore
)
data_model = {

};

属性に指定しているオプションに注目してください。 charset 指定があるのは当然のこととして、どの値を真偽値としてマッピングするか日付時刻のフォーマットも指定可能です。

注目してほしいのは header 指定で、読み込みや書き込みにおいてのヘッダ指定を次のようにかなり柔軟に指定可能です。

指定値 内容
nothing 何もしない (ヘッダ行が無いものとして読み込み、ヘッダを出力しない)
force ヘッダが存在しているものとして読み込み (1行スキップする) 、ヘッダを出力する
skip 読み込み時にヘッダが あれば スキップし、ヘッダは出力しない
auto 読み込み時にヘッダが あれば スキップし、ヘッダを出力する

skipauto といった、ヘッダのあるなしをよしなに扱ってくれます。こんな気配りができるライブラリは割と珍しいと思いませんか。

trim_input オプションは先頭や末尾に空白文字が入っていると除去してくれます。これでわざわざ @Update 演算子を使わなくてもいいですね。

on_more_inputon_less_input なんてオプションもあります。前者は読み込み時にレコードに余計なフィールドがあった場合の動作を、後者はレコードのフィールドが不足している場合の動作を指定できるようになっており、少々変なデータでも対処できるようになっています。この手のライブラリは普通例外を飛ばして終わりということが多いですよね。

on_more_input の場合の指定内容は次の通りです。

指定値 内容
error エラーログを出して異常終了する
report 警告ログを出力した上で無視する
ignore 単に余剰フィールドを無視する

on_less_input の場合の指定内容は次の通りです。

指定値 内容
error エラーログを出して異常終了する
report 警告ログを出力した上で不足フィールドに NULL を入れる
ignore 不足フィールドに NULL を入れる

これで行によってカラム数が異なる CSV 的なもへの対処とかもできそうですよね。

嬉しかった点その2

「読み込み時にファイルのメタ情報をデータモデルに追加できる」という点です。

データフィールド属性に次のような読み込んだファイルに関する情報を埋め込むためのものが用意されています。

属性 内容
@directio.text.file_name 属性を指定されたフィールドにファイルパスを設定する
@directio.text.line_number 属性を指定されたフィールドに当該データのファイルの行番号を設定する (物理的な行番号であるため、途中で改行の入っているレコードが存在した場合、次のレコードは番号がスキップすることになる)
@directio.text.record_number 属性を指定されたフィールドに当該データのファイルのレコード番号を設定する (こちらは論理的な行番号)

でファイルのデータチェックで、ファイル名や行番号の情報も出力したいような場合でも Asakusa 上で実行できるようになりますね。

終わりに

とりあえずはこんなところです。他にも多彩な機能が用意されているので、詳しくはドキュメントを読んでください。

Asakusa Framework は開発の現場で遭遇する「これがあったらいいな」的な機能を結構貪欲に取り込み続けています。取り込んで欲しい機能があったら MLGitHub (日本語でも OK ですよ) などで積極的に提案してみることをお勧めします。