Skip to content

第2章: 開発環境の構築

2.1 技術スタックの選定

ソフトウェア開発の三種の神器

TDD でデータベース設計を進めるための開発環境を構築します。テスト駆動開発のゴールは動作するきれいなコードですが、それを実現するためにはソフトウェア開発の三種の神器が必要です。

uml diagram

今日のソフトウェア開発の世界において絶対になければならない3つの技術的な柱があります。 三本柱と言ったり、三種の神器と言ったりしていますが、それらは

  • バージョン管理
  • テスティング
  • 自動化

の3つです。

— https://t-wada.hatenablog.jp/entry/clean-code-that-works

Java 25 を選んだ理由

本プロジェクトでは、バックエンド言語として Java 25 を採用しています。

uml diagram

観点 Java の利点
型安全性 コンパイル時のエラー検出で堅牢なコードを実現
エコシステム Spring Boot、MyBatis など成熟したフレームワーク群
保守性 静的型付けにより、大規模コードベースでもリファクタリングが安全
テスト JUnit 5、AssertJ、Testcontainers など充実したテストツール
最新機能 最新の言語機能とパフォーマンス改善を活用

Spring Boot の利点

Spring Boot は Java の Web アプリケーション開発において最も広く使われているフレームワークです。

uml diagram

Spring Boot の主な特徴

特徴 説明
自動設定 設定より規約(Convention over Configuration)で開発効率向上
組み込みサーバー Tomcat が組み込まれ、単独で実行可能な JAR を生成
スターター 依存関係をスターターで一括管理
Actuator ヘルスチェック、メトリクスなど運用機能を標準提供
DevTools ホットリロードによる開発効率向上

MyBatis vs JPA の選択

O/R マッパーとして MyBatis を採用しました。

uml diagram

MyBatis を選んだ理由

観点 MyBatis の利点
SQL 制御 複雑な SQL を直接記述でき、パフォーマンスチューニングが容易
学習コスト SQL の知識がそのまま活用でき、学習曲線が緩やか
デバッグ 実行される SQL が明確で、問題の特定が容易
柔軟性 動的 SQL により、条件に応じた柔軟なクエリが記述可能
既存 DB 対応 既存データベースへの適用が容易
// MyBatis Mapper の例
public interface AccountMapper {
    @Select("SELECT * FROM \"勘定科目マスタ\" WHERE \"勘定科目コード\" = #{code}")
    Account findByCode(String code);

    @Insert("INSERT INTO \"勘定科目マスタ\" (\"勘定科目コード\", \"勘定科目名\", \"勘定科目種別\", \"残高\") " +
            "VALUES (#{code}, #{name}, #{accountType}::account_type, #{balance})")
    void insert(Account account);
}

2.2 バックエンド環境

前提条件

以下のツールがインストールされていることを確認してください。

ツール バージョン 備考
Java 25 以上 推奨:Java 25
Gradle 8.x 以上 推奨:Gradle Wrapper 使用
Docker 最新版 Docker Compose 含む
Git 最新版 バージョン管理

Gradle プロジェクトのセットアップ

プロジェクトの初期化

$ mkdir accounting-system
$ cd accounting-system
$ gradle init --type java-application --dsl groovy --test-framework junit-jupiter --package com.example.accounting

対話形式で以下を選択します:

  • Generate build using new APIs and behavior: yes
  • Project name: accounting-system
  • Source package: com.example.accounting

build.gradle の構成

plugins {
    id 'java'
    id 'application'
    id 'jacoco'
    id 'checkstyle'
    id 'pmd'
    id 'com.github.spotbugs' version '6.0.7'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.example.accounting'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '25'
}

repositories {
    mavenCentral()
}

dependencies {
    // Spring Boot
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    // MyBatis
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'

    // Flyway
    implementation 'org.flywaydb:flyway-core'
    implementation 'org.flywaydb:flyway-database-postgresql'

    // Database
    runtimeOnly 'org.postgresql:postgresql'
    runtimeOnly 'com.mysql:mysql-connector-j'

    // Lombok (オプション)
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    // Test
    testImplementation 'org.junit.jupiter:junit-jupiter-api'
    testImplementation 'org.junit.jupiter:junit-jupiter-params'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
    testImplementation 'org.assertj:assertj-core:3.24.2'
    testImplementation 'org.testcontainers:testcontainers:1.19.3'
    testImplementation 'org.testcontainers:postgresql:1.19.3'
    testImplementation 'org.testcontainers:mysql:1.19.3'
    testImplementation 'org.testcontainers:junit-jupiter:1.19.3'

    // SpotBugs
    spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0'
}

application {
    mainClass = 'com.example.accounting.App'
}

test {
    useJUnitPlatform()
    finalizedBy jacocoTestReport
}

jacoco {
    toolVersion = "0.8.11"
}

jacocoTestReport {
    dependsOn test
    reports {
        xml.required = true
        html.required = true
    }
}

Spring Boot アプリケーションの初期化

アプリケーションクラス

@SpringBootApplication
public class AccountingApplication {
    public static void main(String[] args) {
        SpringApplication.run(AccountingApplication.class, args);
    }
}

