go go go 出发咯 - go web开发入门系列(三) 项目基础框架搭建与解读


往期回顾

  • go go go 出发咯 - go web开发入门系列(一) helloworld
  • go go go 出发咯 - go web开发入门系列(二) Gin 框架实战指南

前言


如果你已经跟随 Go 语言的学习路线,掌握了标准库 net/http 的基础,并且体验过像 Gin 这样优秀 Web 框架带来的便捷,那么你很可能会遇到下一个问题:当项目不再是简单的“Hello, World!”,而是需要长期维护、多人协作的真实产品时,我应该如何组织我的代码?

直接在 Gin 的 Handler 函数中编写所有逻辑,一开始可能很方便,但随着业务逻辑变得复杂,代码会迅速变得难以管理。如何优雅地处理数据库交互?如何分离业务逻辑和 Web 逻辑?如何让项目易于测试和扩展?

对于许多从 Java Spring Boot 或其他成熟 MVC 框架转向 Go 的开发者来说,最初常常会感到一丝困惑:“没有了熟悉的注解和大量的自动化配置,我该如何组织我的项目?” Go 语言以其简洁和“显式优于隐式”的哲学著称,但这并不意味着我们需要牺牲代码的结构和可维护性。

恰恰相反,通过遵循一些社区沉淀下来的最佳实践,我们可以构建出比许多“魔法”框架更清晰、更健壮的应用程序。

本文将带您从零开始,搭建一个生产级的 Go Web 应用框架。我们将深入探讨分层架构、依赖注入和面向接口编程这些核心概念,并提供一套可以直接用于您下一个项目的完整代码骨架。


从0到1:构建一个生产级的 Go Web 应用框架

蓝图:清晰的分层架构

一个优秀的项目始于一个清晰的目录结构。这是我们将要使用的蓝图:

/awesomeProject
| ├── cmd/ # 入口文件 
│ 	└── server/ 
│ 		└── main.go # 主程序入口 
├── configs/ # 配置文件 
│ 	└── config.dev.yaml 
├── internal/ # 内部模块 
│ ├── config/ # 配置加载 
│ ├── database/ # 数据库连接 
│ ├── models/ # 数据模型 
│ ├── repository/ # 数据访问层 
│ └── service/ # 业务逻辑层 
├── transport/ # 传输层 
│ └── http/ # HTTP处理 
└── go.mod # 依赖管理

/cmd/server: 存放应用程序的启动入口。一个项目可以有多个 cmd,比如一个用于启动 API 服务,一个用于执行定时任务。

/internal: 存放项目内部的私有代码。Go 语言会强制规定,internal 包只能被其直接父目录下的代码所引用,这为我们提供了一层天然的访问保护。

/configs: 存放所有的配置文件,实现配置与代码的分离。

深入各层:代码如何组织?

现在,让我们深入探索每一层的职责和代码实现。

1. main.go:一切的总装车间

main.go 作为项目的入口类,虽然不处理具体的业务逻辑,但他承接所有的项目流程,起到组装和启动的作用

/cmd/server/main.go
func main() {// 1. 加载配置cfg, err := config.Load("./configs/config.dev.yaml")if err != nil {log.Fatalf("Failed to load config: %v", err)}// 2. 初始化数据库连接 db, err := database.NewConnection(cfg.Database)if err != nil {log.Fatalf("Failed to connect to database: %v", err)}defer db.Close()log.Println("Database connection established")// 3. 依赖注入:将所有组件连接起来//    Repository -> Service -> HandleruserRepo := repository.NewUserRepository(db)userService := service.NewUserService(userRepo)userHandler := http.NewUserHandler(userService)// 4. 初始化 Gin 路由router := gin.Default()// 5. 注册路由api := router.Group("/api/v1"){users := api.Group("/users"){users.POST("", userHandler.Register)users.GET("/:id", userHandler.Get)}}// 6. 启动服务器log.Println("Starting server on :8080")if err := router.Run(":8080"); err != nil {log.Fatalf("Failed to start server: %v", err)}
}
2. Repository 层:数据的唯一守门人

这一层是与数据库直接交互的唯一地方,它封装了所有的 SQL 操作。

