Java クラス設計

オブジェクト指向、SOLID原則に基づいたHTTPリクエスト通信部分を具体的なコードで説明

2020年11月22日

この記事で書いていたことをイメージするために、具体的なコードにしてみました。

コードで具体的に確認してみます。

コードの前提

  • 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>


コード設計

前記事で書いたクラス図はこんな感じでした。

オブジェクト指向、SOLID原則に基づいたHTTPリクエスト通信部分を具体的なコードで説明

突貫で作ったのでクラス名などが若干違いますが、置き換えて理解してください。

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への通信部分を実装します。
コメントで省略していますが、この中で通信の実行やリトライ制御をかけてください。


ざっくりですが、いかがでしょうか。

コードをみるとよりわかりやすくなりますね。

###############お知らせ################
ブログランキングのITカテゴリに参加してみました。
この記事が役に立ったなどお力になれたら、 このバナーを押していただけると嬉しいです。

#####################################

-Java, クラス設計