設定ファイル(application.properties)

# アプリケーション設定
spring.application.name=accounting-system

# データソース設定(PostgreSQL)
spring.datasource.url=jdbc:postgresql://localhost:5432/accounting_system
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.driver-class-name=org.postgresql.Driver

# MyBatis設定
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.type-aliases-package=com.example.accounting.domain.model
mybatis.mapper-locations=classpath:mybatis/mapper/**/*.xml

# Flyway設定
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
spring.flyway.baseline-on-migrate=true

# ログレベル
logging.level.com.example.accounting=DEBUG
logging.level.org.springframework.jdbc.core=DEBUG
logging.level.org.mybatis=DEBUG

2.3 データベース環境

Docker Compose によるデータベース構築

Docker を使用すると、データベース環境を簡単にセットアップできます。

docker-compose.yml

services:
  # PostgreSQL データベース
  postgres:
    image: postgres:16-alpine
    container_name: accounting-system-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-postgres}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
      POSTGRES_DB: ${POSTGRES_DB:-accounting_system}
      TZ: 'Asia/Tokyo'
    ports:
      - "${POSTGRES_PORT:-5432}:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./ops/docker/postgres/init:/docker-entrypoint-initdb.d
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - db_network

  # MySQL データベース
  mysql:
    image: mysql:8.0
    container_name: accounting-system-mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root}
      MYSQL_DATABASE: ${MYSQL_DATABASE:-accounting_system}
      MYSQL_USER: ${MYSQL_USER:-user}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD:-password}
      TZ: 'Asia/Tokyo'
    command: >
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
    ports:
      - "${MYSQL_PORT:-3306}:3306"
    volumes:
      - mysql_data:/var/lib/mysql
      - ./ops/docker/mysql/init:/docker-entrypoint-initdb.d
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - db_network

  # Adminer - データベース管理ツール
  adminer:
    image: adminer:latest
    container_name: accounting-system-adminer
    restart: unless-stopped
    ports:
      - "${ADMINER_PORT:-8080}:8080"
    environment:
      ADMINER_DEFAULT_SERVER: ${DB_TYPE:-postgres}
    networks:
      - db_network
    depends_on:
      - postgres
      - mysql

volumes:
  postgres_data:
  mysql_data:

networks:
  db_network:
    driver: bridge

データベースコンテナの起動

# PostgreSQL を起動
$ docker-compose up -d postgres

# または MySQL を起動
$ docker-compose up -d mysql

# すべてのサービスを起動
$ docker-compose up -d

# コンテナの状態確認
$ docker-compose ps

# ログの確認
$ docker-compose logs -f postgres

Flyway によるマイグレーション管理

データベーススキーマのバージョン管理に Flyway を使用しています。

Flyway とは

Flyway は、データベースのマイグレーション(スキーマ変更の管理)を行うツールです。SQL ファイルでマイグレーションを定義し、バージョン管理されたスキーマ変更を自動的に適用できます。

uml diagram

マイグレーションファイルの命名規則

V{バージョン番号}__{説明}.sql

例:
V001__create_account_master.sql
V002__create_journal_tables.sql
V003__create_balance_tables.sql

重要: - バージョン番号は連番で、欠番があってはいけません - 一度適用されたマイグレーションファイルは変更してはいけません - 変更が必要な場合は、新しいマイグレーションファイルを追加します

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

-- V001__create_account_master.sql

-- 勘定科目種別のenum型を作成
DO $$ BEGIN
    CREATE TYPE account_type AS ENUM ('資産', '負債', '純資産', '収益', '費用');
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- 勘定科目マスタテーブルを作成
CREATE TABLE IF NOT EXISTS "勘定科目マスタ" (
    "勘定科目ID" SERIAL PRIMARY KEY,
    "勘定科目コード" VARCHAR(20) UNIQUE NOT NULL,
    "勘定科目名" VARCHAR(100) NOT NULL,
    "勘定科目種別" account_type NOT NULL,
    "残高" DECIMAL(15,2) DEFAULT 0 NOT NULL,
    "作成日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
    "更新日時" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);

-- テーブルコメント
COMMENT ON TABLE "勘定科目マスタ" IS '勘定科目マスタ(財務会計システムの基本となる勘定科目情報)';
COMMENT ON COLUMN "勘定科目マスタ"."勘定科目ID" IS '勘定科目ID(主キー)';
COMMENT ON COLUMN "勘定科目マスタ"."勘定科目コード" IS '勘定科目コード(例:1000, 2000)';
COMMENT ON COLUMN "勘定科目マスタ"."勘定科目名" IS '勘定科目名(例:現金、売掛金)';
COMMENT ON COLUMN "勘定科目マスタ"."勘定科目種別" IS '勘定科目種別(資産、負債、純資産、収益、費用)';
COMMENT ON COLUMN "勘定科目マスタ"."残高" IS '残高';

2.4 テスト環境

Testcontainers によるテスト環境

テスト環境では、Testcontainers を使用してテスト専用のデータベースコンテナを起動し、各テストの独立性を保ちます。

