Open20

Kotlin の新規プロジェクトで最初に導入するもの

mskmsk

概要

個人または業務問わずに、Kotlin を用いた新規プロジェクトで、最初に導入するライブラリやセットアップをまとめる。
技術ブログのサンプルコードレベルでも共通のものを導入したい。

一覧

共通

  • renovate(or dependabot)
  • github actions (or 他 CI ツール)
  • detekt(or ktlint)
  • dokka

テスト

  • Assertj
  • jqwik

Spring Boot 導入時

  • OpenAPI Generator
  • Database Rider
mskmsk

renovate

https://github.com/apps/renovate

Main 用途

ライブラリの自動アップデート

Sub 用途

なし

mskmsk

導入手順

  1. https://github.com/apps/renovate にアクセス
  2. 右上の「Configure」
  3. install する先を特定リポジトリにする(Only select repositories)

渡す権限

Read access to Dependabot alerts, administration, and metadata
Read and write access to checks, code, commit statuses, issues, pull requests, and workflows

Dependabot のアラート、管理、メタデータへの読み取りアクセス
チェック、コード、コミット状況、課題、プルリクエスト、ワークフローへの読み取りと書き込みのアクセス権

ユーザー権限

Read access to emails

  1. インストール
  2. インストール後、PR (Configure Rnovate)が自動でできる

renovate の設定

  1. PR で作成されたブランチをローカルに pull して、修正する

renovate.json から .github/renovate.json5 に移動する。

共通

リポジトリに限らず入れる設定

.github/renovate.json5
{
  //
  // automerge
  // https://docs.renovatebot.com/configuration-options/#automerge
  //
  // 自動マージ
  // メジャーバージョン・マイナーバージョンで自動マージの true/false を設定できる
  //
  // default false
  automerge: true,

  //
  // extends
  // https://docs.renovatebot.com/configuration-options/#extends
  //
  // プリセットを使用
  //
  extends: [
    "config:base",
    //
    // timezone
    // https://docs.renovatebot.com/configuration-options/#timezone
    // https://docs.renovatebot.com/presets-default/#timezonearg0
    //
    // 概要
    // - タイムゾーン
    //
    // default: null(UTC)
    //
    ":timezone(Asia/Tokyo)"
  ],

  //
  // labels
  // https://docs.renovatebot.com/configuration-options/#labels
  //
  // 概要
  // - 全てのPRにつけるラベル
  //
  labels: ["dependencies"],

  //
  // dependencyDashboard
  // https://docs.renovatebot.com/configuration-options/#dependencydashboard
  //
  // 概要
  // - ダッシュボード作成する・しない
  //
  // default: false
  //
  dependencyDashboard: true,

  //
  // packageRules
  // https://docs.renovatebot.com/configuration-options/#packagerules
  //
  // 概要
  // - updateするパッケージの単位(まとめることができる)
  //
  // default: なし
  //
}

非共通

リポジトリによって変更する設定

.github/renovate.json5
{
  //
  // automerge
  // https://docs.renovatebot.com/configuration-options/#automerge
  //
  // 自動マージ
  // メジャーバージョン・マイナーバージョンで自動マージの true/false を設定できる
  //
  // default false
  automerge: true,
 
  //
  // packageRules
  // https://docs.renovatebot.com/configuration-options/#packagerules
  //
  // 概要
  // - updateするパッケージの単位(まとめることができる)
  //
  // default: なし
  //
  packageRules: [
    {
      groupName: "org.jetbrains.kotlin.*",
      description: "一緒に上げないとCIが落ちる",
      matchPackagePrefixes: [
        "org.jetbrains.kotlin."
      ]
    },
  ]
}

mskmsk

github actions

github 公式の CI ツール。
lint と test を回すために設定する。

./.github/workflows/ci.yml を作成する。
CI に make コマンドを組み込んでいるので、Makefile の作成は必須。

./.github/workflows/ci.yml
name: CI

on:
  push:

