主要观点总结
文章主要描述了如何处理元数据以及使用xml作为存储和管理元数据的载体的问题。文章还讨论了xml模块设计的问题,包括职责错位导致的循环依赖和双向依赖问题。
关键观点总结
关键观点1: 使用xml作为存储和管理元数据的载体
描述为何选择xml作为元数据载体,包括主流文本配置协议、丰富的库方便操作xml等。
关键观点2: xml模块设计的问题
介绍在项目中遇到xml模块设计的问题,包括职责错位导致的循环依赖和双向依赖,以及报表设计器对报表引擎的不必要依赖。
关键观点3: 改进设计
描述如何解决这些问题,即将xml从报表引擎模块中拎出来,成为基础设施层的一个单独模块。
关键观点4: 软件模块设计的体会
强调在进行软件模块的设计时,要注意模块之间的依赖关系,避免出现双向依赖或循环依赖。介绍使用ArchUnit等工具对模块间的依赖关系进行验证和约束的重要性。
正文
因为是元数据(metadata)驱动的SaaS平台,如何处理元数据自然成为整个平台的基础功能。我们选择xml作为存储和管理元数据的载体。之所以选择xml,原因在于:
-
xml是当时的主流文本配置协议,也提供了丰富的库方便操作xml(当时的我还不知道yaml)
-
可以通过xsd配置包括与报表、界面、功能、实体映射等相关的元数据,相当于将xsd作为元数据的领域特定语言,以便于类型的自定义和扩展;
-
因此,在这个项目中,我们是重度使用xml。这在当今的项目可能并不多见,毕竟xml的表现力并不太好,语法又很冗余,显得不够简洁高效。以Kubernetes为例,就全部使用了yaml。鉴于此,我就不再饶舌xml的各种特性与操作了。重点谈谈xml模块的设计。
在我接手EISaaS的架构工作之前,xml的功能已经初具雏形,需要调用xml功能的主要是报表引擎和数据引擎。前者需要通过xml配置报表的各种配置,后者则配置了各种用于生成SQL语句的元数据,以及数据源和数据集信息。
但不知为何,当初的设计竟然将xml的功能和报表引擎模块放到了一起。由于没有清晰的包图,我一开始并没有意识到这一点,直到我引入实体引擎后,再来梳理这些模块彼此之间的关系时,才发现一些模块之间存在双向依赖以及相对不容易发现的循环依赖,如下图所示:
这是典型的职责分配问题。正是因为职责的错位,xml出现在报表引擎中,使得数据引擎为了读取相关的元数据信息而调用报表引擎,反过来,报表引擎需要访问实体引擎,获得报表需要绑定的model,实体引擎则需要通过数据引擎获得对应的数据,如此就形成了报表引擎、实体引擎和数据引擎之间的循环依赖。
同时,实体引擎为了获得实体与数据之间的映射关系,同样需要xml功能,如此又形成了报表引擎和实体引擎之间的双向依赖。
还有一个问题同样是xml错位导致的,那就是报表设计器(tool.reportdesigner)。它是一个用swing开发的客户端应用,用于业务人员设计报表模板。由于报表的格式和数据绑定都需要元数据配置,因此它也需要调用包含了xml的报表引擎。这一调用虽然没有产生循环依赖,却逼着报表设计器依赖了它不需要的除xml之外的其他功能。
罪魁祸首是xml,当然得把xml从不适合的地方赶出来。改进的设计为: