0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

画面の次は Service ― 詳細設計 Excel から業務ロジックを組み立てる全体設計

0
Posted at

画面の次は Service ― 詳細設計 Excel から業務ロジックを組み立てる全体設計

この記事は生成AIとの対話をもとにまとめたものです。

前編: 設計書を「データ」にして画面を自動生成する ― Spring Boot + Thymeleaf で人が HTML を書かない開発

前編では、画面 HTML・Form・Controller を IR(中間表現)から 決定論的に 生成する話をしました。最後に ポート という窓口だけを残し、「業務ロジックは人+AIが書く」と書きました。前編が未読の方は、少なくとも「ポート」と「失敗の4分類」の節に目を通しておくと、本記事が読みやすくなります。

この記事はその続きです。Service 層をどう設計・製造するか の全体設計をまとめます。ここでも方針は同じです。

  • 詳細設計 Excel の自然言語を、そのまま Java に翻訳しない
  • 一度 IR に正規化し、決定論的に作れる部分と人が書く部分を分ける
  • 生成された Service の骨格も、画面と同様に 直接編集しない

対象は、ウォーターフォールで 詳細設計書(Excel) が納品物として残る、Spring Boot + Thymeleaf の業務システムです。

前編からの位置づけ

前編の完成形は次のとおりでした。

specs/screens/(画面 IR)= 正本
   │
   ├─ ScreenEmitter ─→ HTML / Form / Controller
   └─ Port インターフェース ─→ 実装は人+AI

Phase 2(本記事)では、詳細設計 Excel のうち Controller 1メソッド = 1シート の処理定義を IR に落とし、Controller の中身・Service の骨格・Repository / Dao の配線までを生成します。

specs/screens/     ─ ScreenEmitter ─→ 画面・Controller 骨格・Port IF
specs/flows/       ─ FlowEmitter   ─→ Controller メソッド本体・Service 骨格
specs/data/        ─ DataEmitter   ─→ Entity / Migration / Repository
specs/domain/      ─ (将来)      ─ ルール・ユースケース

詳細設計 Excel ──→ パーサ(移行時のみ・一度きり)──→ 上記 IR 群
                    └──→ DocEmitter ──→ 設計書(納品用)

矢印の向きは前編と同じです。Excel からコードを作るのではなく、IR から設計書とコードの両方を作ります。

用語ミニ辞典(Phase 2 追加分)

前編の用語に加えて、本記事で使う語です。

用語 意味
フロー IR 1つの handler(=Controller の1メソッド)に対応する処理ステップの定義。主に YAML
handler ID 画面 IR の actions[].handler とフロー IR の handler を結ぶ共通キー。例: customerEdit.confirm
step type 詳細設計の1行を表す定型パターン。例: validateForm, call, findById
manual step 定型化できないと人が判断した業務処理の step。手実装クラスへの委譲として生成される
data IR テーブル定義・項目・PK/FK など、データ構造の正本
domain IR 判断表・計算式・業務ルールなど(将来拡張)
Service 骨格 メソッドシグネチャ・依存注入・トランザクション境界が入った生成クラス。業務判断は manual step へ委譲し、中身を直接書かない
段階 A/B/C 自動化の成熟度。A=定型ステップのみ、B=ルール IR 化、C=残りを人+AI

なぜ「詳細設計をそのまま AI に読ませない」のか

画面のときと理由は同じです。

  1. 揺れ — 同じ Excel でも、毎回違う Service が出てくる
  2. 検査のしにくさ — 「この行はバリデーション?業務チェック?単なるメモ?」が機械的に追えない
  3. 責務の混在 — 1シートに Controller の話と Service の話と DB の話が混ざっている

だから、詳細設計の内容を 種類ごとに IR に分けます。

