go工程化实践

165 阅读6分钟

1.go工程化实践之前置知识

(1)传统mvc架构

即model view controller

  • controller contains control logic
  • model define data struct

(2)MVC -> DLC

  • model -> dao(data access object)
  • controller -> logic,control

(3)DDD驱动

  • 贫血模式
    • controller->logic->dao
    • 业务逻辑都在logic层中
  • 充血模式
    • 解释比较抽象:如下
    • 要让domain object 即 entity 有更多逻辑
    • 通过聚合来组合entity的逻辑
    • 实际的例子
    public void saveCustomer {
        // 主流程根据不同的用例选择执行不同的对象函数
        // 而不是用数据建模,将所有处理集中在一个函数中
         public void updateColumnA();
         public void updateColumnB();
         public void updateColumnC();
    }
    

(4)SOLID模式 接口隔离,依赖倒置等

// 业务层与model层之间用interface来做隔离,实现依赖导致
class OrderService {
    public function saveOrder($order OrderInterface){
        $order->save();
    }
}

interface OrderInterface(){
    public function save();
}

2.go工程化实践之架构实践

(1)整洁架构

image.png

  • 核心观点:

    • 不与框架绑定
    // ctx不能与gin.context绑定,可以切换框架
    // req需要再封装一层,不能直接与http.request绑定
    func GetOrderHandler(ctx context.context, req interface) error {
        return nil
    }
    
    • 可测试
    // 在没有ui,database环境,web server等外部环境的前提下做到可测试
    // 1.基于mock实现
    // 2.基于go monkeypatching实现
    // 3.第三方类库mock例如: etcd mock,redis client mock
    // 4.标准库mock例如:httptest,gomock,testify
    
    • 不与ui绑定
    // 可以在ui界面在命令行界面且切换
    
    • 不与具体数据库绑定
    type OrderRespository interface{
        // 抽象出接口层
        // 业务依赖于接口
        Get(ctx context.context, id int) ( *domain.Order, error ) 
    }
    
    • 不依赖任何外部代理
  • 具体案例 github.com/eminetto/clean-architecture-go-v2

    github.com/bxcodec/go-clean-arch

3.DDD领域驱动工程化架构

(1)基本名称

  • 战略设计
    • 整个软件系统的架构以及组织
  • 战术设计
    • 名词:值对象 Value Object,实体 Entity,聚合 Aggregate,聚合根 Aggregate Root,仓储 Repository,服务等概念

(2)实践

  • 架构:六边形架构,即端口适配器架构。Ports:Interface,Adapter:Instance
  • 值对象:例如,1,2,3共同组成了一组值对象
  • Entity:实体的每个属性都可以理解为值对象
    type Demo struct{
        Id
        Name
        Gender
    }
    

image.png

  • Aggregate
    type Collection stuct {
        Id
        Tabs []Demo
        Created
    }
    
    • 聚合的粒度会比Entity更粗一点,由Entity以及值对象组成
    • 聚合需要对聚合内的数据一致性负责
    • 聚合外的一致性采用最终一致性保证
    • 微软的聚合举例:

image.png

参考文档:微软DDD设计

4.实践设计一个中台架构

(1)go在数据中台中的应用

  • Apache Beam beam.apache.org 流式计算
  • Go Mysql(解析binlog)
  • log agent(收集日志)

(2)非典型的数据中台的迭代过程

  • API要做到无状态,把数据的CRUD做成一个服务
type HashKey struct {
    StorageType string
    storageInternalType string
}

type Storate Interface {
    func Get()
    func Set()
    func Incr()
}

  • 将用户需要的指标定义为特征,管理在数据库中 例如:使用jpath包管理这些特征
// json:`{errcode:0,data:{game:alex,profile:{age:12}}}`

