【业务与技术架构】从胖 Service 到清晰架构:用 DDD 看业务,用 COLA 放代码

发布时间:2026/7/10 2:54:18
【业务与技术架构】从胖 Service 到清晰架构:用 DDD 看业务,用 COLA 放代码 文章目录一、从 ChatBI 大泥球看 DDD 与 COLA 的分工二、DDD 与 COLA 的关系业务治理地图与代码货架三、ChatBI 案例从胖 Service 到 DDD COLA四、回到架构思维控制变化而不是堆架构结语ChatBI 系统一开始通常很简单用户输入一个问题系统把问题交给大模型生成 SQL查询数据库再把结果返回给用户。这个流程看起来像一条直线所以很多项目最初会把它写在一个ChatServiceImpl里。后来需求越来越多要识别指标口径要做权限校验要选择数据集要改写用户问题要生成 SQL要校验 SQL要查数仓要解释图表要记录会话还要做安全审计。于是一个 ChatBI 服务很快变成这样接收用户问题 识别意图 选择数据集 解析指标口径 拼 Prompt 调用大模型 生成 SQL 校验 SQL 检查权限 执行查询 生成图表 组织自然语言回答 记录会话 埋点审计代码还能跑但系统已经开始变重。问题不只是方法太长而是几类完全不同的复杂度被塞到一起了。ChatBI 的复杂度至少有四类入口复杂度Web、API、消息、插件入口 流程复杂度一次问答要经过多个步骤 业务复杂度指标、口径、权限、数据集、问答语义 技术复杂度LLM、向量库、数仓、缓存、审计、消息如果这些都挤在一个ChatServiceImpl里任何变化都会牵动它模型换了要改指标口径变了要改权限规则变了要改SQL 引擎换了也要改。这就是架构开始失控的地方。DDD 和 COLA 的价值要从这里理解DDD 解决ChatBI 的业务概念和规则怎么建模。 COLA 解决这些模型、流程和技术实现应该放在哪里。先看清业务再安放代码。一、从 ChatBI 大泥球看 DDD 与 COLA 的分工ChatBI 最容易写成一个“智能问答大方法”。比如public ChatAnswerDTO ask(ChatRequest request) { User user userService.getUser(request.getUserId()); DatasetDTO dataset datasetService.matchDataset(request.getQuestion()); if (!permissionService.canAccess(user, dataset)) { throw new BizException(无数据集权限); } MetricDTO metric metricService.resolveMetric(request.getQuestion(), dataset); String prompt promptBuilder.build(request.getQuestion(), dataset, metric); String sql llmClient.generateSql(prompt); sqlValidator.validate(sql); ListMapString, Object rows warehouseClient.query(sql); ChartDTO chart chartService.buildChart(request.getQuestion(), rows); String summary llmClient.summarize(request.getQuestion(), rows); chatRecordMapper.insert(request, sql, rows, summary); auditService.record(user, dataset, sql); return new ChatAnswerDTO(summary, chart); }这段代码表面上是在回答一个问题实际上混了很多职责用户入口处理 问答流程编排 指标和口径识别 数据集权限判断 Prompt 构造 LLM 调用 SQL 校验 数仓查询 图表生成 会话记录 审计记录真正的问题不是“代码长”而是这些职责的变化来源不同。权限规则变化不应该影响 LLM 调用代码。指标口径变化不应该影响 Controller。模型供应商变化不应该影响 ChatBI 的领域规则。数仓查询方式变化不应该影响问答语义建模。DDD 首先帮我们看清这里哪些是 ChatBI 的业务概念。在 ChatBI 里真正重要的业务概念可能不是Request、Response、Mapper而是Question用户问题 Conversation会话 Dataset数据集 Metric指标 Dimension维度 SemanticModel语义模型 QueryIntent查询意图 SqlDraft待校验 SQL Answer回答 DataPermission数据权限这些概念不是为了“显得领域化”而是因为业务人员、数据产品、数据开发都会围绕它们沟通。DDD 要问的是用户问题如何被理解 指标口径由谁决定 数据集选择规则是什么 权限检查基于用户、数据集还是字段级权限 SQL 生成前后分别要满足什么规则 回答结果是否属于一次会话的一部分这些问题的目标是把 ChatBI 的核心业务规则从技术流程里分离出来。COLA 接着处理代码安放问题。它把代码按职责放到不同层Adapter接收外部请求 App编排一次 ChatBI 问答用例 Domain表达 ChatBI 的核心业务概念和规则 Infrastructure适配 LLM、数仓、向量库、权限系统、审计系统 Client定义对外命令、查询、DTO所以二者分工很清楚DDD 帮你识别 ChatBI 的业务模型。 COLA 帮你把入口、流程、规则、技术实现分开放。二、DDD 与 COLA 的关系业务治理地图与代码货架DDD 和 COLA 都会提到domain但它们不是一回事。DDD 是业务建模方法。它关心的是 ChatBI 世界里到底有哪些稳定概念以及规则属于谁。比如什么是指标 什么是维度 什么是语义模型 什么叫用户有权限问这个问题 什么叫 SQL 安全 什么叫一个回答可信COLA 是应用架构。它关心的是这些概念和规则写出来后应该放在哪一层。adapter 放 HTTP、RPC、消息入口 app 放 AskQuestionAppService 这类用例编排 domain 放 Question、Metric、Dataset、SemanticModel 等领域对象 infrastructure 放 LLM、数仓、向量库、审计、外部权限系统适配 client 放 AskQuestionCmd、ChatAnswerDTO 等契约所以DDD 不等于 COLA。你可以用 DDD 建模 ChatBI但不用 COLA。你也可以套了 COLA 目录但 ChatBI 逻辑仍然贫血。二者更准确的关系是DDD 是业务地图COLA 是代码货架。业务地图回答ChatBI 里有哪些关键业务概念 一次问答的规则属于谁 哪些概念属于语义层哪些属于查询执行层 权限、指标、SQL 安全的边界在哪里代码货架回答入口放哪里 用例编排放哪里 业务规则放哪里 LLM 和数仓调用放哪里 对外 DTO 放哪里比如 DDD 识别出这些领域概念Question QueryIntent SemanticModel Metric Dataset DataPermission SqlDraft AnswerCOLA 可以这样承载domain/chat/Question.java domain/chat/Conversation.java domain/semantic/SemanticModel.java domain/semantic/Metric.java domain/dataset/Dataset.java domain/security/DataPermission.java domain/query/SqlDraft.java domain/answer/Answer.java一次问答用例放在app/AskQuestionAppService.java外部入口放在adapter/web/ChatController.java技术实现放在infrastructure/llm/LlmGatewayImpl.java infrastructure/warehouse/WarehouseGatewayImpl.java infrastructure/vector/VectorSearchGatewayImpl.java infrastructure/audit/ChatAuditGatewayImpl.java如果只是把ChatServiceImpl挪到domain/chat/ChatService但里面仍然直接调 LLM、拼 Prompt、查数仓、写审计那不是架构升级只是换目录。判断 COLA 有没有真正发挥作用看三个问题ChatBI 的业务规则是不是进入了 Domain LLM、数仓、向量库是不是隔离在 Infrastructure App 层是不是只编排流程而不是吞掉所有逻辑把 DDD 和 COLA 合起来可以形成一个顺序先用 DDD 问这是 ChatBI 的业务概念吗 再用 DDD 问这条规则属于哪个业务对象 然后用 COLA 问这段代码应该放在哪一层 最后用 COLA 问依赖方向有没有反过来这比直接照抄目录更重要。三、ChatBI 案例从胖 Service 到 DDD COLA我们用“用户提问生成数据分析回答”作为例子。需求是用户输入自然语言问题。 系统识别问题意图。 选择可用数据集。 解析指标和维度。 检查数据权限。 生成 SQL。 校验 SQL 安全性。 执行查询。 生成图表和文字解释。 保存会话记录。如果写成一个胖 Service短期最快。但这个场景的复杂度会持续增长所以必须先区分业务规则和技术实现。DDD 先看业务。用户问“最近 30 天华东区 GMV 环比变化怎么样”系统至少要理解最近 30 天时间范围 华东区筛选条件 GMV指标 环比变化分析意图 怎么样回答形式这不是简单字符串处理而是 ChatBI 的核心业务。因此可以建模为Question用户原始问题 QueryIntent查询意图 Metric指标 Dimension维度 TimeRange时间范围 Dataset数据集 SemanticModel语义模型 DataPermission数据权限 SqlDraft待校验 SQL Answer最终回答其中Metric不是数据库字段的别名。它应该表达业务口径。比如 GMV指标名GMV 业务含义成交总额 计算口径sum(pay_amount) 可用数据集order_summary 默认时间字段pay_time 权限约束按组织和数据域过滤这就是 DDD 的价值把“GMV”从一个 SQL 片段提升为业务概念。接着Domain 不应该直接依赖大模型、数仓或向量库。它定义自己需要的能力public interface IntentGateway { QueryIntent recognize(Question question, SemanticModel semanticModel); } public interface SqlGenerationGateway { SqlDraft generate(QueryPlan queryPlan); } public interface WarehouseGateway { QueryResult query(SqlDraft sql); }这些接口放在 Domain是因为 Domain 要表达“我需要识别意图、生成 SQL、查询数据”。具体用哪个模型、哪个数仓、哪个 SDK是 Infrastructure 的事。COLA 之后可以这样组织client AskQuestionCmd ChatAnswerDTO adapter ChatController app AskQuestionAppService domain chat Question Conversation Answer semantic SemanticModel Metric Dimension TimeRange dataset Dataset DatasetSelector security DataPermission PermissionPolicy query QueryIntent QueryPlan SqlDraft SqlSafetyPolicy gateway IntentGateway SqlGenerationGateway WarehouseGateway AuditGateway infrastructure llm IntentGatewayImpl SqlGenerationGatewayImpl warehouse WarehouseGatewayImpl audit AuditGatewayImpl repository ConversationRepositoryImplApp 层只编排一次问答流程public ChatAnswerDTO ask(AskQuestionCmd cmd) { Question question Question.of(cmd.getQuestion()); UserId userId new UserId(cmd.getUserId()); SemanticModel model semanticModelRepository.getDefault(); QueryIntent intent intentGateway.recognize(question, model); Dataset dataset datasetSelector.select(intent, model); permissionPolicy.check(userId, dataset, intent); QueryPlan plan queryPlanner.plan(intent, dataset, model); SqlDraft sql sqlGenerationGateway.generate(plan); sqlSafetyPolicy.check(sql); QueryResult result warehouseGateway.query(sql); Answer answer answerFactory.create(question, intent, result); conversationRepository.save(question, answer); auditGateway.record(userId, dataset, sql); return chatAnswerAssembler.toDTO(answer); }这段代码仍然能看出完整流程但它不承载所有规则。比如 SQL 安全规则可以放在 Domainpublic class SqlSafetyPolicy { public void check(SqlDraft sql) { if (!sql.isSelectOnly()) { throw new BizException(只允许查询语句); } if (sql.hasUnsafeFunction()) { throw new BizException(SQL 包含高风险函数); } if (!sql.hasLimit()) { throw new BizException(查询必须限制返回行数); } } }指标口径也可以是领域对象public class Metric { private final MetricCode code; private final String expression; private final DatasetId datasetId; public boolean belongsTo(Dataset dataset) { return this.datasetId.equals(dataset.id()); } }LLM 调用留在 Infrastructurepublic class SqlGenerationGatewayImpl implements SqlGenerationGateway { public SqlDraft generate(QueryPlan plan) { String prompt promptTemplate.render(plan); String sql llmClient.generate(prompt); return SqlDraft.of(sql); } }这里的重点是Prompt、模型 SDK、供应商参数没有进入 Domain。Domain 只知道自己需要一个SqlDraft。这样拆完后变化会被隔离。模型从 OpenAI 换成通义千问主要改SqlGenerationGatewayImpl IntentGatewayImplGMV 口径变化主要改Metric SemanticModelSQL 安全规则变化主要改SqlSafetyPolicy数仓从 Hive 换成 ClickHouse主要改WarehouseGatewayImpl前端接口参数变化主要改ChatController AskQuestionCmd这就是 DDD COLA 的组合价值业务规则回到业务模型技术变化关进基础设施层。四、回到架构思维控制变化而不是堆架构学 DDD 和 COLA最终不是为了背概念而是训练判断力。第一步是分类。看到 ChatBI 里一段代码先问它属于哪类职责它是在接收外部请求吗 它是在编排一次问答流程吗 它是在表达指标、权限、SQL 安全等业务规则吗 它是在调用 LLM、数仓、向量库、审计系统吗对应到 COLAAdapter入口 App流程 Domain规则 Infrastructure技术实现如果 Controller 里开始拼 Prompt这是分类错误。如果 Domain 里直接调用 LLM SDK这是分类错误。如果 AppService 里写满指标口径判断这是分类错误。如果 Infrastructure 决定“用户能不能看这个指标”也是分类错误。第二步是建模。ChatBI 里最容易混淆的是把技术对象当业务对象。ChatRequest不是领域模型。SqlDTO不是领域模型。TableDO不是领域模型。PromptTemplate也不一定是领域模型。真正值得建模的是那些稳定的业务概念指标 维度 数据集 语义模型 查询意图 数据权限 SQL 安全策略 会话 回答判断标准很简单业务人员会不会说这个词 数据产品会不会维护它 它有没有规则 它是否会因为业务变化而变化如果答案是肯定的它才值得进入 Domain。第三步是划边界。ChatBI 的变化来源很多边界不清就会迅速失控。入口变化挡在 Adapter。 问答流程变化挡在 App。 指标、权限、语义、SQL 安全规则变化挡在 Domain。 LLM、数仓、向量库、审计系统变化挡在 Infrastructure。比如模型供应商变化不应该影响指标口径。数仓 SQL 方言变化不应该影响用户问题建模。权限系统接口变化不应该影响Question和Metric。前端展示变化不应该影响 SQL 安全策略。边界的意义不是让文件变多而是让变化停在该停的位置。第四步是保持克制。不是所有 ChatBI 能力都要完整 DDD。如果只是一个内部 Demo用户输入问题直接拼 Prompt生成 SQL查库返回结果用简单结构就够了。但如果系统已经出现这些特征就值得引入 DDD COLA指标口径经常变化 权限规则复杂 数据集很多 多模型、多数仓并存 SQL 安全要求高 问答链路需要审计 多个团队共同维护语义层反过来如果只是简单报表查询、一次性验证、短期 PoC就不要硬造聚合、领域事件和复杂网关。架构不是越重越好。成熟的判断是复杂度在哪里就在哪里建模。 变化从哪里来就在哪里设边界。 没有复杂度就不要制造复杂度。结语ChatBI 不是简单的“调用大模型生成 SQL”。真正难的是把自然语言、指标口径、语义模型、数据权限、SQL 安全和查询执行组织成一个可演进的系统。DDD 和 COLA 合在一起给出了一条清晰路径用 DDD 识别 ChatBI 的业务概念。 用 DDD 判断规则属于谁。 用 COLA 安放入口、流程、规则和技术实现。 用 COLA 控制依赖方向。一句话概括用 DDD 看清 ChatBI 的业务用 COLA 放好 ChatBI 的代码。好的架构不是目录漂亮也不是概念堆满而是变化发生时你知道该改哪里也知道哪里不该被影响。