/internal/repository/UserRepository.go
package repositoryimport ("awesomeProject/internal/models""context""database/sql"
)// UserRepository 接口定义了用户数据的所有操作,便于测试和解耦
type UserRepository interface {Create(ctx context.Context, user *models.User) errorFindByID(ctx context.Context, id int64) (*models.User, error)
}// mysqlUserRepository 是 UserRepository 的 MySQL 实现
type mysqlUserRepository struct {db *sql.DB
}// NewUserRepository 创建一个新的 UserRepository 实例
func NewUserRepository(db *sql.DB) UserRepository {return &mysqlUserRepository{db: db}
}func (r *mysqlUserRepository) Create(ctx context.Context, user *models.User) error {query := "INSERT INTO users (name, email) VALUES (?, ?)"result, err := r.db.ExecContext(ctx, query, user.Name, user.Email)if err != nil {return err}id, err := result.LastInsertId()if err != nil {return err}user.ID = idreturn nil
}func (r *mysqlUserRepository) FindByID(ctx context.Context, id int64) (*models.User, error) {query := "SELECT id, name, email FROM users WHERE id = ?"row := r.db.QueryRowContext(ctx, query, id)var user models.Userif err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {if err == sql.ErrNoRows {return nil, nil // Or a custom not found error}return nil, err}return &user, nil
}
3. Service 层:业务逻辑的核心

Service 层负责处理所有的业务规则。它调用 Repository 层来获取和存储数据,但它本身不应该知道数据库的存在。

/internal/service/UserService.go
package serviceimport ("awesomeProject/internal/models""awesomeProject/internal/repository""context"
)type UserService struct {userRepo repository.UserRepository
}func NewUserService(repo repository.UserRepository) *UserService {return &UserService{userRepo: repo}
}func (s *UserService) RegisterUser(ctx context.Context, name, email string) (*models.User, error) {// 可以在这里添加业务逻辑,比如检查email是否已存在等user := &models.User{Name:  name,Email: email,}err := s.userRepo.Create(ctx, user)if err != nil {return nil, err}return user, nil
}func (s *UserService) GetUser(ctx context.Context, id int64) (*models.User, error) {return s.userRepo.FindByID(ctx, id)
}
4. Handler 层:连接世界的桥梁

Handler 层负责处理 HTTP 请求。它解析请求参数,调用 Service 层来完成业务处理,然后将结果打包成 HTTP 响应返回给客户端。

/transport/http/UserHandler.go
package httpimport ("awesomeProject/internal/service""net/http""strconv""github.com/gin-gonic/gin"
)type UserHandler struct {userService *service.UserService
}func NewUserHandler(svc *service.UserService) *UserHandler {return &UserHandler{userService: svc}
}func (h *UserHandler) Register(c *gin.Context) {var req struct {Name  string `json:"name"`Email string `json:"email"`}if err := c.ShouldBindJSON(&req); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}user, err := h.userService.RegisterUser(c.Request.Context(), req.Name, req.Email)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to register user"})return}c.JSON(http.StatusCreated, user)
}func (h *UserHandler) Get(c *gin.Context) {id, err := strconv.ParseInt(c.Param("id"), 10, 64)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user ID"})return}user, err := h.userService.GetUser(c.Request.Context(), id)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get user"})return}if user == nil {c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})return}c.JSON(http.StatusOK, user)
}
5. Config 层:做配置文件的映射

使用 yaml.v3config.yml 处理成结构体,提供读取方法给到 main.go 进行配置文件读取

type DatabaseConfig struct {DSN          string `yaml:"dsn"`MaxOpenConns int    `yaml:"max_open_conns"`MaxIdleConns int    `yaml:"max_idle_conns"`
}type Config struct {Database DatabaseConfig `yaml:"database"`
}func Load(path string) (*Config, error) {data, err := os.ReadFile(path)if err != nil {return nil, err}var cfg Configif err := yaml.Unmarshal(data, &cfg); err != nil {return nil, err}return &cfg, nil
}

框架中使用到的依赖:

  • mysql 连接依赖下载

    go get -u github.com/go-sql-driver/mysql
    
  • 配置文件yml解析依赖下载

    go get gopkg.in/yaml.v3
    

架构对比:Go 框架 vs. Spring Boot MVC

对于有 Spring Boot 背景的开发者,将这个 Go 框架与熟悉的 MVC 架构进行对比。