詳細設計の種類 行き先 IR 自動化の見込み
データ定義 テーブル、項目、PK/FK specs/data/ ほぼ 100%
処理定義 フロー、分岐、トランザクション specs/flows/, specs/domain/ 骨格+定型
説明・背景 備考、経緯 docOnly 設計書再生成のみ

自然言語の備考をそのまま if 文にしない。 昇格できないものは docOnly とテスト骨子に残します。これは前編のポートの話の延長です。

接続の要:handler ID

画面と業務処理をつなぐのは、URL でもクラス名でもなく handler ID です。

画面 IR:

actions:
  - id: confirm
    handler: customerEdit.confirm   # ← この文字列がキー
    next: customer-confirm

フロー IR(別ファイル):

handler: customerEdit.confirm       # ← 画面と同じ ID
screenId: customer-edit
transaction: required

この1本の文字列が、次を結びます。

  • どの画面のどのボタンか
  • 詳細設計のどのシート(1メソッド1シート)か
  • 生成される Controller のどのメソッドか
  • Port のどのメソッドか

handler ID の命名規則はプロジェクトで1つに固定する。 例: {画面camelCase}.{アクション}。シート名と handler を一致させるかは未決定ですが、どちらにせよ 対応表は機械が持つ のが前提です。

詳細設計 Excel と IR の対応

多くの現場では、1画面あたり次のようなシート構成です。

A. 画面の処理シート → 画面 IR(前編)

シートの章 画面 IR
画面表示の内容 layoutId, fields, messages
部品の使い方 widgetId
表示制御 visibleWhen 等
イベント処理 actions(handler, next)

B. Controller 1メソッド = 1シート → フロー IR(本記事の中心)

設計書には、だいたい次の順で書かれています。

Controller → Service → Repository → Dao
  バリデーション
  → Service 引数作成
  → Service 呼び出し
  → Repository 呼び出し
  → Dao DB 処理
  → 結果処理
  → コード値名称変換
  → Model 設定・画面遷移

これを step の配列 に落とします。1行が1ステップではなく、意味のまとまり が1ステップです。

handler: customerEdit.confirm
screenId: customer-edit
transaction: required
input:
  form: CustomerEditForm
output:
  model: [customerName, statusName]

steps:
  - layer: controller
    type: validateForm
  - layer: controller
    type: buildServiceInput
    mapping:
      customerId: form.id
      email: form.email
  - layer: service
    type: call
    method: updateCustomer
  - layer: repository
    type: findById
    entity: Customer
    notFound: customer.notFound   # 該当なしは BusinessException(生成器の既定規約)
  - layer: dao
    type: update
    table: M_CUSTOMER
  - layer: service
    type: manual
    id: mailNotification
    doc: メール変更時は確認メールを送信する
  - layer: service
    type: mapCodeToName
    codeTable: M_STATUS
  - layer: controller
    type: setModelAndRedirect
    next: customer-confirm

設計書の「縦の流れ」が、IR では「横のリスト」になります。

入れ子の規則: リストはフラットですが、生成されるコードの入れ子は layer で決まります。servicecall が現れたら、次に controller へ戻るまでの service / repository / dao ステップは、その Service メソッドの 内側 に展開されます。上の例なら findById から mapCodeToName までが updateCustomer の中身になります。

なお、フラットなリストで無理なく表せるのは 分岐の浅い定型フロー までです。多段分岐やループを steps で無理に表現せず、その部分は manual step(後述の段階 C)に回します。

step type カタログ ― 詳細設計の「部品集」

画面に UI カタログがあるように、フローには step type カタログ があります。詳細設計に繰り返し現れる行を、閉じた型にマッピングします。

設計書に書かれがちなこと type layer
バリデーションチェック validateForm controller
Service 引数作成 buildServiceInput controller
Service 呼び出し call service
Repository 検索・更新 findById / save / delete repository
Dao SQL 処理 select / insert / update / delete dao
結果の分岐・加工 mapResult / branch service
コード→名称変換 mapCodeToName service
定型化できない業務処理(人が判断済み) manual service
Model へ値を載せる setModelAttribute controller
画面遷移 setModelAndRedirect / redirect controller

