用 Go 写一个服务容器( Service Container )

jzhongming · 2017-12-10 13:48:33 · 5094 次点击 · 预计阅读时间 3 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2017-12-10 13:48:33 的文章,其中的信息可能已经有所发展或是发生改变。

我最近一直在做一个相当大的 API 项目,里面包括很多路由规则( routes )、服务接口( services )和处理函数( handlers )等。首先,我注意到 main.go 文件的启动过程开始越来越臃肿。

为了避免设置全局的服务接口,我使用共享结构体( struct )将服务接口与处理函数绑定在一起。举个例子:

main.go

package main

func main() {
    r := gin.Default()

    userRepo := models.NewUserRepo(
        drives.DataStore().C("users"),
    )

    userHandler := handlers.NewUserHandler(userRepo)
    r.GET("/api/v1/users", userHandler.FindAll)
    r.Run(":8080")
}

user_handler.go

type UserHandler struct {  
    userRepo *models.UserRepo
}

func NewUserHandler(userRepo *models.UserRepo) *UserHandler {  
    return &UserHandler{
        userRepo,
    }
}

func (userHandler *UserHandler) FindAll(c *gin.Context) {  
    users, err := userHandler.userRepo.FindAll()
    if err != nil {
        c.JSON(404, nil)
        return
    }
    c.JSON(200, users)
    return
}

这些代码工作的很好,但是,你会发现 main.go 中,我只是写了很少的启动过程,仅仅包含一个处理函数和一个持久化数据( repository )。(译注:用这种方式写代码比较麻烦且臃肿)。

于是我想用 Go 写一个容器。我找不到一个喜欢的第三方库来解决这个事情。所以,想出了下面这段代码。

import(  
    "sync"
) 

type Container struct{  
    mux sync.RWMutex
    m map[string]interface{}
}

// Add service
func (c *Container) Add(name string, object interface{}) {
    c.mux.Lock()
    if c.m == nil {
        c.m = make(map[string]interface{})
    }
    c.m[name] = object
    c.mux.Unlock()
}

// Remove service
func (c *Container) Remove(name string) Container {  
    c.mux.Lock()
    delete(c.m, name)
    c.mux.Unlock()
}

// Get a service
func (c *Container) Get(name string) (object interface{}, bool) {  
    c.mux.RLock()
    object, ok = c.m[name]
    c.mux.RUnlock()
    return object, ok
}

注意这段代码的每个方法都用了 mutex lock 来避免容器的并发问题。

现在代码可以这样写。。。

func GetContainer() Container {  
    c := new(container.Container)
    c.Add("user.handler", handlers.UserHandler)
    return c 
}

现在的 main.go

func main() {  
    container := container.GetContainer()

    userHandler, ok := container.Get("user.handler")

    if !ok {
        log.Fatal("Service not found")    
    }

    r.GET(
       "/api/v1/users", 
       userHandler.(*handlers.UserHandler).FindAll(),
    )
}

同步方面的代码参考自 itsmontoya ,向他致敬

现在我已经将启动过程很简洁地封装成了包。我觉得一个有 PHP 语言背景的人在考虑这段语法实现时候会参考 pimple 框架。

我已经把这个抽象成了自己的库,实现在这里


via: https://ewanvalentine.io/writing-a-service-container-in-go/

作者:Ewan Valentine  译者:jzhongming  校对:rxcai

本文由 GCTT 原创编译,Go语言中文网 荣誉推出


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

5094 次点击  
加入收藏 微博
被以下专栏收入,发现更多相似内容
6 回复  |  直到 2019-01-09 21:48:30
gotowhere
gotowhere · #1 · 7年之前

Add方法里创建字典时没加锁,不会出问题?

polaris
polaris · #2 · 7年之前
gotowheregotowhere #1 回复

Add方法里创建字典时没加锁,不会出问题?

不是加了吗?

jwstudy
jwstudy · #3 · 7年之前

多线程调用Add时可能会出吧。

if c.m == nil {
    c.m = make(map[string]interface{})
}  

这段不是原子操作。c.m可能被多次make 赋值。

polaris
polaris · #4 · 7年之前
jwstudyjwstudy #3 回复

多线程调用Add时可能会出吧。 if c.m == nil { c.m = make(map[string]interface{}) } 这段不是原子操作。c.m可能被多次make 赋值。

哦,这个地方呀。确实,已经修改!感谢!

LichFaker
LichFaker · #5 · 7年之前

mux sync.RWMutex 怎么不用指针形式?

当外部有拷贝Container 并修改时, map是指针引用, 数据会同步修改, 但锁却不是同一把, 还是会panic

jzhongming
jzhongming · #6 · 6年之前
LichFakerLichFaker #5 回复

`mux sync.RWMutex` 怎么不用指针形式? 当外部有拷贝`Container ` 并修改时, `map`是指针引用, 数据会同步修改, 但锁却不是同一把, 还是会`panic`

Container是有receive的对象,不是struct的数据结构,lock的scope只在receive内部,不对外有修改逻辑。对Container的Copy行为,应该慎重,不应该有这样的逻辑,直接使用receive对象就可以。把锁的声明修改为指针,一样可以修改指针的值,引发逻辑错误或者panic,这和形式无关。

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传