彩虹女神跃长空,Go语言进阶之Go语言高性能Web框架Iris项目实战-项目结构优化EP05

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: 前文再续,上一回我们完成了用户管理模块的CURD(增删改查)功能,功能层面,无甚大观,但有一个结构性的缺陷显而易见,那就是项目结构过度耦合,项目的耦合性(Coupling),也叫耦合度,进而言之,模块之间的关系,是对项目结构中各模块间相互联系紧密程度的一种量化。耦合的强弱取决于模块间调用的复杂性、调用模块之间的方式以及通过函数或者方法传送数据对象的多少。模块间的耦合度是指模块之间的依赖关系,包括包含关系、控制关系、调用关系、数据传递关系以及依赖关系。项目模块的相互依赖越多,其耦合性越强,同时表明其独立性越差,愈加难以维护。

前文再续,上一回我们完成了用户管理模块的CURD(增删改查)功能,功能层面,无甚大观,但有一个结构性的缺陷显而易见,那就是项目结构过度耦合,项目的耦合性(Coupling),也叫耦合度,进而言之,模块之间的关系,是对项目结构中各模块间相互联系紧密程度的一种量化。耦合的强弱取决于模块间调用的复杂性、调用模块之间的方式以及通过函数或者方法传送数据对象的多少。模块间的耦合度是指模块之间的依赖关系,包括包含关系、控制关系、调用关系、数据传递关系以及依赖关系。项目模块的相互依赖越多,其耦合性越强,同时表明其独立性越差,愈加难以维护。

项目结构优化

目前IrisBlog项目的问题就是独立性太差,截至目前为止,项目结构如下:

.  
├── README.md  
├── assets  
│ ├── css  
│ │ └── style.css  
│ └── js  
│     ├── axios.js  
│     └── vue.js  
├── favicon.ico  
├── go.mod  
├── go.sum  
├── main.go  
├── model  
│ └── model.go  
├── mytool  
│ └── mytool.go  
├── tmp  
│ └── runner-build  
└── views  
    ├── admin  
    │ └── user.html  
    ├── index.html  
    └── test.html

一望而知,前端页面(views)以及静态文件(assets)的工程化尚可,不再需要进行分层操作,但是在后端,虽然模型层(model.go)和工具层(mytool.go)已经分离出主模块,但主要业务代码还是集中在入口文件main.go中:

package main  
  
import (  
      
    "IrisBlog/model"  
    "IrisBlog/mytool"  
  
    "fmt"  
  
    "github.com/jinzhu/gorm"  
  
    _ "github.com/jinzhu/gorm/dialects/mysql"  
    "github.com/kataras/iris/v12"  
)  
  
func main() {  
  
    db, err := gorm.Open("mysql", "root:root@(localhost)/irisblog?charset=utf8mb4&parseTime=True&loc=Local")  
  
    if err != nil {  
        fmt.Println(err)  
        panic("无法连接数据库")  
    }  
    fmt.Println("连接数据库成功")  
  
    //单数模式  
    db.SingularTable(true)  
  
    // 创建默认表  
    db.AutoMigrate(&model.User{})  
  
    // 逻辑结束后关闭数据库  
    defer func() {  
        _ = db.Close()  
    }()  
  
    app := newApp(db)  
  
    app.HandleDir("/assets", iris.Dir("./assets"))  
    app.Favicon("./favicon.ico")  
    app.Listen(":5000")  
}  
  
