翻这套工业数据 WebApi 的源码,最让我舒服的一处设计,是它把"图纸"和"施工"分得很干净。
每个能力在框架里都拆成两份:一份 Define,只放接口,规定"能干什么";一份 Runtime 或 Engine,放实现,决定"怎么干"。业务代码从头到尾只认 Define 那一层的接口,从不去碰具体实现里用的是哪套 ORM、哪个协议、哪类数据库。
(等价改写,名字换掉了)典型的契约长这样:
// 图纸:只定义能力,不绑定任何框架
public interface IDataPointProvider
{
Task<IReadOnlyList<DataPoint>> ReadAsync(string tag);
}
// 施工:实现可以换数据库、换协议,业务看不见
public class KdbRepository : IDataPointProvider { /* 某国产数据库 */ }
它怎么被用上的
这种分离在平台里不是摆设。像 RepositoryProvider 这个数据契约,被全仓 94 处引用,但引用方全都指着接口,没有一处直接 new 了具体的库实现。换句话说,业务描述的是"我要读一个点",而不是"我要用某国产数据库读这个点"。
这正是端口-适配器思路的落点:业务站在端口这头,只对着契约说话;适配器(具体实现)在另一头,想怎么换都行。框架升级、库换了版本,只要适配器还认那张图纸,业务这头连通知都不用收到。
好处是具体的
业务不依赖框架,换来的是换技术时不用动业务。某天要把底层存储从一种库迁到另一种,只要新实现还满足那张图纸,上层一行都不用改。契约成了稳定的边界,框架选型、版本升级都被挡在这条线之外。
我翻代码时特意追了一条链路:一个采集任务从调度进来,到真正落库,中途过了好几道读写,但没有一处直接依赖具体数据库类型。它只认 IRepositoryProvider 这类图纸。这套系统把"用什么"和"做什么"彻底错开了。
我没法确认当年是不是一开始就想清楚了这一层,但站在这套两百多个工程、约 59 万行 C# 的体量上看,把"图纸"留稳、把"施工"放开,是最值的一笔账。后来我在评审别人设计时,也先看图纸和施工有没有混在一间房里——混了的,早晚出事。