晚上睡不着,起来打点字。

最近我有一个非常深刻,非常深刻,非常深刻,可以说是基本哲学上的learning。

程序员老说自己天天CRUD,拧螺丝工程师,但是程序员真的吃透了CRUD了吗,我最近在这上面连续翻了两次跟头。

我们都知道,或者都喜欢immutable,最好一个东西创建出来就不要改了。为什么呢,因为很多时候我们有出了线上问题要debug的压力,要有GDPR和法律合规性的要求,最好一些关键数据,能够跟踪回溯它修改变化的全过程,那么这样最简单的方式就是创建了以后不再修改它。

所以在这种情况下,CRUD中的U会被先D后C给取代。你要修改嘛,可以,只能先删除旧的再创建新的,其实删除也不是真的删除,只是把它标记成删除,那么这样旧的新的都在,没有少任何东西,最方便跟踪回溯了。如果要正儿八经的实现U,那么势必需要额外的effort把变化给保存下来,也会导致跟踪回溯很麻烦,效果又和先D后C一样,那所以还不如先D后C。

但我的learning是U是CRUD中一定要实现的,只有完整实现CRUD,一个东西才算是对外界暴露了完整的基本功能,才能算是完备的。不完备的东西本身就是有漏洞的。

这个漏洞在于,其实先D后C根本不能取代U,因为这两者完全是不一样的概念。

举个例子有一个东西叫A,虽然你看它只是一个东西A,但是其实计算机世界的数据结构根本没有这么简单的,A可能有100个属性,其中有些属性可能引用了B,而B也可能是另一个什么比较大的东西。

此时如果说我要Create A,其实意味着把A的所有属性都从无到有创建出来,这样A也就创建出来了。

此时如果说我要Update A,其实很多情况下意味着我并不是要Update A的所有100个属性,而是只要Update A里面的几个属性而已。

所以可见Create是整体,而Update是部分,你现在要我先D再C,而我其实要的只是U,意味着我为了U承担了C的职责,这是不对的,因为我对我要U的那一部分属性是知道的,对不是U但是要C的那一部分属性我是不知道的。

因此对于A的客户或者使用者来说是无法完成这样的操作的,因此A肯定会提供一个叫做U的方法,而这个U方法里面其实干的是先D后C的勾当。但是作为A的客户的我来说其实认为是U,并不知道这里面A的具体实现。

那么这种概念和实现上的不对等就会对A的U方法的实现造成困扰,因为这意味着A的U方法必须实现为一个深拷贝。比如说A引用了B,那么A的U方法必须先把A标记为D,然后C出A‘,这是一个深拷贝的过程,包含了深拷贝B从A到A'。那么如果其实B它是一个创建出来以后就不需要再修改的东西,并且B很大的话(比如是你每次打开浏览器访问网页的历史数据),深拷贝它就造成了严重的数据duplicate。现在想象一下你有几十万个A,那么B的数量更大,这种深拷贝就完全无法接受了。

那么这种概念和实现上的不对等也会对A的客户造成很大的困扰,比如说一个实际例子是A里面有一个属性是计数器,每次对A调用方法F,计数器就会加一。此时A的客户Update了A,并没想要update计数器,突然发现A的计数器变成了0,因为A每次先D再C后,它重新create,它在构造函数里计数器就自动设成0了。实际的例子里还有如果A引用了B,并且B的状态是依赖于A的这个计数器属性的值的,客户需要查询A里面的B,那么就会很混乱。

那么这种概念和实现上的不对等还会造成理解A本身的概念的麻烦。比如说A引用了B,但其实A并不希望B被Update,那么如果A把B hide在A内部其实是不对的,A应该把B视为和它平级的东西,尽管A可以引用B。B也应该暴露给客户,而不是只暴露A给客户,这个也有一个实际的例子,结果就是客户不知道B的存在而对A的U方法的行为发生了误解。

总是,用先D再C的方法去实现U,其实并没有真正的实现U,当系统规模越来越大的时候,这就会成为一个瓶颈,越来越难维护,最后会陷入两难的境地。

所以一个基本的结论就是,无论如何,必须完整实现CRUD,在这点上不能有任何妥协。如果需要数据的完整性可追踪性,那么像现在就是数据库里加audit表,每次数据库表有修改的时候就有一个trigger来把修改的东西插入audit表,这样或许比较麻烦,给数据追踪带来了effort,但是未完整实现CRUD是更为根本的要解决的问题,是除了完整实现CRUD并没有其他办法可以解决的,而数据可追踪性和完整性是可以有其他手段去做的。

我们做架构,其实很多时候就是在判断哪些东西或者概念是更为根本的,哪些是根本的会产生深远影响的并且只有华山一条路的,哪些虽然也很重要但是条条大道可以通罗马;有些时候也必须take 一些 effort高的路径,因为选择被一些更为根本的东西限制住了。

上一篇 下一篇