My Note Pad

エンジニアリングや日々の雑感を書いていきます

SpringBootをKubernetes上で動かしてみる

これは2018年02月26日にQiitaに投稿した記事を移行したものです

about

SoftwareDesign 2018/3号を読んでいてKubernetesに少し興味を持ったので試してみました。 色々ハマりどころがあるかと思いましたが、環境構築〜デプロイ、スケールまで割りとすんなりと進めることができました。

環境

インストール

VirtualBox

$ brew cask install virtualbox

Hish Sierraだとインストール中に止まってしまうことがあります。 その場合はシステム環境設定 > セキュリティとプライバシーOracleがブロックされたみたいなものが表示されているので「許可」するとインストールできます。 (スクリーンショット撮り忘れ)

minikube

$ brew cask install minikube

動作確認

minikubeの起動

$ minikube start
Starting local Kubernetes v1.8.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
Loading cached images from config file.

VirtualBox上でminikubeが作成されて起動していることが確認できます

スクリーンショット 2018-02-24 17.28.48.png

デプロイメントの作成

# deployment
$ kubectl run hello-minikube --image=k8s.gcr.io/echoserver:1.4 --port=8080
deployment "hello-minikube" created

$ kubectl expose deployment hello-minikube --type=NodePort
service "hello-minikube" exposed

$ kubectl get pod
NAME                              READY     STATUS    RESTARTS   AGE
hello-minikube-7844bdb9c6-88hl4   1/1       Running   0          2m

この状態でダッシュボードを確認してみると

# ダッシュボードの起動
$ minikube dashboard

デプロイメントとポッドが作成される。 スクリーンショット 2018-02-24 17.35.13.png

# URLの確認
$ minikube service hello-minikube --url

$ curl $(minikube service hello-minikube --url)
CLIENT VALUES:
client_address=172.17.0.1
command=GET
real path=/
・
・
・
# サービスの削除
$ kubectl delete service hello-minikube
service "hello-minikube" deleted

# デプロイメントの削除
$ kubectl delete deployment hello-minikube
deployment "hello-minikube" deleted

# minikube終了
$ minikube stop
Stopping local Kubernetes cluster...
Machine stopped.

Spring BootのDocker環境を用意する

今回は公式チュートリアルの完成版を利用します。 https://spring.io/guides/gs/spring-boot-docker/

スターターをclone

$ git clone https://github.com/spring-guides/gs-spring-boot-docker.git
# 完成版を使う
$ cd gs-spring-boot-docker/complete
$ ./gradlew build docker
・
・
・
:processTestResources NO-SOURCE
:testClasses
:test
:check
:build
:dockerClean UP-TO-DATE
:dockerPrepare
:docker

BUILD SUCCESSFUL

Total time: 1 mins 37.278 secs

動かしてみる

$ docker images
REPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
springio/gs-spring-boot-docker   latest              d486fdb38288        4 minutes ago       116MB

$ docker run -p 8080:8080 -t springio/gs-spring-boot-docker

docker上でSpringBootが起動しました。

スクリーンショット 2018-02-24 21.11.02.png

kubernetes上で動かす

環境変数の設定

$ minikube start
$ eval $(minikube docker-env)
# この状態では一時的にローカルPCからdockerへの接続などができなくなりますが、ターミナルを再起動すれば設定が消えて元にもどります。

サービスの設定ファイルを以下の内容で作成する。

apiVersion: v1
kind: Service
metadata:
  name: hellojavakubernetes
  labels:
    app: hellojavakubernetes
    tier: backend
spec:
  type: NodePort
  ports:
    # the port that this service should serve on
  - port: 8080
  selector:
    app: hellojavakubernetes
    tier: backend

サービスの登録

$ kubectl create -f kubernetes-service.yaml

hellojavakubernetesサービスが登録されました。

スクリーンショット 2018-02-24 21.43.07.png

デプロイメント設定ファイルを以下の内容で作成する。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: hellojavakubernetes
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: hellojavakubernetes
        tier: backend
    spec:
      containers:
      - name: hellojavakubernetes
        image: springio/gs-spring-boot-docker
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 8080

デプロイしてみる。

$ kubectl create -f kubernetes-deployment.yaml
deployment "hellojavakubernetes" created

$ kubectl get deployment
NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hellojavakubernetes   1         1         1            1           3m

hellojavakubernetesというデプロイメントが作成され、ポッドも作成されています。

スクリーンショット 2018-02-24 21.54.37.png

URLを調べる。

$ minikube service hellojavakubernetes --url
http://192.168.99.100:31830

表示されたURLにアクセスする スクリーンショット 2018-02-24 22.04.06.png

正しく表示されました。 ここまで詰まることなく進めることができました。

アプリケーションのスケーリング

せっかくなのでスケーリングしてみます。

$ kubectl get deployments
NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hellojavakubernetes   1         1         1            1           17h