func newApp(db *gorm.DB) *iris.Application {  
  
    app := iris.New()  
  
    tmpl := iris.HTML("./views", ".html")  
    // Set custom delimeters.  
    tmpl.Delims("${", "}")  
    // Enable re-build on local template files changes.  
    tmpl.Reload(true)  
  
    app.RegisterView(tmpl)  
  
      
  
    app.Delete("/admin/user_action/", func(ctx iris.Context) {  
  
        ID := ctx.URLParamIntDefault("id", 0)  
  
        db.Delete(&model.User{}, ID)  
  
        ret := map[string]string{  
            "errcode": "0",  
            "msg":     "删除用户成功",  
        }  
        ctx.JSON(ret)  
  
    })  
  
    app.Put("/admin/user_action/", func(ctx iris.Context) {  
  
        ID := ctx.PostValue("id")  
        Password := ctx.PostValue("password")  
  
        user := &model.User{}  
        db.First(&user, ID)  
  
        user.Password = mytool.Make_password(Password)  
        db.Save(&user)  
  
        ret := map[string]string{  
            "errcode": "0",  
            "msg":     "更新密码成功",  
        }  
        ctx.JSON(ret)  
  
    })  
  
    app.Post("/admin/user_action/", func(ctx iris.Context) {  
  
        username := ctx.PostValue("username")  
        password := ctx.PostValue("password")  
  
        fmt.Println(username, password)  
  
        md5str := mytool.Make_password(password)  
  
        user := &model.User{Username: username, Password: md5str}  
        res := db.Create(user)  
  
        if res.Error != nil {  
  
            fmt.Println(res.Error)  
  
            ret := map[string]string{  
                "errcode": "1",  
                "msg":     "用户名不能重复",  
            }  
            ctx.JSON(ret)  
  
            return  
  
        }  
  
        ret := map[string]string{  
            "errcode": "0",  
            "msg":     "ok",  
        }  
        ctx.JSON(ret)  
  
    })  
  
      
  
    return app  
  
}

入口文件main.go承载了太多业务,既需要负责数据库结构体的创建,又得操心模板的渲染和接口逻辑的编写,说白了:泥沙俱下,沉渣泛起。

事实上,像这样把所有代码都堆到一个文件中,还会带来协作问题,比如,当你花了一整天的时间,好不容易完成了一段业务逻辑,也通过了本地测试,准备第二天提交线上测试,但是第二天上班时却发现这个逻辑莫名其妙地开始报错了,这通常是因为有同事在你走后修改了你编写或者依赖的那个模块,归根结底,并不完全是协作的问题,项目结构也是因素之一。

多个研发同时修改了同一个源代码文件。虽然在规模相对较小、人员较少的项目中,这种问题或许并不严重,但是随着项目的增长,研发人员的增加,这种每天早上刚上班时都要经历一遍的痛苦就会越来越多,甚至会严重到让有的团队在长达数周的时间内都不能发布一个稳定的项目版本,因为每个人都在不停地修改自己的代码,以适应其他人所提交的变更,周而复始,恶性循环。

所以我们必须把业务单独抽离出来,比如用户管理其实是后台模块功能,只有特定的管理员才可能在其页面进行操作,所以我们可以单独创建一个控制层:

mkdir handler  
cd hanler

随后编写后台控制逻辑admin.go:

package handler  
  
import (  
  
    "github.com/kataras/iris/v12"  
)  
  
//用户管理页面模板  
func Admin_user_page(ctx iris.Context) {  
  
    ctx.View("/admin/user.html")  
  
}

这里把用户管理页面的解析函数单独抽离在handler包中,注意函数的首字母要进行大写处理,因为首字母小写函数是私有函数,只能在包内使用,无法被别的包调用。

随后改造入口文件main.go逻辑:

app.Get("/admin/user/", handler.Admin_user_page)

路由匹配时,只需要引入handler包中的Admin\_user\_page函数就可以了。

随后,对路由进行分组优化,同属一个业务的模块绑定在同一个分组中:

adminhandler := app.Party("/admin")  
    {  
        adminhandler.Use(iris.Compression)  
        adminhandler.Get("/user/", handler.Admin_user_page)  
        adminhandler.Get("/userlist/", handler.Admin_userlist)  
        adminhandler.Delete("/user_action/", handler.Admin_userdel)  
        adminhandler.Put("/user_action/", handler.Admin_userupdate)  
        adminhandler.Post("/user_action/", handler.Admin_useradd)  
  
    }

