从用了近十年的 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) }}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 如何设置并使用自定义域名,访问公司的内网设备
- PostgreSQL的最佳群集高可用性方案
- 地球因为人类而带来的灾害 如果地球停转
- 如何使毛囊长出黑发,毛囊长出黑发
- 如何用黑桑葚染发,如何用微波炉烘干金银花
- 莱尔斯丹女鞋价格如何
- 橄榄油礼品如何选购呢
- 求职简历|如何写出有助于求职成功的好的个人求职简历,以及面试技巧
- 根雕艺术品如何鉴别
- 象牙制品收藏价值如何判断