# replicasを指定する
$ kubectl scale --replicas=2 -f kubernetes-deployment.yaml 
deployment "hellojavakubernetes" scaled

# deploymentsの数が2に増えました。
$ kubectl get deployments
NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hellojavakubernetes   2         2         2            2           17h

ポッドの数が2つに増えていることがわかります。

スクリーンショット 2018-02-25 15.48.27.png

続いて設定ファイルから変更してみます。 上の方で作成したkubernetes-deployment.yamlreplicas3に変更します。

# 設定の適用
$ kubectl apply -f kubernetes-deployment.yaml 
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
deployment "hellojavakubernetes" configured

$ kubectl get deployments
NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hellojavakubernetes   3         3         3            3           17h

こちらも無事スケールできました。

後始末

終了する

$ kubectl delete service,deployment hellojavakubernetes
$ minikube stop
$ minikube delete

本番で運用するにはまだまだ調べることが多そうですがKubernetes自体はかなり完成されているなという印象でした。

参考ページ

[minikube] https://github.com/kubernetes/minikube

[Spring Boot with Docker] https://spring.io/guides/gs/spring-boot-docker/

[getting started] https://www.bluefyre.io/getting-started-springboot-kubernetes/

Spring Boot (Kotlin) はじめの一歩

これは2018年01月30日にQiitaに投稿した記事を移行したものです

目的

Spring Bootを学習するきっかけが欲しいと友人にたのまれたので、 実際にペアプロしながら進めていくための資料として作成しました。

Spring Boot (Kotlin)入門 - MVC - Rest - DB Access

使用するもの

  • JDK 8
  • IntelliJ Idea
  • Kotlin
  • Gradle
  • Spring Boot
  • Doma2 (JPAのほうが最初の設定は楽だった・・)

IntelliJ Ideaの代わりにSTS + Kotlin PluginでもOKですが、手順は記載していません。 ※Mavenの場合、dependencyなどをpom用に読み替えてください。 ※Doma2のEntity / Dao実装はKotlinではなくJavaになっています。

環境構築

IntelliJ Ideaのインストール

こちらから https://www.jetbrains.com/idea/

Spring Bootプロジェクトの雛形を作成する

Spring Initializrで雛形を作成する https://start.spring.io/

以下の内容で生成します。

  • Gradle Project
  • Kotlin
  • Spring Boot 1.5.9
  • Dependencies => Web

SpringInitializer.PNG

Group / Artifactはお好みで

雛形のzipファイルがダウンロードされるので、適当な場所に展開しておきます。

プロジェクトを開く

IntelliJを起動してImport Project

Idea.PNG

展開したプロジェクトの雛形の中にあるbuild.gradleを指定する。

import.PNG

Use auto-importにチェックを入れてOK

use_auto_import.PNG

ビルドが始まるので、synced successfullyになればOK

build.PNG

View -> Tool Window -> Gradleを選択してGradleのツールウィンドウを開く。 Gradleツールウィンドウ内のdemo -> Tasks -> application -> bootRun を右クリックして Run 'demo[boot Run]'で起動する

bootRun.PNG

14:44:18: Executing task 'bootRun'...

:compileKotlin
:compileJava NO-SOURCE
:copyMainKotlinClasses
:processResources
:classes
:findMainClass
:bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.9.RELEASE)

・
・
・

s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-01-20 14:44:28.125  INFO 13432 --- [           main] com.example.demo.DemoApplicationKt       : Started DemoApplicationKt in 2.45 seconds (JVM running for 2.75)

※この時点ではControllerなどが無いので、何もできない。

Spring Boot起動中にIntelliJでコード修正が反映されるようにbuild.gradleの設定

plugin定義に以下を追加する

 apply plugin: 'idea'
 idea {
   module {
     outputDir file('build/classes/main')
     testOutputDir file('build/classes/test')
   }
 }
 if(project.convention.findPlugin(JavaPluginConvention)) {
    // Change the output directory for the main and test source sets back to the old path
    sourceSets.main.output.classesDir = new File(buildDir, "classes/main")
    sourceSets.test.output.classesDir = new File(buildDir, "classes/test")
 }

一番下のdependenciesに以下を追加する

    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.springframework.boot:spring-boot-devtools")
  • thymeleaf => テンプレートエンジンのthymeleafを使う
  • spring-boot-devtools => オートリロードを有効にする

オートリロードの設定

Setting -> Build -> Compilerを開き、Build project automaticallyにチェックを入れてApply

buildProjectAutomatically.PNG

Windowsの場合:Sthif + Ctrl -> A Macの場合:Command + Ctrl + A でウィンドウを開き、Registry..で検索

開いたウィンドウでcompiler.automake.allow.when.app.runningにチェックを入れる

compiler.PNG

ThymeleafでHTMLを返す

Controllerの作成

