Open18

Feature-Sliced Designの公式ドキュメントを元に各層ごとの説明や具体的なディレクトリの作成方法をまとめる

saneatsusaneatsu

これは何?

Feature-Sliced Designを勉強しつつ導入しているが、どの層がどういう目的でどういう役割なのか?
具体的に何が許され、どういうディレクトリが作られ、何が禁止されているのか?
などごちゃごちゃになるので、公式ドキュメントの内容を引用しつつ、それを各層ごとにまとめたもの。

自分用メモなので雑。
随時更新している。

FSDの基礎

  • Layer(影響範囲)
    • App: アプリケーションの起動に必要なすべてのもの(ルーティング、エントリーポイント、グローバルスタイル、プロバイダーなど)
    • Processes: 複雑なページ間のシナリオ
    • Pages: ページ全体、またはネストされたルーティングの場合、ページの大部分
    • Widgets: 大きな自己完結型の機能部分、またはインターフェースの大部分。通常はユーザーシナリオ全体を実装する
    • Features: プロダクト機能の再利用可能な実装、つまりユーザーにビジネス価値をもたらすアクション
    • Entities: プロジェクトが扱うビジネスエンティティ、例えばuserやproduct
    • Shared: 再利用可能なコード。特にプロジェクト/ビジネスの詳細から切り離されたもの
  • Slices(ドメイン)
    • スライスの名前は自由に付けることができ、いくつでも作成できます
    • スライスは、意味的に関連するコードをグループ化することで、プロジェクト内のナビゲーションをしやすくします
    • スライスは同じレイヤーの他のスライスを使用できないため、スライス内のコードの強い結合とスライス間の弱い結合が保証されます
  • Segments(技術的目的)
    • セグメントはその目的に応じてコードをグループ化します

移行ガイド

https://feature-sliced.github.io/documentation/ja/docs/guides/migration/from-custom

ざっくりとまずは以下を行った。
基本的にはPages層へ、共通のものはShared層へ、ドメインに関するものはEntities層へ。

  1. Pages Layerに分割
    • 感覚的にもここが初手一番やりやすい
  2. Sharedに分離
    • ドメインロジックから離れているconfigとかlibとかを剥がしていく
    • 第二層のSliceがないのでわかりやすい
  3. クロスインポートの解消
    • Shared層を適切なセグメントに移動
  4. Shared層を分解
    • 特定のページのみで使用されているものはPages層のスライスへ移動させる
      • pages/user/ui みたいな形式
    • Shared層は再利用されるもののみ

参考

全体を把握するために見た記事。
個別具体は各Scrap内に書いていく。

saneatsusaneatsu

https://feature-sliced.github.io/documentation/ja/docs/reference/slices-segments

Shared層とApp層にはスライスが含まれていません。これは、Sharedがビジネスロジックを含むべきではないため、プロダクト的な意味を持たないからです。また、Appはアプリケーション全体に関わるコードのみを含むべきであり、したがって分割は必要ありません。

各スライス(およびスライスを持たない層のセグメント)は、公開APIの定義を含む必要があります。
あるスライス/セグメントの外部モジュールは、そのスライス/セグメントの内部ファイル構造ではなく、公開APIのみを参照できます。

「公開APIの定義を含む必要があります」とあるので、AppとShared以外では、3層目には必ずapi/がなくてはいけないということかな。

詳しくは公開API | Feature-Sliced Design

ReactQueryを使っているがその場合は公式でディレクトリ構成とファイル名の例が書かれているのでこれに従う。

https://feature-sliced.github.io/documentation/ja/docs/guides/tech/with-react-query

これらのセグメントの名前が、その内容が何のために必要かを説明するものであることを確認してください。たとえば、components、hooks、types は、コードを探すときにあまり役に立たないため、悪いセグメント名です。

ミューテーションをクエリと混合することは推奨されません

また、ミューテーションとクエリも1つのファイルにまとめない。

saneatsusaneatsu

https://zenn.dev/akfm/articles/feature-sliced-design

