如何用標(biāo)準(zhǔn)化的,通用的純函數(shù)編程語(yǔ)言Haskell來構(gòu)建Web?Haskell構(gòu)建的Web擁有著什么樣的優(yōu)點(diǎn)呢?讓我們來看看構(gòu)建的過程吧。

作者%20|%20William%20Yao
譯者%20|彎月,責(zé)編%20|%20maozz
出品%20|%20CSDN(ID:CSDNnews)
以下為譯文:
Haskell有大量的庫(kù)可用于滿足基本的后臺(tái)需求,從日志輸出到數(shù)據(jù)庫(kù)訪問,再到Web服務(wù)器的定義和路由,應(yīng)有盡有。
擁有選擇的自由固然很好,但如果你剛剛接觸這個(gè)領(lǐng)域,那么大量的選擇可能會(huì)讓你目不暇接。也許你沒法自信地判斷出這些選擇之間的區(qū)別。例如,你需要查詢數(shù)據(jù)庫(kù)。
那么,你需要Squeal提供的列名強(qiáng)保證,以及深度SQL嵌入功能嗎?還是青睞Opaleye在保證類型安全的前提下的簡(jiǎn)單性?或者只是使用簡(jiǎn)單的postgresql-simple就好?還是使用Selda?或者……
我用最簡(jiǎn)單的庫(kù)寫了一個(gè)Web應(yīng)用程序,一方面是想告訴你,你不需要擔(dān)心技術(shù)棧是否足夠先進(jìn),另一方面也是我自己的學(xué)習(xí)過程。
如果你不知道怎樣用Haskell構(gòu)建真正的應(yīng)用程序,為什么不去學(xué)習(xí)呢?我會(huì)盡可能保持代碼的簡(jiǎn)單性。
點(diǎn)擊此處可查看源代碼(https://gitlab.com/williamyaoh/haskell-web-stack)。
下面我們來看一看我選擇的這些庫(kù),以及它們的功能,還有這個(gè)應(yīng)用程序的功能。
1.這個(gè)Web應(yīng)用程序究竟是什么呢?
它是一個(gè)網(wǎng)站,用戶可以在這個(gè)網(wǎng)站上創(chuàng)建定時(shí)器和做筆記。
我舉個(gè)例子說明該網(wǎng)站的用途:某人想設(shè)置多個(gè)定時(shí)器來跟蹤多個(gè)反應(yīng)物的過程,同時(shí)用筆記來記錄他們需要關(guān)心的事情、下次制作配方時(shí)需要改進(jìn)的事情,等等。
再舉一個(gè)例子,某個(gè)人在玩MOBA游戲,比如英雄聯(lián)盟或者刀塔2,他們可以在另一臺(tái)顯示器上打開一個(gè)頁(yè)面來跟蹤關(guān)鍵技能的冷卻時(shí)間,同時(shí)用筆記記錄宏操作、對(duì)手的組合,以及團(tuán)戰(zhàn)時(shí)需要關(guān)注技的冷卻等。
在這個(gè)應(yīng)用程序中我將演示:
-
會(huì)話,用戶可以刷新頁(yè)面,或者關(guān)閉頁(yè)面后再打開,但在打開之后應(yīng)該看到相同的內(nèi)容。
-
持久化和數(shù)據(jù)庫(kù)訪問,我們需要將每個(gè)用戶的定時(shí)器和筆記保存下來。另一個(gè)需求是定時(shí)器需要維持剩余的時(shí)間(用戶設(shè)置了一個(gè)30分鐘的定時(shí)器,然后不小心關(guān)閉了頁(yè)面,應(yīng)該如何?)
-
運(yùn)行時(shí)的配置,因?yàn)槲覀儾荒馨褦?shù)據(jù)庫(kù)連接的信息硬編碼到代碼里。
-
日志。日志對(duì)于Web應(yīng)用的作用不言而喻。
2.我用到了哪些庫(kù)?
路由和Web服務(wù)器:Spock
最終我選擇了Spock,因?yàn)樗子谑褂谩H绻阌眠^Ruby的Sinatra,那么Spock應(yīng)該不會(huì)陌生。Spock也自帶了會(huì)話管理,這一點(diǎn)非常好。
例如,定義一個(gè)擁有幾條路由的服務(wù)器,返回html和JSON,代碼大致如下:
{-# LANGUAGE OverloadedStrings #-}
import Web.Spock as Spock
import Web.Spock.Config as Spock
import Data.Aeson as A
main :: IO
main = do
spockCfg <- defaultSpockCfg PCNoDatabase
runSpock 3000 $ spock spockCfg $ do
get root $ do
Spock.html "<div>Hello world!</div>"
get "users" $ do
Spock.json (A.object [ "users" .= users ])
get ("users" <//> var <//> "friends") $ \userID -> do
Spock.json (A.object [ "userID" .= (userID :: Int), "friends" .= A. ])
where users :: [String]
users = ["bob", "alice"]
數(shù)據(jù)庫(kù)訪問:postgresql-simple
postgresql-simple基本上只允許您對(duì)數(shù)據(jù)庫(kù)運(yùn)行原始SQL查詢,而沒有多余的裝飾,例如防止注入攻擊。它可以實(shí)現(xiàn)您的期望,僅此而已。
{-# LANGUAGE OverloadedStrings #-}
import Database.PostgreSQL.Simple
userLoginsQuery :: Query
userLoginsQuery =
"SELECT l.user_id, COUNT(1) FROM logins l GROUP BY l.user_id;"
getUserLogins :: Connection -> IO [(Int, Int)]
getUserLogins conn = query_ conn userLoginsQuery
數(shù)據(jù)庫(kù)訪問:postgresql-simple
postgresql-simple可以讓你在數(shù)據(jù)庫(kù)上運(yùn)行原始的SQL查詢,它只提供最基本的額外處理,比如防止注入攻擊等。它僅完成你需要的東西,沒有任何額外的功能。
{-# LANGUAGE OverloadedStrings #-}
import Database.PostgreSQL.Simple
userLoginsQuery :: Query
userLoginsQuery =
"SELECT l.user_id, COUNT(1) FROM logins l GROUP BY l.user_id;"
getUserLogins :: Connection -> IO [(Int, Int)]
getUserLogins conn = query_ conn userLoginsQuery
配置:configurator
configurator能夠從文件中讀取配置,并解析成Haskell數(shù)據(jù)類型。與普通的配置文件讀取器相比它的功能要多一些。
如果你習(xí)慣了直接讀取配置文件,那么configurator還有一些額外功能。例如,配置項(xiàng)可以嵌套分組,configurator還提供了熱重載來監(jiān)視配置文件變化。
# An example config file.
App_name = "The Whispering Fog"
db {
pool {
stripes = 4
resource_ttl = 300
}
username = "pallas"
password = "thefalloflatinium"
dbname = "italy"
}
{-# LANGUAGE OverloadedStrings #-}
import Data.Configurator as Cfg
import Database.PostgreSQL.Simple
data MyAppConfig = MyAppConfig
{ appName :: String
, appDBConnection :: Connection
}
getAppConfig :: IO MyAppConfig
getAppConfig = do
cfgFile <- Cfg.load ["app-configuration.cfg"]
name <- Cfg.require cfgFile "app_name"
conn <- do
username <- Cfg.require cfgFile "db.username"
password <- Cfg.require cfgFile "db.password"
dbname <- Cfg.require cfgFile "db.dbname"
connect $ defaultConnectInfo
{ connectUser = username
, connectPassword = password
, connectDatabase = dbname
}
pure $ MyAppConfig
{ appName = name
, appDBConnection = conn
}
日志:fast-logger
fast-logger提供了一個(gè)相對(duì)簡(jiǎn)單易用的日志解決方案。在Web應(yīng)用程序的示例中,我只輸出到了stderr,但它還可以支持輸出日志到文件。雖然它支持許多類型,但絕大多數(shù)情況下,你需要定義一個(gè)輔助函數(shù),接收一個(gè)LoggerSet,以及需要記錄的信息。
import System.Log.FastLogger as Log
logMsg :: Log.LoggerSet -> String -> IO
logMsg logSet msg =
Log.pushLogStrLn logSet (Log.toLogStr msg)
doSomething :: IO
doSomething = do
logSet <- Log.newStderrLoggerSet Log.defaultBufSize
logMsg logSet "message 1"
logMsg logSet "message 2"
HTML生成:blaze-html
盡管本項(xiàng)目的后臺(tái)并不需要生成太多HTML,但值得一提的是,blaze-html正是我需要的。
基本上它就是將HTML淺層嵌入到了Haskell DSL中。如果你會(huì)編寫HTML,那你就會(huì)使用這個(gè)庫(kù)。
{-# LANGUAGE OverloadedStrings #-}
import Data.ByteString.Lazy
import Text.Blaze.Html5 as HTML
import Text.Blaze.Html5.Attributes as HTML hiding ( title )
import Text.Blaze.Html.Renderer.Utf8 as HTML
dashboardHTML :: HTML.Html
dashboardHTML = HTML.html $
HTML.docTypeHtml $ do
HTML.head $ do
HTML.title "Timers and Notes"
HTML.meta ! HTML.charset "utf-8"
HTML.script ! HTML.src "/js/bundle.js" $ ""
HTML.body $ do
HTML.div ! HTML.id "content" $ ""
dashboardBytes :: ByteString
dashboardBytes = HTML.renderHtml dashboardHTML
構(gòu)建前端:make + npm
沒錯(cuò),它們并不是庫(kù)。但我們依然需要某種JAVAScript前端,因?yàn)槎〞r(shí)器需要實(shí)時(shí)更新。Webpack能夠生成JS包,而Make能夠組裝最終的應(yīng)用程序。
這些東西無(wú)需我多說,網(wǎng)上有很多相關(guān)的資源。
3.我必須要用這些庫(kù)嗎?
當(dāng)然不是。如果你第一次接觸Haskell,那么有這些疑問是很自然的。不要讓這篇文章限制了你的思路。盡管這個(gè)應(yīng)用程序可以運(yùn)行,但許多部分用于生產(chǎn)環(huán)境下的Haskell時(shí)并不理想。
例如,許多Haskell程序員很可能會(huì)使用Servant而不是Spock來定義API端點(diǎn)。如果你想了解其他庫(kù),那當(dāng)然應(yīng)該跟隨你的直覺。
你可以把這些庫(kù)和這個(gè)應(yīng)用程序作為起點(diǎn)。我建議你用這些代碼作為學(xué)習(xí)的機(jī)會(huì),理解它的原理,然后自己試著修改。Haskell很好的一點(diǎn)就是它非常易于重構(gòu)或者升級(jí),而不會(huì)破壞已有的功能。
一旦掌握了這個(gè)應(yīng)用程序,就可以用更高級(jí)的庫(kù)來替換它們,來獲得更多的保證。同時(shí),這也是一個(gè)增量學(xué)習(xí)的過程。
將數(shù)據(jù)庫(kù)訪問的庫(kù)從postgresql-simple升級(jí)到支持類型安全的庫(kù)。我推薦Opaleye!
將API定義的庫(kù)從Spock升級(jí)到Servant
利用QuickCheck或hedgehog增加自動(dòng)測(cè)試。例如,你可以測(cè)試服務(wù)器的每個(gè)錯(cuò)誤響應(yīng)都返回了JSON格式的錯(cuò)誤信息。
你還可以嘗試替換前端和構(gòu)建系統(tǒng)。
升級(jí)前端,使用PureScript或Elm來替換原始的JavaScript
升級(jí)構(gòu)建系統(tǒng),利用Shake替換make構(gòu)建更健壯的系統(tǒng)
原文:https://williamyaoh.com/posts/2019-11-16-a-dead-simple-web-stack.html
本文為 CSDN 翻譯,轉(zhuǎn)載請(qǐng)注明來源出處。
【End】