com.example.demoの下にcontrollerパッケージを作成する。 controllerパッケージの下に、GreetingController.ktを作成する。

package com.example.demo.controller

import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam

@Controller
@RequestMapping("greeting")
class GreetingController {

    @GetMapping("/hello")
    fun hello(
            @RequestParam(value = "name", required = false, defaultValue = "world") name: String,
            model: Model): String {
        model.addAttribute("name", name)
        return "greeting"
    }
}

html(Thymeleaf)の作成

src/main/resources/templatesの下にgreeting.htmlを作成する

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" http-equiv="Content-Type" content="text/html" />
    <title>Getting Started: Saving Web Content</title>
</head>
<body>
    <p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>

動作確認

再度Spring Bootを実行する

Webブラウザhttp://localhost:8080/greeting/helloにアクセス

helloworld.PNG

http://localhost:8080/greeting/hello?name=hogeにしてみると、 表示がHello, Hoge!に変わる。

DB環境準備

Virtual Box / Vagrantのインストール

※Windows10 Homeで作業したためDocker for Windowsが使えないので仕方なくVirtual Box / Vagrantを使っています。 Windows10 ProやMacの方はDockerで構築する方が楽なので、VirtualBox / Vagrantの章は飛ばしてdocker-composeからはじめてOKです

Virtual Box https://www.virtualbox.org/

Vagrant https://www.vagrantup.com/downloads.html

プラグインのインストール

$ vagrant plugin install vagrant-vbguest

CentOS7のBoxをインストール・起動

cd [任意のディレクトリ]
vagrant init centos/7

Vagrantfileが生成されるので、ポートフォワード・IPアドレスバインディングを設定する

# 以下の設定を追加する
  config.vm.network "forwarded_port", guest: 3306, host: 3306
  config.vm.network "private_network", ip: "192.168.33.10"
vagrant up

# 終わったら↓でrunningになっていることを確認
vagrant status

# vagrantに接続する
vagrant ssh

Vagrant上のCentOS7にDockerをインストール

https://docs.docker.com/engine/installation/linux/docker-ce/centos/

# インストール確認
docker -v
# docker起動
sudo systemctl start docker
# 動作確認
sudo docker run hello-world
# vagrant起動時にdockerが起動するように
sudo systemctl enable docker

一般ユーザーでもdockerコマンドを使えるようにする

sudo gpasswd -a $USER docker
sudo systemctl restart docker

# 一度再ログインする
exit

続いてdocker-composeをインストールする ※Docker for Mac (Windows)の場合、同時にインストールされるのでスキップしてOK

https://docs.docker.com/compose/install/#install-compose

$ sudo curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

$ sudo chmod +x /usr/local/bin/docker-compose

$ docker-compose --version

docker-compose.ymlの作成

db:
  image: mysql:5.7
  ports:
    - "3306:3306"
  environment:
    MYSQL_ROOT_PASSWORD: root
  volumes_from:
    - data

data:
  image: busybox:1
  volumes:
    - /var/lib/mysql:/var/lib/mysql # macの場合、適当なディレクトリを指定する

イメージの作成

$ docker-compose up -d
$ docker-compose ps
[vagrant@localhost ~]$ docker-compose ps
     Name                  Command             State            Ports
------------------------------------------------------------------------------
vagrant_data_1   sh                            Exit 0
vagrant_db_1     docker-entrypoint.sh mysqld   Up       0.0.0.0:3306->3306/tcp

mysqlへの接続確認

$ docker exec -it vagrant_db_1 bash
# mysql -uroot -proot

# 適当にデータベースを作成しておく
mysql> create database test;

ホストマシン(Windows / Mac)からMySQLに接続してみる。

設定方法はクライアント次第だけどこんな感じでつながるはず ※Vagrantを使わない場合はHostは172.0.0.1

項目 設定値
Host 192.168.33.10
Database test
User root
Password root

適当にテーブルを作っておく

CREATE TABLE anything(
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(255),
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

プロジェクトにDoma2を設定する

# repositoriesに↓を追加
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }

# compileKotlinの前に↓を追加
processResources.destinationDir = compileJava.destinationDir
compileJava.dependsOn processResources

# dependenciesに↓を追加
    compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6'
    compile('org.seasar.doma.boot:doma-spring-boot-starter:1.0.2')

application.propertiesの設定

# JDBC
spring.datasource.url=jdbc:mysql://192.168.33.10:3306/test
# vagrantを使わない場合はこちら
# spring.datasource.url=jdbc:mysql://172.0.0.1:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

# DOMA
doma.dialect=mysql
doma.naming=snake_lower_case

DBアクセスの実装

src/main配下にjavaディレクトリを作成 src/main/java 配下に com.example.demo.entity, com.example.demo.daoパッケージを作成する。

以下のクラスを作成していく

package com.example.demo.entity;

