
我原本以為把 shared logic 抽成 reusable workflows 會比較乾淨。

結果實際做完之後, 我最後把它們又收回到入口 workflows, 只保留 composite actions。不是因為 reusable workflows 不能用, 而是因為它在這個 case 裡, 沒有帶來我真正想要的「乾淨」。

這篇把我最後的取捨整理下來。

## 一開始為什麼會想用 reusable workflows

理由其實很合理。

如果 `deploy-dev` 和 `deploy-prod` 都有這些步驟:

- test
- build
- migrate
- terraform deploy
- health check

那直覺上就會想抽出兩條 reusable workflows:

```text
service-build-test
service-deploy
```

然後入口 workflows 變成很薄的協調層。

這種設計看起來有幾個明顯優點:

- less YAML duplication
- one shared implementation path
- easier to update one common pipeline

在紙面上很漂亮。

## 問題不是功能, 是操作者體驗

真正讓我改主意的不是功能失敗, 而是操作面和可讀性。

GitHub Actions UI 會把 reusable workflows 也顯示成獨立 runs。這件事帶來兩個實際問題:

1. 操作者會看到太多 workflows
2. 責任歸屬會變得沒那麼清楚

我真正想要的 UI 是:

```text
CI
Deploy Dev
Deploy Prod
Infrastructure
```

但用了 reusable workflows 之後, Actions 頁面會多出內部 workflow 項目。技術上它們沒有壞, 但對平常操作的人來說會變得很雜亂。

## 可讀性不是只有 UI, 也包括原始碼

除了 UI, source code 的追蹤成本也變高。

當 deploy 出錯時, 你常常要來回跳:

- 入口 workflow
- reusable workflow
- composite action
- Terraform root

這條追查路徑不是不能走, 但會比直接在入口 workflow 讀完整 pipeline 還慢。

尤其當 workflow 又牽涉到:

- branch guard
- environment-scoped vars
- job-level permissions
- OIDC auth
- concurrency

如果這些東西拆在不同 workflow file, review 的認知負擔會變高。

## 我後來怎麼分辨什麼該抽, 什麼不該抽

我最後用一個很簡單的判準。

### 適合保留成 composite action 的情況

如果它只是 step group, 適合抽成 composite action。

例如:

- install uv and sync dependencies
- install Cloud SQL Proxy

這種東西抽出來很合理, 因為:

- no job graph
- no permissions model
- no environment decision
- no workflow identity ambiguity

### 適合留在入口 workflow 的情況

如果它本身就是 deploy pipeline 的主體, 我現在更傾向留在 入口 workflow。

例如:

- build and push image
- run migration
- terraform plan and apply
- health checks

這些步驟跟 branch、environment、permissions、identity 都強相關。留在入口 workflow 比較容易審查和除錯。

## 這是一個 DRY vs clarity 的取捨

本質上這不是技術對錯, 是取捨。

| Option | Advantage | Cost |
|--------|-----------|------|
| Reusable workflows | less duplication | worse UI and more indirection |
| Flat 入口 workflows | more explicit | some duplication |
| Composite actions | reuse step groups cleanly | cannot model full workflow graphs |

在我這次的 case 裡, 真正要優先的是:

- readable
- maintainable
- 乾淨的操作者入口面

所以我最後選的是:

- flat 入口 workflows
- composite actions for setup steps
- accept some duplication

## 什麼樣的 duplication 是可以接受的

不是所有重複都該消滅。

如果 `deploy-dev` 和 `deploy-prod` 都各自有一份:

- build step
- migrate step
- terraform apply step

這在某些 case 其實是可以接受的。

因為它換來的是:

- 每條 workflow 都能自成一體
- 每個環境路徑都很清楚
- 權限審查比較直觀
- 操作者不需要理解 workflow 的內部接線

對中小型 repo 來說, 這種重複常常比高度抽象更好維護。

## 什麼時候 reusable workflow 仍然值得用

我不會說 reusable workflow 不該用。

它在這些情況還是有價值:

- many repositories share the same workflow
- the workflow graph itself is the reusable asset
- UI clutter is acceptable
- 操作者根本不會直接碰 Actions 頁面

但如果你的要求是:

- Actions page must stay clean
- 面向操作者的 workflows 必須一眼看懂
- permissions must be easy to audit

那 reusable workflow 就不一定是最好的共用方式。

## 我最後的 workflow shape

我最後比較滿意的是這樣:

```text
.github/
  workflows/
    ci.yml
    deploy-dev.yml
    deploy-prod.yml
    infra.yml
  actions/
    setup-api-env/
    install-cloud-sql-proxy/
```

這個 shape 的好處是, 面向操作者的 workflows 很清楚, shared setup steps 也沒有變成大量 copy-paste。

## 結論

我這次最大的體會是:

**less YAML is not always less complexity.**

reusable workflows 可以減少重複, 但不一定會讓整個系統更容易理解。

如果你真正在乎的是:

- operator clarity
- reviewability
- permission visibility
- GitHub Actions UI 的整潔度

那 flat 入口 workflows + composite actions 很可能比 reusable workflows 更適合。

## References

- [Reusing workflow configurations](https://docs.github.com/en/actions/concepts/workflows-and-actions/reusing-workflow-configurations)
- [Creating a composite action](https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action)
- [GitHub Actions documentation](https://docs.github.com/en/actions)
