php微服务框架 yii,GitHub - yiippee/esim: 微服务业务框架

Esim文档

安装

环境 go 1.3 及以上

使用 module 包管理工具

go get github.com/jukylin/esim

cd github.com/jukylin/esim

go build -o esim ./tool && mv ./esim $GOPATH/bin

创建项目

esim new -s test

浏览器访问

启动服务

cd test

go run main.go

访问

使用组件测试 [推荐]

cd test/internal/transports/http/component-test/

go test

架构

Esim的架构来源于《实现领域驱动设计》的六边形架构和阿里的COLA架构,这2个架构有一个共同点:业务与技术分离。

这点很好的解决了由于微服务架构增加的网络通讯和事件导致service层越来越胖的问题。所以才决定由原来的三层架构转为四层架构。

29e96d1fffa1d68f2a6cff3e0e910f38.png

分层

Esim使用松散的分层架构,上层可以任意调用下层。

Esim使用松散的分层架构,是因为我们在使用四层架构的过程中发现:一些简单的场景,app层的service只是增加了一层调用,

并没有起到协调的作用,反而增加了一些不必要的工作,所以使用松散的架构,让controller直接掉用domain的逻辑。

不能因为使用松散的架构,把原有app层的service职责都放到了controller里面!!!

e4ddf717dda16056841951f851d3d762.png

各层职责

目录

职责

controller

负责显示信息和解析、校验请求,适配不同终端

application

不包含业务规则,为下一层领域模型协调任务,分配工作

domain

负责表达业务概念,业务状态信息和业务规则,是业务软件的核心

infrastructure

为各层提供技术支持,持久化,领域事件等

编码规范

函数第一个参数是ctx!函数第一个参数是ctx!函数第一个参数是ctx!

禁止在defer内做逻辑运算

业务代码形参、实参、返回值的类型不允许使用interface

命名

Jaeger

目录名

小写/中横线

函数名

驼峰

文件名

下划线

变量

驼峰

常量

驼峰

包名

当前目录名

请求地址

*小写

请求参数

小驼峰

返回参数

小驼峰

v1 目录 + 文件名

目录

定义

文件

接口

application/service

应用层

index_service.go

IndexService

domain/service

领域服务

index_service.go

IndexService

domain/entity

实体

index_entity.go

IndexEntity

infra/event

领域事件

index_event.go

PubIndexEvent

IndexEvent

infra/repo

资源库

index_repo.go

DBIndexRepo

IndexRepo

infra/dao

数据访问对象

index_dao.go

IndexDao

v2 目录

目录

定义

文件

接口

application/service

应用层

index.go

IndexService

domain/service

领域服务

index.go

IndexService

domain/entity

实体

index.go

IndexEntity

infra/event

领域事件

index.go

PubIndexEvent

IndexEvent

infra/repo

资源库

index.go

DBIndexRepo

IndexRepo

infra/dao

数据访问对象

index.go

IndexDao

数据库设计规范小三样

`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`last_update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

`is_deleted` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标识',

特性

由三层架构演进为四层架构(DDD + COLA)

面向接口编程

编译时的依赖注入

管控业务使用的网络io

融入log,opentracing,metrice提升服务可观察性

单元测试友好,面向TDD

依赖注入

Esim 使用wire实现编译时的依赖注入,它有以下优点:

当依赖关系图变得复杂时,运行时依赖注入很难跟踪和调试。 使用代码生成意味着在运行时执行的初始化代码是常规的,惯用的Go代码,易于理解和调试。不会因为框架的各种奇技淫巧而变得生涩难懂。特别重要的是,忘记依赖项等问题会成为编译时错误,而不是运行时错误。

与服务定位器不同,不需要费心编造名称来注册服务。 Wire使用Go语法中的类型将组件与其依赖项连接起来。

更容易防止依赖项变得臃肿。Wire生成的代码只会导入您需要的依赖项,因此您的二进制文件将不会有未使用的导入。 运行时依赖注入在运行之前无法识别未使用的依赖项。

Wire的依赖图是静态可知的,这为工具化和可视化提供了可能。

