天気どうしたんや。。。
お盆過ぎ辺りから千葉は台風で雨やら風やら洪水やら何やらで、どうも天気がおかしな感じになっており、気温も暑かったり寒かったりと寒暖差が酷くて、体調崩しそうになりますね。
ココ最近朝晩がクソ寒かったのもあり、飼育していたトカゲが◯んでしまい、非常にペットロス。。。
※餓死とかじゃ無かったのが、唯一の救い(空腹で死ぬのとか残酷すぎるもんね
どうも、まったくやる気が無いえ~すけさんですよ。
フレームワークの README って、だいたい嘘くさくない?
「たった数行で〇〇が作れます!」みたいなやつ。
いや作れるのは分かったけど、それって ToDo アプリの話でしょ?ウチの案件、認証あるし権限あるし帳票も刷るし、一覧の検索条件なんか12個とかあるんだが?ってなるわけですよ。
で、自分で hatake(業務システムの画面を YAML の「定義」で作るフレームワーク)を作って公開したはいいものの、本体の中のデモだけだと自分で見ても「それ中の人だから動かせてるんでしょ?」ってなってしまったので、外から普通に使ってる体の見本を別のリポジトリに切り出しました。
本体のデモとの違いは、こっちは配ったものを git の tag で pin して入れてるってところ。中の人の特権で本体のソースを直接いじったりはしてないので、皆さんが使うときと同じ条件になってるはず(多分
フレームワーク自体の話は前の記事とかに散々書いたので、今回は「見本の中に何が入ってて、どこから読めばいいか」だけの話です。
とりあえず30分で触れるやつを作った
見本の案件はガチの業務っぽいのが2本あるんだけど、いきなりそっちを開くと「DB は?API は?権限は?」みたいなのがわんさか出てきて、hatake が何をするものなのかに辿り着く前に心が折れるので、一番ちっちゃい starter-prj を用意しました。
顧客マスタ1画面だけ。要るのは Docker だけで、Node も Flutter も入れなくていいよ。
cd apps/starter-prj
docker compose up
http://localhost:8090 を開くと顧客マスタが出てきて、検索・並べ替え・新規登録・編集・削除が全部動きます。
データはブラウザの中の作り物なので、再読み込みすると最初の10件に戻る(サーバも DB も無い
で、この画面のために書いたのは definitions/app.yaml の60行ぐらいだけ。画面のコード(Vue)は1行も書いてないです。
main.ts も一応あるけど、定義を読んで Vue に渡してるだけの30行なので、画面のコードとは呼ばないことにしてる(キリッ
中身はこんな感じ。
pages:
- type: crud
id: customer_master
title: 顧客マスタ
repository: customerRepository
key: customerCode
search:
filters:
- { field: customerName, label: 顧客名, type: text, operator: contains }
- { field: kind, label: 区分, type: select, operator: equals, optionsOf: customerKind }
table:
pagination: { pageSize: 10 }
rowActions: [edit, delete]
columns:
- { field: customerCode, label: コード, width: 110, sortable: true }
- { field: customerName, label: 顧客名, sortable: true }
- { field: kind, label: 区分, type: badge, width: 90, optionsOf: customerKind }
search が検索欄、table.columns が一覧の列、sortable: true を付けると見出しを押して並べ替えられる、みたいな感じで、読めば大体分かるようにしてあります。
「法人」「個人」みたいな選択肢は vocabularies に1回だけ書いて、列でも検索欄でも入力欄でも名前で指すだけ。あっちこっちに同じ選択肢をコピペして、片方だけ直し忘れるアレをやらないで済むやつです。
書き換えて、再読み込みするだけ
README には時間割みたいに「10〜15分:列を足す」とか書いてあるので、その通りにやってもらえればいいんだけど、ざっくり言うと電話番号の列と入力欄を足してみて、っていう流れです。
- { field: phone, label: 電話番号, width: 140 }
- field: phone
label: 電話番号
type: text
validators:
- { type: pattern, pattern: "^[0-9-]+$", message: 数字とハイフンで入れてください }
これを足して保存して、ブラウザを再読み込み。ビルドし直しとかは要らないです(この1枚を nginx がそのまま配ってるだけなので
一覧に列が増えて、新規登録にも欄が出て、電話番号に字を入れて保存すると書いた文言で止まります。
わざと書き間違えてみて
個人的にはここが一番見てほしいところ。
required: true を requird: true にして再読み込みすると、画面の代わりにどこを何と書き間違えたかが出ます。
YAML とか JSON で設定を書く系のツールって、知らないキーを黙って捨てるやつが多くて、「必須にしたはずなのに必須になってない」みたいなのを画面をポチポチして初めて気づくっていう、クソみたいな時間の溶かし方をするじゃないですか。
アレが嫌だったので、黙って無視はしないようにしてあります。
画面を開かなくても道具でも確かめられるよ。
docker compose run --rm hatake check definitions/app.yaml
直したあとにもう一回 check を通すと、今度は4つの欄で返ってきます。
「書いたのに効いてない」の欄は空っぽで、「読み返し」の欄には定義を日本語にしたやつ、「好み」の欄には『削除』は誰でも押せますよ(消したら戻らないよ)みたいな余計なお世話、で最後の「人が決めること」の欄に「同じ1件を2人が同時に直したらどっちを残すの?」「番号って誰が付けるの?」みたいな問いが出てきます。
この最後の欄がミソで、定義には書けないけど、決めてないまま画面が動いてたら嘘になることを道具の方から聞き返してくるわけです。で、これには人が答える。AI に答えさせちゃダメ(AI はそれっぽく答えちゃうので
ここら辺の話はこっちの記事で長々と書いてます。
最後に AI に頼んでみる
ここまで手で書いてもらったけど、本来この定義は AI が書くもの という建て付けです。
フォルダに .mcp.json を置いてあるので、Claude Code で開けばそのまま hatake の道具(MCP)に繋がります。これも Docker で動くよ。
顧客マスタに「取引開始日」を足して。日付で、任意。一覧にも出して、新しい順に並べられるように。
書いたら hatake_check を通して、人が決めることが出たら私に聞いて。
みたいに頼むと、AI が定義を書いて、自分で hatake_check を通して、問いが出たら聞き返してきます。
人の仕事は聞かれたことに答えるだけ。楽ちん。
業務っぽいやつも2本入ってる
30分サンプルで「ふーん」ってなったら、次はこっち。
1本目の master-maintenance-prj は社内のマスタメンテ(社員・部署・取引先)で、API は Node。権限とか複雑な検索とか一括処理とか CSV とかまで入ってます。
2本目の order-entry-prj は受注入力で、受注を入れて出荷指示まで。こっちは API を Java(Spring Boot) にしてあって、ウィザードとか明細の表とか計算項目とか帳票の印刷まで入ってます。
どっちも docker compose up で動くよ。
で、この2本はただ動くものを置いてるだけじゃなくて、人が AI に何をしたかを全部残してあります。案件の説明、前書き、依頼文、AI から返ってきた問いと、それに何て答えたか。
定義を書くのは AI なんだけど、AI に依頼を出すのは人なので、その人が何を用意して何を決めたかが残ってないと、結局誰も真似できないんですよね。なので実はこっちがこのリポジトリの本体なのかもしれない。
読む順番が分からんって言われそうだったので、docs/AIで作る道筋.md に受注入力を例にして、要件 → 頼み方 → 定義 → 問い → 設計書 → 試験 → 作ったあとの変更、までを1本で辿れるようにしてあります。
結論だけ先に言うと、人の仕事は「案件の説明を書く」「前書き(用語とか決めごと)を育てる」「問いに答える」の3つで、あとは AI と道具がやってます。
あと、設計書(画面一覧・遷移図・画面設計書・API 一覧)とテストのエビデンスも置いてあるんだけど、これ全部生成物です。定義を直したら作り直すだけなので、設計書が実装と食い違ってるっていう、SIer あるあるのアレが起きません。
hatake 無しでも同じものを作って比べてみた
受注入力の方には no-framework/ っていうフォルダがあって、同じものを hatake を使わずに作ったやつが入ってます。
「自作のミニ hatake」と比べたら勝つのは当たり前で意味がないので、無し版は Spring Boot + Bean Validation + Flutter の Widget っていう、AI に素直に頼んだら出てくる普通の形にしてます。DB もテストも同じファイルを共用。
作るときの比較は、コメントと空行を抜いた行数で、画面(Dart)が 399 行 対 2,164 行で5.4倍。ここは差が出た。
でもサーバ側(Java)は 1,148 行 対 1,163 行でほぼ引き分け。認証とか監査とか DB 周りはそもそもフレームワークの外って決めてるので、そりゃ減らないよねっていう。
業務の規則(入力チェックとか検索条件とか列の出し分け)を Java に書いてた分が、定義の YAML に引っ越しただけで、置き場所が変わっただけだったっていうのは正直に書いておきます。最初の見積もりで「AI が書く量が 1/4 になる」とか言っちゃってたので、そこは訂正。
じゃあ何がいいのかって話で、作ったあとに業務の変更を3つ入れて比べたのが 変更に強いか.md。
行数は 31 行 対 56 行で、これも作るときとほぼ同じ1.8倍。行数だけ見たら大差ないです。
差が出たのは同じことを書く場所の数で、「納期は受注日の翌日以降にして」みたいな規則の変更は hatake だと1か所、無し版だと Java と Dart の2か所。「取消を管理者もできるようにして」みたいな権限の変更は1か所 対 3か所。
で、無し版の厄介なところは、どれか1つ直し忘れても普通に動いちゃうってこと。
画面の方だけ直し忘れたら、画面では当日が通るのに保存したら400で怒られる。ボタンの方だけ直し忘れたら、管理者にボタンが出ないけど誰も困らないので誰も気づかない。
hatake の方は1か所しかないので、そもそも忘れる場所がない、っていうのが一番の違いでした。
もちろん負けたところもあって、注文請書の見出しに項目を足す口が帳票の定義に無くて、列で出したら紙の幅が足りなくて「客先注…」って切れて出たりしてます。無し版は見出しに1行足しただけで綺麗に出た(悔しい
こういう「hatake が負けた所」も全部まとめに書いてあるので、導入を検討するなら各案件の docs/まとめ/ を読んでもらうのが早いです。
3本目は業務のふりすらしてない
kitchen-sink-prj は機能網羅用のアプリで、納品物の見本じゃないです。
DSL に書ける(ノード, キー)の組が今 270 組あるんだけど、それを全部一回ずつ書いて動かしてます。
キー名じゃなくて組で数えてるのは、たとえば roles って項目・列・ボタン・メニュー・カード・画面の6か所に書けるので、1か所で試しただけで「動きました」は言えないよね、っていう理由。
1.0 で DSL を凍らせる前に、一度も動かしたことのないキーを凍らせるのが一番高くつく間違いなので、そのためのやつです。実際、作った初回でフレームワーク側の不具合が2件見つかった。
あとこれは同じ定義を Flutter と Vue と React の3つで同時に描いて並べてます。
cd apps/kitchen-sink-prj
docker compose up
で、8083 が Flutter、8084 が Vue、8085 が React。3つとも同じ API・同じ定義を見てて、画面のコードは1行も書いてないです。
案件側が書いたボタンの処理(actions.ts)なんか、Vue 版と React 版で1文字も違わない。diff が通ります。
「Renderer は交換できます」って口で言うのは簡単なので、実物で見せとこうと思って。
版を上げたときの記録も残してる
フレームワークを使う側で一番怖いのって、版を上げたら何が壊れるか分からないことだと思うんですよね。
なので見本の版を上げるたびに docs/移行-0.9.xxから0.9.yy.md みたいな記録を書いてて、何が壊れて何が壊れなかったかを実測で残してます。自分の案件を上げる前の当たり付けに使ってもらえれば。
ちなみに古い版はディレクトリじゃなくて tag で残してます。v0.9.0/ みたいに版ごとにフォルダを切ると、検索したときに古い書き方の方が先に引っかかるので。見本の価値って「今の正しい書き方」なので、古いのは見えないところに置いとく方がいいかなと。
というわけで
とりあえず apps/starter-prj で docker compose up して、requird って書き間違えてみてください。30分もかからないと思います(早い人は10分
もし「こんな案件だとどうなるの?」とか「ここ書けないじゃん」とかあれば、本体の方に Issue 投げてもらえると泣いて喜びます。


