日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

從用了近十年的 C# 轉到 Go 是一個有趣的旅程。有時,我陶醉于 Go 的簡潔[1];也有些時候,當熟悉的 OOP (面向對象編程)模式[2]無法在 Go 代碼中使用的時候會感到沮喪。幸運的是,我已經摸索出了一些寫 HTTP 服務的模式,在我的團隊中應用地很好。

當在公司項目上工作時,我傾向把可發現性放在最高的優先級上。這些應用會在接下來的 20 年運行在生產環境中,必須有眾多的開發人員和網站可靠性工程師(可能是指運維)來進行熱補丁,維護和調整工作。因此,我不指望這些模式能適合所有人。

Mat Ryer 的文章[3]是我使用 Go 試驗 HTTP 服務的起點之一,也是這篇文章的靈感來源。

代碼組成

Broker

一個 Broker 結構是將不同的 service 包綁定到 HTTP 邏輯的膠合結構。沒有包作用域結級別的變量被使用。依賴的接口得益于了 Go 的組合[4]的特點被嵌入了進來。

type Broker struct {
    auth.Client             // 從外部倉庫導入的身份驗證依賴(接口)
    service.Service         // 倉庫的業務邏輯包(接口)

    cfg    Config           // 該 API 服務的配置
    router *mux.Router      // 該 API 服務的路由集
}

broker 可以使用阻塞[5]函數 New() 來初始化,該函數校驗配置,并且運行所有需要的前置檢查。

func New(cfg Config, port int) (*Broker, error) {
    r := &Broker{
        cfg: cfg,
    }

    ...

    r.auth.Client, err = auth.New(cfg.AuthConfig)
    if err != nil {
        return nil, fmt.Errorf("Unable to create new API broker: %w", err)
    }

    ...

    return r, nil
}

初始化后的 Broker 滿足了暴露在外的 Server 接口,這些接口定義了所有的,被 route 和 中間件(middleware)使用的功能。service 包接口被嵌入,這些接口與 Broker 上嵌入的接口相匹配。

type Server interface {
    PingDependencies(bool) error
    ValidateJWT(string) error

    service.Service
}

web 服務通過調用 Start() 函數來啟動。路由綁定通過一個閉包函數[6]進行綁定,這種方式保證循環依賴不會破壞導入周期規則。

func (bkr *Broker) Start(binder func(s Server, r *mux.Router)) {
    ...

    bkr.router = mux.NewRouter().StrictSlash(true)
    binder(bkr, bkr.router)

    ...

    if err := http.Serve(l, bkr.router); errors.Is(err, http.ErrServerClosed) {
        log.Warn().Err(err).Msg("Web server has shut down")
    } else {
        log.Fatal().Err(err).Msg("Web server has shut down unexpectedly")
    }
}

那些對故障排除(比如,Kubernetes 探針[7])或者災難恢復方案方面有用的函數,掛在 Broker 上。如果被 routes/middleware 使用的話,這些僅僅被添加到 webserver.Server 接口上。

func (bkr *Broker) SetupDatabase() { ... }
func (bkr *Broker) PingDependencies(failFast bool)) { ... }

啟動引導

整個應用的入口是一個 main 包。默認會啟動 Web 服務。我們可以通過傳入一些命令行參數來調用之前提到的故障排查功能,方便使用傳入 New() 函數的,經過驗證的配置來測試代理權限以及其他網絡問題。我們所要做的只是登入運行著的 pod 然后像使用其他命令行工具一樣使用它們。

func main() {
    subCommand := flag.String("start", "", "start the webserver")

    ...

    srv := webserver.New(cfg, 80)

    switch strings.ToLower(subCommand) {
    case "ping":
        srv.PingDependencies(false)
    case "start":
        srv.Start(BindRoutes)
    default:
        fmt.Printf("Unrecognized command %q, exiting.", subCommand)
        os.Exit(1)
    }
}

HTTP 管道設置在 BindRoutes() 函數中完成,該函數通過 ser.Start() 注入到服務(server)中。

func BindRoutes(srv webserver.Server, r *mux.Router) {
    r.Use(middleware.Metrics(), middleware.Authentication(srv))
    r.HandleFunc("/ping", routes.Ping()).Methods(http.MethodGet)

    ...

    r.HandleFunc("/makes/{makeID}/models/{modelID}", model.get(srv)).Methods(http.MethodGet)
}

中間件

中間件(Middleware)返回一個帶有 handler 的函數,handler 用來構建需要的 http.HandlerFunc。這使得 webserver.Server 接口被注入,同時所有的安靜檢查只在啟動時執行,而不是在所有路由調用的時候。

func Authentication(srv webserver.Server) func(h http.Handler) http.Handler {
    if srv == nil || !srv.Client.IsValid() {
        log.Fatal().Msg("a nil dependency was passed to authentication middleware")
    }

    // additional setup logic
    ...

    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            token := strings.TrimSpace(r.Header.Get("Authorization"))
            if err := srv.ValidateJWT(token); err != nil {
                ...
                w.WriteHeader(401)
                w.Write([]byte("Access Denied"))

                return
            }

            next.ServeHTTP(w, r)
        }
    }
}