Esim将wire用于业务与基础设施之间。将基础设施的初始化从业务抽离出来,集中管理。

Esim使用wire示例

基础设施的依赖和初始化都在 infra/infra.go 文件下。wire的使用主要分2步,以增加mysqlClient:

provide

before

type Infra struct {

*container.Esim

}

var infraSet = wire.NewSet(

wire.Struct(new(Infra), "*"),

provideEsim,

)

after

type Infra struct {

*container.Esim

DB mysql.MysqlClient

}

var infraSet = wire.NewSet(

wire.Struct(new(Infra), "*"),

provideEsim,

provideDb,

)

func provideDb(esim *container.Esim) mysql.MysqlClient {

......

return mysqlClent

}

Inject

在当前目录下执行:wire命令,看到:

wire: projectPath/internal/infra: wrote projectPath/internal/infra/wire_gen.go

说明执行成功,就可以在项目中使用了。

infra.NewInfra().DB

依赖倒置

依赖倒置和依赖注入一样,都是应用于业务与基础设施之间。主要的作用是让业务与技术实现分离。

在实际的使用中我们把涉及io操作都放到了基础设施的资源库上。这样做的好处:

单元测试变简单,使用mock代替数据源

不用学习各种 mock sdk,只针对 app 和 domain写单元测试

不依赖远程,可以单机进行开发

工具

esim db2entity -d db_name -t table_name

前置条件:

在项目根目录下

配置环境变量

export ESIM_DB_HOST=127.0.0.1

export ESIM_DB_PORT=3306

export ESIM_DB_USER=root

export ESIM_DB_PASSWORD=123456

由于DDD开发方式多了很多目录,文件,导致这部分工作变得很繁琐,所以db2entity 从mysql数据库的表开始,

自动建立实体,生成简单的CRUD语句和资源库的接口与实现,并把生成的资源库注入到基础设施。

esim factory --sname struct_name -n

前置条件:

在模型目录下

开启 module, export GO111MODULE=on

factory 命令可以自动对结构体进行初始化,内存对齐,生成临时对象池,reset和释放资源等操作,减少一些繁杂操作。

esim ifacer --iname 接口名称

前置条件:

在接口目录下

ifacer 命令根据接口的定义生成空实例

esim test

68747470733a2f2f73312e617831782e636f6d2f323032302f30362f30362f7463397153302e676966

前置条件:

项目目录下

test 命令监听项目被修改文件,并在文件目录下执行go test 自动运行单元测试。

