基本原则
在开始设计之前,考虑主要的设计原则将有助于找到架构的设计的“最佳方案”,降低成本和维护需要,提高系统的可用性和可扩展性。主要的设计原则如下:
l 关键点的分离 将应用程序分成清楚的不同元素,使功能的重叠尽可能的少。
l 单一责任原则 每一个组件或模块应该只负责唯一一个特定的功能。
l 最少知识原则 一个组件或对象应该不用知道其他组件的内部实现细节,而只要按照彼此的约定调用即可。
l 不要重复自己 一个组件对应提供一个功能,一个功能也只应由一个组件提供。而不能将功能的实现分散到很多其他的组件中去。
l 避免在前期做大量的设计如果你的需求不是很清楚,或者有设计随时间演变的可能,应当避免在项目前期做大量的设计工作。这一设计原则通常被叫做“BDUF”。
l 多用组合少用继承 在尽可能的情况下,使用组合的方式来重用功能,而不使用继承的方式。因为继承增加了父子类之间的依赖关系,限制了子类的重用。
设计要点
在设计软件或系统时,软件架构的目标就是通过将设计分割为不同的关注领域来降低其复杂性。例如,用户接口、业务进程和数据访问均可视为不同的关注领域。在每个领域内部,组件应专注于其特定的领域,而不应该混合其他领域的代码。例如,UI进程组件不应该包括直接访问数据源的代码,而应该使用其他的业务组件或数据访问组件检索数据。
下面列出了设置应用程序的指导方针:
l 避免在前期做所有的设计如果系统需求不清楚或存在设计演变的可能,在前期不去做一个完整的设计应该是一个好主意。在项目进程中演化设计应该是一个恰当的方法。
l 分割关注领域 将应用程序分成清楚的不同元素,使功能的重叠尽可能的少。这种方法的好处是一个特征或功能独立于其他的特征或功能,可以实现最优。还有,如果一个特征失败了,不会导致其他的特征也失败,它们之间彼此独立运行,互不影响。这种方法也有助于应用程序的设计和理解,便于复杂的互相依赖的系统的管理。
l 每个组件或模块应有单一的责任 每一个组件或模块应该负责实现特定的特征或功能。这可以使组件实现内聚且更容易优化和变更。
l 一个组件或对象不应该依赖其他组件或对象的内部细节组件或对象调用其他组件或对象的方法,这些被调用的方法应该说明如何去使用它。如果需要,将这些内容放在子组件或其他独立的组件中。这将有助于开发一个具有可维护性和适应性的应用程序。
l 在一个应用程序内部不要复制功能 应该只有一个组件提供特定的功能——这个功能不应该在其他的组件中出现。在一个应用程序中复制功能会使功能的变更很难维护,削弱了程序的条理性,引入了潜在的矛盾。
l 确定应用程序组件的组成部分 实现这一点的最好方法是确定符合你情况的模式,检查这些模式所使用的组件的类型。例如,一个小型的应用程序可能不需要一个业务工作流或者UI进程组件。
l 组织不同类型的组件到各自的逻辑层 在一开始,确定不同的关注领域,然后组织相互关联组件到合适的逻辑层中。
l 保持层间设计模式的一致性 在一个逻辑层中,组件的设计对于一类特定的功能应该是一贯的,有延续性的。例如,如果你选择使用表数据网关的模式创建对象,你就不应该再使用其他的方式来访问数据和初始化业务对象。
l 在同一逻辑层中不应混合不同类型的组件 例如,UI层不应该包含业务进程组件,而应该包括处理用户输入和处理用户请求的组件。
l 确定哪类分层要强制执行在一个严格的分层系统中,A层中的组件不能调用C层中的组件,它应总是调用层B中的组件。而在一个相对宽松的分层系统中,一个层中的组件可以调用其下层中的组件,而不只是正下层中的组件。在任何情况下,都应该避免对上层的调用和依赖。
l 抽象实现层间的松散耦合通过定义接口,层内的组件间可以以一种共知的方式彼此进行请求和响应,这样的实现相对比较灵活。另外,你也可以用接口或抽象类来定义公共的接口或者抽取公共的部分(依赖倒置)。
l 一个组件不要承载太多的功能 例如,一个UI进程组件不应该包括数据访问代码。一种常见的违反模式的情况叫做Blob(一团),在这种情况下,基类提供了过多的功能。一个“团”对象通常有很多函数和属性用来提供混合了日志和异常处理的业务功能。由于要处理子功能的各种变体,做复杂的初始化,这类对象通常非常庞大。最终结果是,这种的设计很容易产生错误,而且很难维护。
l 清楚组件间是如何通信的这首先需要了解你的应用程序是如何部署的。需要明确的是,组件间的通信是否需要跨越物理边界或跨进程,所有组件是否运行在统一进程当中。
l 多用组合少用继承 在尽可能的情况下,使用组合的方式来重用功能,而不使用继承的方式。因为继承增加了父子类之间的依赖关系,限制了子类的重用。这也减少了继承的层级,避免了去处理复杂的层级关系。
l 保持层内或组件内数据格式的一致性 混乱的数据格式会使程序很难运行、扩展和维护。每次你都需要在不同格式间进行转换,执行转换的代码。这样减低了性能,而且没有必要。
l 尽可能的将交叉(横切)代码从业务逻辑中抽象出来 交叉(横切)代码通常涉及到安全性、通信和操作管理(例如,日志和检测)。将这些代码混合到业务逻辑中会导致程序扩展和维护上的困难。修改这些交叉(横切)代码会牵联到所有混合了它们的业务逻辑代码的修改。可以考虑使用框架来实现交叉(横切)的集中处理。
l 命名习惯的统一 看看团队、组织里是否建立了相关的命名规定,如果没有,你应该建立一个公用的命名标准。由此而来的代码的一致性会使团队成员检查代码更容易。这样也更易维护。
l 建立异常处理的标准例如,应该总是在层的边界捕获异常,不应该在层内捕获异常,除非你可以在层内处理它,也不应该用异常来实现业务逻辑。该标准还应包括错误提示、记录日志的策略和对异常的检测
架构框架
下面的表格列出了你在设计架构时应该考虑的主要方面。通过这些关键的问题了解通常会犯错的地方。这个章节为这些不同的方面提供了指导。
表格1架构框架
方面 | 问题 |
认证和授权 | l 缺少跨信任边界的认证 l 缺少跨信任边界的授权 l 松散或不适当的授权 |
缓存 | l 数据缓存反复无常 l 缓存敏感数据 l 选择了错误的缓存方式 |
通信 | l 选择了错误的传输协议 l 多余的跨物理和进程边界的通信 l 没有有效的保护敏感数据 |
组合 | l 彼此协作的应用模块之间相互依赖使开发、测试和维护变得很困难。 l 模块间的依赖变更强制代码重新编译和模块的重新部署 l 顽固的代码依赖使动态的UI布局和更新变得很困难 l 顽固的代码依赖使模块的动态加载变得很困难 |
并发和事务处理 | l 没有保护对静态数据的并发访问 l 死锁导致不适当的锁定 l 没有选择适当的并发处理模式 l 长期保持对数据的锁定 l 不恰当的使用互锁 |
配置管理 | l 缺少或存在错误的配置信息 l 没有对敏感的配置信息采取保护措施 l 无限制的对配置信息的访问 |
链接和聚合 | l 错误的功能分组 l 关注点的分离不清楚 l 层间的链接过于紧密 |
数据访问 | l 对总用户的不必要的验证和授权 l 多余的对数据库的访问 l 业务逻辑掺杂数据访问代码 |
异常管理 | l 处于不稳定状态 l 向最终用户暴露敏感信息 l 使用异常控制程序流程 l 没有记录有关异常足够的细节 |
分层 | l 对组件进行了错误的分层 l 没有遵守分层和从属划分的规则 l 没有考虑层间的物理分布 |
日志和检测 | l 缺少日志和检测 l 日志和检测太过细致 l 没有将日志和检测做成运行时可配置的 l 没有阻止和处理日志记录失败的发生 l 没有对关键的业务进行日志记录 |
状态管理 | l 使用了错误的状态存储 l 没有考虑序列化的需要 l 没有在需要的时候保持状态 |
结构 | l 针对方案选择了错误的结构 l 创建了一个过于复杂的结构,而这是没有必要的 l 没有考虑部署方案 |
用户体验 | l 没有遵循发布的指导方针 l 没有考虑易用性 l 对不相关的函数创建重载接口 |
验证 | l 缺少跨信任边界的验证 l 没有对范围、类型、格式和长度进行有效的验证 l 没有重用验证逻辑 |
工作流 | l 没有考虑管理的需求 l 错误选择工作流模式 l 没有考虑异常状态及对它们的处理 |