触发器设计的一些思考

发布时间 2023-04-28 01:25:27作者: 平静寄居者

触发器这东西貌似N年(大约90年代)很流行。比如那时候用Oracle Forms做开发的,大量的业务逻辑都放在触发器里。后来好像就少用了,最多的是用来审计,比如在数据库里放个历史记录表,每次原表数据有变化时,就在触发器里,将改动前后的数据复制到历史记录表里,便于以后的审计或者排错。

为什么现在少用触发器了?对这个问题没有深入的研究和认识。好像有人说触发器比较tricky,容易出问题。另外一个问题我想是业务逻辑放在触发器里,加重了数据库服务器的负担。

没想到现在用Salesforce,又是大量的触发器。原因大概是因为Salesforce可以直接在界面上修改数据,相当于直接对数据库操作;另外有多种手段修改数据,除了apex,还可以通过flow和lwc等。这么多输入方式无法控制数据更新时的行为,只能在触发器里定义逻辑。比如asp.net里,只能通过服务端的api来更新数据,所以可以在api里定义数据更新时的逻辑,就不需要用触发器,而直接对数据库操作,就只能在触发器里定义逻辑了。

Salesforce的这个设计不是很好。另外一个问题是有触发器功能的组件太多,除了触发器,还有record triggered flow,workflow rule, process explorer等,容易造成冲突。好像workbench里有个工具可以列出作用于每个对象的所有触发器(包括flow,workflow rule等),这个需要协调以避免冲突。

另外,昨天碰到一个问题,另外一个开发员说他写的代码出错,追踪下来,是我写的触发器报错,从出错信息来看,是权限问题,我的类是with sharing,而他是个community user,权限不够,访问某个SObject时出错。解决方法貌似很简单,改成without sharing或者去掉with sharing(这样权限就从他那里继承过来,而他是从without sharing里调用的),但是不好,因为with sharing是缺省设置,如果以后增加新的触发器,还得修改,而且对我的类来说,without sharing没有必要,从逻辑上来说不合理。

开始的思路是不处理他那种类型的数据,但问题是如何过滤掉?类型这个字段碰巧是个Lookup字段,因为是before insert触发器,只有Id可用,但Id又不能硬编码写死。比如说foo这个字段是判断类型的,要拿到Id,只能先SELECT Id FROM Foo WHERE Name = 'bar' 问题是community user没有访问Foo对象的权限。

考虑把这句soql放到trigger里,因为trigger缺省是运行在system context的。但是查了一下,说如果触发器调用with sharing的类,那么触发器也相当于有with sharing的限制了,所以行不通。(https://medium.com/elevate-salesforce/things-to-know-about-apex-triggers-part-1-e0ffd10ddd49)但是实际测试了一下,却证明是可以的。所以结论是:刚进入触发器时,默认是without sharing的,如果在触发器中调用了with sharing的类,那么进入类之后就是with sharing了,而不是整个过程都变成without sharing了。

另外一个考虑是,以前一般说不要把程序逻辑放在触发器。但是从这个例子来看,先在触发器里过滤数据也有它的好处。主要是如果触发器有多个类处理的话,如果每个类都只处理一部分类型的数据,有时可以考虑把过滤的代码放在触发器里。当然,这样相当于把业务逻辑放在了两个地方,不太好。但是,除了解决前述的权限问题外,另外一个好处是如果以后增加新的触发器处理类,或者增加新的触发条件,不会和已有的逻辑冲突。还有的好处是在触发器里一次性过滤完毕,相当于一个分发器,这样不需要把整个数据放到不同的类里一遍遍的筛选。

不过,如前所说,触发器容易出错,所以设计上似乎应该尽量避免,如果能保证只有一个途径更新数据,就不要考虑用触发器来做。