1. 概述
在本教程中,我們將瞭解 十二要素應用程序方法論。
我們還將瞭解如何使用 Spring Boot 開發微服務。 在此過程中,我們將看到如何將十二要素應用程序方法論應用於開發此類微服務。
2. 十二要素方法論是什麼?
十二要素方法論是一套十二個最佳實踐,用於開發能夠作為服務運行的應用。 這最初由 Heroku 為了在其雲平台上部署的服務應用而編寫的,早在 2011 年就完成了。 隨着時間的推移,它已經足夠通用,可以用於任何 軟件即服務 (SaaS) 開發。
那麼,我們所説的軟件即服務是什麼意思呢? 傳統上,我們設計、開發、部署和維護軟件解決方案,以從中獲得業務價值。 但我們並不一定需要進行這個過程才能達到相同的效果。 例如,計算適用的税金是許多領域中的一個通用函數。
現在,我們可能會決定自己構建和管理這個服務,或者 訂閲商業服務提供商。 這樣的 服務提供商 就是我們所説的軟件即服務。
雖然軟件即服務對開發架構沒有任何限制,但採用一些最佳實踐仍然很有用。
如果我們設計我們的軟件,使其在現代雲平台上具有模塊化、可移植性和可擴展性,那麼它將非常適合我們的服務提供商。 這就是十二要素方法論發揮作用的地方。 在教程的後續部分,我們將看到它的實際應用。
3. 使用 Spring Boot 構建微服務
微服務是一種軟件架構風格,用於開發鬆耦合的服務。 關鍵要求是 服務應圍繞業務領域邊界進行組織。 這通常是最難識別的部分。
此外,這裏的服務擁有對其數據的唯一權威,並向其他服務公開操作。 服務之間的通信通常通過輕量級協議(如 HTTP)進行。 這導致了獨立部署和可擴展的服務。
現在,微服務架構和軟件即服務(SaaS)並不依賴於彼此。 但是,很容易理解的是,在 開發軟件即服務時,採用微服務架構具有諸多優勢。 這有助於實現我們之前討論的許多目標,例如模塊化和可擴展性。
Spring Boot 是基於 Spring 的應用程序框架,它消除了開發企業應用程序時大量冗餘代碼。 它為我們提供了一個高度具有主觀性的但靈活的平台,用於開發微服務。 對於本教程,我們將利用 Spring Boot 使用十二要素方法來交付微服務。
4. 應用十二要素方法論
讓我們現在定義一個簡單的應用程序,我們將嘗試使用我們剛才討論的工具和實踐來開發它。我們都喜歡看電影,但很難記住我們已經看過哪些電影。
現在,誰願意開始一部電影,然後稍後放棄它?我們需要的是一個簡單的服務來記錄和查詢我們觀看過的電影:
這是一個相當簡單且標準的微服務,具有數據存儲和 REST 端點。我們需要定義一個模型,以便將其映射到持久化存儲:
@Entity
public class Movie {
@Id
private Long id;
private String title;
private String year;
private String rating;
// getters and setters
}我們已經定義了一個 JPA 實體,其中包含一個 id 和一些其他的屬性。現在讓我們看看 REST 控制器是什麼樣子的:
@RestController
public class MovieController {
@Autowired
private MovieRepository movieRepository;
@GetMapping("/movies")
public List<Movie> retrieveAllStudents() {
return movieRepository.findAll();
}
@GetMapping("/movies/{id}")
public Movie retrieveStudent(@PathVariable Long id) {
return movieRepository.findById(id).get();
}
@PostMapping("/movies")
public Long createStudent(@RequestBody Movie movie) {
return movieRepository.save(movie).getId();
}
}這涵蓋了我們簡單服務的基礎。在後續子章節中,我們將討論如何實施十二要素方法論,同時介紹應用程序的其他部分。
4.1. 代碼庫
最佳實踐之一是使用版本控制系統跟蹤應用程序。 Git 是目前最流行的版本控制系統,並且幾乎無處不在。 原則如下: 應用程序應在一個代碼倉庫中進行跟蹤,並且不得與任何其他應用程序共享該倉庫。
Spring Boot 提供了許多方便的引導應用程序的方法,包括命令行工具和 Web 界面。 一旦我們生成了引導應用程序,就可以將其轉換為 Git 倉庫:
git init此命令應從應用程序的根目錄運行。 在此時,應用程序已經包含一個 .gitignore 文件,它有效地阻止了生成的文件的版本控制。 因此,我們可以立即創建初始提交:
git add .
git commit -m "Adding the bootstrap of the application."最後,如果希望將提交推送到遠程,我們也可以添加遠程並推送提交(這並非嚴格要求):
git remote add origin https://github.com/<username>/12-factor-app.git
git push -u origin master4.2. 依賴關係
接下來,十二要素應用程序 始終應明確聲明其所有依賴關係。我們應該使用依賴關係聲明清單來完成此操作。Java 具有多種依賴管理工具,例如 Maven 和 Gradle。我們可以使用其中之一來實現這一目標。
因此,我們的簡單應用程序依賴於幾個外部庫,例如用於促進 REST API 和連接到數據庫的庫。讓我們看看如何使用 Maven 以聲明方式定義它們。
Maven 要求我們用 XML 文件描述項目的依賴關係,通常稱為 項目對象模型 (POM):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>雖然看起來簡單直接,但這些依賴項通常還存在其他傳遞依賴關係。這在一定程度上增加了複雜性,但也有助於我們達成目標。現在,我們的應用程序沒有明確描述的直接依賴關係。
4.3. 配置
一個應用程序通常具有大量的配置,其中一些配置在部署之間可能不同,而另一些則保持不變。
在我們的示例中,我們有一個持久數據庫。我們需要數據庫的地址和憑據才能連接到它。這些信息很可能在部署之間發生變化。
十二要素應用程序應該對外化所有在部署之間可能不同的配置。 這裏的建議是使用環境變量來管理這些配置。 這有助於將配置與代碼進行清晰的分離。
Spring 提供了一個配置文件,其中我們可以聲明這些配置並將其與環境變量關聯:
spring.datasource.url=jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/movies
spring.datasource.username=${MYSQL_USER}
spring.datasource.password=${MYSQL_PASSWORD}在這裏,我們已將數據庫 URL 和憑據定義為配置,並將實際值映射為從環境變量中提取的值。
在 Windows 上,在啓動應用程序之前,可以設置環境變量:
set MYSQL_HOST=localhost
set MYSQL_PORT=3306
set MYSQL_USER=movies
set MYSQL_PASSWORD=password我們可以使用配置管理工具,例如 Ansible 或 Chef,來自動化這個過程。
4.4. 後台服務
後台服務是指應用程序依賴以進行運行的服務。例如,數據庫或消息中間件。 十二要素應用程序應將所有此類後台服務視為附加資源。 這意味着它不應需要任何代碼更改來交換兼容的後台服務。 唯一的變化應在配置中進行。
在我們的應用程序中,我們使用 MySQL 作為後台服務,以提供持久性。
Spring JPA 使代碼對實際數據庫提供商具有相當的無感性。 我們只需要定義一個倉庫,該倉庫提供所有標準操作:
@Repository
public interface MovieRepository extends JpaRepository<Movie, Long> {
}如我們所見,這並不依賴於 MySQL 本身。Spring 檢測到 classpath 上的 MySQL 驅動程序,並動態提供 MySQL 相關的接口實現。此外,它還會從配置中拉取其他詳細信息。
因此,如果我們決定從 MySQL 遷移到 Oracle,我們只需在依賴項中替換驅動程序並替換配置即可。
4.5. 構建、發佈與運行
十二要素方法嚴格將代碼庫轉換為可運行應用程序的過程劃分為三個獨立的階段:
- 構建階段:在此階段,我們獲取代碼庫,進行靜態和動態檢查,然後生成可執行的包,例如 JAR 文件。 使用 Maven 等工具,這非常簡單:
mvn clean compile test package<ul>
<li>發佈階段:在此階段,我們將獲取可執行包並將其與合適的配置組合。我們這裏可以使用 <a href="https://www.packer.io/">Packer</a> 配合 provisioner(如 <a href="https://www.ansible.com/">Ansible</a>)來創建 Docker 鏡像:</li>
</ul>
packer build application.json<ul>
<li>運行階段:這是應用程序在目標執行環境中運行的階段。如果我們將應用程序作為容器發佈,並使用 <a href="https://www.docker.com/">Docker</a>,那麼運行應用程序可能非常簡單:</li>
</ul>
docker run --name <container_id> -it <image_id>最後,我們並不一定需要手動執行這些階段。 這時,Jenkins 憑藉其聲明式流水線,就顯得非常實用。
4.6. 進程
十二要素應用程序預計將在執行環境中以無狀態進程運行。換句話説,它們不能在請求之間本地存儲持久狀態。 它們可以生成需要存儲在一種或多種狀態化後端服務中的持久數據。
在我們的示例中,我們暴露了多個端點。 任何一個端點上的請求都與之前任何一個請求完全獨立。 例如,如果我們跟蹤用户請求並使用這些信息來為未來的請求提供服務,這違反了十二要素應用程序的要求。
因此,十二要素應用程序不施加此類限制,例如會話粘性。 這使得該應用程序具有很高的可移植性和可擴展性。 在提供自動擴展的雲執行環境中,對於應用程序來説,這是一種非常理想的行為。
4.7. 端口綁定
傳統的 Java Web 應用程序通常以 WAR 或 Web 存檔的形式開發。這通常是一組帶有依賴項的 Servlets,並期望與 Tomcat 等符合規範的容器運行時一起使用。然而,十二要素應用程序則不依賴於任何此類運行時。 它完全自包含,只需一個執行運行時,例如 Java 即可。
在我們的案例中,我們使用 Spring Boot 開發了應用程序。Spring Boot 除了許多其他優點外,還為我們提供了一個默認嵌入式應用程序服務器。因此,我們之前使用 Maven 生成的 JAR 文件只需在具有兼容的 Java 運行時環境中運行,即可執行:
java -jar application.jar在這裏,我們的簡單應用程序通過 HTTP 綁定到特定的端口(例如 8080)來暴露其端點。 啓動應用程序,如上所述,應該可以訪問導出的服務,例如 HTTP。
應用程序可以綁定到多個端口來導出多個服務,例如 FTP 或 WebSocket。
4.8. 併發 (Concurrency)
Java 提供 <em Thread</em> 作為一種經典模型,用於在應用程序中處理併發。線程就像輕量級進程,代表程序中多個執行路徑。線程功能強大,但其在擴展應用程序方面存在侷限性。
十二要素方法論 (Twelve-Factor Methodology) 建議應用程序依賴於進程進行擴展。 這意味着應用程序應該設計成將工作負載分佈到多個進程中。然而,單個進程可以內部利用併發模型,例如 <em Thread</em>。
當 Java 應用程序啓動時,它會獲得一個單一的進程,該進程與底層 JVM 綁定。我們真正需要的是一種方法,可以啓動多個應用程序實例,並對它們之間進行智能負載均衡。由於我們已經將應用程序打包為 Docker 容器,因此 Kubernetes 是這種編排的自然選擇。
4.9. 廢棄處理 (Disposability)
應用程序進程可以有目的地關閉,也可以由於意外事件而關閉。無論哪種情況,十二要素應用程序應該能夠優雅地處理它。換句話説,應用程序進程應該完全可丟棄,而不會產生任何不良副作用。此外,進程應該啓動迅速。
例如,在我們的應用程序中,其中一個端點是為電影創建一個新的數據庫記錄。現在,處理此類請求的應用程序可能會意外崩潰。然而,這不應影響應用程序的狀態。當客户端再次發送相同的請求時,不應導致重複記錄。
總而言之,應用程序應暴露冪等服務。這對於面向雲部署的服務來説也是一個非常理想的屬性。這提供了在任何時候停止、移動或啓動新服務而無需考慮其他因素的靈活性。
4.10. 開發/生產環境一致性
通常情況下,應用程序會在本地機器上進行開發,在其他環境中進行測試,最終部署到生產環境。這些環境往往存在差異。例如,開發團隊在 Windows 機器上工作,而生產部署則在 Linux 機器上進行。
十二要素方法論建議儘可能縮小開發和生產環境之間的差距。 這些差距可能源於漫長的開發週期、不同的團隊參與以及使用的不同的技術棧。
現在,像 Spring Boot 和 Docker 這樣的技術在很大程度上自動彌合了這種差距。容器化應用程序應該在運行的任何地方表現出相同的行為。我們還需要使用相同的後端服務——例如數據庫。
此外,我們應該採用諸如持續集成和持續交付之類的正確流程,以進一步縮小這種差距。
4.11. 日誌
日誌是應用程序在其生命週期中生成的重要數據。它們為應用程序的工作原理提供了寶貴的見解。通常,一個應用程序可以生成不同級別的日誌,具有不同的詳細程度和輸出格式。
然而,十二要素應用程序與日誌生成和處理分離。對於此類應用程序,日誌僅僅是一個按時間順序排列的事件流。 它只是將這些事件寫入執行環境的標準輸出。 這種流的捕獲、存儲、整理和歸檔應由執行環境處理。
為此,我們有許多工具可供選擇。 首先,我們可以使用 SLF4J 在應用程序中抽象地處理日誌記錄。 此外,我們還可以使用像 Fluentd 這樣的工具來收集應用程序和後端服務的日誌流。
我們可以將這些數據輸入到 Elasticsearch 中進行存儲和索引。 最後,我們可以使用 Kibana 生成有意義的數據儀表板以進行可視化。
4.12. 管理流程
我們經常需要執行一些一次性任務或常規操作,與我們的應用程序狀態相關。例如,修復不良記錄。現在,有多種方法可以實現這一點。由於我們可能不經常需要這樣做,我們可以編寫一個小的腳本,使其與另一個環境分開運行。
現在,十二要素方法強烈建議將這些管理腳本與應用程序代碼庫一起維護。在這樣做的同時,應遵循我們應用於主應用程序代碼庫的相同原則。同時,建議使用執行環境的內置 REPL 工具在生產服務器上運行這些腳本。
在我們的示例中,我們如何使用已觀察到的電影來初始化應用程序?雖然我們可以使用我們可愛的端點,但這可能顯得不切實際。我們需要的是一個腳本來執行一次性加載。我們可以編寫一個小的 Java 函數,從文件中讀取電影列表,並將它們批量保存到數據庫中。
此外,我們還可以使用與 Java 運行時集成的 Groovy 來啓動這些流程。
5. 實際應用
因此,我們已經瞭解了十二要素方法的各項建議。將應用程序開發成一個符合 十二要素應用程序 確實有很多好處,尤其是在我們希望將它們作為雲服務部署時。但是,就像所有其他指南、框架和模式一樣,我們必須問自己,這是否是萬能鑰匙?
誠然,在軟件設計和開發中,沒有任何單一方法聲稱是萬能鑰匙。十二要素方法也不例外。雖然 其中一些要素非常直觀,而且我們可能已經在使用它們,另一些要素可能並不適用於我們。在評估這些要素時,必須考慮我們的目標,並做出明智的選擇。
需要注意的是,所有這些要素都旨在幫助我們開發一個模塊化、獨立、可移植、可擴展且可觀察的應用程序。根據應用程序的不同,我們可能可以通過其他更有效的方式來實現它們。也不需要同時採用所有要素,即使採用其中的一些也能使我們比之前更好。
最後,這些要素非常簡單而優雅。在當今我們要求應用程序具有更高吞吐量、更低延遲以及幾乎沒有停機時間和故障的時代,它們更具重要性。 採用這些要素能幫助我們從一開始就取得良好的開端。 結合微服務架構和應用程序容器化,它們似乎正好擊中了要點。
6. 結論
在本教程中,我們深入探討了十二要素方法論的概念。我們討論瞭如何利用 Spring Boot 構建微服務架構,以有效地交付這些微服務。此外,我們對每個要素進行了詳細的探索,並研究瞭如何將其應用於我們的應用程序。我們還探索了多種工具,以有效地應用這些要素。