1
头图
本文由 Worktile 产品研发部负责人徐海峰分享

PingCode 是一款智能化研发管理工具,由 Worktile 团队打造,对标国外的 Jira,致力于研发管理自动化、数据化、智能化,帮助企业提升研发效能,这款产品 2019 年 Q2 开始启动,于 2019 年底正式发布,刚发布的时候叫 Worktile 研发版,2020年08月31日正式独立为 PingCode ,那么作为一款全新的研发工具,很多人会比较关心,这款 SaaS 产品底层的技术架构到底是什么?今天由我这个在 Worktile 待了八年之久的新生代农民工给大家详细揭秘一下。

image.png

技术选型

熟悉 Worktile 的朋友一定知道,我们作为一家活的有点久的创业公司,一直都是使用 Node.js 作为服务端底层技术,在2019年之前,我们已经使用 Node.js 开发了6年之久,伴随着 Node.js 从 0.x 版本升级到了最新的 14.x(当然现在官方已经是 17.x 了),从到处 callback 的时代到大一统的asynca wait,也见证着 Node.js 基金会的分与合,Node.js 有很多优点,但同时也有很多缺点,特别是做中大型的企业级应用,没有类型系统是一个永远的痛,好在 2018 年我们引入了 TypeScript,这简直就是给 Node.js 锦上添了一朵花,我们很庆幸在 2018 年就全面转向了 TypeScript,那么作为 2019 年的时间点开启一个新的产品,我们毫无悬念继续 ALL IN Node.js + TypeScript,同是加上 MongoDB ​和 Redis​,简直是绝配。

前端的技术选型也是毫无悬念,我们是国内为数不多 Angular 坚定追捧者之一的公司,Angular 的哲学也非常符合我司的气质,坚持长期主义,追求极致,什么招人困难,培养人啊都不是问题,关于 PingCode 的前端想更多了解的可以查看 Worktile 前端工程化之路

以下这张图基本覆盖了 PingCode 核心技术栈,之前一个架构叫 MEAN​ (代表 MongoDB + Express + Angular + Node.js),我觉得现在可以叫 MTAN(代表 MongoDB + TypeScript + Angular + Node.js)。

image.png

单体应用到微服务架构

Worktile 过去一直是一个单体应用,甚至过了很多年后才彻底的前后端分离,那么 PingCode 矩阵式的产品结构形态决定了最合适的架构模式是微服务架构,每个子产品独立仓储、独立开发和独立部署,PingCode 整体的分层架构图如下所示:
image.png

我司的仓储和服务采用的是古希腊神话人物命名法,所以你会看到: chaoserostyphonatlas 等古希腊人物。

微服务最难的就是如何拆分服务,纵向来看,我们提供了一些基础的服务,分别为:

  • Atlas: 文件服务,封装 AWS S3 相关的功能,包含: 上传、下载、预览等和文件相关的功能,在私有部署环境下,无缝切换到 MinIO
  • Iris: 消息服务,主要包含消息通知,Feed(实时更新通知)相关功能,主要采用 SocketIORedis
  • Typhon: 帐号服务,包含注册、登录、成员管理、授权、验证相关通用功能

PingCode是矩阵式的子产品的结构模式,所以我们可以很轻松的按照子产品划分微服务,当然有部分子产品之间也会有深度的耦合,所以产品形态决定了我们可以选择一种无脑的横向划分式。

  • Agile: 敏捷开发子产品,提供 Scrum 和 Kanban 两种敏捷开发的项目模板
  • Testhub: 测试管理子产品,提供测试用例,测试计划等和测试相关的功能点
  • Wiki:知识库子产品,企业级知识库。
  • 等等...

我们在 2019 年选择微服务架构有以下两个原因:

  • 过去已经有了6年的技术积累,2018年也开始自研了符合我们的 TypeScript 底层框架 CHAOS
  • 同时随着人员的壮大,需要考虑一种适配多团队多组织的架构模式,微服务是必选之路

微服务架构带来的挑战

采用微服务架构后,对于开发和运维的要求都会变高,这其实不是一个简单的架构升级,而是整个产研能力的升级,初创公司前期还是单体架构更合适,所以在选择微服务架构之前先问一下自己:你准备好了吗?

那么对于开发和运维来说,微服务到底带来了哪些挑战呢?

开发的挑战

微服务主要给我们开发带来了三大挑战,分别为:

image.png

那么我们先看看抽象​,抽象是一个开发者非常重要的能力,这个能力决定了我们是否可以成长为一名合格架构师,是否可以脱离业务做基础架构相关工作,过去因为单体应用过于简单,对于抽象的能力要求不高,微服务后每个服务都是独立仓储、独立开发,随着复杂度的提升,以前在单体应用下一把梭的方式就不适用了。举个例子:注册成功后会创建团队,团队创建后每个子产品需要初始化业务数据(比如示例项目)