初期案は 10種前後。自社の Excel サンプルで「この行はどの type か」を洗い出し、docs/step-type-catalog.md に固定します。迷いやすい type は layer を選択制にせず固定します(例: mapCodeToName は service)。同じ IR からは必ず同じコードが出る、が原則です。

カタログにない行は unknown として止める。 画面の未知 widget と同じ扱いです。流れは3手です。

  1. パーサ・スキーマ検証が unknown を検出し、CI が止まる
  2. 人が判断する — 定型なら step type を1つ増やす。業務判断なら manual step として IR に明示する
  3. manual step は手実装への委譲として生成される(後述の段階 A)

無理に AI に Java を書かせない、判断は必ず人が挟む、という点は画面と同じです。

バリデーションは二重に書かない

詳細設計と画面 IR の両方に「必須チェック」が書いてあると、生成物が二重になります。役割を分けます。

種類 担当 生成元
単項目(必須・桁数) Bean Validation 画面 IR
相関(A 入力時は B 必須) validateForm ステップ 画面 IR or フロー IR の rules
業務チェック(与信・在庫など) Service 内 フロー IR の call 先メソッド

設計書に「単項目チェック」とあっても、画面 IR に既にあれば フロー IR には載せません。 設計書再生成時に両方から出力すれば、納品物としては矛盾しません。

data IR ― いちばん先に自動化する層

Service を語る前に、足元の データ定義 を固めます。ここは自然言語が少なく、自動化率が高いです。

# specs/data/M_CUSTOMER.yaml(イメージ)
table: M_CUSTOMER
columns:
  - name: CUSTOMER_ID
    type: bigint
    primaryKey: true
  - name: EMAIL
    type: varchar(256)
    nullable: false

DataEmitter が決定論的に出すもの:

  • JPA Entity または MyBatis のマッピング
  • Flyway マイグレーション
  • Repository インターフェース(定型 CRUD)
  • SQL が設計書に明示されていれば @Query または MyBatis XML

JPA か MyBatis かはプロジェクトで1つ決める。 IR 上は設計書どおり Repository → Dao の論理層を保ち、Emitter が物理層に畳みます。詳細設計の用語とソースの用語を一致させるためです。

Service は「全部自動」ではなく「骨格+段階的実装」

業務ロジックのすべてを step の組み合わせだけで表現するのは現実的ではありません。だから 3段階 で進めます。

段階 A:定型だけ生成、残りは委譲スタブ

  • step type カタログに収まる行だけコード化
  • manual step は 手実装用インターフェースへの委譲呼び出し として生成する。生成 Service の中に業務判断は書かない
  • 手実装側の初期スタブは UnsupportedOperationException を投げ、@Disabled テスト骨子が付く
  • コンパイルは通るが、未完成箇所は実行時に落ちる — これはバグではなく、仕様上の「未着手マーカー」
// 生成される Service 骨格のイメージ(生成物・編集禁止)
@Service
@Transactional
public class CustomerEditService {

    private final CustomerRepository customerRepository;
    private final CustomerEditManualSteps manualSteps;   // 手実装への窓口(コンストラクタ注入は省略)

    public void updateCustomer(UpdateCustomerCommand cmd) {
        // generated: findById(notFound: customer.notFound)→ update(定型)
        Customer entity = customerRepository.findById(cmd.getCustomerId())
            .orElseThrow(() -> new BusinessException("customer.notFound"));
        // ... update ...

        // generated: manual step "mailNotification" は手実装へ委譲
        manualSteps.mailNotification(cmd, entity);
    }
}
// 生成される手実装インターフェース(生成物・編集禁止。Javadoc は IR の doc から)
public interface CustomerEditManualSteps {

