03. SpringBootから郵便番号検索APIへリクエストを送ってみた

# 概要
[前回](https://qiita.com/supreme0110/items/504781e5087f4c4e771b)でDBにつなげられるようになりました。
今回はRestTemplateを使って、外部のAPIにリクエストを送り、そのレスポンスを受け取ってみましょう。
今回使用させていただくAPIは[郵便番号検索API](http://zipcloud.ibsnet.co.jp/doc/api)です。

# 郵便番号検索APIとは
ベースとなるURLにクエリパラメタを付与して送信することで、
検索結果をJSONオブジェクトで返してくれるAPIです。
詳しくは[リンク先](http://zipcloud.ibsnet.co.jp/doc/api)を参考にしてください。

ではさっそくAPIを作っていきましょう。

# 本題
今回の構成としては郵便番号をクエリパラメタで受け取るコントローラクラスと、
その郵便番号をクエリパラメタとして郵便番号検索APIに送信、結果を受け取るクライアントクラスを作っていきましょう。（データクラスについては割愛します）
郵便番号を入力値として受け取りますので、簡単なバリデーションとエラーハンドラーも実装していきます。

## 1. Controller + Constraintを作る
``` java:GetAddressController.java
@RestController
@RequiredArgsConstructor
@Validated  // (1)
public class GetAddressController {
    private final GetAddressApiClient client;

    @GetMapping("/get/address")
    public GetAddressResponse getAddress(@ZipCode @RequestParam("zipcode") String zipCode) {  // (1)
        return GetAddressResponse.create(client.request(zipCode));
    }
}
```

``` java:ZipCode.java
@Documented
@Constraint(validatedBy = {})
@Target(PARAMETER)
@Retention(RUNTIME)
@Pattern(regexp = "[0-9]{3}[-]{0,1}[0-9]{4}")  // (2)
public @interface ZipCode {
    String message() default "";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
```

``` java:CommonExceptionHandler.java
@RestControllerAdvice  // (3)
public class CommonExceptionHandler {

    @ExceptionHandler(ConstraintViolationException.class)  // (3)
    public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex) {
        GetAddressResponse response = new GetAddressResponse(
                "400",
                "入力された郵便番号が不正です。",
                null
        );
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);  // (4)
    }
}
```
では前回に続き簡単な説明をしていきます。
(1). `@Validated`によってバリデーションが有効化されます。また自作アノテーション(`@ZipCode`)をクエリパラメタに付与することでバリデーションチェックができ、違反すると`ConstraintViolationException`が投げられます。
(2). 自作アノテーションの実装です。詳しくは[こちら](https://qiita.com/b1ueskydragon/items/07ac1f9bc8c55dffc541)の記事をご覧いただきたいのですが、バリデーションのロジックとしては`@Pattern`の部分のみです。
(3). `@RestControllerAdvice`を付与すると`@RestController`が付与された全コントローラに共通する処理(**AOP**)を実装できます。`@ExceptionHandler`の引数に該当する例外をコントローラが投げると、そのメソッドが動きます。この組み合わせはよく使うと思うので覚えておくと便利だと思います。
(4). `@ExceptionHandler`を使用して実装したハンドラの戻り値は`ResponseEntity`クラスとしておきます。これによって与えらえれたボディとHTTPステータスを用いて良しなにHTTPレスポンスに変換してくれます。因みにHeader属性も指定できたりします。

ざっくりこんな感じです。
正規表現をご覧いただければ分かるかと思いますが、郵便番号検索APIの仕様に合わせて「-」はあってもなくてもよい方針としています。
次にClientクラスです。


## 2. Clientを作る

``` java:GetAddressApiClient.java
@Component
public class GetAddressApiClient {
    private final RestTemplate restTemplate;

    public GetAddressApiClient(RestTemplateBuilder restTemplateBuilder,
                               ResponseHeaderInterceptor interceptor) {  // (1)
        restTemplate = restTemplateBuilder
                .rootUri("http://zipcloud.ibsnet.co.jp")
                .additionalInterceptors(interceptor)
                .build();
    }

    public GetAddressApiResponse request(String zipCode) {
        return restTemplate.getForObject("/api/search?zipcode={zipCode}",  // (2)
                GetAddressApiResponse.class,
                zipCode);
    }
}
```

``` java:ResponseHeaderInterceptor.java
@Component
public class ResponseHeaderInterceptor implements ClientHttpRequestInterceptor {

    @Override  // (3)
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {
        ClientHttpResponse response = execution.execute(request, body);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        return response;
    }

}
```
(1). コンストラクタで`RestTeplate`を初期化しています。各実装メソッドで初期化する方法でもよいですが、今回は複数のメソッドを実装する想定で共通部分はコンストラクタで初期化していく実装としました。`rootUri`はそのままルートとなるリクエストURIを設定し、`additionalInterceptors`でレスポンス形式を設定しています。
(2). リクエストを送信しレスポンスを受け取る実装メソッドです。`getForObject()`メソッドの第一引数にリクエストパス、第二引数にレスポンスクラス、第三引数にパラメタにマッピングさせる値を渡します。実装としてはこれだけでリクエストを送信してレスポンスが受け取れるわけです。
(3). `ClientHttpRequestInterceptor`を実装したクラスを`RestTemplate`にaddしてあげることで、レスポンスを`application/json`で受け取ることができるようになります。[こちら](https://qiita.com/yk0614/items/c306bddc66ab7882c7ba)や[こちら](https://qiita.com/shotana/items/14972c681e5fc315deec)を参考にしてください。※実装は丸パクリです。

さあこれだけで実装完了です。
最後に想定通りの動きをしているかPostmanからAPIをたたいてみましょう！

## 3. 実際に叩いてみる
テストパターンとしては以下5パターンとしましょう。
1. 存在する郵便番号を送信する(ハイフンあり) ⇒ 住所情報が取得できる
2. 存在する郵便番号を送信する(ハイフンなし) ⇒ 住所情報が取得できる
3. 存在しない郵便番号を送信する ⇒ 住所情報が空の状態で取得できる
4. バリデーション違反(数値の桁数不足) ⇒ バリデーションエラーとなる
5. バリデーション違反(ハイフンが複数) ⇒ バリデーションエラーとなる

では叩いていきます。

### 3-1. 存在する郵便番号を送信する(ハイフンあり) ⇒ 住所情報が取得できる
<font color="red">結果：**OK!!**</font>
![打鍵2.png](https://qiita-image-store.s3.amazonaws.com/0/188440/c955142a-8fb6-b7db-330c-67a7daea249d.png)

### 3-2. 存在する郵便番号を送信する(ハイフンなし) ⇒ 住所情報が取得できる
<font color="red">結果：**OK!!**</font>
![打鍵1.png](https://qiita-image-store.s3.amazonaws.com/0/188440/3d2878be-773d-e2a7-607e-e8b21856f8cf.png)

### 3-3. 存在しない郵便番号を送信する ⇒ 住所情報が空の状態で取得できる
<font color="red">結果：**OK!!**</font>
![打鍵3.png](https://qiita-image-store.s3.amazonaws.com/0/188440/d31e9228-2322-3416-dc83-b830768126d7.png)

### 3-4. バリデーション違反(数値の桁数不足) ⇒ バリデーションエラーとなる
<font color="red">結果：**OK!!**</font>
![打鍵4.png](https://qiita-image-store.s3.amazonaws.com/0/188440/dcbfe1b3-4cd9-1b72-5d2a-5f0988622cc0.png)

### 3-5. バリデーション違反(ハイフンが複数) ⇒ バリデーションエラーとなる
<font color="red">結果：**OK!!**</font>
![打鍵5.png](https://qiita-image-store.s3.amazonaws.com/0/188440/9583e580-1981-0310-3fc4-5258d2085474.png)

無事に動いているようです！！

# 最後に
これで外部のAPIに対してリクエストを送ることも出来るようになりました！
またシンプルではありますが簡単なバリデーションチェックも実装できたので、
簡単なAPIであれば作ることができるようになったと思います！

間違い等あればコメントお待ちしております！
最後までお読みいただきありがとうございましたー！！