当我们谈论开源 CRM 时,Twenty 是一个不可忽视的名字。这个项目以「构建 Salesforce 的现代化替代品」为目标,在 GitHub 上已收获超过 42,000 颗星、5,600 余次分支,累计提交超过 11,000 次。与传统企业软件不同,Twenty 从一开始就将 TypeScript 作为唯一的编程语言,从前端 React 到后端 NestJS,从数据库模型到工作流引擎,全部由强类型语言驱动。这种全栈 TypeScript 架构不仅为开发者提供了流畅的类型安全保障,更为系统的可维护性与可扩展性奠定了坚实基础。本文将从技术栈选型、实体关系建模、工作流引擎设计与可扩展性四个维度,深度解析 Twenty 的架构决策与工程实践。
全栈 TypeScript 与 Monorepo 架构
Twenty 的技术选型体现了现代前端工程化的核心理念。项目采用 Nx 作为 Monorepo 管理工具,将前端、后端、文档、网站等多个子项目统一纳入单一仓库管理。这种架构模式的优势在于代码复用更为便捷 —— 类型定义、工具函数、业务常量可以在多个包之间直接共享,无需通过发布 npm 包或复制粘贴来传递。当团队需要为 CRM 添加新的国际化语言或修改某个共享的业务规则时,只需在统一的代码库中修改一次,所有依赖方即可自动获得更新。
前端技术栈以 React 为核心,配合 Jotai 进行状态管理。Jotai 是一个基于原子化理念的状态管理库,相比 Redux 或 Zustand,它更强调细粒度的状态订阅与派生状态计算。在 Twenty 的复杂业务场景中,联系人、 公司、交易等数据对象之间存在大量的联动关系 —— 例如更新一家公司的行业字段可能需要同步刷新关联联系人的列表视图,Jotai 的原子化模型能够精确追踪这些依赖变化,避免不必要的渲染开销。样式方面,项目选择 Linaria 作为 CSS-in-JS 解决方案,其核心优势在于支持零运行时开销的静态样式提取,生产环境下的样式最终会打包为普通的 CSS 文件,而非内联在 JavaScript Bundle 中,这对复杂 CRM 界面的加载性能有直接影响。国际化则采用 Lingui 框架,支持运行时语言切换与动态翻译加载。
后端层面,Twenty 选择 NestJS 作为服务框架。NestJS 基于依赖注入与模块化的设计理念,与 Angular 类似的架构风格使得前端开发者能够快速上手。配合 PostgreSQL 作为主数据库、Redis 作为缓存与消息队列基础设施,构成了典型的现代化 Web 服务架构。值得特别关注的是 BullMQ 的引入 —— 它为系统提供了强大的后台任务处理能力,无论是工作流中的异步动作执行,还是批量数据导入导出,都依赖于这个基于 Redis 的任务队列系统。
核心实体关系建模
理解一个 CRM 系统的本质,关键在于其数据模型的设计。Twenty 的实体建模采用了经典的关系型设计思路,同时通过 JSONB 字段支持灵活的扩展字段机制。让我们首先梳理系统中的核心对象:联系人(Contact)、公司(Company)、交易(Deal)、活动(Activity)构成了业务数据的四大支柱。
联系人是 CRM 中最基础的原子实体,每条记录包含姓名、邮箱、电话、所属公司、职位等标准字段,同时支持任意数量的自定义字段。公司实体记录企业客户信息,包括名称、域名、行业、规模等属性。在关系设计上,联系人与公司之间是一对多关联 —— 一家公司可以拥有多名联系人,但每个联系人只能隶属于一家公司。这种设计符合现实业务逻辑,在数据库层面通过外键约束维护完整性。
交易(Deal)是 CRM 业务流的核心载体,承载了销售 pipeline 的推进过程。每笔交易关联一个联系人和一家公司,记录交易名称、金额、阶段、预计成交日期等关键信息。交易与联系人的关系是多对多 —— 同一位决策人可能参与多个交易,而一个交易也可能涉及多个利益相关方。在 Twenty 的实现中,这种多对多关系通过隐式的关联表维护,系统会自动处理级联查询与数据一致性。
活动实体则用于记录与客户的所有交互行为,包括邮件、会议、电话、任务等类型。每条活动可以关联到具体的交易或联系人,并记录时间、时长、备注等细节。这种设计使得销售团队能够完整追溯与客户的沟通历史,为后续的跟进决策提供数据支撑。
在标准关系型字段之外,Twenty 为每个核心实体引入了 customFields(自定义字段)概念。这一机制允许终端用户在不使用代码的情况下,为数据模型添加任意数量的扩展属性。这些扩展字段的值以 JSONB 格式存储在数据库中,既保持了 schema 的灵活性,又利用 PostgreSQL 的 JSONB 索引能力保证了查询性能。从 TypeScript 类型系统的角度看,系统在运行时通过元数据描述文件维护自定义字段的类型信息,前端表单组件根据这些元数据动态渲染对应的输入控件,实现了一个真正的无代码数据建模层。
事件驱动工作流引擎
工作流引擎是 Twenty 区别于简单联系人管理工具的核心组件,也是其作为「现代化 CRM」的技术体现。Twenty 的工作流系统采用事件驱动架构,核心概念包括触发器(Trigger)、动作(Action)与状态机(State Machine)。当业务数据发生变化时,系统会发布相应的领域事件 —— 例如联系人创建、交易阶段变更、活动记录完成 —— 这些事件被工作流引擎捕获后,根据预定义的规则判断是否触发自动化流程。
以销售流程为例,一个典型的交易推进工作流可能包含以下状态:潜在客户、资格审核、需求分析、方案报价、谈判中、成交、丢失。每个状态之间的转换即为一次状态机跳转,而跳转条件可以非常灵活 —— 可以是手动点击「推进到下一阶段」,也可以是满足特定业务规则后的自动触发,例如「当交易金额超过 50 万元且客户已回复邮件时,自动推进到谈判中阶段」。这种设计将销售流程标准化,同时保留了足够的灵活性以适应不同行业、不同规模团队的业务需求。
触发器与动作的分离是工作流引擎设计的另一个关键点。触发器定义了「何时执行」,动作则定义了「执行什么」。一个完整的自动化流程可能包含多个动作,例如当交易阶段变更为「方案报价」时,系统可以同时执行以下操作:创建一条「发送报价单」的任务分配给负责的销售人员、发送一封自动邮件给客户、记录一条活动日志用于审计追踪。这种组合式的动作执行能力,使得业务人员能够在不编写代码的情况下,配置出符合自身业务场景的自动化规则。
在技术实现层面,工作流的持久化采用关系型表结构。WorkflowState 表存储所有可能的状态定义,WorkflowStep 表描述状态之间的转换规则,WorkflowInstance 表则记录每个实体实例当前所处的工作流位置与执行状态。异步动作的执行委托给 BullMQ 队列处理 —— 例如发送邮件这类耗时操作不宜在主请求链路中同步完成,而是生成一个后台任务由 Worker 进程异步执行。这种设计保证了前端响应的及时性,同时通过重试机制与死信队列保障了任务的可靠性。
可扩展性设计
一个成功的 CRM 系统必须能够适应不同行业、不同规模企业的差异化需求。Twenty 在架构层面为可扩展性做了充分准备,主要体现在以下几个维度。
首先是自定义对象与字段机制。如前所述,系统允许用户创建全新的数据对象类型,并为已有的对象添加任意自定义字段。这种能力使得 Twenty 不仅能服务于标准销售流程,还能够适配项目型销售、服务台管理、会员运营等多样化场景。从技术实现角度看,自定义对象的元数据存储在专门的设计表(metadata tables)中,运行时的数据服务根据这些元数据动态拼接 SQL 查询 —— 这是一个典型的元编程模式,在保持系统简洁性的同时实现了无限的扩展可能。
其次是权限体系的精细设计。Twenty 实现了基于角色的访问控制(RBAC),允许管理员定义自定义角色,并为每个角色配置细粒度的数据访问权限。可以指定某个角色只能查看特定字段、只能访问特定对象、或者只能操作特定状态范围内的记录。这种权限模型对于中大型组织尤为重要 —— 销售团队负责人可能需要看到团队所有成员的销售数据,而普通销售人员只能访问自己负责的客户记录。
最后是插件化架构的规划。根据项目路线图,Twenty 未来将引入完整的插件系统,允许第三方开发者通过标准接口扩展系统功能。这包括自定义页面组件、自定义动作类型、自定义数据源连接器等。这一设计理念借鉴了现代 SaaS 平台的插件生态模式,一旦插件能力成熟,Twenty 将从单一的 CRM 产品演变为一个可扩展的业务应用平台,类似于 WordPress 在内容管理系统领域的生态地位。
资料来源
本文核心信息来自 Twenty 官方 GitHub 仓库及其技术栈文档。项目采用 MIT 开源许可证,代码托管于 https://github.com/twentyhq/twenty。