如此,业务和路由解析就彻底分开了,结构体创建函数也清爽了不少:

func newApp(db *gorm.DB) *iris.Application {  
  
    app := iris.New()  
  
    tmpl := iris.HTML("./views", ".html")  
  
    tmpl.Delims("${", "}")  
  
    tmpl.Reload(true)  
  
    app.RegisterView(tmpl)  
  
    adminhandler := app.Party("/admin")  
    {  
        adminhandler.Use(iris.Compression)  
        adminhandler.Get("/user/", handler.Admin_user_page)  
        adminhandler.Get("/userlist/", handler.Admin_userlist)  
        adminhandler.Delete("/user_action/", handler.Admin_userdel)  
        adminhandler.Put("/user_action/", handler.Admin_userupdate)  
        adminhandler.Post("/user_action/", handler.Admin_useradd)  
  
    }  
    return app  
  
}

数据层结构优化

业务层进行了拆分,但是数据层还集成在入口文件中main.go:

package main  
  
import (  
    "IrisBlog/handler"  
    "IrisBlog/model"  
  
    "fmt"  
  
    "github.com/jinzhu/gorm"  
  
    _ "github.com/jinzhu/gorm/dialects/mysql"  
    "github.com/kataras/iris/v12"  
)  
  
func main() {  
  
    db, err := gorm.Open("mysql", "root:root@(localhost)/irisblog?charset=utf8mb4&parseTime=True&loc=Local")  
  
    if err != nil {  
        fmt.Println(err)  
        panic("无法连接数据库")  
    }  
    fmt.Println("连接数据库成功")  
  
    //单数模式  
    db.SingularTable(true)  
  
    // 创建默认表  
    db.AutoMigrate(&model.User{})  
  
    // 逻辑结束后关闭数据库  
    defer func() {  
        _ = db.Close()  
    }()  
  
    app := newApp(db)  
  
    app.HandleDir("/assets", iris.Dir("./assets"))  
    app.Favicon("./favicon.ico")  
    app.Listen(":5000")  
}

这里的含义是,一旦进入入口逻辑,就立刻初始化数据库,随后执行业务代码,当业务执行完毕后,利用延迟函数defer关闭数据库链接。

这种逻辑的弊端是,一旦数据库服务挂掉,整个项目服务也会受影响,再者,很多纯静态化页面并不需要数据库链接,每一次都链接数据库,显然是画蛇添足。

所以单独建立数据包:

mkdir database  
cd database

建立数据层逻辑database.go:

package database  
  
import (  
    "IrisBlog/model"  
    "fmt"  
  
    "github.com/jinzhu/gorm"  
  
    _ "github.com/jinzhu/gorm/dialects/mysql"  
)  
  
func Db() *gorm.DB {  
  
    db, err := gorm.Open("mysql", "root:root@(localhost)/irisblog?charset=utf8mb4&parseTime=True&loc=Local")  
  
    if err != nil {  
        fmt.Println(err)  
        panic("无法连接数据库")  
    }  
    fmt.Println("连接数据库成功")  
  
    //单数模式  
    db.SingularTable(true)  
  
    // 创建默认表  
    db.AutoMigrate(&model.User{})  
  
  
    return db  
}

这里我们构建函数Db(),它返回一个数据库操作的结构体指针,专门用来执行数据库操作,需要注意的是,删除函数内之前的延后defer关闭链接函数,否则链接在函数体内就关闭了,调用方就无法使用数据库了。

调用上,直接调用database包中的Db(),就可以直接使用数据库指针了:

//用户列表接口  
func Admin_userlist(ctx iris.Context) {  
  
    db := database.Db()  
  
    var users []model.User  
    res := db.Find(&users)  
    // 逻辑结束后关闭数据库  
    defer func() {  
        _ = db.Close()  
    }()  
  
    ctx.JSON(res)  
  
}

随后,继续优化入口文件:

