开发日记:从第 1 行代码到 6000+ 测试
本文是 coomia-dip 项目从立项到首个公测版本的完整开发日记。我们用 14 个月的时间,从一行 print("hello ontology") 出发,构建了一个对标 Palantir Foundry 的本体驱动智能决策 PaaS 平台。截至本文撰写时,项目已拥有超过 6000 个自动化测试用例、覆盖 8 个架构分层(Layer),代码行数突破 15 万行。这篇文章将以时间线的方式回顾关键里程碑、技术抉择、踩过的坑,以及我们从中学到的经验教训。
“系列:S14 工程实录 · 第 1 篇 | 难度:中级 | 阅读时间:15 分钟
开发日记:从第 1 行代码到 6000+ 测试
#TL;DR
本文是 coomia-dip 项目从立项到首个公测版本的完整开发日记。我们用 14 个月的时间,从一行 print("hello ontology") 出发,构建了一个对标 Palantir Foundry 的本体驱动智能决策 PaaS 平台。截至本文撰写时,项目已拥有超过 6000 个自动化测试用例、覆盖 8 个架构分层(Layer),代码行数突破 15 万行。这篇文章将以时间线的方式回顾关键里程碑、技术抉择、踩过的坑,以及我们从中学到的经验教训。
#1. 起源:为什么要做这件事
#1.1 Palantir Foundry 的启示
2024 年初,团队在为一家制造业客户做数据中台选型时,深入研究了 Palantir Foundry。我们被它的核心理念——"本体(Ontology)驱动一切"——深深震撼。传统的数据平台停留在"表-列-行"的抽象层次,而 Foundry 将业务概念(设备、工单、供应商)提升为一等公民,所有的数据管道、分析、权限、操作都围绕这些本体对象展开。
然而,Foundry 有两个致命缺陷:一是价格——起步价数百万美元/年;二是数据主权——SaaS 模式意味着数据出境。对于国内的制造业和金融客户,这都是不可接受的。
#1.2 立项决策
经过两周的可行性分析,我们得出结论:Foundry 的核心架构并非不可复制,关键在于 Ontology Runtime、Schema Registry、Action Engine 三大核心组件。我们决定启动 coomia-dip 项目——一个可私有化部署、开源友好的 Palantir Foundry 替代方案。
#1.3 团队组建
初始团队只有 4 个人:
- 1 名架构师(负责整体设计和 Control Layer)
- 1 名后端工程师(负责 Data Layer)
- 1 名 AI 工程师(负责 Intelligence Layer)
- 1 名全栈工程师(负责 SDK 和前端)
这个精简的团队配置,后来被证明既是优势(决策快、沟通成本低)也是劣势(人手不足时只能靠加班)。
#2. 第一个月:架构奠基(Day 1 – Day 30)
#2.1 分层架构的诞生
第一周的全部时间都花在了架构讨论上。我们最终确定了 8 个 Layer 的架构:
| Layer | 职责 | 技术栈 |
|---|---|---|
| A - Platform Deployment & Ops | 部署运维 | Docker Compose, Python |
| B - Control Layer | 控制层 | Spring Boot 3.x, Java 21, gRPC |
| C - Data Layer | 数据层 | Quarkus 3.x, Iceberg+Nessie |
| D - Reasoning & Decision | 推理决策 | Python, FastAPI, gRPC |
| E - Agent Runtime | Agent 运行时 | Python, FastAPI, Temporal |
| F - Pipeline & Orchestration | 管道编排 | Quarkus 3.x, DolphinScheduler |
| G - Metadata & Governance | 元数据治理 | Java, Gradle |
| H - SDK & Developer Experience | SDK 开发者体验 | Python SDK, TypeScript |
为什么是 8 个 Layer?因为我们相信"关注点分离"。每个 Layer 有独立的生命周期、技术栈选择自由度,以及独立部署的能力。当然,后来我们发现 8 个 Layer 在开发期太重了——但这是后话(详见 S14-03)。
#2.2 第一行代码
# ontology_sdk/__init__.py - 2024-02-15
print("hello ontology")
__version__ = "0.0.1"
这行代码今天还在 git 历史中。它提醒我们,每个庞大的系统都始于一个简单的起点。
#2.3 技术选型的关键决策
gRPC over REST:内部服务之间全部使用 gRPC。这个决策在第一周就确定了,原因是:
- 强类型契约(Protobuf IDL)
- 双向流
- 代码生成减少重复劳动
- 性能(二进制序列化 + HTTP/2 多路复用)
Gradle over Maven:Java 项目统一使用 Gradle。Maven 的 XML 配置在多模块项目中过于冗长。
Python for Intelligence Layer:推理引擎和 Agent 运行时用 Python + FastAPI,因为 AI/ML 生态系统几乎全在 Python。
#3. 第二到第三个月:核心组件(Day 31 – Day 90)
#3.1 Ontology Runtime v1
Ontology Runtime 是整个平台的心脏。它负责:
- 管理 ObjectType、LinkType 的定义
- 维护 Object 实例的生命周期
- 提供 OQL(Ontology Query Language)查询接口
第一个版本非常简陋——内存中的 HashMap 存储,没有持久化,没有事务。但它跑通了核心流程:定义一个 ObjectType → 创建 Object → 通过 OQL 查询。
#3.2 Schema Registry
Schema Registry 管理所有本体类型的元数据。我们参考了 Confluent Schema Registry 的设计,但做了本体化的扩展:
- 支持 ObjectType、LinkType、ActionType 三种一等类型
- 版本管理(每次修改生成新版本,旧版本不可变)
- 兼容性检查(新版本必须向后兼容)
#3.3 第一个集成测试
Day 60,我们跑通了第一个端到端集成测试:
1. 通过 SDK 创建 ObjectType "Device"
2. 为 Device 添加属性 name, status, location
3. 创建 3 个 Device 实例
4. 通过 OQL 查询 status == "running" 的设备
5. 验证返回 2 条结果
这个测试虽然简单,但它串联了 SDK → Control Layer → Data Layer 的完整链路。团队为此庆祝了一下——这是系统第一次"活"了起来。
#3.4 测试策略初定
从一开始,我们就决定走"测试驱动"的路线。每个 Layer 独立维护自己的测试套件:
- 单元测试:覆盖核心业务逻辑
- 集成测试:验证 Layer 内组件协作
- 契约测试:验证 gRPC 接口兼容性
- 端到端测试:跨 Layer 的完整业务流程
Day 90 时,测试用例数量达到 约 400 个。
#4. 第四到第六个月:功能爆发(Day 91 – Day 180)
#4.1 Action Engine
Action Engine 是 coomia-dip 的"执行器"。它允许用户定义 Action(如"审批工单"、"调整库存"),并在执行时进行权限检查、参数校验、副作用管理。
Action Engine 的核心设计理念是 "可逆性":每个 Action 必须定义 apply 和 revert 两个方法。这借鉴了 Saga 模式——在分布式环境中,如果一个操作失败了,系统需要能够回滚已完成的步骤。
#4.2 Rule Engine
Rule Engine 实现了前向链推理。用户可以定义规则(如"当设备温度 > 80°C 且运行时间 > 24h 时,触发维护工单"),系统会在数据变更时自动评估规则并执行相应的 Action。
我们最初尝试了 Drools,但发现它太重了——引入了整个 KIE 生态系统。最终我们自研了一个轻量级的规则引擎,基于 Rete 算法的简化版本。
#4.3 数据管道
数据管道是将外部数据导入 coomia-dip 的通道。我们集成了 DolphinScheduler 作为调度引擎,支持:
- 批量导入(CSV、JSON、Parquet)
- 增量同步(CDC 通过 Debezium)
- 实时流(Kafka Consumer)
#4.4 权限模型
权限模型是所有企业级平台的核心。我们实现了三层权限:
- RBAC(基于角色的访问控制)
- ABAC(基于属性的访问控制)
- 行级权限(基于本体属性的数据过滤)
这三层可以组合使用。例如:"财务部门(RBAC)可以查看(ABAC:只读)本部门(行级:department == user.department)的工单"。
#4.5 测试数量爆发
Day 180 时,测试用例数量达到 约 1800 个。增长最快的是 Action Engine 和 Rule Engine 的测试——因为它们的边界条件极多。
#5. 第七到第九个月:稳定性攻坚(Day 181 – Day 270)
#5.1 性能问题浮现
随着测试数据量增加,性能问题开始暴露:
- OQL 查询在 10 万级数据量下响应超过 5 秒
- Action Engine 的并发执行存在死锁风险
- Rule Engine 的规则评估在复杂规则集下呈指数增长
#5.2 OQL 查询优化
OQL 查询优化是这个阶段最大的技术挑战(详见 S14-09)。核心改进包括:
- 引入查询计划优化器
- 实现谓词下推到存储层
- 添加查询结果缓存
- 优化 JOIN 策略(从嵌套循环改为 Hash Join)
优化后,同样的查询从 5 秒降到了 200ms——25 倍的提升。
#5.3 并发控制
Action Engine 的死锁问题源于多个 Action 同时修改同一个 Object。我们引入了乐观并发控制(OCC):
- 每个 Object 维护版本号
- Action 执行时检查版本号
- 冲突时自动重试(最多 3 次)
#5.4 存储架构变迁
存储架构经历了三次大的变迁:
- v1:PostgreSQL + MinIO(简单,但查询能力有限)
- v2:PostgreSQL + ClickHouse + MinIO(OLAP 性能好,但运维复杂)
- v3:Doris + Iceberg/Nessie + MinIO(统一 OLTP/OLAP,版本化存储)
每次变迁的具体原因和过程,详见 S14-02 和 S14-04。
#5.5 测试持续增长
Day 270 时,测试用例数量达到 约 3500 个。我们开始引入性能测试和压力测试——用 Locust 模拟 1000 并发用户的场景。
#6. 第十到第十二个月:产品化(Day 271 – Day 365)
#6.1 SDK 1.0
Python SDK 是用户与 coomia-dip 交互的主要方式。SDK 1.0 的设计目标是:
- 直觉性:API 命名要让从未用过本体平台的开发者也能猜出用法
- 类型安全:所有 API 都有完整的类型注解,IDE 可以提供自动补全
- 渐进式:简单场景一行代码搞定,复杂场景通过 Builder 模式组合
from ontology_sdk import OntoPlatform
platform = OntoPlatform.connect("localhost:8080")
# 一行代码创建对象
device = platform.objects.create("Device", name="CNC-001", status="running")
# 一行代码查询
running_devices = platform.objects.query("Device").where(status="running").list()
# 一行代码执行操作
platform.actions.execute("MaintenanceCheck", target=device)
#6.2 部署方案
我们提供了三种部署方案:
- 开发模式:
docker compose up,单机启动所有服务 - 测试模式:3 节点 Docker Swarm,模拟分布式环境
- 生产模式:Kubernetes + Helm Chart,支持水平扩展
#6.3 文档体系
文档是产品化的关键。我们建立了完整的文档体系:
- 快速开始(5 分钟跑通)
- API 参考
- 架构指南
- 行业案例
- 系列技术文章(就是您正在读的这个系列)
#6.4 测试里程碑
Day 365 时,测试用例数量达到 约 5200 个。测试通过率维持在 99.5% 以上。
#7. 第十三到第十四个月:打磨与开源(Day 366 – Day 420)
#7.1 AI 辅助开发的引入
从第十三个月开始,我们大规模引入 AI 辅助开发(详见 S14-11)。Claude 被用于:
- 代码审查
- 测试用例生成
- 文档撰写
- Bug 调试
AI 的引入显著加速了测试用例的编写速度——从平均每天 15 个提升到 40 个。
#7.2 开源准备
开源不仅仅是把代码放到 GitHub 上。我们做了大量准备工作:
- 代码审计(移除硬编码密钥、内部 IP)
- 许可证选择(Apache 2.0)
- 贡献者指南
- Issue 模板
- CI/CD 流水线(GitHub Actions)
#7.3 6000+ 测试的意义
截至 Day 420,测试用例数量突破 6000 个。这些测试分布如下:
| 类别 | 数量 | 占比 |
|---|---|---|
| 单元测试 | 3200 | 53% |
| 集成测试 | 1800 | 30% |
| 端到端测试 | 600 | 10% |
| 性能测试 | 250 | 4% |
| 契约测试 | 150 | 3% |
6000+ 测试不仅仅是一个数字。它代表的是:
- 每次提交都有信心不会破坏现有功能
- 重构时有安全网(我们做了 3 次大规模重构,全靠测试兜底)
- 新成员入职时有"可执行的文档"
- 回归 Bug 几乎降到零
#8. 关键里程碑时间线
| 时间 | 里程碑 | 测试数 |
|---|---|---|
| Day 1 | 第一行代码 | 0 |
| Day 30 | 架构设计完成,Protobuf 定义完成 | 50 |
| Day 60 | 第一个端到端测试通过 | 200 |
| Day 90 | Ontology Runtime v1 完成 | 400 |
| Day 120 | Action Engine v1 完成 | 800 |
| Day 150 | Rule Engine v1 完成 | 1200 |
| Day 180 | 数据管道 + 权限模型完成 | 1800 |
| Day 210 | OQL 查询优化完成 | 2400 |
| Day 240 | 存储架构迁移到 Doris | 2800 |
| Day 270 | 性能测试 + 压力测试引入 | 3500 |
| Day 300 | SDK 1.0 Beta | 4200 |
| Day 330 | 部署方案 + 文档体系 | 4800 |
| Day 365 | 首个公测版本发布 | 5200 |
| Day 420 | 6000+ 测试里程碑 | 6000+ |
#9. 最深刻的教训
#9.1 过早优化 vs. 过晚优化
我们在存储架构上犯了"过早优化"的错误——第二个月就引入 ClickHouse,结果发现它在我们的场景下并不比 Doris 好,反而增加了运维复杂度。但在 OQL 查询优化上,我们又犯了"过晚优化"的错误——等到用户抱怨才开始优化,修复成本远高于在设计阶段就考虑性能。
教训:先让它工作,再让它正确,最后让它快速。但"最后"不意味着"直到用户投诉"。
#9.2 测试是投资,不是成本
每次有人提议"先跳过测试赶功能"时,我们都坚持写测试。这在短期内确实拖慢了交付速度,但在第七个月性能攻坚期,这些测试救了我们——它们让我们能够大胆重构而不怕破坏功能。
教训:测试的 ROI 在第六个月后开始显现,之后呈指数增长。
#9.3 4 个人的沟通成本被低估了
虽然 4 个人的团队很精简,但当每个人负责不同的 Layer 时,接口变更的沟通成本远超预期。我们后来引入了 Protobuf 作为"接口契约",任何接口变更必须先提 PR 修改 .proto 文件,其他人审查通过后才能实施。
教训:人少不代表不需要流程。
#10. 写在最后
从第 1 行代码到 6000+ 测试,coomia-dip 的开发之旅远比这篇文章能描述的更加曲折。我们经历了技术方案的反复推翻、深夜的 Bug 调试、性能瓶颈的焦虑,也享受过每一个"跑通了!"的瞬间。
这个系列的后续文章将深入展开每一个关键决策和技术挑战。如果你正在做类似的事情——构建一个复杂的平台级产品——希望我们的经验能帮助你少走一些弯路。
#Key Takeaways
- 架构设计要留有余地:分层架构在初期很理想,但在团队规模和开发效率之间需要平衡
- 测试是最好的投资:6000+ 测试让团队有信心进行大规模重构
- 小团队也需要流程:4 个人的团队也需要接口契约和代码审查
- 存储选型要谨慎:每次存储架构变迁的成本都比预期高 3-5 倍
- 性能优化要有计划:不要等到用户投诉,也不要过早优化
#Next Article
下一篇:S14-02 为什么放弃 ClickHouse — 我们将详细讲述存储选型中的 ClickHouse 之殇,以及为什么 Doris 最终胜出。
Tags: #coomia-dip #开发日记 #架构设计 #测试策略 #工程实录 #Palantir替代