Testcontainers とは

Testcontainers は、Docker コンテナをテストコードから制御できる Java ライブラリです。

uml diagram

主な特徴: - テスト実行時に必要なコンテナを自動起動 - テスト終了後に自動的にコンテナを削除 - CI/CD 環境でも動作(Docker が利用可能な環境であれば OK) - 実際の本番環境に近い状態でテスト可能

テストベースクラスの作成

package com.example.accounting;

import org.flywaydb.core.Flyway;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * Testcontainersを使用したデータベーステストの基底クラス
 */
@Testcontainers
public abstract class TestDatabaseConfig {

    @Container
    protected static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:16-alpine")
            .withDatabaseName("testdb")
            .withUsername("testuser")
            .withPassword("testpass");

    protected static Connection connection;

    @BeforeAll
    static void setUp() throws SQLException {
        connection = DriverManager.getConnection(
                postgres.getJdbcUrl(),
                postgres.getUsername(),
                postgres.getPassword()
        );

        // Flyway マイグレーションを実行
        Flyway flyway = Flyway.configure()
                .dataSource(postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword())
                .locations("classpath:db/migration")
                .load();
        flyway.migrate();
    }

    @AfterAll
    static void tearDown() throws SQLException {
        if (connection != null && !connection.isClosed()) {
            connection.close();
        }
    }
}

データベース接続テストの作成

package com.example.accounting;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.sql.ResultSet;
import java.sql.Statement;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Testcontainers データベース接続テスト
 */
@DisplayName("データベース接続テスト")
class DatabaseConnectionTest extends TestDatabaseConfig {

    @Test
    @DisplayName("データベースに接続できる")
    void testDatabaseConnection() throws Exception {
        try (Statement stmt = connection.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT 1")) {
            assertThat(rs.next()).isTrue();
            assertThat(rs.getInt(1)).isEqualTo(1);
        }
    }

    @Test
    @DisplayName("勘定科目マスタテーブルが作成されている")
    void testTableExists() throws Exception {
        String sql = """
            SELECT EXISTS (
              SELECT FROM information_schema.tables
              WHERE table_schema = 'public'
              AND table_name = '勘定科目マスタ'
            )
            """;

        try (Statement stmt = connection.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {
            assertThat(rs.next()).isTrue();
            assertThat(rs.getBoolean(1)).isTrue();
        }
    }
}

Testcontainers のメリット

メリット 説明
完全な分離 各テスト実行時に新しいコンテナを起動、他のテストとの干渉を防止
自動クリーンアップ テスト終了時にコンテナが自動的に削除
本番同等環境 実際の PostgreSQL コンテナを使用、インメモリ DB では検出できない問題も発見
CI/CD 対応 Docker があればどこでもテストが実行可能

2.5 品質管理ツール

静的解析ツール

コード品質を維持するため、複数の静的解析ツールを使用しています。

uml diagram

ツール 目的
Checkstyle コーディング規約のチェック
PMD バグパターンの検出
SpotBugs 潜在的なバグの検出
FindSecBugs セキュリティ脆弱性の検出
JaCoCo テストカバレッジの計測

Checkstyle 設定

<!-- config/checkstyle/checkstyle.xml -->
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
  "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
  "https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
    <module name="TreeWalker">
        <module name="JavadocMethod"/>
        <module name="JavadocType"/>
        <module name="ConstantName"/>
        <module name="LocalVariableName"/>
        <module name="MethodName"/>
        <module name="PackageName"/>
        <module name="TypeName"/>
    </module>
</module>

2.6 CI/CD パイプライン

GitHub Actions の構成

GitHub Actions を使用して CI/CD を実現しています。

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

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Set up JDK 25
      uses: actions/setup-java@v4
      with:
        java-version: '25'
        distribution: 'temurin'

    - name: Setup Gradle
      uses: gradle/gradle-build-action@v3

    - name: Build with Gradle
      run: ./gradlew build

    - name: Run tests
      run: ./gradlew test

    - name: Run static analysis
      run: ./gradlew check

    - name: Generate coverage report
      run: ./gradlew jacocoTestReport

    - name: Upload coverage report
      uses: actions/upload-artifact@v4
      with:
        name: coverage-report
        path: build/reports/jacoco/test/html

uml diagram

テストピラミッド

uml diagram

テスト種別 ツール 対象
単体テスト JUnit 5, AssertJ ドメインモデル、サービス
統合テスト Testcontainers リポジトリ、データベース
受け入れテスト Cucumber ユースケースシナリオ

まとめ

本章では、開発環境の構築について解説しました。

  • 三種の神器: バージョン管理(Git)、テスティング(JUnit 5)、自動化(Gradle)
  • バックエンド: Java 25 + Spring Boot + MyBatis + Gradle
  • データベース: PostgreSQL / MySQL + Docker Compose + Flyway
  • テスト: Testcontainers による本番同等環境でのテスト
  • 品質管理: Checkstyle、PMD、SpotBugs、JaCoCo
  • CI/CD: GitHub Actions による自動化

次章では、本システムのアーキテクチャ設計について詳しく解説します。