如题,这是一个非常基础的问题。几日前与同行讨论到这样一个问题:
企业应用中,在运行时允许一个Method被成功激活的前置条件有哪些方面?
讨论归纳成为如下四个方面,与大家分享,希望可以抛砖引玉:
众所周知,JavaEE规范中明确的定义了安全性模型。虽然国内大多项目并未采用该安全性模型,而是转而通过应用程序自身完成的安全性管理(尽管JavaEE规范中提到这存在存在隐患),但是不可否认安全性约束是企业应用的一个非常重要的基础方面。
?
在JavaEE 8以前,JavaEE框架提供的安全性服务以组件级别安全性为主。相比之下,在概念上其他的应用程序或者PaaS(平台即服务) 提供了更细致的安全性服务。比如:Force.com这种多租户系统,首先提供了Tenant级别的安全性;在一个Tenant内部,也包含数据级别安全性,FLS (Field Level Security) 以及 基于条件的安全性。虽然这些概念不完全跟Method访问前置条件相关,笔者也稍作介绍。
?
JavaEE定义了一组安全性相关的概念:
Force.com一直再演进。笔者接触到了如下安全性约束:
数据级别安全性:数据隔离。包括用户数据的隔离以及租户(Tenant)数据的隔离。
FLS:Field Level Security 域级别访问控制,包括针对某Profile或者User是否可见,是否可写。
基于条件的安全性:数据隔离就会产生数据共享的需求,故在满足一定条件的情况下,数据对不同的用户的可访问性控制。
?
回到JavaEE对业务方法的安全性访问控制,运行时能否激活一个公有方法,首先需要过身份认证和鉴别这一关,对于采用标准JavaEE安全性模型的应用而言,这部分功能是由容器提供的,访问请求在发生授权的过程还没有接触到应用程序本身。可以参考@RolesAllowed相关的API等等。
?
抛开具体的规范、框架和架构,参数的约束包括上下文相关和上下文无关两部分,本节只提到上下文无关的部分。这部分也没什么可说的,众所周知,对于企业应用程序,无论Client, Browser还是Server,都要进行参数自身有效性的校验,这部分也非常基础,比如一个字符串型参数的长度的校验或者一个邮件型字符串的合法性校验等。
?
JSR303 Bean Validation在Server Side辅助完成了部分校验功能,例如有提供@NotNull之类的约束注释。
?
JavaEE核心模式中给出了业务层架构的范例,其中定义了Application Service以及Business Object两个层次概念。大体上,为了解耦不同业务对象之间的依赖关系,引入了Applcation Service的概念。因为JavaEE出现本来目的是为了兴起业务组件的市场。不同的厂商提供不同领域的Business Object的组件,共同部署到一个产品环境或者同一个应用服务器。那么为了解决不同厂商提供的Business Object的互操作或者依赖问题,引入了Application Service的概念,用来集成不同厂商提供的业务模块。然而现实并非完全如专家组所预期,有不少项目通常是一票到底,若开发设计实践(未能合理的抽象)未着重于断绝不同业务对象之间的依赖,那么这将导致整个应用过于复杂,尽管有服务层也未起到预期的作用。这都是题外话,为了介绍Application Service和Business Object的概念引入的。
?
在Application Service层次,参数校验需要结合当前上下文来校验当前请求是否有效。经过一些应用逻辑后,要么激活相关的业务对象方法;要么拒绝请求而未激活业务对象的方法。
在Business Object层次也有类似的情况。
?
这类约束需要应用服务开发人员自己开发完成。
?
业务对象不同于基础代码对象,包含明确的生命周期特征以及业务逻辑。并且业务对象的业务方法的激活通常是有一定的时序要求。在软件建模技术中,这部分由UML的状态图来刻画。比如,对于一个未打开的网络连接进行接收或者发送的行为都是没有意义的;而对于一个已经打开的网络连接执行打开操作也是没有意义的。这种时序性要求本质上源于生命周期约束。
?
由于相同模块的业务对象之间存在关系:依赖,关联,聚合以及组合。故一个业务对象的业务方法除了业务对象自身的生命周期约束外,还可能包括关系对象的生命周期约束。比如一个生产任务的调度行为依赖于所需生产线资源是否空闲可用。生产任务有独立的生命周期,生产线资源有独立的生命周期。生产任务的调度行为依赖于空闲可用的生产线资源。显而易见,倘若为生产任务所指定的生产线资源处于繁忙状态时,对该生产任务的调度行为是无效的。这是“依赖”关系的生命周期约束。
?
再比如,一个根据客户订单生产的系统,包含了客户,某个时期的合同,某一批次订单,BOM信息,排产信息,生产信息以及配送信息等等。倘若在生产环节中,客户被识别出为不良客户,公司需要终止该客户相关的一切生产行为。倘若生产环节不可以被中断,那么系统可能设计成,当下了生产线准备进行打包装的时候,系统确认失败,提示该客户的所有产品均需要拆散,不予进入物流阶段。那么此时对于生产信息而言,它需要参照“父亲”业务对象所在的生命周期,“父亲”对象也需要参照“父亲”对象的生命周期,这是一个递归的过程。倘若不是客户被挂起,而是合同被取消时终止一起生产活动,也可以达到预期的行为。这是“聚合”关系的生命周期约束。
?
显然这种层级关系的递归式生命周期约束检查使得应用代码显得的繁琐。而实际的情况是,设计者或者程序员往往忽略了这部分约束的检查。笔者经历过硅谷一个著名软件服务公司上线运行了6年的软件服务,竟然也缺少这方面约束检查,导致某一个服务经过多次讨论,修改,上线,回滚,最终放弃了提供该服务。这使得在一些边界状态下,系统会产生令客户愤怒的行为。当然这个问题本身很简单,但是疏漏的设计导致现实变得复杂了。对于多层级业务对象而言,如果一开始能设计好生命周期约束,那么解决这个问题是相当容易的事情。
?
在现实的开发设计实践中,不仅仅设计可能会产生疏漏,就算是设计未能产生疏漏,编码也可能与设计产生不一致。因为对于大多数的项目或者产品或者服务而言,正向测试是基础,而负向测试可能永远是不够的。尤其是跨越层级的生命周期变化相关的负向测试,往往会被忽视或者由于测试成本不足而忽略。即便不被忽略,那么无论从开发上或者是测试上,这方面的约束检查都会有较大的开销。
?
不仅如此,对于一个经历着数年考验的系统来说,经历过N次功能代码的修改后,一个业务对象自身的准确的生命周期已经不能被准确的刻画或者描述了,过去的设计文档早已过时。随着某个业务对象自身生命周期的变化,对依赖到这个业务对象的那些业务对象也产生了影响。如何保证某个具体的生命周期状态的语义发生变化后,原来可以通过的测试仍然是正确的行为呢?
?
对于一个多租户的系统,不同租户的相同业务对象是否可以有不同的生命周期呢?这个时候应用程序该如何写代码来校验生命周期约束呢?
?
接下来,试图归纳总结这些生命周期约束的相关问题:
这类约束检查,过去没有第三方支持,需要应用程序员开发。现如今包含生命周期相关约束检查的一种元驱动形式的第三方开源项目出现了。
应用程序员通过基于Java语言来描述业务对象的生命周期来刻画一个业务对象自身的状态转换图,以及业务对象与所关系对象之间的生命周期约束。在运行时生命周期引擎会完成上述工作,而应用程序员不需要调用任何生命周期框架的API。
?
在此分享该开源项目Lifecycle?(on github.com)
?
相关博文生命周期组件框架——关系型状态机服务?
?
样例代码: