STORES Product Blog

こだわりを持ったお商売を支える「STORES」のテクノロジー部門のメンバーによるブログです。

Lombokの@BuilderがCSVファイル生成に役立った話

概要

こんにちは。STORES 決済でJavaエンジニアをしているnannanyです。 今回はファイル生成処理に使ってみて便利だった、Lombok@Builderのオプション機能を紹介していきます。

説明しないこと

Lombok@Builderのオプション機能に焦点を当てるので、

  • そもそもLombok*1とは何か
  • そもそも@Builder*2とは何か

については述べません。

背景

APIによる情報連携が盛んになった世の中ですが、企業間の情報連携においてはまだまだCSV・TSV・Excelといったファイル形式での情報連携が多いです。

そんな中、ファイル生成する処理を最近実装しました。
実装を進めていく中で@Builderにとても助けられているなぁ、と感じることが多かったので、具体例を交えながらその機能を紹介していきます。

前提

以下のようなCSVファイルを作成することを念頭に説明していきます。
(1行目はヘッダーです)

店名,住所,電話番号,提出元企業,同一店舗フラグ
恵比寿商店,東京都渋谷区南1,080-xxxx-xxxx,STORES,0
渋谷商店,東京都渋谷区南2,090-xxxx-xxxx,STORES,0
渋谷商店,東京都渋谷区南2,090-xxxx-xxxx,STORES,1

また、上記のCSVファイルの各行の値を格納するクラスとして、下記のCSVRecordクラスを利用するものとします。 (CSVRecordクラスに設定した値がCSVファイルに書き出されるイメージ)

@Builder
public class CSVRecord {

    private String storeName; // 店名

    private String address; // 住所

    private String phoneNumber; // 電話番号

    private String companyName; // 提出元企業

    private Integer sameFlag; // 同一店舗フラグ
}

@Builder.Default

例で挙げたCSV提出元企業というカラムについて、STORES から提出先の企業へと提出する資料であるため、このカラムには必ずSTORESという値が入ります。

そのため、組み立てのロジックの中で値を入れるのではなく、デフォルト値としてSTORESを設定しておきたいです。

そんなときに使えるのが@Builder.Defaultです。
@Builder.Defaultを付与したフィールドには、デフォルト値を設定できます。
下記のように記述すると、companyNameにはSTORESがデフォルト値として設定されます。

@Builder
public class CSVRecord {

    private String storeName;

    private String address;

    private String phoneNumber;

    @Builder.Default // <-ここ
    private String companyName = "STORES"; // <-ここ 

    private Integer sameFlag;
}

Builderのメソッドを追加

@Builderを付与したクラスには、ビルダークラスが作られ、ビルダークラスには各フィールドの値を設定するメソッドが作られます。
上記の例では、

  • storeNameに値を設定するstoreName(String storeName)
  • addressに値を設定するaddress(String address)
  • phoneNumberに値を設定するphoneNumber(String phoneNumber)
  • companyNameに値を設定するcompanyName(String companyName)
  • sameFlagに値を設定するsameFlag(Integer sameFlag)

がビルダークラスのメソッドとして作られます。

これだけでも便利なのですが、複数の値を含んだレコード・クラスを受け取って、その中身をビルダーに設定したい時もあります。

例えば、下記のようなレコード・クラスがあったとします。

public record StoreInfo(
        String storeName,
        String address,
        String phoneNumber
) {
}

このレコード・クラスを受け取って、CSVRecordのビルダーに設定したいような場合、下記のようにビルダークラスにメソッドを追加することで解決できます。
(CSVRecordに@Builderをつけた場合には自動的にCSVRecordBuilderというクラスができるので下記のような書き方になります。builderClassNameを利用して任意のビルダークラス名にした場合、おそらく該当の名前のインナークラスを定義し、その中にメソッドを書くことになるはずです)

@Builder
public class CSVRecord {

    private String storeName;

    private String address;

    private String phoneNumber;

    @Builder.Default 
    private String companyName = "STORES"; 

    private Integer sameFlag;
    
    public static CSVRecordBuilder storeInfo(StoreInfo storeInfo) { // <-ここ
        return builder()
                .storeName(storeInfo.storeName())
                .address(storeInfo.address())
                .phoneNumber(storeInfo.phoneNumber());
    }
}

toBuilder = true

例のCSVにおいて、同一店舗フラグ0の場合、該当の店舗は同一CSVファイル内で初出の店舗であることを示しています。
逆に、同一店舗フラグ1の場合、同一CSVファイル内で2回目以降の出現であることを示しています。

つまり、同一店舗の情報を複数行記載する場合、同一店舗フラグ以外は同じインスタンスを生成することになります。

そんなときに使えるのがtoBuilder = trueです。 @Builder(toBuilder = true)とすることで、該当クラスのインスタンスを元にあたらしいビルダーインスタンスを生成するtoBulder()メソッドを作ることができます。

@Builder(toBuilder = true) // <-ここ
public class CSVRecord {

    private String storeName;

    private String address;

    private String phoneNumber;

    @Builder.Default 
    private String companyName = "STORES"; 

    private Integer sameFlag;
    
    public static CSVRecordBuilder storeInfo(StoreInfo storeInfo) {
        return builder()
                .storeName(storeInfo.storeName())
                .address(storeInfo.address())
                .phoneNumber(storeInfo.phoneNumber());
    }
}

こうすることによって、CSVRecordクラス内に下記のようなメソッドが作られます。

// 上記をdelombokしたもの
public CSVRecordBuilder toBuilder() {
    return (new CSVRecordBuilder())
        .storeName(this.storeName)
        .address(this.address)
        .phoneNumber(this.phoneNumber)
        .companyName(this.companyName)
        .sameFlag(this.sameFlag);
}

これを使って、特定の店舗の行を2行以上出力したい場合は、

  • 該当の店舗の行を示すインスタンスを1つ組み立てる
  • 組み立てたインスタンスtoBuilder()で複製する
  • 複製したビルダーに同一店舗フラグ1に設定する

という流れで記述できます。 この流れをコードにすると下記のようになります。

CsvRecord csvRecord = ...;
... // CSVRecordのインスタンスを組み立てる処理

for (int i = 0; i < count - 1; i++) { // countは同一店舗の情報を何行出力するかを表す
        csvRecords.add(csvRecord.toBuilder() // csvRecordsはCSVRecordのリスト
                .sameFlag("1")
                .build());
}

このようにして、構成要素の一部だけ異なる行を作成するという処理が書きやすくなりました。

まとめ

今回は、Lombok@Builderのオプション機能について紹介しました。内容をまとめると以下の3点になります。

  • @Builder.Defaultを使うことで、ビルダークラスにデフォルト値を設定できる
  • ビルダークラスにメソッドを追加できる
  • toBuilder = trueを使うことで、該当クラスのインスタンスを元にあたらしいビルダーインスタンスを生成するtoBulder()メソッドを作ることができる