Vertical Slice Architectureを採用している話
本記事はKAEN Advent Calendar 2025の21日目の記事です。
はじめに
弊社KAENでは複数の事業を開発・運営していますが、そのうち一つのプロダクトのサーバサイドで、TypeScript + Vertical Slice Architectureを採用している件について記事にしてみようと思います。
正確に言うと、自分がJOINした時には既に先人(手伝ってもらっていた優秀な前職の先輩)により設計は固まっていたため、これはその後自分がキャッチアップし、解釈して、今日までそれに乗っかって実装してきた物を振り返るみたいな記事です。
尚、具体的なプロダクト名や概念は伏せて書いていますので、分かりづらかったら申し訳無いです...🙇♂️
Vertical Slice Architectureとは
あまり聞き慣れない概念ですが、ぐぐってみると、既にいくつかの記事が存在していますので、参照してみてください。イメージ掴めるかと思います。
- Vertical Slice Architecture - Jimmy Bogard
- Vertical Slice Architecture - Milan Jovanović
- Vertical Slice Architecture: The Best Ways to Structure Your Project - Anton Martyniuk
- Vertical Slice Architectureについて - Zenn
名前が長いので、以下ではVSAと省略させて頂きます。
レイヤードアーキテクチャの課題
さて上記の記事などで主張されている通り、VSAは所謂レイヤードアーキテクチャについてのペインに応える形で登場しています。
Clean ArchitectureやN-Tierアーキテクチャといったレイヤードアーキテクチャでは、技術的な関心事でコードを分割します。
app/
├── controllers/
├── services/
├── repositories/
├── entities/
├ ...
この構成の問題点は、1つの機能を実装するために複数のディレクトリを行き来する必要があることです。
全くの新機能追加時なら問題になりませんが、機能変更時や処理フローのキャッチアップ時には認知コストが高い問題があると思います。
また、レイヤー間の依存関係ルールが厳密な事もあり、それに対応するためのコードもまた複雑性を上げているように思います。
VSAの発想:機能で分ける
VSAは、技術レイヤーではなく、ユースケース(機能)単位でコードを組織化します。この1単位をスライスと呼んでいます。
features/
├── create-xxxx/
│ ├── handler.ts
│ ├── request.ts
│ └── response.ts
└── update-xxxx-status/
├── handler.ts
├── request.ts
└── response.ts
VSAの原則はめっちゃ単純です: スライス間の結合を最小化し、スライス内の結合を最大化する
ある機能に関するコードはすべて同じフォルダにある。別の機能を変更しても、このフォルダには影響しない。
というのが目指す姿かと思います。
ただ、VSAの考え方としては上記の方針くらいの物で、具体的な実装は様々という状態のようです。
弊社での実装の形を少し紹介したいと思います。
弊社での実装と評価
①よくあるVSAの構成
VSA解説記事で、以下のような構成を目にしました👇️
app/
├── entities/ ← ドメインモデルを共有
│ └── xxxx.ts
└── features/
├── create-xxxx/
└── update-xxxx-status/
Entityは複数のスライスで共有し、各スライスはそのEntityを使う形です。
ドメイン知識を共通化するならこの形になるでしょう。自然な感じがします。
②弊社での構成
一方弊社でのVSAはおおむね以下のような構成としています👇️
※usecaseを呼ぶ側は省略しますが、gatewayの実装をDIしつつusecaseを呼び出すIF層的な物があるとお考えください。
src/
├── feature/
│ ├── context-A/
│ │ ├── domain-A/
│ │ │ ├── usecase-A/
│ │ │ │ ├── gateway.ts
│ │ │ │ ├── model.ts
│ │ │ │ ├── usecase.test.ts
│ │ │ │ └── usecase.ts
│ │ │ └── usecase-B/
│ │ │ ├── gateway.ts
│ │ │ ├── model.ts
│ │ │ ├── usecase.test.ts
│ │ │ └── usecases.ts
│ │ └── domain-B/
│ │ └── 略
│ │
│ ├── context-B/
│ │ └── 略
│ 略
│
├── infrastructure/
│ ├── error/
│ │ └── 略
│ ├── util/
│ │ └── 略
略 略
- feature配下をコンテキストで分割しています。コンテキスト以下は業務領域で分割、その中にスライス=usecaseを複数、という整理です。
- ドメイン知識とは関係の無い共通コードはinfrastructureとして、どこからも呼べる物としています。
- modelもgateway(DBアクセス)もスライスに含めてしまう選択をしています。
特に3つ目についてが思い切った選択だと思っており、つまり、特定のスライス毎に専用のmodelがあるという構成です。
恐らくドメインモデルはスライスを跨いで共通定義した方が(①の方)、ビジネスロジックのSSOTとして理想形だと思われるでしょう。
それはそれで全く異論無いのですが、弊社(②の方)でこの形から始めているのは、共通のドメインモデルが肥大化=神化するというありがちなデメリットを避けるためです。
この形で機能開発を進めていき、状態遷移ルールや整合性チェックの重複が辛いと感じ始めたら、ボトムアップ的に共通ドメインモデルに 「押し出す」 のが良いと思っています。
開発していて正直どうか
挙げられているメリットの中では、認知コストの低さは特に感じています。
新規機能を実装する際は言わずもがなですが、
複数スライスに渡る影響範囲の広い変更であっても、一つひとつのスライスが小さく読みやすいので、案外時間はかからないです。
また、AIへ改修をお願いする際にも、VSAでは「このディレクトリだけ見ればいい」と指示できるため、相性良いと感じています。
最後に余談として、VSAと相性の良いDB設計手法もあると思うのですが、次回書けたら書こうと思います...
まとめ
- Vertical Slice Architectureは、機能単位でコードを組織化して認知負荷を下げるアプローチ
- ドメイン知識の散らばりに注意する必要がある
この記事が、誰かの参考になれば幸いです。
宣伝
少し前からKAENはエンジニア募集はじめました!
興味をお持ちいただけたら、下記採用サイトよりぜひエントリーしてください。カジュアル面談も大歓迎です!
Discussion