    /** メール変更時は確認メールを送信する */
    void mailNotification(UpdateCustomerCommand cmd, Customer entity);
}
// domain/ 配下の手実装クラス(初回だけスタブを生成。以後は人+AI が編集してよい唯一の場所)
@Component
public class CustomerEditManualStepsImpl implements CustomerEditManualSteps {

    @Override
    public void mailNotification(UpdateCustomerCommand cmd, Customer entity) {
        // TODO(domain): メール変更時は確認メールを送信する
        throw new UnsupportedOperationException("customerEdit.confirm: step mailNotification");
    }
}
@Test
@Disabled("未実装: フロー IR に未対応の業務ステップ")
@DisplayName("メールアドレス変更時は確認メールを送信する")
void sendConfirmationMailWhenEmailChanged() { }

前編のポートの @Disabled テスト骨子と同じ思想です。設計書の備考が、実装の TODO リストになる。

段階 B:判断表・計算式を rule IR 化

「金額が◯◯以上なら承認必要」のような記述は、将来 specs/domain/ の rule IR に落とし、決定論的に実行します。DSL は小さく始めます(閾値比較、コード値マッチ、加算程度)。

段階 C:残りだけ人+AI が manual step を実装

  • 1 manual step = 手実装クラスの1メソッド。この単位を崩さない
  • 生成された Service 骨格・インターフェースは一切編集しない
  • 手実装は domain/ 配下の manual step 実装クラスのみ

段階 C が画面数と同じペースで増え続けるなら、step type 不足か rule IR 不足の信号です。撤退基準にもします(後述)。

FlowEmitter が出すもの

1つの handler について、おおよそ次が生成されます。

入力 出力
steps(controller 層) Controller の該当メソッド本体
steps(service 層) Service クラスのメソッド骨格
steps(repository/dao 層) 呼び出し配線・トランザクション境界
steps(manual) 手実装インターフェース+初期スタブ(スタブ生成は初回のみ)
transaction @Transactional の付与
input / output 引数マッピング・Model 属性

前編で生成した Controller は メソッドのシェル だけ持ち、Phase 2 で 中身が埋まる イメージです。どちらも生成物であり、手編集しません。

Port との関係 ― 境界は維持する

前編の Port はそのまま残ります。

画面(生成)→ Port IF(生成)→ Port 実装(生成骨格)→ Service(生成骨格)→ manual step 実装(手書き)

フロー IR の call ステップは、Port 実装の内側で Service を呼ぶ形に展開します。Port 実装の骨格も FlowEmitter が生成します(後述の導入順 5)。チーム分業のときは次のように割り切ります。

チーム 触るもの
画面 画面 IR、UI カタログ
配線・骨格 フロー IR、FlowEmitter、DataEmitter
業務 manual step 実装クラス、@Disabled テストを緑にする

生成された Service 骨格は、どのチームも直しません。 業務の変更はフロー IR か manual step 実装に反映し、必要なら再生成します。

失敗時の分類(Phase 2 版)

前編の4分類を、そのまま拡張します。

分類 意味 直す場所
仕様の不備 IR の矛盾・不足 specs/
生成器の不備 IR にあるのに出てこない codegen/
カタログ不足 step type または widget がない カタログ定義
業務の話 ルールが step 化できない domain 手実装(別ループ)

生成された Service に1行足してテストを通す のは、前編と同じく禁止です。金型(FlowEmitter)が直っていません。

移行:画面のときと同じ手順

