基于Gin封装Web框架 - 10. 使用 context 上下文完成依赖注入

简介: 基于Gin封装Web框架 - 10. 使用 context 上下文完成依赖注入

基于Gin封装Web框架 - 10. 使用 context 上下文完成依赖注入

源码参考: https://github.com/go-jarvis/gin-rum

在开发过程中, 不可避免的会用到诸如 数据库、redis 等其他组件。 使用 依赖注入 的方式可以很好的对程序进行解耦。

选择 context 作为容器

之所以选择 Context 作为容器,

其一 , context 具有很强的裂变性,不同 RumGroup 的 context 可以添加属于自己的内容;

// github.com/go-jarvis/gin-rum/rum/rumgroup.go

// baseRumGroup 通过 Rum 返回一个根 RumGroup
func baseRumGroup(ctx context.Context, r *Rum, group string) *RumGroup {
    return &RumGroup{
        RouterGroup: r.RouterGroup.Group(group),
        ctx:         ctx,
    }
}

// newRumGroup 通过 RumGroup 扩展新的 RumGroup
func newRumGroup(base *RumGroup, group string) *RumGroup {
    return &RumGroup{
        RouterGroup: base.RouterGroup.Group(group),
        ctx:         base.ctx,
    }
}

// WithContext 向 RumGoft 中注入任何内容
// 以向 class 控制器传递
func (grp *RumGroup) WithContext(fns ...ContextFunc) {
    for _, fn := range fns {
        grp.ctx = fn(grp.ctx)
    }
}

其二 , context 的包容性, key , value 都是 interface{} 可以包容一切。

// context/context.go
func WithValue(parent Context, key, val interface{}) Context {
    if parent == nil {
        panic("cannot create context from nil parent")
    }
    if key == nil {
        panic("nil key")
    }
    if !reflectlite.TypeOf(key).Comparable() {
        panic("key is not comparable")
    }
    return &valueCtx{parent, key, val}
}

rum 增加上下文支持

随后, 在 RumGroup 中增加 context.Context 字段,并添加相关方法。

type RumGroup struct {
    *gin.RouterGroup
    ctx context.Context
}

type ContextFunc = func(context.Context) context.Context

// WithContext 向 RumGoft 中注入任何内容
// 以向 class 控制器传递
func (grp *RumGroup) WithContext(fns ...ContextFunc) {
    for _, fn := range fns {
        grp.ctx = fn(grp.ctx)
    }
}

WithContext 方法要求传入的是一个 操作 Context 的函数, 这个函数由用户自己实现。WithContext 方法对于 Context 的修改仅限于 RumGroup 自身与及其子 RumGroup。

另外, 在 ClassController 中, 也需要做响应的变更, 需要 Handler(ctx context.Context) 方法支持 context 作为参数传递。

type ClassController interface {
    Method() string
    Path() string

    // Handler() (interface{}, error)  // 老方法
    Handler(context.Context) (interface{}, error)
}

为什么不用 gin.Context

虽然 gin.Context 也实现了 context.Context 的接口, 在在和我们常用的 标准库 还是还是有很多差别

首先, gin.Context 在 gin 初始化的时候会生成一个 公共的祖先 gin.Context, 随着程序的启动, 用户每次请求都将创建一个独立的 gin.Context 副本。

由于 gin 并没有提供一个可以用户初始化的 gin.Context 的 API。

func New() *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
        // ...省略
    }
    engine.RouterGroup.engine = engine
    // pool 是 engine 的私有字段
    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }
    return engine
}

// allocateContext 也是 engine 的私有方法
func (engine *Engine) allocateContext() *Context {
    v := make(Params, 0, engine.maxParams)
    skippedNodes := make([]skippedNode, 0, engine.maxSections)
    return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
}

因此用户只能写入到 每次请求 的 gin.Context 中。
而类似 数据库连接池 这样的句柄, 在程序启动的时候就初始化了,不在会改变。 如果写入到 gin.Context 中就造成了计算资源的浪费。

其次, 在 gin.Context.Value() 方法首先与标准库的实现不同, 有一个 比较的致命问题

// github.com/gin-gonic/gin@v1.7.4/context.go
func (c *Context) Value(key interface{}) interface{} {
    if key == 0 {
        return c.Request
    }
    if keyAsString, ok := key.(string); ok {
        val, _ := c.Get(keyAsString)
        return val
    }
    return nil
}

可以看到, gin 中的 Value() 方法将 key 转为了字符串。 因为此失去了 数据类型 的支持, key 的唯一性概率就大大降低了, 很容易发生覆盖冲突。

这一点在标准库中的 valueCtx 就不存在这种情况, 因为 key 不会被断言, 类型也是一部分

