缘起

我是2021年底开端重视 CloudWeGo 社区的,其时我作为校招生刚刚入职字节跳动,之前主要是重视 Java 方向的技能栈,对go的生态、微服务组件都不是很了解,所以其时在做需求、上线代码、定位线上问题时,都有一些似懂非懂影影绰绰的不踏实感,刚好其时 CloudWeGo 开端开源,所以便萌生了一个想法:从阅览框架源码的视点来了解、学习公司的技能栈,然后更好的确保自己的代码质量,进步自己定位线上问题的才能。

CloudWeGo 是我真正含义上触摸的第一个开源社区,和很多人一样,一开端也不知道这么大的一个项目该从哪里开端学习好,在很长一段时刻里边,也只是停留在没有聚集和总结的通读代码层面上,在这段时刻里边,提交了一些含义不是很大的typo fix,就这样,时刻来到了2022年年初。

2022年3月份,其时Kitex库房发布了一个 good first issue,主要是进步代码单测覆盖率,正愁找不到方向的我决议从这儿下手,招领了一个 package 的单测使命,也是从那时分开端,参加 CloudWeGo 的社区开发者周会等活动

BookShop demo 案例详解——从上手电商到上手CloudWeGo

这个 issue 上手门槛不高,很快便完结了,在代码开发进程中,还发现了一处代码 bug 并提了 bugfix,让我作为一个新手对社区更有参加感了。完结之后,我注意到还有一个good first issue:

BookShop demo 案例详解——从上手电商到上手CloudWeGo

这个 issue 是提交一份 Kitex 和 Hertz 的事务工程实践,考虑到我是抖音电商事务的研制,在平时工作中对电商体系也是在持续的学习,做这个项目不但能够用自己的所学来回馈社区,而且经过做这个项目,也能够不断拓宽我的才能鸿沟,不断促进我去发现缺乏然后补偿,所以其时我招领了电商demo体系开发的使命。

在做这个使命的进程中,有时分也会发现社区短少的才能,然后我便会去做才能补齐,例如:在开发该项目的用户鉴权模块时,Hertz 还没有开源版的 JWT 中间件,在代码调试时,发现 Hertz 还短少一款 Swagger 中间件,所以我其时去做了对应的代码奉献:

BookShop demo 案例详解——从上手电商到上手CloudWeGo

BookShop demo 案例详解——从上手电商到上手CloudWeGo

电商事务很复杂,单从产品的生命周期来说,从最底层的供货商,到分销商到商家,再到最上层的顾客,链路很长,在做这个demo的进程中我一边在学习相关知识,一边也在不断的推翻、删减,我将这次共享的主题定为“从上手电商到上手CloudWeGo”,其实从另一种含义上来说,它也是我这段时刻的生长历程,从一年前到现在,我也是跌跌撞撞,尝试上手电商,上手 CloudWeGo。

下面给咱们概述一下电商体系的组成

电商体系概述

不管是线下买卖还是线上买卖,一次买卖流程中最重要的实体便是产品,电商——一个将产品买卖从线下转为线上的体系,会呈现很多原先咱们在线下买卖时底子不重视、或许压根没有的名词,这些名词的发明、演进的进程也正是人们在电商这个范畴一点点堆集、发展的进程,因而首先对这些名词做一个解释。这儿敲个重点,下面的名词会在今天的共享中频繁提到。

  • 类目和特点

BookShop demo 案例详解——从上手电商到上手CloudWeGo

在最早期的电商体系中,其实底子没有类目和特点的概念,试想一下,假如一个购物网站,里边只要两三家店肆,几百个产品,那么不需要对产品分类或许只需做一个很简略的分类就能让体系运转起来,比如:服装、电器、日用品等等,但跟着产品的丰厚(男装女装、衬衫裙子)和产品数量级的增大(亿、十亿、百亿等级),简略的一层分类已经无法满足需要,因而便衍生出了类目乃至类目树(多级类目)的概念。产品持续丰厚,有日韩风欧美风,阿迪耐克,假如都用类目承接那么类目就会变得反常深、反常复杂,不但不利于保护,而且也会呈现商家类目错放、顾客流式的问题,所以又有了一个概念:特点,日韩风欧美风、阿迪耐克都是这个产品的特点

跟着事务的演进,类目又有前台类目后台类目,特点又有销售特点(决议库存的特点)、类目特点(类目下产品应具有的特征)等等区分

  • SPU 和 SKU

SPU(Standard Product Unit) 中文为标准产品单元,浅显的理解为市面上的一款产品,如 iPhone14,雅诗兰黛特润修护肌活精华露(小棕瓶精华)

