# Role








## Role を定義する

アーキテクチャ定義に従ったアプリには、固有の役割（Role）を持つファイルがあります。
例えば `UseCase`, `Repository`, `Controller`, `ViewModel` などです。

どんな Role があるのかは、`architecture { }` の中で定義します。

```kt
architecture {
    // UseCase という Role を定義する
    "UseCase" {
        // 説明・置き場所・制約を、この中に書く
    }
}
```

Role の説明・置き場所・制約は、その Role のブロックの中に書きます。

- `title`・`summary`・`description` など ... そのRoleの説明です。ドキュメント生成に使われます。
- `layout { }` ... そのRoleのファイルをどこに置けるかを定義します。
  - 詳しくは [レイアウトの定義](/katachi/ja/guides/layout/) を参照してください。
- `"制約名".konsist { }` ... そのRoleのファイルにかけられる制限を定義します。
  - 制約は Konsist で書きます。詳しくは [Konsist との統合](/katachi/ja/guides/konsist-integration/) を参照してください。
- `.template { }` ... `layout { }` で宣言したファイルに付けて、そのファイルを新しく作るときのテンプレートを定義します。
  - 詳しくは [テンプレートからコード生成](/katachi/ja/guides/generate-code-from-template/) を参照してください。

```kt
architecture {
    "UseCase" {
        title = "ユースケース"
        summary = "各画面で発生するアプリ固有の1つの振る舞い"

        example("GetUserUseCase", "ユーザーを取得する")

        // どこに置いてよいか
        layout {
            "src/main/kotlin/com/example/useCase" / "*UseCase".ktFile()
        }

        // 中身がどうあるべきか
        "invoke 関数を1つだけ持つこと".konsist {
            classes().must { it.functions().map { f -> f.name } == listOf("invoke") }
        }
    }
}
```

それぞれを、もう少し詳しく説明します。

<details>
<summary>説明を書く</summary>

最低限必要なのは `summary` だけです。生成ドキュメントでRoleの一覧表に並ぶ1行になります。

```kt
"UseCase" {
    summary = "各画面で発生するアプリ固有の1つの振る舞い"
}
```

`description` には長い説明を、`example()` には代表例を書けます。`example()` は呼んだ順に保持されます。

```kt
"UseCase" {
    title = "ユースケース"
    summary = "各画面で発生するアプリ固有の1つの振る舞い"
    description = """
        UI からは UseCase だけを呼び、Repository を直接触らない。

        ### やってはいけないこと
        - Android の型に依存する
    """.trimIndent()

    example("GetUserUseCase", "ユーザーを取得する")
    example("UpdateUserUseCase", "ユーザーを更新する")
}
```

</details>

<details>
<summary>置き場所を宣言する</summary>

置き場所を1つだけ持つ場合です。

```kt
"UseCase" {
    layout {
        "src/main/kotlin/com/example/useCase" / "*UseCase".ktFile()
    }
}
```

**`layout { }` は複数書けます。** 同じ種類のファイルが複数の場所に現れてよい、という宣言です。種類の違うファイルは、Role を分けて宣言します。

```kt
"Screen" {
    // feature-home のモジュールに
    layout {
        "feature-home/src/main/kotlin/screen" / "*Screen".ktFile()
    }

    // feature-settings のモジュールにも
    layout {
        "feature-settings/src/main/kotlin/screen" / "*Screen".ktFile()
    }
}
```

**宣言していない場所にファイルを置くと違反になります。**

```text
src/main/kotlin/com/example/GetUserUseCase.kt   ← useCase/ の外に置いてしまった
```

テストを実行すると、katachi が近い置き場所と直し方まで1つのブロックで報告します。

```text
Katachi check failed: 1 violation (Unexpected: 1)

[UnexpectedFile] file:///path/to/project/src/main/kotlin/com/example/GetUserUseCase.kt
  No role is defined for this file.

  Nearby locations:
    UseCase file:///path/to/project/src/main/kotlin/com/example/useCase/

  How to fix:
    - Move it to one of the locations above
    - Delete it if it is not needed
    - Add a new role for it:
        "GetUserUseCase" {
          summary = "TODO"
          layout {
            "src/main/kotlin/com/example" / "GetUserUseCase".ktFile()
          }
        }
```

</details>

<details>
<summary>中身に制約をかける</summary>

制約には名前を付けます。**この名前がそのまま違反レポートに出ます。**

```kt
"UseCase" {
    layout {
        "src/main/kotlin/com/example/useCase" / "*UseCase".ktFile()
    }

    "invoke 関数を1つだけ持つこと".konsist {
        classes().must { it.functions().map { f -> f.name } == listOf("invoke") }
    }
}
```