func (c *valueCtx) Value(key interface{}) interface{} {
    if c.key == key {
        return c.val
    }
    return c.Context.Value(key)
}

测试代码参考 context-Context-and-gin-Context

遗留问题

由于使用了自建的 GroupGroup Context, 并且 gin.Context 没有交叉点。 因此 gin.Context 中与 Cancel 相关的方法也无法传递到 ClassController 中的 Handler 方法中。

demo: 使用 context 传递

gorm 数据库句柄 通过 Context 注入到 Rum 中。

封装 db adaptor

首先, 创建新的数据类型, 并用该类型创建 唯一 key

type contextGormDBType int

var contextGormDBKey contextGormDBType = 0

在实践中, 不同的适配器可以创建自己的数据类型。 如此一来, 即使 字面值 相同也不会冲突、覆盖。

其次, 实现 注入提取 函数。

// WithGormDB 注入 *gorm.DB 到 context 中
func WithGormDB(vaule *gorm.DB) func(context.Context) context.Context {
    return func(ctx context.Context) context.Context {
        return context.WithValue(ctx, contextGormDBKey, value)
    }
}

// FromContextGormDB 从 context 中提取 *gorm.DB
func FromContextGormDB(ctx context.Context) *gorm.DB {
    return ctx.Value(contextGormDBKey).(*gorm.DB)
}

在 rum 中注入 adaptor

func main() {

    // 1. 使用 rum 代替 gin
    g := rum.Default()
    g.WithContext(
        db.WithGormDB(db.NewGormDB()),
    )

// 省略

在 ClassController 中使用 adaptor

最后, 在 ClassController 实例中直接使用。

注意, 在控制器定义的时候依旧保持 干净, 无任何依赖适配器的字段。

// GetUserByID class 控制器
type GetUserByID struct {
    httpx.MethodPost

    UserID int `uri:"id"`
}

func (user *GetUserByID) Handler(ctx context.Context) (interface{}, error) {
    // 获取 ctx 中注入的 *gorm.DB 对象
    gorm := db.FromContextGormDB(ctx)

    userModel := &User{}
    tx := gorm.Where("user_id=?", user.UserID).First(&userModel)

    return userModel, tx.Error
}
相关文章
|
2月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
52 4
|
2月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
143 3
|
1月前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
153 45
|
18天前
|
开发框架 搜索推荐 数据可视化
Django框架适合开发哪种类型的Web应用程序?
Django 框架凭借其强大的功能、稳定性和可扩展性,几乎可以适应各种类型的 Web 应用程序开发需求。无论是简单的网站还是复杂的企业级系统,Django 都能提供可靠的支持,帮助开发者快速构建高质量的应用。同时,其活跃的社区和丰富的资源也为开发者在项目实施过程中提供了有力的保障。
|
17天前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
31 2
|
19天前
|
中间件 Go API
Go语言中几种流行的Web框架,如Beego、Gin和Echo,分析了它们的特点、性能及适用场景,并讨论了如何根据项目需求、性能要求、团队经验和社区支持等因素选择最合适的框架
本文概述了Go语言中几种流行的Web框架,如Beego、Gin和Echo,分析了它们的特点、性能及适用场景,并讨论了如何根据项目需求、性能要求、团队经验和社区支持等因素选择最合适的框架。
45 1
|
1月前
|
SQL 安全 PHP
探索PHP的现代演进:从Web开发到框架创新
PHP是一种流行的服务器端脚本语言,自诞生以来在Web开发领域占据重要地位。从简单的网页脚本到支持面向对象编程的现代语言,PHP经历了多次重大更新。本文探讨PHP的现代演进历程,重点介绍其在Web开发中的应用及框架创新,如Laravel、Symfony等。这些框架不仅简化了开发流程,还提高了开发效率和安全性。
27 3
|
1月前
|
前端开发 JavaScript 开发工具
从框架到现代Web开发实践
从框架到现代Web开发实践
39 1
|
1月前
|
SQL 安全 PHP
探索PHP的现代演进:从Web开发到框架创新
PHP 自发布以来一直在 Web 开发领域占据重要地位,历经多次重大更新,从简单的脚本语言进化为支持面向对象编程的现代语言。本文探讨 PHP 的演进历程,重点介绍其在 Web 开发中的应用及框架创新。自 PHP 5.3 引入命名空间后,PHP 迈向了面向对象编程时代;PHP 7 通过优化内核大幅提升性能;PHP 8 更是带来了属性、刚性类型等新特性。
27 3
|
1月前
|
安全 数据库 开发者
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第26天】本文详细介绍了如何在Django框架下进行全栈开发,包括环境安装与配置、创建项目和应用、定义模型类、运行数据库迁移、创建视图和URL映射、编写模板以及启动开发服务器等步骤,并通过示例代码展示了具体实现过程。
51 2