最近在研究一个goframe的开源项目,GitHub - gogf/focus: Community system build using GoFrame.
以下是针对该开源项目的解析,正好学习一下go语言的微服务框架
还记得我之前出的有一篇goframe的文件结构吗?
/
├── app
│ ├── api
│ ├── dao
│ ├── model
│ └── service
├── boot
├── config
├── docker
├── document
├── i18n
├── library
├── packed
├── public
├── router
├── template
├── dockerfile
├── go.mod
└── main.go
这个是goframe的文件结构,咱们先看app下的目录
- app:业务逻辑层,存放所有业务逻辑相关的代码。其中的 api、dao、model、service 是业务开发的主要部分。 - api:业务接口,类似于三层架构设计中的表示层(UI),负责接收并响应客户端的输入与输出,包括对输入参数的过滤、转换、校验,对输出数据结构的维护,并调用 service 实现业务逻辑处理。- dao:数据访问层,负责所有的数据访问收口,通过 ORM 组件等实现数据的增删改查等操作,并将操作结果反馈到业务逻辑层。- model:模型定义层,主要用于数据库表的映射对象,包含数据结构定义,不包含任何方法定义。它服务于表示层、业务逻辑层以及数据访问层,在三层之间进行数据参数传输,强化数据表示的简约性。- service:业务逻辑封装层,类似于三层架构设计中的业务逻辑层(BLL),负责具体业务逻辑的实现以及封装,可被不同的包调用。
- 先看dao文件夹。dao文件夹下有个internal文件夹,我先说这个文件夹是干什么的。
在 GoFrame 框架中,
dao
文件夹下的
internal
文件夹通常用于存放一些内部使用的、不对外暴露的代码。
dao
(Data Access Object,数据访问对象)层主要涉及与数据库的交互操作,例如对数据库表的增删改查(CRUD)等基本操作。**而
internal
文件夹中的代码可能是一些辅助性的、或者是与特定实现细节相关的代码。**
**将这些内部代码放在
internal
文件夹中,可以更好地实现代码的封装和隔离。这样做的目的是限制这些代码的使用范围,防止其他外部的代码直接引用或依赖这些内部的实现细节,从而提高代码的可维护性和可扩展性**。按照 Go 语言的包管理规范,以
internal
命名的包只能被其所在的父级目录及父级目录的子包导入,其他位置的包无法导入。
**而在
dao
文件夹的直接子目录下(非
internal
文件夹),可能会有一些对外公开的 DAO 接口或结构体,供其他模块在项目内部使用,这些公开的部分定义了外部可以使用的数据访问操作和接口。**
学习要深入思考:好,那么我问你,如何实现防止外部代码引用?如果换做你你会怎么设计?
**1.设置这个
internal
文件夹下的包名和其他文件夹下的包名不一样**
2.interal文件夹下的所有的属性和方法的标识符都是首字母小写,避免被其他的包访问到
external外部的 interal内部的
好咱们接下来看看这个文件夹下都有哪些文件
category.go
// ==========================================================================
// Code generated by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/frame/gmvc"
)
// CategoryDao is the manager for logic model data accessing and custom defined data operations functions management.
type CategoryDao struct {
gmvc.M // M is the core and embedded struct that inherits all chaining operations from gdb.Model.
C categoryColumns // C is the short type for Columns, which contains all the column names of Table for convenient usage.
DB gdb.DB // DB is the raw underlying database management object.
Table string // Table is the underlying table name of the DAO.
}
// CategoryColumns defines and stores column names for table gf_category.
type categoryColumns struct {
Id string // 分类ID,自增主键
ContentType string // 内容类型:topic, ask, article, reply
Key string // 栏目唯一键名,用于程序部分场景硬编码,一般不会用得到
ParentId string // 父级分类ID,用于层级管理
UserId string // 创建的用户ID
Name string // 分类名称
Sort string // 排序,数值越低越靠前,默认为添加时的时间戳,可用于置顶
Thumb string // 封面图
Brief string // 简述
Content string // 详细介绍
CreatedAt string // 创建时间
UpdatedAt string // 修改时间
}
// NewCategoryDao creates and returns a new DAO object for table data access.
func NewCategoryDao() *CategoryDao {
columns := categoryColumns{
Id: "id",
ContentType: "content_type",
Key: "key",
ParentId: "parent_id",
UserId: "user_id",
Name: "name",
Sort: "sort",
Thumb: "thumb",
Brief: "brief",
Content: "content",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
}
return &CategoryDao{
C: columns,
M: g.DB("default").Model("gf_category").Safe(),
DB: g.DB("default"),
Table: "gf_category",
}
}
先简单叙述一下在goframe中的interal文件夹下的这段代码的作用
这段 Go 代码主要**定义了与
CategoryDao
(类别数据访问对象)相关的结构体和函数,用于管理对特定数据表(
gf_category
)的操作。**
**以上代码中的
CategoryDao
类似于 Java Spring Boot 中的数据访问层(Data Access Object,DAO)部分。**
在 Spring Boot 中,DAO 层通常用于与数据库进行交互,处理数据的读取、写入、更新和删除等操作,封装了对数据库表的操作逻辑。
与上述 Go 代码中的
CategoryDao
类似,都是为了提供一个专门的、结构化的方式来管理和操作与特定数据表相关的数据。
categoryColumns
结构体:定义了一系列字段,每个字段对应着gf_category
表中的一个列名,例如Id
(分类 ID)、ContentType
(内容类型)等。这些字段名的定义有助于在代码中更清晰地引用和操作表中的列。CategoryDao
结构体:包含了几个重要的成员。-gmvc.M
:继承自gmvc
包中的M
结构体,可能继承了一些与数据库模型操作相关的方法和功能。-C
:categoryColumns
类型,用于存储表的列名信息。-DB
:gdb.DB
类型,代表底层的数据库管理对象,用于执行数据库操作。-Table
:字符串类型,指定了关联的数据表名。NewCategoryDao
函数:用于创建并返回一个CategoryDao
结构体的指针。在函数内部,首先创建了一个categoryColumns
类型的变量columns
,并初始化了其中的列名。然后,通过g.DB("default").Model("gf_category").Safe()
获取了一个与gf_category
表相关的模型对象,并将其赋值给CategoryDao
结构体的M
成员。同时,设置了DB
成员为g.DB("default")
,Table
成员为"gf_category"
。
总的来说,这段代码的目的是提供一个封装的数据访问对象,使得对
gf_category
表的操作更加方便、可管理和具有结构性。通过这种方式,可以在其他部分的代码中使用
CategoryDao
来执行数据库查询、插入、更新等操作,而无需直接处理底层的数据库连接和操作细节,提高了代码的可读性、可维护性和可扩展性。这样的设计有助于将数据访问逻辑与其他业务逻辑分离,符合常见的软件设计原则。
同时,代码中还提到了一些关于现有 ORM(对象关系映射)使用的痛点描述和改进方案设计的链接,可能意味着这段代码是基于某种改进思路或为了解决特定问题而编写的。但具体的痛点和改进内容需要参考链接中的详细信息。如果你在使用 GoFrame 框架进行开发,并且需要操作
gf_category
表,那么可以使用
NewCategoryDao
函数创建的
CategoryDao
对象来进行相关的数据访问操作。这样可以更方便地进行数据库交互,同时避免了直接处理底层数据库操作的复杂性和易错性。例如,可以使用
M
成员提供的方法来执行查询、插入、更新等数据库操作,通过
C
成员中的列名来指定相关的字段等。具体的使用方法可能需要参考
gmvc.M
的定义以及 GoFrame 框架的相关文档和示例。
先看import部分
import (
"github.com/gogf/gf/database/gdb"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/frame/gmvc"
)
"github.com/gogf/gf/database/gdb"
: GoFrame 框架中与数据库操作相关的包。"github.com/gogf/gf/frame/g"
: GoFrame 框架的一些通用功能和工具。"github.com/gogf/gf/frame/gmvc"
: GoFrame 框架中的模型 - 视图 - 控制器(MVC)相关的功能
type CategoryDao struct {
gmvc.M // M is the core and embedded struct that inherits all chaining operations from gdb.Model.
C categoryColumns // C is the short type for Columns, which contains all the column names of Table for convenient usage.
DB gdb.DB // DB is the raw underlying database management object.
Table string // Table is the underlying table name of the DAO.
}
定义了一个名为
CategoryDao
的结构体:
gmvc.M
:这是一个嵌入的结构体,可能继承了来自**gdb.Model
的一系列链式操作方法**。C
:是categoryColumns
类型,用于方便地管理和操作表的列名。DB
:是**gdb.DB
类型,代表底层的数据库管理对象**。Table
:是字符串类型,存储了该 DAO 所操作的表的名称。
总的来说,
CategoryDao
结构体整合了与特定数据库表操作相关的各种元素和功能。
**
CategoryDao
结构体很可以用在以下几个方面:**
- 数据库操作相关的业务逻辑中:用于对特定表(在这里是由
Table
字段指定的表)进行增删改查等操作。 - Web 应用的后端服务:处理与数据库中该表相关的数据请求和响应。
- 数据迁移或数据同步任务:在数据迁移或与其他数据源进行数据同步时,用于读取、写入和转换该表的数据。
- 数据处理脚本或工具:如果有独立的脚本或工具用于处理数据库中的特定数据,这个结构体可能会被使用。
总之,只要涉及到对指定数据库表进行操作和管理数据的场景,都有可能使用这样的结构体来实现相关功能。
**
gmvc.M
是 GoFrame 框架中定义的结构体。在给定的代码中,通过
import "github.com/gogf/gf/frame/gmvc"
语句导入了
gmvc
包,从而可以使用其中定义的
M
结构体。**
**
M
结构体继承了来自
gdb.Model
的一系列链式操作方法,这意味着可以通过
gmvc.M
来使用这些链式操作方法,以便更方便地进行数据访问和操作**。在
CategoryDao
结构体中嵌入
gmvc.M
,可以使
CategoryDao
具备相关的功能和特性。
**
gmvc.M是什么?
**
gmvc是个包,M是个结构体,继承了来自gdb.Model的一系列链式操作方法
gdb.Model是什么?
**在 GoFrame 框架中,
gdb.Model
可能指的是与数据库操作相关的模型。GoFrame 框架中的
gdb
模块提供了一些数据库操作的功能,通过
gdb.Model
可以进行诸如插入、查询、更新、删除等数据库操作,并可能支持获取字段列数据、动态切换数据库对象、实现悲观锁操作等特定的链式操作方法。**
gdb.DB
是从github.com/gogf/gf/database/gdb
导入的。gmvc.M
是从github.com/gogf/gf/frame/gmvc
导入的。
而
categoryColumns
在当前代码文件中自定义的类型,不是从这三个导入的包中引入的。
type categoryColumns struct {
Id string // 分类ID,自增主键
ContentType string // 内容类型:topic, ask, article, reply
Key string // 栏目唯一键名,用于程序部分场景硬编码,一般不会用得到
ParentId string // 父级分类ID,用于层级管理
UserId string // 创建的用户ID
Name string // 分类名称
Sort string // 排序,数值越低越靠前,默认为添加时的时间戳,可用于置顶
Thumb string // 封面图
Brief string // 简述
Content string // 详细介绍
CreatedAt string // 创建时间
UpdatedAt string // 修改时间
}
好,你看
type CategoryDao struct {
gmvc.M // M is the core and embedded struct that inherits all chaining operations from gdb.Model.
C categoryColumns // C is the short type for Columns, which contains all the column names of Table for convenient usage.
DB gdb.DB // DB is the raw underlying database management object.
Table string // Table is the underlying table name of the DAO.
}
咱们这次讲详细点: 这个categoryColumn和嵌套结构体gmvc.M 有什么区别?gmvc.M 是嵌套结构体被嵌套的内容,那categoryColumn是什么呢?
categoryColumns
不是嵌入的结构体,而是在
CategoryDao
结构体中作为一个独立的成员存在。
gmvc.M
是嵌入到
CategoryDao
结构体中的。
在
CategoryDao
结构体中:
gmvc.M
:通过嵌入,CategoryDao
结构体可以直接使用gmvc.M
结构体中的方法和属性。C
:是categoryColumns
类型的成员变量,用于处理与列相关的操作或数据。DB
:是gdb.DB
类型,用于数据库操作。Table
:是字符串类型,用于存储表名。
为什么要这样设计?
categoryColumns
作为独立成员:categoryColumns
被设计为一个独立的结构体,是为了更好地组织和管理与数据库表列相关的信息。通过将列名定义在categoryColumns
结构体中,可以更清晰地表达这些列的含义和用途。这样的设计使得在处理数据库操作时,可以方便地引用和操作特定的列,提高代码的可读性和可维护性。gmvc.M
的嵌入:gmvc.M
被嵌入到CategoryDao
结构体中,可能是为了利用gmvc.M
所提供的功能和特性。gmvc.M
可能是一个包含了数据库模型相关操作的结构体,通过嵌入它,CategoryDao
可以直接访问和使用这些操作,而无需重复定义和实现类似的功能。这种设计可以减少代码冗余,提高代码的复用性。分离关注点:将
categoryColumns
和gmvc.M
分别设计为独立成员和嵌入成员,可以将不同的关注点分离出来。categoryColumns
专注于列的定义和管理,而gmvc.M
专注于数据库模型的操作。这样的分离使得结构体的职责更加明确,便于代码的理解和维护。灵活性和可扩展性:这种设计方式提供了一定的灵活性和可扩展性。如果需要添加新的列或修改列的定义,可以在
categoryColumns
结构体中进行更改,而不会影响到gmvc.M
的功能。同样,如果需要扩展数据库模型的操作,可以在gmvc.M
中添加新的方法或修改现有方法,而不需要修改categoryColumns
结构体。
总的来说,这样的设计是为了在结构体中合理地组织和管理不同的功能和数据,提高代码的可读性、可维护性和可扩展性。
在 Go 语言中,对于嵌入的结构体(Embedded Struct),可以直接访问其字段和方法,就好像它们是外部结构体自身的一部分。
对于非嵌入的独立成员结构体(Independent Member Struct),也是既可以访问其字段,也可以调用其方法(如果有的话)。
在结构体中,嵌入的结构体通常被称为“Embedded Struct”,非嵌入的独立成员结构体通常就称为“Struct Member”或“Independent Struct Member”。
咱们接着往下面看
func NewCategoryDao() *CategoryDao {
columns := categoryColumns{
Id: "id",
ContentType: "content_type",
Key: "key",
ParentId: "parent_id",
UserId: "user_id",
Name: "name",
Sort: "sort",
Thumb: "thumb",
Brief: "brief",
Content: "content",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
}
return &CategoryDao{
C: columns,
M: g.DB("default").Model("gf_category").Safe(),
DB: g.DB("default"),
Table: "gf_category",
}
}
定义了一个名为
NewCategoryDao
的函数,用于创建并返回一个
CategoryDao
类型的指针。
在函数内部:
- 首先初始化了一个
categoryColumns
类型的变量columns
,并为其各个字段赋予了具体的值。 - 然后通过返回一个
&CategoryDao
类型的指针来创建一个新的CategoryDao
实例。在这个实例中: -C
字段被赋值为前面初始化的columns
变量。-M
字段通过调用一些数据库相关的操作进行初始化。-DB
字段通过g.DB("default")
进行初始化。-Table
字段被赋值为"gf_category"
。
这个函数的目的是提供一种创建和初始化
CategoryDao
结构体实例的方式。
其中
func NewCategoryDao() *CategoryDao
定义了一个名为
NewCategoryDao
的函数,该函数返回一个指向
CategoryDao
结构体的指针。
这种函数通常被称为构造函数,用于创建并初始化
CategoryDao
类型的对象
然后创建了一个
categoryColumns
类型的变量
columns
,并为其各个字段进行了初始化赋值。每个字段都被赋予了一个对应的字符串值,这些字符串值很可能是与数据库表中的列名相对应的。
return &CategoryDao{
C: columns,
M: g.DB("default").Model("gf_category").Safe(),
DB: g.DB("default"),
Table: "gf_category",
}
通过创建并返回一个
CategoryDao
结构体的指针来实现
NewCategoryDao
函数的功能。
在返回的
CategoryDao
结构体中:
C
字段被赋值为前面创建的columns
变量。M
字段通过对g.DB("default")
进行一系列操作(Model
和Safe
)来初始化。DB
字段被设置为g.DB("default")
。Table
字段被赋值为字符串"gf_category"
。
这样,调用
NewCategoryDao
函数就可以得到一个经过初始化的
CategoryDao
结构体实例的指针,以便后续进行相关的数据库操作和数据处理。
问题来了:你看这个方法的返回值,用了指针对吧,为什么要用指针?然后你看return的是一个&,这是为什么?它返回的是什么东西?
在 Go 语言中,**返回值带
*
号表示返回的是一个指针。而使用
&
取地址符是为了获取某个对象的地址,并返回该地址的指针。**
在上述代码中,
NewCategoryDao
函数返回的是
*CategoryDao
类型的指针。这样做的目的通常是为了更高效地操作对象,避免不必要的对象复制,并且可以通过指针来修改对象的属性。
具体来说,**
return &CategoryDao{...}
创建了一个
CategoryDao
类型的变量,并使用
&
获取其地址,然后返回这个地址的指针。接收方可以通过这个指针来访问和操作
CategoryDao
对象的成员**。
在这个例子中,**
CategoryDao
结构体可能包含了一些与数据库操作相关的信息,例如数据库连接、表名、列信息等。通过返回指针,调用方可以使用这个
CategoryDao
对象来执行具体的数据库操作,例如查询、插入、更新等。**
使用指针的好处包括:
- 减少内存复制:当返回一个结构体对象时,如果结构体较大,直接复制整个对象可能会消耗较多的内存和时间。通过返回指针,只需要复制指针的值(通常是一个较小的内存地址),而不是整个对象。(大家可以研究一下值传递和引用传递)
- 方便修改对象:通过指针可以直接修改指针所指向的对象的属性,而不需要返回整个对象并进行修改后再重新赋值。
- 共享对象:多个部分可以通过同一个指针来访问和操作同一个对象,方便在不同的地方进行数据共享和交互。
总的来说,返回指针可以提供更高效、灵活的方式来处理和共享复杂的数据结构,特别是在涉及到大型对象或需要在多个地方修改对象状态的情况下。但在使用指针时,需要注意内存管理,避免出现悬空指针或内存泄漏等问题。在 Go 语言中,通常不需要手动释放内存,因为它有自动的垃圾回收机制,但仍然需要正确地使用指针以确保程序的正确性和稳定性。
版权归原作者 阿贾克斯的黎明 所有, 如有侵权,请联系我们删除。