Spring Bootで試験用シミュレータを作ってみる

様々なシステムと連携する業務用Webアプリを開発するお仕事をしています。
連携先のシステムとの連動が開発工程の後期になることが多いため、それまでの期間中に使用する他システムシミュレータをちゃちゃっと準備したいと思い、今回はSpring Bootで作成してみます。

# 環境

macOS Big Sur
Visual Studio Code 1.53.1

# 要件

- HTTP
- REST API
- JSON
- レスポンスのコード、本文をソース修正なく変更できるようにしたい

# 準備

Javaはインストール済み。
Mavenが入っていないのでbrewでインストールする。

```console
% mvn -v
zsh: command not found: mvn

% brew install maven
...

% mvn -v            
Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
Maven home: /usr/local/Cellar/maven/3.8.1/libexec
Java version: 15.0.2, vendor: N/A, runtime: /usr/local/Cellar/openjdk/15.0.2/libexec/openjdk.jdk/Contents/Home
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "11.2.1", arch: "x86_64", family: "mac"
```

# 作成

[4. 初めての Spring Boot アプリケーションの開発](https://spring.pleiades.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started-first-application) の通りに進めていく。

## POMの作成

任意のフォルダにpom.xmlを作成する。

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <description/>
    <developers>
        <developer/>
    </developers>
    <licenses>
        <license/>
    </licenses>
    <scm>
        <url/>
    </scm>
    <url/>

    <!-- Additional lines to be added here... -->

</project>
```

ターミナルでpom.xmlがある場所に移動し`mvn package`を実行すると、`target`配下に`myproject-0.0.1-SNAPSHOT.jar`が作成される。
初回はライブラリをMaven Central Repositoryからダウンロードするため、時間がかかる。

## サンプルコントローラの作成

ソース用のディレクトリ`src/main/java/`を作成し、配下に`Example.java`を作成する。

```java:Example.java
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.web.bind.annotation.*;

@RestController
@EnableAutoConfiguration
public class Example {

    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }

    public static void main(String[] args) {
        SpringApplication.run(Example.class, args);
    }

}
```

ターミナルで`mvn spring-boot:run`を実行すると、アプリケーションが起動される。

```console
% mvn spring-boot:run
.....

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.4)
.....
2021-04-11 09:53:43.339  INFO 33707 --- [           main] Example                                  : Started Example in 1.892 seconds (JVM running for 2.311)
```

ブラウザで http://localhost:8080 にアクセスすると、`Hello World!`が出力されることを確認できる。

## 実装

mainメソッドはコントローラから切り出しておく。
また、適当にパッケージを切っておく。
※上で作成した`Example.java`は削除

```java:Application.java
package mamfrog;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
```

### リクエストパラメータでレスポンスコードを指定

GETパラメータ`code`で指定した数値をレスポンスコードに設定するコントローラを作成する。

```java:RestTestController.java
package mamfrog.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/rest")
public class RestTestController {

    @RequestMapping("/")
    public ResponseEntity<Map<String, Object>> home(
            @RequestParam(value = "code", required = false, defaultValue = "200") String code) {

        Map<String, Object> map = new HashMap<>();
        map.put("code", code);

        return new ResponseEntity<Map<String, Object>>(map, HttpStatus.resolve(Integer.parseInt(code)));
    }

}
```

呼び出し例）http://localhost:8080/rest/?code=201 

不正な値を指定された場合の考慮はしていない。
レスポンスボディはリクエストの`code`をそのまま返却する。

### レスポンスを任意のJSONにする

上記コントローラを改造し、所定の場所にあるJSONファイルの内容をレスポンスボディとして返却するようにする。
今回はJSONライブラリとして[Jackson](https://github.com/FasterXML/jackson)を使用。（pom.xml略）

- 所定の場所`BASE_PATH`は固定値
- JSONファイル名はリクエストパラメータ`dataId`で指定（`[dataId].json`が所定の場所にあること）
- `dataId`が指定なし、または対応するファイルがない場合は空のJSONを返却

```java:RestTestController.java
package mamfrog.controller;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/rest")
public class RestTestController {

    private static final String BASE_PATH = "./";

    @RequestMapping("/")
    public ResponseEntity<Map<String, Object>> home(
            @RequestParam(value = "code", required = false, defaultValue = "200") String code,
            @RequestParam(value = "dataId", required = false) String dataId) {

        Map<String, Object> map = new HashMap<>();

        if (StringUtils.isNotEmpty(dataId)) {
            try {
                InputStream getLocalJsonFile = new FileInputStream(BASE_PATH + dataId + ".json");
                map = new ObjectMapper().readValue(getLocalJsonFile, HashMap.class);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        return new ResponseEntity<Map<String, Object>>(map, HttpStatus.resolve(Integer.parseInt(code)));
    }

}
```

JSONファイルサンプル

```json:test.json
{
    "key1": "aaaaaaaa",
    "key2": false
}
```

呼び出し例）http://localhost:8080/rest/?dataId=test 


連携試験用に内部で使用するだけなので、要件を満たす最低限のコードはこのくらい。
エラー処理などは必要に応じて追加していく感じ。


# 補足

Rest APIの動作確認はGoogle Chromeの拡張機能「[Talend API Tester](https://chrome.google.com/webstore/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm)」が便利。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1065197/090474e0-47b7-c7cb-cf81-422bf8963a7e.png)