ごっそログ

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

Nuxt 3+Spring BootでREST API #12 フロントのインフラを構築する(手動編)

イントロダクション

この回からはデプロイ周りを整備していく。
最終的にはAWS CDKで自動化するが、何をやっているかの理解を深めるために手動→CloudFormation→CDKと段階を踏みながら同じ作業をしていく予定。
今回はマネジメントコンソールをいじりながら手動でデプロイしていく。

目次

S3バケットを作成する

Nuxtのファイルを放り込むためのS3バケットを作成する。

一般的な設定

任意のバケット名を付ける。グローバルに一意な必要がある。

このバケットのブロックパブリックアクセス設定

「パブリックアクセスをすべてブロック」のチェックを外す。

「現在の設定により、このバケットバケット内のオブジェクトが公開される可能性があることを承認します。」にチェックを入れる。

これ以外はデフォルトのままバケットを作成。

Nuxtのビルド & デプロイ

npm run generateを実行。

.output/publicにファイルが吐き出されるので、この中身を全てS3にアップロードする。

プロパティ > 静的ウェブサイトホスティングの編集を押す。

  • 静的ウェブサイトホスティング
    • 「有効にする」を選択。
  • インデックスドキュメント
    • index.htmlを指定。

「変更の保存」を押す。

CloudFrontディストリビューションを作成する

CloudFrontに移動し、「ディストリビューションを作成」を押す。

オリジン
  • オリジンドメイン
    • 先ほど公開したエンドポイントを登録。
  • 名前
    • オリジンドメイン設定時に自動で反映される。
  • S3 バケットアクセス
    • Origin access control settings (recommended) を選択。
  • Origin access control
    • 「コントロール設定を作成」から作成し設定。

デフォルトのキャッシュビヘイビア

設定
  • デフォルトルートオブジェクト - オプション
    • index.html を設定。

上記以外はデフォルトのままで「ディストリビューションを作成」をクリック。

S3バケットポリシーの更新

ディストリビューション作成後、下記のポップアップが出るので、ポリシーをコピーした後、S3のバケットポリシー編集画面に移動する。

ポリシーを貼り付け。

CloudFrontに戻り、一般 > 詳細 > ディストリビューションドメイン名 のURIにアクセスしてみる。
画面が表示されればOK。

参考

Nuxt3 (SSG) を S3 & CloudFront に CloudFormation でデプロイする | mirumi.tech

Nuxt 3+Spring BootでREST API #11 フロントのテストを自動化する

目次

  • フロント用のワークフローを定義する
  • ワークフローを分離する
  • (補足)pathsとpaths-ignoreの挙動について
  • フロント側の試運転
  • API側の試運転

フロント用のワークフローを定義する

.github\workflowsにフロント用のワークフローであるui-test.ymlを作成する。

name: pr_check_ui

on:
  pull_request:
    types: [opened, synchronize]
    paths:
      - "ui/**"

jobs:
  comment:
    runs-on: ubuntu-latest
    steps:
      - name: checkout source
        uses: actions/checkout@v3

      - name: Cache for node_modules
        uses: actions/cache@v2
        with:
          path: "**/node_modules"
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

      - name: install package
        run: cd ui && npm install

      - name: run test
        run: cd ui && npm run test

依存パッケージをインストールして、前回追加したテストコマンドを叩いている。
プロジェクトのルートパスで動作するのでコマンド実行前にフロントのディレクトリに移動する必要がある。
また、onpathsを指定してui以下に変更があった場合だけワークフローが動作するようにしている。

ワークフローを分離する

以前作成したAPI用のワークフロー(api-test.yml)にはpathsの指定がないため、フロントの変更のみを含むプルリクにも反応してしまう。
無駄なので、フロントのみの変更をAPI用のワークフローが無視するように修正していく。

name: pr_check

on:
  pull_request:
    types: [opened, synchronize]
    paths-ignore:
      - "ui/**"

jobs:
  comment:
    runs-on: ubuntu-latest
    steps:
      - name: checkout source
        uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: 17
          distribution: "temurin"

      - uses: actions/setup-node@v1
        with:
          node-version: "14"

      - name: Cache for node_modules
        uses: actions/cache@v2
        with:
          path: "**/node_modules"
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

      - name: build java project by gradle
        run: sh ./gradlew clean build