コンセプト:Public APIにもありましたが、FSDでは公開モジュールはすべて各Slicesのindex.tsのみに存在します。外部公開前のモジュールは、AuthFormではなくFormのように短い命名にしてre export時にユニークな命名に変更することが推奨されています。

へぇ。短い命名にしてre export するんだ。

これも post-like-button みたいなディレクトリにできるがなるべく短くしている、ということなのだろうか?

であれば、 React Queryとの併用 | Feature-Sliced Design に書いてある以下も get-{entity}ではなくget でいいのでは??わからん。

saneatsusaneatsu

App

アプリケーションの起動に必要なすべてのもの(ルーティング、エントリーポイント、グローバルスタイル、プロバイダーなど)

  • 📁 routes — ルーターの設定
  • 📁 store — グローバルストアの設定
  • 📁 styles — グローバルスタイル
  • 📁 entrypoint — アプリケーションコードへのエントリポイント、フレームワーク固有
  • 📁 analytics

Providerもここ

@/app/providers/query-provider.tsx というファイルを作成している。
ref: React Queryとの併用 | Feature-Sliced Design

saneatsusaneatsu

ユーザーが所属するOrganizationを保持しているzustandを使ったStoreもここになる?と思ったけど、その場合features/app/を参照することになってしまうのでないか。

sahred/ かなとも思ったけど、定義は「Shared*(シェアード) — 再利用可能なコード。特にプロジェクト/ビジネスの詳細から切り離されたもの」と書いてある。

その場合「Entities(エンティティ) — プロジェクトが扱うビジネスエンティティ、例えばuserやproduct」と書いてあるのでEntitiesなのか?少なくともFeatureではないしEntitiesか...?

saneatsusaneatsu

https://zenn.dev/moneyforward/articles/e1ed48c3974811#layer

このレイヤーの各スライスには、Entitiesに基づく静的なUIやデータストア、CRUD操作が含まれます。

と書いていて、entities/user/model/store.ts が定義されている。
FSD公式だと、schemaとかはすべてhoge-schema.tsとしているのでこのようなケースでは自分はentities/user/model/user-store.tsと書いていくことにする。

VSCodeのエクスプローラーで見た時にもわかりやすいし。

saneatsusaneatsu

Pages

レイヤー | Feature-Sliced Design

ページは、ウェブサイトやアプリケーションを構成するものです(スクリーンやアクティビティとも呼ばれます)。通常、1ページは1つのスライスに対応しますが、非常に似たページが複数ある場合、それらを1つのスライスにまとめることができます。例えば、登録フォームとログインフォームです。

チームがナビゲートしやすい限り、ページスライスに配置できるコードの量に制限はありません。ページ上のUIブロックが再利用されない場合、それをページスライス内に保持することは完全に問題ありません。

ページスライスには、通常、ページのUIやローディング状態、エラーバウンダリ(📁 ui)、データの取得や変更リクエスト(📁 api)が含まれます。ページが専用のデータモデルを持つことは一般的ではなく、状態の小さな部分はコンポーネント自体に保持されることがあります。

自分の考え

pages/login/model/registration-schema.ts というファイルにバリデーションを書く場合もあるっぽい。
バリデーション実装するなら基本的には entitiesと featuresまでになりそうな気がしている。
複数のfeatureによって1つのWidget(複雑でほかで使い回されうるコンポーネント)を提供する場合だけそれより上の階層でも使いそうなものだけどなぁ。
ここらへんよくわかってない。

ref: 認証 | Feature-Sliced Design

saneatsusaneatsu

Widgets

ウィジェットレイヤーは、大きな自己完結型のUIブロックを対象としています。ウィジェットは、複数のページで再利用される場合や、所属するページにある複数の大きな独立したブロックの一つである場合に最も便利です。

UIのブロックがページの大部分を構成し、再利用されない場合、それはウィジェットであるべきではなく、代わりにそのページ内に直接配置するべきです。

再利用されない場合、それはウィジェットであるべきではなく、代わりにそのページ内に直接配置するべき

  • 複数のPageで再利用可能な大きな自己完結型のUIブロック
  • 単一のページ内でも複数回使われる可能性がある独立したコンポーネント

