重构为自适应对象模型: 实体关系和问责制






3.75/5 (7投票s)
2005年6月3日
16分钟阅读

87550

242
本文将讨论实体关系与自适应对象模型模式的关系。我们将讨论并使用 Martin Fowler 的责任承担模式作为示例,该模式为我们提供了关系形成、类之间的基数,并且是 AOM 模式中工作流的基础。
引言
自适应对象模型是面向对象设计中一个相对未实现的思想,它在高级开发过程中已找到了一些根基。它允许应用程序根据业务规则、环境和用户需求的变化而持续变化(或其需求变化)。其原则是,使用元数据来定义业务对象、规则和流程,而不是硬编码的、静态的方法代码,可以使应用程序更灵活地满足用户/客户的需求。AOM 设计模型还要求维护某些领域“事实”,即对用户生成的元数据所产生的“即时”变化的效应进行受控的解释。
自适应解决方案为我们提供了一种使应用程序(在工作流方面)更具可配置性的方法。它并非万能的解决方案,据我所知,还没有一个端到端的解决方案能满足所有需求,仅仅是因为这些需求更多地由业务需求直接定义。然而,由于业务需求频繁变化,编写不变的代码流程逻辑会导致代码寿命很短。即使是网络行业也感受到了动态配置的需求:如果您订阅了 Yahoo! 或 MSN 等大多数门户网站,您会拥有一个用户可配置的主页,您可以在其中决定要在页面上显示哪些感兴趣的项目。
大多数门户技术间接指向了更具自适应性的模型,仅仅因为它们可以由最终用户或高级用户进行配置。我认为自适应技术,如自适应对象模型,与 Web 门户类似:一些功能需要由开发人员编码,如数据库和业务逻辑,但这些逻辑的实现位置和时间应该(遵循业务趋势)由业务专家而不是开发人员决定。一些自适应模型将非元数据紧密耦合到与层元数据绑定的层模型中,这是一种实现方式,但我更倾向于让 EUD(最终用户开发人员)在如何处理业务数据方面拥有发言权,并简单地允许 AOM 框架将实际的何时何地实现交还给业务专家(这对于大多数开发人员来说是力所不及的)。
为了更好地理解和实践 AOM 设计的技术,我正在撰写本文以及其他几篇解释如何从静态代码模型转换为 AOM 设计(或重构工作)的文章。
本文将讨论实体关系与自适应对象模型模式的关系。我们将讨论并使用 Martin Fowler 的责任承担模式作为示例,该模式为我们提供了关系形成、类之间的基数,并且是 AOM 模式中工作流的基础。
本文的目标读者
本文主要面向架构师和业务 IT 开发经理,以及一些具有高级反射和设计经验的开发人员,还有任何熟悉 AOM 或其他类似模式方法论但尚未看到这些模式实际使用方法的人。本系列文章将是我今年晚些时候发布的一个 AOM 框架的起点。
背景
为什么关系在自适应对象模型模式中很重要?关系或责任承担通过解释由高级用户设置的元数据,赋予该模式通过类、类型和属性关系定义工作流的能力。在处理解释器模式时,主要优点是能够即席创建和修改程序工作流。这与传统的应用程序工作流方法大不相同,后者是硬编码的、相对于用户而言是静态的。传统的应用程序工作流由应用程序开发人员在编译时设置,在应用程序的逻辑流程方面几乎没有灵活性。业务运营中的关系经常变化,有时甚至是每天,任何无法立即响应用户需求的系统通常生命周期都较短,或者限制了使用该软件的业务单元。
为了理解 AOM 如何利用关系,我们首先需要研究 Martin Fowler 的责任承担模式,该模式为我们提供了适合此类工作流的良好关系方法。责任承担模式是一种定义类之间的关系、定义管理这些关系规则以及提供一种语言解释的方式来定义类模式和关系的方法。责任承担使用类和类型结构,使用类型类来超定义实现类。它的工作方式与语言在描述关系方面非常相似。
在此,我们根据 Fowler 模型看到了基本责任承担模式的 UML。
假设我们有一种“总裁”职位类型。这种职位类型可以雇用“高管”职位类型的人员,而“高管”又可以雇用“经理”职位类型的人员,依此类推。因此,典型关系如下所示:
President {who} 'hires' VicePresident {who} 'hires'
SalesManager {who} 'hires' SalesAssociate.
关系或责任承担是总裁与副总裁、副总裁与销售经理以及销售经理与销售助理之间的关系。因此,招聘整个部门的工作流将由“招聘”关键字定义,该关键字本身代表一种责任承担类型。实际的责任承担关系是整个名词-动词-宾语关系,例如:总裁 {谁} '招聘' 副总裁。“招聘”责任承担类型告诉我们希望执行的操作类型,并且是语句的动词。行为的委托人,“总裁”,是行动者,是名词;对行动者负责的实体,“副总裁”,是宾语。因此,我们正在使用常见的语言元素,一个完整的关系模式,从而实现单位之间责任的工作流。
在这些责任承担关系中维护基数。树状结构向上和向下移动,即父级知道所有子级,子级也知道所有父级。我们将关联与实际实体解耦,因此可以定义许多不同的关系,而不是单一的、不变的父子关系,就像我们在当前实体对象中保留父实体或子实体集合时可能会获得的那样。这为我们提供了定义多种不同关系类型的更多选项。
例如,假设我们在应用程序中定义了哪些职位类型可以雇用哪些其他职位类型,但现在我们需要知道哪些职位向哪些其他职位汇报。根据业务的组织变化,不同的职位可能在不同时间向不同的其他职位汇报。如果我们对实体进行了单一的父子关系设置,我们就无法轻易地更改关系类型或责任承担类型。假设我们想定义“汇报给”关系,而不是使用“招聘”责任承担类型。也就是说,销售助理向销售经理汇报,销售经理向副总裁汇报,依此类推。那么这种关系将如下所示:
SalesAssociate {who} 'ReportsTo' SalesManager, SalesManager {who} 'ReportsTo'
VicePresident, VicePresident {who} 'ReportsTo' President
这两个关系链(“招聘”和“汇报给”)可以独立存在,并且仅依赖于决定其特定类型的实体类型和规则。因此,每个属性类型都可以在应用程序中定义不同的工作流。
基于其类型定义另一组关系比任何其他方式更容易找出关系,因此责任承担类型的需要显而易见。
通过元数据定义工作流:知识和操作
我将代码按基本逻辑功能进行了划分,定义了两种类型的类:类型类和操作类或实现类。类型定义类,如 `EntityType`、`AttributeType`、`AccountabilityType` 和 `ConnectionRule`,我将其与“知识”一词分组,因为它们是框架中定义和保存工作流过程基本规则和知识的部分。实现类,如 `Entity`、`Attribute` 和 `Accountability`,我将其与“操作”一词分组,因为它们是参与实际功能的类。不应将操作代码部分与*操作策略*代码混淆,后者是框架策略部分所在的位置。策略讨论超出了本文的范围,将在其他地方涵盖。
让我们看看我们如何开始设置我们的关系数据。在处理像这样的抽象模型时,我总是先从实际的元数据模式开始,然后再创建任何代码,因为这有助于我更好地思考和查看类关系。所以,让我们先看看元数据文件 *KnowledgeConfiguration.config*。请注意,名称中包含“知识”,这表明该元数据定义了 AOM 框架的*知识*部分。
在这里,我们看到了 `AttributeTypes` 的 XML 元数据。它只是告诉我们用于识别该类型的键名、它代表的实际对象类型以及它可能需要的任何事件或验证(这超出了本文的范围)。此类型规范基本上是一个规则,将用于定义应用程序中可用的系统允许的数据类型。
<attributeTypes>
<attributeType name="EmployeeIdType"
attributeDeclaritiveType="System.Int32"
attributeEventType="OnSelect" />
<attributeType name="FullNameType"
attributeDeclaritiveType="System.String"
attributeEventType="OnChange" />
<attributeType name="IsActiveType"
attributeDeclaritiveType="System.Boolean"
attributeEventType="OnChange" />
<attributeType name="StartDateType"
attributeDeclaritiveType="System.DateTime"
attributeEventType="" />
<attributeType name="IsAdminType"
attributeDeclaritiveType="System.Boolean"
attributeEventType="OnSelect" />
</attributeTypes>
接下来,我们看一下 `EntityTypes` 的元数据 XML。`EntityTypes` 在之前的文章中已讨论过,它们充当类定义或抽象类,为实际的类或实体提供契约。我们需要这个类来确定不同类型的实体如何响应并参与关系。它们决定了可以添加到 `Entity` 类中的属性和策略,并有助于在责任承担中定义实体关系。实际上,它们充当了限制和定义实现类的规则库。
<entityTypes>
<!--Executive Entity Types //-->
<entityType name="TopLevelExecutiveType"
parentEntityType=""
allowedAttributeTypes=
"EmployeeIdType,FullNameType,IsActiveType,StartDateType,IsAdminType"/>
<entityType name="ExecutiveType"
parentEntityType=""
allowedAttributeTypes=
"EmployeeIdType,FullNameType,IsActiveType,StartDateType"/>
<!--Employee Entity Types //-->
<entityType name="TrustedEmployeeType"
parentEntityType="EmployeeType"
allowedAttributeTypes=
"EmployeeIdType,FullNameType,IsActiveType,StartDateType,IsAdminType"/>
<entityType name="EmployeeType"
parentEntityType=""
allowedAttributeTypes=
"EmployeeIdType,FullNameType,IsActiveType,StartDateType"/>
</entityTypes>
接下来,我们看一下 `ConnectionRules` 的元数据 XML。`ConnectionRules` 是一种定义哪种类型的 `Entity` 类可以存在于特定类型关系中的方法。`ConnectionRules` 直接定义在 `AccountabilityType` 中,正如你前面所读到的,它定义了关系类型。`ConnectionRules` 只是告诉我们哪种类型的行动者可以在特定类型关系中定义。请注意,`connectionRule` XML 节点除了键名属性外,还包含两个属性。`allowedCommissioner` 说明如果实现此规则,则可以在关系中定义的 `EntityType` 父级是什么,而 `allowedResponsible` 指示可以定义的 `EntityType` 子级是什么。
<connectionRules>
<connectionRule name="TopLevelExecutiveSubordinates"
allowedCommissioner="TopLevelExecutiveType"
allowedResponsible="ExecutiveType" />
<connectionRule name="ExecutiveImmediateSubordinates"
allowedCommissioner="ExecutiveType"
allowedResponsible="ExecutiveType" />
<connectionRule name="ExecutiveTrustedSubordinates"
allowedCommissioner="ExecutiveType"
allowedResponsible="TrustedEmployeeType" />
<connectionRule name="ExecutiveSubordinates"
allowedCommissioner="ExecutiveType"
allowedResponsible="EmployeeType" />
<connectionRule name="ManagerTrustedSubordinates"
allowedCommissioner="ExecutiveType"
allowedResponsible="TrustedEmployeeType" />
<connectionRule name="ManagerSubordinates"
allowedCommissioner="ExecutiveType"
allowedResponsible="EmployeeType" />
</connectionRules>
最后,我们看到定义实际责任承担类型的 XML 元数据,例如“招聘”或“汇报给”。在这个例子中,我们使用专门的名称来进一步描述操作,以减少混淆。任何关系名称的描述性都应恰到好处,不多不少。请记住,与所有编码或关系数据一样,事物的命名对于清晰和易于理解非常重要。请注意,`accountabilityType` 只有两个属性:键名,以及一个逗号分隔的连接规则名称列表,这些名称有助于定义我们要为此关系类型强制执行的规则类型。
<accountabilityTypes>
<accountabilityType name="TopLevelExecutiveHiresExecutive"
rules="TopLevelExecutiveSubordinates" />
<accountabilityType name="ExecutiveHiresManager"
rules="TopLevelExecutiveSubordinates,ExecutiveImmediateSubordinates" />
<accountabilityType name="ExecutiveHiresEmployee"
rules="ExecutiveTrustedSubordinates,ExecutiveSubordinates" />
<accountabilityType name="ManagerHiresEmployee"
rules="ManagerTrustedSubordinates,ManagerSubordinates" />
</accountabilityTypes>
现在我们已经查看了元数据的知识部分,我们可以开始讨论这些规则和类型如何定义类关系。
现在我们将查看元数据源文件 *OperationalConfiguration.config*。这是应用程序实际程序流的元数据,即类、属性和关系数据。首先,让我们看看 `attributes` 部分。在这里,我们看到 `attribute` XML 节点具有通用的键名、知识部分中的 `attributeType`(定义了数据类型),以及另一个 XML 属性 `attributeEvent`,它显示了一种要与属性关联的操作策略调用类型。此元素的用法超出了本文的范围,仅包含在示例中。
<attributes>
<attribute name="EmployeeId"
attributeType="EmployeeIdType"
attributeEvent="GetEmployee({0})" />
</attributes>
接下来,我们看到 XML 元数据的实体部分。这是设置实际功能类类型的地方。请注意,我们具有知识部分中的 `EntityType` 键名和一个逗号分隔的属性键名列表。这告诉我们这是什么类型的类,要关联哪些属性(受 `EntityType` 允许的 `AttributeTypes` 限制),并且基于 `EntityType`,它将告诉我们可以包含在不同类型关系中的实体类型。
为什么我们要定义我们希望放入关系中的类类型?好吧,一个用于内部财务数据的 `Entity` 不会用于外部供应商之间的关系。因此,`EntityType` 可以防止具有某些专有功能的 `Entity` 被包含在为外部供应商服务的应用程序部分中,这些供应商与客户的财务数据无关。`EntityType` 为应用程序内部的不同操作定义了允许的功能片段。
<entities>
<entity name="President"
entityType="TopLevelExecutiveType"
actualAttributes="EmployeeId"
/>
<entity name="VicePresident"
entityType="ExecutiveType"
actualAttributes="EmployeeId"
/>
<entity name="SalesManager"
entityType="ExecutiveType"
actualAttributes="EmployeeId"
/>
<entity name="CustomerServiceManager"
entityType="ExecutiveType"
actualAttributes="EmployeeId"
/>
<entity name="SalesAssociate"
entityType="EmployeeType"
actualAttributes="EmployeeId"
/>
<entity name="CustomerServiceAssociate"
entityType="TrustedEmployeeType"
actualAttributes="EmployeeId"
/>
</entities>
好的,现在我们可以查看责任承担部分的 XML 元数据。在这里,我们注意到除了键名之外,我们还有三个主要元素:`accountabilityType`、`actualParent` 和 `actualChildren`。`accountabilityType` 是知识元素 `AccountabilityType` 的键名,它告诉我们为该工作流使用的操作或动词。`actualParent` 和 `actualChildren` 定义了我们实际想要关联的实体,这些实体受到 `ConnectionRules` 的限制,允许其特定 `EntityType` 访问此关联。
因此,对于这个例子,我们可以假设
- 总裁 {谁} '总裁招聘副总裁' 副总裁。
- 副总裁 {谁} '副总裁招聘管理器' 销售经理或客户服务经理。
- 销售经理或客户服务经理 {谁} '管理器招聘员工' 销售助理或客户服务助理。
<accountibilities>
<!-- Hiring //-->
<accountability name="PresidentHiresVicePresident"
accountabilityType="TopLevelExecutiveHiresExecutive"
actualParent="President"
actualChildren="VicePresident" />
<accountability name="VicePresidentHiresSalesManager"
accountabilityType="ExecutiveHiresManager"
actualParent="VicePresident"
actualChildren="SalesManager" />
<accountability name="VicePresidentHiresCustomerServiceManager"
accountabilityType="ExecutiveHiresManager"
actualParent="VicePresident"
actualChildren="CustomerServiceManager" />
<accountability name="VicePresidentHiresSalesAssociate"
accountabilityType="ExecutiveHiresEmployee"
actualParent="VicePresident"
actualChildren="SalesAssociate" />
<accountability name="SalesManagerHiresSalesAssociate"
accountabilityType="ManagerHiresEmployee"
actualParent="SalesManager"
actualChildren="SalesAssociate" />
<accountability name=
"CustomerServiceManagerHiresCustomerServiceAssociate"
accountabilityType="ManagerHiresEmployee"
actualParent="CustomerServiceManager"
actualChildren="CustomerServiceAssociate" />
注意:在本例中,我们添加了另一组责任承担类型以说明我们可以基于类型拥有多个程序流。我们可以假设
- 副总裁 {谁} '副总裁汇报给总裁' 总裁。
- 销售经理 {谁} '销售经理汇报给副总裁' 副总裁。
- 客户服务经理 {谁}' 经理汇报给高管' 副总裁。
等等……
<!-- Organizational Structure //-->
<accountability name="VicePresidentReportsToPresident"
accountabilityType="ExecutiveReportsToTopLevelExecutive"
actualParent="President"
actualChildren="VicePresident" />
<accountability name="SalesManagerReportsToVicePresident"
accountabilityType="ManagerReportsToExecutive"
actualParent="VicePresident"
actualChildren="SalesManager" />
<accountability name="CustomerServiceManagerReportsToVicePresident"
accountabilityType="ManagerReportsToExecutive"
actualParent="VicePresident"
actualChildren="CustomerServiceManager" />
<accountability name="SalesAssociateReportsToVicePresident"
accountabilityType="EmployeeReportsToExecutive"
actualParent="VicePresident"
actualChildren="SalesAssociate" />
<accountability name="SalesAssociateReportsToSalesManager"
accountabilityType="EmployeeReportsToManager"
actualParent="SalesManager"
actualChildren="SalesAssociate" />
<accountability name=
"CustomerServiceAssociateReportsToCustomerServiceManager"
accountabilityType="EmployeeReportsToManager"
actualParent="CustomerServiceManager"
actualChildren="CustomerServiceAssociate" />
</accountibilities>
通过 AOM 工作流检查逻辑程序流
现在我们对元数据基本模式有了了解,我们可以讨论关系如何在应用程序中定义程序流。对于这个例子,我创建了一个 WinForm 应用程序客户端,这是一个很好的例子,说明了这个模型的非常基本的使用方式。通过这个例子,我创建了动态菜单项,显示操作类*的实体,这些实体又显示指向其关系实体的链接。每个实体的详细信息都显示在窗体上,就像您在常规应用程序中显示数据一样。我们省略了策略的实现方式(这在另一篇文章中有所介绍)以及如何处理属性和应用程序事件。当然,这只是 AOM 模式中关系的一个粗略示例,但我们将通过这个例子看到应用程序工作流如何从传统的静态模型提取到灵活的 AOM 模型。
首先,我们看一下介绍屏幕,看到所有实体都按名称列出。我这样做是为了让我们对从哪里开始有一个想法,但在实际应用程序中,菜单项可以像程序流的任何其他部分一样由 `AccountabilityType` 处理。
接下来,让我们选择副总裁实体。我们看到实体窗体,其中列出了实体的属性和名称。我们还注意到菜单已更改,我们看到三个新菜单项:招聘、经理和下属。招聘菜单列出了我们元数据中关于副总裁可以招聘的第一个工作流。经理和下属菜单列表显示了关于他向谁汇报的关系的第二个列表。经理代表父级关系或该关系的*佣金责任承担*。还记得吗,我们之前在设置“汇报给”的 `AccountabilityTypes` 时?在这里,我们在应用程序中定义了一个完全不同的逻辑工作流。您可以围绕每个责任承担类型构建基于角色的安全机制,以便只有特定的人才能看到应用程序的某些方面。下属的工作方式与经理相同,它使用“汇报给”责任承担类型,但它使用子级关系或*受托责任承担*。
之后
现在我们可以轻松地看到如何在实际应用程序中使用 AOM 中的关系。我们已经提取并检查了两个基本程序工作流,并在客户端界面中看到了它们,几乎没有“硬编码”或静态逻辑代码。我们将所有逻辑规则、流程以及类定义都放在元数据中,然后通过我们的解释器将其渲染到实际的流程中。
我们没有展示的是,即使我们的示例是客户端工作流,几乎任何流程都可以创建底层工作流的明显基本原理。假设我们想要一个审批系统,该系统向组织中的不同人员发送操作项审批请求。根据人员的职位,将创建审批链,请求和审批将在有效审批者之间流动。如果我们想更改哪些职位向哪些其他职位汇报,而不改变实际程序代码呢?我们已经看到,这对于 AOM 来说绝对是一个可行的场景。与大多数灵活的工具一样,它的有用性取决于使用它的人的视野。
我们在项目中忽略了另一个方面,那就是如何操作存在于我们元数据之外的实际业务相关数据。当然,这很容易插入到模型中,而您的元数据和您的业务数据如何紧密耦合实际上取决于您,尽管我会倾向于将两者分开,即使它们位于同一个数据库或存储库中。还记得策略吗?这就是它们发挥作用的地方,以及属性事件和其他系统相关算法。
当然,我只描述了一种看待 AOM 模式的方式,即在处理事件和策略方面。还有另一种我们以后可能会讨论的方式,它使用属性的策略和事件来构成业务规则和逻辑。但这要留待以后。
关注点
这是我撰写的关于真实世界自适应对象建模的系列文章的第六部分。所有示例和大部分本文内容均摘自我作为架构师的专业经验。提供的示例仅为模板,设计者必须牢记,他们是决定何处最适合在代码中使用不同模式(如果有的话)的人。
决定从现有代码执行重构工作到模式或增强的设计模型,必须权衡代码本身的必要性和需求。模式和自适应模型只是设计模板,是为实现更好的整体设计而进行的辅助。我可能会强调,努力使用高级设计方法将加强您的整体设计能力,但就像您的基本编码技能一样,这是需要学习和培养的。
相关文章
本系列的其他文章包括
- 类型对象
- 属性
- 策略
- 实体关系和责任承担/基数.
- 幽灵加载、数据映射等……
- 解释器方法论(即将推出)
- 关于实际项目设计实用性的讨论。
- 关于 AOM 框架、UI 和高级概念的讨论.
历史
这是第一次修订,是系列文章的第六部分。
致谢
感谢
- Joe Yoder 和 Ralph Johnson,感谢他们关于 AOM 模式的各种文章。
- Martin Fowler,感谢他对我的文章的关注以及他在责任承担模式方面的工作。
- Doga Oztuzun,感谢他的见解和分享他相关的项目数据。
- 也感谢其他我可能忘记提及的人。