【CodePipeline×ElasticBeanstalk】JavaアプリケーションをCodePipelineでElasticBeanstalkにCI/CDする その1

Javaアプリケーション（データベースから値を取得し、JSON形式で結果を返す）をCodePipelineでElasticBeanstalkにCI/CDするハンズオンです。

長いので、

- 【CodePipeline×ElasticBeanstalk】JavaアプリケーションをCodePipelineでElasticBeanstalkにCI/CDする その1（本記事）
<br>→ 1. Java（Spring Boot）アプリケーションの作成, 2. Gitリポジトリの作成
- [【CodePipeline×ElasticBeanstalk】JavaアプリケーションをCodePipelineでElasticBeanstalkにCI/CDする その2](https://qiita.com/C_HERO/items/37b931cca5892c2345ff)
<br>→ 3. Elastic Beanstalk環境の作成, 4. データベースの設定
- [【CodePipeline×ElasticBeanstalk】JavaアプリケーションをCodePipelineでElasticBeanstalkにCI/CDする その3](https://qiita.com/C_HERO/items/62e8344f3b3de6f464b4)
<br>→ 5. パイプラインの作成, 6. 後片付け

にの3記事に内容を分割しています。
作業時間は事前準備を除いて、トータル3時間前後を想定。各記事1時間程度が目安。

## 環境
- OS：Windows10
- IDE：Eclipse 2020-03
- JDK：Amazon Correto 8
- フレームワーク：Spring Boot
- AWS
    - CodePipeline
        - CodeCommit
        - CodeBuild
        - CodeDeploy
    - Elastic Beantalk
        - Java SE（Java 8 バージョン 2.10.8）
      <br>→ 異なるプラットフォームブランチ（Corretto 11など）でも同手順でできます。
        - RDS：MySQL Community Edition（バージョン8.0.17）

## イメージ図
本手順でできる内容のざっくりとしたイメージ図です。
ローカルで編集したコードをコミット → CodeCommitにプッシュすると、CodePipelineにより、アプリケーション実行環境であるElastic Beanstalkまで自動でビルド、デプロイを行ってくれるCI/CDの仕組みを構築します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/c0bf756e-3372-fe67-5d3d-9d9f8475eb38.png)


## 事前準備
①Eclipseが未インストールの場合は、
[Pleiades All in One Eclipse ダウンロード(リリース 2020-06) | MergeDoc Project](https://mergedoc.osdn.jp/)
より最新版（2020年8月3日時点）をダウンロードしてください。STS と Lombok が設定済みのため、Spring Bootによる開発にすぐ着手できます。

②JDKはAmazon Correto 8を想定しています。
[Amazon Corretto 8 のダウンロード | aws](https://docs.aws.amazon.com/ja_jp/corretto/latest/corretto-8-ug/downloads-list.html)
より、ダウンロード→インストールし、Eclipseへの設定を済ませておいてください。

③CodeCommitが使えるよう、
[AWSのCodeCommit使い方。アクセスキー作るところからなど。| Qiita](https://qiita.com/sachiko-kame/items/dff732276dffd22e7682)
を参考に、事前準備を行っておいてください。

## 手順
### 1. Java（Spring Boot）アプリケーションの作成
まず、EclipseでJava（Spring Boot）アプリケーションを作成します。
店舗IDをもとに、店舗詳細情報を取得する至ってシンプルな内容になっています。
パッケージの構成は以下の通り。

```
sample-eb-java
     |
    src/main/java
     |----jp.co.sample_eb_java
     |               |---- app
     |               |     |---- controller
     |               |     |---- response
     |               |
     |               |---- domain
     |               |      |---- service
     |               |      |---- repository
     |               |      |---- model
     |               |  
     |               |---- exception
     |               
     |
    src/main/resources
     |----application.yml
     |
    Buildfile
    Procfile
    build.gradle
    .ebextensions

・・・以下、省略
```

#### [1] プロジェクトの作成

(1) Eclipseを開いたら、「ファイル」（①）> 「新規」（②） > 「プロジェクト」（③）の順でクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/662f4d82-e48b-7598-7212-cbe9ad38251b.png)

(2) ウィザードが立ち上がったら、「Springスターター・プロジェクト」を選択し（①）、「次へ」（②）をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/3a7fc687-8e2b-6fb5-7244-9ef601da0536.png)

(3) 以下のキャプチャのように設定し（①）、「次へ」（②）をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/7608439b-114e-80ae-6fb1-d6eafb1ec142.png)

(4)Lombok, MySQL Driver, Spring Data JPA, Spring Webにチェックを入れ（候補に出ていない場合は検索ボックスより検索してください）、「次へ」（②）を押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/a38b3c14-6a83-4725-5422-351c72b17f42.png)

#### [2] 設定ファイルの変更・作成