onpaths-ignoreを指定して、ui以下の変更しか含まれないプルリクの場合は無視するように設定した。
フロントとは異なり、paths-ignoreを用いて指定しているところに注意。
今回の構成では、apiは他の外部のプロジェクト(common, repositoryなど)にも依存する場合があり、フロント以外の変更がある場合には基本的に常に動かしておきたいためこのような指定にした。
このあたりはプロジェクトの設計や都合に応じて適宜考慮すること。

(補足)pathsとpaths-ignoreの挙動について

  • paths

    • マッチするファイルの変更が1つでもあれば起動。
  • paths-ignore

    • マッチするファイルの変更しかなければ起動しない。
    • マッチしないファイルの変更が1つでもあれば起動。

フロント側の試運転

フロント側のみに変更を入れて、プルリクを出してみる。

フロントのワークフローのみが動いている。

API側の試運転

API側のみに変更を入れて、プルリクを出してみる。

APIのワークフローのみが動いている。

参考

Nuxt 3+Spring BootでREST API #10 Jestでテストを書く

目次

  • Jestのインストール・設定
  • package.jsonの編集
  • テストコードを書く
  • テストを走らせる

Jestのインストール

TypeScriptで記述する前提で必要なライブラリをインストールしていく。

npm install --save-dev jest typescript ts-jest @types/jest

設定ファイルの作成。

npx ts-jest config:init

package.jsonの編集

npm run testでテスト実行できるようにする。

"scripts": {
  "test": "jest"
},

テストコードを書く

まずはテスト対象になるコード。
./logic/sum.tsに、単純な足し算を行う関数を作成。

export const sum = (a: number, b: number) => a + b;

テストコードは以下。
./test/sum.test.tsにテストを記述。
1と2を与えて3が返ってくればPASS。

import { test, expect } from "@jest/globals";
import { sum } from "../logic/sum";

test("adds 1 + 2 to equal 3", () => {
  expect(sum(1, 2)).toBe(3);
});

テストを走らせる

npm run testを叩いてみる。

補足

折角のNuxt3なのでVitestを使ってみたかった……が、今回は全体的な構築を主眼に置いているので導入が簡単なJestにした。
Nuxt3 + Vitestでのテストはまた別途やりたい。 Nuxt 3 × Vitest で単体テストの実行環境を作る

参考

Nuxt 3+Spring BootでREST API #9 NuxtからHTTPリクエストを送る

目次

  • フロントの実装
  • APIサーバの実装
  • 試運転

フロントの実装

<script setup lang="ts">
const onClick = async () => {
  const uri = "http://localhost:8080/hello";
  fetch(uri, {
    method: "GET",
    redirect: "follow",
  })
    .then((response) => {
      return response.json();
    })
    .then((json) => {
      console.log(json.message);
    })
    .catch((err) => {
      console.error(err);
    });
};
</script>
<template>
  <div>
    <button @click="onClick()">Hello</button>
  </div>
</template>

ボタンを配置し、クリック時にAPIリクエストが飛ぶようにする。
後々環境変数から読み込むようにしたいが、ひとまずlocalhostをハードコード。

APIサーバの実装

リクエストを受け取り、挨拶を返すコントローラクラス。

package com.sample.api.controller;

import com.sample.api.controller.out.GetOut;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("hello")
@RequiredArgsConstructor
@CrossOrigin
public class HelloController {

    @RequestMapping(method = RequestMethod.GET)
    public GetOut sayHello() {
        return new GetOut("Hello, client");
    }
}

試運転

ボタンを押下するとAPIサーバからレスポンスが返却された。

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

目次

今回は短め。

  • プロジェクトの新規作成
  • 試運転

コマンドのインストール

npx nuxi init (プロジェクト名) でNuxtプロジェクトを作成する。

試運転

uiディレクトリに移動し、依存パッケージのインストール。

npm run devを実行しhttp://localhost:3000/ にアクセスして、Nuxtデフォルトの画面が表示されていればOK。

Nuxt 3+Spring BootでREST API #7 GitHub ActionsでCI環境を構築する

目次

リポジトリを作成する

前回作成したJUnitをCI環境に組み込み、プルリクエストを作成、更新した時点で自動でテストが走るようにしたい。
今回はGitHub Actionsを使用するが、まずはリポジトリを作らないと始まらないのでrest-api-sampleという名前で作成する。

GitHub Actionsワークフローの定義を行う

次にワークフローの定義。

リポジトリのルートに.github\workflowsディレクトリを作成。
workflowsの中にyaml形式でファイルを作成することでワークフローとして認識される。
今回はapi-test.ymlという名前で作成した。

