# The Twelve-Factor App
[The Twelve-Factor App](https://12factor.net/) 文章主要是探討 SaaS(Software as a Service) 開發的方法論,主要作者為 **Heroku** cofounder 及 CTO 的 **Adam Wiggins**,,曾參與過上百個專案開發與部署,並透過 Heroku 平台見證了數十萬應用程式的開發、營運、擴展。
適合所有 Service 開發者及維運人員閱讀。
:::info
下面就針對筆者的閱讀提供簡易的整理,方便大家理解。
:::
## 前言
作者提出的方法論
1. Use declarative formats for setup automation, to minimize time and cost for new developers joining the project;
2. Have a clean contract with the underlying operating system, offering maximum portability between execution environments;
3. Are suitable for deployment on modern cloud platforms, obviating the need for servers and systems administration;
4. Minimize divergence between development and production, enabling continuous deployment for maximum agility;
5. And can scale up without significant changes to tooling, architecture, or development practices.
:::info
作者提到以上方法論沒有限定的程式語言與後端開發服務組合
:::
這邊引用 <https://dzone.com> 整理的圖,整理的很不錯,可以先對整個方法論有個概覽

## I. Codebase
### One codebase tracked in revision control, many deploys

:::info
- 使用版本控制工具對 Source Code 進行控管,可以使用 Git, Mercurial 或是 Sbuversion
- 一份 codebase 就是指一個 code repo
- 每個 App 都應該只對應一個 codebase
- 如果一個程式有多個 codebase,那他就不是一個 App,他應該是一個分散式系統,系統中每個部件即為一個 App,而每個部件都可以各自遵守 **twelve-factor**
:::
:::danger
- 多個 Apps 共享同一份 code 違反了 twelve-facor,解法為將共同的程式碼部分抽出透過 [Dependency Manager](#II-Dependencies) 作為 Library 引入
:::
## II. Dependencies
### Explicitly declare and isolate dependencies
目前大多數的程式語言都有提供套件(package)管理工具,例如 python 使用 pip、golang 使用 go mod。
而 package 又可分為兩種 scope,**system scope**(site package) 及 **local scope** (vendoring or bundling)
:::info
- 使用相依套件管理工具管理你的專案套件
- 套件管理工具最好可以保證相依套件都是使用 local scope,避免隱式使用 system scope package,例如 python 使用 virtualenv 做隔離
-
:::
:::danger
- 千萬不要讓你的 App 依賴 system scope 的套件,例如 curl 指令,雖然他存在於大數的系統中,但無法被保證,你應該想辦法將它包進 local package,例如 python 可以使用 requests package 替代。
:::
## III. Config
### Store config in the environment
部署 App 時,我們常常透過 config 去控制 app 運作行為,例如
- 開發時指定為 dev 模式,輸出更多 debug log 方便開發與除錯。*staging production 亦然*
- 相依服務的憑證,如 Amazon S3、database
- 部署的實例名稱,例如 k8s 指定 nodename
:::info
- 將 config 獨立於 codebase,每次部署時 config 可能都有很大的變化,但 codebase 並不會
- 想知道 config 有沒有正確地從 codebase 獨立出來的標準是,該 codebase 是否可以隨時 open source,而不會洩漏任何憑證或機敏資訊
- 建議使用環境變數而不是 config file 管理,config file 可以有很多種格式,不同程式語言之間的慣例又不同,使用起來相對麻煩,也容易不小心被 add 到 repo 裡,相反環境變數有著良好的規範,針對不同程式語言以及作業系統都有著相同的格式
:::
:::danger
- 不要將 config 寫死在 code 裡
:::
## IV. Backing services
### Treat backing services as attached resources

:::info
- App 不應該區分本地或第三方服務,對 App 而言都是透過 URL 或其他憑證存取
- App 應該可以切換本地 database 到第三方 database (如 Amazon RDS),只需要更改 config,而不需要更改 code
- 每個連結的服務都是一份資源(resource),這些資源跟其連結的 App 保持松耦合關係
- 資源可以隨意被附加或分離,例如 database 出現異常,管理員可以從先前的備份啟動一個新的 database 並連結上 App,異常 database 則先斷開連結
:::
## V. Build, release, run
### Strictly separate build and run stages

要將 codebase 轉換為可部署資源需要經過以下三個步驟
1. Build stage: 將 codebase 轉換為可執行程式,包含相依套件下載、連結、編譯等等,最後產出二進制檔或是資源或是其他資源
2. Release stage: 將 Build stage 產出的資源,與此次部署相對應的 config 打包起來,產出可以立即在部署環境上執行的包
3. Run stage: 又稱為 Runtime,意指將 release 包運作在部署環境上
:::info
- 應該嚴格的拆分 build, release 跟 run stage
- 每份 Release 都應該有一個唯一的 ID,例如 timestamp 或是 git hash
- 要更動 Release,應該要建立一份新的 Release 版本,任何對現有 Release 的修改都是不應該的
:::
:::danger
- 不要在 runtime 階段修改程式碼,你應該修改 codebase 再出一個版本
:::
## VI. Processes
### Execute the app as one or more stateless processes
:::info
- Processes 最好是**無狀態**且**資源獨立**
- 任何需要保存的資料必須被保存到一個有狀態的後端服務,通常是 database
:::
:::danger
- web system 不應該依賴 **sticky sessions**,是指把 user session data 保存在 process 的記憶體中並希望未來的請求也會被送到同一個 process 處理,相反你應該保存在帶有過期機制的緩存中(Memcached or Redis)
:::
## VII. Port binding
### Export services via port binding
:::info
- App 應該可以自我監聽端口以提供服務,如 <http://localhost:8080>
- 並不僅限於 HTTP,其他的還有 grpc 或是 redis 的 Redis serialization protocol (RESP)
- Port binding 方法代表你的 App 可以容易的成為其他 App 的 backing service,你只需要在你的 config 裡加入 backing service 的連結資訊即可
:::
:::danger
- 如[Dependency Manager](#II-Dependencies)提到,盡量不要使用 system scope 的 webserver 應用來提供服務,你應該要將 webserver library 加入到 local scope 依賴來使用(如 python 的 Tornado)
:::
## VIII. Concurrency
### Scale out via the process model

:::info
- Process are a first class citizen
- 使用 [Unix process model](https://adam.herokuapp.com/past/2011/5/9/applying_the_unix_process_model_to_web_apps/) 將不同類型的工作分配給對應的 Process Type 處理,如 HTTP 請求交給 web process、需要長時間運行的後台任務交由 worker process 處理
- 對 share-nothing、stateless 的 process 增加 concurrency 會非常的容易且可靠
:::
:::danger
- App process 不應該 daemonize,應該交由其他如系統 process manager(systemd),或是雲平台如 kubernetes
:::
## IX. Disposability
### Maximize robustness with fast startup and graceful shutdown
:::info
- Process 應盡量縮減啟動時間,這將會增加 scaling 的彈性及速度,理想上是幾秒鐘
- 當 Process 接收到 SIGTERM signal 時,應該進行 gracefully shutdown
- Process 應該足夠健全以面對 crash 的發生
:::
## X. Dev/prod parity
### Keep development, staging, and production as similar as possible
從過往經驗來看,開發跟正式環境總是會有很大的差距,比如說開發人員會在開發環境做一些 live patch 或是一些暫時性的操作,這些差異主要分為三個區塊
1. 時間差距:開發者通常會寫 幾天、幾星期、甚至幾個月的程式碼,才將程式交付到正式環境
2. 個人差距:開發者寫程式碼,維運人員部署
3. 工具差距:開發者可能會使用像是 Nginx、SQLite,或是不同的作業系統如 OSX,但是部署的時候卻是使用別的工具,如 Apache、MySQL、Linux
:::info
- 建立 CD 流程,減少開發和正式環境的差距
- 減少時間差距:開發人員每幾個小時或是幾分鐘就部署一次程式碼
- 減少個人差距:開發人員密切參與部署工作,並觀察程式在正式環境的行為
- 減少工具差異:盡可能保持開發及正式環境的一致性
| | 傳統應用 | 12Factor應用 |
| ---------- | ------- | ----------- |
| 每次部署間隔 | 幾星期 | 幾小時 |
| 開發及部署人員| 不同人 | 同一人 |
| 開發及正式環境| 差異化 | 盡可能一致 |
:::
:::danger
- 不建議在開發和正式環境使用不同的後端服務,如開發使用 SQLite 正式環境使用 MySQL,雖然現在大多程式語言的 Library 做了良好的抽象,讓你可以輕易地切換後端服務,但實務上還是會有微小的相容性問題
:::
## XI. Logs
### Treat logs as event streams
:::info
- 將事件流直接輸出到 stdout 即可
- 讓執行環境負責將每個 process 的 log 彙整起來並導向最終的位置,這樣方便長期保存以及後續觀看。
- log 導向可以使用 Logplex 和 Fluentd
- 搜集好的 log 可以使用 Splunk 或 Hadoop/Hive 之類的工具來做分析
:::
:::danger
- 不要讓 App 自己處理事件流的導向及存儲
:::
## XII. Admin processes
### Run admin/management tasks as one-off processes
:::info
- 開發人員常常使用一次性指令或腳本來管理或維護 App
- 這可能包含:Database Migration、REPL shell 或其他維護腳本
- 一次性管理程式應該和你的 App 使用相同的環境
:::