逆に特定のページ固有で再利用されないものはPage層に入れる。

saneatsusaneatsu

Features

このレイヤーは、アプリ内の主要なインタラクション、つまりユーザーが行いたいことを対象としています。これらのインタラクションは、ビジネスエンティティを含むことが多いです。

アプリのフィーチャーレイヤーを効果的に使用するための重要な原則は、すべてのものがフィーチャーである必要はないということです。何かがフィーチャーである必要がある良い指標は、それが複数のページで再利用されるという事実です。

例えば、アプリに複数のエディターがあり、すべてにコメントがある場合、コメントは再利用されるフィーチャーです。スライスはコードを迅速に見つけるためのメカニズムであり、フィーチャーが多すぎると重要なものが埋もれてしまいます。

理想的には、新しいプロジェクトに入ったとき、既存のページやフィーチャーを見ると、アプリの機能性が分かります。何がフィーチャーであるべきかを決定する際には、プロジェクトの新参者が重要なコードの大きな領域を迅速に発見できるように最適化してください。

フィーチャーのスライスには、インタラクションを実行するためのUI(例えばフォーム、📁 ui)、アクションを実行するために必要なAPI呼び出し(📁 api)、検証および内部状態(📁 model)、フィーチャーフラグ(📁 config)が含まれる場合があります。

自分の理解

直訳の通り「機能」そのもの。
ふわっとしているがゆえに判断が難しいが、「ユーザーが行いたいことを対象にしています」と書かれている通り。

例えばログインとか、サインアップとか。
Entityで作られたEmailField、PasswordField、UsernameFieldを使って初めてユーザーが行いたいこと(ログイン)を実現できる。
features/user/ui/email-form.tsx というファイルを作成した。

saneatsusaneatsu

アプリのフィーチャーレイヤーを効果的に使用するための重要な原則は、すべてのものがフィーチャーである必要はないということです。何かがフィーチャーである必要がある良い指標は、それが複数のページで再利用されるという事実です。

じゃあ再利用されないものはFeatures層に置かないほうが良いのか?
例えばログインページで LoginForm.tsx というコンポーネントは、ログインページでしか利用されず再利用されることは無い。つまりページ固有のコンポーネント。

この場合 Pages層に置いたほうがいいということになるのか?🤔

https://feature-sliced.github.io/documentation/ja/docs/reference/layers#widgets

UIのブロックがページの大部分を構成し、再利用されない場合、それはウィジェットであるべきではなく、代わりにそのページ内に直接配置するべきです。

公式もこう言っている。

https://zenn.dev/kyuki/articles/d736b0957e6336

Todoアプリでは、todoのリストを表示するホームページ (todo-list)、特定のタスクに関する詳細情報を提供するタスクの詳細ページ (todo-details)などが該当します。

pages
└── todo-home
    ├── index.ts
    └── ui
        ├── TodoHome.css.ts
        └── TodoHome.tsx

ん〜わからん。
仮に「そのページ固有のコンポーネントはPages層直下に置く」が正しい場合、ページ固有のものが多い場合Pages層が結構でかくなっていくのか?

Atomicデザイン的なものの場合、当然上の層の方がフォルダの大きさが小さくなっていくかと思っていたけどそうじゃないのか?

https://zenn.dev/kyuki/articles/d736b0957e6336#widgets

Widgets
entitiesとfeaturesにあるブロックを結合して構成するレイヤー。
ビジネスロジックは、基本的にに含まない。
再利用可能なコンポーネントを用意したい際に利用する。

Widgets層には「ビジネスロジックは、基本的にに含まない」とも書いてある(公式にはこの記述は見つからなかったのであとで探してみるが)。
Widgets層の段階でビジネスロジックが含まれていないのに、それより上の層でビジネスロジック含めていいのか?

https://feature-sliced.github.io/documentation/ja/docs/get-started/tutorial#認証

