Skip to content
8treenetPublic

About

用 Go 写 DDD 服务的框架:六边形架构管边界,实体和聚合管业务行为,基础设施交给框架。

Topics

Resources

Stars

2.3k stars

Watchers

31 watching

Forks

Latest commit

 

History

309 Commits

Folders and files

Repository files navigation

Freedom

Freedom 是一个用 Go 写 DDD 服务的框架。controller 只管收发,业务逻辑只认持久化接口,事务不用改函数签名。
请求生命周期、依赖注入、缓存、领域事件、CRUD 生成,这些框架先做掉。

License Go Reference Go Version Ask DeepWiki GitHub release Stars

骨架

生成项目后大概长这样:

myapp/
├── adapter/
│   ├── controller/     # 入站:HTTP
│   └── repository/     # 出站:持久化 / 上游接口
├── domain/
│   ├── aggregate/      # 聚合:CQS 的命令与查询
│   ├── entity/         # 实体:业务行为 + 领域事件
│   ├── event/          # 领域事件
│   ├── po/             # 持久化对象
│   └── vo/             # 值对象
├── infra/              # 基础设施
└── config/             # 配置
  • domain:实体、聚合、领域事件,业务规则都在这里。
  • adapter:入站接 HTTP 和消息,出站做持久化和上游接口调用。
  • infra:事务、缓存、HTTP 客户端这类东西,用的时候注入。

分层本身不新鲜。六边形架构、DDD、依赖倒置,大家讲了很多年。Freedom 把这些约定写进框架:注册、注入、生命周期,不用每个项目重新约定一遍。

请求进来,先有一个 Worker

每个 HTTP 请求对应一个 Worker。日志、链路信息、事务句柄、请求级缓存、标准 context 都挂在它上面。

Service 和 Infra 从对象池里取,Repository、Factory 每次请求新建。对象可以复用,状态不共享。不用自己清缓存,也不用担心两个请求共用一个实例。

在请求里开 goroutine,记得先调 Worker.DeferRecycle()。否则请求结束对象被回收,goroutine 里再用就晚了。

依赖注入:少写点 new

type UserController struct {
    UserSev *domain.UserService
}

func (c *UserController) GetBy(id int) freedom.Result {
    user, err := c.UserSev.Get(id)
    return &infra.JSONResponse{Object: user, Error: err}
}

Controller 里没有 new,也不用在 main 里手写一串对象再一个个塞进去。字段写上,框架按类型注入;UserService 里的 Repository 也一样。

GetBy(id int) 对应 GET /user/{id},参数也不用自己拆。

事务只多一层 Execute

比如转账:

return cmd.tx.Execute(func() error {
    cmd.fromAccount.Withdraw(cmd.amount)
    cmd.toAccount.Deposit(cmd.amount)

    if err := cmd.accountRepo.Save(cmd.fromAccount); err != nil {
        return err
    }
    return cmd.accountRepo.Save(cmd.toAccount)
})

Repository 还是那个 Repository,但拿到的 DB 已经换成事务里的。业务方法不用加 tx 参数,也不用把调用链一路改到底。上面两个 Save 要么都成功,要么都回滚。

事件从实体里发出来

func (u *User) ChangePassword(newPassword, oldPassword string) error {
    if u.Password != oldPassword {
        return errors.New("password error")
    }
    u.SetPassword(newPassword)

    u.AddPubEvent(&event.ChangePassword{
        UserID:      u.ID,
        NewPassword: newPassword,
        OldPassword: oldPassword,
    })
    return nil
}

实体只负责记录事件。什么时候投递、投给谁,由基础设施决定。直接在这里发 Kafka 也不是不行,但事务一滚,消息已经发出去了,数据就对不上。接到事务后面,提交了才发。

表结构直接生成代码

# MySQL
freedom new-po --dsn "root:密码@tcp(127.0.0.1:3306)/mydb?charset=utf8"

# PostgreSQL
freedom new-po --driver postgres -d "host=127.0.0.1 port=5432 user=root password=123456 dbname=mydb sslmode=disable"

# 或者给一份 JSON Schema
freedom new-po --json ./domain/po/schema.json

生成的 PO 带 setter,改过哪些字段它自己记着。保存时只 update 这几个字段,不会整行覆盖。手写这些代码不算难,就是烦。

快速开始

# 1. 安装 CLI
go install github.com/8treenet/freedom/freedom@latest

# 2. 生成项目
freedom new-project myapp
cd myapp
go mod tidy

# 3. 跑起来
go run main.go

打开 http://127.0.0.1:8000,或者 curl 一下 /ping。

目前支持

能力 内容
架构 六边形架构、DDD、CQS、聚合、实体、值对象、领域事件
运行时 请求级 Worker、依赖注入、对象池、服务定位器
HTTP Iris MVC、HTTP/2、H2C、AutoTLS、中间件
数据 GORM、MySQL、PostgreSQL、Redis、两级实体缓存
消息 Kafka 生产者/消费者、领域事件投递
治理 Prometheus(HTTP / ORM / Client / Kafka)、Trace、请求日志、Recover
工程 项目脚手架、PO/CRUD 生成、单元测试工具、优雅停机
配置 TOML、YAML、环境变量指定配置目录

文档

文档 你会看到什么
路由指南 路由、参数、控制器
服务指南 服务层如何编排业务
PO 指南 持久化对象与数据库操作
DDD 指南 实体、聚合、工厂、CQS
Worker 指南 请求运行时与对象生命周期
HTTP 客户端指南 客户端、链路透传、超时控制

示例

不想翻文档的话,直接跑例子:

示例 适合看什么
base 最小闭环,先跑通
http2 H2C 服务间调用、依赖倒置
infra-example 自定义 Infra、事务、Kafka、领域事件
fshop 电商全链路:购物车、订单、库存、事件

适合谁

如果你在做:

  • 业务不只是 CRUD 的系统
  • 要长期维护、多人协作的 Go 服务
  • 想把领域模型从数据表里拆出来的团队

Freedom 会比较合适。

如果你只是想尽快把接口发出去,Gin、Echo、Iris 都比 Freedom 省事。DDD 有成本,Freedom 不打算把这件事说得很轻松。

最后

Freedom 不保证架构不出问题。它能做的是先把请求、事务、缓存、注入这些事接好,让你打开 domain 时,里面只有业务。

Issue、PR、文档修正都欢迎。用着顺手的话,点个 Star。

Apache 2.0 · Issues · Releases

About

用 Go 写 DDD 服务的框架:六边形架构管边界,实体和聚合管业务行为,基础设施交给框架。

Topics

Resources

Stars

2.3k stars

Watchers

31 watching

Forks

Releases

Packages

Used by

Contributors

Languages