jobs:
  ci-test:
    runs-on: ubuntu-latest
    #
    # GitHubActionsで利用可能な権限
    #
    # URL
    #   - https://docs.github.com/ja/actions/using-jobs/assigning-permissions-to-jobs
    #
    # もし指定したら、それ以外は全てnoneになる
    #
    permissions:
      actions: none
      checks: write
      contents: read
      deployments: none
      id-token: none
      issues: none
      discussions: none
      packages: none
      pages: none
      pull-requests: write
      repository-projects: none
      security-events: none
      statuses: write
    steps:
      - name: リポジトリのチェックアウト
        uses: actions/checkout@v3
      - name: Javaの環境をセットアップ
        uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '17'
          cache: 'gradle'
      - name: '【mainブランチ】'
        if: github.ref == 'refs/heads/main'
        run: make test
        env:
          TZ: Asia/Tokyo
      - name: '【mainブランチ以外】'
        if: github.ref != 'refs/heads/main'
        run: make test
        env:
          TZ: Asia/Tokyo

  ci-lint:
    runs-on: ubuntu-latest
    steps:
      - name: リポジトリのチェックアウト
        uses: actions/checkout@v3
      - name: Javaの環境をセットアップ
        uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '17'
          cache: 'gradle'
      - name: make lint
        run: make lint
mskmsk

Makefile

上記の CI に必要な Makefile。
detekt については後述。

.PHONY: test
test: ## テスト実行
	./gradlew test

.PHONY: fmt
fmt: ## format
	./gradlew detekt --auto-correct

.PHONY: lint
lint: ## lint
	./gradlew detekt
################################################################################
# Utility-Command help
################################################################################
.DEFAULT_GOAL := help

################################################################################
# マクロ
################################################################################
# Makefileの中身を抽出してhelpとして1行で出す
# $(1): Makefile名
define help
  grep -E '^[\.a-zA-Z0-9_-]+:.*?## .*$$' $(1) \
  | grep --invert-match "## non-help" \
  | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
endef

################################################################################
# タスク
################################################################################
.PHONY: help
help: ## Make タスク一覧
	@echo '######################################################################'
	@echo '# Makeタスク一覧'
	@echo '# $$ make XXX'
	@echo '# or'
	@echo '# $$ make XXX --dry-run'
	@echo '######################################################################'
	@echo $(MAKEFILE_LIST) \
	| tr ' ' '\n' \
	| xargs -I {included-makefile} $(call help,{included-makefile})
mskmsk

detekt

Kotlin の linter かつ formatter。ktlint よりも選択肢が多い。

https://github.com/detekt/detekt

https://detekt.dev/

build.gradle.kts に以下を追記する。

build.gradle.kts
plugins {
    /**
     * detekt
     *
     * URL
     * - https://github.com/detekt/detekt
     * GradlePlugins(plugins.gradle.org)
     * - https://plugins.gradle.org/plugin/io.gitlab.arturbosch.detekt
     * Main用途
     * - Linter/Formatter
     * Sub用途
     * - 無し
     * 概要
     * KotlinのLinter/Formatter
     */
    id("io.gitlab.arturbosch.detekt") version "1.23.4"
}

dependencies {
	/**
	 * detektの拡張: detekt-formatting
	 *
	 * 概要
	 * - formattingのルール
	 * - 基本はktlintと同じ
	 * - format自動適用オプションの autoCorrect が使えるようになる
	 */
	detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.4")
}

detekt のバージョンによっては、kotlin("jvm")kotlin("plugin.spring") のバージョンの設定も必要。

build.gradle.kts
plugins {
	// Kotlin と Spring Boot で使う Kotlin Plugin のバージョン
	kotlin("jvm") version "1.9.21"
	kotlin("plugin.spring") version "1.9.21"
}

Makefile を作成して、 lint と format コマンドを登録する。
./gradlew detekt コマンドは型解決してくれないので、./gradlew detektMain./gradlew detektTest を設定する。

https://zenn.dev/loglass/articles/51958a02455a10

Makefile
.PHONY: fmt.main
fmt.main: ## main ソースセットの型解決を使用して format
	./gradlew detektMain --auto-correct

.PHONY: lint.main
lint.main: ## main ソースセットの型解決を使用して lint
	./gradlew detektMain

.PHONY: fmt.test
fmt.test: ## test ソースセットの型解決を使用して format
	./gradlew detektTest --auto-correct

.PHONY: lint.test
lint.test: ## test ソースセットの型解決を使用して lint
	./gradlew detektTest

################################################################################
# Utility-Command help
################################################################################
.DEFAULT_GOAL := help