公式のチュートリアルを見るとビジネスロジック含むようなものをバンバン pages/sign-in/ui/RegisterPage.tsxや、pages/feed/api/loader.tsに含めている。

なのに、SignUpでユーザーを作成する処理は shared/api/auth.server.ts とShared層に入れている。これってドメイン関連だからEntities層じゃないの??てかどうせRegisterPageでしか使わないならPages層に定義していいのでは?

と思っていたが、shared/api/auth.server.ts の関数をpages/sign-in/api/register.ts で呼び出して使っている。この例はRemixだけどNext.jsでReact Queryを使っている場合、Shared層やEntitiesのapi/でAPIを呼び出すような関数を定義して、ページ固有の呼び出しに関してはPages層でuseQuery/useMutateをapi/models/とかに定義したらいいのかな。

saneatsusaneatsu

https://feature-sliced.github.io/documentation/ja/docs/guides/tech/with-react-query

ReactQuery使っている場合は entities/hoge/api にCRUDの関数を置く。

└── src/                                        #
    ├── app/                                    #
    |   ...                                     #
    ├── pages/                                  #
    |   ...                                     #
    ├── entities/                               #
    |     ├── {entity}/                         #
    |    ...     └── api/                       #
    |                 ├── `{entity}.query`      # クエリファクトリー、キーと関数が定義されている
    |                 ├── `get-{entity}`        # エンティティを取得する関数
    |                 ├── `create-{entity}`     # エンティティを作成する関数
    |                 ├── `update-{entity}`     # オブジェクトを更新する関数
    |                 ├── `delete-{entity}`     # オブジェクトを削除する関数
    |                ...                        #
    |                                           #
    ├── features/                               #
    |   ...                                     #
    ├── widgets/                                #
    |   ...                                     #
    └── shared/                                 #
        ...                                     #

で、Mutationは以下2パターン。
取り敢えずミューテーションとエンティティ関連の関数をまとめるのは推奨されていないので分けてれば良さそう。

  1. 使用場所の近くにAPIセグメントにカスタムフックを定義する
    • e.g. @/features/update-post/api/use-update-title.ts
  2. 別の場所(Shared層やEntities層)にミューテーション関数を定義し、コンポーネント内でuseMutationを直接使用する

複数箇所でがっつり共通化したいわけでもないけど1にしとくか。
因みにbulletproof-reactだとミューテーションとエンティティをまとめた上で1の方法を取っていたような気がする。

saneatsusaneatsu

「例えばプロジェクトという概念があってそれのCRUD操作ってentityとfeatureどっちが良い?」という問いに対するGeminiの回答

CRUD操作は Feature 層

なるほど、プロジェクトのCRUD操作を Entity と Feature のどちらに配置するのが適切か、というご質問ですね。これは Feature-Sliced Design における重要な判断ポイントです。

結論から言うと、基本的なCRUD操作は Entity ではなく、Feature に配置するのが一般的で、より推奨されるプラクティス です。

理由を詳しく説明しますね。

Entity (エンティティ) 層の役割:

  • Entity 層は、アプリケーションのドメインオブジェクト(概念) を表現します。この例で言うと、「プロジェクト」そのもののデータ構造、型定義、ビジネスロジックのうち、どの Feature にも依存しない、本質的な部分 を含みます。
  • Entity は、アプリケーション全体で共通して利用される可能性のある、最も抽象的な層 です。
  • CRUD 操作のような特定のユースケースに強く結びついたロジックは、Entity 層には含めません。Entity はあくまでデータとその基本的な振る舞いを定義する場所に留めます。

Feature (フィーチャー) 層の役割:

  • Feature 層は、特定のビジネス機能やユーザーシナリオ を実装します。プロジェクトのCRUD操作(作成、読み取り、更新、削除)は、まさに「プロジェクトを管理する」という具体的な機能に該当します。
  • Feature は、Entity 層のオブジェクトを利用して、ユーザーインターフェース(UI)、APIとの連携、状態管理、ビジネスロジック などを組み合わせ、具体的な機能を実現します。
  • プロジェクトの作成フォーム、プロジェクトの一覧表示、プロジェクトの編集画面、削除ボタンの処理などは、Feature 層に属するでしょう。