SKU(Stock Keeping Unit) 中文为库存量单位,库存最终是落在 SKU 维度,例如A商家售卖的一款iPhone14-紫色-512GB,库存还剩100件,这儿的iPhone14便是A商家的产品,iPhone14-紫色-512GB便是这个产品下的一个 SKU

SKU、SPU、产品的联系图:

BookShop demo 案例详解——从上手电商到上手CloudWeGo

从电商体系全链路的视点来看,体系内主要有以下四种人物:

人物 场景
商家 产品办理,素材办理,库存办理,点评办理,履约发货、报名活动等
顾客 查找产品、浏览商列商详、订单创立、退单、收货、点评等
运营 体系元数据装备(如类目保护、销售特点保护、SPU保护、资质保护)、营销活动、商家判罚等
审核员 产品审核、召回、解封,资质鉴真等

服务划分和责任:

电商体系从范畴上大致能够划分成如下几个方向:产品、营销、买卖、售后、供应链、安全、店肆、账号等等,每个方向依据责任的不同、重要性的不同又能够划分为多个微服务

BookShop demo 案例详解——从上手电商到上手CloudWeGo

体系架构&&代码分层规划

体系架构

由上文可知,电商体系链路长,内容多,从 demo 演示的视点考虑,进行了如下裁剪,期望能够以最低的门槛协助咱们了解电商、了解 CloudWeGo:

产品实体精简:精简掉了类目、类目特点、销售特点等概念,不强调 SKU 的概念,产品发布退化为图书标品(SPU)的发布,库存落在 SPU 上而不是 SKU 上(这也是 BookShop 的由来),库存只保留简略的现货库存,放弃阶梯库存、区域库存、途径库存等概念

struct BookProperty {
    1: string isbn // ISBN
    2: string spu_name // 书名
    3: i64 spu_price // 定价
}
struct Product {
    1: i64 product_id
    2: string name // 产品名
    3: string pic // 主图
    4: string description // 概况
    5: BookProperty property // 特点
    6: i64 price // 价格
    7: i64 stock // 库存
    8: Status status // 产品状况
}

链路精简:只保留了产品(担任办理产品、办理库存、查找产品)、订单(担任订单创立、取消、列表、查询)、账号(担任创立、登录、鉴权)三个服务,串联商家发布产品->顾客购买这一条链路

架构图

BookShop demo 案例详解——从上手电商到上手CloudWeGo

组件选取

  • Hertz:用于编写Face的服务,统一对外http层

    • jwt 中间件:供给商家、顾客账号鉴权才能
    • swagger 中间件:主动生成文档、接口测验
    • gzip 中间件:压缩传输数据,削减带宽
    • pprof 中间件:服务性能分析东西
  • Kitex:用于编写Item、User、Order服务,体系RPC层

  • MySQL:数据库存储

  • Redis:缓存,用于存储用户账号信息

  • ETCD:分布式存储体系,用于服务注册发现

  • ES:查找引擎,用于产品C端查找

  • Kibana:用于ES数据可视化操作

代码分层架构

为什么要分层

分层的本质是办理软件的复杂度,将不同复杂度、不同改变频率的模块区分开

BookShop demo 案例详解——从上手电商到上手CloudWeGo

经过代码分层咱们能够做到

  • 高内聚:分层的规划能够阻隔改变速度不同的内容,简化体系,让不同层专心于做不同的工作
  • 可扩展:分层之后能够对某一层更简单做扩展(比如典型的换 repository)
  • 可复用:分层之后同一层能够有多种用处,一同由于复用,咱们能够下降代码保护成本和改动风险

如何分层

三层架构

经典的三层架构如图所示

暂时无法在飞书文档外展现此内容

DDD四层架构

《范畴驱动规划:软件中心复杂性应对之道》中推荐的分层架构

BookShop demo 案例详解——从上手电商到上手CloudWeGo

  • 用户接口层:担任向用户显现信息或许解释命令。
  • 应用层:界说软件要完结的使命,而且指挥表达范畴概念的对象来处理问题。应用层要尽量简略,不包含事务规矩或许事务知识,只为下一层中的范畴对象协调使命、分配工作,使它们相互协作。
  • 范畴层:担任表达事务概念,事务状况信息以及事务规矩。虽然保存事务状况的技能细节是由基础设施层完成的,可是反应事务状况的状况是由本层控制而且运用的。范畴层是事务软件的中心,范畴模型就位于这一层。
  • 基础设施层:为其他层供给通用的技能才能,包含三层架构中的数据拜访层,也包含从三层架构的事务逻辑剥离出来的技能框架、中间件体系、其他体系调用的相关代码。

优点:

  • 第一次把代码的分层和 DDD 的范畴建模对应起来了,匹配了事务逻辑和事务实体的建模结果
  • 对三层架构做了进一步细分,每层的责任更加清晰

