彩虹女神躍長空,Go語言進階之Go語言高性能Web框架Iris項目實戰-項目結構優化EP05

来源:https://www.cnblogs.com/v3ucn/archive/2022/09/05/16659544.html
-Advertisement-
Play Games

前文再續,上一回我們完成了用戶管理模塊的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 ,與君共觴,和君共勉。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 構建者是一種可以將複雜對象的構建和表示分離開來,從而使得一個構建過程可以生成多個不同的表示對象。建造者模式通過一步一步構建對象。 ...
  • 觀察者模式又叫發佈-訂閱(Publish-Subscribe)模式,是對象的行為模式,訂閱是表示這些觀察者對象需要向目標對象進行註冊,這樣目標對象才知道有哪些對象在觀察它。發佈指的是當目標對象的狀態改變時,它就向它所有的觀察者對象發佈狀態更改的消息,以讓這些觀察者對象知曉。定義對象間的一種一對多的依... ...
  • 工作中總是遇到數據存儲相關的 Bug 工單,新需求開發設計中也多多少少會有數據模型設計和存儲相關的問題。經過幾次存儲方案設計選型和討論後發現需要有更全面的思考框架。 日常開發中常用的存儲方案選型很多都是 “拿來主義” 的,憑藉著經驗、習慣選用,但對它們的細節特性或約束少有研究。 除了手邊會用的存儲方... ...
  • 坦克大戰【3】 筆記目錄:(https://www.cnblogs.com/wenjie2000/p/16378441.html) 坦克大戰0.6版 √增加功能 防止敵人坦克重疊運動 記錄玩家的成績(累積擊毀敵方坦克數),存檔退出【io流】 記錄當時的敵人坦克坐標與方向,存檔退出【io流】 玩游戲時 ...
  • 發現問題 前幾天在看別人的項目的時候,發現一個問題,簡單復現一下這個問題 // 註意這是一個Integer對象的數組哦 Integer[] arr = new Integer[]{9999,88,77}; List<Integer> list = Arrays.asList(arr); // 執行以 ...
  • 前置知識 什麼是進程,什麼又是線程?咱不是講系統,簡單說下,知道個大概就好了。 進程:一個可執行文件執行的過程。 線程:操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務 什 ...
  • Spring(三)——AOP 概念 什麼是AOP (1)面向切麵編程(方面),利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。 (2)通俗描述:不通過修改源代碼方式,在主幹功能裡面添加新功能 AOP底層原理 JDK動態 ...
  • 線程基礎03 6.用戶線程和守護線程 用戶線程:也叫工作線程,當線程的任務執行完或者通知方法結束。平時用到的普通線程均是用戶線程,當在Java程式中創建一個線程,它就被稱為用戶線程 守護線程(Daemon):一般是為工作線程服務的,當所有的用戶線程結束,守護線程自動結束 常見的守護線程:垃圾回收機制 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...