【API】郵便番号検索APIを使ってみた

#APIを使用したアプリを作ってみる

#背景
 - APIを使用したアプリケーションが増えてきている
 - APIを使用する、作るエンジニアの仕事も増えている
 - APIを扱う技術の需要が増えそう

#目次
*  [0. 環境確認](#0環境確認)
*  [1. APIの確認](#1apiの確認)
*  [2. プロジェクトの作成](#2プロジェクトの作成)
*  [3. バックエンドの実装](#3バックエンドの実装)
*  [4. フロントエンドの実装](#4フロントエンドの実装)
*  [5. 動作確認](#5動作確認)
*  [6. おまけ](#6おまけ)
*  [7. まとめ](#7まとめ)

#0.環境確認
 - ___OS___： Windows10 home
 - ___IDE___ : Eclipse(Photon 4.8)
 - ___ビルドツール___ : Gradle
 - ___サーバーサイド言語___ : Java(1.8)
 - ___フレームワーク___ : SpringBoot(2.2.7)
 - ___JavaScript ライブラリ___ : jQuery(3.3.1)
 - ___テンプレートエンジン___ : thymeleaf

#1.APIの確認
 - 以下のサイトからAPIの使用を確認します。
 [https://zip-cloud.appspot.com/doc/api](https://zip-cloud.appspot.com/doc/api)

 - 仕様を確認します。
  -  https://zip-cloud.appspot.com/api/search をベースにする
  -  リクエストパラメータ
    
    |  パラメータ名  |  項目名  |  必須  |  備考  |
    | ---- | ---- | ---- | ---- |
    |  zipcode  |  郵便番号  |  ○  |  7桁の数字。ハイフン付きでも可。完全一致検索。  |
    |  callback  |  コールバック関数名  |  -  |  JSONPとして出力する際のコールバック関数名。UTF-8でURLエンコードした文字列。  |
    |  limit  |  最大件数  |  - |  同一の郵便番号で複数件のデータが存在する場合に返される件数の上限値（数字）　※デフォルト：20  |
  -  レスポンスパラメータ
    
    |  フィールド名	 |  項目名  |  備考  |
    | ---- | ---- | ---- |
    |  status  |  ステータス  |  正常時は 200、エラー発生時にはエラーコードが返される  |
    |  message  | メッセージ  |  エラー発生時に、エラーの内容が返される。  |
    |  results  | zipcode(郵便番号) <br> prefcode(都道府県コード)<br> address1(都道府県名) <br> address2(市区町村名)<br>address3(町域名) <br>kana1(都道府県名カナ)<br>kana2	(市区町村名カナ) <br>kana3(町域名カナ) |  複数の場合、配列となる  |

  -  （例）郵便番号「7830060」で検索する場合
      -  リクエストURL
        https://zip-cloud.appspot.com/api/search?zipcode=7830060
      -  レスポンス<br>  
        {
	"message": null,
	"results": [
		{
			"address1": "北海道",
			"address2": "美唄市",
			"address3": "上美唄町協和",
			"kana1": "ﾎｯｶｲﾄﾞｳ",
			"kana2": "ﾋﾞﾊﾞｲｼ",
			"kana3": "ｶﾐﾋﾞﾊﾞｲﾁｮｳｷｮｳﾜ",
			"prefcode": "1",
			"zipcode": "0790177"
		},
		{
			"address1": "北海道",
			"address2": "美唄市",
			"address3": "上美唄町南",
			"kana1": "ﾎｯｶｲﾄﾞｳ",
			"kana2": "ﾋﾞﾊﾞｲｼ",
			"kana3": "ｶﾐﾋﾞﾊﾞｲﾁｮｳﾐﾅﾐ",
			"prefcode": "1",
			"zipcode": "0790177"
		}
	],
	"status": 200
}

#2.プロジェクトの作成
別の記事で詳細に紹介しているので、そちらを参照ください。
 [GradleのSpringBootプロジェクトを作成する](https://qiita.com/j-work/items/8c6c161b0f0baaaf2c3f)


#3.バックエンドの実装
 - build.gradle

```gradle:build.gradle
//中略
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	compile("com.fasterxml.jackson.core:jackson-databind")
}


```

 - Controllerクラス
  - パターン1：Json形式の文字列で受け取り

```java:FrontController.java
@Controller
public class FrontController {

	@Autowired
	private FrontService frontService;

    @RequestMapping({ "/", "/index" })
    public String index() {
    	return "index";
    }
	@ResponseBody
    @RequestMapping(value = "/getAddress" ,method = RequestMethod.POST, produces="application/json;charset=UTF-8")
    public String getAddress(@RequestBody(required = false) AddressForm addressForm) {
		return frontService.getAddress(addressForm.getZipcode());
    }
}
```
 - Serviceクラス
 
```java:FrontService.java 
public interface FrontService {
	public String getAddress(String zipCode);
}
```
```java:FrontServiceImpl.java
@Service
public class FrontServiceImpl implements FrontService {

    /** 郵便番号検索API リクエストURL */
    private static final String URL = "https://zip-cloud.appspot.com/api/search?zipcode={zipcode}";

	@Override
	public String getAddress(String zipCode) {
		String zipCodeJson = restTemplate.getForObject(URL, String.class, zipCode);
		return zipCodeJson;
	}
}
```

 - formクラス

```java:AddressForm.java
@Data
public class AddressForm {

	/** 郵便番号 */
	String zipcode;
}
```

#4.フロントエンドの実装
 - html

```html:index.html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>address</title>
		<script type="text/javascript" th:src="@{/jquery/jquery-3.3.1.js}"></script>
		<script th:src="@{/js/index.js}"></script>
	</head>
	<body>
		<form name="getAddress">
			<input id="zipcode" type="text">
			<button type="button" id="getAddressBtn">住所取得</button>
			<div id="dispAddress"></div>
		</form>
	</body>
</html>
``` 
 - JavaScript

```javascript:index.js
$(function() {
	$('#getAddressBtn').on('click', function() {
		var params = {
				"zipcode" : $('#zipcode').val()
		};
		$.ajax({
			url : 'getAddress',
			type: 'POST',
			contentType: "application/json",
	        data: JSON.stringify(params),
			dataType : 'json',
			async: false,
			success: function (data) {
				$("#dispAddress").empty();
			    var dispAddress = document.getElementById("dispAddress");
				var table = document.createElement("table");
				table.setAttribute("border","2");
				table.setAttribute("cellpadding","15");
				table.setAttribute("style","margin :15px");

			    $(data.results).each(function(index, result){
					table.appendChild(createRow("郵便番号",result.zipcode));
					table.appendChild(createRow("都道府県コード",result.prefcode));
					table.appendChild(createRow("都道府県名",result.address1));
					table.appendChild(createRow("市区町村名",result.address2));
					table.appendChild(createRow("町域名",result.address3));
					table.appendChild(createRow("都道府県名カナ",result.kana1));
					table.appendChild(createRow("市区町村名カナ",result.kana2));
					table.appendChild(createRow("町域名カナ",result.kana3));
			    });
				dispAddress.appendChild(table);
			}
		});
	});
});

function createRow(header , value){
	var tr = document.createElement("tr");
	var th = document.createElement("th");
	th.append(header);
	var td = document.createElement("td");
	td.append(value);
	tr.appendChild(th);
	tr.appendChild(td);
	return tr;
}
```

#5.動作確認
 - ブラウザで「localhost:8080」にアクセス
![ブラウザ起動.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621352/73f46076-adc5-ad29-1b85-f571aaee3c0d.png)

 - 「100-0001」（皇居の郵便番号）を入力して住所取得ボタン押下
![ブラウザ起動2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/621352/477f038d-1651-4ab3-ca59-b5b12e28a829.png)

 - 住所が表示された。

#6.おまけ
___Json形式をDTOクラスに変換して受け取る方法___
 - Controllerクラス
 
```java:FrontController.java
@Controller
public class FrontController {

	@Autowired
	private FrontService frontService;

    @RequestMapping({ "/", "/index" })
    public String index() {
    	return "index";
    }
	@ResponseBody
    @RequestMapping(value = "/getAddress" ,method = RequestMethod.POST, produces="application/json;charset=UTF-8")
    // 戻り値をString → ZipcodeDto に変更
    public ZipcodeDto getAddress(@RequestBody(required = false) AddressForm addressForm) {
		return frontService.getAddress(addressForm.getZipcode());
    }
}
```

 - Serviceクラス（修正）
 
```java:FrontService.java 
public interface FrontService {
    // 戻り値をString → ZipcodeDto に変更
	public ZipcodeDto getAddress(String zipCode); 
}
```
 - DTOクラス（追加）

```java:ZipcodeDto.java
@Data
public class ZipcodeDto {
    /** ステータス */
    int status;

    /** メッセージ */
    String message;

    /** 郵便番号情報リスト */
    List<ZipcodeResultDto> results = new ArrayList<>();
}
```
 - DTOクラス（追加）

```java:ZipcodeResultDto.java
@Data
public class ZipcodeResultDto {

	/** 郵便番号 */
    String zipcode;

    /** 都道府県コード */
    String prefcode;

    /** 都道府県名 */
    String address1;

    /** 市区町村名 */
    String address2;

    /** 町域名 */
    String address3;

    /** 都道府県名カナ */
    String kana1;

    /** 市区町村名カナ */
    String kana2;

    /** 町域名カナ */
    String kana3;
}

```

 - Service実装クラス
   - ObjectMapperにURLを渡してDTOクラスに変換するパターン
 

```java:FrontServiceImpl.java
@Service
public class FrontServiceImpl implements FrontService {
     // ObjectMapperを追加
	@Autowired
	ObjectMapper objectMapper;

     // URLのパラメータを正規表現に変更
    private static final String URL = "https://zip-cloud.appspot.com/api/search?zipcode=%s";

	@Override
	public ZipcodeDto getAddress(String zipCode) {
		ZipcodeDto zipcodeDto = null;;
		try {
            // ObjectMapperでURLと受け取りクラスを指定
			java.net.URL url = new java.net.URL(String.format(URL,zipCode));
			zipcodeDto = objectMapper.readValue(url, ZipcodeDto.class);
		} catch (Exception e) {
			e.getStackTrace();
		}
		return zipcodeDto;
	}
}
``` 
 - Service実装クラス
   - restTemplateにMappingJackson2HttpMessageConverterを設定して変換するパターン 


```java:FrontServiceImpl.java
@Service
public class FrontServiceImpl implements FrontService {

     // restTemplateを追加
	RestTemplate restTemplate = new RestTemplate();

    private static final String URL = "https://zip-cloud.appspot.com/api/search?zipcode={zipCode}";

	@Override
	public ZipcodeDto getAddress(String zipCode) {
		ZipcodeDto zipcodeDto = null;;
		try {

           // reatTemplateのmessageConverterにMappingJackson2HttpMessageConverter を追加
			MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
		    List<MediaType> supportedMediaTypes = new ArrayList<>(messageConverter.getSupportedMediaTypes());
		    supportedMediaTypes.add(MediaType.TEXT_PLAIN);
		    messageConverter.setSupportedMediaTypes(supportedMediaTypes);
		    restTemplate.setMessageConverters(Collections.singletonList(messageConverter));
			zipcodeDto = restTemplate.getForObject(URL, ZipcodeDto.class, zipCode);
		} catch (Exception e) {
			e.getStackTrace();
		}
		return zipcodeDto;
	}
}
```

#7.まとめ
 - APIを使用するのは簡単（認証機能付きはもう少し難しい）
 - 他APIからデータ活用できる
 - いろんなAPIを組み合わせて新しいサービスを作れそう。