請容我引用一句話做為開場:
Command line interface is user friendly. It’s just picky about friend.
文字命令介面其實是對使用者友善的,它只是比較挑朋友而己。
這個看起來像是笑話的句子,其實值得仔細思索一下。沒錯, CLI 看起來不漂亮,要背一大堆指令,需要相當的練習才能上手,打一大堆字很麻煩,這些都是真的。但是回想我們最初學習的時候,當你願意放下成見,試著跟他相處一陣子,就會發現它帶給你許多 GUI 無法比擬的好處:容易組合各種已存在的小工具,完成各式各樣的工作,還非常容易自動化、排程,更進一步,還可以利用本機軟體或是網路的 api 延伸出各種服務間的互動。而且你還會了解有些看似神奇功能的原理,其實相當簡單。這些都是開發者做了才有的 GUI 軟體難以想像的。
Functional programming、Elixir、Haskell 跟 Phoenix 也差不多是這麼一回事 (Vim、Emacs、git 也是)。如果你能多少放下原先對好用、直覺、使用者友善、已經習慣這樣做的預設立場。看到熟悉的字時,不要太急著用之前的經驗去 mapping*,因為同一個詞,在這個脈絡下,有可能指的是完全不同的東西。就漸漸能用新的觀點來思考程式該如何架構,不同的組件間該怎麼互動。而就像 CLI 一樣,你也許會學到一些在物件導向的世界裡難以想像的好東西。
*: 這個字用在這就很 functional。XD
Phoenix 自 2013 年由 Chris McCord 所主導開發。在 1.2 版之前在表面上跟 Ruby on Rails 框架相當類似,但由於是函數式編程語言,各個組件的使用方式有許多大異其趣的設計。可以看看 Chris McCord 在 2015 寫的這篇 Phoenix is not Rails.
而從 2017 釋出的 1.3 版開始,開發團隊加進了更多不同的設計。因此連表面上都開始有點不同了。即使如此,熟悉 Rails 的開發者還是會發現有很多之前的概念與經驗可以套用在這個框架上。不過正如前言所說,mapping 之前,建議停看聽一下。
接下來的幾篇 Phoenix 介紹,並不著重在如何做出什麼功能,而是希望籍由示範導讀,來了解 Phoenix 如何設計各個組件,探索背後的哲學與世界觀。有了這個層次的認知,往後在實作功能時應該會更容易上手。
年紀大了,就會開始喜歡講些長篇大論的道理。到這裡還是有點意猶未盡,那我們就再從 Phoenix 的世界觀繼續吧。
Phoenix 認為,所謂的網頁應用程式,就是一個函式。打開瀏覽器,輸入的網址,帶上的選項,還有cookie,以及你每一次按下的按鈕與連結 (其實也是個網址),就是你呼叫這個函式的參數。
而這個函式呼叫的回傳值,就是你拿到的 HTML (或 JSON) 原始碼。其間在資料庫讀取及寫入的資料,可視為此函式呼叫的副作用。
之前提到過那些函數式編程語言裡的 function composition、資料轉變的旅程的概念,就在這裡與我們無縫接軌了。這個叫做網頁應用程式的函式,是由許多的小函式 compose 在一起的。每個小函式各自處理它拿到的輸入,將最初的 HTTP request,逐漸轉變成 HTML (或 JSON) 輸出。
而支撐著這個世界觀的,有兩個哲學:
與其用魔術般的方式讓程式看起來很簡潔,寧願一開始多打一些字,讓程式彼此的連接更為明顯,閱讀起來會更好理解,將來也更好維護。當然也不是說要變成寫程式之前要改一大堆 XML 的 configuration hell 那樣...
而這個哲學也適用於函式庫的設計方向。當然一整包套件裝了就神奇的動起來感覺似乎很棒。但是夠資深的developer,多少都踩過套件帶來的地雷。無論是很難變更套件的預設行為,或是因為不夠清楚套件的內部運作,帶來的龐大維護及除錯成本,甚至一個向左對齊就能搞掛一堆著名框架的案例。Elixir 社群的共識是,做出一堆很專注,易合成的函式庫,讓你可以快速的理解,接著組合到你的流程裡。就算初期會多花一些些時間,以長期的維護而言,依然是划算的。
正因為這個哲學,有一段時間 Phoenix 被戲稱為 "Rails with less magic"。
話說,這句哲學好像是跟 Python 抄來致敬的。
就像做出專注、易合成的函式,根本就是函數式編程的基本哲學一樣。 Phoenix 也希望在其它方向上,是遵從 Elixir 及 Erlang 語言及生態系的哲學及慣例的,儘可能使用生態系裡已有的解法。像是 OTP、「Let it crash」等等。特別是 1.3 版資料夾結構的改變,就是為了明示 Phoenix,也只是一種 Elixir mix project 而己。
Best of both world - development productivity and application performance.
而這一切的目標,正是上面那句官網的標語。希望能在開發(及維護)上帶來很高的生產力,而在效能上也不需要妥協。當然更重要的,是讓 developer 能享受開發的過程。
嗯,該是我要節制的時候了。因為把手弄髒才學得到東西,我們先把專案建起來吧。
我們要建立一個叫 hello_phx
的專案。先到 shell 裡,輸入以下的指令:
$ mix phx.new hello_phx
可以看到如下的畫面。Phoenix 幫你生了一堆檔案出來,接著會問你要不要安裝它依賴的函式庫。按下 [Enter] 讓它安裝:
Note: 某些舊版的教學,會使用 mix phoenix.new
這個 1.2 版以前的指令。目前還是可以使用,會生成 1.2 版時期的資料夾配置 (比較像 Rails)。這部份會留待下篇解說。
剛剛的畫面最下方有提示,我們要先切換到專案資料夾裡,並初始化資料庫:
$ cd hello_phx
$ mix ecto.create
運作正常的話,應該會印出
The database for HelloPhx.Repo has been created
Note: 如果這部份出了錯誤,有可能是你的環境沒有安裝及設定好 PostgreSQL,之前環境安裝篇已有相應的修正了。你也可能需要修改 config/dev.exs
檔案,修改資料庫的使用者名稱及密碼。
# config/dev.exs
#...
# Configure your database
config :hello_phx, HelloPhx.Repo,
adapter: Ecto.Adapters.Postgres,
username: "postgres", # <=== 使用者名稱
password: "postgres", # <=== 使用者密碼
database: "hello_phx_dev",
hostname: "localhost",
pool_size: 10
回到 shell,再用 mix phx.server
將專案跑起來。
$ mix phx.server
用瀏覽器打開 http://localhost:4000
,就可以看到網站應用程式的畫面如下。
回到正在跑 phx.server
的 shell,也可以看到目前接收到的 HTTP request:
Note: 按兩次 [Ctrl] + [C] 可以終止應用程式。
mix phx.new 專案名稱
來建立新專案mix ecto create
來初始化資料庫mix phx.server
把應用程式跑起來明天要來生成基本的 CRUD,並比較一下新舊版的資料夾結構差異,以及探討要這樣設計的理由。
Happy hacking! 明天見囉。