次に、設定ファイルの変更と作成を行います。

(1)application.propertiesをapplication.ymlに名前変更し、以下のように記述します。
※application.propertiesのままでもいいのですが、最近の潮流（?）を踏まえてそうしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/f4d7fc47-1a2b-90ff-e61e-78d34c6aa2d3.png)

```yaml:application.yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://${DB_HOST:ホスト名}:${DB_PORT:ポート番号}/${DB_NAME:データベース名}?serverTimezone=JST
    username: ${DB_USERNAME:ユーザー名}
    password: ${DB_PASSWORD:パスワード}
  jpa:
    database: MYSQL
    hibernate.ddl-auto: none
server:
  port: ${SERVER_PORT:サーバーポート番号}
```
日本語の箇所は後で変更するので、いったんこのままで。

(2)ルートディレクトリにBuildfileを作成し、以下の通り記述。

```:Buildfile
build: ./gradlew assemble
```
(3)ルートディレクトリにProcfileを作成し、以下の通り記述。

```:Procfile
web: java -jar build/libs/sample-eb-java.jar
```

(4) build.gradleに追記します。追記前の箇所を探し、「bootJar.archiveName = "sample-eb-java.jar"」を追記してください。

```gradle:build.gradle（追記前）
configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}
```
```gradle:build.gradle（追記後）
configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
	bootJar.archiveName = "sample-eb-java.jar"
}
```

#### [3]ソースコードの作成
続いて、ソースコードの作成です。
パッケージ構成はこの章（1. Java（Spring Boot）アプリケーションの作成）の冒頭の通りですが、上まで戻るのも面倒かと思うので、キャプチャを貼っておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/3be7ba05-99ea-46a9-c49b-e062ae2bbcbf.png)


(1)まずはEntityクラス。
後から出てきますが、データベースに作成するshop_informationsテーブルに対応する内容となっています。

```java:ShopInformation.java
package jp.co.sample_eb_java.domain.model;

import java.io.Serializable;
import javax.persistence.*;

import lombok.Getter;
import lombok.Setter;


/**
 * 店舗情報テーブルに紐づくEntityクラス
 * 
 * @author CHI-3
 *
 */
@Entity
@Getter
@Setter
@Table(name="shop_informations")
@NamedQuery(name="ShopInformation.findAll", query="SELECT s FROM ShopInformation s")
public class ShopInformation implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="shop_id")
	private Integer shopId;

	private String access;

	private String address;

	@Column(name="business_hour")
	private String businessHour;

	@Column(name="regular_holiday")
	private String regularHoliday;

	@Column(name="shop_name")
	private String shopName;

	private String tel;

	@Column(name="zip_code")
	private String zipCode;

	public ShopInformation() {
	}

}
```

(2)続いて、Repositoryインターフェース。テーブル操作に使用するインターフェースです。

```java:ShopInformationRepository.java
package jp.co.sample_eb_java.domain.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import jp.co.sample_eb_java.domain.model.ShopInformation;

/**
 * 店舗情報を扱うリポジトリインターフェース
 *
 * @author CHI-3
 *
 */
@Repository
public interface ShopInformationRepository extends JpaRepository<ShopInformation, Integer>{

	/**
	 * 店舗IDに紐づく店舗情報を取得
	 *
	 * @param shopId 店舗ID
	 * @return 店舗情報
	 */
	public ShopInformation findByShopId(Integer shopId);

}
```

(3) 続いて、Serviceクラス。ロジックを提供します。

```java:ShopInformationService.java
package jp.co.sample_eb_java.domain.service;

import java.util.Optional;

import org.springframework.stereotype.Service;

import jp.co.sample_eb_java.domain.model.ShopInformation;
import jp.co.sample_eb_java.domain.repository.ShopInformationRepository;
import lombok.RequiredArgsConstructor;

/**
 * 店舗情報を取得するサービスクラス
 *
 * @author CHI-3
 *
 */
@Service
@RequiredArgsConstructor
public class ShopInformationService {

	private final ShopInformationRepository shopInformationRepository;

	/**
	 * 店舗情報を取得
	 *
	 * @param shopId 店舗ID
	 * @return 店舗情報
	 * @throws Exception
	 */
	public ShopInformation getShopInformation(Integer shopId) throws Exception{
		// 店舗情報を取得：対象店舗が存在しなければ例外をThrow
		ShopInformation shopInformation = Optional.ofNullable(shopInformationRepository.findByShopId(shopId)).orElseThrow(Exception::new);
		return shopInformation;
	}

}
```

(4)次に、Responseクラス。返却する値の成形を行います。

