コンテンツにスキップ

記法クックブック — karasu でモデリングするための idiom 集

English · 日本語(このファイル)

docs/spec/syntax.md文法そのもの — すべてのキーワードと 規則です。このクックブックはその欠けていた相棒で、「X をどう表現するか」に答える コンパクトな idiom カタログです。文法からは到達できるが、文法を読んだだけでは 自明でないパターンを対象にします。各エントリはコピーして使える worked snippet です。

意図的に短くしてあるので、プロジェクトをリバースエンジニアリングするとき syntax.md一緒に LLM へ渡せますLLM でリバースする を参照)。人間のオンボーディング資料も兼ねます。生成されたモデルは常に仕様ではなく 地図として扱い、コードと照合してください。

  • LLM と使う: syntax.md の後にこのファイルを貼る。文法だけでなく idiom を 示すので、モデルが独自の形を発明せず karasu 慣用の形を選ぶ。
  • 人間が読む: 必要なパターンを拾い読みする。各エントリは単独で完結している。

各エントリは同じ形をとる: いつ使うか(When)、規則を 1 行で述べたパターン、 最小の .krs、そしてなぜそう書くか(Why)。


1. キー/バリューストア(Redis, etcd)— leaf-less な database

Section titled “1. キー/バリューストア(Redis, etcd)— leaf-less な database”

When — table/collection の構造としてモデル化する意味がないストア: セッション キャッシュ、KV/コンフィグストア、ロックサービスなど。

パターンleaf を持たない database を宣言し、サービスからノード粒度の エッジService --> Store)でつなぎ、具体的なエンジンは物理層store ユニット で名指しする。@kv アノテーションや新しい kv kind を凍結してはいけない — KV ストアは単に分解しない database である。

system Web {
service ApiGateway {
label "API Gateway"
}
database SessionStore [cache] { // leaf-less: 中に `table` を書かない
label "Session store"
}
ApiGateway --> SessionStore "Reads/writes session tokens" // ノード粒度のエッジ
}
deploy "production" {
store "session-kv" {
type "Redis 7" // 具体的なエンジンはここ
realizes SessionStore
}
}

Why

  • resource の dot-path ではなくエッジでノード粒度参照する。 resource <Db>.<Leaf> 参照には宣言済みの leaf が要る。KV ストアには名前を付ける leaf が無いので、 resource SessionStore と書くと未割り当てになる(warning が出て、ノードは自身の usecase の中にしか描かれず、本来ストアが居るべき system ビューには出てこない)。 慣用的な接続は直接の Service --> SessionStore エッジで、 hato 例が leaf-less な D1 / R2 / Tasks を配線しているのと同じ方式。
  • エンジンは物理層に。 「Redis 7」は技術の選択。論理層は技術非依存に保ち、 store { type … } ユニットがどのエンジンが論理ストアを realize するかを記録する。 Redis を etcd に載せ替えても論理モデルは揺れない。
  • 語彙を増やさない。 アノテーション(@…)はライフサイクル標識(deprecated, experimental)であって kind ではない — @kv は意図的に却下。leaf-less な database が既に「興味深い下部構造を持たないストア」を表現している。
  • [cache] は「正本ではない」と言うタグ。 セッションストアが消えると全員が ログアウトするが、業務データは失われない — それがこのタグの判定そのものである。 付けるのは役割であって技術ではない。[kv] が却下なのは @kv と同じ理由 (ストアの役割タグ)。

2. 正本ではないストア — [index] / [cache] / [analytics]

Section titled “2. 正本ではないストア — [index] / [cache] / [analytics]”

When — system of record から派生した検索/ベクトルインデックス (ElasticSearch, OpenSearch, pgvector)で、それ自体が正本ではないもの。

パターンdatabase[index] を付ける。具体的なエンジンは物理層に置く。

database SearchIndex [index] {
table documents
}
// 物理層
deploy "production" {
store "search" {
type "ElasticSearch 8"
realizes SearchIndex
}
}

Why[index] は技術ではなく役割(正本に対する二次インデックス)を表し、 index バッジを付ける。ベクトル DB や ElasticSearch がそれ自体正本なら素の database のまま([index] なし)。1 つの Postgres が正本かつインデックスを兼ねる 場合も付けない。idiom #1 と同じ「役割はタグ・技術は物理層」の規律で、エンジンごとに vector-store / search kind を増やすのを避ける。 ADR-1718 を参照。

