为什么我选择 .NET 9 + CQRS 做 AI 微服务

技术选型不是追热点,而是匹配团队、业务边界和未来维护成本。AI 服务看起来很新,但真正决定它能不能长期跑起来的,仍然是工程结构是否清楚、问题是否可定位、团队是否能接住。

选型背景

我们在做企业 AI 智能问答和 Multi-Agent 编排时,最早遇到的问题不是模型调用,而是工程边界。一个 AI 请求可能要经过用户鉴权、知识库检索、Prompt 组装、模型调用、工具执行、结果评估、日志追踪和人工兜底。每一步看起来都不复杂,但串起来之后,系统很容易变成一条难以维护的长链路。

这也是我没有把 AI 能力简单塞进原有业务系统的原因。AI 服务需要独立演进,同时又不能脱离现有会员、订单、客服、权限这些业务能力。它更像一个“业务中台旁边的智能决策层”,既要能快速实验,也要能稳定上线。

AI 微服务的核心不是“调用大模型”,而是把不确定的智能能力放进可控的工程结构里。

为什么是 .NET 9

我选择 .NET 9,首先不是因为它新,而是因为它和团队能力匹配。团队长期使用 C# 和 .NET 技术栈,业务系统、后台管理、数据接口都已经围绕这套生态沉淀了很多经验。AI 服务如果强行换一套完全陌生的技术栈,短期看很“先进”,长期看会增加招聘、排障、交接和运维成本。

对企业内部系统来说,工程稳定性比语言流行度更重要。.NET 9 在 Web API、依赖注入、配置管理、异步编程、日志、OpenTelemetry、容器化部署等方面已经非常成熟,足够支撑 AI 服务的核心需求。

  • 性能足够:AI 服务的瓶颈通常不在 Web 框架,而在模型响应、检索、外部工具调用和网络延迟。
  • 类型系统清晰:对复杂请求、工具参数、评估结果、审计日志非常友好。
  • 团队接得住:线上问题出现时,不需要先跨过技术栈理解成本。
  • 生态完整:认证、配置、日志、监控、消息队列、数据库访问都有稳定方案。

为什么加 CQRS

CQRS 的价值,在这个项目里不是为了“架构好看”,而是为了把读和写的复杂度分开。AI 系统里有大量查询型操作:查知识库、查会员信息、查订单、查历史对话、查规则。也有一些写入型操作:保存会话、记录评估、创建工单、写入反馈、沉淀样本。

如果所有逻辑都堆在一个服务类里,很快就会出现三类问题:查询越来越重,写入副作用越来越多,测试越来越难写。CQRS 帮我把“我要读取什么”和“我要改变什么”拆成两个方向,代码结构更容易被团队理解。

Query
负责读取知识、会话、会员、订单、配置等上下文,尽量做到无副作用、可缓存、可追踪。
Command
负责保存会话、写入反馈、创建工单、记录评估结果,每个动作都有明确业务含义。
Handler
把每个请求收敛到独立处理器里,减少大 Service 类,便于单元测试和灰度替换。
Pipeline
统一处理日志、鉴权、参数校验、异常、耗时统计和链路追踪,避免横切逻辑散落各处。

AI 微服务的边界

我不建议把 AI 微服务设计成一个什么都管的“超级智能层”。它的职责应该很清楚:负责智能问答、意图识别、RAG 编排、工具选择、结果评估和对话记忆。至于订单状态、会员等级、售后流程这些核心业务事实,仍然由原有业务系统提供。

这样做有两个好处。第一,AI 服务不会复制业务系统的数据和规则,避免出现“两个真相源”。第二,业务系统也不会被 AI 实验节奏拖着走,双方通过稳定 API 协作。

能力 归属 原因
会员、订单、工单数据 业务系统 这些是核心事实源,不能由 AI 服务维护副本。
知识检索与 Prompt 组装 AI 微服务 变化频繁,适合独立实验、调参和灰度。
工具调用编排 AI 微服务 需要根据意图动态选择工具,并记录完整调用链。
权限与审计 双方协同 业务系统提供权限边界,AI 服务负责请求级审计。

落地结构

第一版我会把项目拆成几个清楚的模块,而不是一开始就上很重的分布式架构。微服务不是文件夹越多越好,而是边界越清楚越好。

AiService.Api
AiService.Application
AiService.Domain
AiService.Infrastructure
AiService.Workers

Api 负责对外接口,Application 放 CQRS Handler 和业务用例,Domain 放领域对象和规则,Infrastructure 接数据库、向量库、模型供应商和外部业务 API,Workers 处理异步评估、对话摘要、样本沉淀等后台任务。

这个拆法的关键是:让每个模块知道自己负责什么,也知道自己不负责什么。尤其 AI 项目很容易把 Prompt、检索、业务规则和异常处理混在一起,越早建立边界,后面越少返工。

可观测性比想象中重要

AI 服务上线后,最难排查的问题往往不是“接口报错”,而是“回答不对”。这类问题必须能回放完整链路:用户问了什么,识别到什么意图,召回了哪些文档,拼出了什么 Prompt,调用了哪个模型,工具返回了什么,最终为什么这样回答。

所以我会把可观测性放到第一版,而不是等线上出问题再补。

  • TraceId:贯穿一次 AI 请求的所有步骤。
  • Prompt 版本:每次回答都记录使用的 Prompt 模板和版本。
  • 检索证据:记录召回文档、分数、来源和截断后的上下文。
  • 工具调用:记录工具名、参数、耗时、结果和失败原因。
  • 评估结果:把用户反馈、自动评分、转人工状态沉淀下来。
架构取舍
AI 服务不是越智能越好,而是越可解释、可回滚、可定位越好。企业系统里,黑盒能力必须被工程日志和流程边界包住。

没有选择的方案

技术选型的另一半,是说清楚为什么没有选别的方案。我并不是认为 Python、Node.js 或 Go 不适合 AI 服务,而是它们在这个具体场景下不是最优解。

方案 优点 没有选择的原因
Python FastAPI AI 生态丰富,原型速度快。 团队主力不是 Python,长期维护和业务系统整合成本更高。
Node.js 接口开发快,前后端协作轻。 强类型和复杂业务建模不是团队最熟悉的路径。
直接写进主业务系统 短期接入最快。 AI 迭代频率高,容易污染主系统边界,灰度和回滚成本高。

总结

我选择 .NET 9 + CQRS,不是为了证明某个技术栈更高级,而是因为它在这个场景里能平衡三件事:团队能维护、业务边界清楚、AI 能力可以独立演进。

对 AI 微服务来说,模型只是系统的一部分。真正决定项目能不能走远的,是工程结构、日志、评估、回滚、权限和团队协作方式。技术选型最终要回答的不是“流不流行”,而是“半年后线上出问题时,团队能不能快速定位并修好”。

欢迎通过邮件和我交流:shaoyanyan91@163.com


本文首发于邵炎炎个人博客。转载请注明出处。