package main  
  
import (  
    "IrisBlog/handler"  
    "github.com/kataras/iris/v12"  
)  
  
func main() {  
  
    app := newApp()  
  
    app.HandleDir("/assets", iris.Dir("./assets"))  
    app.Favicon("./favicon.ico")  
    app.Listen(":5000")  
}  
  
func newApp() *iris.Application {  
  
    app := iris.New()  
  
    tmpl := iris.HTML("./views", ".html")  
  
    tmpl.Delims("${", "}")  
  
    tmpl.Reload(true)  
  
    app.RegisterView(tmpl)  
  
    adminhandler := app.Party("/admin")  
    {  
        adminhandler.Use(iris.Compression)  
        adminhandler.Get("/user/", handler.Admin_user_page)  
        adminhandler.Get("/userlist/", handler.Admin_userlist)  
        adminhandler.Delete("/user_action/", handler.Admin_userdel)  
        adminhandler.Put("/user_action/", handler.Admin_userupdate)  
        adminhandler.Post("/user_action/", handler.Admin_useradd)  
  
    }  
  
  
  
}

这里优化了main函数,使其逻辑更加简明和清晰。

最后,优化数据层逻辑database.go:

package database  
  
import (  
    "IrisBlog/model"  
    "fmt"  
  
    "github.com/jinzhu/gorm"  
  
    _ "github.com/jinzhu/gorm/dialects/mysql"  
    _ "github.com/jinzhu/gorm/dialects/sqlite"  
)  
  
const db_type int = 1  
  
func sqlite3() *gorm.DB {  
  
    db, err := gorm.Open("sqlite3", "/tmp/IrisBlog.db")  
  
    if err != nil {  
        fmt.Println(err)  
        panic("无法连接数据库")  
    }  
    fmt.Println("连接sqlite3数据库成功")  
  
    return db  
  
}  
  
func mysql() *gorm.DB {  
  
    db, err := gorm.Open("mysql", "root:root@(localhost)/irisblog?charset=utf8mb4&parseTime=True&loc=Local")  
  
    if err != nil {  
        fmt.Println(err)  
        panic("无法连接数据库")  
    }  
    fmt.Println("连接mysql数据库成功")  
  
    return db  
  
}  
  
func Db() *gorm.DB {  
  
    switch db_type {  
    case 0:  
        db := mysql()  
        //单数模式  
        db.SingularTable(true)  
        // 创建默认表  
        db.AutoMigrate(&model.User{})  
        return db  
    case 1:  
        db := sqlite3()  
        //单数模式  
        db.SingularTable(true)  
        // 创建默认表  
        db.AutoMigrate(&model.User{})  
        return db  
    default:  
        panic("未知的数据库")  
    }  
  
  
}

这里我们分别封装mysql和sqlite3数据库指针函数,然后通过switch语句来根据不同的开发环境而进行切换和控制。

至此,项目结构的首次结构性优化就完成了,优化后的结构如下:

├── README.md  
├── assets  
│ ├── css  
│ │ └── style.css  
│ └── js  
│     ├── axios.js  
│     └── vue.js  
├── database  
│ └── database.go  
├── favicon.ico  
├── go.mod  
├── go.sum  
├── handler  
│ └── admin.go  
├── main.go  
├── model  
│ └── model.go  
├── mytool  
│ └── mytool.go  
├── tmp  
│ └── runner-build  
└── views  
    ├── admin  
    │ └── user.html  
    ├── index.html  
    └── test.html

结语

