4.第一个示例的编码工作
使用CA编码项目的核心结构是:由多个子系统组成多个不同的服务来提供项目的各种功能。请不要将这里提到的子系统与大家在别的项目实施方法里的概念混为一谈,CA里的子系统概念是完全不一样的,下面我们详细阐述这一点。
同一事物在不同领域里的本质特征是不尽相同的,例如书在销售领域的关注点是价格、好评度、热销情况等。但在阅读领域里,书更多的关注点是页码、每页内容、段落注释等特征。因此,要想用常规的方法在不同领域重用同一个事物模型是非常困难的。CA为了解决这类问题将整个项目切分为多个子系统,每个子系统关注各自领域内的特征。这些子系统是真正实现业务逻辑的地方,子系统之间会存在一定的依赖关系,但是这种依赖关系是良性的,不会影响系统的重用性。也就是说,每个子系统都可以单独拿出来引用到别的项目子系统中扩展重用。开发人员可以根据需要将多个子系统组装在一起构成一个新的服务,这项服务适用于某一个特定领域,例如:
文章子系统 + 汽车子系统 = 提供汽车文集的服务(汽车门户站点)
相册子系统 + 用户子系统 = 提供用户管理个人相册的服务(社交项目)
销售子系统 + 书籍子系统 = 书籍贸易(电商站点)
从层次结构上来讲,服务属于应用层,直接对表现层负责。子系统里的领域对象及业务代码则属于领域模型层。应用层调用领域模型提供的领域方法以便完成业务需求。
一个项目无论规模多么庞大都可以划分成多个规模量为1的子系统,由于这些子系统的代码量足够少,所以可维护性极高。与传统开发的模式相比,CA里的子系统特点如下:
1) 子系统不是抽象的概念而是真实存在的代码集合。在.Net平台里一个子系统体现为一个程序集。
2) 子系统内部仅关注于领域模型的建立,没有任何数据存储的代码。数据的存储由基础设施层里的数据仓储提供。这意味着你可以随时改变存储的机制:切换数据库类型、改变表结构、分布式部署数据库等持久化操作都不会影响到领域模型的改变。
3) 子系统不仅仅用于一个项目,它可以被任意项目使用。以.Net平台为例,子系统有自己所在的解决方案。当其他项目要使用该子系统时,可以以项目引用、程序集引用等方式重用子系统,但绝对不是复制粘贴源代码到新项目里。子系统的源代码只有一份,升级子系统会让所有使用它的项目收益。
4) 多个子系统可以集成工作,一个子系统里的领域模型是可以被其他子系统扩展的。这里说的扩展是指在不破坏原有代码的情况下,以继承、组合等方式扩充领域模型的能力。与这种方式相比,很多传统开发模式里所谓的“二次开发”就是把以前做过的代码、设计过的数据库表复制到新项目里,再更改源代码和表设计以满足新的需求,这根本就不是扩展而是重写。
5) 子系统不能直接用于表现层,它们工作的场所是在应用层的服务里。你可以使用任意技术搭建服务。在.Net平台下可以部署在IIS里,也可以使用专用于CA的服务器端应用程序部署项目的服务。
有了CA开发项目的结构说明和之前分析原始需求的结果,我们可以继续展开会议系统的编码工作了。
根据前文所述,我们要先为“菜单”、“功能”、“用户”、“角色”等事物创建一个服务,服务会提供各种接口以供表现层调用,例如:创建菜单、新增功能描述等服务接口。请注意,把“菜单”、“功能”、“用户”、“角色”这些事物放在同一个服务里未必正确,我们会在后续的开发工作里基于各种原则将服务分离,创建多个服务、多个子系统。但是在眼前我们不必过多考虑这一点,大胆的去做吧。
为服务命名是我们要考虑的第一件事。大家不要忽略命名的重要性,为服务命名、为子系统命名、为领域对象、领域属性、领域方法命名都是需要你认真对待的工作。经过一番思考后,我们认为“菜单”、“功能”、“用户”、“角色”等事物是一个项目里几乎必备的事物,是一切的源头。所以我们引用门户(Portal)这个词作为服务的名称,表示系统的入口,因此服务的全称为PortalService。
以.Net平台为例,我们为门户服务建立解决方案PortalService的结构如下图:
1) 解决方案文件夹Framework里引用的是CA提供的部分类库,在后续教程里会详细说明这些库的用法。在这里我们只用知道引用的这几个库是构建服务必不可缺的。
2) 解决方案文件夹Subsystems表示服务需要用到的子系统,目前服务没有引用任何子系统,稍后我们会创建。
3) portal.services.codeart.cn是托管至IIS的门户服务站点,你也可以使用其他技术部署服务,在这里我们以站点为例。
4) PortalService.Application是门户服务的应用程序集,在这个程序集里主要使用子系统提供的应用命令来完成服务的调用,后面会有详细的说明。
5) PortalServiceTest是单元测试程序集。
创建完门户服务解决方案后,我们需要为其添加子系统。正确的划分子系统是使用CA的一项重要工作。你可以从以下几个角度去分析如何找出子系统:
1) 在已知的事物里,哪些事物是最独立的?独立是指构建该事物的模型不会依赖于其他事物的模型。由于菜单、角色都会涉及到功能的分配,它们的领域模型与功能肯定会有某种依赖关系,所以我们认为菜单和角色这两个事物不不够独立。那么“功能”呢?描述系统的功能只需要一个简单的名称和描述即可,不会依赖任何其他事物而存在,所以”功能“足够独立。我们以“功能”为突破口找出潜在的子系统。
2) 为独立的事物正名。只要是确定要为其建立模型的事物,我们都需要考虑它的名称是否合理。因为我们得到的事物是从现实世界里表面需求分析而来的,这样的事物并非真正贴切程序里的领域模型,在程序世界里有其独有的描述方式。“功能” 这个名称比较含糊,能代表的概念很多,不适用于程序命名。另外,我们在谈及到角色的时候,不是角色有哪些功能而是角色拥有哪些权限。所以,将“功能”正名为“权限”是一个不错的主意。我们统一语言后,会将之前分析到的需求更改为“可以为角色分配权限”、“可以为菜单设置哪些权限能够使用它”。
3) 确定了独立事物的名称后,我们就能以此为基础假设要建立一个与该事物相关的子系统。在这个例子里也就是“权限子系统”。目前,该子系统需要提供哪些应用上的帮助我们还比较模糊,但是可以确定的是权限子系统需要提供创建权限、修改权限、删除权限等操作。权限子系统里面一定会有权限的领域模型。
4) 事实上分析到第3步就可以编码完成权限子系统的第一个版本了,但是由于我们提供的是使用CA的教程,不可能完全演绎出真实项目迭代实施的每个细节,真要如此需要写一本独立的书籍了,也许以后我会抽时间去著作完成,但是在这里我们会浓缩下项目实施的过程。提前告知各位正确的设计方式。“权限子系统”这个想法很好,从概念上讲几乎无懈可击,但是从务实的角度来考虑会有些问题。如果我们为一个领域模型去创建一个子系统,这样使用起来会比较麻烦。你试想一下,如果有“菜单子系统”、“角色子系统”、“权限子系统”,当我们要在服务里创建一个角色时,这个服务必须引用“角色子系统”和“权限子系统”才能完成工作,如果对象引用链比较多,你有可能需要引用的子系统数量远超过预,例如:用户子系统会引用账户子系统、账户子系统会引用权限子系统和角色子系统、用户子系统还会引用地理位置子系统用以表示用户所在地。这时候服务要使用用户子系统就不得不多引用4个额外的项目,不仅麻烦也不利于维护更新。关于如果切断引用链,让子系统更加的独立的话题后面会有更详细的说明,这里我们只用知道尽量不要为一个事物单独创建子系统。