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


概要

前回でDBにつなげられるようになりました。
今回はRestTemplateを使って、外部のAPIにリクエストを送り、そのレスポンスを受け取ってみましょう。
今回使用させていただくAPIは郵便番号検索APIです。

郵便番号検索APIとは

ベースとなるURLにクエリパラメタを付与して送信することで、
検索結果をJSONオブジェクトで返してくれるAPIです。
詳しくはリンク先を参考にしてください。

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

本題

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

1. Controller + Constraintを作る

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));
    }
}
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 {};
}
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). 自作アノテーションの実装です。詳しくはこちらの記事をご覧いただきたいのですが、バリデーションのロジックとしては@Patternの部分のみです。
(3). @RestControllerAdviceを付与すると@RestControllerが付与された全コントローラに共通する処理(AOP)を実装できます。@ExceptionHandlerの引数に該当する例外をコントローラが投げると、そのメソッドが動きます。この組み合わせはよく使うと思うので覚えておくと便利だと思います。
(4). @ExceptionHandlerを使用して実装したハンドラの戻り値はResponseEntityクラスとしておきます。これによって与えらえれたボディとHTTPステータスを用いて良しなにHTTPレスポンスに変換してくれます。因みにHeader属性も指定できたりします。

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

2. Clientを作る

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);
    }
}
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で受け取ることができるようになります。こちらこちらを参考にしてください。※実装は丸パクリです。

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

3. 実際に叩いてみる

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

では叩いていきます。

3-1. 存在する郵便番号を送信する(ハイフンあり) ⇒ 住所情報が取得できる

結果:OK!!

3-2. 存在する郵便番号を送信する(ハイフンなし) ⇒ 住所情報が取得できる

結果:OK!!

3-3. 存在しない郵便番号を送信する ⇒ 住所情報が空の状態で取得できる

結果:OK!!

3-4. バリデーション違反(数値の桁数不足) ⇒ バリデーションエラーとなる

結果:OK!!

3-5. バリデーション違反(ハイフンが複数) ⇒ バリデーションエラーとなる

結果:OK!!

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

最後に

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

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