因为注册的功能是在 Typhon 帐户服务中,帐户服务是一个被其他子产品都调用的基础服务,在单体应用下通过EventBus​发送一个通知,其他子模块订阅进行初始化即可,甚至还可以直接调用其他模块的初始化逻辑代码,但是微服务下每个子服务是独立的,让帐户服务调用每个子产品的 RPC 会导致依赖混乱,最终可能会引入消息队列,每个子产品加一个初始化的服务订阅消息队列,那么本身很简单的功能就变得复杂,微服务架构对于开发人员的要求也就提高了,开发需要站在更高的角度思考如何抽象,服务间如何更好的通信等等以前在单体应用下不用考虑的问题。

还有就是每个服务都是独立的,就需要把通用的基础类库以及每个服务的 SDK 抽取出来,业务基础类库的抽取需要的就是抽象能力​,如果业务抽象都做不好那如何做基础架构呢。我们基于 TypeScript 抽象了一个底层的框架CHAOS​(相当于 NestJS),同时抽象了底层的业务类库EROS​和PC-CORE​,下图是基础类库相关的依赖和模块示意图。

image.png

第二个挑战就是工程​,上面已经提到了我们会有多个基础类库以及服务的 SDK,这些私有类库包如何管理和发布等等一些列工程相关问题随之而来。

对于私有类库的发布,我们一开始是使用 GitHub 的一个私有仓储作为类库的包,然后在业务中通过配置依赖的 GitHub 链接实现的,使用如下:

为此还单独开发了个发包工具发布到 git 仓储中,感兴趣的可以查看 git-publish
"dependencies: {
  "@worktile/chaos": "git+ssh://git@github.com:worktile/chaos-built.git#1.0.0",
}

后来 GitHub 提供了仓储的私有 Package,我们就直接使用 GitHub 的私有仓储进行包管理,这样就不用单独创建一个built​仓储来存放类构建后的产物了,之前还要给开发人员单独分配这个 built 仓储的权限等等。采用 GitHub 私有包后只需要开发人员配置创建一个AccessToken​即可,这样只要有访问仓储的对应权限就可以安装或者发布仓储私有包的权限。

对于工程我们还遇到了release​,版本管理​,分支管理​等等一系列问题,这些都是在微服务架构模式下需要一一解决的问题,没有标准的方案,适合团队并满足当前团队需求的都是好的方案,工程问题也是我们团队从团伙走向正规军的必经之路,所以对于开发人员来说除了写业务代码外可能还需要写开发工具,开发规范文档等等。

第三个挑战就是协作​,每个团队负责自己的产品或者服务,服务会有依赖会有调用,那么团队之间如何协作呢?我们把所有基础类库和 SDK 统一采用 Kanban 的方式进行项目管理,大家都可以提交 Issue 和贡献代码,对所有人都是透明的,每个基础库都会有个负责人,主要负责 Code Review 和类库的规划,当然协作还包含其他方方面面,我们采用敏捷的方式持续迭代,持续改进。

下图就是基础设施的 Kanban 项目示例:

image.png

以上是微服务架构对于我们造成的三大挑战,或许对于抽象,工程之类的问题你可能想到的解决方案就是 Monorepo, 但是 Monorepo 也不是银弹,前期可能是最好的解决方案,但是随着仓储代码的庞大和成员的增多,也会遇到一些列的问题,比如构建慢,测试时间长,冲突等等 Monorepo 的问题,所以我们在三年前做了一些三年后才看到收益的事情,这也是我们对 PingCode 产品的信心,但回过头来看,这些挑战丝毫不影响我们产品迭代的速度,和竞品比起来丝毫不弱,甚至更强。

运维的挑战

微服务导致服务增多、仓储增多,依赖关系复杂,那么对于运维来说,如何管理好这些服务就变得比较困难,我们也是在 2019 年引入 Jenkins 了实现 CI、CD。

有 CI、CD 但如果没有测试基本是很难前行的,在 PingCode 的服务端中我们也会强制每个 API 都有测试,并且业务代码的测试覆盖率必须在 80% 以上,基础类库必须要到 90% 以上,虽然测试覆盖率指标不完全代表测试的全面性,但至少核心逻辑会被覆盖,其次对于业务耦合的服务还会有集成测试、持久化测试以及性能测试等,其实测试不是微服务带来的直接挑战,单体应用下也需要测试。

