# ArchitectureProcessor とそのカスタマイズ







このページでは、katachi の重要な構成要素の 1 つである ArchitectureProcessor を解説し、katachi の理解を深めます。

後半では、独自の ArchitectureProcessor を書いて実行する方法を解説します。

## ArchitectureProcessor とは

katachi の `ArchitectureProcessor` は、アーキテクチャ定義をもとに何かを行う interface です。

```kt
@ExperimentalKatachiApi
public interface ArchitectureProcessor<Args, R> {
    // 引数の parser
    public val argsSerializer: KSerializer<Args>

    // 何かを行う
    public fun process(context: ArchitectureProcessContext<Args>): Result<R>
}
```

`process` の本体は `runCatching { }` (もしくは `runCatchingScoped` ユーティリティ) で囲ってください。`runCatchingScoped` は、見つかった不足を `failure(...)` で全部貯めて、最後にまとめて失敗にできます（[sample/custom-processor の RoleDocCoverage.kt](https://github.com/TBSten/katachi/blob/main/sample/custom-processor/architecture-test/src/test/kotlin/com/example/processors/RoleDocCoverage.kt)）。

| 戻り値 | 意味 | Gradle タスクでの扱い |
| --- | --- | --- |
| Result.success | 成功 | `[OK]` |
| Result.failure | 失敗 <br/> （違反が見つかった、ドキュメントが古い、処理ができなかった など） | `[FAILED]`。例外のメッセージを表示し、exit が非ゼロになる |

## katachi の 2 要素

katachi は大きく分けて、**アーキテクチャ定義** と **ArchitectureProcessor** の 2 つの要素でできています。

```mermaid
flowchart LR
    D["architecture { }<br/>アーキテクチャ定義"]
    D --> Layout["LayoutCheck<br/>配置を検証する"]
    D --> Konsist["FileConstraintCheck<br/>konsist { } の制約を検証する"]
    D --> Doc["GenerateDocumentation<br/>ドキュメントを生成"]
    D --> Template["GenerateCodeFromTemplate<br/>コードを生成"]
    D -.-> Custom["あなたの ArchitectureProcessor<br/>集計・抽出・別形式への出力"]

    style D fill:#5B8DEF22,stroke:#5B8DEF,stroke-width:2px
    style Custom fill:#BDBDBD22,stroke:#BDBDBD,stroke-dasharray:4 3,color:#8A8A8A
```

アーキテクチャ定義そのものは何も実行しません。**`ArchitectureProcessContext` を受け取って何かをするのが ArchitectureProcessor** です。

- アーキテクチャ定義は、プロジェクトのアーキテクチャを `architecture { }` ブロックの中に書いたものです。
- アーキテクチャを定義しただけでは何も起こりません。その定義を使って何かを行うのが ArchitectureProcessor です。

## ビルトインの ArchitectureProcessor

katachi は 5 つの ArchitectureProcessor を提供しています。

<table>
    <thead>
        <tr>
            <th>Processor</th>
            <th style="min-width: 12rem;">何を行うか？</th>
            <th style="min-width: 10rem;">ドキュメント</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>projectArchitecture.assert()</code><br />(<code>LayoutCheck</code>)</td>
            <td>ファイル配置がアーキテクチャ定義に沿っているかを検証し、エラーがあればアサーション例外を発生させます。</td>
            <td></td>
        </tr>
        <tr>
            <td><code>FileConstraintCheck</code></td>
            <td><code>{"konsist { }"}</code> に書かれた制約を評価します。<code>projectArchitecture.assert(FileConstraintCheck())</code> のように渡します。</td>
            <td><a href="/katachi/ja/guides/konsist-integration/">Konsist との統合</a></td>
        </tr>
        <tr>
            <td><code>GenerateDocumentation</code></td>
            <td>プロジェクト定義に書かれた summary, description などのプロパティをもとにドキュメントを生成します。</td>
            <td><a href="/katachi/ja/guides/document-generation/">ドキュメント生成</a></td>
        </tr>
        <tr>
            <td><code>GenerateCodeFromTemplate</code></td>
            <td>layout のファイルの宣言に付けた <code>{".template { }"}</code> の内容をもとにファイルを生成します。</td>
            <td><a href="/katachi/ja/guides/generate-code-from-template/">テンプレートからのコード生成</a></td>
        </tr>
        <tr>
            <td><code>DescribeTemplates</code></td>
            <td><code>{".template { }"}</code> を持つ宣言の一覧や、1つのテンプレートのパラメータと生成されるファイルを表示します。ファイルは書きません。</td>
            <td><a href="/katachi/ja/guides/generate-code-from-template/">テンプレートからのコード生成</a></td>
        </tr>
    </tbody>
</table>

いずれもプロジェクトのアーキテクチャ定義を受け取り、検証や生成に使っています。

## ArchitectureProcessor の実行

ArchitectureProcessor の実行方法にはいくつかのパターンがあります。

推奨される実行方法は ArchitectureProcessor ごとに異なります。個々の実行方法は、それぞれのドキュメントを参照してください。

<Tabs>
    <TabItem label="API をプログラムから呼び出す">

ArchitectureProcessor に API（多くの場合トップレベル関数）が用意されていれば、その API を呼び出して ArchitectureProcessor を実行できます。

例えば LayoutCheck なら、`projectArchitecture.assert()` を呼び出すと実行できます。

```kt
projectArchitecture.assert() // 内部で LayoutCheck().process(...) を呼び出す
```

    </TabItem>
    <TabItem label="コマンドラインから実行する">

コマンドラインから実行するには、ArchitectureProcessor を Gradle plugin に登録しておく必要があります。

```kts title="architecture-test/build.gradle.kts" {5-6}
katachi {
    processors {
        // ...

        // ⭐️ ArchitectureProcessor 名 と 対応するクラス名を指定する
        register("someProcessor", "com.example.SomeProcessor")
    }
}
```

また、登録と同時にデフォルト値を設定することもできます。

```kts title="architecture-test/build.gradle.kts"
katachi {
    processors {
        // 登録と同時にデフォルト値を書く
        register("someProcessor", "com.example.SomeProcessor") {
            arg("someArg1", "someValue1")
        }

        // 既に登録されている processor にデフォルト値だけ足す
        args("docs") {
            arg("mode", "check")
        }
    }
}
```

登録した processor ごとに Gradle タスクが用意されます。
タスク名は `katachi` + 登録キーの先頭を大文字にしたものです（例: `register("someProcessor", ...)` → `katachiSomeProcessor`）。

実行例:

```
$ ./gradlew katachiSomeProcessor
[1/3] Processors: someProcessor

[2/3] Processing...

========================================
[3/3] Katachi processor run: 1 succeeded, 0 failed

[OK] someProcessor

========================================
```

    </TabItem>
</Tabs>

---

## カスタムの ArchitectureProcessor

ArchitectureProcessor は単純な interface なので、自分で実装して自由に処理を追加できます。

GitHub リポジトリの sample/custom-processor モジュールに、カスタム ArchitectureProcessor の例があります。

<LinkCard title="sample/custom-processor" description="引数なし・引数あり・検査する processor の 3 本と、その登録" href="https://github.com/TBSten/katachi/tree/main/sample/custom-processor" />

### 1. ArchitectureProcessor を実装する

ArchitectureProcessorNoArg か ArchitectureProcessor を実装したクラスを用意し、process メソッドを実装してください。

process メソッドに渡される `context` には、アーキテクチャ定義や引数などが入っています。これらを必要に応じて参照しながら、ArchitectureProcessor のロジックを書きます。

<Tabs>
    <TabItem label="引数なし">
        引数が必要ない ArchitectureProcessor の場合は `ArchitectureProcessorNoArg` interface を実装してください。

        型引数には実行時の戻り値の型を指定します。

        ```kt {2,3}
        // ArchitectureProcessorNoArg を実装した object を定義
        object CountFilesPerRole : ArchitectureProcessorNoArg<Map<String, Int>> {
            override fun process(context: ArchitectureProcessNoArgContext): Result<Map<String, Int>> = runCatching {
                // context.architecture    ... アーキテクチャ定義
                // context.roles / groups  ... 定義された Role・group
                // context.declaredEntries ... layout を flat にした宣言の一覧
                // context.filesOf(role)   ... その Role にマッチした実ファイル
                // context.rawArgs         ... 渡された引数（名前と値）をそのまま
                // context.log()           ... 進捗を表示します

                context.log("Counting files per role...")
                context.roles.associate { role -> role.name to context.filesOf(role).size }
            }
        }
        ```
    
    </TabItem>
    <TabItem label="引数あり">
        katachi は引数のパースに [kotlinx.serialization](https://github.com/kotlin/kotlinx.serialization) を使うため、プロジェクトに kotlinx.serialization の依存を追加してください。

        ```kts {3} title="architecture-test/build.gradle.kts" 
        plugins {
            kotlin("jvm") version "<kotlin-version>"
            kotlin("plugin.serialization") version "<kotlin-version>"
        }
        ```        

        引数が必要な ArchitectureProcessor の場合は `ArchitectureProcessor` interface を実装してください。

        第1型引数に引数の型、第2型引数に戻り値の型を指定します。

        ```kt {1, 3, 5, 16-17} nocollapse
        object CountFilesPerRole : ArchitectureProcessor<CountFilesPerRole.Args, Map<String, Int>> {
            // --arg で受け取る値の型と、その読み方を渡します
            override val argsSerializer: KSerializer<Args> = Args.serializer()

            override fun process(context: ArchitectureProcessContext<Args>): Result<Map<String, Int>> = runCatching {
                // context.args を使って引数を参照する
                val args: CountFilesPerRole.Args = context.args
                val prefix = args.prefix

                context.log("Counting files per role under $prefix...")
                context.roles
                    .filter { role -> role.qualifiedName.startsWith(prefix) }
                    .associate { role -> role.name to context.filesOf(role).size }
            }

            @Serializable
            data class Args(val prefix: String = "")
        }
        ```

    </TabItem>
</Tabs>

`context` から取得できる情報の詳細は [`ArchitectureProcessContext` の API リファレンス](/katachi/api-docs/katachi/me.tbsten.katachi.processor/-architecture-process-context/index.html) を参照してください。


<details>
    <summary>Role の metadata</summary>

    場合によっては、ArchitectureProcessor の側で、アーキテクチャ定義に独自のプロパティを指定してほしくなるかもしれません。
    その場合は **Role の metadata** を使えます。

    例えば、すでに非推奨なのにそのまま残っている Role を Obsolete としてマークしたいなら、以下のように Obsolete という metadata を定義して設定できます。

    ```kt nocollapse
    // metadata のキー
    val Obsolete: MetadataKey<Boolean> = metadata()

    var MetadataScope.obsolete: Boolean? by Obsolete

    // Processor 側
    object ObsoleteRoles : ArchitectureProcessorNoArg<List<String>> {
        override fun process(context: ArchitectureProcessNoArgContext): Result<List<String>> = runCatching {
            context.roles.filter { it[Obsolete] == true }.map { it.name }
        }
    }

    // 利用側
    architecture {
        "UseCase" {
            obsolete = true
        }
    }
    ```

</details>

### 2. ArchitectureProcessor を実行する方法を用意する

利用者が ArchitectureProcessor を実行する方法を用意してください。

プログラムから実行したいなら API を用意する方法を、ワンショットで実行したいならコマンドラインから実行する方法をおすすめします。

<Tabs>
    <TabItem label="API を用意する">

        `Architecture` の拡張関数を用意します。katachi が `assert()` を用意しているのと同じ形です。

        ```kt
        fun Architecture.countFilesPerRole(): Map<String, Int> = process(CountFilesPerRole, CountFilesPerRole.Args()).getOrThrow()
        ```

        引数なしの定義の場合は、`process(CountFilesPerRole)` と書きます。

        利用者は processor を知らなくても、用意された API を呼び出せます。

        ```kt
        val counts = projectArchitecture.countFilesPerRole()
        ```

    </TabItem>

    <TabItem label="Gradle タスクから実行する">

        `katachi.processors.register` に登録すると、登録したキーから決まる名前の Gradle タスクとして呼べるようになります。

        （例: `countFilesPerRole` → `katachiCountFilesPerRole`）

        ```kts title="architecture-test/build.gradle.kts"
        katachi {
            processors {
                register("countFilesPerRole", "com.example.processors.CountFilesPerRole")
            }
        }
        ```

        ```shell
        ./gradlew katachiCountFilesPerRole
        ```

        `--arg` の値は `Args` のフィールドの型に合わせて読まれます。カンマで分割される型などの細かい決まりは [`decodeFromStringMap` の API リファレンス](/katachi/api-docs/katachi/me.tbsten.katachi.processor/decode-from-string-map.html) を参照してください。

    </TabItem>
</Tabs>

---

用意した実行方法は **processor の KDoc などにドキュメントとして書き、実行方法まで利用者に伝えてください**。

## まとめ

- **`assert()` もドキュメント・テンプレート生成も ArchitectureProcessor** です。
- 標準の ArchitectureProcessor が使う仕組みと同じ仕組みで、カスタムの ArchitectureProcessor を作れます。

## 次のステップ

- [ArchitectureProcessor の API リファレンス](/katachi/api-docs/katachi/me.tbsten.katachi.processor/-architecture-processor/index.html) ... `ArchitectureProcessor` の型と、`process` で使える API の一覧です。
- [ドキュメント生成](/katachi/ja/guides/document-generation/) ... 同じ仕組みに乗っている、いちばん大きなビルトイン processor です。
    - ドキュメント生成の ArchitectureProcessor の [KDoc](https://tbsten.github.io/katachi/api-docs/katachi/me.tbsten.katachi.docs/-generate-documentation/index.html), [ソースコード](https://github.com/TBSten/katachi/blob/main/katachi/src/main/kotlin/me/tbsten/katachi/docs/GenerateDocumentation.kt#L88) も確認してください。
- [テンプレートからコード生成](/katachi/ja/guides/generate-code-from-template/) ... 定義からコードを出す processor です。
    - テンプレートからコード生成の ArchitectureProcessor の [KDoc](https://tbsten.github.io/katachi/api-docs/katachi/me.tbsten.katachi.template/-generate-code-from-template/index.html), [ソースコード](https://github.com/TBSten/katachi/blob/main/katachi/src/main/kotlin/me/tbsten/katachi/template/GenerateCodeFromTemplate.kt#L114) も確認してください。