name: api-test

on:
  pull_request:
    types: [ opened, synchronize ]

jobs:
  api-test:
    runs-on: ubuntu-latest
    steps:
      - name: checkout source
        uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: 17
          distribution: "temurin"

      - uses: actions/setup-node@v1
        with:
          node-version: "14"

      - name: Cache for node_modules
        uses: actions/cache@v2
        with:
          path: "**/node_modules"
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

      - name: build java project by gradle
        run: sh ./gradlew clean build

onでワークフローをトリガーするイベントを定義する。
今回はプルリクエストが作成されたり、更新されたりしたタイミングでワークフローを実行したいので、typesにopened, synchronizeを指定した。

jobsで実行させたいジョブを定義する。

stepsの具体的な内容に関しては今回は詳述しないが、ざっくり言うと

  • リポジトリからソースをチェックアウトする
  • JDK 17のセットアップを行う
  • Node.jsのセットアップを行う
  • ライブラリのインストールをキャッシングする
  • APIプロジェクトをビルドする (ここでテストが走る)

のような流れになっている。

ひとまず上記のyamlファイルをプッシュして準備完了。

試運転

適当なブランチを切ってプルリクを出し、CIが動くか試してみる。
まずtest_ghaという名前のブランチを切る。

適当にロジックを更新して

コミットして、リモートにプッシュしたらプルリクの作成を行う。

定義したジョブが走っているのがわかる。

テストにパスし、ビルド完了。

あえてテストコードを間違った内容に変更し、テストが失敗したときの動作を見てみる。
テストコードを変更し、再度コミット、プッシュ。
(先ほどtypesにsynchronizeを設定したので) プルリクが更新された場合もワークフローが実行されることがわかる。

テスト失敗。

というわけで、簡易的なAPIプロジェクトのCI環境が完成した。

参考

https://docs.github.com/ja/actions/using-workflows

GitHub Actionsでキャッシュを利用してワークフローの実行時間を短縮する - Fusic Tech Blog

Nuxt 3+Spring BootでREST API #6 JUnitでテストを書く

目次

  • Gradleでテストコマンドを実行できるようにする
  • テスト対象のロジッククラスを記述する
  • テストコードを書く
  • テストを実行する

Gradleでテストコマンドを実行できるようにする

apiプロジェクトのbuild.gradleに以下の記述を追加する。

testImplementation('org.springframework.boot:spring-boot-starter-test') {
    exclude group: 'junit', module: 'junit'
    exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.mockito:mockito-inline:3.6.28'
testImplementation 'net.java.dev.jna:jna-platform:5.8.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

全体はこんな感じ。

plugins {
    id 'org.springframework.boot' version '2.7.5'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
    id 'java'
}

group = 'com.sample'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'junit', module: 'junit'
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'org.mockito:mockito-inline:3.6.28'
    testImplementation 'net.java.dev.jna:jna-platform:5.8.0'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

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

rootのbuild.gradleのsubprojectsに以下の記述を追加する。

test {
    useJUnitPlatform()
}

全体はこんな感じ。

/*
 * This file was generated by the Gradle 'init' task.
 *
 * This is a general purpose Gradle build.
 * Learn more about Gradle by exploring our samples at https://docs.gradle.org/6.9.3/samples
 */
allprojects {
    group = 'com.sample'
}

subprojects {
    apply plugin: 'java'
    sourceCompatibility = '17'

    test {
        useJUnitPlatform()
    }
}

IntelliJ画面右のGradleタブよりbuildを実行。 Gradleタブよりtestコマンドが実行できるようになっていればOK。

テスト対象のロジッククラスを記述する

logicパッケージを作成し、名前を引数で渡したら挨拶を返すシンプルなロジッククラスを作成。

テストコードを書く

最低限だけどこんな感じ。

package com.sample.api.logic;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

public class HelloLogicTest {

    HelloLogic logic = new HelloLogic();

    @Test
    public void test_sayHello() throws Exception {

        try {
            String param = "Taro";
            String expected = "Hello, Taro";
            String result = logic.sayHello(param);
            assertEquals(expected, result, "sayHello()");
        } catch (Exception e) {
            fail(e.getMessage());
        }
    }
}

テストを実行する

先ほどのGradleタブからtestを選んで実行。

テスト成功。

参考

Spring BootでテストするWebアプリケーション① - Gradleプロジェクトの編集 - ぺんぎんらぼ