缺陷:

  • 没有处理事务逻辑(应用层+范畴层)层依靠基础设施层的问题。即没有消除由于基础设施层里详细技能完成的改变可能导致的应用服务层和范畴层的改变。
改善的DDD四层架构

为了处理四层架构在基础设施层的依靠问题,在《完成范畴驱动规划》一书中提出了依靠倒置的改善计划,如下所示:

BookShop demo 案例详解——从上手电商到上手CloudWeGo

所谓依靠倒置:高层模块不直接依靠于低层模块,两者都依靠于笼统,笼统不依靠细节,细节应该依靠笼统。

Repository和DI

“ DDD规划的目标是重视范畴模型而并非技能来创立更好的软件,假定开发人员构建了一个SQL,并将它传递给基础设施层中的某个查询服务然后依据表数据的结构集取出所需信息,最终将这些信息供给给结构函数或许Factory,开发人员在做这一切的时分早已不把模型看做重点了,这个整个进程就变成了数据处理的风格 ”

——摘 Eric Evans《范畴驱动规划》

在改善的DDD四层架构里,咱们提到了repository(仓储)DI(依靠注入)的概念,Repository是范畴层界说的一个接口,它笼统了事务逻辑对实体的拜访(包括读取和存储)的技能细节,它的作用便是经过阻隔详细的存储层技能完成来确保事务逻辑的稳定性。在DDD改造后的四层架构中,仓储接口的界说在范畴层,完成在Infrastructure层。

暂时无法在飞书文档外展现此内容

在图左中,必然很简单让范畴模型对数据库、内存等这儿基础设施的代码产生依靠,然后让基础设施的概念侵略到范畴模型变得简单。咱们习惯于面向数据和进程的开发,当这类代码和范畴模型的代码界限变得没那么明显的时分,聚集于模型也简单被破坏,倒置依靠整齐架构分层给了咱们处理这个问题很好的实践。咱们能够把仓储的行为笼统为根本的接口,然后利用控制回转,把完成该节点的仓储注入范畴模型的运行态中,如图右。

结合本电商项目中的代码举例:

在item服务的范畴层,供给了创立产品(AddProduct)的才能,它不直接依靠基础设施层,而是依靠 Repository的一个接口:

// domain/service/product_update_service.go
func (s *ProductUpdateService) AddProduct(ctx context.Context, entity *entity.ProductEntity) error {
        err := repository.GetRegistry().GetProductRepository().AddProduct(ctx, entity)
        if err != nil {
                return err
        }
        return nil
}
// domain/repository/product_repository.go 
type ProductRepository interface {
        AddProduct(ctx context.Context, product *entity.ProductEntity) error
        UpdateProduct(ctx context.Context, origin, target *entity.ProductEntity) error
        GetProductById(ctx context.Context, productId int64) (*entity.ProductEntity, error)
        ListProducts(ctx context.Context, filterParam map[string]interface{}) ([]*entity.ProductEntity, error)
}

infrastructure层完成这个接口

// infras/repository/product_repo_impl.go
func (i ProductRepositoryImpl) AddProduct(ctx context.Context, product *entity.ProductEntity) error {
        if product == nil {
                return errors.New("刺进数据不可为空")
        }
        po, err := converter.ProductDO2POConverter.Convert2po(ctx, product)
        if err != nil {
                return err
        }
        return DB.WithContext(ctx).Create(po).Error
}

运用前,进行依靠注入

repository.GetRegistry().SetProductRepository(productRepository)

详细来说,这样做能够带来什么优点?

假如有一天,跟着产品的量级逐渐变大,即使是B端商家检索产品也不再适合直接运用DB,而是运用ES,那么咱们能够直接新增一个repository接口的ES完成,将新完成依靠注入,这样彻底不需要改变上层任何代码,层与层之间的耦合更小更轻,更利于项目的保护

项目代码讲解

  1. 详见回放视频(“27:18”处开端):meetings.feishu.cn/s/1is2wi7yt…
  2. GitHub :github.com/cloudwego/b…

总结

以上是我本次共享的全部内容,最终是本项目和一些相关项目的链接,前文提到,电商是个很庞大的体系,我只不过是完成了其中十分小的一部分,欢迎感兴趣的同学来参加奉献,一同让这个demo变得更完善

  • book-shop: github.com/cloudwego/b…
  • hertz-contrib: github.com/hertz-contr…
  • kitex-contrib: github.com/kitex-contr…

项目地址

  • GitHub:github.com/cloudwego

  • 官网:www.cloudwego.io

  • 【CSG 第四期】CloudWeGo 事务实践案例解读开端啦

活动链接:github.com/cloudwego/c…

BookShop demo 案例详解——从上手电商到上手CloudWeGo