プロジェクトセットアップ手順書¶
本ドキュメントは、新しいサブシステム(アプリケーション)を追加する際の手順を再利用可能な形式でまとめたものです。
概要¶
本プロジェクトでは、以下の 3 つのサブシステムを同一構成で管理しています:
| システム | ポート | ディレクトリ |
|---|---|---|
| SMS(販売管理システム) | 8080 | apps/sms/ |
| FAS(財務会計システム) | 8081 | apps/fas/ |
| PMS(生産管理システム) | 8082 | apps/pms/ |
新しいサブシステムを追加する場合は、既存のシステム(FAS または PMS)をテンプレートとして使用します。
前提条件¶
必須ツール¶
- Java 25(Nix 環境で提供)
- Docker & Docker Compose
- Node.js 22(Gulp タスク用)
- Heroku CLI(デプロイ用)
- GitHub CLI(gh)
推奨ツール¶
- Nix(開発環境の統一管理)
- IntelliJ IDEA または VS Code
Phase 1: アプリケーション基盤の作成¶
1.1 ディレクトリ構造の作成¶
# 新システム名を定義(例: XMS)
export NEW_SYSTEM=xms
export NEW_SYSTEM_UPPER=XMS
export NEW_PORT=8083
export NEW_PG_PORT=5435
# 既存システムをコピー
cp -r apps/pms apps/${NEW_SYSTEM}
# ディレクトリ構造
apps/${NEW_SYSTEM}/
├── backend/ # Spring Boot アプリケーション
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/example/${NEW_SYSTEM}/
│ │ │ └── resources/
│ │ └── test/
│ ├── build.gradle.kts
│ ├── Dockerfile
│ └── config/ # 品質チェック設定
├── docker-compose.yml
├── README.md
└── ops/
├── docker/schemaspy/
└── nix/shell.nix
1.2 パッケージ名・設定の変更¶
build.gradle.kts¶
plugins {
java
jacoco
checkstyle
pmd
id("org.springframework.boot") version "4.0.0"
id("io.spring.dependency-management") version "1.1.7"
id("com.github.spotbugs") version "6.0.27"
}
group = "com.example.${NEW_SYSTEM}"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
// バージョン管理
val mybatisVersion = "4.0.0"
val testcontainersVersion = "1.20.4"
val springdocVersion = "2.8.3"
val thymeleafLayoutDialectVersion = "3.4.0"
val poiVersion = "5.3.0"
val openhtmltopdfVersion = "1.1.36"
dependencies {
// Spring Boot Starters
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-jdbc")
implementation("org.springframework.boot:spring-boot-starter-actuator")
// Thymeleaf
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:$thymeleafLayoutDialectVersion")
// Webjars
implementation("org.webjars:bootstrap:5.3.3")
implementation("org.webjars:webjars-locator-core:0.59")
// 帳票出力
implementation("org.apache.poi:poi-ooxml:$poiVersion")
implementation("io.github.openhtmltopdf:openhtmltopdf-pdfbox:$openhtmltopdfVersion")
// OpenAPI/Swagger
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:$springdocVersion")
// Database
implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:$mybatisVersion")
implementation("org.springframework.boot:spring-boot-starter-flyway")
implementation("org.flywaydb:flyway-database-postgresql")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("org.postgresql:postgresql")
// H2 Database(デモ環境用 - implementation でサーブレット登録可能に)
implementation("com.h2database:h2")
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
// Test
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-webmvc-test")
testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.mybatis.spring.boot:mybatis-spring-boot-starter-test:$mybatisVersion")
testImplementation(platform("org.testcontainers:testcontainers-bom:$testcontainersVersion"))
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:postgresql")
testCompileOnly("org.projectlombok:lombok")
testAnnotationProcessor("org.projectlombok:lombok")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
// JaCoCo
jacoco {
toolVersion = "0.8.14" // Java 25 support
}
// Checkstyle
checkstyle {
toolVersion = "10.20.2"
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
isIgnoreFailures = false
}
// SpotBugs (Java 25 対応: 4.9.7+)
spotbugs {
ignoreFailures = false
excludeFilter = file("${rootDir}/config/spotbugs/exclude.xml")
toolVersion = "4.9.8"
}
// PMD (Java 25 対応: 7.16.0+)
pmd {
toolVersion = "7.16.0"
isConsoleOutput = true
ruleSetFiles = files("${rootDir}/config/pmd/ruleset.xml")
ruleSets = listOf()
isIgnoreFailures = false
}
// カスタムタスク
tasks.register<Test>("tdd") { /* TDD用 */ }
tasks.register("qualityCheck") { /* 品質チェック全実行 */ }
tasks.register("fullCheck") { /* 全テストと品質チェック */ }
application.yml¶
spring:
application:
name: ${NEW_SYSTEM}
datasource:
url: jdbc:postgresql://localhost:${NEW_PG_PORT}/${NEW_SYSTEM}
username: postgres
password: postgres
driver-class-name: org.postgresql.Driver
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true
h2:
console:
enabled: false
server:
port: ${NEW_PORT}
mybatis:
mapper-locations: classpath:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
application-demo.yml¶
spring:
datasource:
url: jdbc:h2:mem:${NEW_SYSTEM}_demo;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=PostgreSQL
driver-class-name: org.h2.Driver
username: sa
password:
flyway:
enabled: false
sql:
init:
mode: always
schema-locations: classpath:db/demo/schema.sql
data-locations: classpath:db/demo/data.sql
h2:
console:
enabled: true
path: /h2-console
settings:
web-allow-others: true
1.3 H2 Console 設定(Spring Boot 4.0 対応)¶
重要: Spring Boot 4.0 では H2 Console サーブレットを明示的に登録する必要があります。
H2ConsoleConfig.java¶
package com.example.${NEW_SYSTEM}.infrastructure.config;
import org.h2.server.web.JakartaWebServlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* H2 Console 設定クラス.
* Spring Boot 4.0 で H2 Console サーブレットを明示的に登録する.
*/
@Configuration
@ConditionalOnProperty(name = "spring.h2.console.enabled", havingValue = "true")
public class H2ConsoleConfig {
/**
* H2 Console サーブレットを登録.
*
* @return ServletRegistrationBean
*/
@Bean
public ServletRegistrationBean<JakartaWebServlet> h2ConsoleServlet() {
ServletRegistrationBean<JakartaWebServlet> registrationBean =
new ServletRegistrationBean<>(new JakartaWebServlet());
registrationBean.addUrlMappings("/h2-console/*");
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
}
注意: この設定を有効にするには、build.gradle.kts で H2 を implementation として追加する必要があります(runtimeOnly ではなく)。
// ❌ これではサーブレット登録不可
runtimeOnly("com.h2database:h2")
// ✅ これならサーブレット登録可能
implementation("com.h2database:h2")
1.4 MyBatis databaseId 設定¶
H2 と PostgreSQL の両方に対応するための設定です。
MyBatisConfig.java¶
package com.example.${NEW_SYSTEM}.infrastructure.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import javax.sql.DataSource;
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
import java.util.Properties;
@Configuration
public class MyBatisConfig {
@Bean
public String databaseId(DataSource dataSource) {
VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
Properties properties = new Properties();
properties.setProperty("PostgreSQL", "postgresql");
properties.setProperty("H2", "h2");
databaseIdProvider.setProperties(properties);
try {
String productName = JdbcUtils.extractDatabaseMetaData(
dataSource, "getDatabaseProductName");
return databaseIdProvider.getDatabaseId(dataSource);
} catch (MetaDataAccessException e) {
throw new RuntimeException("Failed to determine database type", e);
}
}
}
1.5 Dockerfile¶
# ビルドステージ
FROM gradle:jdk25 AS builder
WORKDIR /app
COPY ./ ./
RUN gradle build -x test --no-daemon
# 実行ステージ
FROM eclipse-temurin:25-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
EXPOSE ${NEW_PORT}
# デフォルトは demo プロファイル(Heroku 用)
ENV SPRING_PROFILES_ACTIVE=demo
CMD java -Dserver.port=${PORT:-${NEW_PORT}} -jar app.jar
1.6 docker-compose.yml¶
services:
postgres:
image: postgres:16
container_name: ${NEW_SYSTEM}-postgres
ports:
- "${NEW_PG_PORT}:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: ${NEW_SYSTEM}
volumes:
- ${NEW_SYSTEM}_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: ${NEW_SYSTEM}-backend
ports:
- "${NEW_PORT}:${NEW_PORT}"
environment:
SPRING_PROFILES_ACTIVE: default
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${NEW_SYSTEM}
SPRING_DATASOURCE_USERNAME: postgres
SPRING_DATASOURCE_PASSWORD: postgres
depends_on:
postgres:
condition: service_healthy
schemaspy:
build:
context: ./ops/docker/schemaspy
dockerfile: Dockerfile
container_name: ${NEW_SYSTEM}-schemaspy
volumes:
- ../docs/assets/schemaspy-output/${NEW_SYSTEM}:/output
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_HOST: postgres
DATABASE_PORT: 5432
DATABASE_NAME: ${NEW_SYSTEM}
DATABASE_USER: postgres
DATABASE_PASSWORD: postgres
volumes:
${NEW_SYSTEM}_postgres_data:
Phase 2: 開発インフラの設定¶
2.1 Nix 開発環境¶
apps/${NEW_SYSTEM}/ops/nix/shell.nix¶
{ packages ? import <nixpkgs> {} }:
packages.mkShell {
name = "${NEW_SYSTEM}-dev";
buildInputs = with packages; [
# Java
jdk25
gradle
# Database
postgresql_16
# Tools
docker
docker-compose
];
shellHook = ''
echo "🚀 ${NEW_SYSTEM_UPPER} Development Environment"
echo "Java: $(java -version 2>&1 | head -1)"
echo "Gradle: $(gradle --version | grep Gradle)"
echo ""
echo "Available commands:"
echo " cd apps/${NEW_SYSTEM}/backend && ./gradlew bootRun"
echo " cd apps/${NEW_SYSTEM}/backend && ./gradlew bootRun --args='--spring.profiles.active=demo'"
'';
}
flake.nix に追加¶
devShells = {
# ... 既存の設定 ...
${NEW_SYSTEM} = import ./apps/${NEW_SYSTEM}/ops/nix/shell.nix { inherit packages; };
};
2.2 Gulp タスクの追加¶
ops/scripts/docker-${NEW_SYSTEM}.js¶
既存の docker-pms.js をコピーして、以下を置換:
- pms → ${NEW_SYSTEM}
- 5434 → ${NEW_PG_PORT}
ops/scripts/schemaspy-${NEW_SYSTEM}.js¶
既存の schemaspy-pms.js をコピーして置換。
ops/scripts/heroku-${NEW_SYSTEM}.js¶
既存の heroku-pms.js をコピーして置換。
ops/scripts/test.js に追加¶
const ${NEW_SYSTEM_UPPER}_BACKEND_DIR = path.join(process.cwd(), 'apps', '${NEW_SYSTEM}', 'backend');
gulp.task('test:${NEW_SYSTEM}:backend', (done) => {
try {
console.log('Running ${NEW_SYSTEM_UPPER} Backend tests...');
const cmd = process.platform === 'win32' ? 'gradlew.bat test' : 'sh gradlew test';
execSync(cmd, {
cwd: ${NEW_SYSTEM_UPPER}_BACKEND_DIR,
stdio: 'inherit'
});
done();
} catch (error) {
console.error('${NEW_SYSTEM_UPPER} Backend tests failed');
done(error);
}
});
// test:backend に追加
gulp.task('test:backend', gulp.parallel(
'test:sms:backend',
'test:fas:backend',
'test:pms:backend',
'test:${NEW_SYSTEM}:backend'
));
gulpfile.js に追加¶
import ${NEW_SYSTEM}DockerTasks from './ops/scripts/docker-${NEW_SYSTEM}.js';
import ${NEW_SYSTEM}SchemaspyTasks from './ops/scripts/schemaspy-${NEW_SYSTEM}.js';
import ${NEW_SYSTEM}HerokuTasks from './ops/scripts/heroku-${NEW_SYSTEM}.js';
${NEW_SYSTEM}DockerTasks(gulp);
${NEW_SYSTEM}SchemaspyTasks(gulp);
${NEW_SYSTEM}HerokuTasks(gulp);
// dev タスクに追加
export const dev = gulp.series(
gulp.parallel('mkdocs:serve', 'mkdocs:open'),
gulp.parallel('docker:sms:up', 'docker:fas:up', 'docker:pms:up', 'docker:${NEW_SYSTEM}:up'),
gulp.parallel('schemaspy:sms:generate', 'schemaspy:fas:generate', 'schemaspy:pms:generate', 'schemaspy:${NEW_SYSTEM}:generate')
);
2.3 pre-commit フックの設定¶
.husky/pre-commit に追加¶
npx lint-staged --config .lintstagedrc.${NEW_SYSTEM}.json
.lintstagedrc.${NEW_SYSTEM}.json¶
{
"apps/${NEW_SYSTEM}/backend/src/**/*.java": [
"bash -c 'cd apps/${NEW_SYSTEM}/backend && ./gradlew checkstyleMain checkstyleTest pmdMain spotbugsMain --no-daemon'"
]
}
Phase 3: CI/CD の設定¶
3.1 GitHub Actions CI ワークフロー¶
.github/workflows/ci-${NEW_SYSTEM}.yml¶
name: ${NEW_SYSTEM_UPPER} CI
on:
push:
branches: [ main ]
paths:
- 'apps/${NEW_SYSTEM}/**'
- '.github/workflows/ci-${NEW_SYSTEM}.yml'
- 'flake.nix'
- 'flake.lock'
pull_request:
branches: [ main ]
paths:
- 'apps/${NEW_SYSTEM}/**'
- '.github/workflows/ci-${NEW_SYSTEM}.yml'
- 'flake.nix'
- 'flake.lock'
workflow_dispatch:
jobs:
backend-check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-unstable
extra_nix_config: |
experimental-features = nix-command flakes
- name: Cache Nix store
uses: actions/cache@v4
with:
path: /nix/store
key: nix-${{ runner.os }}-${{ hashFiles('flake.lock') }}
restore-keys: |
nix-${{ runner.os }}-
- name: Run Quality Check and Test
run: nix develop .#${NEW_SYSTEM} --command bash -c "cd apps/${NEW_SYSTEM}/backend && ./gradlew fullCheck"
- name: Upload Test Report
if: always()
uses: actions/upload-artifact@v4
with:
name: ${NEW_SYSTEM}-backend-test-report
path: apps/${NEW_SYSTEM}/backend/build/reports/tests/test
- name: Upload JaCoCo Report
if: always()
uses: actions/upload-artifact@v4
with:
name: ${NEW_SYSTEM}-backend-jacoco-report
path: apps/${NEW_SYSTEM}/backend/build/reports/jacoco/test/html
3.2 Heroku デプロイワークフロー¶
.github/workflows/deploy-demo-${NEW_SYSTEM}.yml¶
name: Deploy ${NEW_SYSTEM_UPPER} Demo to Heroku
on:
push:
branches: [ main ]
paths:
- 'apps/${NEW_SYSTEM}/backend/**'
- '.github/workflows/deploy-demo-${NEW_SYSTEM}.yml'
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Heroku CLI
run: curl https://cli-assets.heroku.com/install.sh | sh
- name: Login to Heroku Container Registry
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY_${NEW_SYSTEM_UPPER} }}
run: heroku container:login
- name: Build and push
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY_${NEW_SYSTEM_UPPER} }}
run: |
docker build --platform linux/amd64 --provenance=false -t registry.heroku.com/deploy-demo-${NEW_SYSTEM}/web apps/${NEW_SYSTEM}/backend
docker push registry.heroku.com/deploy-demo-${NEW_SYSTEM}/web
- name: Release
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY_${NEW_SYSTEM_UPPER} }}
run: heroku container:release web -a deploy-demo-${NEW_SYSTEM}
Phase 4: Heroku デプロイ¶
4.1 Heroku アプリの作成¶
# Heroku CLI でログイン
heroku login
# アプリ作成
heroku create deploy-demo-${NEW_SYSTEM}
# PostgreSQL アドオン追加(本番環境用、オプション)
# heroku addons:create heroku-postgresql:essential-0 -a deploy-demo-${NEW_SYSTEM}
# Container Registry ログイン
heroku container:login
# イメージをビルドしてプッシュ
docker build --platform linux/amd64 --provenance=false \
-t registry.heroku.com/deploy-demo-${NEW_SYSTEM}/web \
apps/${NEW_SYSTEM}/backend
docker push registry.heroku.com/deploy-demo-${NEW_SYSTEM}/web
# リリース
heroku container:release web -a deploy-demo-${NEW_SYSTEM}
# ログ確認
heroku logs --tail -a deploy-demo-${NEW_SYSTEM}
4.2 GitHub Secrets の設定¶
-
Heroku API Key を取得:
heroku auth:token -
GitHub リポジトリの Settings > Secrets and variables > Actions に追加:
- Name:
HEROKU_API_KEY_${NEW_SYSTEM_UPPER} - Value: 取得した API Key
Phase 5: ドキュメント更新¶
5.1 README.md の更新¶
プロジェクトルートの README.md に以下を追加:
- 進捗表に新システムを追加
- デモリンクを追加
- ディレクトリ構成に追加
- Gulp タスク一覧に追加
- Nix 環境設定に追加
5.2 docs/index.md の更新¶
- [${NEW_SYSTEM_UPPER}デモ](https://deploy-demo-${NEW_SYSTEM}-XXXXX.herokuapp.com/){:target="_blank"}
- [SchemaSpy ER 図(${NEW_SYSTEM_UPPER})](./assets/schemaspy-output/${NEW_SYSTEM}/index.html){:target="_blank"}
5.3 apps/${NEW_SYSTEM}/README.md の作成¶
既存の apps/pms/README.md をテンプレートとして、システム固有の情報に書き換えます。
チェックリスト¶
アプリケーション基盤¶
- ディレクトリ構造の作成
- build.gradle.kts の設定
- application.yml の設定
- application-demo.yml の設定
- H2ConsoleConfig.java の作成
- MyBatisConfig.java の作成
- Dockerfile の作成
- docker-compose.yml の作成
- スキーマファイル(Flyway migration, demo schema/data)
開発インフラ¶
- Nix shell.nix の作成
- flake.nix への追加
- Gulp タスク(docker, schemaspy, heroku)
- gulpfile.js への登録
- test.js への追加
- pre-commit フック設定
- lint-staged 設定
CI/CD¶
- GitHub Actions CI ワークフロー
- GitHub Actions デプロイワークフロー
- GitHub Secrets の設定
Heroku¶
- Heroku アプリの作成
- Container Registry へのプッシュ
- リリース実行
- 動作確認
ドキュメント¶
- README.md の更新
- docs/index.md の更新
- apps/${NEW_SYSTEM}/README.md の作成
トラブルシューティング¶
H2 Console が 404 エラーになる¶
症状: /h2-console にアクセスすると "No static resource h2-console" エラー
原因: Spring Boot 4.0 では H2 Console サーブレットが自動登録されない
解決策:
1. build.gradle.kts で H2 を implementation に変更
2. H2ConsoleConfig.java を作成して JakartaWebServlet を明示的に登録
Heroku デプロイで Docker イメージがプッシュできない¶
症状: docker push が失敗する
解決策:
# platform と provenance オプションを指定
docker build --platform linux/amd64 --provenance=false \
-t registry.heroku.com/deploy-demo-${NEW_SYSTEM}/web \
apps/${NEW_SYSTEM}/backend
GitHub Actions で Nix キャッシュが効かない¶
解決策: flake.lock のハッシュをキーに含める
key: nix-${{ runner.os }}-${{ hashFiles('flake.lock') }}