制約の対象は、その Role の `layout { }` に当てはまったファイルです。`layout { }` の無い Role には制約を書けません。

`mustNot` と `mustBeEmpty()` で禁止も書けます。

```kt
"RepositoryInterface" {
    layout {
        "src/main/kotlin/com/example/repository" / "*Repository".ktFile()
    }

    // 満たすべき条件を否定で書く
    "internal にしないこと".konsist {
        classes().mustNot { it.hasInternalModifier }
    }

    // 該当するものが1つも無いことを求める
    "Impl を同じ場所に置かないこと".konsist {
        classes().filter { it.hasNameEndingWith("Impl") }.mustBeEmpty()
    }
}
```

**制約を満たさないクラスがあると違反になります。**

```kt
// useCase/ の中に置かれているが、invoke 以外の関数を持っている
class GetUserUseCase(private val repository: UserRepository) {
    suspend fun invoke(id: String) = repository.find(id)
    fun clearCache() = repository.clear()   // ← これ
}
```

テストで `assert(FileConstraintCheck())` を呼ぶと、配置の違反と同じレポートに並びます。**どのファイルの何行目かまで出ます。**

```text
[UnsatisfiedFileConstraint] file:///path/to/project/src/main/kotlin/com/example/useCase/GetUserUseCase.kt
  Role: UseCase / FileConstraint: "invoke 関数を1つだけ持つこと"
  Declaration: GetUserUseCase (line 3)
  Declared at: ProjectArchitecture.kt:12
```

</details>


なお、Role の名前はドキュメント生成などで使うため、ASCII（英字で始まり、英数字・`_`・`-` が続く）しか使えません。日本語の表示名は `title` に書きます。

## Role のグループ

`"グループ名".group { ... }` を使うと、Roleをグループにまとめられます。

```kt {2}
architecture {
    "domain".group {
        "UseCase" {
            // ...
        }

        "RepositoryInterface" {
            // ...
        }
    }
}
```

## Role 設計のベストプラクティス

### 良い Role 定義は変更を必要としない

**アプリに機能を足すたびに `architecture { }` を書き換える必要があるなら、その `architecture { }` の定義は悪い設計です。**

`architecture { }` がディレクトリ構造の写しになって二度手間にならないよう、`*` や `**` といったワイルドカードをうまく使ってください。

### 1つの Role には1種類のファイルだけを入れる

**1つの Role は、1種類のファイルだけを表すようにしてください。**
たとえば Repository の interface と実装は、名前は似ていても別の種類のファイルです。1つの Role にまとめると、説明・制約・テンプレートが「どちらのファイルの話か」で分かれてしまい、`konsist { }` の制約にも「interface なら…、実装なら…」という条件分岐が入ります。
種類ごとに Role を分ければ、説明も制約もその種類のファイルだけを対象にでき、生成されるドキュメントも種類ごとのページになります。

<CodeComparison>
    <CodeComparison.Panel label="🆖 2種類のファイルを1つの Role に" labelVariant="danger">

    ```kt {4-5,7-8} nocollapse
    "Repository" {
        summary = "データの取得と保存"
        layout {
            "repository" / "*Repository".ktFile()      // interface
            "repository" / "*RepositoryImpl".ktFile()  // 実装
        }
        "interface なら宣言だけ、実装なら…".konsist {
            // どちらのファイルかで制約を分ける必要がある
        }
    }
    ```

    </CodeComparison.Panel>

    <CodeComparison.Panel label="🆗 種類ごとに Role を分ける" labelVariant="success">

    ```kt {1,3,9,11} nocollapse
    "RepositoryInterface" {
        summary = "データの取得と保存の窓口"
        layout { "repository" / "*Repository".ktFile() }
        "interface だけを置くこと".konsist {
            classes().mustBeEmpty()
        }
    }

    "RepositoryImplementation" {
        summary = "RepositoryInterface の実装"
        layout { "repository" / "*RepositoryImpl".ktFile() }
    }
    ```

    </CodeComparison.Panel>
</CodeComparison>

## Role 定義を分割する

プロジェクトが大きくなるほど、`architecture { }` の定義は肥大化します。

`architecture { }` は Kotlin DSL なので、普通の Kotlin と同じように拡張関数へ切り出せます。

ここではおすすめの **Role 定義の分割方法** を紹介します。

### `architecture { }` 定義を分割する

膨らみがちなのは **各 Role 内の詳細** です。
Role ごとに拡張関数に切り出しましょう。

