322 lines
9.4 KiB
Markdown
322 lines
9.4 KiB
Markdown
# Book 业务分区
|
||
|
||
## 定义Book业务逻辑
|
||
|
||
业务功能: CRUD
|
||
1. 创建书籍(录入)
|
||
2. Book列表查询
|
||
3. Book详情查询
|
||
4. Book更新
|
||
5. Book删除
|
||
|
||
通过Go语言的里面的接口 来定义描述业务功能
|
||
|
||
```go
|
||
// book.Service, Book的业务定义
|
||
type Service interface {
|
||
// 1. 创建书籍(录入)
|
||
CreateBook(context.Context, *CreateBookRequest) (*Book, error)
|
||
// 2. Book列表查询
|
||
QueryBook(context.Context, *QueryBookRequest) (*types.Set[*Book], error)
|
||
// 3. Book详情查询
|
||
DescribeBook(context.Context, *DescribeBookRequest) (*Book, error)
|
||
// 4. Book更新
|
||
UpdateBook(context.Context, *UpdateBookRequest) (*Book, error)
|
||
// 5. Book删除
|
||
DeleteBook(context.Context, *DeleteBookRequest) error
|
||
}
|
||
```
|
||
|
||
## 业务的具体实现(TDD: Test Drive Develop)
|
||
|
||
1. BookServiceImpl
|
||
|
||
```go
|
||
// CreateBook implements book.Service.
|
||
func (b *BookServiceImpl) CreateBook(context.Context, *book.CreateBookRequest) (*book.Book, error) {
|
||
panic("unimplemented")
|
||
}
|
||
|
||
// DeleteBook implements book.Service.
|
||
func (b *BookServiceImpl) DeleteBook(context.Context, *book.DeleteBookRequest) error {
|
||
panic("unimplemented")
|
||
}
|
||
|
||
// DescribeBook implements book.Service.
|
||
func (b *BookServiceImpl) DescribeBook(context.Context, *book.DescribeBookRequest) (*book.Book, error) {
|
||
panic("unimplemented")
|
||
}
|
||
|
||
// QueryBook implements book.Service.
|
||
func (b *BookServiceImpl) QueryBook(context.Context, *book.QueryBookRequest) (*types.Set[*book.Book], error) {
|
||
panic("unimplemented")
|
||
}
|
||
|
||
// UpdateBook implements book.Service.
|
||
func (b *BookServiceImpl) UpdateBook(context.Context, *book.UpdateBookRequest) (*book.Book, error) {
|
||
panic("unimplemented")
|
||
}
|
||
```
|
||
|
||
## 编写单元测试
|
||
|
||
```go
|
||
func TestCreateBook(t *testing.T) {
|
||
req := book.NewCreateBookRequest()
|
||
req.SetIsSale(true)
|
||
req.Title = "Go语言V4"
|
||
req.Author = "will"
|
||
req.Price = 10
|
||
ins, err := svc.CreateBook(ctx, req)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
t.Log(ins)
|
||
}
|
||
```
|
||
|
||
## 业务对象注册(ioc controller)
|
||
|
||
手动维护
|
||
```sh
|
||
pkg gloab
|
||
|
||
bookContrller = xxx
|
||
commentContrller = xx
|
||
...
|
||
```
|
||
|
||
通过容器来维护对象
|
||
|
||
```go
|
||
// Book业务的具体实现
|
||
type BookServiceImpl struct {
|
||
ioc.ObjectImpl
|
||
}
|
||
|
||
// 返回对象的名称, 因此我需要 服务名称
|
||
// 当前的MySQLBookServiceImpl 是 service book.APP_NAME 的 一个具体实现
|
||
// 当前的MongoDBBookServiceImpl 是 service book.APP_NAME 的 一个具体实现
|
||
func (s *BookServiceImpl) Name() string {
|
||
return book.APP_NAME
|
||
}
|
||
|
||
func init() {
|
||
ioc.Controller().Registry(&BookServiceImpl{})
|
||
}
|
||
```
|
||
|
||
## 面向接口
|
||
|
||
对象取处理, 断言他满足业务接口,然后我们以接口的方式来使用
|
||
|
||
```go
|
||
func GetService() Service {
|
||
return ioc.Controller().Get(APP_NAME).(Service)
|
||
}
|
||
|
||
const (
|
||
APP_NAME = "book"
|
||
)
|
||
```
|
||
|
||
第三方模块,可以依赖 接口进行开发
|
||
```go
|
||
// AddComment implements comment.Service.
|
||
func (c *CommentServiceImpl) AddComment(ctx context.Context, in *comment.AddCommentRequest) (*comment.Comment, error) {
|
||
// 能不能 直接Book Service的具体实现
|
||
// (&impl.BookServiceImpl{}).DescribeBook(ctx, nil)
|
||
// 依赖接口,面向接口编程, 不依赖具体实现
|
||
book.GetService().DescribeBook(ctx, nil)
|
||
panic("unimplemented")
|
||
}
|
||
```
|
||
|
||
## 开发API
|
||
|
||
接口是需求, 对业务进行设计, 可以选择把这些能力 以那种接口的访问对外提供服务
|
||
|
||
1. 不对外提供接口,仅仅作为其他的业务的依赖
|
||
2. (API)对外提供 HTTP接口, RESTful接口
|
||
3. (API)对内提供 RPC接口(JSON RPC/GRPC/thrift)
|
||
|
||
1. 开发业务功能
|
||
```go
|
||
func (h *BookApiHandler) createBook(ctx *gin.Context) {
|
||
req := book.NewCreateBookRequest()
|
||
if err := ctx.BindJSON(req); err != nil {
|
||
response.Failed(ctx, err)
|
||
return
|
||
}
|
||
|
||
ins, err := h.svc.CreateBook(ctx.Request.Context(), req)
|
||
if err != nil {
|
||
response.Failed(ctx, err)
|
||
return
|
||
}
|
||
|
||
// 返回响应
|
||
response.Success(ctx, ins)
|
||
}
|
||
```
|
||
|
||
2. 注册路由
|
||
```go
|
||
type BookApiHandler struct {
|
||
ioc.ObjectImpl
|
||
|
||
// 业务依赖
|
||
svc book.Service
|
||
}
|
||
|
||
func (h *BookApiHandler) Name() string {
|
||
return "books"
|
||
}
|
||
|
||
// 对象的初始化, 初始化对象的一些熟悉 &BookApiHandler{}
|
||
// 构造函数
|
||
// 当你这个对象初始化的时候,直接把的处理函数(ApiHandler注册给Server)
|
||
func (h *BookApiHandler) Init() error {
|
||
h.svc = book.GetService()
|
||
r := ioc_gin.ObjectRouter(h)
|
||
|
||
r.GET("", h.queryBook)
|
||
r.POST("", h.createBook)
|
||
return nil
|
||
}
|
||
|
||
func init() {
|
||
ioc.Api().Registry(&BookApiHandler{})
|
||
}
|
||
```
|
||
|
||
## 业务注册
|
||
|
||
每写完一个业务,就需要在 注册到ioc(注册表)
|
||
```go
|
||
// 业务加载区, 选择性的价值的业务处理对象
|
||
|
||
import (
|
||
// Api Impl
|
||
_ "122.51.31.227/go-course/go18/book/v4/apps/book/api"
|
||
|
||
// Service Impl
|
||
_ "122.51.31.227/go-course/go18/book/v4/apps/book/impl"
|
||
_ "122.51.31.227/go-course/go18/book/v4/apps/comment/impl"
|
||
)
|
||
```
|
||
|
||
|
||
## 启动服务
|
||
|
||
```go
|
||
import (
|
||
"github.com/infraboard/mcube/v2/ioc/server/cmd"
|
||
|
||
// 业务对象
|
||
_ "122.51.31.227/go-course/go18/book/v4/apps"
|
||
)
|
||
|
||
func main() {
|
||
// ioc框架 加载对象, 注入对象, 配置对象
|
||
// server.Gin.Run()
|
||
// application.Get().AppName
|
||
// http.Get().Host
|
||
// server.DefaultConfig.ConfigFile.Enabled = true
|
||
// server.DefaultConfig.ConfigFile.Path = "application.toml"
|
||
// server.Run(context.Background())
|
||
// 不能指定配置文件逻辑
|
||
// 使用者来说,体验不佳
|
||
|
||
// ioc 直接提供server, 直接run就行了,
|
||
// mcube 包含 一个 gin Engine
|
||
// CLI, start 指令 -f 指定配置文件
|
||
cmd.Start()
|
||
}
|
||
```
|
||
|
||
```toml
|
||
[app]
|
||
name = "simple_api"
|
||
description = "app desc"
|
||
address = "localhost"
|
||
encrypt_key = "defualt app encrypt key"
|
||
|
||
[datasource]
|
||
provider = "mysql"
|
||
host = "127.0.0.1"
|
||
port = 3306
|
||
database = "go18"
|
||
username = "root"
|
||
password = "123456"
|
||
auto_migrate = false
|
||
debug = false
|
||
|
||
[http]
|
||
host = "127.0.0.1"
|
||
port = 8010
|
||
path_prefix = "api"
|
||
|
||
[comment]
|
||
max_comment_per_book = 200
|
||
```
|
||
|
||
```sh
|
||
➜ v4 git:(main) ✗ go run main.go -f application.toml start
|
||
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
|
||
|
||
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
|
||
- using env: export GIN_MODE=release
|
||
- using code: gin.SetMode(gin.ReleaseMode)
|
||
|
||
2025-05-25T15:59:33+08:00 INFO config/gin/framework.go:41 > enable gin recovery component:GIN_WEBFRAMEWORK
|
||
200
|
||
[GIN-debug] GET /api/simple_api/v1/books --> 122.51.31.227/go-course/go18/book/v4/apps/book/api.(*BookApiHandler).queryBook-fm (4 handlers)
|
||
[GIN-debug] POST /api/simple_api/v1/books --> 122.51.31.227/go-course/go18/book/v4/apps/book/api.(*BookApiHandler).createBook-fm (4 handlers)
|
||
2025-05-25T15:59:33+08:00 INFO ioc/server/server.go:74 > loaded configs: [app.v1 trace.v1 log.v1 validator.v1 gin_webframework.v1 datasource.v1 grpc.v1 http.v1] component:SERVER
|
||
2025-05-25T15:59:33+08:00 INFO ioc/server/server.go:75 > loaded controllers: [comment.v1 book.v1] component:SERVER
|
||
2025-05-25T15:59:33+08:00 INFO ioc/server/server.go:76 > loaded apis: [books.v1] component:SERVER
|
||
2025-05-25T15:59:33+08:00 INFO ioc/server/server.go:77 > loaded defaults: [] component:SERVER
|
||
2025-05-25T15:59:33+08:00 INFO config/http/http.go:144 > HTTP服务启动成功, 监听地址: 127.0.0.1:8010 component:HTTP
|
||
```
|
||
|
||
## 总结
|
||
|
||
业务分区框架, 我们专注于业务对象的开发, mcube相对于一个工具箱,承接其他非业务的公共功能
|
||
|
||
|
||
## 其他非功能需求
|
||
|
||
工具箱 提供很多工具,开箱即用, 比如health check, 比如metrics
|
||
|
||
```go
|
||
// 健康检查
|
||
_ "github.com/infraboard/mcube/v2/ioc/apps/health/gin"
|
||
// metrics
|
||
_ "github.com/infraboard/mcube/v2/ioc/apps/metric/gin"
|
||
```
|
||
|
||
|
||
[metric.v1 books.v1 health.v1] metric, health 使用注入的对象
|
||
```sh
|
||
➜ v4 git:(main) ✗ go run main.go -f application.toml start
|
||
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
|
||
|
||
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
|
||
- using env: export GIN_MODE=release
|
||
- using code: gin.SetMode(gin.ReleaseMode)
|
||
|
||
2025-05-25T16:06:42+08:00 INFO config/gin/framework.go:41 > enable gin recovery component:GIN_WEBFRAMEWORK
|
||
200
|
||
[GIN-debug] GET /metrics/ --> github.com/infraboard/mcube/v2/ioc/apps/metric/gin.(*ginHandler).Registry.func1 (5 handlers)
|
||
2025-05-25T16:06:42+08:00 INFO metric/gin/metric.go:89 > Get the Metric using http://127.0.0.1:8010/metrics component:METRIC
|
||
[GIN-debug] GET /api/simple_api/v1/books --> 122.51.31.227/go-course/go18/book/v4/apps/book/api.(*BookApiHandler).queryBook-fm (5 handlers)
|
||
[GIN-debug] POST /api/simple_api/v1/books --> 122.51.31.227/go-course/go18/book/v4/apps/book/api.(*BookApiHandler).createBook-fm (5 handlers)
|
||
[GIN-debug] GET /healthz/ --> github.com/infraboard/mcube/v2/ioc/apps/health/gin.(*HealthChecker).HealthHandleFunc-fm (5 handlers)
|
||
2025-05-25T16:06:42+08:00 INFO health/gin/check.go:55 > Get the Health using http://127.0.0.1:8010/healthz component:HEALTH_CHECK
|
||
2025-05-25T16:06:42+08:00 INFO ioc/server/server.go:74 > loaded configs: [app.v1 trace.v1 log.v1 validator.v1 gin_webframework.v1 datasource.v1 grpc.v1 http.v1] component:SERVER
|
||
2025-05-25T16:06:42+08:00 INFO ioc/server/server.go:75 > loaded controllers: [comment.v1 book.v1] component:SERVER
|
||
2025-05-25T16:06:42+08:00 INFO ioc/server/server.go:76 > loaded apis: [metric.v1 books.v1 health.v1] component:SERVER
|
||
2025-05-25T16:06:42+08:00 INFO ioc/server/server.go:77 > loaded defaults: [] component:SERVER
|
||
2025-05-25T16:06:42+08:00 INFO config/http/http.go:144 > HTTP服务启动成功, 监听地址: 127.0.0.1:8010 component:HTTP
|
||
``` |