🎯

Feature-Sliced Design と Packaged by Feature: スケーラブルなアーキテクチャの設計

に公開

アプリケーションを作ってていつも悩むことってありませんか?コードが増えるたびに「あ、どこにこの機能を入れればいいんだろう」ってなること。実装は進んでるのに、なぜか開発速度は落ちてくる。あの感覚、嫌ですよね。

実は、多くのプロジェクトで同じ問題を抱えています。開発初期は何も考えずにコードを書いても何とかなります。でも、機能が増えてチームも大きくなると、「なぜかバグが増える」「変更がやたら大変」「テストが書きにくい」という状況に陥るんです。その原因の大半は、コードの整理方法にあります。

この記事では、その課題を解決する2つのアーキテクチャパターン——Feature-Sliced DesignPackaged by Feature について、詳しく掘り下げていきます。

典型的なプロジェクトの衰退パターン

多くのプロジェクトは、技術層で整理されています。controllers, services, utils、もしくはapi, components, hooksみたいな感じで。

でも実装が進むと、ここにほころびが生じます。「ユーザー機能の一部がorders配下に、別の一部がutilesにある」みたいなことが起きるんです。なぜなら、どこに配置するべきか判断基準がないから。

試しに、3年運用されているプロジェクトのコードを見てみてください。ファイル構造を眺めていると、経路が複雑で、変更のたびに複数のディレクトリをいじる羽目になるって感じることが多い。そして古いコードが消えずに残ってたりする。なんか「このプロジェクト、成長が止まってるな」って感じがする。

その感覚、実は正しいんです。

Feature-Sliced Design (FSD) という選択肢

ここで登場するのが Feature-Sliced Design です。2020年代に、特にロシアの開発コミュニティで生まれたこの方法論は、その後、急速に世界中に広がっています。

GitHub のスター数も右肩上がり。採用企業も増えています。ただし、日本ではまだ知名度が低い。多くの日本企業はまだ従来のアーキテクチャで頑張ってる段階です。別に悪い選択ではないんですが、成長の痛みを抱えてることが多い。

何が問題だったのか

FSD が解決する最大の問題は、「機能をどう配置するか」という判断の曖昧さです。従来のアプローチでは、その判断が開発者の経験や好みに大きく依存していました。

新しいメンバーが加わると、古いメンバーとコード配置で意見が衝突します。「これは services に入るべき」「いや、components の中の utils じゃない?」みたいな。この議論、時間かけても結論が出ないことが多い。結局、「どっちでもいいじゃん」という投げやりな状態に陥ります。コミュニケーションコストが無駄に増える。

FSD は、明確で再現可能な設計ルールを提供します。これにより、新しいメンバーが加わった時でも、コードの構造を直感的に理解できるようになる。議論の余地がなくなる。

3層構造を理解する

FSD が提案する構造は、シンプルながら強力です。アプリケーションを3つのレベルで分割します。

レイヤー(Layers)

最上位の分割です。最大7つのレイヤーがあり、重要な原則があります:依存関係は常に一方向

  • app - アプリケーションのエントリーポイント、グローバル設定
  • processes - 複数ページに跨るビジネスプロセス。「チェックアウトフロー」とか
  • pages - ページレベルの組み立て
  • features - 再利用可能な機能単位。「商品を追加」「レビューを投稿」みたいな単位
  • entities - ビジネスの中核となるデータモデル。User、Product、Order など
  • shared - どのレイヤーからもアクセス可能なユーティリティ

最初は「え、そんなに必要?」と思うかもしれません。実際、小規模プロジェクトならいくつか省略していい。でも大規模化するにつれ、この構造の価値が明らかになります。

導入当初、「features と pages の違いってなに?」という質問が必ず出ます。その時点では、その区別が明確じゃなくて当然。でも数週間運用していると、自然とその区別が体に染み込みます。

スライス(Slices)

各レイヤー内では、ビジネスエンティティごとに分割されます。これをスライスと呼びます。

ECサイトなら products, cart, orders といった具合です。ここの粒度が重要なんですが、最初から完璧に決めることはほぼ不可能です。実装が進む中で「あ、ここは一つのスライスにしない方がいいな」という調整が必ず入ります。

中規模チームの場合、大体3ヶ月運用してから「実は粒度が違ったね」という修正が入ります。そのたびに、関連するファイルを整理し直すことになる。初めてこれをやると、「あ、なるほど FSD にしといて正解だった」ってなります。古いアーキテクチャだと、こういう修正は地獄ですから。

実務では、初期段階で時間をかけすぎるより、ざっくり決めて、走りながら調整する方がうまくいく。

セグメント(Segments)