import org.seasar.doma.Entity;
import org.seasar.doma.GeneratedValue;
import org.seasar.doma.GenerationType;
import org.seasar.doma.Id;
import org.seasar.doma.Table;

import java.sql.Timestamp;

@Entity
@Table(name = "anything")
public class AnythingEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Integer id;

    public String name;

    public Timestamp createdAt;

    public Timestamp updatedAt;
}
package com.example.demo.dao;

import com.example.demo.entity.AnythingEntity;
import org.seasar.doma.Dao;
import org.seasar.doma.Insert;
import org.seasar.doma.Select;
import org.seasar.doma.boot.ConfigAutowireable;

import java.util.List;

@ConfigAutowireable
@Dao
public interface AnythingDao {

    @Select
    List<AnythingEntity> selectAll();

    @Insert
    int insert(AnythingEntity anything);
}

===ここからkotlin配下===

com.example.demo.serviceパッケージを作成する

以下のクラスを作成する

package com.example.demo.service

import com.example.demo.dao.AnythingDao
import com.example.demo.entity.AnythingEntity
import org.springframework.stereotype.Service

@Service
class AnythingService(
        val anythingDao: AnythingDao
) {

    fun findAll(): List<AnythingEntity> {
        return this.anythingDao.selectAll()
    }

    fun insert(anything: AnythingEntity): Int {
        return this.anythingDao.insert(anything)
    }
}
package com.example.demo.controller

import com.example.demo.entity.AnythingEntity
import com.example.demo.service.AnythingService
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("anything")
class AnythingController(
        val anythingService: AnythingService
) {

    @GetMapping("/findAll")
    fun findAll(): List<AnythingEntity> {
        return this.anythingService.findAll()
    }

    // 本当はGetにするべきではないですが、面倒なので・・・
    @GetMapping("/insert")
    fun insert(@RequestParam(value = "name", required = false, defaultValue = "doma") name: String): String {

        val entity = AnythingEntity()
        entity.name = name
        this.anythingService.insert(entity)

        return "success"
    }
}

sqlファイルを作成する

SELECT
  *
FROM
  anything

動作確認

curlかブラウザで以下を実行 http://localhost:8080/anything/insert http://localhost:8080/anything/insert?name=fuga http://localhost:8080/anything/findAll

以下のような結果が帰ってくればOK ※上の2行でDBへのインサート、最後の行でインサートした結果を取得しています。

[{"id":1,"name":"doma","createdAt":1516459862000,"updatedAt":1516459862000},{"id":2,"name":"fuga","createdAt":1516459895000,"updatedAt":1516459895000}]

まとめ

今回はSpring Bootで開発を始める第一歩を書きました。 Doma2を使ったのは自分が触ってみたかったからです。 余裕があったら続編を書くかもしれません。

最終的なコードはこちら

https://github.com/Yuki10Kobayashi/SpringBootDoma2Demo

Javaで書かれたSpring BootプロジェクトをKotlin対応した話

これは2017年12月18日にQiitaに投稿した記事を移行したものです。

背景

某社のエンジニアチームで使用技術のモダナイズを進めており、「Java / SpringBootで書かれたシステムをどうせならKotlinで書きたいよね」という話が出てきたので、Kotlinで開発できるように対応を進めています。

最初からKotlinで始める(Gradle)といった記事はよく見かけますが、Javaで書かれている途中からKotlinに対応する(Maven)という記事があまり見つからなかったので、手探りで対応していきました。

既存の環境

基本的にはSpringBootではREST APIのみを実装しています。

Kotlin対応させる方針

  • 並行でJavaでの開発が進んでいるため、全コードをJavaからKotlinに変換するのは今回は見送り
  • Controller / ServiceはKotlinで書けるようにする
  • JPAなどのデータアクセス層はJavaのまま残しておく
  • 最終的には全部Kotlinにしたい

また、今回の作業はIntelliJ上で行いました。

移行作業

pom.xmlの設定

※今回の投稿に関係ある部分を中心に記載しています。

  • Kotlinバージョンの定義
  <properties>
...
     <java.version>1.8</java.version>
+    <kotlin.version>1.2.10</kotlin.version>
+    <kotlin.compiler.incremental>true</kotlin.compiler.incremental>
  </properties>
  • Kotlinの依存ライブラリを追加
  <dependencies>