################################################################################
# マクロ
################################################################################
# Makefileの中身を抽出してhelpとして1行で出す
# $(1): Makefile名
define help
  grep -E '^[\.a-zA-Z0-9_-]+:.*?## .*$$' $(1) \
  | grep --invert-match "## non-help" \
  | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
endef

################################################################################
# タスク
################################################################################
.PHONY: help
help: ## Make タスク一覧
	@echo '######################################################################'
	@echo '# Makeタスク一覧'
	@echo '# $$ make XXX'
	@echo '# or'
	@echo '# $$ make XXX --dry-run'
	@echo '######################################################################'
	@echo $(MAKEFILE_LIST) \
	| tr ' ' '\n' \
	| xargs -I {included-makefile} $(call help,{included-makefile})

mskmsk

Suppress

技術記事のサンプルコードだととりあえず、Suppress で抑制したいときがある。
頻出のものをまとめる。

@Suppress("SpreadOperator")
fun main(args: Array<String>) {
	@Suppress("SpreadOperator")
	runApplication<OpenapiGeneratorSampleApplication>(*args)
}
@Suppress("EmptyFunctionBlock")
@SpringBootTest
class OpenapiGeneratorSampleApplicationTests {

	@Test
	@Suppress("EmptyFunctionBlock")
	fun contextLoads() {
	}

}
mskmsk

設定を上書きしたいとき

まず、detekt の設定ファイルを書き出す。
config/detekt/detekt.yml に書き出される。

detekt の設定ファイルを書き出すコマンド
./gradlew detektGenerateConfig

続いて、編集したいルールのファイルを作成する。

上書き用のファイルを作成
touch config/detekt/detekt-override.yml

detekt-override.yml で修正したいルールを記入する。コメントで default デフォルト値 を書くとわかりやすい。

以下は記入例

config/detekt/detekt-override.yml
---
# comments のルール
comments:
  active: true
  #
  # UndocumentedPublicClass
  #
  # 概要
  # - コメントがないパブリッククラスを禁止する
  #
  UndocumentedPublicClass:
    active: true  # (default: false)
  #
  # UndocumentedPublicClass
  #
  # 概要
  # - コメントがないパブリックな関数を禁止する
  #
  UndocumentedPublicFunction:
    active: true  # (default: false)
  #
  # UndocumentedPublicClass
  #
  # 概要
  # - コメントがないパブリックなプロパティを禁止する
  #
  UndocumentedPublicProperty:
    active: true  # (default: false)

# style のルール
style:
  active: true
  #
  # UnusedImports
  #
  # 概要
  # - 未使用の import を禁止する
  #
  UnusedImports:
    active: true  # (default: false)

build.gradle.kts に読み込ませる。

build.gradle.kts
/**
 * detektの設定
 *
 * 基本的に全て `detekt-override.yml` で設定する
 */
detekt {
    /**
     * ./gradlew detektGenerateConfig でdetekt.ymlが生成される(バージョンが上がる度に再生成する)
     */
    config = files(
        "$projectDir/config/detekt/detekt.yml",
        "$projectDir/config/detekt/detekt-override.yml",
    )
}

mskmsk

pre-commit で commit のたびに lint を実行させる

.githooks/pre-commit を作成する

mkdir ./.githooks
touch ./.githooks/pre-commit

以下を記入する。

./.githooks/pre-commit
#!/usr/bin/env bash
echo "Running detekt check..."
OUTPUT="/tmp/detekt-main-$(date +%s)"
make lint.main > $OUTPUT
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
  cat $OUTPUT
  rm $OUTPUT
  echo "**********************************************************"
  echo "                   Detekt failed                          "
  echo " make fmt.main を実行するか、直接修正してからコミットしてください "
  echo "**********************************************************"
  exit $EXIT_CODE
fi
rm $OUTPUT

OUTPUT="/tmp/detekt-test-$(date +%s)"
make lint.test > $OUTPUT
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
  cat $OUTPUT
  rm $OUTPUT
  echo "**********************************************************"
  echo "                   Detekt failed                          "
  echo " make fmt.test を実行するか、直接修正してからコミットしてください "
  echo "**********************************************************"
  exit $EXIT_CODE
fi
rm $OUTPUT

実行可能ファイルにして、git hook のパスに指定する。

chmod +x .githooks/pre-commit
git config --local core.hooksPath .githooks