路由

路由有著與中間件有著類似的套路——簡單的設置,但是有著同樣的收益。

func GetLatest(srv webserver.Server) http.HandlerFunc {
    if srv == nil {
        log.Fatal().Msg("a nil dependency was passed to the `/makes/{makeID}/models/{modelID}` route")
    }

    // additional setup logic
    ...

    return func(w http.ResponseWriter, r *http.Request) {
        ...

        makeDTO, err := srv.Get
    }
}

目錄結構

代碼的目錄結構對可發現性進行了高度優化。

├── App/
|   └── service-api/**
├── cmd/
|   └── service-tool-x/
├── internal/
|   └── service/
|       └── mock/
├── pkg/
|   ├── client/
|   └── dtos/
├── (.editorconfig, .gitattributes, .gitignore)
└── go.mod
  • app/ 用于項目應用——這是新來的人了解代碼傾向的切入點。dd
    • ./service-api/ 是該倉庫的微服務 API;所有的 HTTP 實現細節都在這里。
  • cmd/ 是存放命令行應用的地方。
  • internal/ 是不可以被該倉庫以外的項目引入的一個特殊目錄[8]
    • ./service/ 是所有領域邏輯(domain logic)所在的地方;可以被 service-api,service-tool-x,以及任何未來直接訪問這個目錄可以帶來收益的應用或者包所引入。
  • pkg/ 用于存放鼓勵被倉庫以外的項目所引入的包。
    • ./client/ 是用于訪問 service-api 的 client 庫。其他團隊可以使用而不是自己寫一個 client,并且我們可以借助我們在 cmd/ 里面的 CI/CD 工具來 “dogfood it[9]” (使用自己產品的意思)。
    • ./dtos/ 是存放項目的數據傳輸對象,不同包之間共享的數據且以 json 形式在線路上編碼或傳輸的結構體定義。沒有從其他倉庫包導出的模塊化的結構體。/internal/service 負責 這些 DTO (數據傳輸對象)和自己內部模型的相互映射,避免實現細節的遺漏(如,數據庫注釋)并且該模型的改變不破壞下游客戶端消費這些 DTO。
  • .editorconfig,.gitattributes,.gitignore 因為所有的倉庫必須使用 .editorconfig,.gitattributes,.gitignore[10]
  • go.mod 甚至可以在有限制的且官僚的公司環境[11]工作。

最重要的:每個包只負責意見事情,一件事情!

HTTP 服務結構

└── service-api/
    ├── cfg/
    ├── middleware/
    ├── routes/
    |   ├── makes/
    |   |   └── models/**
    |   ├── create.go
    |   ├── create_test.go
    |   ├── get.go
    |   └── get_test.go
    ├── webserver/
    ├── main.go
    └── routebinds.go
  • ./cfg/ 用于存放配置文件,通常是以 JSON 或者 YAML 形式保存的純文本文件,它們也應該被檢入到 Git 里面(除了密碼,秘鑰等)。
  • ./middleware 用于所有的中間件。
  • ./routes 采用類似應用的類 RESTFul 形式的目錄對路由代碼進行分組和嵌套。
  • ./webserver 保存所有共享的 HTTP 結構和接口(Broker,配置,Server等等)。
  • main.go 啟動應用程序的地方(New(),Start())。
  • routebinds.go BindRoutes() 函數存放的地方。

你覺得呢?

如果你最終采用了這種模式,或者有其他的想法我們可以討論,我樂意聽到這些想法!


via: https://www.dudley.codes/posts/2020.05.19-golang-structure-web-servers/

作者:James Dudley[12]譯者:dust347[13]校對:unknwon[14]

本文由 GCTT[15] 原創編譯,Go 中文網[16] 榮譽推出

參考資料

[1]

簡潔:
https://www.youtube.com/watch?v=rFejpH_tAHM

[2]

模式:
https://en.wikipedia.org/wiki/Software_design_pattern

[3]

Mat Ryer 的文章:
https://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html

[4]

Go 的組合:
https://www.ardanlabs.com/blog/2015/09/composition-with-go.html

[5]

阻塞:
https://stackoverflow.com/questions/2407589/what-does-the-term-blocking-mean-in-programming

[6]

閉包函數:
https://gobyexample.com/closures

[7]

Kubernetes 探針:
https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)0

[8]

特殊目錄:
https://dave.cheney.net/2019/10/06/use-internal-packages-to-reduce-your-public-api-surface

[9]

dogfood it: https://en.wikipedia.org/wiki/Eating_your_own_dog_food

[10]

所有的倉庫必須使用 .editorconfig,.gitattributes,.gitignore:
https://www.dudley.codes/posts/2020.02.16-git-lost-in-translation/

[11]

有限制的且官僚的公司環境:
https://www.dudley.codes/posts/2020.04.02-golang-behind-corporate-firewall/

[12]

James Dudley: https://www.dudley.codes/

[13]

dust347: https://github.com/dust347

[14]

unknwon: https://github.com/unknwon

[15]

GCTT: https://github.com/studygolang/GCTT

[16]

Go 中文網: https://studygolang.com/

分享到:
標簽:服務 Web
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定