Java Lambda の入出力型を試してみた〜POJO 編〜

Java Lambda の入出力の型には、いろんなパターンが使えます。

* Map 方式
* POJO 方式
* InputStream/OutputStream 方式

公式ドキュメントでは [ここ](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-handler.html#java-handler-types) に書かれています。

[Map 編](https://qiita.com/kazfuku/items/6b2e9131eb5e6e175922) では、Map 型を使用して JSON を扱う方法を紹介しました。

Map 方式のコードをみてみると、Object 型から String/Integer/Long 型などへのキャストが多数現れます。

```Java
public class MapFunction implements RequestHandler<Map<String, Object>, Map<String, Object>> {
    @Override
    public Map<String, Object> handleRequest(Map<String, Object> event, Context context) {
        String name = (String) event.get("name");
        System.out.println(name); // orange juice

        Integer price = (Integer) event.get("price");
        System.out.println(price); // 1000

        Long releaseDate = (Long) event.get("releaseDate");
        System.out.println(releaseDate); // 1601606387939
...
```

コードが汚いだけでなく、型が変更された場合にコンパイル時に引っかからず、実行時に ClassCastException が出てしまうかもしれません。

そこで、POJO 方式です。

まずは、Map の代わりとなる POJO クラスを作ります。Size クラスは内部クラスにしていますが、普通のクラスにしてもいいですね。

```Java
import java.util.List;

public class Product {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private Integer price;

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    private Long releaseDate;

    public Long getReleaseDate() {
        return releaseDate;
    }

    public void setReleaseDate(Long releaseDate) {
        this.releaseDate = releaseDate;
    }

    private Double rate;

    public Double getRate() {
        return rate;
    }

    public void setRate(Double rate) {
        this.rate = rate;
    }

    private List<String> rawMaterial;

    public List<String> getRawMaterial() {
        return rawMaterial;
    }

    public void setRawMaterial(List<String> rawMaterial) {
        this.rawMaterial = rawMaterial;
    }

    private Size size;

    public Size getSize() {
        return size;
    }

    public void setSize(Size size) {
        this.size = size;
    }

    public static class Size {
        private Integer height;

        public Integer getHeight() {
            return height;
        }

        public void setHeight(Integer height) {
            this.height = height;
        }

        private Integer width;

        public Integer getWidth() {
            return width;
        }

        public void setWidth(Integer width) {
            this.width = width;
        }

        private Integer depth;

        public Integer getDepth() {
            return depth;
        }

        public void setDepth(Integer depth) {
            this.depth = depth;
        }
    }
}
```

Lambda 関数のハンドラでは、このクラスを入出力の型として使います。

```Java
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

import java.util.Arrays;
import java.util.List;

public class PojoFunction implements RequestHandler<Product, Product> {
    @Override
    public Product handleRequest(Product product, Context context) {
        String name = product.getName();
        System.out.println(name); // orange juice

        Integer price = product.getPrice();
        System.out.println(price); // 1000

        Long releaseDate = product.getReleaseDate();
        System.out.println(releaseDate); // 1601606387939

        Double rate = product.getRate();
        System.out.println(rate); // 4.5

        List<String> rawMaterial = product.getRawMaterial();
        System.out.println(rawMaterial); // [orange, fragrance]

        Product.Size size = product.getSize();
        Integer height = size.getHeight();
        System.out.println(height); // 10

        Integer width = size.getWidth();
        System.out.println(width); // 20

        Integer depth = size.getDepth();
        System.out.println(depth); // 30

        Product response = new Product();
        response.setName("coffee");
        response.setPrice(Integer.valueOf(500));
        response.setReleaseDate(System.currentTimeMillis());
        response.setRate(Double.valueOf(4.2));
        response.setRawMaterial(Arrays.asList("coffee", "sugar"));

        Product.Size responseSize = new Product.Size();
        responseSize.setHeight(Integer.valueOf(100));
        responseSize.setWidth(Integer.valueOf(200));
        responseSize.setDepth(Integer.valueOf(300));
        response.setSize(responseSize);

        return response;
    }
}
```

キャストがキレイに消えました。これで、型を変更してもコンパイルエラーにできますね。

これを以下のJSON を渡して実行すると、

```Json
{
    "name": "orange juice",
    "price": 1000,
    "releaseDate": 1601606387939,
    "rate": 4.5,
    "rawMaterial": ["orange", "fragrance"],
    "size": {
        "height": 10,
        "width": 20,
        "depth": 30
    }
}
```

こんな出力になります。

```Json
{
  "name": "coffee",
  "price": 500,
  "releaseDate": 1601623033115,
  "rate": 4.2,
  "rawMaterial": [
    "coffee",
    "sugar"
  ],
  "size": {
    "height": 100,
    "width": 200,
    "depth": 300
  }
}
```

POJO 方式だと、Map 方式の HashMap を使う場合と違って、順序は維持されてるようです。

## 便利なライブラリ
JSON を POJO クラスにマッピングできることを利用して、便利に使えるライブラリ aws-lambda-java-libs が AWS 公式で提供されています。
https://github.com/aws/aws-lambda-java-libs/tree/master/aws-lambda-java-events

たとえば、API Gateway の Lambda プロキシ統合の場合、こんな感じで用意されている POJO クラスを入出力に使えます。

```Java
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent event, Context context) {
    APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
    response.setStatusCode(Integer.valueOf(200));
    response.setBody("ok");
    return response;
}
```

## さいごに
次は、InputStream/OutputStream 方式を紹介しようと思います。

Java Lambda の入出力型を試してみた〜Stream 編〜
https://qiita.com/kazfuku/items/6f0f55ffa3a88d76cfaa