・・・
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-stdlib</artifactId>
+      <version>${kotlin.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-reflect</artifactId>
+      <version>${kotlin.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-test</artifactId>
+      <version>${kotlin.version}</version>
+      <scope>test</scope>
+    </dependency>
  </dependencies>
  • ビルド設定

元々maven-compiler-pluginは使用していませんでしたが、JavaとKotlinのどちらでも開発ができるように、パッケージをそれぞれ

  • src/main/java
  • src/main/kotlin
  • scr/test/java
  • src/test/kotlin

で分けため、maven-compiler-pluginを使用しています。

また、Springで使用する各種アノテーションを使用できるようにするため、compilerPluginsとしてspringを定義しています。 ドキュメントにも記載されていますが、springプラグインを定義するとall-openの定義は不要になるようです。

  <build>
     <finalName>${project.name}</finalName>
-    <sourceDirectory>src/main/java</sourceDirectory>
-    <testSourceDirectory>src/test/java</testSourceDirectory>
    <resources>
      <resource>
        <directory>${resources.directory}</directory>
      </resource>
      <resource>
        <directory>src/main/resources</directory>
      </resource>
     </resources>
     <testResources>
       <testResource>
         <directory>src/test/resources</directory>
       </testResource>
     </testResources>
     <plugins>
       <plugin>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-maven-plugin</artifactId>
         <configuration>
           <executable>true</executable>
         </configuration>
       </plugin>
+      <plugin>
+        <artifactId>kotlin-maven-plugin</artifactId>
+        <groupId>org.jetbrains.kotlin</groupId>
+        <version>${kotlin.version}</version>
+        <configuration>
+          <compilerPlugins>
+            <plugin>spring</plugin>
+          </compilerPlugins>
+        </configuration>
+        <executions>
+          <execution>
+            <id>compile</id>
+            <goals> <goal>compile</goal> </goals>
+            <configuration>
+              <sourceDirs>
+                <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
+                <sourceDir>${project.basedir}/src/main/java</sourceDir>
+              </sourceDirs>
+            </configuration>
+          </execution>
+          <execution>
+            <id>test-compile</id>
+            <goals> <goal>test-compile</goal> </goals>
+            <configuration>
+              <sourceDirs>
+                <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
+                <sourceDir>${project.basedir}/src/test/java</sourceDir>
+              </sourceDirs>
+            </configuration>
+          </execution>
+        </executions>
+        <dependencies>
+          <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-maven-allopen</artifactId>
+            <version>${kotlin.version}</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.5.1</version>
+        <executions>
+          <!-- Replacing default-compile as it is treated specially by maven -->
+          <execution>
+            <id>default-compile</id>
+            <phase>none</phase>
+          </execution>
+          <!-- Replacing default-testCompile as it is treated specially by maven -->
+          <execution>
+            <id>default-testCompile</id>
+            <phase>none</phase>
+          </execution>
+          <execution>
+            <id>java-compile</id>
+            <phase>compile</phase>
+            <goals> <goal>compile</goal> </goals>
+          </execution>
+          <execution>
+            <id>java-test-compile</id>
+            <phase>test-compile</phase>
+            <goals> <goal>testCompile</goal> </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
  </build>

これでIntelliJ上でKotlinを書く準備が整いました。

STS(Eclipse)の対応

ここまでの対応で普段STSを使用しているメンバーに確認してもらったところ、うまく動作せずに少しハマったのでSTSでの対応方法も記載します。

  • EclipseマーケットプレイスからKotlin Plugin for Eclipseをインストールします。

  • src/main/kotlinがビルドパスとして認識されていないので、Projects > Properties > Java Build PathのSourceからAdd Fileでkotlinのフォルダをチェック。 src/test/kotlinも同様

buildPath.png

  • プロジェクトを右クリックし、Configure Kotlin > Add Kotlin Natureを実行

下図では、既にAdd Kotlin Nature済みのため選択できなくなっていますが、実行前であれば選択できます。

AddKotlinNature.png

これでSTS上でもコンパイルが通り、Kotlinでの開発が行えるようになりました。

Kotlinを書く

JavaコードからKotlinコードへコンバート

IntelliJのコンバート機能が優秀なので、基本的にはIntelliJでコンバートしました。 一気にまとめては怖いので、1コードもしくは数コードづつ変換しました。

変換したいコードを選択して、Code > Convert Java File to Kotlin FileでKotlinのコードにコンバートします。

ConvertToKotlin.png

※たまにおかしな変換をするので都度手で修正します。

また、パッケージがsrc/main/java配下のままであるため、src/main/kotlin配下の同パッケージ以下に移動します。 これもIntelliJRefactor > Move...でいい感じにリファクタリングしてくれます。

DI(Autowiredなど)

たとえば以下のコードは

@Service
public class HogeService {
    @Autowired
    private HogeRepository hogeRepository;
}

Kotlinではlateinitでも宣言できますが、今回はconstructor injectionで定義するようにしました。

@Service
class HogeService(
    private val hogeRepository: HogeRepository
) {
}

Lombokまわり

Lombokを使っていると、Kotlinにコンバートした際にコンパイルエラーが出ることがありました。

基本的には、Kotlinにコンバートするクラス、Kotlinから参照するクラスは、Delombokすることで解消させました。

  • Delombok

IntelliJlombokプラグインを使用します。 Refactor > DelombokからLombok化を解除してくれます。

Delombok.png

loggerの宣言を省略して、いきなりログ出力するコードを書くことができます。 Kotlinにした場合、以下のコードでlog変数が解決できずコンパイルエラーとなってしまいます。

@Service
@Slf4j
public class HogeService {
    public void hoge() {
        log.info("hoge");
    }
}

kotlinではcompanion objectでロガーを初期化することで利用できるようになります。

@Service
class HogeService {
    companion object {
        private val log = LoggerFactory.getLogger(HogeService::class.java)
    }
}

アノテーションでのDIの方法などを試されている方もいたので、楽にできるよう検討したいと思います。 http://saiya-moebius.hatenablog.com/entry/2017/11/08/033932

以下のようなLombok化されたJavaのコードをKotlinから読み、hogeを取得しようとしてもprivateでアクセスでいないと言われてしまいます。 この場合、上に書いたようにDelombok化して対応しました。 (全部KotlinになればそもそもLombok使わなくても・・)

@Entity
@Data
@EqualsAndHashCode(callSuper = false)
@ToString(callSuper = true)
public class Hoge extends AbstractEntity {
    @Column(name = "hoge")
    private String hoge;
}

今後について

現時点で一部のコードはKotlin化して動作するようになりました。 しかし、実際にコードを書いている途中で問題が起こることが何度かあったので、今後も問題が発生する可能性があります。 そこでの対処方などはどんどん追記していければなと思います。

※Kotlin楽しい!!

IntelliJ Ideaでインポートのワイルドカードを無効化する

この記事はQiitaに2016年11月18日に投稿した記事を移行したものです。

はじめに

IntelliJのバージョンを最新の2016に上げたところ、でJavaのimportをIntelliJに行わせようとすると(option + return)、ワイルドカードでインポートされるようになってしまった。 コードレビューで指摘を受けてしまった。

import java.util.*;
import java.util.stream.Collectors;

ワイルドカードを使わないようにする方法

以下を参考にしました。 http://stackoverflow.com/questions/3348816/intellij-never-use-wildcard-imports

Preferences -> Editor -> Code & Style -> JavaJavaコードスタイルの設定ページを開く。

Importsタブを開くと、以下のような設定画面が表示される。

f:id:yuki10k:20200328222641p:plain

ワイルドカードを使わないようにするには、以下の3点を設定する。

  • Use single class importにチェックを入れる
  • Class count to use import with '*': を999などの大きな値にする
  • Names count to use static import with '*': を999などの大きな値にする

これでワイルドカードでのインポートがされないようになりました。

SPGアメックスカードを申し込んだ理由と手元に届くまで

先日SPGアメックスのカードを申し込み無事審査に通り手もとに届いたので、これからSPGアメックスのカードを発行しようとしている方の参考になればと思い、申込みしてから手元に届くまでの流れと日数を時系列で紹介していきます。

SPGアメックスとは

www.americanexpress.com

アメリカン・エキスプレスが発行する、Marriott Bonvoyとの提携カードです。

主な特徴は

  • Marriott Bonvoyゴールドエリートの資格が得られる
    • チェックアウト時間を14:00まで延長可能
    • 部屋が空いていればアップグレードされる可能性
    • ウェルカムギフトポイント
    • 対象レストランで15%OFF などなど
  • 毎年更新のタイミングでMarriott Bonvoyの無料宿泊券が貰える
  • カードを利用している限りはMarriott Bonvoyポイントの有効期限は実質なし
  • ポイントを40社もの航空会社のマイルに交換可能

www.marriott.co.jp

その代わり年会費は高く、税込み 34,100円となっています。

申し込んだ理由

  • 以前の記事でも触れましたが、最近は自分でもストレスが溜まっているなということで旅行なんかにいってリフレッシュしたい。
  • 今まではあまり旅行に行けていなかったので、毎年もらえる無料宿泊券を使って強制的に旅行に行ってリフレッシュする状態をつくる。
  • 高級ホテルに泊まってみたい ←興味。
  • 実質ポイント無期限なので、じっくりとマイルを貯められる。

申し込みから手元に届くまで

想像していたより時間がかかりました。

申し込み 3/3(火)

私の場合は普通にWebから申し込みをしました。
紹介してもらった場合などはさらにポイントボーナスもあるそうですが、知り合いにホルダーがいなかったので・・。
申し込みする際には、Marriott Bonvoy会員番号が必要となるため予めMarriott Bonvoyの登録をしておく必要がありました。

www.marriott.co.jp

早い人だと申し込み完了時点で審査結果が出るそうですが、私の場合は持ち越しでした。

入会特典についてのメール・アメックスから電話 3/4(水)

まずは入会特典についての紹介メールがアメックスより届きました。
また、同日にアメックスから電話が職場にかかってきました。
本当に職場にかかってくることもあるのか・・と思いつつ簡単な質問に答えて終了。

Marriott Bonvoyのステータスがゴールドに 3/7(土)

Marriott Bonvoyのステータスがゴールドに変化していました。
この地点で審査には通過したと思って良さそうです。

Welcomeメール 3/8(日)

アメックスより、Welcomeメールが届き審査に通ったことが通知されました。

本人限定受け取りの連絡 3/11(水)

Welcomeメールの翌日からソワソワしながら毎日ポストを確認していましたが、本人限定受け取りの通知が入っていたのは3/11(水)でした。
楽天カード等とは違い、カードとは別で通知が来るので郵便局に電話をし配達日を翌日にしてもらいました。
※通常の再配達はWebでの申込みが可能ですが、今回は電話もしくはFAXとなっていました。
この際、受け取りなどで身分証明書の提示が必要であることが知らされます。
また、受け取りが円滑に行われるように予め免許証の番号を電話口で伝えました。

受け取り 3/12(木)

受け取る際には、前日電話した際に伝えた免許証番号を参照され本人確認となります。
このあたりは普通のカードと違ってしっかりしているところですね。

申し込んだら忘れずにやること

アメックスは他のカードとは違い、手元にカードが届いてから暗証番号の登録などが必要でした。

  • アメックスオンラインの登録
  • 暗証番号の登録
  • 引き落とし口座の登録
  • 利用可能限度額の確認

今後

メインのカードをSPGアメックスに切り替えて色々なところに旅行に行けるよう、コツコツとポイントを貯めていきます。
次回更新でもらえる無料宿泊券への飛行機をマイルでまかなうようにできるよう頑張っていきます。

エンジニアの心を整える技術2を読んだ

先日Webで見かけた「エンジニアの心を整える技術2」という本が気になって読んでみたので、その感想です。

エンジニアの心を整える技術2とは

BOOTHというpixiv社が運用している、クリエイターが作品を自由に販売できるサイトで購入できる書籍です。

booth.pm

本来は技術書典8で販売される予定だったらしいですが、残念ながらコロナウィルスの影響で中止になってしまいました。
そのため、PDF版のみですがBOOTHにて購入できました。

techbookfest.org

なぜ購入しようと思ったのか

紹介文を読んでいて、以下の部分がまさに当てはまるなぁと思ったからです。

あなたは、いま、このような想いの中に、いるのかもしれません。

* プロジェクトの終わりの見えない忙しさ
* 残業続きで抜けない疲れにぐったり
* 最新技術やプログラミング言語習得の流行り廃りの虚しさ
* 職場でのやり場のない怒りや悲しみ
* 未来に対する漠然とした不安

自身に当てはめると以下のような感じでした。
少しでも状況や心の持ちようを改善できないかと思い、思わず手が出ました。

  • 業務負荷が上がってきており、週末では疲れが取れない
  • そこそこユーザーの増えてきたSaaSのサービスを主担当のエンジニアとしては自分1人だけなので心理的につらい
  • ロールが少し変わったため、自分の生産性を発揮できてはいない
  • 設計やコードに携わることが減ってきており、最新技術についていけなくなるおのではという不安

読んでみた感想

SHIROBAKOの名言が所々散りばめられていて良い

SHIROBAKOとは、アニメ制作現場をアニメ化したもので、2020年2月29日に劇場版も公開されています。
色々な部分で、「あるある」と共感できる内容になっており心に染みる名言も多い作品になっています。
もちろん劇場版も観ました!

shirobako-anime.com

まえがきの段階から、確信を突かれるようなSHIROBAKOのセリフが載っており一気に引き込まれました。

Slackとの付き合い方を見直すきっかけになった

3.6.4 Slack をミュート・退出せよ。心に「ゆとり」を取り戻せ

上記の章には、「はっ」とさせられました。
Slackは便利な反面、様々な情報が飛び交いメンションがついたものは「すぐに見なければ」という意識になってしまっていたなと思います。
この章を読んで、Slackとの付き合い方を少し見直したいなと考えさせられました。

自己肯定感について考えるきっかけになった

第4章 自己肯定感を整える技術

この章については丸々、グサりとくる内容でした。
とくに自己否定に関する部分は自分に当てはまる部分が多く、心の持ちようを変えたいと考えさせられます。

全体を通して

ページ数はそれほど多くないので、小一時間で読める内容になっていました。
そのため、一気に最後まで読み進められ、ビジネス書などにあるような寄り道もほぼないため、スッと内容が頭に入ってきました。
一部は実践的な内容も書かれていたので、試してみたいなと思います。

BOOTHにて購入できるので、気になった人は読んでみてください。

ETORENでGalaxy Note10+(SM-N9750)を購入したので注文から届くまで

今回、ETORENという海外のサイトで、Galaxy Note10+を購入しました。

届くまで意外に時間がかかったので他の方の参考になるかと思い、購入した背景と注文してから届くまでを書いていきます。

なぜGalaxy Note10+ を購入したのか

今まではPixel3を購入していました。
写真は綺麗に撮れるし、Android OSを開発しているGoogleフラグシップスマホということで、アップデートも早いので基本的には満足していました。
では何が買い替えの決め手になったかというと、以下の点になります。

バッテリーが致命的に持たない

それほどハードに使っているわけでもなく、Pixel3でゲームをするわけでもないのですが、充電は1日に朝出社してからと夜帰宅してからの2回必要でした。
寝る前に充電して、少し使ってから朝起きたら80%くらいになっていることもしばしば・・

たまにメモリが不足する

2018年のフラグシップとしては少ないメモリの4GBがPixel3に載っています。
しかし、利用するアプリによってはもっさりすることもありメモリが足りてないなーと感じることがしばしばありました。
SoCはSnapdragon 845が載っているので、メモリが足りている場合はサクサク動いてくれました。、

画面が小さい

軽くて持ち運びがしやすい反面、画面の小ささが気になるようになりました。
ここは好みの問題ですが、次は画面サイズの大きいものを買おうと前々から考えていました。

Galaxy Note10+について

www.galaxymobile.jp

上記の問題を全て解決してくれる上で、候補はある程度に絞れました。

  • Pixel4 XL
  • Galaxy Note 10+
  • Galaxy S20+ (Note10+ 購入時点では未発表)
  • iPhone 11 Pro Max

などです。
ただ、花粉症シーズンなどマスクを使う場合には顔認証は使い物にならないので、基本的には指紋認証に対応している機種でということになりました。
Galaxy S20シリーズの発表まで待つべきかとも考えましたが、リーク情報を見ている限りではNote 10+でもよさそうという結論に至り、Note 10+に決定しました。
2020年3月に5Gがスタートするということで、気がかりではありましたが5G対応のiPhoneが発売するまではあまり普及しないだろうと考え、今回の購入に至りました。

ETORENについて

jp.etoren.com

海外のスマートホンなどを扱うオンラインショップになります。
Expansysと並んで有名ではないでしょうか。
ETORENの特徴は、輸入時にかかる関税がインクルードされていることです。
そのため、サイト表記の価格 + 送料が実際に支払う総額になります。

また、日々価格が変動するのも海外サイトっぽいところです。
私が購入したときは、93,500円 + 送料でした。 f:id:yuki10k:20200209172029p:plain

購入から届くまで

ETORENは基本的にはページが日本語化されているため、通常のネットショッピングと同じ感覚で購入できると思います。
支払い方法はクレジットカードやPayPalなどが選べました。
また、住所は英語で入力するのですが、以下のサイトなどを使えば困ることはないです。

JuDress | 住所→Address変換

実際のオーダーになります。
日本のキャリアモデルより3万円くらい安いです。
技適なし / Felicaも搭載されていないためご購入を検討される場合は自己責任でお願いします。

f:id:yuki10k:20200209171827p:plain

実際に注文をしたのが2/2(日)で、発送されたのが2/6(木)でした。
発送予定日は 2/3(月) ~ 2/5(水)となっていたので、2/5(水)の夜になっても発送されず不安になったので問い合わせしたところ、2/6(木)に返信があり、当日中に発送されるということで実際にちゃんと発送されました。
このあたりは日本のネットショッピングに慣れているとソワソワする部分です。

実際に発送されてからの流れですが、DHLに引き渡されて、シンガポールから東京まで1晩のうちに届いていました。
DHLから佐川急便に引き渡されて、2/8(土)に無事到着しました。
日本の物流もすごいと思いますが、国際物流も洗練されているなと感じました。

こちらがDHLでの履歴になります。 f:id:yuki10k:20200209172811p:plain

到着したので開封

早速開封していきます。
ちゃんとしたダンボール箱で届きました。

f:id:yuki10k:20200209171022j:plain

中もプチプチでしっかりと梱包されています。

f:id:yuki10k:20200209171029j:plain

箱にはペンの絵が書かれており、非常にシンプルです。

f:id:yuki10k:20200209171041j:plain

箱を開けたところ。

f:id:yuki10k:20200209171052j:plain

蓋側には、SIMピンと付属の透明ケース。

f:id:yuki10k:20200209171105j:plain

本体の下には説明書

f:id:yuki10k:20200209171115j:plain

さらにイヤホン、ACアダプター、USB-Type Cケーブルなどが入っています。

f:id:yuki10k:20200209171121j:plain

本体は透明なシールで包まれていました。

f:id:yuki10k:20200209171124j:plain

オーラグローの背面は、見る角度で表情を変える、非常に美しい仕上がり。

f:id:yuki10k:20200209171127j:plain

最後に

スマホの買い替え後は移行が大変でした。。
まだ少ししか触れていませんが、上の方で上げたPixel3の不満は全て解消してくれる素晴らしい端末だと思います。
これから、初のSペンなど色々試していきたいと思います。