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

简介: 前文再续,上一回我们完成了用户管理模块的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 ,与君共觞,和君共勉。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
相关文章
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
852 4
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
966 1
|
存储 前端开发 Java
【JAVA】Java 项目实战之 Java Web 在线商城项目开发实战指南
本文介绍基于Java Web的在线商城技术方案与实现,涵盖三层架构设计、MySQL数据库建模及核心功能开发。通过Spring MVC + MyBatis + Thymeleaf实现商品展示、购物车等模块,提供完整代码示例,助力掌握Java Web项目实战技能。(238字)
1403 0
|
人工智能 测试技术 Go
Go 语言的主流框架
本文全面解析了 Go 语言主流技术生态,涵盖 Web 框架、微服务、数据库工具、测试与部署等多个领域。重点介绍了 Gin、Echo、Beego 等高性能框架,以及 gRPC-Go、Go-Micro 等微服务组件。同时分析了 GORM、Ent 等 ORM 工具与测试部署方案,并结合场景提供选型建议,助力开发者构建高效稳定的 Go 应用。
2923 0
|
消息中间件 缓存 NoSQL
Redis各类数据结构详细介绍及其在Go语言Gin框架下实践应用
这只是利用Go语言和Gin框架与Redis交互最基础部分展示;根据具体业务需求可能需要更复杂查询、事务处理或订阅发布功能实现更多高级特性应用场景。
654 86
|
JavaScript Java 微服务
现代化 Java Web 在线商城项目技术方案与实战开发流程及核心功能实现详解
本项目基于Spring Boot 3与Vue 3构建现代化在线商城系统,采用微服务架构,整合Spring Cloud、Redis、MySQL等技术,涵盖用户认证、商品管理、购物车功能,并支持Docker容器化部署与Kubernetes编排。提供完整CI/CD流程,助力高效开发与扩展。
1314 64
|
JavaScript 前端开发 Java
【GoWails】Go做桌面应用开发?本篇文章带你上手Wails框架!一步步带你玩明白前后端双端的数据绑定!
wails是一个可以让你使用Go和Web技术编写桌面应用的项目 可以将它看作Go的快并且轻量级的Electron替代品。可以使用Go的功能,并结合现代化UI完成桌面应用程序的开发
1922 5
|
开发框架 前端开发 Go
【GoGin】(0)基于Go的WEB开发框架,GO Gin是什么?怎么启动?本文给你答案
Gin:Go语言编写的Web框架,以更好的性能实现类似Martini框架的APInet/http、Beego:开源的高性能Go语言Web框架、Iris:最快的Go语言Web框架,完备的MVC支持。
886 1
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
804 1
|
监控 Java 编译器
限流、控并发、减GC!一文搞懂Go项目资源优化的正确姿势
本章介绍Go语言项目在构建与部署阶段的性能调优和资源控制策略,涵盖编译优化、程序性能提升、并发与系统资源管理、容器化部署及自动化测试等内容,助力开发者打造高效稳定的生产级应用。