11|面向接口编程(下):一切皆服务,服务基于协议
该思维导图由 AI 生成,仅供参考
服务容器的实现
- 深入了解
- 翻译
- 解释
- 总结
本文介绍了面向接口编程的理念,通过定义接口和协议来实现模块间的交互。作者提出了将框架主体作为服务容器,其他模块作为服务提供者的核心设计思想,并详细讲解了服务容器的接口设计和具体实现,以及HadeContainer数据结构的设计和实现。文章还介绍了如何将服务容器融合进入框架中,并展示了如何创建一个服务提供方和通过服务提供方创建服务的方法。总体而言,本文深入浅出,适合技术人员快速了解面向接口编程的概念和实践方法。 在本文中,作者详细介绍了面向接口编程的核心理念,即将框架主体作为服务容器,其他模块作为服务提供者,通过定义接口和协议来实现模块间的交互。文章深入探讨了服务容器的接口设计和具体实现,以及HadeContainer数据结构的设计和实现,展示了如何将服务容器融合进入框架中,并演示了创建服务提供方和通过服务提供方创建服务的方法。这种设计的拓展性非常好,保证了服务协议不变的情况下,不用担心具体的某个服务实现进行了变化。整体而言,本文对面向接口编程的概念和实践方法进行了深入浅出的阐述,对于技术人员来说具有很高的参考价值。
《手把手带你写一个 Web 框架》,新⼈⾸单¥59
全部留言(17)
- 最新
- 精选
- 芒果少侠老师我提个并发场景下的潜在bug:如果serviceProvider是延迟加载的话,多个请求同时调用Make的时候,这时候是读锁,所以在后面新建出来实例后,回写map的时候,可能会出现并发写的情况。https://github.com/gohade/coredemo/blob/geekbang/11/framework/container.go#L133 我这边有一个解决思路,即避免并发写map的异常,又可以保证后续并发调用Make的读性能。不知道老师觉得怎么样: func (w *MyContainer) Make(key string) (interface{}, error) { w.lock.RLock() // 简单使用读锁会有并发写map的可能 // defer w.lock.RUnlock() if ins := w.instances[key]; ins != nil { w.lock.RUnlock() return ins, nil } w.lock.RUnlock() w.lock.Lock() defer w.lock.Unlock() // 双重检查,在获得写锁之后,可能有别的协程已经创建完成,可以直接返回,避免再创建 if ins := w.instances[key]; ins != nil { return ins, nil } sp := w.providers[key] if sp == nil { return nil, fmt.Errorf("provider %s not bound", key) } params := sp.Params(w) ins, err := w.newInstance(sp, params) if err != nil { return nil, err } w.instances[key] = ins return ins, nil }
作者回复: 是的,这里有问题,单纯用读锁是不行的,应该用你这种办法,读锁加写锁的逻辑,感谢指正,我修改下
2021-10-1213 - 宙斯总结: 1 根据容器定义『为服务提供注册绑定、提供获取服务实例』,定义接口Bind,Make方法,并扩展IsBind和MustMake,MakeNew接口规约。 2 定义container结构体,并实现上面的5个规约,特别是Bind和Make实现。 3 融入框架中,并希望在框架中通过context使用,通过在Engine和Context中维护container,并在container中定义Make获取服务的方法。 4 创建服务规约(contract),然后实现服务提供者(provider)和服务具体业务逻辑(server)。 并且需要定义服务结构体,且该服务需要提供NewInstance类型方法,在NewInstance类型方法里是服务和服务提供者真正产生关联的地方(初始化时服务只和服务提供者关联),在main中注册是服务提供者,在controller里通过context调用Make去获取服务。 问题:我们将服务容器的 Make 系列的方法在 Context 中实现了,为什么不把 Bind 系列方法也在 Context 中实现呢? Context 允许 Bind 方法有什么好处和什么不好的地方呢? 回答: 不放Bind到context原因:正如文中提到Bind是服务提供者注册,对服务提供者来说只需要全局注册一次即可,至于里面的服务是实例化一次还是实例多次和服务提供者注册一次没关系,Context是基于请求的它是负责获取服务,服务实例可能是使用单例,也可能是每次请求获得一个新实例,因此Bind通常不由Contxet控制,每次Bind都会覆盖以前相同的Provider。 若Context 允许 Bind 方法好处坏处? 好处:每个请求都会有独立的服务提供者(但没什么用)。 坏处:有并发写入问题,需要加锁,性能降低。
作者回复: 是的,这里没有必要引入并发问题
2021-10-243 - 友Bind方法我觉得是对于整个项目的生命周期而言的,在整个容器中存一份,比如 连接池对象,和适合单例的对象,将Bind放在context中就相当于面向的是请求级别,实话说没太大用,还让container有了并发的风险 还得做好锁导致性能也下降
作者回复: 是的,主要是考虑到有并发风险
2021-12-071 - Geek_6dc1bc容器绑定到ctx中会不会有这个文章中说的:https://cloud.tencent.com/developer/news/462918 ,复用context出现的问题?我看了下gin里面也是用的context pool的机制来保证一个高性能
作者回复: 不会的,我理解你发的文章中说的是context的复用问题。a携程拿context的时候,context还被b使用着。而我们这个框架底层实际上是net/http。context是每个请求各自一个context。首先这个context不会有问题。 其次我们将容器绑定在context中,只需要控制住容器的原子性,就是在初始化的时候才修改,其他时候都只有获取其中的服务。就不会出现服用问题
2022-08-02归属地:北京 - 心平气和这个算是微服务架构吗
作者回复: 微服务更多是从架构层面来进行分割的,哪些业务划分为一个小的服务。 从框架层面,如果内部各个服务依赖的是http协议,这个框架也算是微服务。
2022-07-13归属地:北京2 - 友刚刚重构了一手代码,按照自己的思路写了一些并发的粒度控制 https://github.com/zzm996-zzm/arms/blob/master/framework/container.go 之后会在这个仓库补全单元测试,刚刚发现想测试很多情况 但是手动测试太费劲
作者回复: 赞
2021-12-072 - 宙斯你好 在make时的文稿里,容器中还未实例化,第一次实例化,inst, err := hade.newInstance(sp, nil) 这里没有带参数params,这是为什么没有params呢?
作者回复: 在微信群已经回答了,可以进入下一层看下,没有params就是用provider的params了做参数
2021-10-242 - void1. HadeContainer 为什么要嵌套 Container?注释说“强制要求 HadeContainer 实现 Container 接口”,但是嵌套做不到强制实现啊。下边代码可以正常运行(不调用接口方法的情况下): type TTT interface { GetName() string } type MyTT struct { TTT name string } func main() { t := MyTT{ TTT: nil, name: "name", } fmt.Println(t) // t.GetName() // 调用方法时才会panic } 如果希望MyTT必须实现TTT的话,使用 “var _ Container = (*HadeContainer)(nil)” 可以在编译时保证。 2. Bind方法78行,为什么要用errors.New,而不是直接返回err? 3. make方法加了读锁,调用的 findServiceProvider 又加了读锁,属于重复加锁。但是后边写map没有加写锁。这个问题别的同学已经提到并给出了改正代码2021-11-1528
- fursen什么时候是最开心的时候; 就是代码跑起来的时候2022-02-012
- 党有点spring的感觉,控制反转,把功能都交给容器进行管理,真的比引入一个包直接使用好么?2022-06-0711