スライス内では、さらに細かく分割されます。

  • api - APIコールやデータ取得ロジック
  • ui - UIコンポーネント
  • model - 状態管理とビジネスロジック
  • lib - ユーティリティ関数

責任が明確になることで、コードレビューも簡単になります。「このコンポーネントの中にロジックが入ってる」という問題を、即座に指摘できる。ベストプラクティスが共有されやすくなります。

Public API という仕組み

FSD が支持される理由の一つが、Public API という概念です。

各スライスは index.ts を持ち、ここで「外部に公開する」インターフェースを定義します。スライスの内部実装は隠蔽され、他のレイヤーはこの Public API 経由でのみアクセスできます。

// src/features/products/index.ts - 公開部分
export { ProductCard } from "./ui/ProductCard";
export { useProducts } from "./model/useProducts";
export * from "./model/types";

// src/features/products/api/fetchProducts.ts - 内部実装
// 外部からは直接アクセスできない

これにより、スライス内部の変更が他に波及するのを防げます。もし api/fetchProducts を大幅に書き直しても、index.ts のインターフェースが変わらなければ、他のコードは影響を受けません。

「あ、このロジック移せばいいんだ」という判断が、心理的に楽になります。チームの学習曲線も緩くなります。

バックエンド視点:Packaged by Feature

ここで視点を変えて、バックエンドの世界へ。マイクロサービスアーキテクチャでは、サービスの分割方法が大きな課題になります。

従来のアプローチの限界

多くのマイクロサービス設計では、こんな感じで分割されます。

  • User Service
  • Product Service
  • Order Service
  • Payment Service

一見、合理的に見えるかもしれません。でも実装が進むと、矛盾が生じます。

例えば「注文を作成する」という処理を考えてください。これには複数のステップがあります:

  1. 商品の存在確認(Product Service)
  2. 在庫の確認(Product Service)
  3. ユーザーの確認(User Service)
  4. 支払い処理(Payment Service)
  5. 注文レコードの作成(Order Service)

複数のサービスにまたがる複雑な呼び出しが必要になります。そして、どこかで失敗したら?トランザクションの管理が地獄になります。最悪の場合、在庫は減ったが支払いが失敗した——みたいな矛盾した状態が生まれます。

さらに問題なのは、この構造だとチーム編成が曖昧になることです。誰が注文処理全体を責任を持つのか。Product チームに入るのか、Order チームか、それともチーム横断的な「Order Flow」チームを作るのか。

予想通り、責任を押し付け合う事態に陥ります。良くあるパターンですね。

Packaged by Feature の考え方

ここで登場するのが Packaged by Feature です。

技術層ではなく、ビジネス機能を中心にサービスを分割します。

  • 注文処理サービス(Order Service)— 注文作成から配送完了までの全ロジック。注文に関する全てを管理
  • ユーザー管理サービス(User Service)— 認証と基本情報管理
  • 支払いサービス(Payment Service)— 支払い関連のロジック

各サービスが独立したビジネス機能を完結させます。それぞれ独立したデータベースを持つことが多いです。

こうすることで何が得られるか?

  • チーム編成が明確——「Order Service チーム」が注文処理全体を担当。責任が明確で、議論の余地がない
  • デプロイメントが独立——一つのサービスの変更は他に影響しない。金曜夜のデプロイでも怖くない
  • スケーリングが柔軟——需要の高い機能だけスケールアップできます。ブラックフライデーなら Order Service だけ強化する、とか
  • 実装がシンプル——ビジネスロジックが一箇所に集約される。責任が明確な分、バグが少なくなる傾向がある
  • チームの認識がズレにくい——何をやるべきか一目瞭然。朝礼で説明する時間が減ります

ただし、サービス間の通信は?データの一貫性は?こういう問題は確かに増えます。

実は同じ思想の両輪

ここまで読むと「別の概念じゃん」と思うかもしれませんが、実は深い関連性があります。

層が違うだけなんです。

  • FSD はフロントエンド内の組織方法
  • Packaged by Feature はバックエンド(マイクロサービス)の組織方法

でも、どちらも「ビジネス機能を中心に設計する」という根本的な哲学は同じです。

理想的なチーム構成を想像してみてください:

フロントエンドチームが FSD に従って features/orders で注文機能を実装。バックエンドチームが Packaged by Feature で Order Service を開発。両者の思考が完全に統一されています。機能リクエストが来ても「あ、Order Service の下でやるんだな」「Order Service チームに確認だな」と即座に判断できます。

ドメイン駆動設計(DDD) の思想とも相通じるところがあります。実は、DDD から着想を得ている部分も多い。このレベルまで達すると、開発速度は飛躍的に上がります。

ドキュメントも不要になるほど、構造が自明になります。

