- Authors

- Name
- Youngju Kim
- @fjvbn20031
目次
- プロジェクト標準フォルダ構造
- レイヤードアーキテクチャvsドメイン中心構造
- application.yml階層構造
- 外部設定の優先順位
- @ConfigurationPropertiesの活用
- マルチモジュールプロジェクト構造
- Kubernetes ConfigMap/Secret連携
- クイズ
1. プロジェクト標準フォルダ構造
Spring BootプロジェクトはMaven/Gradleの標準ディレクトリレイアウトに従います。一貫した構造はチームの協調作業と保守性を大幅に向上させます。
my-project/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/app/
│ │ │ ├── AppApplication.java
│ │ │ ├── config/
│ │ │ │ ├── SecurityConfig.java
│ │ │ │ ├── WebMvcConfig.java
│ │ │ │ └── SwaggerConfig.java
│ │ │ ├── controller/
│ │ │ │ ├── UserController.java
│ │ │ │ └── ProductController.java
│ │ │ ├── service/
│ │ │ │ ├── UserService.java
│ │ │ │ └── UserServiceImpl.java
│ │ │ ├── repository/
│ │ │ │ └── UserRepository.java
│ │ │ ├── domain/
│ │ │ │ ├── User.java
│ │ │ │ └── dto/
│ │ │ │ ├── UserRequest.java
│ │ │ │ └── UserResponse.java
│ │ │ ├── exception/
│ │ │ │ ├── GlobalExceptionHandler.java
│ │ │ │ └── BusinessException.java
│ │ │ ├── security/
│ │ │ │ ├── JwtTokenProvider.java
│ │ │ │ └── JwtAuthFilter.java
│ │ │ └── util/
│ │ │ └── DateUtils.java
│ │ └── resources/
│ │ ├── application.yml
│ │ ├── application-dev.yml
│ │ ├── application-staging.yml
│ │ ├── application-prod.yml
│ │ ├── db/
│ │ │ └── migration/
│ │ │ ├── V1__create_users.sql
│ │ │ └── V2__add_user_index.sql
│ │ ├── static/
│ │ │ ├── css/
│ │ │ └── js/
│ │ └── templates/
│ │ └── email/
│ │ └── welcome.html
└── test/
├── java/
│ └── com/example/app/
└── resources/
├── application-test.yml
└── data.sql
パッケージ別の役割
| パッケージ | 役割 | 含まれるクラス |
|---|---|---|
| config | Spring設定クラス群 | SecurityConfig, WebMvcConfig |
| controller | HTTPリクエスト処理(プレゼンテーション層) | @RestControllerクラス |
| service | ビジネスロジック(ビジネス層) | @Serviceクラス |
| repository | データアクセス(データ層) | @Repository, JpaRepository |
| domain | ドメインモデル | @Entity, DTO, VO |
| exception | 例外処理 | @ControllerAdvice, カスタム例外 |
| security | セキュリティコンポーネント | Filter, Provider, Handler |
| util | 共通ユーティリティ | 静的ヘルパーメソッド |
2. レイヤードアーキテクチャvsドメイン中心構造
従来のレイヤード構造(機能別パッケージ)
com/example/app/
├── controller/
│ ├── UserController.java
│ ├── OrderController.java
│ └── ProductController.java
├── service/
│ ├── UserService.java
│ ├── OrderService.java
│ └── ProductService.java
└── repository/
├── UserRepository.java
├── OrderRepository.java
└── ProductRepository.java
メリット:レイヤー別にファイルを見つけやすい デメリット:ドメイン単位の変更時に複数のパッケージを同時に修正する必要がある
ドメイン中心構造(ドメイン別パッケージ)
com/example/app/
├── user/
│ ├── UserController.java
│ ├── UserService.java
│ ├── UserRepository.java
│ ├── User.java
│ └── UserDto.java
├── order/
│ ├── OrderController.java
│ ├── OrderService.java
│ └── Order.java
└── product/
├── ProductController.java
└── Product.java
メリット:ドメイン別の凝集度が高く、独立デプロイに有利 デメリット:同一レイヤーのファイルを一度に確認しにくい
ヘキサゴナルアーキテクチャのフォルダ構造
com/example/app/
└── user/
├── application/
│ ├── port/
│ │ ├── in/
│ │ │ └── CreateUserUseCase.java
│ │ └── out/
│ │ └── UserRepository.java
│ └── service/
│ └── CreateUserService.java
├── domain/
│ └── User.java
└── adapter/
├── in/
│ └── web/
│ └── UserController.java
└── out/
└── persistence/
└── UserJpaRepository.java
ヘキサゴナルアーキテクチャは外部依存(DB、メッセージブローカーなど)をインターフェースで隔離し、テスト容易性と交換可能性を高めます。
3. application.yml階層構造
基本構造とProfile分離
Spring Boot 2.4以降、単一ファイル内で---区切りを使用して複数のプロファイルを定義できます。
# application.yml - 共通設定
spring:
application:
name: my-service
jackson:
default-property-inclusion: non_null
time-zone: Asia/Tokyo
logging:
level:
root: INFO
com.example: DEBUG
management:
endpoints:
web:
exposure:
include: health,info,metrics
---
# devプロファイル
spring:
config:
activate:
on-profile: dev
datasource:
url: jdbc:postgresql://localhost:5432/devdb
username: devuser
password: devpass
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
logging:
level:
org.hibernate.SQL: DEBUG
---
# stagingプロファイル
spring:
config:
activate:
on-profile: staging
datasource:
url: jdbc:postgresql://staging-db:5432/stagingdb
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
---
# prodプロファイル
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:postgresql://prod-db:5432/proddb
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
show-sql: false
logging:
level:
root: WARN
com.example: INFO
別ファイルへのProfile分離(大規模プロジェクト向け推奨)
resources/
├── application.yml # 共通設定
├── application-dev.yml # 開発環境
├── application-staging.yml # ステージング環境
└── application-prod.yml # 本番環境
プロファイルの有効化方法:
# コマンドラインで有効化
java -jar app.jar --spring.profiles.active=prod
# 環境変数で有効化
export SPRING_PROFILES_ACTIVE=prod
4. 外部設定の優先順位
Spring Bootは多くのソースから設定を読み込みます。優先順位が高いものが低いものを上書きします。
優先順位(高いものが低いものを上書き)
- コマンドライン引数(
--server.port=9090) SPRING_APPLICATION_JSON環境変数(インラインJSON)ServletConfig初期化パラメーターServletContext初期化パラメーター- JNDI属性(
java:comp/env/) - Javaシステムプロパティ(
System.getProperties()) - OS環境変数
- ランダム値プロパティソース(
random.*) - プロファイル別外部アプリケーションファイル(JAR外部の
application-dev.yml) - 外部アプリケーションファイル(JAR外部の
application.yml) - プロファイル別内部アプリケーションファイル(JAR内部の
application-dev.yml) - 内部アプリケーションファイル(JAR内部の
application.yml) @PropertySourceアノテーション- デフォルト値(
SpringApplication.setDefaultProperties)
環境変数による設定のオーバーライド
# プロパティ名のドット(.)はアンダースコア(_)に変換し大文字にする
export SERVER_PORT=9090
export SPRING_DATASOURCE_URL=jdbc:postgresql://prod-db:5432/mydb
export SPRING_DATASOURCE_USERNAME=produser
コマンドライン引数の例
java -jar app.jar \
--server.port=9090 \
--spring.profiles.active=prod \
--spring.datasource.url=jdbc:postgresql://prod-db:5432/mydb
5. @ConfigurationPropertiesの活用
@ConfigurationPropertiesを使うと、関連する設定を型安全なクラスにバインドして管理できます。
application.yml設定
app:
api-key: my-secret-key
timeout: 30s
allowed-hosts:
- api.example.com
- admin.example.com
retry:
max-attempts: 3
delay: 1s
mail:
host: smtp.example.com
port: 587
from: no-reply@example.com
@ConfigurationPropertiesクラス
@ConfigurationProperties(prefix = "app")
@Validated
@Getter
@Setter
public class AppProperties {
@NotNull
private String apiKey;
private Duration timeout = Duration.ofSeconds(30);
private List<String> allowedHosts = new ArrayList<>();
private Retry retry = new Retry();
private Mail mail = new Mail();
@Getter
@Setter
public static class Retry {
private int maxAttempts = 3;
private Duration delay = Duration.ofSeconds(1);
}
@Getter
@Setter
public static class Mail {
private String host;
private int port = 587;
private String from;
}
}
Beanの登録
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
}
または、クラスに直接@Componentを追加:
@Component
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties { ... }
IDEオートコンプリートのサポート(メタデータ生成)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
この依存関係を追加するとコンパイル時にMETA-INF/spring-configuration-metadata.jsonが生成され、IDEでapplication.ymlのオートコンプリートが有効になります。
6. マルチモジュールプロジェクト構造
大規模プロジェクトでは、単一モジュールよりもマルチモジュール構造がコードの再利用と境界の分離に優れています。
Mavenマルチモジュール構造
my-project/ # 親POM
├── pom.xml # 親POM
├── my-api/ # REST APIモジュール
│ ├── pom.xml
│ └── src/main/java/
│ └── com/example/api/
├── my-domain/ # ドメイン/ビジネスロジックモジュール
│ ├── pom.xml
│ └── src/main/java/
│ └── com/example/domain/
├── my-infrastructure/ # DB、外部API連携モジュール
│ ├── pom.xml
│ └── src/main/java/
│ └── com/example/infra/
└── my-common/ # 共通ユーティリティモジュール
├── pom.xml
└── src/main/java/
└── com/example/common/
親pom.xmlの例
<project>
<groupId>com.example</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>my-common</module>
<module>my-domain</module>
<module>my-infrastructure</module>
<module>my-api</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
モジュール間の依存関係管理
my-api → my-domain → my-common
my-api → my-infrastructure → my-domain
循環依存が発生するとコンパイルエラーになります。共通インターフェースを独立したモジュールに抽出して循環を防ぎます。
7. Kubernetes ConfigMap/Secret連携
ConfigMapによる設定注入(環境変数方式)
# kubernetes/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
data:
SPRING_PROFILES_ACTIVE: 'prod'
SERVER_PORT: '8080'
SPRING_DATASOURCE_URL: 'jdbc:postgresql://db-service:5432/mydb'
# kubernetes/deployment.yaml
spec:
containers:
- name: my-app
envFrom:
- configMapRef:
name: my-app-config
- secretRef:
name: my-app-secret
Secretによる機密情報の注入
# Secretの作成
kubectl create secret generic my-app-secret \
--from-literal=SPRING_DATASOURCE_USERNAME=produser \
--from-literal=SPRING_DATASOURCE_PASSWORD=secretpass \
--from-literal=JWT_SECRET=myJwtSecretKey
ボリュームマウント方式(ファイルで注入)
ConfigMapをファイルとしてマウントすると、application.yml全体を外部で管理できます。
# kubernetes/deployment.yaml
spec:
volumes:
- name: config-volume
configMap:
name: my-app-config-files
containers:
- name: my-app
volumeMounts:
- name: config-volume
mountPath: /app/config
env:
- name: SPRING_CONFIG_LOCATION
value: 'classpath:/,file:/app/config/'
spring-cloud-kubernetes連携
Spring Cloud Kubernetesを使用すると、ConfigMap変更時にアプリケーションを再起動することなく設定を動的にリロードできます。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>
spring:
cloud:
kubernetes:
config:
enabled: true
name: my-app-config
namespace: default
reload:
enabled: true
mode: event
period: 15000
8. クイズ
Q1. Spring BootでJAR外部の設定がJAR内部の設定より優先順位が高い理由は?
答え: 本番環境でアプリケーションを再ビルドすることなく設定を変更できるようにするためです。
解説: Spring Bootの外部設定優先順位体系は12-Factor Appの原則に従っています。JAR外部のapplication.yml、環境変数、コマンドライン引数がJAR内部の設定より高い優先順位を持ちます。これにより、同じJARファイルを開発・ステージング・本番環境にそのままデプロイし、設定のみを環境ごとに異なる形で注入することができます。
Q2. @ConfigurationPropertiesと@Validatedを一緒に使う理由は?
答え: アプリケーション起動時に設定値の妥当性を検証し、無効な設定によるランタイムエラーを事前に防ぐためです。
解説: @ValidatedとJSR-303 Bean Validationアノテーション(@NotNull、@Min、@Maxなど)を組み合わせると、Spring Bootアプリケーションの起動時に設定値が有効かどうかを検証します。必須設定の欠如や値の範囲外の場合、即座にBindValidationExceptionが発生し、迅速なフィードバックを提供します。
Q3. マルチモジュールプロジェクトで循環依存を防ぐ方法は?
答え: 共通インターフェースや共有ドメインモデルを独立した共通モジュール(common/sharedモジュール)に分離し、依存関係の方向を単方向に保ちます。
解説: モジュールAがモジュールBに依存し、モジュールBがモジュールAに依存する循環依存が発生するとコンパイルが不可能になります。両モジュールが共通して必要なインターフェースやDTOを別のcommonモジュールに分離すると、AとBが共にcommonに依存する単方向の構造を作ることができます。
Q4. Kubernetesでアプリケーション設定を再デプロイなしに動的にリロードする方法は?
答え: spring-cloud-starter-kubernetes-client-configを使用し、spring.cloud.kubernetes.reload.enabled=trueに設定します。
解説: Spring Cloud Kubernetesは2つのリロードモードを提供します。eventモードはKubernetes APIイベントを検知してConfigMapの変更に即座に反応し、pollingモードは定期的にConfigMapを確認します。@RefreshScopeアノテーションが付いたBeanはConfigMap変更時に自動的に再初期化されます。
Q5. application.ymlでspring.config.activate.on-profileの役割は?
答え: 特定のプロファイルが有効な場合にのみ、その設定ブロックが適用されるよう指定します。
解説: Spring Boot 2.4から、単一のapplication.ymlファイルで---区切りを使って複数のドキュメントブロックに分割できます。各ブロックにspring.config.activate.on-profileを指定すると、指定したプロファイルが有効な場合にのみそのブロックの設定が適用されます。以前のspring.profiles構文は非推奨であり、spring.config.activate.on-profileの使用が推奨されます。