404 Not Found

404 Not Found


nginx

フェーズ1総合練習:OrderFlowの基盤フレームワーク構築

知識を実践に—このレッスンでは最初の5レッスンで取り上げたすべての概念を統合し, OrderFlow電子商取引注文管理システムの完全な基盤フレームワークを構築します。

1. 学ぶ内容


2. チームワークの実話

(1) ペインポイント:知識が断片的で実践に移せない

AliceはSpring Bootの自動設定, REST API, 設定管理などを学び終えましたが, 実際にOrderFlowプロジェクトを構築しようとすると, どこから始めればよいかわかりませんでした。各トピックが独立して教えられていたため, 開発環境でH2を, 本番環境でMySQLに切り替える方法や, REST APIと設定管理を統合する方法がわかりませんでした。

(2) 総合練習による解決策

このレッスンはすべての概念を完全なプロジェクトに統合します。プロジェクトの作成, 環境設定, API作成からStarterのカスタマイズまで, 実際の開発ワークフローをシミュレーションします。

(3) 成果

Aliceが総合練習を完了すると, OrderFlowのプロジェクトスケルトンがその後のコースの基盤として直接使えるようになり, 開発効率が3倍に向上します。


3. プロジェクトの作成と環境設定

(1) プロジェクトの初期化

(1) ▶ サンプル:Spring Initializrを使ったプロジェクト作成

BASH
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

出力:

TEXT
{"status":"ok","data":{}}

(2) マルチ環境設定

100%
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) プロジェクトのパッケージ構造

TEXT
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とコントローラ

JAVA
// 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();
    }
}

出力:

TEXT
// 実行成功

(2) ▶ サンプル:Order DTOとコントローラ

JAVA
// 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) {}
}

出力:

TEXT
// 実行成功

5. ConfigurationPropertiesによるビジネス設定

(1) ▶ サンプル:OrderFlowProperties

JAVA
// 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
) {}

出力:

TEXT
// 実行成功
YAML
# 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) ▶ サンプル:不要な自動設定の除外

JAVA
@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);
    }
}

出力:

TEXT
// 実行成功
⚠️ 注意: このフェーズでは実際のデータベースを使用しないためDataSourceを除外しています。フェーズ2でJPAを導入した後, この除外を削除する必要があります。


7. Postman Collectionの整理

(1) ▶ サンプル:Postmanテストスクリプト

BASH
# 商品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

出力:

TEXT
{"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の完全な基盤フレームワーク

JAVA
// 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
) {}
YAML
# 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
💻 出力:

TEXT
# 開発モード起動
$ 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

❓ よくある質問

Q フェーズ1の練習のプロジェクト構造はその後のレッスンで使えますか?
A はい。このレッスンで構築したフレームワークがフェーズ2〜5の基盤となります。フェーズ2ではJPA, サービス層, 検証などを追加し, プロジェクトを段階的に強化していきます。
Q なぜDataSourceの自動設定を一時的に除外しているのですか?
A このフェーズではインメモリマップでデータストレージをシミュレートしています。フェーズ2でSpring Data JPAを導入すると実際のデータソースが必要になるため, その時点で除外を削除できます。
Q devとprodの設定差異はどう管理すべきですか?
A 共通設定をapplication.ymlに, 環境固有の設定をapplication-{profile}.ymlに配置してください。機密情報 (パスワード, キー)は常に環境変数でインジェクションしてください。
Q Profileが有効になっていることを確認するにはどうすればよいですか?
A 起動ログにThe following 1 profile is active: "dev"と出力されます。Controllerで@Value("${spring.profiles.active}")をインジェクションして返すこともできます。
Q recordをDTOとして使用する制限は何ですか?
A recordは不変であり, JPAエンティティ (引数なしコンストラクタと可変フィールドが必要)には適していません。ただし, コントローラ層のリクエスト/レスポンスDTOとしては非常に適しています。
Q Postman Collectionはどのように整理すべきですか?
A リソースごとにグループ分け (Products, Orders)し, 各リクエストにアサーション (ステータスコード, レスポンスフィールド)を追加し, 環境変数でベースURLを管理してください。

📖 まとめ


📝 練習問題

  1. 基本問題 (難易度 ⭐):このレッスンのすべてのコードを完成させ, 開発環境で正常に起動することを確認し, すべてのcurlテストコマンドが通ることを検証してください。

  2. 応用問題 (難易度 ⭐⭐):PUT /api/v1/products/{id}DELETE /api/v1/products/{id}エンドポイントを追加し, OrderControllerにGET /api/v1/orders一覧エンドポイントを追加し, statusクエリパラメータでのフィルタリングを実装してください。

  3. チャレンジ問題 (難易度 ⭐⭐⭐):orderflow-spring-boot-starterを作成し, OrderFlowヘルスチェックエンドポイント (アプリケーション名, バージョン, Profile, JVM情報を返す)をラップし, OrderFlowプロジェクトに統合して自動設定が有効になることを確認してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%