```java:ShopInformationResponse.java
package jp.co.sample_eb_java.app.response;

import jp.co.sample_eb_java.domain.model.ShopInformation;
import lombok.Builder;
import lombok.Getter;

/**
 * 店舗情報取得用レスポンスクラス
 * @author CHI-3
 *
 */
@Getter
@Builder
public class ShopInformationResponse {
	/** 店舗情報 */
	private ShopInformation shopInformation;
}
```

(5)お次は、Controllerクラス。JSON形式で値を返却するため、@RestControllerを使っています。

```java:ShopInformationController.java
package jp.co.sample_eb_java.app.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import jp.co.sample_eb_java.app.response.ShopInformationResponse;
import jp.co.sample_eb_java.domain.model.ShopInformation;
import jp.co.sample_eb_java.domain.service.ShopInformationService;
import lombok.RequiredArgsConstructor;

/**
 * 店舗情報を取得するAPI
 * 
 * @author CHI-3
 *
 */
@RestController
@RequiredArgsConstructor
public class ShopInformationController {

	private final ShopInformationService shopInformationService;

	/**
	 * 店舗情報を取得
	 *
	 * @param shopId 店舗ID
	 * @return レスポンスエンティティ（店舗情報）
	 * @throws Exception
	 */
	@GetMapping("/shop-information/{shopId}")
	public ResponseEntity<ShopInformationResponse> getShopInformation(@PathVariable("shopId") Integer shopId) throws Exception{
		ShopInformation shopInformation = shopInformationService.getShopInformation(shopId);
		ShopInformationResponse shopInformationResponse = ShopInformationResponse.builder().shopInformation(shopInformation).build();
		return new ResponseEntity<>(shopInformationResponse, HttpStatus.OK);
	}

}
```
(6) 最後に、ExceptionHandler。エラーハンドリングしてくれるクラスです。
今回は、存在しない店舗IDをリクエストした際などに、400エラー（Bad Request）を返却する内容になっています。

```java:ExceptionHandler.java
package jp.co.sample_eb_java.exception;


import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import lombok.extern.slf4j.Slf4j;

/**
 * 例外ハンドラー
 *
 * @author CHI-3
 *
 */
@ControllerAdvice
@Slf4j
public class ExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @org.springframework.web.bind.annotation.ExceptionHandler({Exception.class})
    public @ResponseBody
    ResponseEntity<Object> handleError(final Exception e) {
        log.info("call ExceptionHandler", e);
        return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
    }
}
```

#### [4] jarファイルの作成

(1) Elastic Beantalkの環境作成時用にjarファイルを作成します。
プロジェクトのルートディレクトリに移動し、ビルドコマンドを実行。
配置が以下のような場合は下記のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/b2553ec0-8d24-01b6-6a60-46deff651578.png)

```:実行コマンド
> cd C:\pleiades-2020-03\workspace\sample-eb-java
> gradlew build
```

<b>結果が「BUILD SUCCESSFUL」となればOK！</b>

(2) build/libs配下にjarファイルが作成されていることを確認しましょう。
（これを環境作成時に使用します。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/47661cb9-eda0-62c6-2a99-187b2bfb23a1.png)

### 2. Gitリポジトリの作成
手順1で作成したプロジェクトをGit管理できるようにします。
手順1で作成したプロジェクト上でローカルリポジトリを作成し、リモートリポジトリと同期する流れで行います。

#### [1] ローカルリポジトリの作成
(1) コマンドプロンプト、またはGitBashを開き、1で作成したプロジェクトのルートディレクトリに移動（cd）した後、リポジトリの初期化を行います（以下のコマンドを実行）。

```
> git init
```
プロジェクトのルートディレクトリに.gitフォルダができればOK。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/50b3d777-1b7a-4a15-d3ac-88a93c68dc91.png)

(2)コミットする前に、 .gitignoreの書き換えを行います。
デフォルトの記述では、STSなど、必要な機能がコミット対象外となってしまい、後々動作しないなど、不具合の原因になりかねません。
以下の通り記述を変更します。
classファイルやlockファイルなど、コミットの必要のないものが対象外となります。

```:.gitignore
bin/*
.lock
```

(3) 編集内容をコミットします。
以下のコマンドを順に実行すればOK。

```
> git add .
> git commit -m "first commit"
```