既存の詳細設計 Excel がある場合も、画面 Phase 1 と同型です。

  1. AI 等で Excel → フロー IR / data IR(一度きり
  2. 逆翻訳チェック: IR → 再生成 Excel → 元と差分比較
  3. 差分なしの handler から承認
  4. 元 Excel 凍結、変更は IR 経由で設計書再生成

画面と業務で 承認の単位 を揃えると運用が楽です。おすすめは 1画面+その画面の handler 群を1セットir-managed に切り替えることです。

リポジトリ構成(Phase 2 追加分)

specs/
  screens/          # 前編どおり
  flows/            # 1 handler = 1 yaml
  data/             # テーブル定義
  domain/           # ルール・ユースケース(将来)
  schema/           # JSON Schema

codegen/
  emitters/
    ScreenEmitter
    FlowEmitter       # 追加
    DataEmitter       # 追加

src/main/java/.../
  web/              # Controller, Form(生成)
  port/             # Port IF + 実装
  domain/           # Service 骨格(生成)+ manual step 実装(手書き領域)
  infrastructure/   # Entity, Repository, Dao(生成中心)

docs/
  step-type-catalog.md   # 自社 Excel 行 → type 対応表
  legacy/                # 凍結 Excel
  generated/             # 再生成設計書

導入順(Phase 2)

いきなり全 handler を IR 化しません。

  1. data IR で主要テーブル1〜2個 → Entity / Migration が通る
  2. handler 対応表 — 画面 IR の handler と詳細設計シートの対応を機械が検証
  3. フロー IR スキーマ + step type カタログ(10種前後)
  4. 垂直スライスinitconfirm の2メソッドだけ FlowEmitter 通過
  5. Port 実装骨格 + @Disabled テスト骨子が揃う
  6. 詳細設計パーサは 移行時のみ。恒常パイプラインにしない

撤退基準

次のどれかに当てはまったら、方式を見直します。

  • 詳細設計の行の 5割超 が step type 化できない
  • 段階 C の手実装が 画面数と同じペース で増え続ける
  • handler ID と Excel シートの対応がチームごとにバラバラで統一できない

やってはいけないこと(Phase 2 版)

やりがちなこと 何が起きるか
詳細設計 Excel を毎回 LLM に読ませて Service 全文生成 揺れ・検査不能・レビュー不能
生成 Service にビジネスロジックを直書き 再生成で消える。業務判断は manual step 実装クラスへ
画面 IR とフロー IR を1ファイルに混ぜる 画面変更が Service 生成に波及し、影響が読めない
バリデーションを画面とフローの両方に書く 二重チェック・矛盾
備考の自然言語をそのまま if 誤読が本番に入る。昇格できないものは docOnly + テスト骨子
Repository と Dao を設計書なしに手書きで増やす data IR を迂回し、DB 定義がずれる

小さく始める:1画面・2 handler で試す

前編の垂直スライス(一覧・編集・確認・完了)に、次を足します。

  1. customer-listinit — 検索条件の表示だけ(Service はほぼ空)
  2. customer-editconfirm — 更新フロー一式(validate → update → 遷移)

ゴールは 「IR に1ステップ足すと、再生成だけでテストの期待が変わる」 ことです。画面 Phase 1 と同じゲート(コンパイル・テスト・生成物編集禁止)を、そのまま Phase 2 に拡張します。

まとめ ― Service 層の全体設計

  • 詳細設計 Excel は data IR / フロー IR / docOnly に分ける。自然言語を直接 Java にしない
  • 画面と業務は handler ID で結ぶ。1メソッド1シートは specs/flows/{handler}.yaml
  • 処理の定型は step type カタログ に閉じる。未知は unknown で止め、カタログを育てる
  • data IR はほぼ全自動Service は骨格を自動+業務は段階 A→B→C
  • 備考の業務ルールは @Disabled テスト骨子 へ。前編のポートと同じ思想
  • 生成された Service 骨格も 直接編集しない。手で書いてよいのは domain/ の manual step 実装クラスだけ。失敗は4分類して上流を直し再生成
  • 移行は画面と同じく 逆翻訳差分承認Excel 凍結

前編が「人が HTML を書かない」話だったなら、Phase 2 は「人が配線コードを書かない。書くのは業務判断だけ」という話です。配線は IR と生成器に任せ、人と AI の精力は テストを緑にすること に集中させる。それが Service 層の全体設計です。


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?