https://detekt.dev/docs/gettingstarted/git-pre-commit-hook/

mskmsk

OpenAPI Generator

OpenAPI からコードを自動生成する。
以下の記事に投稿した。

https://zenn.dev/msksgm/articles/20221021-kotlin-spring-openapi-generator

mskmsk

openapi generator の依存関係。

build.gradle.kts
plugins {
    /**
     * openapi.generator
     *
     * 公式ページ
     * - https://openapi-generator.tech/
     * GradlePlugins(plugins.gradle.org)
     * - https://plugins.gradle.org/plugin/org.openapi.generator
     * GitHub
     * - https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-gradle-plugin
     * Main用途
     * - スキーマファイルからコード生成
     * Sub用途
     * - スキーマファイルからドキュメント生成
     * 概要
     * - スキーマ駆動開発するために使う
     * - API仕様をスキーマファイル(yaml)に書いて、コード生成し、それを利用するようにする
     * - 可能な限りプロダクトコードに依存しないようにする(生成したコードにプロダクトコードを依存させる)
     */
    id("org.openapi.generator") version "6.2.0"
}

dependencies {
    /**
     * Swagger Annotations
     * Swagger Models
     * Jakarta Annotations API
     *
     * MavenCentral
     * - https://mvnrepository.com/artifact/io.swagger.core.v3/swagger-annotations
     * - https://mvnrepository.com/artifact/io.swagger.core.v3/swagger-models
     * - https://mvnrepository.com/artifact/jakarta.annotation/jakarta.annotation-api
     * Main用途
     * - OpenAPI Generatorで作成されるコードで利用
     * Sub用途
     * - 無し
     * 概要
     * - OpenAPI Generatorで作成されるコードがimportしている
     * - 基本的にプロダクションコードでは使わない想定
     */
    compileOnly("io.swagger.core.v3:swagger-annotations:2.2.6")
    compileOnly("io.swagger.core.v3:swagger-models:2.2.6")
    compileOnly("jakarta.annotation:jakarta.annotation-api:2.1.1")

    /**
     * Spring Boot Starter Validation
     *
     * MavenCentral
     * - https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation
     * Main用途
     * - OpenAPI Generatorで作成されるコードで利用
     * Sub用途
     * - 無し
     * 概要
     * - OpenAPI Generatorで作成されるコードがimportしている
     * - javax.validation*を利用するため
     * [Spring-Boot-2.3ではjavax.validationを依存関係に追加しなければならない](https://qiita.com/tatetsujitomorrow/items/a397c311a95d66e4f955)
     */
    implementation("org.springframework.boot:spring-boot-starter-validation")
}
mskmsk

さらに task コマンドと、build 先のファイルを import できるようにする。

build.gradle.kts
import org.openapitools.generator.gradle.plugin.tasks.GenerateTask // GenerateTask のオプションのために、import が必要。あらかじめ、build して依存ファイルがないと import できない

/**
 * OpenAPI Generator を使ってコード生成
 */
task<GenerateTask>("generateApiServer") {
	generatorName.set("kotlin-spring")
	inputSpec.set("$projectDir/openapi.yaml")
	outputDir.set("$buildDir/openapi/server-code/") // .gitignoreされているので注意(わざとここにあります)
	apiPackage.set("com.example.yourapp.openapi.generated.controller") // 各自のアプリケーションに合わせてパス名を変更する
	modelPackage.set("com.example.yourapp.openapi.generated.model") // 各自のアプリケーションに合わせてパス名を変更する
	configOptions.set(
		mapOf(
			"interfaceOnly" to "true",
		)
	)
	/**
	 * true にすると tags 準拠で、API の interface を生成する
	 */
	additionalProperties.set(
		mapOf(
			"useTags" to "true"
		)
	)
}

/**
 * Kotlinをコンパイルする前に、generateApiServerタスクを実行
 * 必ずスキーマファイルから最新のコードが生成され、もし変更があったらコンパイル時に失敗して気付けるため
 */
tasks.compileKotlin {
	dependsOn("generateApiServer")
}

/**
 * OpenAPI Generator によって生成されたコードを import できるようにする
 */
kotlin.sourceSets.main {
	kotlin.srcDir("$buildDir/openapi/server-code/src/main")
}

mskmsk

Database Rider

