きり丸の技術日記

技術検証したり、資格等をここに残していきます。

SpringBootで別システムのAPIを実行する(RestOperations)

きり丸アドベントカレンダー2020の23記事目です。


自システムのREST APIを作成するときには、@RestControllerを付与すれば簡単に実装できます。

今回はRestOperationsを使用して、別システムのAPIを実行します。

ゴール

  • アプリケーションから別システムに対してREST通信を行う

今回は、次のサイトが提供してくれている住所コードから住所名を取得するAPIを実行しましょう。トークン発行等が不要です。

zipcloud.ibsnet.co.jp

APIの使い方


GETメソッドで次のURLを実行します。クエリパラメータで「zipcode」に住所コードを入力すると、住所名を取得できます。

https://zipcloud.ibsnet.co.jp/api/search?zipcode=2430018

レスポンスのBody:

{
  "message": null,
  "results": [
    {
      "address1": "神奈川県",
      "address2": "厚木市",
      "address3": "中町",
      "kana1": "カナガワケン",
      "kana2": "アツギシ",
      "kana3": "ナカチョウ",
      "prefcode": "14",
      "zipcode": "2430018"
    }
  ],
  "status": 200
}

環境

  • Java
    • 15
  • org.springframework.boot:spring-boot-starter-web
    • 2.4.0
  • Lombok

手順

外部連携用の設定ファイルを用意する


外部システムのURLを構築するための設定ファイルを用意します。現時点では分割する必要はありませんが、タイムアウト値等は共通的に持っておく方があとあと便利です。

YAMLファイルでの設定値を共通的にもつ方法は、こちらを参照してください。

※アンカーとエイリアスを紹介した時の記事

ファイル名:application-external.yml

external:
  commons: &external-common
    schema: https
    connectionTimeout: 3
    readTimeout: 3
  zip-cloud:
    <<: *external-common
    host: zipcloud.ibsnet.co.jp
    path: /api/search

外部連携用の設定ファイルを読み込むJavaファイルを用意する


URLの作成ロジックは共通的に作っておいた方が楽です。ですので、私は設定ファイルを読み込むJava側でロジックを持ちます。

今回はUriComponentsBuilderを使ってURLを構築します。

特殊な設定値はportくらいです。portを-1に設定した時にポート番号をURLに含めなくなります。

port 生成されるURI
80 http://localhost:80/api/v1
-1 http://localhost/api/v1

また今回はクエリパラメータに?zipcode={値}を設定したいので、クエリパラメータをURL生成時に作成します。

ファイル名:ExternalProperties.java

@Setter
public abstract class ExternalProperties {
  private String schema;
  private String host;
  private int port;
  private String path;

  @Getter
  private int connectionTimeout;
  @Getter
  private int readTimeout;

  private UriComponentsBuilder createUriBuilder() {
    return UriComponentsBuilder.newInstance()
        .scheme(schema)
        .host(host)
        .port(port != 0 ? port : -1)
        .path(path);
  }

  public String getUrl(Map<String, String> map) {
    UriComponentsBuilder builder = createUriBuilder();

    map.forEach(builder::queryParam);
    return builder.toUriString();
  }
}

実際に読み込ませるときは、作成したJavaファイルを継承しましょう。

@ConfigurationPropertiesを指定することで、設定ファイルを読み込むことができます。

ファイル名:ZipCloudClientProperties.java

@Component
@ConfigurationProperties(prefix = "external.zip-cloud")
public class ZipCloudClientProperties extends ExternalProperties {
}

APIの取得結果を受け取るDTOを作成する


レスポンスを受け取って処理をしたいので、型を作りましょう。

単純に項目名とマッピングできるDTOを作成すれば大丈夫です。

ファイル名:ZipCloudDto.java

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ZipCloudDto {
  private String message;
  private List<ZipAddressDto> results;
  private String status;
}

ファイル名:ZipAddressDto.java

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ZipAddressDto {
  private String address1;
  private String address2;
  private String address3;
  private String kana1;
  private String kana2;
  private String kana3;
  private String prefcode;
  private String zipcode;
}

APIを実行するコードを書く


RestTemplateを使用して、別システムへアクセスします。

RestTemplateBuilderからRestTemplateを生成します。タイムアウト値等やインターセプターを設定してから生成できます。

