この記事で書いていたことをイメージするために、具体的なコードにしてみました。
コードで具体的に確認してみます。
コードの前提
- Eclipse2019年版
- JavaバージョンやSpringBootのバージョンは以下の通り
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
コード設計
前記事で書いたクラス図はこんな感じでした。
突貫で作ったのでクラス名などが若干違いますが、置き換えて理解してください。
Controller
@RestController
@AllArgsConstructor
public class SampleController {
/** ユーザー検索API */
private final APIService<UserSearchRequestDto, UserSearchResponseDto> userSearchService;
/**
* リクエスト処理
* @return レスポンス電文
*/
@GetMapping("sample")
public String index() {
UserSearchRequestDto request = UserSearchRequestDto.initialize("userid", "authorize");
UserSearchResponseDto response = userSearchService.execute(request);
System.out.println(response);
return "result string";
}
}
フィールドのAPIServiceは総称型のDTOを指定してUserSearchRequestDtoの実体を生成しています。
こうすることで、各APIの要求する電文情報を個別に定義することができる様になり、柔軟性を劇的に向上させることができます。
UserRequestService
package com.example.demo.service.api.users;
import java.io.IOException;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
import org.springframework.stereotype.Component;
import com.example.demo.controller.serviceis.APIService;
import com.example.demo.dto.api.users.UserSearchRequestDto;
import com.example.demo.dto.api.users.UserSearchResponseDto;
import com.example.demo.service.api.AbstractApi;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* ユーザー検索APIを実行する
*
*/
@Component
public class UserSearchService extends AbstractApi<UserSearchRequestDto>
implements APIService<UserSearchRequestDto, UserSearchResponseDto> {
/**
* コンストラクタ<br>
* 内部で使用するリクエスト情報を渡す。
*
* @param request リクエスト情報
*/
public UserSearchService(UserSearchRequestDto request) {
super(request);
}
/**
* リクエスト情報をAPIに送信する
*/
@Override
public UserSearchResponseDto execute(UserSearchRequestDto request) {
//APIがJSONを要求しているなら、引数からJSONを生成する。
//Jacksonでも使って、DTOからJsonを作る
String json = null;
try {
json = new ObjectMapper().writeValueAsString(request);
} catch (JsonProcessingException e) {
e.printStackTrace();
//例外処理はプロジェクトのルールに則ってスローするなり握り潰すなりしてください
}
//送信処理を呼び出し
String responseString = send(json);
ObjectMapper mapper = new ObjectMapper();
UserSearchResponseDto responseDto = null;
try {
responseDto = mapper.readValue(responseString, UserSearchResponseDto.class);
} catch (JsonMappingException e) {
//例外処理はプロジェクトのルールに則ってスローするなり握り潰すなりしてください
e.printStackTrace();
} catch (JsonProcessingException e) {
//例外処理はプロジェクトのルールに則ってスローするなり握り潰すなりしてください
e.printStackTrace();
}
return responseDto;
}
@Override
protected HttpsURLConnection createHeader(UserSearchRequestDto request) {
URL url;
HttpsURLConnection connection = null ;
try {
//このURLは本クラスのフィールドに用意した設定情報から受け取るなど改良してください。UserSearchRequestDtoには持たせない方がいいです。
url = new URL("https://~~");
connection = (HttpsURLConnection) url.openConnection();
connection.addRequestProperty("Authorization", request.getAuthorization());
} catch (IOException e) {
e.printStackTrace();
//例外処理はプロジェクトのルールに則ってスローするなり握り潰すなりしてください
}
return connection;
}
}
インタフェースから実装したexecuteメソッドです。
ここでは、APIごとにリクエストの形式を調整してsendメソッドに渡します。また、sendメソッドの戻り値をDTOに変換して、API固有の応答としてあげます。
ここはプレゼンテーション層ということができると思います。
また、createHeaderメソッドは抽象メソッドのため、ここで実装しています。API固有のヘッダ情報などを設定してやりましょう。
AbstractApi
package com.example.demo.service.api;
import javax.net.ssl.HttpsURLConnection;
import lombok.AllArgsConstructor;
/**
* WebAPIへの共通処理クラス
* @param <T>
*/
@AllArgsConstructor
public abstract class AbstractApi<T> {
/**リクエスト情報*/
private final T request;
/**
* APIへ電文を送信する
* @param body 送信する電文
* @return 受信した応答電文
*/
protected String send(String body) {
//まずはヘッダの生成
HttpsURLConnection connection = createHeader(request);
//あとはボディの設定やリクエスト送信、タイムアウト処理、リトライ処理などを実装
//この辺で、受信した応答電文から文字列を取得
StringBuffer sb = new StringBuffer();
//今は固定のJSONを設定
sb.append("{\"test\":\"testvalue\"}");
//受信した応答電文を返す
return sb.toString();
}
/**
* リクエストのヘッダを設定する
*
* @param request リクエスト情報
* @return
*/
protected abstract HttpsURLConnection createHeader(T request);
}
APIへの通信部分を実装します。
コメントで省略していますが、この中で通信の実行やリトライ制御をかけてください。
ざっくりですが、いかがでしょうか。
コードをみるとよりわかりやすくなりますね。