アノテーションで、DB テストを容易にするライブラリ
以下に記事を投稿した。

https://zenn.dev/msksgm/articles/20221014-kotlin-dbtest-with-database-rider

mskmsk

build.gradle.kts

build.gradle.kts
dependencies {
   // 略

    /**
     * Database Rider
     *
     * - Rider Core
     * - Rider Spring
     * - Rider JUnit 5
     *
     * URL
     * - https://database-rider.github.io/database-rider/
     * MavenCentral
     * - https://mvnrepository.com/artifact/com.github.database-rider/rider-core
     * - https://mvnrepository.com/artifact/com.github.database-rider/rider-spring
     * - https://mvnrepository.com/artifact/com.github.database-rider/rider-junit5
     * Main用途
     * - JUnitでDB周りのテスト時のヘルパー
     * 概要
     * - テーブルの事前条件、事後条件を簡潔に設定できる
     */
    implementation("com.github.database-rider:rider-core:1.35.0")
    implementation("com.github.database-rider:rider-spring:1.35.0")
    testImplementation("com.github.database-rider:rider-junit5:1.35.0")
}
mskmsk

基本的な書き方

class ClassNameTest {

    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    @DBRider
    @DisplayName("List(コメント一覧を表示)")
    class InnterClassNameTest {
        @Test
        @DBRider
        @DataSet("datasets/path/to/input/data.{yml,csv,cml}") // 入力データのファイルを指定
        @ExpectedDataSet(
            value = ["datasets/path/to/expected/data.{yml,csv,cml}"], // 期待値のデータのファイルを指定
            orderBy = ["id"],
            ignoreCols = ["createdAt", "updatedAt"], // メタデータを無視する
        )
        // NOTE: 一度データを書き出したら、コメントアウトする
        @ExportDataSet(
            format = DataSetFormat.YML, // テスト完了後にデータを書き出す形式を指定
            outputName = "src/test/resources/datasets/path/to/export/data.{yml,csv,cml}", // 書き出すファイル先を指定
            includeTables = ["customer"] // 含めるカラムを指定
        )
        fun `テスト`() {
                // テストを書く
        }
    }
}

mskmsk

dataset ファイルのパス

ファイルを配置するのは、src/test/resources 配下に設置する。
dataset ファイルのパスは @DataSet@ExpectedDataSet のときで指定方法が異なる。

アノテーション 指定先
@DataSet resources 配下から指定する。resources は含まない "datasets/yml/XXX/input-file.yml"
@ExpectedDataSet src を含むパスで指定する "src/test/resources/datasets/yml/XXX/expected-file.yml"
mskmsk

dokka

Kotlin のドキュメント生成ツール

https://github.com/Kotlin/dokka

plugins {
    /**
     * dokka
     *
     * URL
     * - https://github.com/Kotlin/dokka
     * GradlePlugins(plugins.gradle.org)
     * - https://plugins.gradle.org/plugin/org.jetbrains.dokka
     * Main用途
     * - ドキュメント生成
     * Sub用途
     * - 特になし
     * 概要
     * - JDocの代替(=KDoc)
     */
    id("org.jetbrains.dokka") version "1.7.20"
}

dependencies {
    /**
     * dokkaHtmlPlugin
     *
     * dokka Pluginを適用するのに必要
     */
    dokkaHtmlPlugin("org.jetbrains.dokka:kotlin-as-java-plugin:1.7.20")
}

mskmsk

Intellij plugin

KDoc-er をインストールする。そうしないとコマンド補完がされない。

mskmsk

ドキュメント生成

以下のコマンドで、HTML を生成する。
./build/dokka/html に生成される。

./gradlew dokkaHtml
mskmsk

Assertj

assertion を簡潔に書くためのライブラリ。

https://github.com/assertj/assertj

dependencies {
    /**
     * AssertJ
     *
     * URL
     * - https://assertj.github.io/doc/
     * MavenCentral
     * - https://mvnrepository.com/artifact/org.assertj/assertj-core
     * Main用途
     * - JUnitでassertThat(xxx).isEqualTo(yyy)みたいな感じで比較時に使う
     * Sub用途
     * - 特になし
     * 概要
     * - JUnit等を直感的に利用するためのライブラリ
     */
    testImplementation("org.assertj:assertj-core:3.23.1")
}