今回使用する「ZipCloud」のAPIは、レスポンスのメディアタイプがtext/plainなのでデフォルトだと受け取れません。text/plainを受け取れるようにコンバータをRestTemplateに追加します。

実際に他システムのAPIを実行するgetForEntity()の第一パラメータにURL、第二パラメータに返却の型を指定します。

@Component
@RequiredArgsConstructor
public class ZipCloudClientImpl implements ZipCloudClient {
  private final ZipCloudClientProperties props;
  private final RestTemplateBuilder restTemplateBuilder;

  @Override
  public ZipCloudDto getAddressByZipcode(String zipCode) {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setSupportedMediaTypes(List.of(MediaType.TEXT_PLAIN));

    RestTemplate restTemplate = restTemplateBuilder
        .setConnectTimeout(Duration.ofSeconds(props.getConnectionTimeout()))
        .setReadTimeout(Duration.ofSeconds(props.getReadTimeout()))
        .build();
    restTemplate.getMessageConverters().add(converter);

    HashMap<String, String> hashMap = new HashMap<>();
    hashMap.put("zipcode", zipCode);

    ResponseEntity<ZipCloudDto> responseEntity = restTemplate.getForEntity(props.getUrl(hashMap), ZipCloudDto.class);
    return responseEntity.getBody();
  }
}

テストコードを書く


RestTemplateBuilderがSpringのDI機能を使用しているので、DIできるように@SpringJUnitConfigを使用します。読み込む設定ファイルも必要になるので、RestTemplateAutoConfiguration.classも指定します。

後は基本的にはコードを見ていただければ分かるかと思います。書いてあるのはセットアップとテストだけなので。なお、設定値を見ていただけると分かるとおり、相手の本番環境にアクセスします。

相手の環境がダウンしているとアクセス失敗し、テストが正常動作しません。挙動確認が終わり次第、自前でモックサーバーを立てるとよいでしょう。モックサーバーの建て方も記事にする予定です。

@SpringJUnitConfig(classes = {
    RestTemplateAutoConfiguration.class
})
class ZipCloudClientImplTests {

  ZipCloudClient target;

  @Autowired
  private RestTemplateBuilder restTemplateBuilder;

  @BeforeEach
  void setup(){
    ZipCloudClientProperties properties = new ZipCloudClientProperties();
    properties.setSchema("https");
    properties.setHost("zipcloud.ibsnet.co.jp");
    properties.setPort(0);
    properties.setPath("/api/search");

    target = new ZipCloudClientImpl(properties, restTemplateBuilder);
  }

  @Test
  void test_01(){
    ZipCloudDto expected = ZipCloudDto.builder()
        .message(null)
        .results(List.of(
            ZipAddressDto.builder()
                .address1("神奈川県")
                .address2("厚木市")
                .address3("中町")
                .kana1("カナガワケン")
                .kana2("アツギシ")
                .kana3("ナカチョウ")
                .prefcode("14")
                .zipcode("2430018")
                .build()
        ))
        .status("200")
        .build();

    ZipCloudDto actual = target.getAddressByZipcode("2430018");

    assertThat(actual).isEqualTo(expected);
  }

  @Test
  void test_02(){
    ZipCloudDto expected = ZipCloudDto.builder()
        .message("パラメータ「郵便番号」の桁数が不正です。")
        .status("400")
        .build();

    ZipCloudDto actual = target.getAddressByZipcode("XXX");

    assertThat(actual).isEqualTo(expected);
  }

}

ソースコード

アドベントカレンダー23日目。
github.com

終わりに

自分で作ったシステムでできることには限界があります。やれることを増やすためにも、別システムを使用したり逆に情報を提供したりする必要があります。

REST通信できることで、やれることが大きく広がりますので、ぜひ試してみてください。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考記事

敬称略

paiza開発日誌:面倒な手続き不要!「Web API」の超お手軽活用術をJavaScriptコード付きで一挙大公開! paiza.hatenablog.com

類似記事

きり丸アドベントカレンダー2020 adventar.org

きり丸のHerokuページ
https://kirimaru-todoapp.herokuapp.com/

24日目のアドベントカレンダーの記事 https://nainaistar.hatenablog.com/entry/2020/12/24/083000nainaistar.hatenablog.com

f:id:nainaistar:20201109133010p:plain