概念Go 框架 (我们构建的)Java Spring Boot MVC核心差异 (哲学对比)
依赖注入 (DI)手动注入:在 main.go 中显式调用构造函数 (NewService(repo)) 来创建和连接实例。自动注入:通过 @Autowired 或构造函数注入,由 IoC 容器在启动时自动扫描和装配。显式 vs. 隐式:Go 的方式让你对依赖关系一目了然;Spring 的方式更便捷,但有时像个“黑盒”。
控制器 (Controller)Handler 函数:一个普通的 Go 函数,通过 router.POST(...) 绑定到特定路由。@RestController:一个带有 @RestController 注解的类,方法用 @RequestMapping 等注解来映射路由。函数 vs. 对象:Go 更倾向于使用简单的函数来处理请求;Spring 将相关请求组织在一个控制器类中。
业务逻辑层Service 结构体:通过构造函数接收 Repository 接口。@Service:一个带有 @Service 注解的类,通过 @Autowired 注入 Mapper/DAO 接口。概念上非常相似,都是处理业务逻辑。主要区别在于依赖注入的方式(手动 vs. 自动)。
数据访问层Repository 接口与实现:手动编写 SQL,通过 Scan 函数进行字段映射。Mapper/DAO 接口 (MyBatis/JPA):通过注解或 XML 定义 SQL,框架自动实现接口并完成数据映射。手动挡 vs. 自动挡:Go 提供了完全的 SQL 控制权;Spring Data/MyBatis 提供了极大的便利性,隐藏了许多底层细节。
实体/领域模型models 结构体 (struct):纯粹的数据载体。Entity 类 (class):通常带有 @Entity, @Table 等注解,既是数据载体也参与 ORM 映射。角色基本相同,都是定义核心数据结构。
配置手动加载:在 main.go 中调用库(如 gopkg.in/yaml.v3)来读取并解析 config.yaml自动加载与绑定:Spring Boot 自动读取 application.properties/yml,并通过 @Value@ConfigurationProperties 自动绑定到对象。手动 vs. 自动:Go 需要你明确地加载配置;Spring 提供了强大的自动化配置和 Profile 管理能力。

mysql建表语句忘了同步了,贴一下出来

create table users
(id    bigint auto_incrementprimary key,name  varchar(255) not null,email varchar(255) not null,constraint emailunique (email)
);

🌍代码框架链接

感兴趣的小伙伴,开始实践叭!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/88400.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/88400.shtml
英文地址,请注明出处:http://en.pswp.cn/web/88400.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【字节跳动】数据挖掘面试题0014:SQL中count(1), count(*), count(列)区别

文章大纲SQL 中 count(1)、count(*)、count(某列) 的区别一、核心定义与行为差异二、示例说明差异三、性能差异与优化四、适用场景建议五、面试应答要点六、索引扫描与全表扫描1. 索引扫描的触发条件2. 全表扫描的适用场景3. 常见面试问题点Q1:索引扫描一定比全表扫…

Linux面试问题-软件测试

1、你在上一家公司常用的Linux命令有哪些?答:使用vim/vi编辑文件,使用cat,more,less,head查看文件,使用grep过滤日志中的error,使用ps查看进程,使用top查看实时进程,netstat查看端口…

时序数据库的存储之道:从数据特性看技术要点

时序数据的独特挑战时序数据(Time-Series Data)是指按时间顺序记录的一系列数据点,在物联网、金融、工业监控等领域无处不在。与传统数据相比,时序数据具有几个鲜明特点:时间导向性:每个数据点都带有精确的时间戳高写入量&#xf…

【vim中替换】

vim中替换1 : s/在Vim中经常高频使用到的命令:1 : s/ :s 命令的基本语法是 :[range]s/{pattern}/{string}/[flags],其中: • [range] 是可选的范围,用于指定替换的行范围。例如,% 表示全文,10,…

Qt实战:使用QSqlDatabase连接MySQL,并实现增删改查

文章目录一、创建数据表二、连接MySQL数据库三、封装成一个完整的轻量级 ORM 风格类四、实现派生具体模型类五、支持多线程连接池 ORM 事务封装一、创建数据表 数据库名: 我们先创建一个数据库,名字叫 game_db: CREATE DATABASE IF NOT E…

Python脚本保护工具库之pyarmor使用详解

概要 PyArmor是一个专门为Python代码提供加密保护的第三方库,旨在解决Python源代码易被反编译和泄露的安全问题。作为一种动态代码保护工具,PyArmor能够对Python脚本进行混淆和加密处理,有效防止源代码被恶意获取、分析或篡改。该库特别适用于商业软件开发、知识产权保护和…

仓颉编程语言:从入门到精通

为啥要瞅瞅仓颉这玩意儿? 有一说一,现在的编程语言多得跟米一样,对吧?那一门新语言想火,没点绝活儿肯定不行。仓颉(Cangjie)这哥们儿,是华为搞出来的新玩意儿,静态编译的…

线性探针是什么:是一种用于探测神经网络中特定特征的工具

线性探针是什么 线性探针是一种在机器学习和相关领域广泛应用的技术,用于评估预训练模型特征、检测数据中的特定序列等。在不同的应用场景下,线性探针有着不同的实现方式和作用: 评估预训练模型特征:在机器学习中,线性探针是一种评估预训练模型“特征迁移能力”的标准化方…

