ごっそログ

勉強したことなどを書いていきます

Nuxt 3+Spring BootでREST API #5 flywayでDBのマイグレーションを行う

目次

flywayのインストール

前回で無事にマルチプロジェクト構成にできたので、今回はマイグレーション周りを整備していく。
build.gradle に組み込む方法もあるっぽいけど今回はnpmでnode-flywaydbを導入する。
今回はひとまずローカルで実行できるところまで。

まずはnpm initnpm install node-flywaydbでインストールを行う。

現時点のpackage.json

設定ファイルの記述

以下を参考に config.js を記述する。
github.com

// This can be a function or an object literal.
module.exports = function () {
  console.log(`migaration start`);
  return {
    flywayArgs: {
      url: `jdbc:mysql://localhost:3307/base`,
      schemas: "base",
      locations: "filesystem:migrate/sql",
      user: "root",
      password: ****,
      sqlMigrationSuffixes: ".sql",
      baselineOnMigrate: true,
    },
    // Use to configure environment variables used by flyway
    env: {
      JAVA_ARGS: "-Djava.util.logging.config.file=./conf/logging.properties",
    },
    version: "6.3.2", // optional, empty or missing will download the latest
    mavenPlugins: [
      {
        // optional, use to add any plugins (ie. logging)
        groupId: "org.slf4j",
        artifactId: "slf4j-api",
        version: "1.7.25",
        // This can be a specifc url to download that may be different then the auto generated url.
        downloadUrl:
          "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar",
      },
      {
        groupId: "org.slf4j",
        artifactId: "slf4j-jdk14",
        version: "1.7.25",
      },
    ],
    downloads: {
      storageDirectory: "migrate/lib", // optional, the specific directory to store the flyway downloaded files. The directory must be writable by the node app process' user.
      expirationTimeInMs: -1, // optional, -1 will never check for updates, defaults to 1 day.
    },
  };
};

package.jsonの編集