为什么我们一开始不直接采用低耦合高内聚的项目架构?因为别人的经验并不是我们的经验,只有真正经历过才是真实的开发经验,项目开发没有标准答案,只有选择,然后承担后果,只有尝试过苦涩的果实之后,下一次才会做出正确的选择。该项目已开源在Github:https://github.com/zcxey2911/IrisBlog ,与君共觞,和君共勉。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
19天前
|
安全 网络协议 网络安全
【Docker项目实战】使用Docker部署web-check网站分析工具
【4月更文挑战第20天】使用Docker部署web-check网站分析工具
54 1
|
2月前
|
安全
网易web安全工程师进阶版课程
《Web安全工程师(进阶)》是由“ i春秋学院联合网易安全部”出品,资深讲师团队通过精炼的教学内容、丰富的实际场景及综合项目实战,帮助学员纵向提升技能,横向拓宽视野,牢靠掌握Web安全工程师核心知识,成为安全领域高精尖人才。 ## 学习地址
23 6
网易web安全工程师进阶版课程
|
1月前
|
存储 算法 编译器
掌握Go语言:探索Go语言递归函数的高级奥秘,优化性能、实现并发、解决算法难题(28)
掌握Go语言:探索Go语言递归函数的高级奥秘,优化性能、实现并发、解决算法难题(28)
|
10天前
|
监控 安全 Go
【Go语言专栏】Go语言中的并发性能分析与优化
【4月更文挑战第30天】Go语言以其卓越的并发性能和简洁语法著称,通过goroutines和channels实现并发。并发性能分析旨在解决竞态条件、死锁和资源争用等问题,以提升多核环境下的程序效率。使用pprof等工具可检测性能瓶颈,优化策略包括减少锁范围、使用无锁数据结构、控制goroutines数量、应用worker pool和优化channel使用。理解并发模型和合理利用并发原语是编写高效并发代码的关键。
|
10天前
|
缓存 监控 测试技术
【Go语言专栏】使用Go语言构建高性能Web服务
【4月更文挑战第30天】本文探讨了使用Go语言构建高性能Web服务的策略,包括Go语言在并发处理和内存管理上的优势、基本原则(如保持简单、缓存和并发控制)、标准库与第三方框架的选择、编写高效的HTTP处理器、数据库优化以及性能测试和监控。通过遵循最佳实践,开发者可以充分利用Go语言的特性,构建出高性能的Web服务。
|
10天前
|
中间件 Go API
【Go 语言专栏】Go 语言中的 Web 框架比较与选择
【4月更文挑战第30天】本文对比了Go语言中的四个常见Web框架:功能全面的Beego、轻量级高性能的Gin、简洁高效的Echo,以及各自的性能、功能特性、社区支持。选择框架时需考虑项目需求、性能要求、团队经验和社区生态。开发者应根据具体情况进行权衡,以找到最适合的框架。
|
12天前
|
中间件 Go API
Golang深入浅出之-Go语言标准库net/http:构建Web服务器
【4月更文挑战第25天】Go语言的`net/http`包是构建高性能Web服务器的核心,提供创建服务器和发起请求的功能。本文讨论了使用中的常见问题和解决方案,包括:使用第三方路由库改进路由设计、引入中间件处理通用逻辑、设置合适的超时和连接管理以防止资源泄露。通过基础服务器和中间件的代码示例,展示了如何有效运用`net/http`包。掌握这些最佳实践,有助于开发出高效、易维护的Web服务。
27 1
|
17天前
|
Cloud Native 网络协议 Go
[云原生] Go web工作流程
[云原生] Go web工作流程
|
2月前
|
SQL 机器学习/深度学习 缓存
Go语言Web应用实战与案例分析
【2月更文挑战第21天】本文将通过实战案例的方式,深入探讨Go语言在Web应用开发中的应用。我们将分析一个实际项目的开发过程,展示Go语言在构建高性能、可扩展Web应用方面的优势,并分享在开发过程中遇到的问题和解决方案,为读者提供宝贵的实战经验。
|
2月前
|
安全 中间件 Go
Go语言Web服务性能优化与安全实践
【2月更文挑战第21天】本文将深入探讨Go语言在Web服务性能优化与安全实践方面的应用。通过介绍性能优化策略、并发编程模型以及安全加固措施,帮助读者理解并提升Go语言Web服务的性能表现与安全防护能力。