当然为了减轻一些繁杂的工作,esim test还会执行[wire](https://github.com/google/wire), [mockery](https://github.com/vektra/mockery)等命令

配置

环境设置

esim 默认为 dev 环境,esim主要由 dev 和 pro 环境

export ENV=pro

配置文件

配置文件在项目的conf目录下

conf/conf.yaml

provide

func provideConf(){

options := config.ViperConfOptions{}

file := []string{"conf/monitoring.yaml", "conf/conf.yaml"}

conf := config.NewViperConfig(options.WithConfigType("yaml"),

options.WithConfFile(file))

return conf

}

reference

service_name := infra.NewInfra().Conf.GetString("appname")

日志

日志会根据不同环境打印,开发和测试环境会把所以日志打印到终端,生产只会打印warn及以上的日志。

Esim提供了2套日志接口,一套没有上下文,一套有。使用上下文是为了把分布式环境下的日志通过tracer_id串起来。

provide

func provideLogger(conf config.Config) log.Logger {

var loggerOptions log.LoggerOptions

logger := log.NewLogger(

loggerOptions.WithDebug(conf.GetBool("debug")),

)

return logger}

reference

infra.NewInfra().Logger.Infof("info %s", "test")

infra.NewInfra().Logger.Infoc(ctx, "info %s", "test")

HTTP

比官方接口多了(ctx)参数

provide

func provideHttp(esim *container.Esim) *http.HttpClient {

clientOptions := http.ClientOptions{}

httpClent := http.NewHttpClient(

clientOptions.WithTimeOut(esim.Conf.GetDuration("http_client_time_out")),

clientOptions.WithProxy(

func() interface {} {

monitorProxyOptions := http.MonitorProxyOptions{}

return http.NewMonitorProxy(

monitorProxyOptions.WithConf(esim.Conf),

monitorProxyOptions.WithLogger(esim.Logger))

}),

)

return httpClent

}

reference

resp, err := infra.NewInfra().Http.GetCtx(ctx, "http://www.baidu.com")

defer resp.Body.Close()

Mongodb

provide

func provideMongodb(esim *container.Esim) mongodb.MgoClient {

options := mongodb.MgoClientOptions{}

mongo := mongodb.NewMongo(

mgoClientOptions.WithLogger(esim.logger),

mgoClientOptions.WithConf(esim.conf),

mgoClientOptions.WithMonitorEvent(

func() MonitorEvent {

monitorEventOptions := MonitorEventOptions{}

return NewMonitorEvent(

monitorEventOptions.WithConf(esim.conf),

monitorEventOptions.WithLogger(esim.logger),

)

},

)

)

return mongo

}

reference

import "go.mongodb.org/mongo-driver/bson"

type Info struct{

Title string

}

info := Info{}

coll := infra.NewInfra().Mgo.GetColl("database", "coll")

filter := bson.M{"phone": "123456"}

res := coll.FindOne(inf.Mgo.GetCtx(c.Request.Context()), filter).Decode(&info)

GRPC

provide

func provideGrpcClient(esim *container.Esim) *grpc.GrpcClient {

clientOptional := grpc.ClientOptionals{}

clientOptions := grpc.NewClientOptions(

clientOptional.WithLogger(esim.Logger),

clientOptional.WithConf(esim.Conf),

)

grpcClient := grpc.NewClient(clientOptions)

return grpcClient

}

reference

import (

"pathto/protobuf/passport"

)

conn := infra.NewInfra().GrpcClient.DialContext(ctx, ":60080")

defer conn.Close()

client := passport.NewUserInfoClient(conn)

getUserByUserNameRequest := &passport.GetUserByUserNameRequest{}

getUserByUserNameRequest.Username = "123456"

replyData, err = client.GetUserByUserName(ctx, getUserByUserNameRequest)

Redis

provide

func provideRedis(esim *container.Esim) *redis.RedisClient {

redisClientOptions := redis.RedisClientOptions{}

redisClent := redis.NewRedisClient(

redisClientOptions.WithConf(esim.Conf),

redisClientOptions.WithLogger(esim.Logger),

redisClientOptions.WithProxy(

func() interface{} {

monitorProxyOptions := redis.MonitorProxyOptions{}

return redis.NewMonitorProxy(

monitorProxyOptions.WithConf(esim.Conf),

monitorProxyOptions.WithLogger(esim.Logger),

monitorProxyOptions.WithTracer(esim.Tracer),

)

},

),

)

return redisClent

}

reference

conn := infra.NewInfra().Redis.GetCtxRedisConn()

defer conn.Close()

key := "username:"+username

exists, err := redis.Bool(conn.Do(ctx, "exists", key))

Mysql

provide

func provideDb(esim *container.Esim) *mysql.MysqlClient {

mysqlClientOptions := mysql.MysqlClientOptions{}

mysqlClent := mysql.NewMysqlClient(

mysqlClientOptions.WithConf(esim.Conf),

mysqlClientOptions.WithLogger(esim.Logger),

mysqlClientOptions.WithProxy(

func() interface{} {

monitorProxyOptions := mysql.MonitorProxyOptions{}

return mysql.NewMonitorProxy(

monitorProxyOptions.WithLogger(esim.Logger),

monitorProxyOptions.WithConf(esim.Conf),

monitorProxyOptions.WithTracer(esim.Tracer),

)

},

),

)

return mysqlClent

}

reference

var user model.User

infra.NewInfra().DB.GetDb(ctx, "db").Table("table").Where("username = ?", username).

Select([]string{"id"}).First(&user)

Opentracing

Esim使用Jaeger实现分布式追踪,默认为关闭状态。

开启需要使用jaeger-client-go自带的环境变量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值