Vertical Slice Architectureとは?機能単位で縦割りする設計と実装例
Vertical Slice Architecture(VSA)は、コントローラ・サービス・リポジトリという技術的な役割ではなく、「注文を取り消す」「請求書を発行する」といった機能単位でコードをまとめる設計手法です。ただし検索でこの言葉にたどり着く人の意図は一つではありません。ゲーム開発の「バーティカルスライス」は出資判断に使うプレイ可能なデモを指し、アジャイルの「垂直分割」はユーザーストーリーの切り方を指します。同じ単語で別の話をしている状態です。この記事では最初に3つの意味を切り分けたうえで、設計手法としてのVSAの定義・C#とGoの実装コード・採用を見送るべき条件までを扱います。
まとめ:Vertical Slice Architectureの要点
先に結論を示します。
- VSAは、1つの機能に関わるUI・ビジネスロジック・データアクセスを1か所へ束ね、機能間の依存を切る構造です。提唱者Jimmy Bogardの原則は「スライス間の結合を最小化し、スライス内の結合を最大化する」。
- 「バーティカルスライス」はゲーム開発・アジャイル・設計手法で指すものが違います。設計手法を指す場合のみVSAです。
- レイヤードやクリーンアーキテクチャとの最大の差は、1機能の変更で触るファイルが1ディレクトリに収まる点です。代わりにスライス間の重複コードを条件付きで許容します。
- .NETでVSAを組む場合、定番だったMediatRが2025年7月2日のv13.0.0以降は相互ライセンス(RPL-1.5)か商用ライセンスかの選択になりました。無償で使えるかは規模と公開義務の受諾で決まります。
- 機能数が少ないアプリ、あるいは複数機能が同一の複雑なドメインルールを共有するシステムでは採用しません。重複の害が利益を上回ります。
以降で、この5点の根拠と実装方法を順に説明します。
バーティカルスライスの3つの意味と混同しやすい場面
検索クエリ「バーティカルスライス」「vertical slice」は、文脈によって別物を指します。ゲーム開発とWeb開発の会話が交わる場面では、設計の話をしているつもりで相手はデモの話をしていた、という取り違えが起こります。まずここを切り分けます。
ゲーム開発:出資判断に使うプレイ可能な1レベル
ゲーム開発でバーティカルスライスと言えば、グラフィック・サウンド・UI・操作感を最終品質まで作り込んだ、遊べる1ステージ分のビルドを指します。全体のごく一部の分量でも、完成後の体験がそのまま判断できる状態にすることが目的です。パブリッシャーへの出資申請やチーム内の方向性確定に使われます。ソースコードの配置とは無関係で、成果物の作り方を指す言葉です。
アジャイル:全レイヤを貫くユーザーストーリー分割
アジャイル開発の垂直分割は、ユーザーストーリーを「DB設計」「API実装」「画面実装」と工程で割らず、「ユーザーが1件検索できる」のように画面からDBまでを貫く単位で割る考え方です。水平分割ではスプリント終了時に動くものが何も無いのに対し、垂直分割なら小さくても動作するソフトウェアが残ります。VSAで作る Features ディレクトリは、この分割単位をそのままディレクトリ名にしたものです。設計と計画の単位が一致するため、チケットとディレクトリが1対1で対応します。
設計手法:機能単位でコードを束ねるVertical Slice Architecture
この記事の主題であるVSAは、2018年にJimmy Bogardが名付けた、ソースコードの配置に関する設計手法です。ゲーム開発の用法とは目的が異なり、アジャイルの垂直分割とは思想を共有しています。日本語では垂直スライスアーキテクチャ、バーティカルスライスアーキテクチャと表記されます。以降はすべてこの意味で使います。
Vertical Slice Architectureの定義と提唱の経緯
VSAは、リクエスト1本を単位として、そのリクエストが必要とするすべての関心事を1か所にまとめる構造です。「注文取消」というスライスの中に、入力の受け取り・検証・業務ルールの判定・データ更新・レスポンス生成が同居します。同じ機能のコードが層をまたいで散らばりません。
Jimmy Bogardが2018年4月19日に公開した記事「Vertical Slice Architecture」が、この名前を定着させました。同記事の中心的な主張は次の一文です。「Minimize coupling between slices, and maximize coupling in a slice(スライス間の結合を最小化し、スライス内の結合を最大化する)」。あわせて「most abstractions melt away(抽象化のほとんどは溶けて消える)」とし、層をまたぐ共通のリポジトリ・サービス・コントローラといった抽象は不要になると述べています。
起源を2018年と書く解説を見かけますが、これは記事化された年です。原型は講演「SOLID Architecture in Slices not Layers」で、NDC Osloでの登壇動画がLos Techiesのブログで2015年7月2日に公開告知され、Øredev 2016でも同じ演題で登壇しています。講演から記事化までおよそ3年あった計算です。なお2018年の記事自体にコード例は掲載されておらず、概念図と議論だけで構成されています。実装の具体は各自が補う必要があります。
レイヤード・クリーンアーキテクチャとの構造比較
VSAの位置づけは、他の構造と並べると輪郭がはっきりします。
| 観点 | レイヤード(N層) | クリーンアーキテクチャ | Vertical Slice Architecture |
|---|---|---|---|
| 分割の基準 | 技術的な役割 | 依存方向とドメイン | 機能(リクエスト) |
| 1機能の変更範囲 | 全層を横断 | 全層+境界の定義 | 1スライス内で完結 |
| 抽象化の量 | 中 | 多 | 少 |
| 重複コード | 抑制 | 抑制 | 条件付きで許容 |
| 影響範囲の把握 | 横断調査が必要 | 横断調査が必要 | ディレクトリ単位 |
| 相性のよい状況 | 要件が安定 | ドメインが複雑 | 機能追加が頻繁 |
クリーンアーキテクチャは依存の向きを内側へ固定し、DBやフレームワークの差し替えに強い構造を作ります。VSAが最適化しているのは差し替え耐性ではなく変更速度です。読み取り専用の一覧APIにまでリポジトリとユースケースの抽象を通す構成が過剰だと感じたなら、その部分にVSAの発想が効きます。依存方向を軸にした設計思想についてはクリーンアーキテクチャとは?4層構造・SOLID原則・依存性逆転を実装目線で解説、外部との境界をポートとアダプタで切る手法はヘキサゴナルアーキテクチャとは?ポートアンドアダプターの仕組みとクリーン・オニオンとの違いで扱っています。
実装例:フォルダ構成とスライス1本のコード
VSAは概念より配置を見たほうが速く理解できます。C#とGoの2言語で、注文取消という1スライスを示します。
フォルダ構成:機能名で切るディレクトリ配置
技術的な役割名(Controllers、Services、Repositories)のディレクトリを作らず、機能名で切ります。
src/
Features/
Orders/
PlaceOrder.cs
CancelOrder.cs
GetOrderDetail.cs
Shipping/
CreateShipment.cs
Shared/
Persistence/AppDbContext.cs
Behaviors/RequestLogging.cs
「注文取消の仕様が変わった」と言われたとき、開くファイルは CancelOrder.cs の1本です。レイヤード構成なら OrdersController・OrderService・OrderRepository・DTO を順に追うことになります。この差が、VSAが変更速度で有利になる理由の実体です。Shared に置くのは、機能に依存しない基盤だけに限定します。
C#(ASP.NET Core Minimal API):エンドポイントと業務ルールの1ファイル集約
リクエスト型・エンドポイント登録・処理本体を1ファイルに置きます。MediatRなどのライブラリは必須ではありません。
// src/Features/Orders/CancelOrder.cs
using Shop.Shared.Persistence;
namespace Shop.Features.Orders;
public sealed record CancelOrderRequest(string Reason);
public static class CancelOrder
{
public static void Map(IEndpointRouteBuilder app) =>
app.MapPost("/orders/{id:guid}/cancel", Handle);
private static async Task<IResult> Handle(
Guid id,
CancelOrderRequest body,
AppDbContext db,
CancellationToken ct)
{
var order = await db.Orders.FindAsync(new object[] { id }, ct);
if (order is null) return Results.NotFound();
if (order.Status == OrderStatus.Shipped)
return Results.Conflict("出荷済みの注文は取り消せません");
order.Status = OrderStatus.Cancelled;
order.CancelReason = body.Reason;
await db.SaveChangesAsync(ct);
return Results.NoContent();
}
}
出荷済みなら取り消せないという業務ルールが、エンドポイントの定義と同じ画面に見えている点がVSAの狙いです。ルールを探してサービス層を辿る手間が消えます。エンティティ Order と列挙型 OrderStatus は Shared 側に置く前提のため、上記のとおり using で参照します。テンプレートから始めたい場合は、MITライセンスの Hona/VerticalSliceArchitecture(GitHubスター126)が参考になります。ただし2024年11月13日以降コミットが止まっているため、最新の.NETバージョンに追随しているかを取り込み前に確認してください。
Go:機能単位パッケージによる同型構造
VSAは.NET専用の手法ではありません。Goでは機能名のパッケージがそのままスライスになります。
// internal/feature/cancelorder/cancelorder.go
package cancelorder
import (
"context"
"encoding/json"
"net/http"
)
const (
StatusShipped = "shipped"
StatusCancelled = "cancelled"
)
type Order struct {
ID string
Status string
CancelReason string
}
type request struct {
Reason string
}
type store interface {
Load(ctx context.Context, id string) (Order, error)
Save(ctx context.Context, o Order) error
}
func Handle(s store) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req request
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
o, err := s.Load(r.Context(), r.PathValue("id"))
if err != nil {
http.Error(w, "not found", http.StatusNotFound)
return
}
if o.Status == StatusShipped {
http.Error(w, "already shipped", http.StatusConflict)
return
}
o.Status = StatusCancelled
o.CancelReason = req.Reason
if err := s.Save(r.Context(), o); err != nil {
http.Error(w, "internal", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}
}
store インターフェースを共通パッケージではなくスライス側に置いている点に注目してください。利用側がインターフェースを定義するGoの慣習と、スライス内で完結させるVSAの原則が一致します。Go 1.22 で標準の net/http がメソッド指定つきルーティングと r.PathValue に対応したため、ルータライブラリを入れずにこの構造を組めます。登録側は mux.Handle(“POST /orders/{id}/cancel”, cancelorder.Handle(s)) の1行です。
スライスをまたぐ共通処理と重複コードの線引き
VSAで最初に詰まるのがここです。配置のルールは単純でも、共通処理をどこに置くかの判断は毎回発生します。
横断的関心事:スライスの外に置く判断基準
認証・ロギング・トランザクション・入力検証は、各スライスへ書き写すと保守不能になります。これらはスライスの中ではなく実行経路に置きます。ASP.NET Coreならミドルウェアまたはエンドポイントフィルタ、Goならハンドラを包むミドルウェア関数です。判断基準は単純で、機能の仕様書に書かれない処理は横断的関心事、書かれる処理はスライス内、と切り分けます。「出荷済みは取り消せない」は仕様書に載るのでスライス内、「監査ログを残す」は載らないので経路側です。
重複を許す条件と、許さない条件
VSAは重複を許容しますが、無条件ではありません。許してよいのは、たまたま今は同じ形をしているだけで、別々の理由で変わりうるコードです。注文一覧と管理画面一覧のレスポンス組み立てが似ていても、片方だけ項目追加されることは普通に起きるため、共通化するとかえって壊れます。
逆に、複数スライスが同一の業務ルールを実装している場合は共通化します。金額計算や与信判定のロジックが3か所にあって常に同時に修正されるなら、それは重複ではなく共有すべきドメインの知識です。判断の目安ははっきりしています。片方だけを変更したくなる場面が想像できるなら重複のまま残し、常に両方を同時に直すことになるなら共通化する。この線引きを曖昧にしたまま「VSAだから重複してよい」と進めると、修正漏れによる不具合が積み上がります。
MediatR前提の.NET実装で2025年に変わったライセンス条件
MediatRを前提にしたVSAの実装例は広く使われていますが、その前提は2025年に変わりました。導入前に確認してください。
MediatRは2025年7月2日にv13.0.0を公開し、同日に商用ライセンス版の提供が発表されました。提供元はJimmy Bogardが設立したLucky Penny Softwareです。v13.0.0以降の選択肢は3つあります。GitHub公開ソースの Reciprocal Public License 1.5(RPL-1.5)を受諾する(公開義務と引き換えに企業規模を問わず無償)、無償のCommunity版を使う、有償の商用ライセンスを買う、のいずれかです。v12.5.0(2025年4月1日公開)以前は従来のApacheライセンスのまま据え置かれており、既存プロジェクトが即座に条件違反になるわけではありません。最新版はv14.2.0(2026年7月2日公開)です。
Community版は次の4条件をすべて満たす必要があります。年間売上(非営利団体は予算)が500万USD未満、外部からの調達額が1,000万USD以下、政府・準政府機関でない、大学など高等教育機関が組織運用ソフトウェアとして使う用途でない。登録は審査なしのセルフサービスですが、ライセンスキーの取得は必要です。売上基準だけを見て無償と判断すると、調達額の条件で外れる場合があります。
新規に.NETでVSAを始めるなら、まずMediatRなしで書いてください。上のC#の例のとおり、Minimal APIの静的メソッド1本でスライスは成立します。MediatRの価値の中心はパイプライン動作(検証・ロギングの共通処理)ですが、これはエンドポイントフィルタでも実現できます。上の3つの選択肢のいずれかに該当し、かつチームがMediatRに習熟している場合に限り採用する、という順序が安全です。バージョンを固定して古いライセンスに留まる運用は、セキュリティ修正を受け取れなくなるため避けてください。
適用判断:向くプロジェクトと見送るべき条件
効果が出る条件:機能追加が続き、担当が分かれるプロジェクト
VSAが効くのは、機能単位の追加・変更が継続し、複数人が別々の機能を並行して触る状況です。担当者ごとに触るディレクトリが分かれるため、コンフリクトが減ります。効果は「直近1か月に追加した機能の変更で何ファイル触ったか」という数字で測れます。既存の大規模モノリスを機能単位で切り出していく移行期にも向きます。切り出しの前段としてモジュール境界を引く考え方はモジュラモノリスとは何か?その基本概念と特徴を解説が参考になります。
見送るべき条件:機能が少ない、またはドメインルールが共有される場合
ここは立場を明確にします。次の場合、VSAは採用しません。
- エンドポイントが十数本程度で今後も増えない小規模アプリ。ディレクトリを機能で割る利益より、構造を説明するコストが上回ります。
- 複数機能が同一の複雑なドメインルールを共有するシステム(保険料計算、在庫引当など)。スライス内で完結させると同じルールが分散し、修正漏れが起きます。この場合はドメインモデルを中心に据えた設計を選びます。
- チームがリファクタリングに不慣れな場合。Bogard自身が原記事で、複雑なロジックをドメインへ押し出す判断が必要になると書いており、VSAは「まず素直に書き、必要になったら共通化する」段階的な改善を前提とします。最初に書いた形を誰も直さない体制では重複が放置されます。
1つ目と2つ目は、部分適用で折り合いをつけられます。参照系だけVSA、更新系はドメインモデル経由という構成は現実的な落としどころです。3つ目は体制側の問題なので、部分適用では解決しません。
モジュラモノリス・CQRS・フロントエンド設計との関係
モジュラモノリス:粒度の違いと併用の可否
モジュラモノリスはデプロイ単位を1つに保ったままモジュール境界を引く手法で、境界の粒度は「注文」「在庫」といった業務ドメイン単位です。VSAが決めるのはその内側、リクエスト単位の配置です。粒度が異なるため、モジュール内をVSAで構成する組み合わせは矛盾しません。将来的にマイクロサービスへ分割する計画があるなら、境界の考え方はマイクロサービスとは?モノリスとの違い・メリット・デメリットと選び方、モジュラモノリスまで整理で確認できます。
CQRS:更新系と参照系での非対称な実装
VSAと組み合わせる頻度が最も高いのがCQRSです。更新系スライスはドメインモデルを経由し、参照系スライスは SELECT id, status, total FROM orders のように必要な列だけを直接取得してレスポンスに詰める、という非対称な実装が許容されます。両者を同じ抽象で扱わないことが、レイヤードでは書けなかった素直なコードにつながります。読み取り用と書き込み用のモデルを分ける発想の詳細はCQRS(コマンドクエリ責務分離)とは?仕組み・Read Model/Write Model・Query Serviceと実装パターンを解説を参照してください。
フロントエンド:Feature-Sliced Designとの対応関係
フロントエンドでVSAに相当するのがFeature-Sliced Designです。components・hooks・utilsという技術的な分類でディレクトリを切る代わりに、機能単位のスライスへ束ねます。Layers・Slices・Segmentsという3階層で責務範囲を規定する点が特徴で、命名規約が明文化されている分だけVSAより導入手順が具体的です。Feature-Sliced Designとは?Layers・Slices・Segmentsで学ぶフロントエンド設計で構造を確認できます。
よくある質問
VSAは何の略ですか?
Vertical Slice Architectureの略です。日本語では垂直スライスアーキテクチャ、バーティカルスライスアーキテクチャと表記されます。検索では Vertical Slices Architecture、Slice Architecture、Vertical Architecture といった表記ゆれも見られますが、指している構造は同じです。なおVSAという略号は他分野でも使われており、IT運用管理ツールの Kaseya VSA(Virtual System Administrator)が代表例です。設計手法の情報を探すときは文脈語を添えて検索すると目的の情報に絞れます。
スライスの粒度はどの単位で区切りますか?
APIのエンドポイント1本、または画面上の1操作を基準にします。「注文を取り消す」「注文一覧を取得する」はそれぞれ別スライスです。CRUDでまとめて OrderFeature のような塊にすると、レイヤードのサービスクラスと同じ肥大化が起きます。判断に迷ったら、そのコードを変更する理由が1つかどうかを確認してください。取消の仕様変更と一覧の項目追加は別々に発生するため、分けます。逆に、常に同時に変わるものは1スライスにまとめて構いません。
既存のレイヤード構成から段階的に移行できますか?
できます。全面書き換えは不要で、新規に追加する機能からスライス形式で書き、既存の層はそのまま残す進め方が現実的です。Features ディレクトリを新設し、そこに追加したファイルだけが自己完結している状態を作ります。既存のリポジトリやサービスをスライスから呼び出しても構いません。移行が進んでいるかは、追加機能の修正で触ったファイル数が Features 配下に収まっているかで判断できます。この数字が減らないなら、スライスの中で既存層に依存しすぎている可能性があります。
Vertical Slice Architectureのテストはどう書きますか?
スライス単位の統合テストを主軸にします。1スライスが入口から永続化まで含むため、HTTPリクエストを投げて結果を検証する形が自然で、モックの量も減ります。ASP.NET Coreなら WebApplicationFactory、Goなら httptest.NewRecorder でハンドラを直接叩く構成です。層ごとのユニットテストを機械的に用意すると、抽象を減らしたVSAの利点が失われます。ユニットテストは、金額計算のような入出力が明確なロジックに絞って書いてください。
マイクロサービス化の前段として使えますか?
有効です。機能単位でコードが分離されている状態は、切り出し候補の特定と分離作業を単純にします。ただしVSAのスライスをそのままサービス境界にはしません。エンドポイント1本ごとにサービスを作れば分散モノリスになります。まずVSAでコードを整理し、次に複数スライスをまとめた業務ドメイン単位でモジュール境界を引き、最後に分離する、という順序で進めます。