なぜ CRUD 操作が Feature に配置されるべきなのか?

  1. 関心の分離: CRUD 操作は特定の機能(プロジェクト管理)に強く結びついています。これらを Feature 層に置くことで、Entity 層はより純粋なドメインモデルとしての役割に集中できます。
  2. 再利用性と独立性: Entity 層を薄く保つことで、異なる Feature 間で Entity をより柔軟に再利用できます。Feature は特定の Entity を利用しますが、Entity 自体は特定の Feature に依存しません。
  3. 変更の局所化: プロジェクトのCRUD処理に関する変更があった場合、影響範囲は Feature 層内に限定されやすく、Entity 層への影響を最小限に抑えられます。
  4. テストの容易性: Feature ごとにロジックがまとまっているため、CRUD操作に関するテストも Feature の単位で実施しやすくなります。

具体的な配置のイメージ:

  • Entity:
    • entities/project/model.ts (プロジェクトの型定義、基本的な属性など)
    • entities/project/index.ts (Entity 層からのエクスポート)
  • Feature:
    • features/project-create/ui/ProjectCreateForm.tsx (プロジェクト作成フォームのUI)
    • features/project-create/api/projectCreateApi.ts (プロジェクト作成APIとの連携)
    • features/project-create/model/projectCreateStore.ts (プロジェクト作成の状態管理)
    • features/project-list/ui/ProjectList.tsx (プロジェクト一覧表示のUI)
    • features/project-list/api/projectListApi.ts (プロジェクト一覧取得APIとの連携)
    • features/project-delete/ui/ProjectDeleteButton.tsx (プロジェクト削除ボタンのUI)
    • features/project-delete/api/projectDeleteApi.ts (プロジェクト削除APIとの連携)

このように、CRUD 操作はそれぞれの機能(作成、一覧表示、削除など)に対応する Feature の中で、UI、API連携、状態管理といった関連する要素と共に実装されるのが FSD の考え方です。

もし、Entity 層に CRUD 操作に近いロジックを含める場合があるとすれば、それは Entity 自身のライフサイクルに関わる非常に基本的な振る舞いに限られるかもしれません。しかし、それでも多くの場合、Feature 層で Entity を利用して具体的な操作を実装する方が推奨されます。

Feature-Sliced Design は、アプリケーションの規模が大きくなるほど、その恩恵を受けやすくなります。初期段階では少し複雑に感じるかもしれませんが、長期的な保守性や拡張性を考えると、この考え方を意識して設計を進めることは非常に有益ですよ。

saneatsusaneatsu

Entity

このレイヤーのスライスは、プロジェクトが扱う現実世界の概念を表します。一般的には、ビジネスがプロダクトを説明するために使用する用語です。
例えば、SNSは、ユーザー、投稿、グループなどのビジネスエンティティを扱うかもしれません。

エンティティスライスには、データストレージ(📁 model)、データ検証スキーマ(📁 model)、エンティティ関連のAPIリクエスト関数(📁 api)、およびインターフェース内のこのエンティティの視覚的表現(📁 ui)が含まれる場合があります。視覚的表現は、完全なUIブロックを生成する必要はなく、アプリ内の複数のページで同じ外観を再利用することを主に目的としています。異なるビジネスロジックは、プロップスやスロットを通じてそれに付加されることがあります。

フィーチャーとエンティティの違いは何ですか?

エンティティはアプリケーションが扱う現実世界の概念です。フィーチャーはユーザーに実際の価値を提供するインタラクションであり、ユーザーがエンティティで行いたいことです。

ref: FAQ | Feature-Sliced Design

Mutation

プロジェクトにすでにエンティティの分割があり、各クエリが1つのエンティティに対応している場合、エンティティごとに分割するのが最良。