「first commit」の部分（コミットメッセージ）は任意の内容でOKですが、編集内容がわかるメッセージにしましょう(以降同様。）


(4) 再度、.gitignoreを編集します。
機能としては必要だけれども、ローカルからのコミット対象外となるものを記述。

```:.gitignore

# Created by https://www.toptal.com/developers/gitignore/api/java,gradle,eclipse
# Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,eclipse

### Eclipse ###
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders

# External tool builders
.externalToolBuilders/

# Locally stored "Eclipse launch configurations"
*.launch

# PyDev specific (Python IDE for Eclipse)
*.pydevproject

# CDT-specific (C/C++ Development Tooling)
.cproject

# CDT- autotools
.autotools

# Java annotation processor (APT)
.factorypath

# PDT-specific (PHP Development Tools)
.buildpath

# sbteclipse plugin
.target

# Tern plugin
.tern-project

# TeXlipse plugin
.texlipse

# STS (Spring Tool Suite)
.springBeans

# Code Recommenders
.recommenders/

# Annotation Processing
.apt_generated/
.apt_generated_test/

# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet

# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project

### Eclipse Patch ###
# Spring Boot Tooling
.sts4-cache/

### Java ###
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

### Gradle ###
.gradle
build/

# Ignore Gradle GUI config
gradle-app.setting

# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar

# Cache of project
.gradletasknamecache

# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties

### Gradle Patch ###
**/build/

# End of https://www.toptal.com/developers/gitignore/api/java,gradle,eclipse
```

※上記は[gitignore.io](https://www.toptal.com/developers/gitignore)で、

- Java
- Gradle
- Eclipse

を条件として作成しました。

変更が完了したら、コミットしておきましょう。

```
> git add .gitignore
> git commit -m "fix .gitignore"
```

(5) gradlewの権限変更を行います。
<b>【重要】この手順を飛ばしてしまうと、ビルド、デプロイ時にエラーになるので、忘れず実施してください。</b>

まず、gradlewの権限を確認します。

```
> git ls-files -s gradlew
100644 fbd7c515832dab7b01092e80db76e5e03fe32d29 0       gradlew
```
上記のままだと、読み取り権限しかないので、以下のコマンドを実行して、実行権限を与えます。

```
> git update-index --add --chmod=+x gradlew
```
実行が完了したら、再度権限確認を行いましょう。
以下の通り、最初の6桁の数字の下3桁が755になっていればOK。

```
> git ls-files -s gradlew
100755 fbd7c515832dab7b01092e80db76e5e03fe32d29 0       gradlew
```

変更内容をコミットします。

```
> git add gradlew
> git commit -m "fix permission of gradlew"
```

#### [2] リモートリポジトリの作成
(1) AWSマネジメントコンソールにログインし、「サービス」（①）からCodeCommitを探してクリック（②）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/91390c93-e947-0162-2475-dcbe2e2bacb1.png)

(2) CodeCommitのページに遷移したら、「リポジトリを作成」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/04dff57b-0911-91e7-4094-ecaaf14698c7.png)

(3)手順1で作成したプロジェクトと同名のリポジトリ名をつけ（①）、「作成」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/e2928caa-81b8-d8fc-8950-e3f298bf59d2.png)

#### [3] ローカルリポジトリとリモートリポジトリを同期
(1)リモートリポジトリの作成が完了すると、以下のようなページに遷移するので、「URLのクローン」（①） > 「HTTPSのクローン」（②）をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/78ae2d17-6191-7222-a5ab-59d870acbccf.png)

(2)コマンドプロンプト or GitBashを開き、プロジェクトのルートディレクトリにて、以下のコマンドを実行。

```
> git remote add origin (1)の「HTTPSのクローン」でコピーされたURI
```

(3) 内容をリモートリポジトリのmasterにプッシュ。

```
> git push -u origin master
```

(4)コンソールにて、リモートリポジトリにローカルリポジトリの内容が反映されていることを確認しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/a9a09f72-b144-14fb-ddd7-7613c25e2d9b.png)

## 続編について
続きは、

- [【CodePipeline×ElasticBeanstalk】JavaアプリケーションをCodePipelineでElasticBeanstalkにCI/CDする その2](https://qiita.com/C_HERO/items/37b931cca5892c2345ff)

にて。
3. Elastic Beanstalk環境の作成, 4. データベースの接続設定を行います。

## 更新履歴

- 2020/08/13：手順「1. Java（Spring Boot）アプリケーションの作成」に「[4] jarファイルの作成」を追記

## 参考
### 環境
- [Java SE プラットフォーム履歴 | aws](https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/platforms/platform-history-javase.html)

### 手順
#### 1. Java（Spring Boot）アプリケーションの作成

- [SpringBoot+Vue.js+ElementUI+Firebaseでマスタ管理アプリ入門 | Qiita](https://qiita.com/shunp/items/abea7fa01e7a664c85da)

#### 2. Gitリポジトリの作成
- [【 GitHub 】ローカル作成したリポジトリをリモートに「Push」するまで！| Qiita](https://qiita.com/Futo_Horio/items/4d669f695680bc13d5fa)
- [git：ファイルのgit上でのパーミッションを確認する方法備忘録 | Qiita](https://qiita.com/k-shimoji/items/a393bc2e1b79a2d965e3)
- [Git で管理しているファイルの実行権限（パーミッション）を変更する | まぐまぐGitノート](https://maku77.github.io/git/file-permission.html)