微服务不是简单拆分Web API,而是需满足松耦合、独立部署、业务边界拆分、进程隔离与数据自治;C#实现依赖架构决策与工具链,禁用共享数据库、直接引用和硬编码配置。

微服务不是用 C# 写几个 Web API 就算数
很多人以为把 Program.cs 拆成多个 WebApplication.CreateBuilder 项目,再起几个不同端口,就完成了微服务——这其实是分布式单体(distributed monolith)。真正微服务的核心是**松耦合、独立部署、按业务边界拆分、进程隔离、数据自治**。C# 本身不提供“微服务语法”,它靠的是架构决策 + 工具链 + 约束机制来落地。
关键判断:如果你的多个 C# 服务共享数据库表、通过直接引用对方的 ClassLibrary、或一个服务启动失败导致其他服务无法响应健康检查,那还没进入微服务阶段。
服务间通信必须绕过直接引用,优先选 HTTP + REST 或 gRPC
常见错误是让订单服务直接 new 一个 InventoryServiceClient 并调用其 public 方法——这本质是紧耦合,编译期强依赖,版本升级一崩全崩。
- 用
HttpClient调用 REST 接口时,必须封装为独立客户端类(如InventoryApiClient),并注入IHttpClientFactory管理生命周期;避免在 Controller 里直接 newHttpClient - 跨服务调用高频、低延迟场景(如内部网关到下游服务),用 gRPC 更合适:定义
.proto文件,生成强类型 C# 客户端/服务端代码,天然支持流式、超时、取消令牌 - 绝对不要在服务 A 的代码里引用服务 B 的项目或 NuGet 包(除非是共享的 DTO 库,且该库仅含
record或class,不含逻辑) - 同步调用要设硬超时(如
new TimeSpan(0, 0, 2)),否则一个慢服务会拖垮整个调用链
服务发现与配置不能靠 appsettings.json 硬编码
本地开发时写死 "InventoryUrl": "https://localhost:5001" 没问题,但上线后必须动态获取。否则加个新实例、切个集群,就得改所有服务的配置再重启。
推荐组合:
- 服务注册发现:用
Consul或ETCD(Kubernetes 原生用kube-dns+Service对象,无需额外组件) - 配置中心:用
Microsoft.Extensions.Configuration.AzureAppConfiguration或Consul KV,而非只依赖appsettings.Production.json - C# 侧集成:在
Program.cs中用builder.Services.AddConsulServiceDiscovery()(需自行封装或用社区库如DotNetCore.Cap的扩展) - 务必启用健康检查端点(
MapHealthChecks("/health")),并让服务发现组件定期探测,自动剔除失联实例
每个微服务必须拥有自己的数据库,且禁止跨库 JOIN
这是最容易被忽视也最致命的一点。看到“用户信息要和订单一起查”,第一反应不该是 INNER JOIN users ON orders.user_id = users.id,而应是:“订单服务能不能缓存必要用户字段?或者前端发起两次请求?”
原因很实际:
- SQL Server 的 Linked Server 或 PostgreSQL 的 dblink 性能差、故障面大,且违反数据自治原则
- 一个服务的数据库迁移(如加字段)不能影响其他服务,否则上线节奏被锁死
- 备份、扩缩容、读写分离都得按服务粒度独立操作
- 若真需最终一致性,用事件驱动:订单创建后发
OrderCreatedEvent到RabbitMQ或Azure Service Bus,用户服务监听并更新本地order_summary表
复杂点在于事件幂等性处理和补偿事务设计——比如库存扣减失败,得触发 OrderCancelledEvent 回滚已发通知。这部分没有银弹,必须按业务语义写具体逻辑,不能指望框架自动搞定。


