type TestJpath struct{
    age int `jpath:data.profile.age`
}

  • 当特征与特征之间产生依赖的时候
    • 例如:查找订单的时候,需要查找用户绑定的取件人。 我们首先拿到的是订单记录的uid特征,根据uid特征查找到取件人特征uid,根据取件人uid再获取取件人信息。批量获取特征时,整个执行过程就是从叶子节点开始求值,直到根节点,这是一个拓扑排序的过程
    • 拓扑排序的同源合并, 可以参考 文章(拓扑排序的同源合并以及存储实践)
      • 输入参数一致,特征存储类型均为hash(有主键),特征的key template一致(都为key:value结构或者都为key:arr结构),特征的集群一致(例如:在一个redis集群中)
      • 例如下面图示中,b,d这一层,在设计的时候

image.png

  • 中台的配置管理,接入门户,流式计算,统一查询,DSL/UDF

    • 首先到这一步的时候,只能参考业内先进的经验
    • 例如流式计算可以通过sql抽象

    image.png

    • Google的f1 Query,oltp,olap,etl的需求都在f1中进行了抽象

    image.png

(3)中台的常见问题

  • 中台系统式内部saas,非常复杂,建设成本很高
  • 优先照顾大业务,忽视小业务的需求
  • 有的直接替代了前台业务的功能
  • 复杂的业务中台,对个别业务没有加速效应,反而拖累业务发展
  • 某公司收购的新业务,接入中台一年都没有接完

(4)CRUD程序员怎么进步

  • 重复的代码进行自动化,抽象化,配置化

  • 多学习一下公司同类的需求怎么做,例如google(research.google/pubs)

5.微服务在复杂业务场景的拆分难题

(1)区分业务与领域

image.png

  • 业务流程式不容易变化的
    • 有了业务模型,就有了公司业务迭代以及优化的基础
    • 业务流程一般是不容易变化的,只会微调的(例如购物:业务流程包括下单检查,下单,发货,收货;但是拼多多对购物的业务流程做了调整,比较明显的一点是取消了购物车)

(2)单体服务的困境

image.png

  • 多个开发分团队共用一个代码仓库,解决代码冲突成本非常高。
  • 每个上线批次的灰度时间叠加成本

服务拆分之后:

image.png

(3)微服务拆分之后的困境

  • 服务发现
  • 服务可观测性
  • 服务间通信安全性
  • 服务间调用稳定性
  • 自动化部署

image.png

(4)微服务拆分实践

a.按照业务能力拆解

  • 识别业务能力举例
    • 供应商管理
      • 餐馆管理 CourierService
      • 送餐员管理 RestaurantService
    • 消费者管理
      • 消费者信息 ConsumerService
    • 订单获取与履行
      • 消费者订单管理 OrderService
      • 餐馆订单管理 KitchenService
    • 送餐管理
      • 送餐员状态管理 DeliverService
      • 配送管理
    • 会计
      • 消费者记账 AccountService
      • 餐馆记账
      • 配送员记账

b.按照子域进行分解

  • 领域:你的公司在解决什么样的行业问题(例如:出行)
  • 子域:领域内可以细分为哪些子域。(例如:出租车出行,快车出行,拼车出行)
  • 限界上下文:实现这个子域的代码集合

image.png

c.拆分中注意的原则

  • 单一职责原则(SRP)
  • 闭包原则(CCP):如果两个类/服务经常一起修改,应该把他们放在一起

(5) 拆分后的集成模式

a.超时控制

  • 需要有熔断保护

b.服务发现

  • 注册中心注册服务(比较常用的模式)
  • DNS解析进行服务发现(以前百度有做过这块的实践)

c.使用异步消息进行集成

  • 延迟通知可以使用queue,但是不能取代rpc模式
  • 即使基于queue解耦了,也会面临延时导致的一致性问题 image.png

d. Event Sourcing

  • 数据库中不记录最终的状态,而是记录所有的修改历史,以方便回源
  • 优化
    • 每过一段时间进行一次快照计算
    • 当前处于实践的案例比较少,还在摸索阶段

6.go的一些工具

  • 初始化依赖注入工具,wire
wire.Build(NewServer,NewDb,NewEtcd,NewRedis,NewLogger)