npmコマンドでマイグレーションを実行できるようにする。
scripts が以下になるように2行追記。

  "scripts": {
    "migrate": "flyway -c migrate/config.js migrate",
    "clean": "flyway -c migrate/config.js clean",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

npm run migrate を実行してみる。
config.jsで指定したbase というスキーマができていれば成功。

マイグレーションファイルの作成

migration直下に sqlディレクトリを作成し、ここにマイグレーションファイルを配置していくことにする。
今回は V1.0.0__init.sqlという名前で作成した。flywayのマイグレーションファイルには命名規則があり、正常に動作させるには厳密に従う必要があるので注意すること。(ここでは詳述しない)
以下、特に意味のないテスト用テーブルのDDL

DROP TABLE IF EXISTS t_test;
CREATE TABLE t_test (
  id varchar(8) not null comment 'ID',
  name varchar(20) not null comment '名称',
  constraint t_test_pk primary key (id)
) comment='テスト';

試運転

再度 npm run migrate を実行してみる。

テーブルが作成された。
先ほどpackage.jsonに定義した、マイグレーションの進行状況をクリーンするためのコマンドも動作確認しておく。
npm run clean を実行。

テーブルが削除されていればOK。

参考

GradleでFlywayでMySQLをマイグレーション - Qiita

Nuxt 3+Spring BootでREST API #4 マルチプロジェクト構成にする

目次

  • Gradleのインストール
  • プロジェクト構成
  • ルートプロジェクトの作成
  • ルートプロジェクトの作成
  • Gradle Wrapperの導入 & ビルド

Gradleのインストール

Gradleコマンドを使えるようにする。Gradle Releasesにアクセス。
Downloadからcompleteを選択してダウンロード。

zipファイルを解凍して任意のパスに配置する。
以下の環境変数を登録する。

  • GRADLE_HOME
    • 値: gradleのパス
  • PATH
    • 値: %GRADLE_HOME%\bin

gradle -v でパスが通っているか確認する。

プロジェクト構成

現状APIのプロジェクトを作成しただけなので、ルートディレクトリを切って、API、フロント、マイグレーションなどの単位で分割されたマルチプロジェクト構成にしていく。
構成は以下のようなイメージ。

.(root)
├─ api (APIサーバのプロジェクト)
├─ common (共通ライブラリ)
├─ iac (IaCのプロジェクト)
├─ migrate (マイグレーション用のプロジェクト)
├─ repository (DAOなどDBアクセス用のプロジェクト)
├─ ui (フロントのプロジェクト)
├─ build.gradle
└─ setting.gradle

ルートプロジェクトの作成

gradle init を実行。

ルートパスに作成されたsettings.gradleを編集する。

/*
 * This file was generated by the Gradle 'init' task.
 *
 * The settings file is used to specify which projects to include in your build.
 *
 * Detailed information about configuring a multi-project build in Gradle can be found
 * in the user manual at https://docs.gradle.org/6.9.3/userguide/multi_project_builds.html
 */

rootProject.name = 'rest-api-sample'
include 'api', 'common', 'repository'

これでapi, common, repository がサブプロジェクトとして認識されるようになる。

Gradle Wrapperの導入 & ビルド

Gradleの実行環境がなくても (gradleコマンドなしで) ビルドできるようにするために、Gradle Wrapper を導入する。
gradle wrapper を実行。
ここでビルドが通らず FAILURE: Build failed with an exception. と怒られ、以下のエラーが出た。

* What went wrong:
Could not compile settings file 'C:\work\repos\*******\rest-api-sample\settings.gradle'.
> startup failed:
  General error during semantic analysis: Unsupported class file major version 61

ひとまずgradle wrapper周りのライブラリは追加されたので、ビルドできない原因を突き止める。
エラー内容でググったところ、Gradle 6系だとJava16以降のバージョンをサポートしていないようなので、gradle-wrapper.properties を以下のように修正した。

distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip

再度build実行したところ、無事成功。

参考

Gradleでマルチプロジェクトを作成する

Windows10にGradleをインストール - Qiita

GradleでJavaのマルチプロジェクト作成 - Qiita

Gradle Wrapper スクリプト (gradlew) を作成する - まくまくGradleノート

【Spring】General error during conversion: Unsupported class file major version 61の対応 - Tech Hotoke Blog

Nuxt 3+Spring BootでREST API #3 DBをDockerイメージとして起動する

目次

  • Docker経由でMySQLイメージを取得する
  • docker-compose.yml を記述する
  • Dockerイメージを立ち上げて疎通確認

Docker経由でMySQLイメージを取得する

MySQLイメージを取得する。 Dockerを起動した状態で以下コマンドを実行。

$ docker pull mysql:latest

docker-compose.yml を記述する

設定内容は以下。

version: "3"
services:
  db:
    image: mysql:5.7
    container_name: mysql_sample
    environment:
      MYSQL_ROOT_PASSWORD: ****
      MYSQL_DATABASE: base
      MYSQL_USER: docker
      MYSQL_PASSWORD: ****
      TZ: "Asia/Tokyo"
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - ./db/data:/var/lib/mysql
      - ./db/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./db/sql:/docker-entrypoint-initdb.d
    ports:
      - 3307:3306

localhostで動作することを想定してportは3307を指定。

Dockerイメージを立ち上げて疎通確認

以下コマンドを実行。

docker-compose up -d db

A5M2で疎通確認。

Nuxt 3+Spring BootでREST API #2 HTTPリクエストを受け付けるコントローラを作成する

目次

  • コントローラクラスを作成する
  • リクエストを返すことを確認する

コントローラクラスを作成する

com.sample.apicontrollerのパッケージを切って、以下のヘルスチェック用コントローラクラスを作成する。

package com.sample.api.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("healthcheck")
public class HealthCheckController {

    @RequestMapping(method = RequestMethod.GET)
    public String healthCheck() {
        return "OK";
    }
}

ヘルスチェックはAWSにデプロイした際にALBからのヘルスチェックを受けるためにも使用する。(後述)

リクエストを返すことを確認する

Postmanから http://localhost:8080/healthcheck にアクセスする

OKが返ってくればOK

Nuxt 3+Spring BootでREST API #1 APIプロジェクトを作成する

目次

  • Spring Initializrでプロジェクトを作成する
  • IntelliJの設定を行う
  • プロジェクトを実行する

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

Spring Initializr で雛形を作成する。
今回は以下の内容を設定した。

  • Project: Gradle
  • Language: Java
  • Spring Boot: 2.7.5
  • Metadata
    • Group: com.sample
    • Artifact: api
    • Name: api
    • Packaging: Jar
    • Java: 17
  • Dependencies

spring initializr

「GENERATE」を押下すると、プロジェクトのzipファイルがダウンロードされる

IntelliJの設定を行う

Gradle JVMの設定
  • 設定 > ビルド、実行、デプロイ > Gradle
  • Gradle JVMのバージョンが先ほどSpring Initializrで選択したJavaのバージョンと一致しているか確認しておく

アノテーションプロセッサを有効にする

コンパイル時にアノテーションに基づいてコードを検証、生成する仕組み(らしい)

SDKの設定
  • プロジェクト構造 > プロジェクト
  • SDKを選択
  • 言語レベルがSpring Initializrで選択したものになっていることを確認

プロジェクトを実行して立ち上がればOK

参考

spring boot で web api サーバを作る - Qiita

Nuxt 3+Spring BootでREST API #0 基本構成

イントロダクション

ここ数ヶ月、タイトルにあるような技術構成でWebアプリの開発をしている。
構築でいろいろと詰まったりしたので備忘録として作業ログを残していく。

やること

APIサーバ、フロントエンド、DBなど基盤構築の手順を記録する。
インフラ(AWS)、CI/CD(GitHub Actions)などにも触れる。

やらないこと

基盤構築周りの話がメインになるため、Nuxtのライフサイクルやら、テスト手法やら、個々の技術要素についての詳細には立ち入らない。

技術構成

以下のような感じ。

API
フロントエンド
  • Nuxt 3
  • テストはJest
RDB
インフラ
  • フロントはS3においてCloudFrontで配信
  • APIサーバはECSにデプロイ
  • Cognitoで認証認可を行う
CI/CD
  • GitHub Actionsで自動テスト+自動デプロイ
  • CDKでプロビジョニングのコード化を行うところまでする

目次

適宜リンクは更新していく。

まずはAPIプロジェクトを作成するところから。

[C#] 型引数を変えながらジェネリクスメソッドをループ処理で実行する

メモ。

経緯

引数が指定した型に変換可能であるかを判定して結果を返すジェネリックメソッド CanParse<T>を作った。
シグネチャは以下の通り。

public static bool CanParse<T>(string value) where T : IConvertible, new()
{
    // 判定処理(省略)
}

このメソッドの動きを確認するため、
適当な値が入った以下のようなリストを用意した。

var values = new List<string>()
{
    "-129", "-128", "-1", "0", "127", "128", "255", "256",
    "65535", "65536", "-32769", "-32768", "32767", "32768",
    "-2147483649", "-2147483648", "2147483647", "2147483648"
};

このリスト内の全ての値を、全てのプリミティブな数値型(byte, decimal, double, float, int, uint, long...)に対して総当たりで実行し、メソッドの動きを確認したかったのだがループ処理の実装で詰まってしまった。

// 型リスト
var types = new List<Type>()
{
    typeof(byte),
    typeof(decimal),
    typeof(double)
    // 以下略
};
// 総当たり
foreach (var type in types)
{
    foreach (var value in values)
    {
        // ここでコンパイルエラー
        Console.WriteLine(string.Format("{0}<{1}>: {2}\t({3})", nameof(CanParse), type, CanParse<type>(value), value ?? "null"));
    }
}

CanParse<type>(value)が問題。
型引数は変数として扱うことができないため、上記の書き方だとエラーとなってしまう。

対処法

デリゲートで実装することでループ処理できるようになった。

using System;
using System.Collections.Generic;
using System.Reflection;

// delegate bool CanParseDelegate(string value);
// 上のスコープで↑を宣言済

// 値リスト
var values = new List<string>()
{
    "-129", "-128", "-1", "0", "127", "128", "255", "256",
    "65535", "65536", "-32769", "-32768", "32767", "32768",
    "-2147483649", "-2147483648", "2147483647", "2147483648"
};

// デリゲートリスト
var delegates = new List<CanParseDelegate>
{
    new CanParseDelegate(CanParse<bool>),
    new CanParseDelegate(CanParse<byte>),
    new CanParseDelegate(CanParse<sbyte>),
    new CanParseDelegate(CanParse<char>),
    new CanParseDelegate(CanParse<decimal>),
    new CanParseDelegate(CanParse<double>),
    new CanParseDelegate(CanParse<float>),
    new CanParseDelegate(CanParse<int>),
    new CanParseDelegate(CanParse<uint>),
    new CanParseDelegate(CanParse<long>),
    new CanParseDelegate(CanParse<ulong>),
    new CanParseDelegate(CanParse<short>),
    new CanParseDelegate(CanParse<ushort>)
};

foreach (var dlg in delegates)
{
    foreach (var value in values)
    {
        // 型引数取得
        var arg = dlg.Method.GetGenericArguments();
        Console.WriteLine(string.Format("{0}<{1}>: {2}\t({3})", nameof(CanParse), arg[0], dlg(value), value ?? "null"));
    }
}

まとめ

  • 型引数は変数として扱うことができないため、ループ処理する際にはデリゲートを用意する等の工夫が必要。
  • リフレクションでごり押しする方法もあるらしい。(試していない)
  • GetGenericArguments()で型引数が取得できる。
    • Typeの配列として返されるため、参照する際は添字が必要な点に注意。