在 Go 项目开发中,项目的架构设计和代码实现需要不断地优化和改进。miniblog 在项目演进的过程中,其架构和代码实现也在持续向更优的方向演变。
我在腾讯和字节跳动期间,主要负责 Kubernetes 平台的设计与开发,对 Kubernetes 源码有较为深入的研究。在学习 Kubernetes 源码的过程中,我发现 Kubernetes 可以通过插件化的方式定义新的 REST 资源,而无需额外实现任何代码,即可直接完成 REST 资源的增删改查操作。进一步研究 Kubernetes 存储层的实现后,我发现,只需在存储层嵌入一个标准的存储实现,便可赋予 REST 资源完整的存储能力,这种设计类似于面向对象编程中的继承。
由此,我开始思考,是否可以将 Kubernetes 这种优秀的设计理念应用到业务开发中,以提升开发效率并增强代码的扩展能力。经过深入思考和多次编码尝试后,miniblog 项目成功实现了类似 Kubernetes 的设计,从而为业务开发带来了更高效、更灵活的代码实现。、
此外,miniblog 项目中 pkg/目录下的包可供第三方项目导入使用。在一个企业中,通常会有多个 Go 项目,这些项目也会提供对外使用的共享包。这种情况下,不同项目可能会针对相同功能分别提供独立的共享包,而这些包实际上是可以复用的。为提高代码的复用率并避免重复造轮子,miniblog 将共享包集中到一个应用基座项目中实现,或从该项目中导入共享包。miniblog 项目所使用的应用基座是 OneX 项目,其地址为:https://github.com/onexstack/onex。
应用基座是 OneX 技术体系中的核心概念,旨在提供通用的共享能力,主要包括以下三类:
- 基础服务:提供基础的服务;如认证授权服务、分布式缓存服务、资源限流服务等;
- 应用框架:提供共享的应用开发框架,这些框架可以提高应用的开发效率。例如异步任务处理框架、声明式框架等;
- 共享包:提供可复用的通用组件和工具。例如分布式锁、错误处理包、日志包等;
OneX 技术体系中的所有项目都复用应用基座中的共享组件,从而大幅提升整个技术体系所有项目的开发效率和代码复用率。
升级架构设计
结合上述思考,我升级了 miniblog 的软件架构,如图 13-1 所示。
图 13-1 软件架构升级
在图 13-1 所示的新架构中,Handler 层、Biz 层和 Store 层与本课程第 7 讲设计的简洁架构保持一致。不同之处在于,Store 层新增了一个子层:标准 Store 层。标准 Store 层之上的各个 REST 资源的数据存储操作均继承标准 Store 层的接口实现,无需重新实现。
新架构实现
在升级架构之前,Store 层 User 资源和 Post 资源的接口定义如下:
// UserStore 定义了 user 模块在 store 层所实现的方法.
type UserStore interface {
Create(ctx context.Context, obj *model.UserM) error
Update(ctx context.Context, obj *model.UserM) error
Delete(ctx context.Context, opts *where.Options) error
Get(ctx context.Context, opts *where.Options) (*model.UserM, error)
List(ctx context.Context, opts *where.Options) (int64, []*model.UserM, error)
UserExpansion
}
// UserExpansion 定义了用户操作的附加方法.
type UserExpansion interface{}
// PostStore 定义了 post 模块在 store 层所实现的方法.
type PostStore interface {
Create(ctx context.Context, obj *model.PostM) error
Update(ctx context.Context, obj *model.PostM) error
Delete(ctx context.Context, opts *where.Options) error
Get(ctx context.Context, opts *where.Options) (*model.PostM, error)
List(ctx context.Context, opts *where.Options) (int64, []*model.PostM, error)
PostExpansion
}
// PostExpansion 定义了帖子操作的附加方法.
type PostExpansion interface{}UserStore 和 PostStore 接口除了返回的 GORM Model 结构体不一致之外,其他方法的参数定义和方法实现基本都是一样的。因此,可以考虑通过 Go 泛型,来标准化 Store 层,提升代码的可复用性和灵活性。
为了保留旧有实现示例,供以后学习、参考使用。新增加一个 ConcretePost 类型的 Store 层资源(Concrete 意为确切的,与抽象相对,形容直接使用 *gorm.DB 类型的实例同数据库交互),并实现 ConcretePost 资源在 Store 层的增删改查方法。实现方法同结构升级前 User 资源和 Post 资源的实现方法保持一致。
标准 Store 层代码实现
标准 Store 层代码位于 github.com/onexstack/onexstack/pkg/store 包中,实现代码如代码清单 13-1 所示。
代码清单 13-1 Store 层标准化实现
// DBProvider defines an interface for providing a database connection.
type DBProvider interface {
// DB returns the database instance for the given context.
DB(ctx context.Context, wheres ...where.Where) *gorm.DB
}
// Option defines a function type for configuring the Store.
type Option[T any] func(*Store[T])
// Store represents a generic data store with logging capabilities.
type Store[T any] struct {
logger Logger
storage DBProvider
}
// WithLogger returns an Option function that sets the provided Logger to the Store for logging purposes.
func WithLogger[T any](logger Logger) Option[T] {
return func(s *Store[T]) {
s.logger = logger
}
}
// NewStore creates a new instance of Store with the provided DBProvider.
func NewStore[T any](storage DBProvider, logger Logger) *Store[T] {
if logger == nil {
logger = empty.NewLogger()
}
return &Store[T]{
logger: logger,
storage: storage,
}
}
// db retrieves the database instance and applies the provided where conditions.
func (s *Store[T]) db(ctx context.Context, wheres ...where.Where) *gorm.DB {
dbInstance := s.storage.DB(ctx)
for _, whr := range wheres {
if whr != nil {
dbInstance = whr.Where(dbInstance)
}
}
return dbInstance
}
// Create inserts a new object into the database.
func (s *Store[T]) Create(ctx context.Context, obj *T) error {
if err := s.db(ctx).Create(obj).Error; err != nil {
s.logger.Error(ctx, err, "Failed to insert object into database", "object", obj)
return err
}
return nil
}
...
// List retrieves a list of objects from the database based on the provided where options.
func (s *Store[T]) List(ctx context.Context, opts *where.Options) (count int64, ret []*T, err error) {
err = s.db(ctx, opts).Order("id desc").Find(&ret).Offset(-1).Limit(-1).Count(&count).Error
if err != nil {
s.logger.Error(ctx, err, "Failed to list objects from database", "conditions", opts)
}
return
}上述代码实现了一个通用的数据存储库(Store),通过泛型支持和接口解耦,对数据库操作进行了封装和抽象。该 Store 层以统一的方式处理 GORM 模型的创建、更新、删除、查询以及条件过滤,提供了标准化的数据库操作接口。
实现的核心是一个泛型的 Store[T] 结构体,其中 T 代表任意的 GORM 模型类型。Store 依赖两个主要接口:存储层的 DBProvider 接口和日志工具的 Logger 接口。通过 DBProvider,Store 可以动态获取数据库连接,并支持通过 where 条件封装实现灵活的查询操作。而日志功能通过可选的 Logger 接口实现,进一步解耦了日志逻辑的具体实现。如果未传递自定义日志器,系统会默认使用一个空日志实现(empty.NewLogger),以确保基本功能的独立性和无依赖性。
此外,Store 使用了函数选项(Option[T])模式,使用户能够灵活配置 Store 的行为,例如绑定自定义日志。这种设计显著提升了模块的可用性和扩展性。在 Go 项目开发中,函数选项模式是一种常见的设计模式,能够灵活调整共享组件的参数配置。通过这种方式,既满足了调用方的差异化需求,又实现了功能的高度共享和复用,同时保持代码的简洁性和可维护性。
miniblog Store 层标准化改造
在实现了标准 Store 层代码之后,便可以在 User 资源和 Post 资源的 Store 层结构体中内嵌上述标准 Store 类型,从而复用其方法。修改 internal/apiserver/store/user.go 文件,代码实现如下:
// userStore 是 UserStore 接口的实现.
type userStore struct {
*genericstore.Store[model.UserM]
}
...
// newUserStore 创建 userStore 的实例.
func newUserStore(store *datastore) *userStore {
return &userStore{
Store: genericstore.NewStore[model.UserM](store, NewLogger()),
}
}userStore 通过嵌套标准存储层 *genericstore.Store[model.UserM],复用了标准存储层的 CURD 方法,从而实现了 UserStore 接口的所有功能,并减少了代码重复。
同样的,internal/apiserver/store/post.go 代码实现如下:
// postStore 是 PostStore 接口的实现.
type postStore struct {
*genericstore.Store[model.PostM]
}
...
// newPostStore 创建 postStore 的实例.
func newPostStore(store *datastore) *postStore {
return &postStore{
Store: genericstore.NewStore[model.PostM](store, NewLogger()),
}
}共享包迁移
将 miniblog 项目中的以下包,分别在 onexstack 项目中实现(已有则复用,否则新建):
github.com/onexstack/miniblog/pkg/token => github.com/onexstack/onexstack/pkg/token
github.com/onexstack/miniblog/pkg/version => github.com/onexstack/onexstack/pkg/version
github.com/onexstack/miniblog/pkg/auth => github.com/onexstack/onexstack/pkg/authn # 认证
=> github.com/onexstack/onexstack/pkg/authz # 授权之后将对应的包替换为 onexstack 项目中的包即可。
实现内存数据库
为了简化项目部署,让读者以最快、最便捷的方式部署好 miniblog 项目。miniblog 项目,实现了一个可选的内存数据库。当开启内存数据库模式时,数据会保存在内存中,而非 MariaDB 中。以此解除部署时对 MariaDB 的依赖。
修改 cmd/mb-apiserver/app/options/options.go 文件,在 ServerOptions 中新增 EnableMemoryStore 初始化配置项。
修改 internal/apiserver/server.go 文件,改造 NewDB() 方法,使其可以根据是否启用内存数据库选择不同的*gorm.DB 实例创建逻辑。NewDB() 实现代码如代码清单 13-2 所示。
代码清单 13-2 内存数据库实现
// NewDB 创建一个 *gorm.DB 实例.
func (cfg *Config) NewDB() (*gorm.DB, error) {
if !cfg.EnableMemoryStore {
log.Infow("Initializing database connection", "type", "mysql", "addr", cfg.MySQLOptions.Addr)
return cfg.MySQLOptions.NewDB()
}
log.Infow("Initializing database connection", "type", "memory", "engine", "SQLite")
// 使用SQLite内存模式配置数据库
// ?cache=shared 用于设置 SQLite 的缓存模式为 共享缓存模式 (shared)。
// 默认情况下,SQLite 的每个数据库连接拥有自己的独立缓存,这种模式称为 专用缓存 (private)。
// 使用 共享缓存模式 (shared) 后,不同连接可以共享同一个内存中的数据库和缓存。
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
if err != nil {
log.Errorw("Failed to create database connection", "err", err)
return nil, err
}
// 自动迁移数据库结构
if err := db.AutoMigrate(&model.UserM{}, &model.PostM{}, &model.CasbinRuleM{}); err != nil {
log.Errorw("Failed to migrate database schema", "err", err)
return nil, err
}
// 注意:这里仅仅为了实现快速部署,降低学习难度。
// 在真实企业开发中,不能再代码中硬编码这些初始化配置,
// 尤其是硬编码密码、密钥之类的信息.
// 插入 casbin_rule 表记录
adminR, userR := "role::admin", "role::user"
casbinRules := []model.CasbinRuleM{
{PType: ptr.To("g"), V0: ptr.To("user-000000"), V1: &adminR},
{PType: ptr.To("p"), V0: &adminR, V1: ptr.To("*"), V2: ptr.To("*"), V3: ptr.To("allow")},
{PType: ptr.To("p"), V0: &userR, V1: ptr.To("/v1.MiniBlog/DeleteUser"), V2: ptr.To("CALL"), V3: ptr.To("deny")},
{PType: ptr.To("p"), V0: &userR, V1: ptr.To("/v1.MiniBlog/ListUser"), V2: ptr.To("CALL"), V3: ptr.To("deny")},
{PType: ptr.To("p"), V0: &userR, V1: ptr.To("/v1/users"), V2: ptr.To("GET"), V3: ptr.To("deny")},
{PType: ptr.To("p"), V0: &userR, V1: ptr.To("/v1/users/*"), V2: ptr.To("DELETE"), V3: ptr.To("deny")},
}
if err := db.Create(&casbinRules).Error; err != nil {
log.Fatalw("Failed to insert casbin_rule records", "err", err)
return nil, err
}
// 插入默认用户(root用户)
user := model.UserM{
UserID: "user-000000",
Username: "root",
Password: "miniblog1234",
Nickname: "administrator",
Email: "colin404@foxmail.com",
Phone: "18110000000",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := db.Create(&user).Error; err != nil {
log.Fatalw("Failed to insert default root user", "err", err)
return nil, err
}
return db, nil
}代码清单 13-2,实现了一个 NewDB 方法,用于创建并初始化一个 *gorm.DB 实例,支持两种数据库模式:MySQL 和 SQLite 内存模式。通过配置项 EnableMemoryStore 来决定使用哪种数据库模式。如果 EnableMemoryStore 为 false,则使用 MySQL 数据库,调用 cfg.MySQLOptions.NewDB() 方法初始化 MySQL 连接。如果 EnableMemoryStore 为 true,则使用 SQLite 的内存模式,配置连接字符串为 file::memory:?cache=shared。这种模式允许多个数据库连接共享同一个内存数据库和缓存,非常适合快速部署和测试。
在 SQLite 模式下,代码通过 GORM 的 AutoMigrate 方法自动迁移了用户表 (UserM)、博客表 (PostM) 和 Casbin 权限规则表 (CasbinRuleM)。同时,插入了初始权限规则,用于实现基于角色的访问控制(RBAC)。例如,用户 user-000000 被绑定到管理员角色 role::admin,拥有对所有资源的访问权限;普通用户角色 role::user 则被限制访问某些接口或操作。
此外,代码还在用户表中插入了一条默认管理员账户记录,包含用户 ID、用户名、密码及其他基本信息。这种硬编码方式简化了开发和测试,但在生产环境中应避免,尤其是涉及敏感信息时,应通过配置文件或环境变量加载。
Makefile 项目管理进阶
在完成应用框架构建、功能开发以及静态代码检查等基础开发工作后,我们需要重新梳理如何高效管理一个相对稳定的项目。之所以选择在这一阶段讨论项目管理,是因为过早规划可能导致考虑不周,从而面临后期重构的风险;而过晚规划则可能无法尽早享受高效项目管理所带来的开发效率提升。
为什么要通过 Makefile 管理项目
优秀的开发者在开发一个 Go 项目时,会提前考虑应用的扩展性,在开发之初,甚至功能都是按着大规模应用的标准去设计的。所以,即使 miniblog 是一个小而精的项目,但在开发时,仍然要假想未来 miniblog 随着时间的推移,功能的迭代,最后变得异常庞大的场景。
对于一个庞大的应用,可能有众多开发者,每天都在提交代码,每天都在重复执行:静态代检查、代码编译、镜像构建、单元测试、Protobuf 文件编译等操作。试想如果没有一个统一、高效的项目管理,你很可能会面临以下 2 个核心难题:
- 执行结果不一致带来一些潜在的问题和沟通成本:这么多开发者,相同的操作每个人执行的命令和结果都是不一样的。不一致的结果,可能会程序运行结果不一致,并带来一些沟通成本;
- 执行效率低下:例如编译一个 Protobuf 文件需要键入的命令长度就可能超过 200 个字符。如果没有一个高效的编译方法,每次手动输入这么多命令字符,整个操作是非常低效的,而且还可能因为命令执行错误,导致运行失败。
上面 2 个难题最终导致的结果就是:项目管理效率低,或者说开发效率低。
所以为了提高开发效率,在开发 Go 项目时,需要有一个统一、高效的项目管理手段。当前,对于一个 Go 项目,有多种项目管理手段,但最普适的方法是使用 Makefile 来管理项目。
如何通过 Makefile 管理项目?
在 Go 项目开发中,开发者可能会编写各种质量、各种结构的 Makefile 文件。一个简单的 Makefile 如下:
# 变量定义
APP_NAME = myapp
SRC = ./...
OUTPUT_DIR = ./bin
BUILD_FILE = $(OUTPUT_DIR)/$(APP_NAME)
# 默认目标
all: build
# 构建二进制文件
build:
@echo "Building the application..."
@mkdir -p $(OUTPUT_DIR)
go build -o $(BUILD_FILE) ./main.go
# 运行应用程序
run:
@echo "Running the application..."
@$(BUILD_FILE)
# 运行测试
test:
@echo "Running tests..."
go test $(SRC)
# 格式化代码
fmt:
@echo "Formatting code..."
go fmt $(SRC)
# 清理生成的文件
clean:
@echo "Cleaning build files..."
@rm -rf $(OUTPUT_DIR)
# 伪目标(防止文件与目标名称冲突)
.PHONY: all build run test fmt clean对于一个小型项目而言,上述 Makefile 基本能够满足需求。然而,对于一个大型项目来说,上述 Makefile 并不适用。大型项目在开发过程中通常涉及功能繁多、条件复杂,且命令较为冗长。如果直接将这些功能简单拼凑到一个 Makefile 文件中,虽然能够一定程度上提升开发效率,但效果非常有限。一个冗长、难以阅读且操作复杂的 Makefile 同样会对项目管理造成阻碍。
如果想管理大型项目,还需要升级 Makefile 的实现,通过结构化、灵活、可扩展的方式来编写 Makefile。
结构化 Makefile 设计方式
结构化设计 Makefile 文件的方式有很多种,一个经典的结构化 Makefile 设计示例如下:
├── Makefile
├── scripts/
│ ├── make-rules/
│ │ ├── all.mk
│ │ ├── common.mk
│ │ ├── generate.mk
│ │ ├── golang.mk
│ │ ├── swagger.mk
│ │ └── tools.mk上述 Makefile 结构也是 miniblog 项目采用的结构。项目根目录下的 Makefile 主要用来聚合 Makefile 核心规则,具体的规则实现存放于 scripts/make-rules/ 目录下。all.mk 也是一个 Makefile 文件,该文件导入了 scripts/make-rules/ 目录下的其他 Makefile 文件。
项目根目录下的 Makefile 文件通过导入 scripts/make-rules/all.mk,间接导入 scripts/make-rules/ 目录下的 Makefile 文件,例如:
include scripts/make-rules/common.mk
include scripts/make-rules/tools.mk # include at second order
include scripts/make-rules/golang.mk
include scripts/make-rules/generate.mk
include scripts/make-rules/swagger.mk这种设计方式能够确保根目录下的 Makefile 文件结构清晰、易于阅读和维护。同时,由于功能按类别拆分到 scripts/make-rules/ 目录中的多个 Makefile 文件,也能保证每个文件代码简洁、功能聚焦,从而提升代码的可读性和可维护性。
对于较复杂的功能实现,可以通过编写 Shell 脚本来完成。这些 Shell 脚本可以放置于 scripts/ 目录中,并在 Makefile 中进行引用。相比直接在 Makefile 中实现功能,使用 Shell 脚本不仅提高了开发效率、降低了实现难度,还能够有效减少 Makefile 文件的代码量。
因此,一个合理的 Makefile 结构可能如图 13-2 所示。
图 13-2 结构化 Makefile
结构化 Makefile 开发实战
根据上述结构化 Makefile 设计思路,miniblog 也对当前的 Makefile 机制和规则进行了升级。升级后的 Makefile 更加易读、易用,并且具有很高的灵活性和扩展性。
在此假设读者已掌握 Makefile 的基本语法和高级语法。为了实现结构化的 Makefile,首先需要 Makefile 规则,按功能类别进行分类。目前,Makefile 的规则全部集中于一个 Makefile 文件中,且缺乏类似于 make help 的命令,这使得 Makefile 文件的阅读和使用较为困难。
通过以下命令可以获取当前 Makefile 集成的管理功能:
$ grep .PHONY Makefile
.PHONY: all
.PHONY: build
.PHONY: format
.PHONY: add-copyright
.PHONY: tidy
.PHONY: clean
.PHONY: ca
.PHONY: test
.PHONY: cover
.PHONY: lint根据当前 Makefile 中集成的规则,可以将这些规则划分为以下类别,如表 13-1 所示。
表 13-1 Makefile 规则分类
为了提高 Makefile 代码的可维护性,应将同类功能集中在一个 Makefile 中实现,并将这些文件存放在 scripts/make-rules/ 目录下。例如,与 go 命令相关的功能(如 build、lint、tidy、test、cover 和 format)可以统一配置在 scripts/make-rules/golang.mk 中入。
此外,为了简化 Makefile 的实现,可以将部分复杂的管理逻辑通过 Shell 脚本实现,并存放在 scripts/ 目录中供 Makefile 调用。
为进一步增强 Makefile 的灵活性与扩展性,可以充分利用 Makefile 的高级语法。
结构化后的 Makefile 文件位于 feature/s35 分支中,运行 make help 输出帮助信息如下:
$ make help
Usage:
make <TARGETS> <OPTIONS>
Targets:
build:
build 编译源码,依赖 tidy 目标自动添加/移除依赖包.
test:
test 执行单元测试.
cover 执行单元测试,并校验覆盖率阈值.
...
选项:
BINS 要构建的二进制文件。默认值为cmd中的所有文件。
此选项可用于以下命令:make build
示例:make build BINS="miniblog"
VERSION 编译到二进制文件中的版本信息。
V 设置为1以启用详细的构建信息输出。默认值为0。从 make help 的输出中可以看出,新版 Makefile 通过 make help 命令能够清晰地展示各功能目标及其用途。此外,重构后的 Makefile 在多个地方实现了灵活且可扩展的功能,如 help 目标的实现方式:
help: Makefile ## 打印 Makefile help 信息.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<TARGETS> <OPTIONS>\033[0m\n\n\033[35mTargets:\033[0m\n"} /^[0-9A-Za-z._-]+:.*?##/ { printf " \033[36m%-45s\033[0m %s\n", $$1, $$2 } /^\$$\([0-9A-Za-z_-]+\):.*?##/ { gsub("_","-", $$1); printf " \033[36m%-45s\033[0m %s\n", tolower(substr($$1, 3, length($$1)-7)), $$2 } /^##@/{ printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' Makefile #$(MAKEFILE_LIST)
@echo -e "$$USAGE_OPTIONS"上述 awk 命令会解析 Makefile 文件,根据 ## 进行解析,以展示需要在 make help 中输出的目标说明及分类。如果在 Makefile 中新增目标,无需修改 help 目标的实现即可在 make help 输出中自动反映新增目标。
如何新增一个新资源
因为整个 miniblog 项目非常规范,所以可以快速添加一个新的 REST 资源。新增 REST 资源时,需要先给 REST 资源起以下几个名字:
- 类型:资源的类型名称,例如 Post,使用大写驼峰格式;
- 单数:资源的单数形式,例如 post,使用小写驼峰格式,首字母小写;
- 复数:资源的复数形式,例如 posts,使用小写驼峰格式,首字母小写。
这里假设需要新增一个 Comment 资源,用来记录博客的评论,并将这些记录保存在数据库中。可以按以下顺序来实现 Comment 资源相关的功能代码:
- 定义 API 接口;
- 编译 Protobuf 文件;
- 在数据库中创建 comment 表,并修改 cmd/gen-gorm-model/gen_gorm_model.go 文件,添加 comment 表的 GORM Model 生成代码;
- 运行 go run cmd/gen-gorm-model/gen_gorm_model.go 命令,生成 GORM Model;
- 完善 API 接口请求参数的默认值设置方法(修改 pkg/api/apiserver/v1/comment.pb.defaults.go 文件);
- 实现 API 接口的请求参数校验方法(在文件 internal/apiserver/pkg/validation/comment.go 中实现);
- 实现 Comment 资源的 Store 层代码(在文件 internal/apiserver/store/comment.go 中实现);
- 实现 Comment 资源的 Model 和 Proto 的转换函数(在 internal/apiserver/pkg/conversion/comment.go 文件中实现);
- 实现 Comment 资源的 Biz 层代码(在文件 internal/apiserver/biz/v1/comment/comment.go 中实现);
- 实现 Comment 资源的 Handler 层代码(在文件 internal/apiserver/handler/comment.go 中实现)。
在具体实现时,可参考已有资源的实现。例如复制已有资源的实现文件,通过替换字符串等方式,快速实现 Comment 资源的代码。
当然,更简洁推荐的方法是使用 OneX 技术栈中的项目开发脚手架,来自动添加新的资源实现:osctl。
小结(AI 自动生成并人工审核)
本文详细探讨了 Go 项目开发中如何通过优化架构设计和代码实现来提升开发效率和扩展能力。
以 miniblog 项目为例,文章展示了如何借鉴 Kubernetes 的设计理念,通过标准化 Store 层引入泛型支持,实现资源增删改查功能的复用,减少重复代码。
此外,miniblog 通过迁移共享包至 OneX 应用基座,进一步提升了代码的复用率和项目间的协作效率。文章还介绍了内存数据库的实现,简化项目部署流程,并通过结构化的 Makefile 提升项目管理效率,解决了大型项目中执行结果不一致和效率低下的问题。
最后,文章阐述了如何快速新增 REST 资源,结合 OneX 技术栈的脚手架工具,进一步简化了资源开发流程,体现了规范化设计和工具化支持在现代项目开发中的重要性。