└── src/                                        #
    ├── app/                                    #
    |   ...                                     #
    ├── pages/                                  #
    |   ...                                     #
    ├── entities/                               #
    |     ├── {entity}/                         #
    |    ...     └── api/                       #
    |                 ├── `{entity}.query`      # クエリファクトリー、キーと関数が定義されている
    |                 ├── `get-{entity}`        # エンティティを取得する関数
    |                 ├── `create-{entity}`     # エンティティを作成する関数
    |                 ├── `update-{entity}`     # オブジェクトを更新する関数
    |                 ├── `delete-{entity}`     # オブジェクトを削除する関数
    |                ...                        #
    |   

エンティティごとに区切れない場合は sharedへ

└── src/                                        #
   ...                                          #
    └── shared/                                 #
          ├── api/                              #
         ...   ├── `queries`                    # クエリファクトリー
               |      ├── `document.ts`         #
               |      ├── `background-jobs.ts`  #
               |     ...                        #
               └──  index.ts                    #

自分の理解

shadcnのuiは必ずShared層に行く。
それに対してEmailとか、モデル(型定義)が必要なものであればその1つ上のEntityに行く。
UsernameFieldとかは、SignUp時に重複がないかなどを確認するというAPI呼び出しも行っている。
Entityは model/ や、 api/ などを持つことがポイント
ということはこれらは「プロジェクトが扱う現実世界の概念」といえると考えた。

entities/user/ui/email-field.tsx というファイルを作成した。

saneatsusaneatsu

Shared

特徴はSliceを含まないこと。つまり、 shared/user/ みたいにならない。

スライスはビジネスドメインによってレイヤーを分割することを目的としていますが、Sharedにはビジネスドメインが存在しないため、Shared内のすべてのファイルは互いに参照し、インポートすることができます。

  • 📁 api — APIクライアントおよび特定のバックエンドエンドポイントへのリクエストを行う関数
  • 📁 ui — アプリケーションのUIキット このレイヤーのコンポーネントはビジネスロジックを含むべきではありませんが、ビジネスに関連することは許可されています。例えば、会社のロゴやページレイアウトをここに置くことができます。UIロジックを持つコンポーネントも許可されています(例えば、オートコンプリートや検索バー)
  • 📁 lib — 内部ライブラリのコレクション このフォルダーはヘルパーやユーティリティとして扱うべきではありません(なぜこれらのフォルダーがしばしばダンプに変わるか)。このフォルダー内の各ライブラリは、日付、色、テキスト操作など、1つの焦点を持つべきです。その焦点はREADMEファイルに文書化されるべきです。チームの開発者は、これらのライブラリに何を追加でき、何を追加できないかを知っているべきです
  • 📁 config — 環境変数、グローバルフィーチャーフラグ、アプリの他のグローバル設定
  • 📁 routes — ルート定数、またはルートをマッチさせるためのパターン
  • 📁 i18n — 翻訳のセットアップコード、グローバル翻訳文字列

他には shared/api/endpoints/login.ts, shared/auth などのディレクトリもOK

ref: 認証 | Feature-Sliced Design

同一ディレクトリ内で型を参照したい場合は Shared層に入れる(Shared層は例外的に同一ディレクトリを参照しにいけるので)
ただし、Entities層でクロスインポートしたい場合は、 entities/song/@x のようなディレクトリを作成することで対応する。
詳しくは以下のリンクを参照。

また、公式には クロスインポートは、同じレイヤーの別のスライスからインポートする状況です。通常、これはレイヤーに関するインポートルールによって禁止されています と書かれているがShared層はこれに当たらない。というのも、Shared層にはスライスが無い。

実際に、このレイヤーは、Appレイヤーと同様に、スライスを含みません。スライスはビジネスドメインによってレイヤーを分割することを目的としていますが、Sharedにはビジネスドメインが存在しないため、Shared内のすべてのファイルは互いに参照し、インポートすることができます。と公式に書かれている。

ref: 型 | Feature-Sliced Design

Public APIによるバンドルサイズの増加??

https://qiita.com/kyntk/items/db0f6fdc1024f31d81e5