[index] には同じ軸の兄弟が 2 つあり、いずれも database だけでなく storage にも 付けられる。[cache] は再構築できるストア(セッションストア、CDN のオリジン キャッシュ。消えて業務データが失われるならそれは正本なのでタグを付けない)、 [analytics] は分析のために取り込んだ DWH / データレイク。タグ無しは正本その ものを指す。ストアの役割タグを参照。

3. 境界の外側にあるもの — [external] タグ

Section titled “3. 境界の外側にあるもの — [external] タグ”

When — システムが依存するが所有はしない、サードパーティ API・マネージド SaaS・ 外部ストア。

パターン — ノード id に [external] を付ける。service と infra kind (database / queue / storage)に適用できる。

system Shop {
service PaymentGateway [external] {
label "Payment gateway"
}
database AnalyticsDB [external] { // マネージドなサードパーティストア
label "Vendor analytics DB"
}
}

Why[external] はノードを破線・グレー系のボーダーで描き、読者が即座に システム境界を見て取れる。外部ストアは shared-infra-fan-in 診断(idiom #4)の 対象からも除外される — ベンダー API を複数サービスで共有するのは想定内であって 設計上の匂いではない。

When — 複数のサービスが同じデータストアを読み書きする。

パターン — ストアは一度だけ宣言し、各サービスの usecase が共有 leaf を resource <Db>.<Leaf> の dot-path で参照する。resolver がこれらを集約して service → database エッジを自動導出する。

database ArticleDB {
table articles
}
service ArticleDelivery {
domain Delivery {
usecase "Fetch an article" {
resource ArticleDB.articles
}
}
}
service Authoring {
domain Publishing {
usecase "Publish an article" {
resource ArticleDB.articles
}
}
}

Why — 2 つのサービスが 1 つの ArticleDB に fan-in すると shared-infra-fan-in の info 診断が出る(決してエラーではない — karasu は結合を可視化するだけで、それを 禁じない)。ここは store に leaf(articles)がモデル化されているので resource dot-path を使う。leaf-less なストアがノード粒度エッジを使う idiom #1 と対比のこと。 診断リファレンス を参照。

5. ドメイン越え・システム越えの参照

Section titled “5. ドメイン越え・システム越えの参照”

When — あるドメインが、別のドメイン・サービス・システムが所有するものに 依存する。

パターン — エッジは参照元のブロック内で宣言する。origin-scope 規則が縛るのは 参照元であって参照先ではないので、ブロックは所有していないものに依存できる。別の システムへは System.Node dot-notation を使う(参照先システムは ghost として描画)。

system Shop {
service BillingService {
domain Billing {
label "Billing"
Billing -> Contract "Created from a contract" // Contract は別サービスにある
}
}
// システム越え: PaymentGateway は別の `system`(別所で import 済み)
OrderService -> PaymentGateway.PaymentService "Request payment"
}

Why — サービス越えのドメインエッジは system ビュー上で暗黙のサービスレベル エッジに自動導出される。高レベルの絵を読みやすく保ちつつ、詳細は書いた場所に残る。 モデル化していないエンドポイントは破棄されず unresolved-edge-endpoint として 報告される。

6. モデルを複数ファイルに分割する

Section titled “6. モデルを複数ファイルに分割する”

When — 1 つの system が単一ファイルには大きすぎる、あるいはチームごとに別々の スライスを所有する。

パターンファセット(サービスごとのファイル + 共有 infra.krs)で分割し、 import で綴じ合わせる。同一 id の system / deploy / organization ブロックは マージ(reopen)される。ファイル全体は import "x.krs"、単一ノードは import { Node } from "x.krs" で取り込む。

// index.krs — エントリポイント
import "infra.krs" // 共有の database / queue / storage
import "reader.krs" // 1 ファイル 1 サービス
import "editor.krs"
system Blog {
label "Blog Platform Demo"
}

Why — モデルにおいてファイルはグルーピング単位ではない: 分割は純粋に オーサリングの都合で、マージ結果は単一ファイルと同一に描画される。共有ストアは 1 つの infra.krs にまとめ、各スライスがそれを import することで、各スライスも 単独で描画できる。 multi-file-system 例を参照。

7. Cloudflare Workers — wrangler.toml から

Section titled “7. Cloudflare Workers — wrangler.toml から”

いつ — 物理層が wrangler.toml にある Cloudflare Workers のサーバーレスアプリ。 compose / k8s ファイルが無いため、binding を手でモデル化すると具体技術(“D1 (SQLite)”)が 論理ラベルに漏れやすい。

パターンkarasu translate --from wrangler に決定的に抽出させる。adapter は 論理 system(engine-neutral な infra + Worker の service + edge)と物理 deploy を出力し、 具体的な Cloudflare 技術は論理ラベルではなく store { type ... } に落ちる:

system Hato {
service Hato { label "hato" }
database DB { } // D1
storage EXPORTS { } // R2
queue TASKS { } // Queues
database SEARCH [index] { } // Vectorize — 派生ベクトルインデックス(イディオム #2)
database CACHE [cache] { } // KV — 再構築できるストア(イディオム #2)
service AI [external] { } // Workers AI — 外部モデルサービス(イディオム #3)
service SessionActor [external] { } // Durable Object — 不透明な stateful actor
Hato --> DB // 所有 infra は -->
Hato -> AI // 外部 / 他 Worker は ->
Hato -> AuthWorker // service binding = Worker→Worker RPC edge
}
deploy "hato" {
function "hato" { runtime "cloudflare-workers"; realizes Hato }
store DBStore { type "Cloudflare D1"; realizes DB }
store SEARCHStore { type "Cloudflare Vectorize"; realizes SEARCH }
}

なぜ — binding→karasu のマッピングは新構文を作らず既存イディオムを再利用する: Vectorize → database [index](イディオム #2 の派生インデックス)、Workers AI / Durable Object → service [external](イディオム #3、この adapter からは不透明)、 service binding → -> の communication edgeKV → database [cache] (イディオム #2 の兄弟の役割 — Worker が再構築できるストア)。 未知の binding 種別は warning を出して skip する — 決して推測しない。実行は karasu translate --from wrangler wrangler.toml > index.krs

8. 横断的関心事(PCI・PII・認証)— 正しい register を選ぶ

Section titled “8. 横断的関心事(PCI・PII・認証)— 正しい register を選ぶ”

When — コンプライアンスのスコープ、データ分類、ポリシー(「この service 群は PCI 対象」「この entity は PII」「この usecase はログイン必須」)を要素に印付けたく なり、タグ([pci])やアノテーション(@requires_auth)を発明したくなったとき。

Pattern — ラベルは 4 つの register に分かれる。外部で定義された集合への所属は タグでも、アノテーションでもない:

言いたいこと Register 構文
その要素がアーキテクチャ上何であるか アーキタイプ 組み込みタグ — [external][index]
どの開発状態にあるか lifecycle 組み込みアノテーション — @deprecated@new
view で peer をどうまとめるか view 内グルーピング boundary
外部定義のどの集合に属するか 所属 facet(#2065 Part B、導入予定)— それまでは description + link

.krs — 今日の PCI / 認証の書き方(prose + link)。ルールの内容はモデル化しない:

system Shop {
service Checkout {
description "PCI DSS 対象 — 評価資料を参照"
link "https://wiki.example.com/pci/scope" "PCI スコープ資料"
domain Ordering {
usecase PlaceOrder {
description "認証必須。誰が呼べるかは IAM ポリシーが定める"
link "https://wiki.example.com/policies/iam" "IAM ポリシー"
}
}
}
}

Why — タグ / アノテーションの語彙はツール所有であり、非 builtin 名は v1.x で tag-not-builtin / annotation-not-builtin の deprecation warning を受け、構文 v2.0 はツール語彙のみを受理する。所属は意味論的にもタグに合わない: database は PCI スコープに入っていようがいまいが database であり、対象 10 要素中 9 要素にしか [pci] が付いていない図は偽の監査保証として読まれる。ルールの内容(role・ プラン・条件)は恒久的に prose + link のまま (ADR-832)で、facet 導入後に第一級に なるのは適用範囲だけである。本当に必要なのが足りないアーキタイプなら、代わりに 組み込みタグの追加要望を出す — tags-annotations.ja.md を参照。

© 2026 Hiroki Kondo · Licensed underApache-2.0

Built with Cloudflare