devContainerでNext(React)＋SpringBoot(Java)の開発環境を作成する BackEnd編


Next＋springBootの開発環境構築2部作のBackEnd編になります。
FrontEndについては[こちら](https://qiita.com/ede/items/e1259f005af47be9ffe2)。

## 構成
- SDKMAN
- SpringBoot
- gradle
- Java
- mySQL

# 手順

## DevContainerを作成
- 1.VScodeを起動する
適当なフォルダーを作成して、そのフォルダー配下でVSCodeを起動する。
```
~$ mkdir sample-project-backend
~$ cd sample-project-backend

sample-project-backend$ code .
```
- 2.VSCodeのコントロールパネル(`ctrl + shift + P`)から、`「Dev containers:Add Dev Containers Configuration Files...」`を選択す
- 3.`「Java」`を選択
- 4.「Java」のバージョンを選択(17)。
- 5.CLI等を入れるかと尋ねられるので、何も選択せずに作成をする。以下の感じになればOK
```
sample-project-backend(root)
  └─ .devcontainer
      └─ devcontainer.json
```
※ほんとなら動作確認したほうがいいでしょうけど、公式から取ってきただけなので、大丈夫でしょう（笑）

## DevContainerをカスタマイズ
`devContainer`の起動を`DockerFile`を使用して行いたいので、修正・作成をしていきます。
- 1.`devcontainer.json`を修正
```devcontainer.json
{
	"name": "java-backend",
	"dockerComposeFile" : "./compose.yml",
	"service": "java-backend",
 	"shutdownAction": "stopCompose",
	"workspaceFolder": "/work/backend",

	"customizations": {
		"vscode": {
			"settings": {
				"editor.formatOnSave": true
			},
			"extensions": [
				"mhutchie.git-graph",
			]
		}
	},
    "otherPortsAttributes": {
		"onAutoForward": "ignore"
	},
	"remoteUser": "vscode"
}
```
- 2.`compose.yml`を作成する。
`devcontainer.json`から`compose.yml`を呼ぶようにしたいので、compose.ymlを作成する。
```.devcontainer/compose.yml
services:
  java-backend:
    container_name: java-backend
    build: ./docker/java
    volumes:
      - ..:/work/backend:cached
    command: sleep infinity
```
- 3.以下のDockerFileを作成する。
ここでは、SDKMANをインストールするためのパッケージと、Gitを追加している。
```.devcontainer/docker/java/Dockerfile
FROM mcr.microsoft.com/vscode/devcontainers/java:17-jdk-bullseye

RUN apt-get update && apt-get -y install git zip unzip curl && \
curl -s "https://get.sdkman.io" | bash

WORKDIR /work/backend
```

- 4.起動して確かめる。 
コントロールパネル(`ctrl + shift + P`)から`Dev Containers: Rebuild and Reoprn in container`を選択する。起動後に`SDKMAN`のバージョンを確認し下記のようになっていればOK
```
sdk version 

SDKMAN!
script: 5.18.2
native: 0.4.3
```

- 5.ファイル構成は以下の通り
```
sample-project-backend(root)
  └─ .devcontainer
      ├─ docker
      │   └─ java
      │       └─ Dockerfile
      ├─ compose.yml
      └─ devcontainer.json
```

## springプロジェクトの作成。
- 1.コンテナを起動している状態で、`Spring CLI`および`gradle`をインストールしていきます。
```
sdk install springboot 3.1.5
sdk install gradle 8.4
```

- 2.`Spring CLI`と`gradle`の確認をして、下記のようになっていることを確認。
```
$ spring --version

Spring CLI v3.1.5

$ gradle --version

------------------------------------------------------------
Gradle 8.4
------------------------------------------------------------
```

- 3.プロジェクトを作成します。
下記のコマンドを使用してプロジェクトの作成を行います。↓軽く説明
  - gradleの依存関係に`spring-boot-starter-web`を注入
  - プロジェクトはgradle(java)を使用
  - Javaのバージョンを指定(17)
  - プロジェクト名を`sample-app`とする。
```
$ spring init -d=web --type=gradle-project -j=21.0.1 -n=sample-app sample-app 
Using service at https://start.spring.io
Project extracted to '/work/backend/sample-app'

$ mv sample-app/* ./. 
```
若干変更していますが、`build.gradle`は下記のとおりです。
```build.gradle
plugins {
	id 'java'
	id 'org.springframework.boot' version '3.1.5'
	id 'io.spring.dependency-management' version '1.1.3'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	toolchain{
		languageVersion = JavaLanguageVersion.of(17)
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('bootBuildImage') {
	builder = 'paketobuildpacks/builder-jammy-base:latest'
}

tasks.named('test') {
	useJUnitPlatform()
}
```

- 4.ひとまず動作確認
下記のコマンドを使用してとりあえず、プロジェクトを動かしてみましょう。
```
$ gradle bootRun
```
プロジェクトに何もないので、`http://localhost:8080/`で確認を行うと下記のような画面になると思います。

![backend_SpringInit_デフォルト.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3615210/94c8c663-204a-a20c-7ed9-d6baafec4825.png)

- 5.ここからおなじみの`Hello World`を表示するように修正します。

  - `VsCode`にて`ctrl ＋ C`を押下して動作中のgradleを止める
  - `src/main/java/com/example/sampleapp/SampleAppApplication` 下記変更を適用します。

```diff_java:src/main/java/com/example/sampleapp/SampleAppApplication.java
package com.example.sampleapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+ import org.springframework.web.bind.annotation.RequestMapping;
+ import org.springframework.web.bind.annotation.RestController;

+ @RestController
@SpringBootApplication
public class SampleAppApplication {

+	@RequestMapping
+	String SayHello(){
+		return "Hello World";
+	}
	public static void main(String[] args) {
		SpringApplication.run(SampleAppApplication.class, args);
	}

}
```

- 6.再度動作確認
`http://localhost:8080/`で`Hello World`が表示されればOK!!!
この時点のディレクトリは以下の通り
```
sample-project-backend(root)
  ├─ .devcontainer
  │   ├─ docker
  │   │   └─ java
  │   │       └─ Dockerfile
  │   ├─ compose.yml
  │   └─ devcontainer.json
  │
  ├─ src
  │   ├─ main
  │   │   ├─ resources
  │   │   │   ├─ static
  │   │   │   ├─ templates
  │   │   │   └─ application.properties
  │   │   └─ java
  │   │       └─ com   
  │   │           └─ example  
  │   │               └─ sampleapp
  │   │                   └─ SampleAppApplication.java
  │   └─ test
  │   │   └─ java
  │   │      └─ com   
  │   │          └─ example  
  │   │              └─ sampleapp
  │   │                  └─ SampleAppApplication.java
  │
  ├─ gradle
  │   ├─ gradle-wrapper.jar
  │   └─ gradle-wrapper.properties
  │
  ├─ .gradle/* # 長いので中身割愛
  │   
  ├─ build/*   # 長いので中身割愛 
  ├─ .gitignore
  ├─ build.gradle
  ├─ gradlew
  ├─ gradlew.bat
  ├─ README.md
  └─ setting.gradle
```

## mySQLの構築
- 1.mySQL用のコンテナの追加
データを保管するためのデータベースとして今回はmySQLを使用する。
下記のように`compose.yml`を追加して、mySQL用のコンテナを立ち上げる。

```diff_yaml:.devcontainer/compose.yml
services:
  java-backend:
    container_name: java-backend
    build: ./docker/java
    volumes:
      - ..:/work/backend:cached
    command: sleep infinity
+    depends_on:
+      - mysql
+  
+  mysql:
+    container_name: java-backend-mysql
+    build: ./docker/mysql
+    env_file:
+      - ./docker/mysql/.env
+    ports:
+      - 3306:3306
+    volumes:
+      - db-store:/var/lib/mysql
+
+volumes: 
+  spring-store:
+  spring-logs:
```

- 2.ビルドファイルを追加する。
`compose.yml`に直接記載してもよいですが、カスタマイズしやすいように`Dockerfile`および`.env`を追加する。
※そもそもフォルダーがないと思うので、フォルダーも併せて作成をお願いします。
```.devcontainer/docker/mysql/Dockerfile
FROM mysql:8.0.30
```
```.devcontainer/docker/mysql/.env
TZ=Asia/Tokyo

MYSQL_DATABASE=spring_db
MYSQL_USER=spring
MYSQL_PASSWORD=password
MYSQL_ROOT_PASSWORD=password
```

- 3.疎通確認
疎通確認をするために、`mysql-client`を入れて動作確認する。
メインのコンテナ用`Dockerfile`を少しカスタマイズします。

```diff_docker:.devcontainer/docker/java/Dockerfile
FROM mcr.microsoft.com/vscode/devcontainers/java:1-21-bullseye

- RUN apt-get update && apt-get -y install git zip unzip curl && \
+ RUN apt-get update && apt-get -y install git zip unzip curl default-mysql-client  && \
curl -s "https://get.sdkman.io" | bash

WORKDIR /work/backend
```
再度、コンテナをビルドして立ち上げた後に、コマンドを使用して下記のような表記が出れば成功です。

```
$ mysql -h java-backend-mysql -u spring -ppassword
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.30 MySQL Community Server - GPL

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
```

## その他のカスタマイズ
　開発環境の構築自体は上記までで終了ですが、開発をしていく上でいろいろ便利だったりするので、少し調整をした内容を記載していきます。

### `debug`機能を追加
　VSCodeを使用している場合左サイドバーから`Run and Debug(ctrl + shift + D)`を選択し、初回であれば、`create a launch.json file`を選択することで下記のjava用のjsonファイルが生成され、`debug`が使用可能になります。
```.vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "java",
            "name": "Current File",
            "request": "launch",
            "mainClass": "${file}"
        },
        {
            "type": "java",
            "name": "SampleAppApplication",
            "request": "launch",
            "mainClass": "com.example.sampleapp.SampleAppApplication",
            "projectName": "backend"
        }
    ]
}
```
### spring/gradleのインストールを自動化
　場合によってコンテナが動作せず、コンテナを潰して立ち上げ直すことも多々あります。その都度`SpringBoot`と`Gradle`をターミナルで入れなおすのは面倒だと思います。あくまで一例ですが、コンテナ起動時にシェルスクリプトを実行する方法で解決できます。
実行ファイル`createCommand.sh`を作成して、`devcontainer.json`で呼び出しましょう。
```.devcontainer/createCommand.sh
#!/bin/bash

# 一時的にSDKのパスを通す
source "/usr/local/sdkman/bin/sdkman-init.sh"

# spring/gradleのインストール
sdk install springboot 3.1.5
sdk install gradle 8.4
```
```diff_json:.devcontainer/devcontainer.json
+	"postCreateCommand": "bash .devcontainer/createCommand.sh"
```

### UTC標準時から日本時間に変更する
　シンプルですが、下記のようにtimezoneの設定を入れる。
```diff_docker:.devcontainer/dokcer/java/Dockerfile
+ ENV TZ=Asia/Tokyo
```

### 拡張機能の追加
　VSCodeでは様々な拡張機能を選択することができます。
　左サイドバーから`EXTENSIONS(ctrl + shift + X)`を選択すると拡張機能の一覧が確認でき、そのままインストールを選択することも可能です。また、歯車マークから`Copy Extension ID`を選択し、`devcontainer.json`に追加することでコンテナ起動時にインストールしてくれます。
サンプルとして以下に一例を示します。
```.devcontainer/devcontainer.json
	"customizations": {
		"vscode": {
			"extensions": [
				"mhutchie.git-graph",
				"vscjava.vscode-gradle",
				"mikestead.dotenv",
				"cweijan.vscode-mysql-client2"
			]
		}
	}
```


## 参考文献
- [Spring ドキュメント 入門セクション](https://spring.pleiades.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started)