「再exportするとバンドルサイズが増える」という記事だが、よく読むとこの例ではFSDのルールを部分的に破ってしまっていて(おそらくこれは意図して破っていそうです)循環参照していたから、という結論。
つまりPublic API自体が悪いのではない。
通常ならツリーシェイキングされてバンドルから削除される。
「Public APIを削除したらサイズが減った」というより、「FSDのルールを破って循環参照したためサイズが増えた」ということだと思った。

じゃあ循環参照を避けるためにPublic APIを使わなければ解決なのか?と思ったが当然そんなこともないよな?と思ったので公式ドキュメントを見てみたらちゃんとこの件について書いてあった。

ref: 公開API | Feature-Sliced Design

循環インポートはインデックスファイルなしでも発生する可能性がありますが、インデックスファイルがあると、循環インポートを誤って作成する明確な機会が生まれます

この問題を防ぐために、次の2つの原則を考慮してください。

  • ファイルが同じスライス内にある場合は、常に相対インポートを使用し、完全なインポートパスを記述すること
  • ファイルが異なるスライスにある場合は、常に絶対インポートを使用すること(エイリアスなどで)

なるほど。ファイルが同じスライスにある場合は「同じスライスだよ」ということを示したいから自分の好みで相対インポートを使っていたんだけど、循環参照を減らすためという素晴らしい目的があったのか。

インデックスファイルがすべてを再エクスポートする場合、いくつかのバンドラーはツリーシェイキング(インポートされていないコードを削除すること)に苦労するかもしれません。

通常、これは公開APIにとって問題ではありません。なぜなら、モジュールの内容は通常非常に密接に関連しているため、1つのものをインポートし、他のものをツリーシェイキングする必要がほとんどないからです

自分の認識合ってた。

公式が用意しているFSD専用のアーキテクチャリンターを入れると良いよと書いてある。
https://github.com/feature-sliced/steiger

watchモードあるけど多分これVSCodeでリアルタイムに分析してくれるわけではないよな?
自分はESLintではなくBiomeを使っているが、Biomeにはno-cycleのような循環参照を検知する機能はまだない。
しかし、インポートを禁止するルールは設定できるのでこれを設定することでレイヤー間のインポートは制限できそう。

https://biomejs.dev/linter/rules/use-import-restrictions/

FSD公式にも書いてあるTkTodoのバレルファイルの使用をやめるように、との記事。

スライスを持つレイヤーのセグメントにインデックスファイルを持たないようにすること。
たとえば、「コメント」フィーチャーのインデックス(📄 features/comments/index.js)がある場合、そのフィーチャーのuiセグメントのために別のインデックスを持つ理由はありません(📄 features/comments/ui/index.js)。

これとか気をつけておこう。
FSD使っているとindexファイルが多くなるのがいやだ的な記事を見たけどこれしてたら一定緩和されないだろうか。

ref: Please Stop Using Barrel Files | TkDodo's blog

saneatsusaneatsu

Sharedには「📁 api — APIクライアントおよび特定のバックエンドエンドポイントへのリクエストを行う関数」と書いてあるけど、Slicesがないし、「特にプロジェクト/ビジネスの詳細から切り離されたもの」なので、例えば shared/api/user/get-projects.ts みたいにドメインを切ることは無いし、shared/api/projects.ts みたいなことも無いわけだよな?

おそらくその場合は「Entities(エンティティ) — プロジェクトが扱うビジネスエンティティ、例えばuserやproduct」と書かれているEntitiesに行くべき。実際に「エンティティスライスには、データストレージ(📁 model)、データ検証スキーマ(📁 model)、エンティティ関連のAPIリクエスト関数(📁 api)」と書かれている。

saneatsusaneatsu

https://feature-sliced.github.io/documentation/ja/docs/get-started/overview#segments

  • ui: 表示に関連するすべて: UIコンポーネント、日付フォーマッター、スタイルなど
  • api: バックエンドとのやり取り: リクエスト関数、データ型、マッパー
  • model: データモデル: バリデーションスキーマ、インターフェース、ストレージ、ビジネスロジック
  • lib: 他のモジュールが必要とするライブラリコード
  • config: 設定ファイルとフィーチャーフラグ