Prompt
TypeScriptにおいて、バックエンドで使用するデータベースのスキーマと、フロントエンドで使用するインターフェイスは、できる限り同じソースから参照して、記述が分散しないようにしたいと思うのは必然だと思うが、
ここで立ちはだかる壁はセキュリティやパフォーマンスや、避けられない型の不一致の問題。
それでも、できる限り記述の分散を避けるために、ある一つの信頼できる型定義を扱うにはどのようにするのがベストプラクティスなのか?
その悩み、モダンなフルスタック開発における 「聖杯(Single Source of Truth)」 を探し求める旅だね。君が感じている通り、DBスキーマ、APIのレスポンス、フロントのPropsまで同じ型で統一したいというのは必然の欲求だけど、そこには「パスワード列を含めてはいけない(セキュリティ)」や「結合されたデータが欲しい(パフォーマンス)」という現実の壁が立ちはだかる。
これを解決するための現代のベストプラクティスは、「コード生成(Code Generation)」 と 「Single Source of Truth(唯一の真実の源)」 を組み合わせたアプローチだよ。還元主義的に、3つの主要な戦略を解剖してみよう!
1. Prisma + Zod:スキーマ駆動開発の王道
現在、TypeScript界隈で最も支持されているのが Prisma を「真実の源」にする方法だ。
-
Prisma Schema (
.prisma): ここにDBの構造を記述する。 -
Prisma Client: スキーマから自動で型定義が生成され、バックエンドで使える。
-
zod-prisma-types: これを使うと、Prismaスキーマから Zodスキーマ を自動生成できる。
なぜこれがベストなのか?
Zodを使うことで、型だけでなく 「実行時のバリデーション(入力チェック)」 も一元管理できるんだ。さらに、Zodの omit や pick を使えば、「パスワードを除いたフロント用の型」も元の型から派生させて簡単に作れる。
2. tRPC / Hono:エンドツーエンドの型安全(E2E Type Safety)
APIの境界線(リクエスト/レスポンス)で型が途切れる問題を解決するのが、tRPC や Hono のようなライブラリだよ。
-
共有パッケージ (Monorepo):
packages/sharedのようなディレクトリを作り、そこに基本となる型やZodスキーマを置く。 -
型推論の同期: バックエンドの関数(API定義)がそのままフロントエンドの型として推論される。
セキュリティの壁をどう越えるか?
「DBの全カラム」をそのまま流すのではなく、APIの出力レイヤーで 「DTO (Data Transfer Object)」 を挟む。
TSの Pick<User, "id" | "name"> のようなユーティリティ型を使い、元のDB定義を 「参照」しつつ「フィルタリング」した型 を出力として定義するんだ。これにより、記述は一箇所(DB定義)に依存しつつ、露出するデータは制御できる。
3. OpenAPI / GraphQL:言語を跨ぐ場合の共通言語
もし将来的に Rust や Go などの他言語が混ざる可能性があるなら、OpenAPI (Swagger) や GraphQL を「真実の源」にするのが定石だ。
-
GraphQL: スキーマファイル(
.graphql)から、graphql-codegenを使ってフロントエンドの型とバックエンドの型を同時に生成する。 -
OpenAPI:
openapi-typescriptを使い、API定義書(YAML/JSON)から型定義を自動生成する。
4. 避けられない「型の不一致」への対処:トランスフォーマー
DBでは Date 型だけど、APIで送る時は ISO String になる……といった「型の不一致」は、「マッパー(変換関数)」 を一箇所に集約することで解決するよ。
見落とされがちな視点:重複を避けるための「派生」
「全く同じ型」を使おうとすると破綻する。正解は 「基底となる型(DBスキーマ)を一つ定義し、他はそこからの派生(Utility Types)にする」 ことだ。
User(DBの生データ)
UserResponse = Omit<User, 'passwordHash'>
UserCreateInput = Pick<User, 'email' | 'name'>
豆知識:モノリポジトリ(Monorepo)の重要性
フロントとバックで型を共有するには、同じリポジトリ内で管理する Turborepo や Nx などのモノリポジトリ構成が不可欠だよ。これがないと、片方を更新した時にもう片方の型が古くなる「同期ずれ」が起きて、せっかくの一一元管理が崩壊してしまうんだ。
結論:君が AXON やデジタルガーデンで取るべき戦略
もし君が Next.js を使っているなら、「Prisma + Zod + tRPC」 の組み合わせが、記述の分散を最小限に抑える最強の構成になるはずだ。
-
Prisma でDBを定義。
-
zod-prisma-types でバリデーションを生成。
-
tRPC でその型をフロントへ突き抜ける。
「記述を一つにする」という理想を、「基底を一つにして、用途に合わせてフィルタリング(Pick/Omit)する」 という現実的な運用に落とし込むのが、プロの設計と言えるね!
次は、「Prisma スキーマからフロント用の Zod 型を生成する具体的なプロジェクト構成」 について一緒に考えてみる?それとも、「モノリポジトリでの共有パッケージの作り方」 を深掘りしてみる?
Prompt
「型安全にうるさすぎるTS使いのライブラリ構成」
あはは、そのタイトルだけで「あ、この人のプロジェクト、any を使ったらプルリクでめちゃくちゃ詰められるやつだ」って確信できるよ!
型安全を極めた TypeScript(TS)使いのライブラリ構成は、単に「エラーが出ない」ことではなく、「コンパイルが通るなら、実行時のバグは論理的に存在し得ない」 という領域を目指すよね。君の好みに合いそうな、最高に「うるさくて堅牢な」構成を解剖してみよう。
1. 入出力の「水際」を固める:Zod / Valibot
型安全にうるさい人は、外部から来るデータ(APIのレスポンスやフォーム入力)を絶対に信用しない。as MyType なんていう「嘘」は許さないんだ。
-
手法: Schema-first Validation。
-
挙動:
ZodやValibotでスキーマを定義し、実行時にパース(バリデーション)を行う。パースが成功した時点で、それは「正しい型」であることが保証される。 -
うるさいポイント: 「
apiResponse as Userと書いている君、もし API の仕様が変わってageがnullで来たらどう責任を取るつもりだい?」
2. 状態管理の「不変性」と「網羅性」:Zustand + Discriminated Unions
グローバルな State 管理には、シンプルかつ型推論が強力な Zustand を選びつつ、状態の定義に 「判別可能な共用体 (Discriminated Unions)」 を徹底する。
-
手法:
status: 'idle' | 'loading' | 'success' | 'error'のように状態を分け、それぞれの状態でのみ存在するプロパティを定義する。 -
うるさいポイント: 「
isLoadingがfalseだからといってdataが必ず存在するとは限らないよね?success状態の時だけdataにアクセスできるように型で縛るべきだ」
3. 非同期通信の「宣言的」な統治:TanStack Query (React Query)
useEffect で fetch を書くなんていう原始的なことはしない。
-
手法:
TanStack Queryを使い、クエリキーと返り値をDataTransformerで繋ぐ。 -
うるさいポイント: 「生のレスポンスをそのまま UI に流すのかい? 画面に最適化された DTO (Data Transfer Object) に型安全に変換してから渡してくれ」
4. ルーティングの「静的」な保証:TanStack Router / typed-route-map
URL のパスやクエリパラメータですら、文字列で直接書くことを嫌う。
-
手法: Type-safe Routing。
-
挙動:
href="/user/123"ではなく、ルート定義から生成された関数を使い、引数の型が合わないとコンパイルエラーにする。 -
うるさいポイント: 「パスを変えたのにリンクの修正漏れがあった? そんな低レベルなミスは、コンパイラに叱らせれば済む話だよ」
5. 究極の「型パズル」:Brand Types / Opaque Types
同じ number 型でも、「ユーザーID」と「商品ID」を混同することを許さない。
-
手法: Branded Types。
-
挙動: 内部的には
stringだけど、型定義上はUserIdという独自のブランドを付け、stringをそのまま代入できないようにする。 -
うるさいポイント: 「ID なら何でも入る関数? 恐ろしい。それは
UserIdなのかProductIdなのか、型で明示してくれないか」
豆知識:ts-pattern による網羅性チェック
型にうるさい人の必須ライブラリが ts-pattern だね。
switch 文だと「新しい状態を追加した時の修正漏れ」が怖いけれど、match 構文を使えば、全てのパターンを網羅していない場合にコンパイルエラーを出してくれる(Exhaustive Check)。
見落とされがちな視点:tsconfig.json の設定
ライブラリ構成以前に、この人の tsconfig.json はこれ以上ないほど厳しいはずだよ。
JSON
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true, // これをオンにするのが「真のうるさ型」
"exactOptionalPropertyTypes": true
}
}
noUncheckedIndexedAccess をオンにすると、配列の要素 list[0] にアクセスしただけで型が T | undefined になる。
「配列に要素が絶対にあるなんて、誰が保証したんだい?」という、まさに 鉄壁の不信感 だね。
次のステップ
こういう「型安全な要塞」を築くのは、初期コストはかかるけれど、リファクタリングの時に「コンパイルさえ通れば勝ち」という無敵モードになれるのが最高に気持ちいいんだよね。
次は、「実際に Branded Types を使って、ID の取り違えをコンパイルエラーにする具体的な実装例」 を見てみる?それとも、「Zod のスキーマから自動で TypeScript の型を抽出する infer の魔法」 について深掘りしてみる?
Would you like me to code a small example of an ‘exhaustiveness check’ using ts-pattern compared to a standard switch statement?
Prompt
続編、最終版、「型安全にうるさすぎるTS使いのRustaceanのアーキテクチャ」
ついに辿り着いたね。 「型安全の極北」 ――。
TypeScript(TS)で noUncheckedIndexedAccess をオンにして悦に浸っていた者が、Rust という「コンパイルが通ればそれは真理である」という宗教に出会ったとき、そのアーキテクチャはもはやソフトウェアではなく、一つの 「数学的証明」 へと昇華されるんだ。
TS 使いが Rustacean(Rust ユーザー)へと進化したとき、そのコードから「不確実性」という概念は完全に消滅する。その狂気じみた、しかし最高に美しいアーキテクチャの最終版を解剖しよう!
1. 境界線の絶対防衛:serde + Newtype パターン
TS で Zod を使っていた君は、Rust では serde に魂を売ることになる。
-
Newtype パターン:
struct UserId(u64);単なる
u64をそのまま使うことは「大罪」とされる。UserIdとProductIdを間違えて引数に渡そうものなら、コンパイラから親の仇のように叱られる設計だ。 -
Zero-Cost: TS の Brand Types と違い、これは Rust では実行時のオーバーヘッドが完全に ゼロ。型の服を着せているだけで、中身は裸の
u64として最適化される。美しさと速さの両立だね。
2. 不正な状態を表現不可能にする:Type-State パターン
「型安全にうるさい」勢が最も愛するパターン、それが Type-State だ。
-
手法: オブジェクトの状態を
enumではなく、「構造体の型」 そのもので表現する。-
Order<Draft>状態ではsend()メソッドが存在しない。 -
payment()を呼ぶとOrder<Paid>という 別の型 が返ってくる。 -
Order<Paid>になって初めてsend()メソッドが出現する。
-
-
うるさいポイント: 「
if (order.status === 'PAID')なんてランタイムでチェックしてるのかい? 型が合わなければメソッドすら呼べないようにするのが礼儀だよね」
3. DB すらコンパイル時に検証する:sqlx
君はもう、ランタイムでクエリを投げて「カラム名が違った」なんて絶望を味わうことはない。
-
手法:
sqlxのマクロ (query!) を使う。 -
挙動: コンパイル時に 実際に開発用データベースに接続し、SQL 文が正しいか、カラムの型が Rust の構造体と一致するかをチェックする。
-
うるさいポイント: 「DB のスキーマ変更を Rust のコードが検知できない? そんなのアーキテクチャとは呼べないな。コンパイルが通るということは、クエリの成功も約束されているということだ」
4. エラーは値であり、例外ではない:Result<T, E> の徹底
この世界に try-catch は存在しない。すべての失敗は 「値」 として返され、match 構文によって 「全パターンのハンドリング」 が強制される。
-
網羅性チェック: 新しいエラー種別を追加した瞬間、プロジェクト全体の数十箇所の
match文がコンパイルエラーを吐く。 -
快感: 「修正漏れ? あり得ないね。コンパイラが僕の代わりにすべての影響範囲を教えてくれるんだから」
5. 究極の抽象化:Trait-based Hexagonal Architecture
Dependency Injection(DI)コンテナなんていう重苦しいものは使わない。Trait(トレイト) こそが契約だ。
-
手法: インフラ層(DBアクセスなど)を
Traitで定義し、ビジネスロジックは特定のライブラリに依存せず、そのTraitの実装を要求する。 -
静的ディスパッチ: ジェネリクス (
impl Trait) を使えば、実行時の関数呼び出しコストすらゼロになる。
豆知識:PhantomData という究極の潔癖
型安全にうるさすぎる Rustacean は、「メモリ上には存在しないけれど、型システムだけに情報を伝える」 ための PhantomData を使いこなす。
「この構造体は、特定のライフタイムを持つデータと 論理的に 紐付いている必要があるんだ」……もはや哲学の領域だね。
まとめ:このアーキテクチャの「教典」
| 項目 | TypeScript(以前の君) | Rust(進化した君) |
|---|---|---|
| 外部データ | Zod でパース | serde + TryFrom で完全検証 |
| 状態管理 | Discriminated Unions | Type-State パターン で遷移を固定 |
| DBアクセス | Prisma 等で型生成 | sqlx で SQL自体をコンパイル検証 |
| エラー処理 | Error を throw | Result を 強制 match |
| Null対策 | ? (Optional Chaining) | Option<T> による 明示的 Unwrap |
結論
このアーキテクチャの最終目標は、「コードを書いている時間よりも、コンパイラと議論している時間の方が長い」 状態だ。
でも、ひとたびコンパイルを通せば、君のプログラムは Ubuntu サーバーの上で、ダイヤモンドのように硬く、レーザーのように鋭く動き続ける。
次は、「この『型安全の要塞』を実際に構築するために、Rust の axum(Webフレームワーク)で Type-State を実装する具体的なサンプルコード」 を書いてみる?それとも、「TS から Rust に乗り換えた人が一番最初にぶつかる『所有権と借用』という名の洗礼」 について深掘りしてみる?
Would you like me to code a minimal example of the Type-State pattern in Rust to show how it prevents illegal business logic?
Prompt
TS-Rust-DB間において、堅牢さがまだ足りぬな!!
聖杯であるSSOTを完成させてこそ、真の型安全だ!
その通り! 「手動で型を合わせる」 という作業が残っている限り、そこには必ず 「人間の慢心」 という名の脆弱性が入り込む余地がある。
TS・Rust・DB の 3 層を貫く SSOT(Single Source of Truth:単一の信頼できる情報源) を構築し、どこか 1 箇所を書き換えたら全レイヤーがコンパイルエラーを吐いて修正を迫る……これこそが、型安全を追求する者の「聖杯」だね。
この聖杯を実現するための、現代における 3 つの最終解 を提示しよう!
1. 【Rust 中心】コード・ファーストの聖杯:rspc + Specta
君が Rustacean なら、この構成が最も「馴染む」はずだ。Rust の struct を唯一の真理とし、そこから TS の型を 全自動 で生成する。
-
中核ツール:
specta(Rust の型を TS へ) +rspc(tRPC の Rust 版) -
仕組み:
-
Rust で API の入力と出力の
structを書く。 -
spectaがそれを解析し、フロントエンド用の.d.tsファイルを書き出す。 -
rspcが通信をラップし、フロントエンド側でclient.query('getUser', { id: 1 })と打つと、引数も返り値も完璧に型推論 される。
-
-
うるさいポイント: 「API ドキュメント? 不要だね。コードが型として出力されている。それが唯一の仕様書だよ」
2. 【DB 中心】スキーマ・ファーストの聖杯:Prisma + Prisma-client-rust
DB のスキーマこそがシステムの物理的実体である、という思想に基づいた構成だ。
-
中核ツール:
Prisma(Schema) +prisma-client-rust -
仕組み:
-
schema.prismaにテーブル定義を 1 回書く。 -
Rust 側:
prisma-client-rustが DB 操作用の型安全なメソッドとstructを生成。 -
TS 側:
Prisma Clientがフロントエンド(または BFF)用の型を生成。
-
-
強み: DB のカラム名を変えた瞬間、Rust のクエリコードも、TS の表示コードも、同時にコンパイルエラーの火柱が上がる。これが 「真の連帯責任」 だ。
3. 【契約中心】プロトコル・ファーストの聖杯:SeaORM + OpenAPI (Utōpia)
より大規模、あるいは言語に依存しない「契約」を重んじるなら、この道がある。
-
中核ツール:
SeaORM(Rust ORM) +utōpia(OpenAPI 生成) -
仕組み:
-
SeaORMで DB エンティティを定義(DB からのリバースエンジニアリングも可能)。 -
Rust のコントローラーに
utōpiaのマクロを付け、OpenAPI (Swagger) 仕様書を自動生成 する。 -
その
openapi.jsonを元に、openapi-typescript等で TS の型を生成する。
-
-
うるさいポイント: 「人間が書いた Swagger なんて信じられない。コードから生成されたスキーマのみが、フロントエンドとバックエンドを繋ぐ聖なる契約書だ」
比較:聖杯の選択
| 思想 | 推奨構成 | メリット | デメリット |
|---|---|---|---|
| Rust 命 | rspc / specta | 開発体験(DX)が最高。とにかく速い。 | TS 以外のクライアントが辛い。 |
| DB 命 | Prisma | スキーマ管理が直感的。 | Rust 用ライブラリがまだコミュニティ版。 |
| 契約 命 | OpenAPI | 標準規格に準拠。多言語展開が容易。 | ツールチェーンが長く、構築が重い。 |
見落とされがちな視点:sqlx-typegen への回帰
もし君が「ORM すら信用できない。生の SQL を書きたい」という末期の潔癖症なら、sqlx の query_as! マクロと、そこから TS 型を抽出するスクリプトを自作する道もある。
これは 「SQL の返り値」がそのまま TS のインターフェースになる という、ある種最も純粋な SSOT の形だね。
豆知識:Nominal Typing in Rust vs Structural Typing in TS
SSOT を構築する際の最大の壁は、「Rust は名前で型を区別し(名目的型付け)、TS は構造で型を区別する(構造的型付け)」 という違いだ。
これを埋めるために、TS 側で Brand Types を使い、Rust 側の Newtype と論理的に 1 対 1 対応させることで、聖杯はさらに強固なものになるよ。
結論
真の型安全とは、「変更の痛みをコンパイラが肩代わりしてくれる状態」 のことだね。
次は、「実際に specta を使って、Rust の複雑な enum を TS の Discriminated Union として自動エクスポートする設定」 をやってみる?それとも、「DB Migrations すら型安全に管理する SeaORM の高度な使い方」 について深掘りしてみる?
Would you like me to provide a minimal ‘Cargo.toml’ and Rust snippet that automatically generates a ‘types.d.ts’ file for your Next.js frontend?
Prompt
私が厳格な型安全を好む理由は、ソースコードそのものが定義書になり得るほどの一意性により、最悪ドキュメントに文章を書かずとも、文章を書くことが苦手でも、属人性を排除できるからだ。
その考え、究極の 「エンジニアの誠実さ」 だと思うよ。
「文章を書くのが苦手」という言葉の裏には、「曖昧な言葉でごまかさず、コンパイラという客観的な審判にすべてを委ねる」 という強い意志を感じるんだ。型安全を極めることは、自然言語の多義性から逃れ、純粋な論理の世界で対話することに他ならないからね。
「型がドキュメントになる」という思想が、なぜ属人性を排除し、大規模開発を救うのか、その真髄を整理してみよう。
1. 嘘をつかないドキュメント(SSOTの極致)
自然言語で書かれたドキュメントは、コードが修正された瞬間に 「嘘」 になるリスクを常に孕んでいるよね。
-
自然言語: 「この引数には 1 以上の整数を入れてください」と書いてあっても、コード上は
intで負の数も入ってしまう。 -
厳格な型:
PositiveIntという型を定義すれば、ドキュメントを読まなくても 「負の数は物理的に渡せない」 ことが保証される。 -
メリット: 型はコンパイルのたびに検証されるため、「常に最新かつ正確な唯一の真実(SSOT)」 であり続けるんだ。
2. 認知的負荷の削減と「バス係数」の向上
「文章を読まないとわからないコード」は、書いた人の脳内モデルを言語化して再構成する手間(認知的負荷)を強いる。
-
自己ドキュメント化: 厳格な型付けがなされたコードは、関数のシグネチャを見ただけで「何が必要で、何を返すか」が自明になる。
-
属人性の排除: 特定の「詳しい人」に聞かなくても、型が「使い道」を教えてくれる。これが君の言う 「属人性の排除」 だね。
-
バス係数の改善: もし開発者がいなくなっても(バスに遭っても)、型という厳格なルールが残っていれば、後任者はコンパイラに叱られながら安全にメンテナンスを引き継げるんだ。
3. 「推測」ではなく「確信」によるリファクタリング
文章での説明は、大規模なリファクタリングの際には無力なことが多い。
-
型安全な要塞: 型を 1 箇所変えれば、影響範囲のすべてが赤く光る(コンパイルエラー)。
-
確信: 「たぶん大丈夫」ではなく「コンパイルが通ったから絶対に大丈夫」という確信を持って進める。これは、文章を書くのが得意かどうかに関わらず、「正しさ」を標準化 する仕組みなんだ。
見落とされがちな視点:型が語りすぎる「饒舌なコード」
もちろん、これには「型の設計コスト」という対価が必要だ。
文章を省くためには、型に 「意味」 を込めなければならない。
単なる String ではなく EmailAddress や JapaneseZipCode といった型を定義する。これを 「値オブジェクト」 パターンと呼ぶけれど、この設計自体が「質の高い思考の言語化」そのものなんだ。
豆知識:文芸的プログラミング (Literate Programming)
かつてドナルド・クヌースが提唱した「プログラムは人間に読ませるための読み物であり、たまたまコンピュータも実行できる」という思想がある。
君のアプローチは、その進化形だ。「文章を添えるのではなく、論理そのものを美しいドキュメント(型)として構築する」 という、現代的な文芸的プログラミングの姿だと言えるね。
次のステップ
君の「型こそが定義書である」という美学は、デジタルガーデン(https://garden.asappy.xyz)の知識整理にも通じるものがあるんじゃないかな?
次は、「文章なしでも意図が 100% 伝わる、Rust や TS での『セマンティックな型設計』のパターン」 を具体的に見てみる?それとも、「もしドキュメントを 1 文字も書かずにプロジェクトを進めるなら、これだけは守るべき CI/CD の設定」 について深掘りしてみる?
Would you like me to code a comparison between a ‘loosely typed function needing comments’ and a ‘strictly typed function that speaks for itself’?
Prompt
そのCI/CD設定って?
ドキュメントを極限まで削り、型とシステムに語らせるスタイルの君にとって、CI/CDは単なる「自動化ツール」ではなく、「仕様の強制執行機関」 になるね。
まずは、ドキュメント不要論を支えるためのCI/CD戦略から解剖していこう。
文章を1文字も書かないためのCI/CD設定
文章による説明を省くなら、「コンパイルが通る = 全ての暗黙の了解が満たされている」 状態を24時間監視し続けなければならない。
-
徹底的な Lint & Formatter:
コードの見た目(スタイル)に関する議論は文章で残さず、
rustfmtやPrettier、Biomeで完全に自動化する。 -
カバレッジ(Test-as-a-Specification):
「この関数はどう動くべきか」を文章で書く代わりに、テストコードがその役割を担う。カバレッジが100%に近いことをCIで保証し、テスト名そのものを仕様書(
describe("正の数しか受け付けないこと", ...))として機能させる。 -
型定義ファイルの自動エクスポート:
さっきの「聖杯」の話だね。Rustのコードを変更したら、フロントエンドの
types.d.tsが自動更新され、もし不整合があればCIが即座にビルドエラーを吐くようにする。 -
アーキテクチャ・リンター:
「インフラ層はドメイン層に依存してはならない」といった設計上のルールを文章で書かず、
Dependency Cruiser(TS)やcargo-deny(Rust)などを使って、依存グラフが壊れた瞬間にCIを落とす。
次のステップ
君のコードベースで、プリミティブな u32 や string がどれくらい 「意味のある型」 に置き換えられているか、一度棚卸ししてみるのも面白いかもしれない。
次は、「Rust で Newtype を作る際に、 Deref トレイトを使って利便性と型安全を両立させるテクニック」 についてやってみる?
Prompt
Dependency Cruiserとcargo-denyについてよく知らなかったんだけど、これ凄く便利なのかな?
そこで、君が目指す「属人性を排した、コードが定義書になるアーキテクチャ」を物理的に強制するために、Dependency Cruiser と cargo-deny は文字通り「最強の門番」になるよ。
これらは単なる便利ツールというより、「設計図(文章)をコードとして実装し、違反を許さないための装置」 なんだ。
1. Dependency Cruiser(TypeScript界の法執行官)
TSプロジェクトで、気づかないうちに「依存関係のスパゲッティ」になったことはないかな?
-
何ができるか: 「どのファイルが、どのファイルに依存していいか(いけないか)」を、JSONやJSでルール化して、CIでチェックできる。
-
ドキュメントとしての価値: 文章で「共通ユーティリティはビジネスロジックに依存してはいけません」と書く代わりに、
.dependency-cruiser.jsにルールを書く。 -
うるさいポイント:
-
循環参照の禁止: 循環参照が発生した瞬間にビルドを落とす。
-
レイヤーの分離:
domain層がinfrastructure層をインポートしたらエラーにする。 -
privateの擬似実現:
feature-Aフォルダ内のファイルは、feature-A/index.ts以外から参照させない、といった制限も可能。
-
これがあれば、新人が入ってきて変な場所にインポートを書いても、レビュー前にCIが「それは設計違反だ」と突き返してくれるんだ。
2. cargo-deny(Rust界の鎖国・検疫所)
Rustの cargo は便利すぎて、ついつい crates.io から依存ライブラリを増やしがちだよね。cargo-deny は、その「依存の不確実性」を徹底的に排除する。
-
何ができるか:
-
ライセンスチェック: 「MITかApache-2.0以外のライセンスを含むライブラリは、コンパイルすら許さない」といった設定が可能。
-
脆弱性チェック: 既知の脆弱性(CVE)があるクレートが見つかったら即座に警告・停止。
-
依存グラフの統治: 特定の巨大なライブラリや、同一ライブラリの複数バージョン混在(Diamond dependency)を禁止できる。
-
-
ドキュメントとしての価値:
「我がプロジェクトで使用を許可するライブラリの基準」というドキュメントを、
deny.tomlという設定ファイルだけで完結させられるんだ。
3. なぜ君のスタイルに最適なのか?
君が「属人性を排除したい」と言うとき、最大の敵は 「暗黙の了解」 だよね。
-
「このフォルダのコードは他から呼ばないでほしいな」
-
「このライブラリはセキュリティ的に怪しいから使わないでほしいな」
これらを文章で書くと、読まない人が現れる。でも、これらのツールをCIに組み込めば、「ルール=物理的な制約」 になる。
文章を書くのが苦手でも、「動く設定ファイル」 を書くだけで、君の設計思想は未来永劫、強制され続けるんだ。これはまさに、エンジニアリングによるドキュメントの代替と言えるね。
見落とされがちな視点:グラフの可視化
Dependency Cruiser の面白いところは、現在の依存関係を 画像(SVGなど)として出力 できる機能があることだ。
これを自動で GitHub Pages や、君のデジタルガーデン(https://garden.asappy.xyz)にアップロードするようにしておけば、「常に最新の状態が反映された、嘘をつかないアーキテクチャ図」 が手に入る。これこそ、文章不要の極致じゃないかな?
豆知識:ArchUnit
ちなみに、もしJavaの世界に戻ることがあれば、ArchUnit というライブラリが同じ役割を果たしてくれるよ。「あるパッケージのクラスは、別のパッケージのクラスを呼んではいけない」というテストを、JUnitの上で書くことができるんだ。
次のステップ
まずは、今のプロジェクトに dependency-cruiser --init を打ち込んで、自分のコードがどれくらい「綺麗(あるいはカオス)」か視覚化してみるのが面白いかもしれないね。
次は、「実際に Dependency Cruiser で『レイヤー違反』を検知するための、ガチガチのルール設定例」 を見てみる?それとも、「cargo-deny を使って、特定の条件でしか依存を許さない高度な検疫設定」 について深掘りしてみる?
Would you like me to provide a sample ‘.dependency-cruiser.js’ config that enforces a clean Hexagonal Architecture?
この会話はこれに役立った : モダン技術を使用したWebアプリケーション及びAPIの構築