現実はもっと複雑

ただし、どちらのパターンにも課題があります。綺麗事だけでは片付きません。

FSD を導入する時の実際

実装してみようとするチームが直面する問題:

  • 学習曲線が急 — 特に経験が浅い開発者には、3層構造と依存関係ルールが複雑に見える。最初は「なぜこんなに複雑?」という抵抗感がある。でも2週間もすれば慣れます
  • 初期設計の難しさ — スライスの粒度をどのレベルに設定するか、その判断には経験が必要。ここで失敗することは多い
  • 小規模プロジェクトは過剰 — フリーランスの小さな案件には、逆に足かせになります。手数を増やすだけになりかねない

完璧に従おうとするプロジェクトは大体うまくいきません。「このロジックはどこに入るべき?」という議論が増えて、実装が止まる。

実際のプロジェクトでは、ある程度の「妥協」や「例外」を許容せざるを得ない。「実用性とのバランス」が重要です。最も良いアーキテクチャは、チームが実際に使えるアーキテクチャですから。理想と現実のギャップを埋めるのが、リーダーの仕事です。

Packaged by Feature の落とし穴

バックエンド側も問題があります。綺麗な理想図が、現実ではどうなるか。

  • 分散トランザクション — 複数のサービスにまたがるビジネスロジックが複雑です。SAGA パターンなど、新たな技術を学ぶ必要がある。メンテナンスも大変。実装してみて、初めてその複雑さに気づきます
  • データの一貫性 — サービス間でデータを同期させるのは大変。イベント駆動型アーキテクチャが必要になることが多い。ここで新たな障害が増えます
  • テストが複雑 — 統合テストが難しく、多くのモック設定が必要。テストの実行時間も増える。テストを書くだけで一日が終わることもある
  • デプロイメントの複雑さ — 全体を一括デプロイできないため、バージョン管理が大変。一つのサービスが古いバージョンのままだと、全体が不安定になります
  • 障害の波及 — 一つのサービスの遅延が、全体に影響する可能性がある。1秒の遅延が全体に波及することもあります
  • ネットワークのオーバーヘッド — 単一プロセスなら関数呼び出しで済むことが、ネットワークを介する必要がある。レイテンシが跳ね上がります

実務でマイクロサービスを導入したチームの話を聞くと、「モノリシックの方が楽だった」というケースも珍しくありません。特に初期段階では。実装スピード、テストの手軽さ、デバッグの容易性——モノリシックの利点は多い。

でも、ある規模を超えると、その優位性は逆転します。チームが10人を超えた辺りから、モノリシックの弊害が顕著になります。

規模に応じた選択

では、どっちを選ぶべき?

小規模プロジェクト(開発者 1-2 名)

  • 従来のアーキテクチャで十分。FSD も Packaged by Feature も不要です
  • 単純にコードを書いてテストすればいい
  • 複雑さを導入することで、逆に生産性が落ちます

中規模プロジェクト(開発者 3-5 名)

  • FSD の導入を検討する段階。チームが安定してきた頃が導入のタイミングです
  • ただ、全部は必要ないかも。フロントエンドだけ、とか。あるいは layers の一部だけ
  • バックエンドはまだ単一サービス。Packaged by Feature は時期尚早。今マイクロサービス化すると、後悔する確率が高い

大規模プロジェクト(開発者 6 名以上)

  • FSD は強い味方になります。ほぼ必須に近い。ここまで来たら、アーキテクチャの投資は必須です
  • バックエンドが複数チーム体制になるなら、Packaged by Feature の検討も
  • ただし、本当に複数サービスが必要なのか、再度検討すべき。多くの企業が「とりあえずマイクロサービス」で失敗している。Netflix ですら、最初はモノリシックだったんですから

最後に

Feature-Sliced Design と Packaged by Feature は、プロジェクトが成長する段階で、その価値が明らかになるアーキテクチャパターンです。

ただし、どちらも「万能薬」ではありません。組織の成熟度、チームの経験、プロジェクトの規模——こうした要因を総合的に判断する必要があります。

そして、最も大切なアドバイスはこれです:完璧を目指さないこと。FSD にしろ Packaged by Feature にしろ、導入当初から 100% 遵守しようとするプロジェクトはだいたい失敗します。

段階的に導入し、チームと一緒に調整していく。その過程で「あ、ここは例外にしよう」「この部分は別の方法がいい」という判断が必ず出てくる。その柔軟性が、実は最も大事なんです。

プロジェクトは生き物です。組織とともに進化します。その進化のプロセスで、このどちらかのパターンが、きっと役に立つ瞬間が来るはずです。

GitHubで編集を提案
株式会社three dots.

Discussion