SpringBootの開発環境をdockerでつくる

#はじめに
趣味の開発でチームでSpringBootを使ってwebサービスを作ろうという話になった。javaはわかるがSpringBootの知識は0なのでdockerを使って試しにREST APIを作ってみた。
作成するにあたって、以下の記事を参考にしました。

- [Spring Boot with Docker な開発環境を考える](https://blog.tiqwab.com/2017/03/21/docker-java.html)
- [Spring Boot の Docker コンテナと MySQL の Docker コンテナの接続](https://hirooka.pro/?p=8895)

開発環境は以下の通り

- OS: macOS High Sierra
- docker: 17.12.0-ce
- docker-compose version 1.18.0

今回の記事のソースはこちら

- [Github watariGIT/boot-get-started](https://github.com/watariGIT/boot-get-started)

#作るもの
<img width="807" alt="スクリーンショット 2018-03-04 22.02.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/137213/821c4d8c-a027-4ede-f034-7e77316fab35.png">


- DBコンテナは既存のMySQLのimageを用いる
- APPコンテナはDockerFileを作成しファイルをマウントすることで、ビルドする度に新たにimageを作成する必要を無くす
- MyadminコンテナはMySQL簡易操作用

#SpringBootの構築
SpringBootわけわからんおじさんなので、準備としてローカルでRestApiを作ります
##DBの準備
mysqlのコンテナに必要なデータを入れて立ち上げとく。これをしないとテストも通らない為ビルドができない。
コンテナ起動時に必要なことは以下の通り

- 事前にsqlファイルを作り`/docker-entrypoint-initdb.d`にマウントすると起動時に実行してくれるのでマウントさせる。
- SpringBoot用のmysqlユーザ sbootを作成する
- localhostのポート3306にリンクさせる

これらを加味すると以下のようなコマンドになる
`docker container run -v $(PWD)/sql:/docker-entrypoint-initdb.d -d  -e MYSQL_DATABASE=mydb -e MYSQL_USER=sboot -e MYSQL_PASSWORD=sboot -e MYSQL_ROOT_PASSWORD=root-pass -p 3306:3306 -d mysql`

長くはなるが、docker-composeを作るまでの我慢しよう。


##build.gradleの作成
SpringBootはビルド、ライブラリ管理にgradleを使っているそうです。
[SPRING INITIALIZR](https://start.spring.io/)に必要事項を記入し雛形を落としましょう。
ディレクトリトップにあるbuild.gradleファイルを以下のように編集します。

```gradle
buildscript {
    ext {
        springBootVersion = '1.5.2.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

jar {
    baseName = 'boot-get-started'
    version = '0.0.1-SNAPSHOT'
}

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("org.springframework.boot:spring-boot-starter-data-rest")
    compile('mysql:mysql-connector-java:6.0.6')
    compileOnly('org.projectlombok:lombok')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}
```
dependenciesのとこに必要なライブラリを記入すれば良いらしい便利。
testCompileのとこで多分テストもしてくれてるうれしい。
ここらへんもあとで詳しく学びたい。

`gradlew clean build`でライブラリのインストールが完了します。

##Entity,Repositoryの作成
DBにたいおうするEnttityとRepositoryを`src/main/java/com/example`に作成する。
ここら辺は[参考記事](https://blog.tiqwab.com/2017/03/21/docker-java.html)をコピっただけなのであまり理解はしていない。
今回は環境構築がメインなので割り切る。

##application.ymlの作成
`src/main/resources/application.yml`を作成する。
ここにDBの接続先を記入する。
アプリケーションをローカルで実行する場合と、コンテナ上で接続する場合とでは接続先が変わることに注意する。

```yml
spring:
  profiles:
    active: localhost
---
spring:
  profiles: localhost
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mydb
    username: sboot
    password: sboot
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    show-sql: true
    hibernate:
      ddl-auto: update
  data:
    rest:
      base-path: /api

---
spring:
  profiles: docker
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://dbserver/mydb
    username: sboot
    password: sboot
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    show-sql: true
    hibernate:
      ddl-auto: update
  data:
    rest:
      base-path: /api
```
localhost、dockerに対応するプロファイルを作りlocalhostをデフォルトにする。
プロファイルの切り替えはDockerFile内で行う(後述)

##ビルド
mysqlコンテナを立てた状態で`gradlew build`をして、`BUILD SUCCESSFUL`が出ればOK。
ビルドがうまくいったので次はアプリケーションをコンテナ化し実行できるようにする。

#DockerFile、Composeの作成

最初にアプリケーションのDockerFileを作成する。

```dockerfile
# use alpine as base image
FROM ubuntu:16.04

RUN apt-get update
RUN apt-get -y install openjdk-8-jdk
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64

# recommended by spring boot
VOLUME /tmp

# create directory for application
RUN mkdir /app
WORKDIR /app

# jar target
ENV JAR_TARGET "boot-get-started-0.0.1-SNAPSHOT.jar"

# set entrypoint to execute spring boot application
ENTRYPOINT ["sh","-c","java -jar -Dspring.profiles.active=docker build/libs/${JAR_TARGET}"]
```
- `ENTRYPOINT`の`-Dspring.profiles.active=docker`で`application.yml`に記載したプロファイルに切り替える。
- 実行時にビルドしたファイルを`/app`にマウントする。
- `JAR_TARGET`に指定したファイルを実行する。(デフォルトでは"boot-get-started-0.0.1-SNAPSHOT.jar")

次にdb,app,phpmyadminコンテナを組み合わせたdocker-compose.ymlを記載する

```yml
version: '2'
services:

    dbserver:
        image: mysql
        volumes:
            - ./sql:/docker-entrypoint-initdb.d
            - mysql-db:/var/lib/mysql
        environment:
            MYSQL_DATABASE: mydb
            MYSQL_USER: sboot
            MYSQL_PASSWORD: sboot
            MYSQL_ROOT_PASSWORD: root
           
    app:
        build: .
        image: watari/boot:0.1.0                            
        depends_on:
            - dbserver
        ports:
            - "8080:8080"
        volumes:
            - .:/app
        environment:
            JAR_TARGET: boot-get-started-0.0.1-SNAPSHOT.jar

    myadmin:
        image: phpmyadmin/phpmyadmin
        depends_on:
            - dbserver
        environment:
            PMA_ARBITRARY: 1
            PMA_HOST: dbserver
            PMA_USER: root
            PMA_PASSWORD: root
        ports:
            - "1111:80"

volumes:
    mysql-db:
        driver: local
```

#実行
`docker-compose up`で実行できる。初回はDockerファイルからイメージを作成する必要があるので`--build`オプションをつけると良い。
コンテナを立ち上げ実行ができたら　`curl`コマンドで確認する。

```bash
$ curl http://localhost:8080/api/users
{
  "_embedded" : {
    "users" : [ {
      "firstName" : "Taro",
      "lastName" : "Yamada",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/users/1"
        },
        "user" : {
          "href" : "http://localhost:8080/api/users/1"
        }
      }
    }, {
      "firstName" : "Hanako",
      "lastName" : "Tanaka",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/api/users/2"
        },
        "user" : {
          "href" : "http://localhost:8080/api/users/2"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/api/users"
    },
    "profile" : {
      "href" : "http://localhost:8080/api/profile/users"
    }
  }
}
```

#おわりに
SpringBootの知識はないまんだがコンテナを使うことで簡単に環境構築することができた。
けど、buildをローカルでやってるのでなんだかなという感じ、buildもコンテナにやらせた方が良いのでは？とも考えている。
それと今はemacsベースで開発してるけど、今後eclipseなどの統合開発環境で開発するときにはうまく組み合わせる必要があるなってかんじです(ここらへんのノウハウもない)。
これをたたき台に試行錯誤していこうかなと思います。