除了 CI、CD 外,所有的服务统一采用 Kubernetes 进行容器化部署,服务的监控和预警、ELB、日志等等和运维相关的挑战都随之而来,日志存储采用 ElasticSearch ,管理和查看采用了 Kibana。

单服务架构

微服务会把所有的服务拆分,但是对于一个单服务来说,我们大多数子产品的架构都是类似的,除了通用的功能外,会按照模块进行划分,每个模块大致分三层包含:Facade 层​ (其实就是 API 或者 Controller 层,这一层只包含路由定义和参数输入和输出,不包含业务逻辑),Service 层​(领域层,也是业务逻辑层),Repository 层​(包含对于数据库单个实体的增删改查操作,每个 Repository 会对于一个 Entity 实体)。

image.png

下面的代码段表示一个ProjectEntity​和ProjectRepository

@collectionName("agile_projects")
@indexes<ProjectEntity>([
    {
        team: Direction.ascending,
    }
])
export class ProjectEntity extends BusinessEntity {
    public name?: string;

    public name_pinyin?: string;

    public description?: string;

    public icon?: string;

    public color?: string;

    public identifier?: string;

    public members?: PrincipalMember[];

    @defaultValue(() => Visibility.private)
    public visibility?: Visibility;
}

@injectable()
export class ProjectRepository extends DefaultBusinessRepository<ProjectEntity> {
    constructor() {
        super(ProjectEntity);
    }
}

一个 Facade 示例代码如下:

@facade()
@middlewares(
 typhonClient.passport.authMiddleware(), 
 typhonClient.application.applicationExpireMiddleware(ApplicationType.agile)
)
export class ProjectFacade extends FacadeBase {
    @inject()
    private projectService: ProjectService;

    @post("/project", CreateProjectRequest)
    @middlewares(projectPermission.authCreateProjectMiddleware())
    public async createProject(request: CreateProjectRequest): Promise<Response<ProjectEntity, void>> {
        const project = await this.projectService.createProject(request);
        return new Response(project);
    }
}

关于我们底层框架 CHAOS,这里再多介绍一下,CHOAS 提供了从应用、路由、容器再到数据访问层的一个满足我们业务需求的一整套企业级框架,那么对于应用和路由,我们是基于 Koa.js 之上的二度封装,CHAOS 屏蔽了 Koa 相关 API,这样即使底层切换到 Express 或者更先进的 Web 框架对于用户来说也是无感的,其次就是 Repository 也是对于 mongodb 模块的二度封装,除此之外的一些核心功能是完全自研的,包括:

  • Container: 容器,帮助应用轻松实现依赖注入
  • Rpc: 微服务 RPC 通信框架
  • Reminder: 定时任务的基础类库,负责提供任务的注册、执行、错误处理等
  • 其他模块 ...

    微前端架构

    和微服务架构一样,我们也是在 2019 年引入了微前端,因为没有找到合适的微前端框架,我们自研了 ngx-planet - 一个我认为是最好用的 Angular 框架下的微前端解决方案。以下是整个微前端的架构示意图:

image.png

Portal​ 是主应用,也叫基座应用,它负责左侧的导航、全局数据管理、通知、搜索等全局功能,最重要的是运行时通过 ngx-planet 实现子应用的注册、加载和销毁。

每个应用按照子产品划分,构建提供manifest.json​静态资源文件列表,主应用会根据这个资源列表加载响应的脚本和样式。

那么采用微前端架构后对于前端开发带来的挑战和微服务是类似的,我也从抽象​、工程​和协作​ 三方面阐述一下。

第一个就是抽象​, 我们在 2018 年已经打造了内部的组件库,那么在 2019 年微前端后也开始需要构建业务组件库,做业务组件其实最能考验开发者的抽象能力,组件库的组件可以参考别的组件库对应组件的设计和API,但是业务组件库无从参考,我们经常会遇到某个小伙伴费劲心思提交了一个业务组件库的 PR,结果一 Code Review 发现这个已经支持了或者有比这更好的解决方案,所以后来我们制定了一个规范,当你对于组件库或者业务组件提交一个新特性的时候描述一下具体的场景和需求,然后思考一下你准备如何设计(包含 API 参数,命名,结构等等)然后在评论中@一下大家,这样所有人都可以基于你的方案进行头脑风暴,最终确定一个大家觉得比较完善的方案。

在 PingCode 中我们除了使用了第三方的 highcharts 作为报表库以外,基本没有引入过其他第三方库,不是说我们的团队有多么的厉害,而是得益于 Angular 的 CDK , 通过 CDK 可以轻松开发组件库和业务组件库,CDK 让我们这种前端 20 人不到的团队把不可能​变成了可能​(当然组价库的部分组件也会借鉴(chao) Angular MaterialNG-ZORRO 等优秀组件库的做法)。有人说我们是 Angular 框架下国内轮子造的最多的公司,其中就包含:

这些都不是 KPI 项目,都是我们在解决 PingCode 和 Worktile 遇到的实际业务问题,无奈 Angular 社区没有更好的方案,我们满足了自己同时为了回馈社区开源出去了而已,我们也会持续完善和迭代已经开源的类库,开源本身对于开发来说也是一个技术挑战,做好开源项目其实是很不容易的。以上这些特定场景的解决方案都是抽象能力的表现,PingCode 前端开发者也在做这些开源项目的过程提升了抽象能力。

第二个挑战是工程​,和微服务的一样,过去没有采用 Monorepo 直接上的微前端,具体原因就不一一说了,但是到目前为止,对于微前端带来的核心工程问题,我们都解决了,发包采用 GitHub 的私有 Package,一开始 Angular 编译类库特别慢,对于频繁发包的业务组件库带来了不小的挑战,随着 Angular 的升级,目前速度已经明显上升,后续会通过 GitHub 的 Actions 做到自动发包,这样也就减少了人工的重复操作。对于本地开发效率的提升也一直是我们比较关注的问题,我们本身有一个测试环境,所有的服务和前端应用都部署在这个内部可以访问的测试环境中,前端的本地开发可以只启动本地服务,然后通过测试环境的 Portal 访问本地即可,然后本地的 Portal 也可以使用测试环境的子应用,我们的解决方案主要针对开发单独做了简易的代理配置界面,可以直接在微前端测拦截并代理前端资源,无需本地启动应用和安装代理工具。

image.png

对于协作​我想说一些和技术框架无关的事情,我本人一直有对代码的极致追求,我希望我所在的团队不是仅仅的完成任务,而是应该想如何做到最好,技术能够得到更好的提升,即使现在能力达不到但是一定要有一颗追求极致的心,所以我经常看到一些不合适的命名或者不好的设计都会提出来,同时因为团队壮大后跨团队之间沟通会更少,为了让整个团队的技术氛围更加积极,我们会坚持双周一次的技术分享、每日一学、内部技术周刊等等提高大家沟通能力和分享能力的事。

Angular 前端架构

采用微前端后,单个前端的架构就变的比较简单,PingCode 每个子产品按照特性模块划分业务,每个特性模块会包含: pages​stores​components​、pipe​services​ 等功能,除了特性模块外还会存在一些全局的core​shared​,这里只存放当前子产品的通过组件和服务,跨子产品的基本都已经在业务组件库中。

PingCode 前端除了 Wiki 编辑器这种特定领域外,最复杂的就是数据管理(也可以叫状态管理),因为产品的形态决定我们对于数据的实时性要求非常高,保证在不刷新页面的情况下数据可以实时更新数据, 数据更新会来自首次加载、编辑和WebSocket,Angular 状态管理可以参考我几年前写的博客 Angular 真的需要状态管理么?主要基于我们封装的一个小型状态管理类库 @tethys/store 实现 Service + Observable​组合,多 Store 的结构图为:

image.png

数据管理中最值得一说的就是引用数据的维护,PingCode 服务端 API 只会返回原子数据,前端通过 RxJS 进行数据的聚合,基于 @tethys/store 会非常方便,有时间我会单独写文章详细介绍一下,数据格式如下图所示:

image.png

以上是我对 PingCode 产品的技术架构揭秘,懒懒散散说了很多或许和技术架构无关的话题,希望给读者带来一些共鸣和思考,这也是一个中小研发团队一路走来的心路历程,PingCode 技术架构未必是做的最好的架构,但是站在目前的角度看是比较适合的架构,我们会保持初心,继续前进,为研发提供更好管理工具。


最后,推荐我们的智能化研发管理工具 PingCode 给大家。

PingCode官网

关于PingCode

PingCode是由国内老牌SaaS厂商Worktile 打造的智能化研发管理工具,围绕企业研发管理需求推出了Agile(敏捷开发)、Testhub(测试管理)、Wiki(知识库)、Plan(项目集)、Goals(目标管理)、Flow(自动化管理)、Access (目录管理)七大子产品以及应用市场,实现了对项目、任务、需求、缺陷、迭代规划、测试、目标管理等研发管理全流程的覆盖以及代码托管工具、CI/CD流水线、自动化测试等众多主流开发工具的打通。

自正式发布以来,以酷狗音乐、商汤科技、电银信息、51社保、万国数据、金鹰卡通、用友、国汽智控、智齿客服、易快报等知名企业为代表,已经有超过13个行业的众多企业选择PingCode落地研发管理。


PingCode研发中心
111 声望22 粉丝