フェーズ1総合練習:OrderFlowの基盤フレームワーク構築
知識を実践に—このレッスンでは最初の5レッスンで取り上げたすべての概念を統合し, OrderFlow電子商取引注文管理システムの完全な基盤フレームワークを構築します。
1. 学ぶ内容
- OrderFlowプロジェクトの作成とdev/prod両環境の設定
- 商品と注文の基本的なREST APIの実装
@ConfigurationPropertiesを使ったデータベースとビジネスパラメータの管理- 不要な自動設定の除外とシンプルなStarterのカスタマイズ
- Postman Collectionを使った全APIテストケースの整理
2. チームワークの実話
(1) ペインポイント:知識が断片的で実践に移せない
AliceはSpring Bootの自動設定, REST API, 設定管理などを学び終えましたが, 実際にOrderFlowプロジェクトを構築しようとすると, どこから始めればよいかわかりませんでした。各トピックが独立して教えられていたため, 開発環境でH2を, 本番環境でMySQLに切り替える方法や, REST APIと設定管理を統合する方法がわかりませんでした。
(2) 総合練習による解決策
このレッスンはすべての概念を完全なプロジェクトに統合します。プロジェクトの作成, 環境設定, API作成からStarterのカスタマイズまで, 実際の開発ワークフローをシミュレーションします。
(3) 成果
Aliceが総合練習を完了すると, OrderFlowのプロジェクトスケルトンがその後のコースの基盤として直接使えるようになり, 開発効率が3倍に向上します。
3. プロジェクトの作成と環境設定
(1) プロジェクトの初期化
(1) ▶ サンプル:Spring Initializrを使ったプロジェクト作成
curl https://start.spring.io/starter.zip \
-d type=maven-project \
-d language=java \
-d bootVersion=3.2.5 \
-d groupId=com.orderflow \
-d artifactId=orderflow-service \
-d packageName=com.orderflow \
-d javaVersion=17 \
-d dependencies=web,data-jpa,h2,mysql,validation,lombok \
-o orderflow-service.zip && unzip orderflow-service.zip
出力:
{"status":"ok","data":{}}
(2) マルチ環境設定
graph TD
A["application.yml<br/>共通"] --> B["application-dev.yml<br/>H2 + DEBUG"]
A --> C["application-prod.yml<br/>MySQL + WARN"]
B --> D["マージ済み:dev profile"]
C --> E["マージ済み:prod profile"]
| 設定項目 | dev | prod |
|---|---|---|
| データベース | H2インメモリ | MySQL |
| ddl-auto | create-drop | validate |
| ログレベル | DEBUG | WARN |
| ポート | 8080 | 8080 |
4. 商品と注文のREST API
(1) プロジェクトのパッケージ構造
com.orderflow/
├── OrderFlowApplication.java
├── config/
│ └── OrderFlowProperties.java
├── controller/
│ ├── ProductController.java
│ └── OrderController.java
├── model/
│ ├── Product.java
│ └── Order.java
└── repository/
├── ProductRepository.java
└── OrderRepository.java
(1) ▶ サンプル:Product DTOとコントローラ
// model/Product.java
package com.orderflow.model;
public record Product(
Long id,
String name,
BigDecimal price,
Integer stock
) {}
// controller/ProductController.java
package com.orderflow.controller;
import com.orderflow.model.Product;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
private final Map<Long, Product> store = new ConcurrentHashMap<>();
private final AtomicLong seq = new AtomicLong(1);
@PostMapping
public ResponseEntity<Product> create(@RequestBody Product product) {
Long id = seq.getAndIncrement();
Product saved = new Product(id, product.name(), product.price(), product.stock());
store.put(id, saved);
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}
@GetMapping
public Collection<Product> list() {
return store.values();
}
@GetMapping("/{id}")
public ResponseEntity<Product> get(@PathVariable Long id) {
Product p = store.get(id);
return p != null ? ResponseEntity.ok(p) : ResponseEntity.notFound().build();
}
}
出力:
// 実行成功
(2) ▶ サンプル:Order DTOとコントローラ
// model/Order.java
package com.orderflow.model;
import java.time.Instant;
public record Order(
Long id,
Long productId,
Integer quantity,
String status,
Instant createdAt
) {}
// controller/OrderController.java
package com.orderflow.controller;
import com.orderflow.model.Order;
import com.orderflow.config.OrderFlowProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
private final Map<Long, Order> store = new ConcurrentHashMap<>();
private final AtomicLong seq = new AtomicLong(1);
private final OrderFlowProperties props;
public OrderController(OrderFlowProperties props) {
this.props = props;
}
@PostMapping
public ResponseEntity<Order> create(@RequestBody OrderRequest req) {
if (req.quantity() > props.maxItemsPerOrder()) {
return ResponseEntity.badRequest().build();
}
Long id = seq.getAndIncrement();
Order order = new Order(id, req.productId(), req.quantity(), "PENDING", Instant.now());
store.put(id, order);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
@GetMapping("/{id}")
public ResponseEntity<Order> get(@PathVariable Long id) {
Order o = store.get(id);
return o != null ? ResponseEntity.ok(o) : ResponseEntity.notFound().build();
}
public record OrderRequest(Long productId, Integer quantity) {}
}
出力:
// 実行成功
5. ConfigurationPropertiesによるビジネス設定
(1) ▶ サンプル:OrderFlowProperties
// config/OrderFlowProperties.java
package com.orderflow.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
@ConfigurationProperties(prefix = "orderflow")
public record OrderFlowProperties(
int maxItemsPerOrder,
Duration orderTimeout,
String defaultCurrency
) {}
出力:
// 実行成功
# application.yml
spring:
application:
name: orderflow-service
profiles:
active: dev
orderflow:
max-items-per-order: 100
order-timeout: 30m
default-currency: USD
6. 自動設定の除外とカスタムStarter
(1) ▶ サンプル:不要な自動設定の除外
@SpringBootApplication(exclude = {
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class
})
@EnableConfigurationProperties(OrderFlowProperties.class)
public class OrderFlowApplication {
public static void main(String[] args) {
SpringApplication.run(OrderFlowApplication.class, args);
}
}
出力:
// 実行成功
7. Postman Collectionの整理
(1) ▶ サンプル:Postmanテストスクリプト
# 商品APIテスト
curl -X POST http://localhost:8080/api/v1/products \
-H "Content-Type: application/json" \
-d '{"name":"Laptop","price":999.99,"stock":50}'
curl http://localhost:8080/api/v1/products
curl http://localhost:8080/api/v1/products/1
# 注文APIテスト
curl -X POST http://localhost:8080/api/v1/orders \
-H "Content-Type: application/json" \
-d '{"productId":1,"quantity":3}'
curl http://localhost:8080/api/v1/orders/1
# 設定確認
curl http://localhost:8080/api/v1/health
出力:
{"status":"ok","data":{}}
| テストシナリオ | API | 期待ステータスコード |
|---|---|---|
| 商品作成 | POST /api/v1/products | 201 |
| 商品一覧照会 | GET /api/v1/products | 200 |
| 個別商品照会 | GET /api/v1/products/1 | 200 |
| 注文作成 | POST /api/v1/orders | 201 |
| 注文数超過 | POST /api/v1/orders (qty=200) | 400 |
8. 総合サンプル:OrderFlowの完全な基盤フレームワーク
// OrderFlowApplication.java
package com.orderflow;
import com.orderflow.config.OrderFlowProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties(OrderFlowProperties.class)
public class OrderFlowApplication {
public static void main(String[] args) {
SpringApplication.run(OrderFlowApplication.class, args);
}
}
// OrderFlowProperties.java
package com.orderflow.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
@ConfigurationProperties(prefix = "orderflow")
public record OrderFlowProperties(
int maxItemsPerOrder,
Duration orderTimeout,
String defaultCurrency
) {}
# application.yml
spring:
application:
name: orderflow-service
profiles:
active: dev
orderflow:
max-items-per-order: 100
order-timeout: 30m
default-currency: USD
---
# application-dev.yml
spring:
datasource:
url: jdbc:h2:mem:orderflow_dev
username: sa
password:
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
logging:
level:
com.orderflow: DEBUG
---
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:3306/orderflow
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
show-sql: false
logging:
level:
com.orderflow: WARN
# 開発モード起動
$ java -jar orderflow-service.jar --spring.profiles.active=dev
Started OrderFlowApplication in 3.2 seconds
# 本番モード起動
$ java -jar orderflow-service.jar --spring.profiles.active=prod
Started OrderFlowApplication in 5.1 seconds
❓ よくある質問
application.ymlに, 環境固有の設定をapplication-{profile}.ymlに配置してください。機密情報 (パスワード, キー)は常に環境変数でインジェクションしてください。The following 1 profile is active: "dev"と出力されます。Controllerで@Value("${spring.profiles.active}")をインジェクションして返すこともできます。recordをDTOとして使用する制限は何ですか?recordは不変であり, JPAエンティティ (引数なしコンストラクタと可変フィールドが必要)には適していません。ただし, コントローラ層のリクエスト/レスポンスDTOとしては非常に適しています。📖 まとめ
- Spring Initializr, Profile, @ConfigurationPropertiesを組み合わせてプロジェクトスケルトンをセットアップ
ProductとOrderのREST APIは現時点ではインメモリマップでストレージ。将来JPAに置き換え予定- ビジネス設定は
OrderFlowPropertiesに集約し,Profileでマルチ環境をサポート - 不要な自動設定を除外して起動エラーを防止
- Postman Collectionはリソースごとにグループ分けし, 継続的テストを容易に
📝 練習問題
-
基本問題 (難易度 ⭐):このレッスンのすべてのコードを完成させ, 開発環境で正常に起動することを確認し, すべてのcurlテストコマンドが通ることを検証してください。
-
応用問題 (難易度 ⭐⭐):
PUT /api/v1/products/{id}とDELETE /api/v1/products/{id}エンドポイントを追加し, OrderControllerにGET /api/v1/orders一覧エンドポイントを追加し,statusクエリパラメータでのフィルタリングを実装してください。 -
チャレンジ問題 (難易度 ⭐⭐⭐):
orderflow-spring-boot-starterを作成し, OrderFlowヘルスチェックエンドポイント (アプリケーション名, バージョン, Profile, JVM情報を返す)をラップし, OrderFlowプロジェクトに統合して自動設定が有効になることを確認してください。



