本文以“合同”为例,来探讨特定应用场景的程序设计方案。
合同数据包含生效日 和 到期日。
到了合同的到期日,合同涉及的业务就不能再做了。
这是企业应用系统中常见的应用场景。
即:
- 业务发生时,要判断对应的合同是否有效
- 合同到期时,要标记为“已到期”
我们先定义合同的数据结构:
| 名称 | 类型 | 说明 |
|---|---|---|
| contactId | int | 合同id |
| effectiveTime | date | 生效日 |
| expiredTime | date | 到期日 |
| status | char | 合同状态。INIT:初始-EFFECTIVE:生效中-EXPIRED:已到期 |
接着,我们定义合同服务类
interface contactService {
// 判断合同是否有效
boolean contactIsEffective(contactId);
// 标记到期合同的状态为“已到期”
void processExpiredcontacts();
}
当合同到期后,系统要标记合同的状态为“已到期”,我们的程序怎么实现呢?
这里需要考虑的一个重点是,要保证上面的第一点“到期的合同不能再发生业务”,否则就是系统bug了。
使用定时任务,大家自然而然会想到。
每天凌晨0:00定时跑批,检查系统里是否有当日到期的合同,到期则将status由“EFFECTIVE”改为“EXPIRED”。
这里会存在一个问题,就是无法精准保证“到期的合同不能再发生业务”。
毕竟定时任务执行也有耗时的,在这期间被调用的 contactIsEffective(contactId) 会存在错误地返回true的情况。
这个问题怎么解决?
更坏的情况,假如,合同到期的粒度不是date,而是更细粒度的hour/minute,例如 某合同的到期时间是"2025-02-21 17:20:00",上面的定时任务,就更吃力了。
所以,怎么解决呢?
邻家系统一招搞定,简直不要太帅。
在 contactService#contactIsEffective 方法里,获取指定的合同记录后,当 status=EFFECTIVE 时,则再判断一下 expiredTime, 如果 expiredTime≥当前时间,则触发合同状态的变更,并返回false。(注:这里只谈程序实现思路,对于并发调用的控制等方面,本文不做讨论)
是不是很简单?是的,亲爱的你,有没有想起缓存技术中,缓存过期的惰性删除策略呢?是不是有异曲同工之妙呢!