---
title: Spring WebFlux.fnハンズオン - 7. 収入APIの実装
tags: ["Spring WebFlux.fn Handson", "Reactor", "Reactor Netty", "Netty", "Spring 5", "Spring WebFlux", "Java", "Cloud Foundry", "Pivotal Web Services", "Pivotal Cloud Foundry"]
categories: ["Programming", "Java", "org", "springframework", "web", "reactive"]
date: 2019-06-26T18:23:11Z
updated: 1970-01-01T00:00:00Z
---

本ハンズオンで、次の図のような簡易家計簿のAPIサーバーをSpring WebFlux.fnを使って実装します。
あえてSpring BootもDependency Injectionも使わないシンプルなWebアプリとして実装します。

**ハンズオンコンテンツ**

1. [はじめに](/entries/500)
1. [簡易家計簿Moneygerプロジェクトの作成](/entries/501)
1. [YAVIによるValidationの実装](/entries/502)
1. [R2DBCによるデータベースアクセス](/entries/503)
1. [Web UIの追加](/entries/504)
1. [例外ハンドリングの改善](/entries/505)
1. [収入APIの実装](/entries/506) 👈
1. [Spring Bootアプリに変換](/entries/507)
1. [GraalVMのSubstrateVMでNative Imageにコンパイル](/entries/510)

**目次**
<!-- toc -->

### 収入APIの実装

Web UIにはIncome(収入) API用の画面も含まれています。Expenditure(支出) APIを参考にして、Income APIも実装して見てください。

一部のコードを下記に示します。

#### Incomeモデルの作成


```java
package com.example.income;

import am.ik.yavi.builder.ValidatorBuilder;
import am.ik.yavi.core.ConstraintViolations;
import am.ik.yavi.core.Validator;
import am.ik.yavi.fn.Either;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

import java.time.LocalDate;

@JsonDeserialize(builder = IncomeBuilder.class)
public class Income {

    private final Integer incomeId;

    private final String incomeName;

    private final int amount;

    private final LocalDate incomeDate;

    private static final Validator<Income> validator = ValidatorBuilder.of(Income.class)
        .constraint(Income::getIncomeId, "incomeId", c -> c.isNull())
        .constraint(Income::getIncomeName, "incomeName", c -> c.notEmpty().lessThanOrEqual(255))
        .constraint(Income::getAmount, "amount", c -> c.greaterThan(0))
        .constraintOnObject(Income::getIncomeDate, "incomeDate", c -> c.notNull())
        .build();

    public Income(Integer incomeId, String incomeName, int amount, LocalDate incomeDate) {
        this.incomeId = incomeId;
        this.incomeName = incomeName;
        this.amount = amount;
        this.incomeDate = incomeDate;
    }

    public Integer getIncomeId() {
        return incomeId;
    }

    public String getIncomeName() {
        return incomeName;
    }

    public int getAmount() {
        return amount;
    }

    public LocalDate getIncomeDate() {
        return incomeDate;
    }

    public Either<ConstraintViolations, Income> validate() {
        return validator.validateToEither(this);
    }

    @Override
    public String toString() {
        return "Income{" +
            "incomeId=" + incomeId +
            ", incomeName='" + incomeName + '\'' +
            ", amount=" + amount +
            ", incomeDate=" + incomeDate +
            '}';
    }
}
```

```java
package com.example.income;

import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;

import java.time.LocalDate;

@JsonPOJOBuilder
public class IncomeBuilder {

    private int amount;

    private LocalDate incomeDate;

    private Integer incomeId;

    private String incomeName;

    public IncomeBuilder() {
    }

    public IncomeBuilder(Income income) {
        this.amount = income.getAmount();
        this.incomeDate = income.getIncomeDate();
        this.incomeId = income.getIncomeId();
        this.incomeName = income.getIncomeName();
    }

    public Income build() {
        return new Income(incomeId, incomeName, amount, incomeDate);
    }

    public IncomeBuilder withAmount(int amount) {
        this.amount = amount;
        return this;
    }

    public IncomeBuilder withIncomeDate(LocalDate incomeDate) {
        this.incomeDate = incomeDate;
        return this;
    }

    public IncomeBuilder withIncomeId(Integer incomeId) {
        this.incomeId = incomeId;
        return this;
    }

    public IncomeBuilder withIncomeName(String incomeName) {
        this.incomeName = incomeName;
        return this;
    }
}
```

#### データベースの作成


```java
    public static Mono<Void> initializeDatabase(String name, DatabaseClient databaseClient) {
        if ("H2".equals(name)) {
            return databaseClient.execute("CREATE TABLE IF NOT EXISTS expenditure (expenditure_id INT PRIMARY KEY AUTO_INCREMENT, expenditure_name VARCHAR(255), unit_price INT NOT NULL, quantity INT NOT NULL, expenditure_date DATE NOT NULL)")
                .then()
                .then(databaseClient.execute("CREATE TABLE IF NOT EXISTS income (income_id INT PRIMARY KEY AUTO_INCREMENT, income_name VARCHAR(255), amount INT NOT NULL, income_date DATE NOT NULL)")
                    .then());
        } else if ("PostgreSQL".equals(name)) {
            return databaseClient.execute("CREATE TABLE IF NOT EXISTS expenditure (expenditure_id SERIAL PRIMARY KEY, expenditure_name VARCHAR(255), unit_price INT NOT NULL, quantity INT NOT NULL, expenditure_date DATE NOT NULL)")
                .then()
                .then(databaseClient.execute("CREATE TABLE IF NOT EXISTS income (income_id SERIAL PRIMARY KEY, income_name VARCHAR(255), amount INT NOT NULL, income_date DATE NOT NULL)")
                    .then());
        }
        return Mono.error(new IllegalStateException(name + " is not supported."));
    }
```
