檢視原始碼 相依性和傘狀專案
在本章中,我們將討論如何在 Mix 中管理相依性。
我們的 kv
應用程式已完成,因此現在是時候實作將處理我們在第一章中定義的請求的伺服器了
CREATE shopping
OK
PUT shopping milk 1
OK
PUT shopping eggs 3
OK
GET shopping milk
1
OK
DELETE shopping eggs
OK
不過,我們不會在 kv
應用程式中加入更多程式碼,而是將 TCP 伺服器建置為另一個應用程式,作為 kv
應用程式的用戶端。由於整個執行時間和 Elixir 生態系統都針對應用程式而設計,因此將專案分解成較小的應用程式並讓它們協同運作,會比建置一個龐大且單一的應用程式更有意義。
在建立新的應用程式之前,我們必須討論 Mix 如何處理相依性。實際上,我們通常會使用兩種相依性:內部相依性和外部相依性。Mix 支援處理這兩種相依性的機制。
外部相依性
外部相依性是指與您的業務領域無關的相依性。例如,如果您需要為分散式 KV 應用程式建立 HTTP API,您可以使用 Plug 專案作為外部相依性。
安裝外部相依性很簡單。最常見的方式是使用 Hex 套件管理員,方法是在 mix.exs
檔案中的 deps 函式內列出相依性
def deps do
[{:plug, "~> 1.0"}]
end
此相依性指的是已推送到 Hex 的 1.x.x 版本系列中 Plug 的最新版本。這由版本號碼前面的 ~>
表示。如需指定版本需求的更多資訊,請參閱 Version
模組的文件。
通常,穩定版本會推送到 Hex。如果您想要依賴於仍在開發中的外部相依性,Mix 也能管理 Git 相依性
def deps do
[{:plug, git: "https://github.com/elixir-lang/plug.git"}]
end
您會注意到,當您將相依性加入專案時,Mix 會產生一個 mix.lock
檔案,以保證「可重複建置」。必須將鎖定檔案簽入您的版本控制系統,以保證使用專案的每個人都會使用與您相同的相依性版本。
Mix 提供許多用於處理相依性的任務,您可以在 mix help
中看到這些任務。
$ mix help
mix deps # Lists dependencies and their status
mix deps.clean # Deletes the given dependencies' files
mix deps.compile # Compiles dependencies
mix deps.get # Gets all out of date dependencies
mix deps.tree # Prints the dependency tree
mix deps.unlock # Unlocks the given dependencies
mix deps.update # Updates the given dependencies
最常見的任務是 mix deps.get
和 mix deps.update
。取得相依性後,系統會自動為您編譯這些相依性。您可以透過輸入 mix help deps
,以及在 Mix.Tasks.Deps
模組的文件中,進一步了解 deps。
內部依賴
內部依賴是專屬於您的專案。它們通常在您的專案/公司/組織範圍之外沒有意義。大多數時候,您會希望將它們保密,無論是出於技術、經濟或業務原因。
如果您有內部依賴,Mix 支援兩種方法來處理它們:Git 儲存庫或傘形專案。
例如,如果您將 kv
專案推送到 Git 儲存庫,您需要在您的 deps 程式碼中列出它才能使用它
def deps do
[{:kv, git: "https://github.com/YOUR_ACCOUNT/kv.git"}]
end
不過,如果儲存庫是私有的,您可能需要指定私人 URL git@github.com:YOUR_ACCOUNT/kv.git
。無論如何,只要您擁有適當的憑證,Mix 都可以為您擷取它。
在 Elixir 中不建議使用 Git 儲存庫作為內部依賴。請記住,執行時期和 Elixir 生態系統已經提供了應用程式的概念。因此,我們希望您經常將您的程式碼分解成可以在邏輯上組織的應用程式,即使是在單一專案中。
但是,如果您將每個應用程式作為一個獨立的專案推送到 Git 儲存庫,您的專案可能會變得非常難以維護,因為您將花費大量時間管理這些 Git 儲存庫,而不是撰寫您的程式碼。
由於這個原因,Mix 支援「傘形專案」。傘形專案用於建置在單一儲存庫中一起執行的應用程式。這正是我們將在下一節中探討的樣式。
讓我們建立一個新的 Mix 專案。我們將有創意地將它命名為 kv_umbrella
,這個新專案將同時包含現有的 kv
應用程式和新的 kv_server
應用程式。目錄結構將如下所示
+ kv_umbrella
+ apps
+ kv
+ kv_server
這種方法的有趣之處在於 Mix 有許多便利功能可供處理此類專案,例如使用單一指令編譯和測試 apps
中的所有應用程式。但是,即使它們都列在 apps
中,它們仍然彼此分離,因此您可以根據需要個別建置、測試和部署每個應用程式。
讓我們開始吧!
傘形專案
讓我們使用 mix new
來開始一個新專案。這個新專案將命名為 kv_umbrella
,而且我們需要在建立時傳遞 --umbrella
選項。不要在現有的 kv
專案中建立這個新專案!
$ mix new kv_umbrella --umbrella
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating apps
* creating config
* creating config/config.exs
從列印出的資訊中,我們可以看到產生了更少的文件。產生的 mix.exs
檔案也不同。讓我們來看一下(註解已移除)
defmodule KvUmbrella.MixProject do
use Mix.Project
def project do
[
apps_path: "apps",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
defp deps do
[]
end
end
讓這個專案與前一個專案不同的,是專案定義中的 apps_path: "apps"
項目。這表示這個專案將作為一個 umbrella。此類專案沒有原始檔或測試,儘管它們可能有自己的相依性。每個子應用程式都必須定義在 apps
目錄中。
讓我們進入 apps 目錄並開始建立 kv_server
。這次,我們要傳遞 --sup
旗標,它會告訴 Mix 自動為我們產生一個監督樹,而不是像我們在前面章節中手動建立一個
$ cd kv_umbrella/apps
$ mix new kv_server --module KVServer --sup
產生的檔案類似於我們最初為 kv
產生的檔案,有一些差異。讓我們開啟 mix.exs
defmodule KVServer.MixProject do
use Mix.Project
def project do
[
app: :kv_server,
version: "0.1.0",
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications
def application do
[
extra_applications: [:logger],
mod: {KVServer.Application, []}
]
end
# Run "mix help deps" to learn about dependencies
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
# {:sibling_app_in_umbrella, in_umbrella: true},
]
end
end
首先,由於我們在 kv_umbrella/apps
中產生這個專案,Mix 自動偵測到 umbrella 結構,並在專案定義中加入四行
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
這些選項表示所有相依性都將簽出至 kv_umbrella/deps
,而且它們將共用相同的建立、組態和鎖定檔。我們尚未討論組態,但從這裡我們可以建立直覺,即所有組態和相依性都跨 umbrella 中的所有專案共用,而且不是針對每個應用程式。
第二個變更在 mix.exs
中的 application
函式中
def application do
[
extra_applications: [:logger],
mod: {KVServer.Application, []}
]
end
因為我們傳遞了 --sup
旗標,Mix 自動加入 mod: {KVServer.Application, []}
,指定 KVServer.Application
是我們的應用程式回呼模組。 KVServer.Application
將啟動我們的應用程式監督樹。
事實上,讓我們開啟 lib/kv_server/application.ex
defmodule KVServer.Application do
# See https://hexdocs.dev.org.tw/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
@impl true
def start(_type, _args) do
# List all child processes to be supervised
children = [
# Starts a worker by calling: KVServer.Worker.start_link(arg)
# {KVServer.Worker, arg},
]
# See https://hexdocs.dev.org.tw/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: KVServer.Supervisor]
Supervisor.start_link(children, opts)
end
end
請注意,它定義了應用程式回呼函式 start/2
,而且它並未定義使用 Supervisor
模組的名為 KVServer.Supervisor
的監督程式,而是方便地內嵌定義了監督程式!你可以透過閱讀 Supervisor
模組文件,進一步了解此類監督程式。
我們已經可以試用我們的首個 umbrella 子項。我們可以在 apps/kv_server
目錄中執行測試,但那不會很有趣。相反地,請前往 umbrella 專案的根目錄,然後執行 mix test
$ mix test
它運作了!
由於我們希望 kv_server
最終使用我們在 kv
中定義的功能,因此我們需要將 kv
新增為我們應用程式的相依性。
umbrella 專案中的相依性
umbrella 專案中應用程式之間的相依性仍然必須明確定義,而 Mix 使得這項工作變得容易。開啟 apps/kv_server/mix.exs
,然後將 deps/0
函式變更為以下內容
defp deps do
[{:kv, in_umbrella: true}]
end
上述程式碼行讓 :kv
可用於 :kv_server
中的相依性,並在伺服器啟動之前自動啟動 :kv
應用程式。
最後,將我們迄今為止建置的 kv
應用程式複製到我們的新 umbrella 專案中的 apps
目錄。最終目錄結構應符合我們先前提到的結構
+ kv_umbrella
+ apps
+ kv
+ kv_server
我們現在需要修改 apps/kv/mix.exs
,以包含我們在 apps/kv_server/mix.exs
中看到的 umbrella 項目。開啟 apps/kv/mix.exs
,並將以下內容新增至 project/0
函式
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
現在,你可以使用 mix test
從 umbrella 根目錄執行兩個專案的測試。太棒了!
不要盲目追隨
umbrella 專案是一種便利工具,可協助你整理和管理多個應用程式。雖然它在應用程式之間提供了一定程度的分離,但這些應用程式並未完全解耦,因為它們共用相同的組態和相同的相依性。
將多個應用程式保留在同一儲存庫中的模式稱為「單一儲存庫」。umbrella 專案透過提供便利工具來同時編譯、測試和執行多個應用程式,最大化了此模式。
如果您發現自己處於一個位置,您想要在每個應用程式中使用不同組態,以取得相同的依賴關係或使用不同的依賴關係版本,那麼您的程式碼庫很可能已經成長到超出傘狀架構所能提供的範圍。
好消息是,分解傘狀架構非常簡單,因為您只需要將應用程式移到傘狀架構專案的 apps/
目錄外部,並更新專案的 mix.exs 檔案,不再設定 build_path
、config_path
、deps_path
和 lockfile
組態。您可以透過多種方式依賴於傘狀架構外部的私人專案
- 將其移到同一個儲存庫中的個別資料夾,並使用路徑依賴關係指向它(單一儲存庫模式)
- 將儲存庫移到個別的 Git 儲存庫,並依賴於它
- 將專案發佈到私人 Hex.pm 組織
總結
在本章中,我們進一步瞭解 Mix 依賴關係和傘狀架構專案。雖然我們可以在沒有伺服器的情況下執行 kv
,但我們的 kv_server
直接依賴於 kv
。透過將它們分解成個別的應用程式,我們可以更進一步地控制它們的開發和測試方式。
在使用傘狀架構應用程式時,在它們之間建立明確的界線非常重要。我們即將推出的 kv_server
只能存取 kv
中定義的公開 API。將您的傘狀架構應用程式視為任何其他依賴關係,甚至是 Elixir 本身:您只能存取公開且有文件記載的內容。深入依賴關係中的私人功能是一種不良的作法,最終會導致您的程式碼在新的版本推出時中斷。
傘狀架構應用程式也可以用作最終從您的程式碼庫中提取應用程式的踏腳石。例如,想像一個必須向其使用者傳送「推播通知」的網路應用程式。整個「推播通知系統」可以在傘狀架構中開發為一個個別的應用程式,擁有自己的監督樹和 API。如果您遇到另一個專案需要推播通知系統的情況,則可以將系統移到私人儲存庫或 Hex 套件。
最後,請記住,雨傘專案中的應用程式都共用相同的組態和依賴關係。如果您的雨傘中的兩個應用程式需要以截然不同的方式組態同一個依賴關係,甚至使用不同的版本,那麼您可能已經不再享受雨傘帶來的優點。請記住,您可以打破雨傘,但仍然可以利用「單一儲存庫」背後的優點。
在我們的雨傘專案啟動並執行後,就可以開始撰寫我們的伺服器了。