<CodeComparison>
    <CodeComparison.Panel label="分割前">

    ```kt title=ProjectArchitecture.kt nocollapse
    architecture {
        "UseCase" {
            layout { 
                "src/main/kotlin/com/example/project/useCase" / "*UseCase".ktFile()
            }
            "invoke 関数を1つだけ持つこと".konsist {
                classes().must { it.functions().map { f -> f.name } == listOf("invoke") }
            }
            // ...
        }

        "RepositoryInterface" {
            layout { 
                "src/main/kotlin/com/example/project/repository" / "*Repository".ktFile()
            }
            "class を置かないこと".konsist {
                classes().mustBeEmpty()
            }
            // ...
        }

        // ...
    }
    ```

    </CodeComparison.Panel>

    <CodeComparison.Panel label="分割後" labelVariant="success">

    <FileTree>
        - ProjectArchitecture.kt
        - **domain/**
            - UseCaseRole.kt
            - RepositoryInterfaceRole.kt
    </FileTree>

    ```kt title=ProjectArchitecture.kt
    architecture {
        useCase()
        repositoryInterface()

        // ...
    }
    ```

    ```kt title=domain/UseCaseRole.kt
    // hint: DeclarationContainerScope は architecture { } と "...".group { } のレシーバです。
    fun DeclarationContainerScope.useCase() = "UseCase" {
        layout {
            "src/main/kotlin/com/example/project/useCase" / "*UseCase".ktFile()
        }
        "invoke 関数を1つだけ持つこと".konsist {
            classes().must { it.functions().map { f -> f.name } == listOf("invoke") }
        }
        // ...
    }
    ```

    ```kt title=domain/RepositoryInterfaceRole.kt
    fun DeclarationContainerScope.repositoryInterface() = "RepositoryInterface" {
        layout {
            "src/main/kotlin/com/example/project/repository" / "*Repository".ktFile()
        }
        "class を置かないこと".konsist {
            classes().mustBeEmpty()
        }
        // ...
    }
    ```

    </CodeComparison.Panel>

</CodeComparison>

### Role 定義の中を分割する

Role 定義の中でも、特に `"...".konsist { }` は、制約が多かったり複雑になったりして肥大化しがちです。

`"...".konsist { }` は、積極的に private な拡張関数へ切り出しましょう。

<CodeComparison>
    <CodeComparison.Panel label="分割前">
      ```kt
        fun DeclarationContainerScope.useCase() = "UseCase" {
            layout {
                "src/main/kotlin/com/example/project/useCase" / "*UseCase".ktFile()
            }
            "invoke 関数を1つだけ持つこと".konsist {
                classes().must { it.functions().map { f -> f.name } == listOf("invoke") }
            }
            // ...
        }
      ```
    </CodeComparison.Panel>

    <CodeComparison.Panel label="分割後" labelVariant="success">
      ```kt
        fun DeclarationContainerScope.useCase() = "UseCase" {
            layout {
                "src/main/kotlin/com/example/project/useCase" / "*UseCase".ktFile()
            }

            onlyInvokeFun()
            // ...
        }

        private fun RoleScope.onlyInvokeFun() = 
            "invoke 関数を1つだけ持つこと".konsist {
                classes().must { it.functions().map { f -> f.name } == listOf("invoke") }
            }
      ```
    </CodeComparison.Panel>
</CodeComparison>

## まとめ

- Role は **プロジェクトの中に現れてよいファイルの種類** です。パッケージやモジュールとは独立しています。
- 1つのRoleに、説明（`title` / `summary`）・置き場所（`layout { }`）・制約（`konsist { }`）が **まとまります**。
- **Role 定義はすぐに肥大化します。** `architecture { }` や Role 定義が膨らまないよう、拡張関数を使って適切に分割しましょう。

```kt
architecture {
    "domain".group {
        "UseCase" {
            summary = "各画面で発生するアプリ固有の1つの振る舞い"

            // 置き場所が増えても、説明はこの1箇所のまま
            layout {
                "core/domain/src/main/kotlin/useCase" / "*UseCase".ktFile()
            }

            "invoke 関数を1つだけ持つこと".konsist {
                classes().must { it.functions().map { f -> f.name } == listOf("invoke") }
            }
        }
    }
}
```

## 次のステップ

- [Layout](/katachi/ja/guides/layout/) ... Roleのファイルをどこに置けるか宣言する方法を学びましょう。
- [Konsist との統合](/katachi/ja/guides/konsist-integration/) ... Roleのファイルの中身に制約をかける方法を学びましょう。
- [初めてのアーキテクチャ定義](/katachi/ja/get-started/first-architecture/) ... あなたのプロジェクトに katachi を導入して、実際に Role を書いてみましょう。