【论文阅读】Few-Shot PPG Signal Generation via Guided Diffusion Models

从少量样本数据选择到后处理的整体框架。首先,扩散模型在N样本数据集和指导下的训练。接着,模型生成一个增强的数据集,并进一步优化以提高保真度。最后,这些合成数据与少量样本训练数据集结合,用于基准模型的训练和评估。数据分布从最初的红色变为保真度增强的蓝色,这表明…

CentOS-7的“ifupdown“与Debian的“ifupdown“对比 笔记250706

CentOS-7的"ifupdown"与Debian的"ifupdown"对比 笔记250706 CentOS 7 和 Debian 的 ifupdown 工具名称相同,但在实现机制、配置文件语法和系统集成上存在显著差异。以下是核心对比分析: ⚙️ 一、核心差异概览 对比维度CentOS 7De…

架构如传承:技术长河中的可持续乐章

代码结构:协作基石 在软件开发的世界里,代码结构就如同建筑的框架,支撑着整个项目的运行。想象一下,你加入了一个新的开发团队,接手一个已经有一定规模的项目。当你打开代码库,看到的是一团乱麻般的代码&a…

Ubuntu22.04更新Openssh至9.9p2无法正常连接,报错解决

Ubuntu22.04更新Openssh至9.9p2无法正常连接,报错解决 1.报错信息如下所示ExecStart/usr/sbin/sshd -D $SSHD_OPTS (codeexited, status255/EXCEPTION)2.这通常说明 SSH 配置文件存在语法错误、缺失关键文件,或者端口被占用等问题。 3.检查配置文件是否有…

基于小程序的智能停车管理系统设计与开发

项目介绍 本课程演示的是一款基于小程序的智能停车管理系统设计与开发,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含:项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 3…

多模态大语言模型arxiv论文略读(155)

Panther: Illuminate the Sight of Multimodal LLMs with Instruction-Guided Visual Prompts ➡️ 论文标题:Panther: Illuminate the Sight of Multimodal LLMs with Instruction-Guided Visual Prompts ➡️ 论文作者:Honglin Li, Yuting Gao, Chengl…

SAP ERP与Oracle EBS对比,两个ERP系统有什么区别?

据统计,2024年中国ERP软件市场规模预计突破210亿元,其中SAP和Oracle占据第一梯队,共占国内ERP市场45%以上的份额,在高端市场尤其显著。SAP和Oracle作为ERP行业的两大巨头,具体有什么区别呢?SAP是什么&#…

网络安全之RCE分析与利用详情

Gogs背景介绍Gogs(Go Git Service)是一款用Go语言编写的轻量级、开源的Git仓库托管系统。它的设计目标是让搭建和维护Git服务变得简单、快速,同时提供类似GitHub的功能,但对资源消耗更少,适合个人或者小型团队使用&…

OpenCV图片操作100例:从入门到精通指南(2)

接上篇,本文将继续分享OpenCV实用技巧,涵盖图像处理、目标检测、3D视觉等进阶领域!六、图像变换进阶17. 图像金字塔# 高斯金字塔下采样 smaller cv2.pyrDown(img)# 高斯金字塔上采样 larger cv2.pyrUp(img)用于多尺度图像处理,构…

2、Connecting to Kafka

KafkaAdmin-请参阅配置主题ProducerFactory-请参阅发送消息ConsumerFactory-请参阅接收消息从2.5版本开始&#xff0c;每个版本都扩展了KafkaResourceFactory。这允许在运行时通过向引导服务器的配置中添加Supplier<String>来更改引导服务器&#xff1a;setBootstrapServ…

二进制部署CentOS8.5+Kubernetes1.33.2+Docker28.3.1高可用集群

Kubernetes 集群部署202507 本实验主要软件环境及资源如下&#xff1a; 二进制部署CentOS8.5Kubernetes1.33.2Docker28.3.1高可用集群 一、系统要求 ​Kubermetes 系统由一组可执行程序组成&#xff0c;用户可以通过Kubernetes在GitHub 的项目网站下载编译好的二进制文件或…

127. Java 泛型 - 泛型类与子类型

文章目录127. Java 泛型 - 泛型类与子类型1. 泛型类和接口的子类型化示例&#xff1a;ArrayList 和 List2. 自定义泛型接口的子类型化示例&#xff1a;泛型接口的子类型解释3. 泛型类和接口的类型参数4. 总结127. Java 泛型 - 泛型类与子类型 1. 泛型类和接口的子类型化 在 J…