非标自动化东莞网站建设,做cf网站,网站建设图片素材,我厂有大量手袋订单外发系统设计说明书(架构、概要、详细)目录结构 虽然这些文档一般来说公司都是有模板的#xff0c;但我写这些文档以来基本上是每写一次就把目录结构给改一次#xff0c;应该说这是因为自己对这些文档的理解开始加深#xff0c;慢慢的越来越明白这些文档的作用和其中需要阐述的东…系统设计说明书(架构、概要、详细)目录结构 虽然这些文档一般来说公司都是有模板的但我写这些文档以来基本上是每写一次就把目录结构给改一次应该说这是因为自己对这些文档的理解开始加深慢慢的越来越明白这些文档的作用和其中需要阐述的东西觉得这三份文档主要阐述了一个系统的设计和实现过程从系统分解为层次、层次内的模块以及相互的接口、模块分解为对象以及对象的接口、实现这些对象接口的方法。这次又整了一份^_^欢迎大家指正。XXX架构设计说明书 (架构设计重点在于将系统分层并产生层次内的模块、阐明模块之间的关系) 一. 概述 描述本文的参考依据、资料以及大概内容。 二. 目的 描述本文编写的目的。 三. 架构设计 阐明进行架构设计的总体原则如对问题域的分析方法。 3.1. 架构分析 对场景以及问题域进行分析构成系统的架构级设计阐明对于系统的分层思想。 3.2. 设计思想 阐明进行架构设计的思想可参考一些架构设计的模式需结合当前系统的实际情况而定。 3.3. 架构体系 根据架构分析和设计思想产生系统的架构图并对架构图进行描述说明分层的原因、层次的职责并根据架构图绘制系统的物理部署图描述系统的部署体系。 3.4. 模块划分 根据架构图进行模块的划分并阐明模块划分的理由绘制模块物理图以及模块依赖图。 3.4.1. 模块描述 根据模块物理图描述各模块的职责并声明其对其他模块的接口要求。。 3.4.2. 模块接口设计 对模块接口进行设计并提供一定的伪代码。 XXX概要设计说明书 (概要设计重点在于将模块分解为对象并阐明对象之间的关系) 一. 概述 描述本文的参考依据、资料以及大概内容。 二. 目的 描述本文的编写目的。 三. 模块概要设计 引用架构设计说明书中的模块图并阐述对于模块进行设计的大致思路。 3.1. 设计思想 阐明概要设计的思想概要设计的思想通常是涉及设计模式的。 3.2. 模块A 3.2.1. 概要设计 根据该模块的职责对模块进行概要设计(分解模块为对象、描述对象的职责以及声明对象之间的接口)绘制模块的对象图、对象间的依赖图以及模块主要功能的序列图分别加以描述并相应的描述模块异常的处理方法。3.2.2. 模块接口实现 阐明对于架构设计中定义的模块接口的实现的设计。 XXX详细设计说明书 (详细设计重点在于对模块进行实现将模块的对象分解为属性和方法并阐述如何实现) 一. 概述 阐述本文的参考依据、资料以及大概内容。 二. 目的 阐述本文的编写目的。 三. 模块详细设计 3.1. 设计思想 阐述对模块进行详细设计的思想。 3.2. 模块A 3.2.1. 详细设计 根据模块概要设计详细描述对于模块内对象的实现包括对象的职责、属性、方法、对象内功能的流程图、对象关联的类、对象的异常。(需要绘制的主要为类图) 在大型的项目中是有必要分开的.... 架构解决系统核心用例以及关键性需求的设计形成抽象的基础结构划分模块、形成模块接口. 概要解决模块以及模块接口的实现形成模块中核心对象以及对象的接口定义.. 详细解决模块中具体对象的实现以及对象接口的实现 演进架构中的领域驱动设计 原文链接http://www.infoq.com/cn/articles/ddd-evolving-architecture
作者Mat Wall and Nik Silver译者王丽娟发布于2009年9月21日 上午11时9分
社区Architecture,Java主题设计,面向对象设计,建模标签Hibernate,领域驱动设计领域驱动设计能非常容易地应用于稳定领域其中的关键活动适合开发人员对用户脑海中的内容进行记录和建模。但在领域本身不断变化和发展的情况下领域驱动设计变得更具有挑战性。这在敏捷项目中很普遍在业务本身试图演进的时候也会发生。本文分析了在反思、重建guardian.co.uk这一为期两年的计划背景下我们是如何利用DDD的。我们展示了如何确保在软件架构中反映最终用户演变的认知以及怎样实现该架构来保证以后的变化。我们提供了模型中重要项目过程、具体演进步骤的细节。 相关厂商内容 【入门】如何完成一次基于TOP的完整开发测试 【进阶】通过淘宝网平台做个淘宝客网站 【进阶】通过淘宝网平台做个外部独立网店 【下载】淘宝开放平台PHP SDK改进版V0.1 【 直播】淘宝开放平台应用审核全过程 相关赞助商 参与赢在淘宝活动解析淘宝开放平台让商业创意和技术完美地合二为一现在点击浏览淘宝开发者社区 顶层标题
计划背景从DDD开始增量计划中的DDD过程进化的领域模型代码级别的演进演进架构中DDD的一些教训附录具体示例
1. 计划背景
Guardian.co.uk有相当长的新闻、评论、特性历史记录目前已拥有超过1800万的独立用户和每月1.8亿的页面访问量。在此期间的大部分时间网站都运行在原始的Java技术之上但在2006年2月开始了一项重要的工作计划——将网站移植到一个更现代的平台上去计划的最初阶段于2006年11月推出当时推出了具有新外观的旅游网站接着在2007年5月推出新的主页之后相继推出了更多内容。尽管在2006年2月仅有少数几个人在做这项工作但后来团队最多曾达到了104人。
然而计划的动机远不止是要一个新外观。多年的经验告诉我们有更好的办法来组织我们的内容有更好的方式将我们的内容商业化以及在此背后还有更先进的开发方法——这才是关键之所在。
其实我们考虑工作的方式已超越了我们软件可以处理的内容。这就是为什么DDD对我们来说如此有价值。
遗留软件中阻碍我们的概念不匹配有两个方面我们先简单看一下这两方面问题首先是我们的内部使用者其次是我们的开发人员。这些问题都需要借助DDD予以解决。
1.1. 内部使用者的问题
新闻业是一个古老的行业有既定的培训、资格和职制但对新闻方面训练有素的新编辑来说加入我们的行列并使用Web工具有效地工作是不可能的尤其在刚来的几个月里。要成为一个高效的使用者了解我们CMS和网站的关键概念还远远不够还需要了解它们是如何实现的。
比如说将缓存内容的概念这应该完全是系统内部的技术优化暴露给编辑人员编辑们要将内容放置在缓存中以确保内容已准备好还需要理解缓存工作流以用CMS工具诊断和解决问题。这显然是对编辑人员不合理的要求。
1.2. 开发人员的问题
概念上的不匹配也体现在技术方面。举例来说CMS有一个概念是“制品”这完全是所有开发人员每天工作的核心。以前团队中的一个人坦言在足足九个月之后他才认识到这些“制品”其实就是网页。围绕“制品”形成的含义模糊的语言和软件越来越多这些东西让他工作的真正性质变得晦涩难懂。
再举一个例子生成我们内容的RSS订阅特别耗时。尽管各个版块的主页都包含一个清晰的列表里面有主要内容和附加内容但底层软件并不能对两者予以区分。因此从页面抽取RSS订阅的逻辑是混乱的比如“在页面上获取每个条目如果它的布局宽大约一半儿、长度比平均长度长一些那它可能是主要内容这样我们就能抽取链接、将其作为一个订阅”。
很显然对我们来说人们对他们工作开始、网页和RSS订阅及其如何实现缓存工作流、“制品”、混乱逻辑的认识之间的分歧给我们的效益造成了明显而惨重的影响。
2. 从DDD开始
本部分阐述了我们使用DDD的场景为什么选择它它在系统架构中所处的位置还有最初的领域模型。在后面的章节中我们会看一下如何把最初的领域知识传播给扩充的团队如何演进模型以及如何以此为中心来演进我们的编码技术。
2.1. 选择DDD
DDD所倡导的首要方面就是统一一致的语言以及在代码中直接体现用户自己的概念。这能有效地解决前面提及的概念上的不匹配问题。单独看来这是一个有价值的见解但其本身价值或许并不比“正确使用面向对象技术”多很多。
使其深入的是DDD引入的技术语言和概念实体、值对象、服务、资源库等。这确保了在处理非常大的项目时我们的大型开发团队有可能一致地进行开发——长远来看这对维护质量是必不可少的。甚至在废弃我们更底层的代码时我们稍后会进行演示统一的技术语言能让我们恢复到过去、改进代码质量。
2.2. 在系统中嵌入领域模型
本节显示了DDD在整个系统架构中的地位。
我们的系统逐渐建立了三个主要的组件渲染应用的用户界面网站面向编辑、用于创建和管理内容的应用程序跟系统交互数据的供稿系统。这些应用都是基于Spring和Hibernate构建的Java Web应用并使用Velocity作为我们的模板语言。
我们可以看下这些应用的布局 Hibernate层提供数据访问使用EHCache作为Hibernate的二级缓存。模型层包含领域对象和资源库服务则处于其上一层。在此之上我们有Velocity模板层它提供页面渲染逻辑。最顶层则包含控制器是应用的入口点。
看一下这个应用的分层架构仅仅将模型当做是应用的一个自包含的层是很有意思的。这个想法大致正确但模型层和其它层之间还是有一些细微的差别由于我们使用领域驱动设计所以我们需要统一语言不仅要在我们谈论领域时使用还要在应用的任何地方使用。模型层的存在不仅是为了从渲染逻辑中分离业务逻辑还要为使用的其它层提供词汇表。
另外模型层可作为代码的独立单元进行构建而且可以作为JAR包导入到依赖于它的许多应用中。其它任何层则不是这样。对构建和发布应用来说这意味着在我们基础设施的模型层改变代码一定是跨所有应用的全局变化。我们可以在前端的网站中改变Velocity模板只用部署前端应用就可以管理系统或供稿系统则不用。如果我们在领域模型中改变了一个对象的逻辑我们必须更新所有依赖于模型的应用因为我们只有而且期望只有一个领域视图。
这有一种危害就是领域建模的这种方法可能会导致单一模型如果业务领域非常庞大改变起来的代价会非常昂贵。我们认识到了这一点因此随着领域不断增长我们必须确保该层没有变得太过笨重。目前虽然领域层也是相当庞大和复杂的但是这还没有带来什么问题。在敏捷环境中工作我们希望无论如何每两周都要推出所有系统的最新变化。但我们持续关注着该层代码改变的成本。如果成本上升到不能接受的程度我们可能会考虑将单一模型细分成多个更小的模型并在每个子模型之间给出适配层。但是我们在项目开始时没有这样做我们更偏向于单一模型的简单性而不是用多个模型时必须解决的更为复杂的依赖管理问题。
2.3. 早期的领域建模
在项目初期大家在键盘上动手开始编写代码之前我们就决定让开发人员、QA、BA、业务人员在同一个房间里一起工作以便项目能够持续。在这个阶段我们有一个由业务人员和技术人员组成的小型团队而且我们只要求有一个稳妥的初次发布。这确保了我们的模型和过程都相当简单。
我们的首要目标是让编辑我们业务代表的关键组成部分就项目最初迭代的期望有一个清楚的认识。我们跟编辑们坐在一起就像一个整体团队一样用英语与他们交流想法允许各个功能的代表对这些想法提出质疑和澄清直到他们认为我们正确理解了编辑需要的内容。
我们的编辑认为项目初期优先级最高的功能是系统能生成网页这些网页能显示文章和文章分类系统。
他们最初的需求可归纳为
我们应该能将一篇文章和任何给定的URL关联起来。我们要能改变选定的不同模板渲染结果页面的方式。为了管理我们要将内容纳入宽泛的版面也就是新闻、体育、旅游。系统必须能显示一个页面该页面包含指向特定分类中所有文章的链接。
我们的编辑需要一种非常灵活的方式来对文章进行分类。他们采用了基于关键字的方法。每个关键字定义了一个与内容相关的主题。每篇文章可以和很多关键字关联因为一篇文章可以有很多主题。
我们网站有很多编辑每个人负责不同版块的内容。每个版块都要求有自己导航和特有关键字的所有权。
从编辑使用的语言来看我们似乎在领域中引入了一些关键实体
页面 URL的拥有者。负责选择模板来渲染内容。模板 页面布局任何时候都有可能改变。技术人员将每个模板实现为磁盘上的一个Velocity文件。版块 页面更宽泛的分类。每个版块有一个编辑还有对其中页面共同的感官。新闻、旅游和商业都是版块的例子。关键字 描述存在于版块中主题的方式。关键字过去用于文章分类现在则驱动自动导航。这样它们将与某个页面关联关于给定主题所有文章的自动页面也能生成。文章 我们能发布给用户的一段文本内容。
提取这些信息后我们开始对领域建模。项目早期做出的一个决定编辑拥有领域模型并负责设计技术团队则提供协助。对过去不习惯这种技术设计的编辑来说这是相当大的转变。我们发现通过召开由编辑、开发人员、技术架构师组成的研习会我们能用简单、技术含量较低的方法勾画出领域模型并对它持续演进。讨论模型的范围使用钢笔、档案卡和Blu-Tak绘制备选解决方案。每个候选模型都会进行讨论技术团队把设计中的每个细节含义告诉给编辑。
尽管过程在最初相当缓慢但很有趣。编辑发现这非常容易上手他们能信手拈来、提出对象然后及时从开发人员那里获得反馈以知道生成的模型是否满足了他们的需求。对于编辑们能在过程中迅速掌握技术的要领技术人员都感到很惊喜而且所有人都对生成的系统是否能满足客户需求很有信心。
观察领域语言的演变也很有趣。有时文章对象会被说成是“故事”。显然对于同一实体的多个名称我们并没有一种统一语言这是一个问题。是我们的编辑发现他们描述事物时没有使用统一的语言也是他们决心称该对象为文章。后来任何时间有人说“故事”就会有人说“你的意思不是文章吗”在设计统一语言时持续、公共的改进过程是种很强大的力量。
我们的编辑最初设计生成的模型是这样的 [网页及其版块之间的关系由应用于文章的关键字导出文章是网页中的核心内容。网页的版块由应用于文章的首个关键字的版块确定。]
由于并非所有的团队成员都参与了该过程的所有阶段所以我们需要向他们介绍工作进展并将这些全部展现在了墙上。然后开发人员开始了敏捷开发之旅而且由于编辑和开发人员、BA、QA都在一起工作任何有关模型及其意图的问题都能在开发过程的任何时候获得第一手的可靠信息。
经过几次迭代之后系统初具形态我们还建立工具来创建和管理关键字、文章和页面。随着这些内容的创建编辑们很快掌握了它们并且给出了一些修改建议。大家普遍认为这一简单的关键模型能正常运行而且可以继续下去、形成网站初次发布的基础。
3. 增量计划中的DDD过程
首次发布之后我们的项目团队伴随着技术人员和业务代表们的成长一起取得了进步打算演进领域模型。很显然我们需要一种有组织的方式来为领域模型引入新的内容、进行系统的演进。
3.1. 新员工入门
DDD是入门过程中的核心部分。非技术人员在整个项目生命周期内都会加入项目因为工作计划是横跨各个编辑领域的反过来在恰当的时机我们也会引入版面编辑。技术人员很容易就能加入项目因为我们持续不断地雇用新员工。
我们的入门过程包括针对这两类人的DDD讲习尽管细节不同但高层次的议题涵盖两个相同的部分DDD是什么它为什么重要领域模型本身的特定范围。
描述DDD时我们强调的最重要的内容有
领域模型归业务代表所有。这就要从业务代表的头脑里抽象概念并将这些概念嵌入到软件中而不能从软件的角度思考并试图影响业务代表。技术团队是关键的利益相关者。我们将围绕具体细节据理力争。
覆盖领域模型的特定范围本身就很重要因为它予以就任者处理项目中特定问题的真正工具。我们的领域模型中有几十个对象所以我们只关注于高级别和更为明显的少数几个在我们的情况中就是本文所讨论的各种内容和关键字概念。在这里我们做了三件事
我们在白板上画出了概念及其关系因此我们能给出系统如何工作的一个有形的表示。我们确保每位编辑当场解释大量的领域模型以强调领域模型不属于技术团队所有这一事实。我们解释一些为了达到这一点而做出的历史变迁所以就任者可以理解(a)这不是一成不变的而是多变的(b)为了进一步开发模型他们可以在即将进行的对话中扮演什么样的角色。
3.2. 规划中的DDD
入门是必不可少的不过在开始计划每次迭代的时候知识才真正得以实践。
3.2.1 使用统一语言
DDD强制使用的统一语言使得业务人员、技术人员和设计师能围坐在一起规划并确定具体任务的优先次序。这意味着有很多会议与业务人员有关他们更接近技术人员也更加了解技术过程。有一位同事她先担任项目的编辑助理然后成长为关键的决策者她解释说在迭代启动会议上她亲自去看技术人员怎样判断和激烈地评估任务开始更多地意识到功能和努力之间的平衡。如果她不和技术团队共用一种语言她就不会一直出席会议也不会收获那些认识。
在规划阶段利用DDD时我们使用的两个重要原则是
领域模型归属于业务领域模型需要一个权威的业务源。
领域模型的业务所有权在入门中就进行了解释但在这里才发挥作用。这意味着技术团队的关键角色是聆听并理解而不是解释什么可能、什么不可能。需求抽象要求将概念性的领域模型映射到具体的功能需求上并在存在不匹配的地方对业务代表提出异议或进行询问。接着存在不匹配的地方要么改变领域模型要么在更高层次上解决功能需求“你想用此功能达成什么效果”。
对领域模型来说权威的业务源是我们组织的性质所明确需要的。我们正在构建一个独立的软件平台它需要满足很多编辑团队的需求编辑团队不一定以同样的方式来看世界。Guardian不实行许多公司实行的“命令和控制”结构编辑台则有很多自由也能以他们认为合适的方式去开发自己的网站版面并设定预期的读者。因此不同的编辑对领域模型会有略微不同的理解和观点这有可能会破坏单一的统一语言。我们的解决办法是确定并加入业务代表他们在整个编辑台都有责任。对我们来说这是生产团队是那些处理日常构建版面、指定布局等技术细节的人。他们是文字编辑依赖的超级用户作为专家工具建议因此技术团队认为他们是领域模型的持有者而且他们保证软件中大部分的一致性。他们当然不是唯一的业务代表但他们是与技术人员保持一致的人员。
3.2.2 与DDD一起计划的问题
不幸的是我们发现了在计划过程中应用DDD特有的挑战尤其是在持续计划的敏捷环境中。这些问题是
本质上讲我们正在将软件写入新的、不确定商业模式中绑定到一个旧模型业务人员“入乡随俗”。
我们反过来讨论下这些问题……
EricEvans撰写关于创建领域模型的文章时观点是业务代表的脑海中存在着一个模型该模型需要提取出来即便他们的模型不明确他们也明白核心概念而且这些概念基本上能解释给技术人员。然而在我们的情况中我们正在改变我们的模型——事实上是在改变我们的业务——但并不知道我们目标的确切细节。马上我们就会看到这一点的具体例子。某些想法显而易见也很早就建立起来了比如我们会有文章和关键字但很多并不是这样引入页面的想法还有一些阻力关键字如何关联到其它内容则完全是各有各的想法。我们的教科书并没有提供解决这些问题的指南。不过敏捷开发原则则可以
构建最简单的东西。尽管我们无法在早期解决所有的细节但通常能对构建下一个有用的功能有足够的理解。频繁发布。通过发布此功能我们能看到功能如何实际地运转。进一步的调整和进化步骤因此变得最为明显不可避免它们往往不是我们所预期的。降低变化的成本。利用这些不可避免的调整和进化步骤减少变化的成本很有必要。对我们来说这包括自动化构建过程和自动化测试等。经常重构。经过几个演进步骤我们会看到技术债务累积这需要予以解决。
与此相关的是第二个问题与旧模型有太多的精神联系。比如说我们的遗留系统要求编辑和制作人员单独安排页面布局而新系统的愿景则是基于关键字自动生成页面。在新系统里Guantánamo Bay页面无需任何人工干预许多内容会给出GuantánamoBay关键字仅简单地凭借这一事实就能显示出来。但结果却是这只是由技术团队持有的过度机械化的愿景技术团队希望减少体力劳动和所有页面的持续管理。相比之下编辑人员高度重视人的洞察力他们带入过程的不仅有记述新闻还有展现新闻对他们来说为了突出最重要的故事而不仅仅是最新的为了用不同的方法和灵敏性比如9·11和Web 2.0报导区别对待不同的主题个人的布局是很有必要的。
对这类问题没有放之四海而皆准的解决办法但我们发现了两个成功的关键专注于业务问题而不是技术问题铭记“创造性冲突”这句话。在这里的例子里见解有分歧但双方表达了他们在商业上的动机后我们就开始在同一个环境里面工作了。该解决方案是创造性的源于对每个人动机的理解也因此解决了大家了疑虑。在这种情况下我们构建了大量的模板每个都有不同的感觉和影响等编辑可以从中选择并切换。此外每个模板的关键区域允许手动选择显示的故事、页面的其余部分自动生成内容小心不要重复内容对于该手动区域如果管理变得繁重就可以随时关闭从而使网页完全自动化。
我们发现的第三个挑战是随着业务人员“入乡随俗”也就是说他们已深深融入技术且牵涉到了要点以至于他们会忘记对新使用系统的内部用户来说系统该是什么样。当业务代表发现跟他们的同事很难沟通事情如何运转或者很难指出价值有限的功能时就有危险的信号了。KentBeck在《解析极限编程》第一版中说现场客户与技术团队直接交互绝不会占用他们很多的时间通过强调这一点就可以保证在现场的客户。但我们在与有几十个开发人员、多名BA、多名QA的团队一起工作时我们发现即使有三个全职的业务代表有时也是不够的。由于业务人员花费了太多的时间与技术人员在一起以至他们与同事失去联系成为真正的问题。这些都是人力解决方案带来的人力问题。解决方案是要提供个人备份和支持让新的业务人员轮流加入团队可能从助理开始着手进行逐步成长为关键决策角色允许代表有时间回归他们自己的核心工作比如一天、一周等。事实上这还有一个附加的好处就是能让更多的业务代表接触到软件开发还可以传播技巧和经验。
4. 进化的领域模型
在本章中我们看看模型在方案的后期是如何演进的。
4.1. 演进第一步超越文章
首次发布后不久编辑要求系统能处理更多的内容类型而非只有文章一类。尽管这对我们来说毫不稀奇但在我们构建模型的第一个版本时我们还是明确决定对此不予考虑太多。
这是个关键点我们关注整个团队能很好地理解小规模、可管理块中的模型和建模过程而不是试图预先对整个系统进行架构设计。随着理解的深入或变化稍后再改变模型并不是个错误。这种做法符合YAGNI编码原则你不会需要它因为该做法防止开发人员引入额外的复杂度因而也能阻止Bug的引入。它还能让整个团队安排时间去对系统中很小的一块达成共识。我们认为今天产出一个可工作的无Bug系统要比明天产出一个完美的、包括所有模型的系统更为重要。
我们的编辑在下一个迭代中要求的内容类型有音频和视频。我们的技术团队和编辑再次坐在一起讨论了领域建模过程。编辑先跟技术团队谈道音频和视频很显然与文章相似应该可以将视频或音频放在一个页面上。每个页面只允许有一种内容。视频和音频可以通过关键字分类。关键字可以属于版面。编辑还指明在以后的迭代中他们会添加更多类型的内容他们认为现在该是时候去理解我们应该如何随着时间的推移去演进内容模型。
对我们的开发人员来说很显然编辑想在语言中明确引入两个新条目音频和视频。音频、视频和文章有一些共同点它们都是内容类型这一点也很明确。我们的编辑并不熟悉继承的概念所以技术团队可以给编辑讲解继承以便技术团队能正确表述编辑所看到的模型。
这里有一个明显的经验通过利用敏捷开发技术将软件开发过程细分为小的块我们还能使业务人员的学习曲线变得平滑。久而久之他们能加深对领域建模过程的理解而不用预先花费大量的时间去学习面向对象设计所有的组件。
这是我们的编辑根据添加的新内容类型设计的模型。 这个单一的模型演变是大量更细微的通用语言演进的结果。现在我们有三个外加的词音频、视频和内容我们的编辑已经了解了继承并能在以后的模型迭代中加以利用对添加新的内容类型我们也有了以后的扩展策略并使其对我们的编辑来说是简单的。如果编辑需要一个新的内容类型而这一新的内容类型与我们已有的内容类型相同、在页面和关键字之间有大致相同的关系那编辑就能要求开发团队产出一个新的内容类型。作为一个团队我们正通过逐步生成模型来提高效率因为我们的编辑不会再详细检查漫长的领域建模过程去添加新的内容类型。
4.2. 演进第二步
由于我们的模型要扩展到包括更多的内容类型它需要更灵活地去分类。我们开始在领域模型中添加额外的元数据但编辑的最终意图是什么还不是非常清楚。然而这并不让我们太过担忧因为我们对元数据进行建模的方法与处理内容的方法一样将需求细分为可管理的块将每个添加到我们的领域中。
我们的编辑想添加的第一个元数据类型是系列这一概念。系列是一组相关的内容内容则有一个基于时间的隐含顺序。在报纸中有很多系列的例子也需要将这一概念解释为适用于Web的说法。
我们对此的初步想法非常简单。我们将系列添加为一个领域对象它要关联到内容和页面。这个对象将用来聚集与系列关联的内容。如果读者访问了一种内容该内容属于某个系列我们就能从页面链接到同一系列中的前一条和后一条内容。我们还能链接到并生成系列索引页面该页面可以显示系列中的所有内容。
这里是编辑所设计的系列模型 与此同时我们的编辑正在考虑更多的元数据他们想让这些元数据与内容关联。目前关键字描述了内容是关于什么的。编辑还要求系统能根据内容的基调对内容进行不同的处理。不同基调的例子有评论、讣告、读者供稿、来信。通过引入基调我们就可以将其显示给读者让他们找到类似的内容其它讣告、评论等。这像是除关键字或系列外另一种类型的关系。跟系列一样基调可以附加到一条内容上也能和页面有关系。
这里是编辑针对基调设计的模型 完成开发后我们有了一个能根据关键字、系列或基调对内容进行分类的系统。但编辑对达到这一点所需的技术工作量还有一些关注点。他们在我们下次演进模型时向技术团队提出了这些关注点并能提出解决方案。
4.3. 演进第三步重构元数据
模型的下一步演进是我们的编辑想接着添加类似于系列和基调的内容。我们的编辑想添加带有贡献者的内容这一概念。贡献者是创建内容的人可能是文章的作者或者是视频的制作人。跟系列一样贡献者在系统中有一个页面该页面会自动聚集贡献者制作的所有内容。
编辑还看到了另一个问题。他们认为随着系列和基调的引入他们已经向开发人员指明了大量非常相似的细节。他们要求构建一个工具去创建系列构建另一个工具去创建基调。他们不得不指明这些对象如何关联到内容和页面上。每次他们都发现他们在为这两种类型的领域对象指定非常相似的开发任务这很浪费时间还是重复的。编辑更加关注于贡献者还有更多的元数据类型会加入进来。这看起来又要让编辑再次指明、处理大量昂贵的开发工作所有这些都非常相似。
这显然成为一个问题。我们的编辑似乎已经发现了模型的一些错误而开发人员还没有。为什么添加新的元数据对象会如此昂贵呢为什么他们不得不一遍又一遍地去指定相同的工作呢我们的编辑问了一个问题该问题是“这仅仅是‘软件开发如何工作’还是模型有问题”技术团队认为编辑熟悉一些事情因为很显然他们理解模型的方式与编辑不同。我们与编辑一起召开了另一个领域建模会议试图找出问题所在。
在会议上我们的编辑建议所有已有的元数据类型实际上源于相同的基本思想。所有的元数据对象关键字、系列、基调和贡献者可以和内容有多对多的关系而且它们都需要它们自己的页面。在先前的模型版本中我们不得不知道对象和页面之间的关系。我们重构了模型引入了一个新的超类——Tag标签并作为其它元数据的超类。编辑们很喜欢使用“超类”这一技术术语将整个重构称为“Super-Tag”尽管最终也回到了现实。
由于标签的引入添加贡献者和其它预期的新元数据类型变得很简单因为我们能够利用已有的工具功能和框架。
我们修订后的模型现在看起来是这样的 我们的业务代表在以这种方式考虑开发过程和领域模型发现这一点非常好还发现领域驱动设计有能力促进在两个方向都起作用的共同理解我们发现技术团队对我们正努力解决的业务问题有良好且持续的理解而且出乎意料业务代表能“洞察”开发过程还能改变这一过程以更好地满足他们的需求。编辑们现在不仅能将他们的需求翻译为领域模型还能设计、检查领域模型的重构以确保重构能与我们目前对业务问题的理解保持同步。
编辑规划领域模型重构并成功执行它们的能力是我们领域驱动设计guardian.co.uk成功的一个关键点。
5. 代码级别的演进
前面我们看了领域模型方面的进化。但DDD在代码级别也有影响不断变化的业务需求也意味着代码要有变化。现在我们来看看这些变化。
5.1. 构建模型
在构建领域模型时要确认的第一件事就是领域中出现的聚集。聚集可认为是相关对象的集合这些对象彼此相互引用。这些对象不应该直接引用其它聚集中的其它对象不同聚集之间的引用应该由根聚集来完成。 看一下我们在上面定义的模型示例我们开始看到对象成形。我们有Page和Template对象它们结合起来能给Web页面提供URL和观感。由于Page是系统的入口点所以在这里Page就是根聚集。
我们还有一个聚集Content它也是根聚集。我们看到Content有Article、Video、Audio等子类型我们认为这些都是内容的子聚集核心的Content对象则是根聚集。
我们还看到形成了另一个聚集。它是元数据对象的集合Tag、Series、Tone等。这些对象组成了标签聚集Tag是根聚集。
Java编程语言提供了理想的方式来对这些聚集进行建模。我们可以使用Java包来对每个聚集进行建模使用标准的POJO对每个领域对象进行建模。那些不是根聚集、且只在聚集中使用的领域对象可以有包范围内使用的构造函数以防它们在聚集外被构造。
上述模型的包结构如下所示“r2”是我们应用套件的名称 com.gu.r2.model.page com.gu.r2.model.tag com.gu.r2.model.content com.gu.r2.model.content.articlecom.gu.r2.model.content.videocom.gu.r2.model.content.audio
我们将内容聚集细分为多个子包因为内容对象往往有很多聚集特定的支持类这里的简化图中没有显示。所有以标签为基础的对象往往要更为简单所以我们将它们放在了一个包里而没有引入额外的复杂性。
不过不久之后我们认识到上述包结构会给我们带来问题我们打算修改它。看看我们前端应用的包结构示例了解一下我们如何组织控制器就能阐述清楚这一问题 com.gu.r2.frontend.controller.pagecom.gu.r2.frontend.controller.articl
这里看到我们的代码集要开始细分为片段。我们提取了所有的聚集将其放入包中但我们没有单独的包去包含与聚集相关的所有对象。这意味着如果以后领域变得太大而不能作为一个单独的单元来管理我们希望将应用分解处理依赖就会有困难。目前这还没有真正带来什么问题但我们要重构应用以便不会有太多的跨包依赖。经过改进的结构如下 com.gu.r2.page.model (domain objects in the page aggregate)com.gu.r2.page.controller (controllers providing access to aggregate)com.gu.r2.content.article.modelcom.gu.r2.content.article.controller...etc
除了约定我们在代码集中没有其它任何的领域驱动设计实施原则。创建注解或标记接口来标记聚集根是有可能的实际上是争取在模型包锁定开发减少开发人员建模时出错的几率。但实际上并不是用这些机械的强制来保证在整个代码集中都遵循标准约定而是我们更多地依赖了人力技术比如结对编程和测试驱动开发。如果我们确实发现已创建的一些内容违反了我们的设计原则这相当少见那我们会告诉开发人员并让他完善设计。我们还是喜欢这个轻量级的方法因为它很少在代码集中引入混乱反而提升了代码的简单性和可读性。这也意味着我们的开发人员更好地理解了为什么一些内容是按这种方式组织而不是被迫去简单地做这些事情。
5.2. 核心DDD概念的演进
根据领域驱动设计原则创建的应用会具有四种明显的对象类型实体、值对象、资源库和服务。在本节中我们将看看应用中的这些例子。
5.2.1 实体
实体是那些存在于聚集中并具有标识的对象。并不是所有的实体都是聚集根但只有实体才能成为聚集根。
开发人员尤其是那些使用关系型数据库的开发人员都很熟悉实体的概念。不过我们发现这个看似很好理解的概念却有可能引起一些误解。
这一误解似乎跟使用Hibernate持久化实体有点儿关系。由于我们使用Hibernate我们一般将实体建模为简单的POJO。每个实体具有属性这些属性可以利用setter和getter方法进行存取。每个属性都映射到一个XML文件中定义该属性如何持久化到数据库中。为了创建一个新的持久化实体开发人员需要创建用于存储的数据库表创建适当的Hibernate映射文件还要创建有相关属性的领域对象。由于开发人员要花费一些时间处理持久化机制他们有时似乎认为实体对象的目的仅仅只是数据的持久化而不是业务逻辑的执行。等他们后来开始实现业务逻辑时他们往往在服务对象中实现而不是在实体对象本身中。
在下面简化的代码片段中可以看出此类错误。我们用一个简单的实体对象来表示一场足球赛 public class FootballMatch extends IdBasedDomainObject{ private final FootballTeam homeTeam;private final FootballTeam awayTeam;private int homeTeamGoalsScored;private int awayTeamGoalsScored;FootballMatch(FootballTeam homeTeam, FootballTeam awayTeam) { this.homeTeam homeTeam; this.awayTeam awayTeam; } public FootballTeam getHomeTeam() { return homeTeam; } public FootballTeam getAwayTeam() { return awayTeam; } public int getHomeTeamScore() { return homeTeamScore; } public void setHomeTeamScore(int score) { this.homeTeamScore score; } public void setAwayTeamScore(int score) { this.awayTeamScore score; } }
该实体对象使用FootballTeam实体去对球队进行建模看起来很像使用Hibernate的开发人员所熟悉的对象类型。该实体的每个属性都持久化到数据库中尽管从领域驱动设计的角度来说这个细节并不真的重要我们的开发人员还是将持久化的属性提升到一个高于它们应该在的水平上去。在我们试图从FootballTeam对象计算出谁赢得了比赛的时候这一点就可以显露出来。我们的开发人员要做的事情就是造出另一种所谓的领域对象就像下面所示 public class FootballMatchSummary { public FootballTeam getWinningTeam(FootballMatch footballMatch) { if(footballMatch.getHomeTeamScore() footballMatch.getAwayTeamScore()) { return footballMatch.getHomeTeam(); } return footballMatch.getAwayTeam(); } }
片刻的思考应该表明已经出现了错误。我们已经创建了一个FootballMatchSummary类该类存在于领域模型中但对业务来说它并不意味着什么。它看起来是充当了FootballMatch对象的服务对象提供了实际上应该存在于FootballMatch领域对象中的功能。引起这一误解的原因好像是开发人员将FootballMatch实体对象简单地看成了是反映数据库中持久化信息而不是解决所有的业务问题。我们的开发人员将实体考虑为了传统ORM意义上的实体而不是业务所有和业务定义的领域对象。
不愿意在领域对象中加入业务逻辑会导致贫血的领域模型如果不加以制止还会使混乱的服务对象激增——就像我们等会儿看到的一样。作为一个团队现在我们来检视一下创建的服务对象看看它们实际上是否包含业务逻辑。我们还有一个严格的规则就是开发人员不能在模型中创建新的对象类型这对业务来说并不意味着什么。
作为团队我们在项目开始时还被实体对象给弄糊涂了而且这种困惑也与持久化有关。在我们的应用中大部分实体与内容有关而且大部分都被持久化了。但当实体不被持久化而是在运行时由工厂或资源库创建的话有时候还是会混淆。
一个很好的此类例子就是“标签合成的页面”。我们在数据库中持久化了编辑创建的所有页面的外观但我们可以自动生成从标记组合比如USAEconomics或TechnologyChina聚集内容的页面。由于所有可能的标记组合总数是个天文数字我们不可能持久化所有的这些页面但系统还必须能生成页面。在渲染标记组合页面时我们必须在运行时实例化尚未持久化的新Page实例。项目初期我们倾向于认为这些非持久化对象与“真正的”持久化领域对象不同也不像在对它们建模时那么认真。从业务观点看这些自动生成的实体与持久化实体实际上并没有什么不同因此从领域驱动设计观点来看也是如此。不论它们是否被持久化对业务来说它们都有同样的定义都不过是领域对象没有“真正的”和“不是真正的”领域对象概念。
5.2.2 值对象
值对象是实体的属性它们没有特性标识去指明领域中的内容但表达了领域中有含义的概念。这些对象很重要因为它们阐明了统一语言。
值对象阐述能力的一个例子可以从我们的Page类更详细地看出。系统中任何Page都有两个可能的URLs。一个URL是读者键入Web浏览器以访问内容的公开URL。另一个则是从应用服务器直接提供服务时内容依存的内部URL。我们的Web服务器处理从用户那里传入的URL并将它转换为适合后端CMS服务器的内部URL。
一种简化的观点是在Page类中两个可能的URL都被建模为字符串对象 public String getUrl(); public String getCmsUrl();
不过这并没有什么特别的表现力。除了这些方法返回字符串这一事实之外只看这些方法的签名很难确切地知道它们会返回什么。另外想象一下这种情况我们想基于页面的URL从一个数据访问对象中加载页面。我们可能会有如下的方法签名
public Page loadPage(String url);
这里需要的URL是哪一个呢是公开的那个还是CMS URL不检查该方法的代码是不可能识别出来的。这也很难与业务人员谈论页面的URL。我们指的到底是哪一个呢在我们的模型中没有表示每种类型URL的对象因此在我们的词汇里面也就没有相关条目。
这里还含有更多的问题。我们对内部URL和外部URL可能有不同的验证规则也希望对它们执行不同的操作。如果我们没有地方放置这个逻辑那我们怎么正确地封装该逻辑呢控制URLs的逻辑一定不属于Page我们也不希望引入更多不必要的服务对象。
领域驱动设计建议的演进方案是明确地对这些值对象进行建模。我们应该创建表示值对象的简单包装类以对它们进行分类。如果我们这样做Page里面的签名就如下所示 public Url getUrl();public CmsPath getCmsPath();
现在我们可以在应用中安全地传递CmsPath或Url对象也能用业务代表理解的语言与他们谈论代码。
5.2.3 资源库
资源库是存在于聚集中的对象在抽象任何持久化机制时提供对聚集根对象实体的访问。这些对象由业务问题请求与领域对象一起响应。
将资源库看成是类似于有关数据库持久化功能的数据访问对象而非存在于领域中的业务对象这一点很不错。但资源库是领域对象他们响应业务请求。资源库始终与聚集关联并返回聚集根的实例。如果我们请求一个页面对象我们会去页面资源库。如果我们请求一个页面对象列表来响应特定的业务问题我们也会去页面资源库。
我们发现一个很好的思考资源库的方式就是把它们看成是数据访问对象集合之上的外观。然后它们就成为业务问题和数据传输对象的结合点业务问题需要访问特定的聚集而数据传输对象提供底层功能。
这里举一小段页面资源库的代码例子我们来实际看下这个问题 private final PageDAOPage pageDAO; private final PagesRelatedBySectionDAO pagesRelatedBySectionDAO; public PageRepository(PageDAOPage pageDAO,EditorialPagesInThisSectionDAO pagesInThisSectionDAO, PagesRelatedBySectionDAO pagesRelatedBySectionDAO) { this.pageDAO pageDAO; this.pagesRelatedBySectionDAO pagesRelatedBySectionDAO;} public ListPage getAudioPagesForPodcastSeriesOrderedByPublicationDate(Series series, int maxNumberOfPages) { return pageDAO.getAudioPagesForPodcastSeriesOrderedByPublicationDate(series, maxNumberOfPages); } public ListPage getLatestPagesForSection(Section section, int maxResults) {return pagesRelatedBySectionDAO.getLatestPagesForSection(section, maxResults); }
我们的资源库有业务请求获取PublicationDate请求的特定播客系列的页面。获取特定版面的最新页面。我们可以看看这里使用的业务领域语言。它不仅仅是一个数据访问对象它本身就是一个领域对象跟页面或文章是领域对象一样。
我们花了一段时间才明白把资源库看成是领域对象有助于我们克服实现领域模型的技术问题。我们可以在模型中看到标签和内容是一种双向的多对多关系。我们使用Hibernate作为ORM工具所以我们对其进行了映射Tag有如下方法
public ListContent getContent();
Content有如下方法 public ListTag getTags();
尽管这一实现跟我们的编辑看到的一样是模型的正确表达但我们有了自己的问题。对开发人员来说代码可能会编写成下面这样 if(someTag.getContent().size() 0){... do some stuff}
这里的问题是如果标签关联有大量的内容比如“新闻”我们最终可能会往内存中加载几十万的内容条目而只是为了看看标记是否包含内容。这显然会引起巨大的网站性能和稳定性问题。
随着我们演进模型、理解了领域驱动设计我们意识到有时候我们必须要注重实效模型的某些遍历可能是危险的应该予以避免。在这种情况下我们使用资源库来用安全的方式解决问题会为系统的性能和稳定性牺牲模型个别的清晰性和纯洁性。
5.2.4. 服务
服务是通过编排领域对象交互来处理业务问题执行的对象。我们所理解的服务是随着我们过程演进而演进最多的东西。
首要问题是对开发人员来说创建不应该存在的服务相当容易他们要么在服务中包含了本应存在于领域对象中的领域逻辑要么扮演了缺失的领域对象角色而这些领域对象并没有作为模型的一部分去创建。
项目初期我们开始发现服务开始突然涌现带着类似于ArticleService的名字。这是什么呀我们有一个领域对象叫Article那文章服务的目的是什么检查代码时我们发现该类似乎步了前面讨论的FootballMatchSummary的后尘有类似的模式包含了本该属于核心领域对象的领域逻辑。
为了对付这一行为我们对应用中的所有服务进行了代码评审并进行重构将逻辑移到适当的领域对象中。我们还制定了一个新的规则任何服务对象在其名称中必须包含一个动词。这一简单的规则阻止了开发人员去创建类似于ArticleService的类。取而代之我们创建ArticlePublishingService和ArticleDeletionService这样的类。推动这一简单的命名规范的确帮助我们将领域逻辑移到了正确的地方但我们仍要求对服务进行定期的代码评审以确保我们在正轨上以及对领域的建模接近于实际的业务观点。
6. 演进架构中DDD的一些教训
尽管面临挑战但我们发现了在不断演进和敏捷的环境中利用DDD的显著优势此外我们还总结了一些经验教训
你不必理解整个领域来增加商业价值。你甚至不需要全面的领域驱动设计知识。团队的所有成员差不多都能在他们需要的任何时间内对模型达成一个共同的理解。随着时间的推移演进模型和过程是可能的随着共同理解的提高纠正以前的错误也是可能的。
我们系统的完整领域模型要比这里描述的简化版本大很多而且随着我们业务的扩展在不断变化。在一个大型网站的动态世界里创新永远发生着我们始终要保持领先地位并有新的突破对我们来说有时很难在第一次就得到正确的模型。事实上我们的业务代表往往想尝试新的想法和方法。有些人会取得成果其他人则会失败。是逐步扩展现有领域模型——甚至在现有领域模型不再满足需求时进行重构——的业务能力为guardian.co.uk开发过程中遇到的大部分创新提供了基础。
7. 附录具体示例
为了了解我们的领域模型如何生成真实的结果这里给出了一个例子先看单独的内容……
有关页面本身的一些音频内容它有几个标签贡献者是Matt Wells关键字包括“Digital Media”和“Radio”它属于“Media Talk”系列。这些标签都链接在页面上。Matt Wells有他自己的页面而且有特定的模板“Digital Media”关键字也有其自己的页面使用不用的模板“Media Talk”系列也有自己的页面音频内容有一个页面页面列出了所有的音频标签组合页面可以完全实时生成。
8. 关于作者
Nik Silver是Guardian News Media软件开发总监。他于2003年在公司引入敏捷软件开发负责软件开发、前端开发和质量保证。Nik偶尔会在blogs.guardian.co.uk/inside上写Guardian技术工作相关的内容并在他自己的站点niksilver.com上写更宽泛的软件问题。
Matthew Wall是Guardian News Media的软件架构师深入研究敏捷环境下大型Web应用的开发。他目前最关心的是为guardian.co.uk开发下一代的Web平台。他在JAOO、ServerSide、QCon、XTech和OpenTech上做过关于此及相关主题的各种演讲。 Web架构设计经验分享 作者朱燚 来源http://www.cnblogs.com/yizhu2000/archive/2007/12/04/982142.html 本人作为一位web工程师着眼最多之处莫过于 性能与架构本次幸得参与sd2.0大会得以与同行广泛交流,于此二方面有些心得不敢独享与众博友分享本文是这次参会与众同撩交流的心得有兴趣者可以查看视频
架构设计的几个心得 一不要过设计never over design 这是一个常常被提及的话题但是只要想想你的架构里有多少功能是根本没有用到或者最后废弃的就能明白其重要性了初涉架构设计往往倾向于设计大而化一的架构希望设计出具有无比扩展性能适应一切需求的增加架构web开发领域是个非常动态的过程我们很难预测下个星期的变化而又需要对变化做出最快最有效的响应。。
ebay的工程师说过他们的架构设计从来都不能满足系统的增长所以他们的系统永远都在推翻重做。请注意不是ebay架构师的能力有问题他们设计的架构总是建立旧版本的瓶颈上希望通过新的架构带来突破然而新架构带来的突破总是在很短的时间内就被新增需求淹没于是他们不得不又使用新的架构 web开发是个非常敏捷的过程变化随时都在产生用户需求千变万化许多方面偶然性非常高较之软件开发希望用一个架构规划以后的所有设计是不现实的
二web架构生命周期web architecture‘s life cycle 既然要杜绝过设计又要保证一定的前瞻性那么怎么才能找到其中的平衡呢希望下面的web架构生命周期能够帮到你 所设计的架构需要在110倍的增长下通过简单的增加硬件容量就能够胜任而在510倍的增长期间请着手下一个版本的架构设计使之能承受下一个10倍间的增长
google之所以能够称霸不完全是因为搜索技术和排序技术有多先进其实包括baidu和yahoo所使用的技术现在也已经大同小异然而google能在一个月内通过增加上万台服务器来达到足够系统容量的能力确是很难被复制的 三缓存Cache 空间换取时间缓存永远计算机设计的重中之重从cpu到io到处都可以看到缓存的身影web架构设计重缓存设计必不可少关于怎样设计合理的缓存jbosscache的创始人淘宝的创始人是这样说的其实设计web缓存和企业级缓存是非常不同的企业级缓存偏重于逻辑而web缓存简单快速为好。。
缓存带来的问题是什么是程序的复杂度上升因为数据散布在多个进程所以同步就是一个麻烦的问题加上集群复杂度会进一步提高在实际运用中采用怎样的同步策略常常需要和业务绑定
老钱为搜狐设计的帖子设计了链表缓存这样既可以满足灵活插入的需要又能够快速阅读而其他一些大型社区也经常采用类此的结构来优化帖子列表memcache也是一个常常用到的工具
钱宏武谈架构设计视频 http://211.100.26.82/CSDN_Live/140/qhw.flv
Cache的常用的策略是让数据在内存中而不是在比较耗时的磁盘上。从这个角度讲mysql提供的heap引擎存储方式也是一个值得思考的方法,这种存储方法可以把数据存储在内存中,并且保留sql强大的查询能力,是不是一举两得呢?
我们这里只说到了读缓存其实还有一种写缓存在以内容为主的社区里比较少用到因为这样的社区最主要需要解决的问题是读问题但是在处理能力低于请求能力时或者单个希望请求先被缓存形成块然后批量处理时写缓存就出现了在交互性很强的社区设计里我们很容易找到这样的缓存
四核心模块一定要自己开发DIY your core module 这点我们是深有体会钱宏武和云风也都有谈到我们经常倾向于使用一些开源模块如果不涉及核心模块确实是可以的如果涉及那么就要小心了因为当访问量达到一定的程度这些模块往往都有这样那样的问题当然我们可以把问题归结为对开源的模块不熟悉但是不管怎样核心出现问题的时候不能完全掌握其代码是非常可怕的 五合理选择数据存储方式reasonable data storage 我们一定要使用数据库吗不一定雷鸣告诉我们搜索不一定需要数据库云风告诉我们游戏不一定需要数据库那么什么时候我们才需要数据库呢为什么不干脆用文件来代替他呢 首先我们需要先承认数据库也是对文件进行操作。我们需要数据库主要是使用下面这几个功能一个是数据存储一个是数据检索在关系数据库中我们其实非常在乎数据库的复杂搜索的能力看看一个统计用的tsql就知道了(不用仔细读,扫一眼就可以了)
select c.Class_name,d.Class_name_2,a.Creativity_Title,b.User_name,(select count(Id) from review where Reviewida.Id) as countNum from Creativity as a,User_info as b,class as c,class2 as d where a.user_idb.id and a.Creativity_Classc.Id and a.Creativity_Class_2d.Id select a.Id,max(c.Class_name),(max(d.Class_name_2),max(a.Creativity_Title),max(b.User_name),count(e.Id) as countNum from Creativity as a,User_info as b,class as c,class2 as d,review as e where a.user_idb.id and a.Creativity_Classc.Id and a.Creativity_Class_2d.Id and a.Ide.Reviewid group by a.Id ..............................................
我们可以看出需要数据库关联排序的能力这个能力在某些情况下非常重要但是如果你的网站的常规操作全是这样复杂的逻辑那效率一定是非常低的所以我们常常在数据库里加入许多冗余字段来减小简单查询时关联等操作带来的压力我们看看下面这张图可以看到数据库的设计重心和网站(指内容型社区)需要面对的问题实际是有一些偏差的 同样其他一些软件产品也遇到同样的问题所以具我了解有许多特殊的运用都有自己设计的特殊数据存储结构与方法比如有的大型服务程序采取树形数据存储结构lucene使用文件来存储索引和文件
从另外一个角度上看使用数据库意味着数据和表现是完全分离的这当然是经典的设计思路也就是说当需要展示数据时不得不需要一个转换的过程也可以说是绑定的过程当网站具备一定规模的时候数据库往往成为效率的瓶颈所以许多网站也采用直接书写静态文件的方法来避免读取操作时的绑定
这并不是说我们从今天起就可以把我们亲爱的数据库打入冷宫而是我们在设计数据的持久化时需要根据实际情况来选择存储方式而数据库不过是其中一个选项 六搞清楚谁是最重要的人whos the most important guy 在用例需求分析的时候常常讲到涉众就是和你的设计息息相关的人在web中我们一定以为最重要的涉众莫过于用户了。在一个传统的互动社区开发中最重要的东西是内容用户产生内容所以用户就是上帝至于内容挑选工具不就是给坐我后面三排的妹妹们用的吗凑或行了实在有问题我就在数据里手动帮你加得了。。这大概是眼下许多小型甚至中型网站技术人员的普遍想法。钱宏武在他的讲座里谈到了这个问题实际上网站每天产生的内容非常的多普通人是不可能看完的而编辑负责把精华的内容推荐到首页上所以很多用户读到的内容其实都依赖于编辑的推荐所以设计让编辑工作方便的工具也是非常重要有时甚至是最重要的。 七不要执着于文档dont be crazy about document web开发的文档重要吗什么文档最重要我的看法是web开发中交流文档
现在大的软件公司比较流行的做法是 注重产品设计文档在这种方法里产品文档非常详尽并且没有歧义开发人员基于设计文档开发测试人员基于设计文档制定测试方案任何新人都可以通过阅读产品设计文档来了解项目的概况
而web项目从概念到实现的时间是非常短的而且越短越好并且由于变化迅速要想写出完整的产品和需求文档是几乎不可能的大多数情况是等你写出完备的文档项目早就是另外一个样子但是没有文档的问题是如果团队发生变化添加新成员怎样才能了解软件的结构和概念呢一种是每个人都了解软件的整个结构除非你的团队整体消失否则任何一个人都能够担当培养新人的责任这种face2face交流比文档有效率很多。
于是就有了前office开发者现任yahoo中国某产品开发负责人的刘振飞所感觉到的落差他说我们的项目是吵出来的我听完会心一笑 八团队team 不要专家团队而要外科手术式的团队,你的团队里一定要有清道夫需要有弓箭手让他们和项目一起成长才是项目负责人的最大成就
总结
0)架构是一种权衡 1)web开发的特点是是没有太复杂的技术难点一切在于迅速的把握需求其实这正式敏捷开发的要旨所在一切都可以非常快速的建立非常快速的重构我们的开发工具底层库和框架包括搜索引擎和web文档提供的帮助都提我们供给了敏捷的能力。
2)此外相应的最有效率的交流方式必须留给web开发那就是face2face面对面不要太担心你的设计不能被完备的文档所保留下来他们会以交流代码和小卡片的方式保存下来
3)人的因素会更加重要无论是对用户的需求还是开发人员的素质。
另有关web效率有著名的14条规则由yahoo性能效率小组所总结并广为流传。业已出现相关插件YSlow针对具体网页按彼规则评分这次该小组负责人Tenni Theurer也受邀来到此次大会我把Tenni小姐之前真的没有想到她是个女孩并且如此年轻和她的团队的14 rules列在下面 Make Fewer HTTP RequestsUse a Content Delivery NetworkAdd an Expires HeaderGzip ComponentsPut CSS at the TopMove Scripts to the BottomAvoid CSS ExpressionsMake JavaScript and CSS ExternalReduce DNS LookupsMinify JavaScriptAvoid RedirectsRemove Duplicate ScriptsConfigure ETagsMake Ajax Cacheable 通过安装firebug和YSlow这两个firefox插件(请注意要先安装firebug再安装yslow,下载后拖动到firefox里即可)我们可以看到你的网页根据下面的规则的评分,这是我在博客园博客首页的评分截图,上面D表示总分,下面是单项评分,A最好F最差,不知道还有没有G :) 相关连接 yahoo性能团队:http://developer.yahoo.com/performance/ 软件架构设计 事实上经过从上面三个方面审视架构我们已经建立了一个完整的而且比较良好的架 构。但我们还需要从第四个方面在更高的层次审视我们的架构需要考虑的又一个问题就是 软件的复用。复用可以大大降低后期成本提高整个软件系统的可升级性与可维护性。我们 可以考虑哪些结构可以使用已经存在的可复用结构和产品某些结构可以利用 GoF 的设计模 式设计可复用的构件已备后期使用。还需要根据需求分析得出的易变点仔细设计产品结构 确保后期的变化不至于对产品带来太大的影响。而复用的一个重要的手段就是面向构件的 方法。 1软件复用 软件复用是指重复使用“为了复用目的而设计的软件”的过程。在过去的开发实践中 我们也可能会重复使用“并非为了复用目的而设计的软件”的过程或者在一个应用系统的 不同版本间重复使用代码的过程这两类行为都不属于严格意义上的软件复用。 通过软件复用在应用系统开发中可以充分地利用已有的开发成果消除了包括分析、 设计、编码、测试等在内的许多重复劳动从而提高了软件开发的效率同时通过复用高 质量的已有开发成果避免了重新开发可能引入的错误从而提高了软件的质量。在基于复 用的软件开发中为复用而开发的软件架构本身就可以作为一种大粒度的、抽象级别较高的 软件构件进行复用而且软件架构还为构件的组装提供了基础和上下文对于成功的复用具 有非常重要的意义。 软件架构研究如何快速、可靠地用可复用构件构造系统的方式着眼于软件系统自身的 整体结构和构件间的互联。其中主要包括软件架构原理和风格软件架构的描述和规范 特定领域软件架构构件如何向软件构架的集成机制等。 2面向构件的方法简述 构件也称为组件面向构件的方法包含了许多关键理论这些关键理论解决了当今许多 备受挑剔的软件问题这些理论包括 构件基础设施 软件模式 软件架构 基于构件的软件开发 构件可以理解为面向对象软件技术的一种变体它有四点原则区别于其它思想封装、 多态、后期绑定、安全。从这个角度来说它和面向对象是类似的。不过它取消了对于继承 的强调。 在面向构件的思想里认为继承是个紧耦合的、白盒的关系它对大多数打包和复用来 说都是不合适的。构件通过直接调用其它对象或构件来实现功能的复用而不是使用继承来 实现它事实上在我们后面的讨论中也会提到面向对象的方法中还是要优先使用组合而 不是继承但在构件方法中则完全摒弃了继承而是调用在构件术语里这些调用称作“代 理”delegation。 实现构件技术关键是需要一个规范这个规范应该定义封装标准或者说是构件设计的 公共结构。理想状态这个规范应该是在行业以至全球范围内的标准这样构建就可以在系统、 企业乃至整个软件行业中被广泛复用。构件利用组装来创建系统在组装的过程中可以把 多个构件结合在一起创建一个比较大的实体如果构件之间能够匹配用户的请求和服务的规 范它们就能进行交互而不需要额外的代码。 3面向构件的软件模式 面向构件技术的特色在于迅速、灵活、简洁面向构件技术之于软件业的意义正如由 生产流水线之于工业制造是软件业发展的必然趋势。软件业发展到今天已经不是那种个 人花费一段时间即可完成的小软件。软件越来越复杂时间越来越短软件代码也从几百行 到现在的上百万行。把这些代码分解成一些构件完成可以减少软件系统中的变化因子。 1面向构件方法模式 面向构件技术的思想基础在软件复用技术基础是根据软件复用思想设计的众多构件。 面向构件将软件系统开发的重心移向如何把应用系统分解成稳定、灵活、可重用的构件和如 何利用已有构件库组装出随需而变的应用软件。 基于面向构件的架构可以描述为系统框架构件组建。框架是所有构件的支撑框架; 每个构件实现系统的每个具体功能;组建可以视为构件的插入顺序不同构件的组成顺序不 同其实现的整体功能也就不同。 面向构件技术将把软件开发分成几种框架开发设计、构件开发设计、组装如果用现 代的工业生产做比喻框架设计就是基本的生产机器的开发研究构件开发就是零件的生产 组装就是把零件组装成汽车、飞机等等各种产品。 2面向构件开发的不足之处 1系统资源耗费 从软件性能角度看用面向构件技术开发的软件并不是最佳的。除了有比较大的代码冗 余外因为它的灵活性在很大程度上是以空间和时间等为代价实现的。 2面向构件开发的风险 从细节来看构件将构件的实现细节完全封装如果没有好的文档支持有可能导致构 件的使用结果不是使用者预期的。比如构件使用者对某构件的出错机制认识不够 3开放式系统技术 专用软件是由单个供应商生产的不符合统一标准的产品。这些单个供应商通过版本更换 来控制软件的形式与功能。但是谁这系统越来越复杂当一个系统建立起来以后往往更倾 向于依赖于通用的商业软件这种依赖往往成为内部软件复用的非常有效的形式。正是这种 状态我们需要讨论一下开放式系统技术这个问题。 商业软件成为复用的有效形式主要原因是规模经济的作用通用的商业软件的质量 往往超过终端用户自主开发能力。 商业软件也可以在某个领域内实现专有也就是提供应用程序接口API为应用软件 提供服务。当软件不断升级的过程中这些接口的复杂性可能超出用户的需要这就需要有 复杂性的控制。 一个解决方案就是实现开放式系统技术开放式系统技术与专有技术有根本的不同。在 开放式系统技术中由供应商组织来开发独立于专有实现的规范所有的供应商实现的接口 都严格的一致并且规定了统一的术语这样就可以是软件的生命周期得以延长这种开放 式系统技术特别适合于面向对象的方法。 为了使商业技术能适应各种应用需求对软件开发和安装就有一定的要求这种要求称 之为配置profiling适当的配置软件的嵌入也称为开放是软件的一个特点。 从架构的角度来看不少系统结构具有大量的一对一接口和复杂的相互连接关系这种 模型被称之为“烟囱”模型当系统规模增大以后这种关系会以平方律的速度增加复杂 性的增加会带来相当多的问题尤其是升级和修改越来越难以进行而系统的可扩展性恰恰 是开发成本的重要部分。 为此我们可以建立一个称之为对象管理器的层用于统一协调各个对象的沟通从可 维护性角度这是一个比较好的结构但是在某些特别强调效率的点可以避开它。系统的架构 的一个重要原则就是对软件接口定义一个已经规划的定义为系统集成方案提供更高的统一 性软件的子系统部分要由应用程序接口来定义。这样就削弱了模块之间的依赖性这样 的系统就比较容易扩展和维护并且支持大规模的集成。 经过从四个方面审视我们的架构经过分析、权衡、设计和部分编码我们就可以得到 一个稳固的架构。这个架构经过经过评审就可以作为后期开发的基础架构.
综上所述我们就可以比较条理化的建立软件架构设计的流程了。典型软件架构设计的 流程如下图所示。 一、业务架构概念 在构建软件架构之前架构师需要仔细研究如下几个问题 系统是为什么目的而构建的 系统投运后服务于哪些利益相关者的利益 什么角色在什么时候操作或者维护系统 业务系统实现方法是怎样的 整个业务系统是如何依靠系统而运转的 为了回答这些问题需要仔细阅读需求分析文档中的业务模型建立、问题域及其解决构 思、产品模型的构思等等前期文档站在系统架构的角度全面清晰的建立业务模型包括 组织结构关系、业务功能、业务流程、业务信息交互方法、业务地理分布、业务规则和约束 条件。这个阶段的主要活动如下 建立产品范围、目的、最终用户、业务背景等重要的初始信息。 建立完整的业务和系统的术语字典确保项目相关人员理解上保持一致。 建立宏观层面业务的总体概念明确总体流程、业务功能的边界、交互与协作方式 建立系统的概念模型。 汇总业务总的组织结构与协作职能关系。 分析业务的组成节点以及节点间交互、协同与信息的依赖关系。 分析业务节点的事件、消息以及由此引发的状态转换关系。 汇总业务运行的基本数据模型以便于跟踪信息的流动与格式转换。分析业务数据 的关联关系。 理解问题域以及系统需要解决的问题。 分析业务运作层面的基本业务规则与约束条件。 这个阶段的活动非常重要架构师只有具备了这些相互关联的业务概念以后才可能从 这些概念中抽取恰当的架构因素。 二、产品架构概念 在理解业务的基础上我们需要进一步思考产品架构的概念这个阶段从活动的层面看 实际上与建立业务架构概念是一样的但是思维的重心转移到如下几个方面 新系统投入运行以后最高层面的业务会怎样运作 新系统是如何解决原来工作系统的问题的 新系统的投运会对原来的组织结构划分发生什么样的影响 由于新系统取代了原来的一些业务职能业务节点的分布会发生怎样的变化变化 后的节点间的信息又是怎样交换与依赖的 变化后的业务事件传递又会发生怎样的变化 新的系统加入以后哪些业务流程将会发生重大变化哪些不会发生变化 业务的状态转换关系将会如何随着新系统地加入而改变 业务的数据模型将会如何随着业务流程的变化而变化的 新系统地加入将会如何影响新的业务规则和约束 从这些角度出发我们会重新构建未来新系统投运以后的业务规则相应的新规则也需 要建立这就实现了业务过程的重构。 三、建立稳定的架构基线 在对业务领域与问题域有深刻的理解以后我们需要继续研究如下一些问题 这个复杂系统应该分成多少和哪些子系统 子系统是如何分布在不同的业务节点或者物理节点上的 这些分散的子系统将提供哪些接口这些接口如何进行交互 各个子系统需要交互哪些数据 每个单独的子系统所需要实现的功能有哪些 整个系统对各个子系统有哪些功能、性能和质量上的要求 “基线”这个词有两个意义 这个阶段将会对整体架构策略做出重大的设计上的决策。一旦作出了这些决策后 续开发没有重大情况不允许变动。 这个阶段完成的工作本身就是架构阶段的重要成果需要广泛认同、集体遵守以 及具备强制的约束力。 尽管在后期的演变中这样的基线实际上还会不断的精化和优化但最初下功夫构建稳 定的架构基线是十分必要的。这个阶段的活动如下 校验与确认前期所有的业务架构与产品架构的信息必要的时候补充相应的信息。 修订和增补术语字典确保所有的相关人员对术语有相同的认知。 把整个系统功能进行拆分并且分解到不同的运行节点上构建不同的系统集和子 系统在全局范围内划分接口与交互规则。 汇总系统/子系统接口信息便于检索与浏览。 规划整个系统的通信链路、通信路径、通信网络等传输媒介。 把产品架构概念中的业务职能与系统功能相对应从而确保满足业务要求。 分析系统/子系统在运行起动态协作需要交互的信息。 构建和模拟整个系统在业务环境下的动态特性规划全系统内部状态变化过程、触 发的事件及约束条件。 详细汇总各个子系统间信息传递的过程、内容以及其它辅助信息。 根据初始的数据模型构建数据物理模型。 汇总质量上对系统的要求并把这些质量要求细化分解量化到各个子系统中去。 构建整个系统与子系统的构建和演化计划在迭代过程中构建整体项目规划和初始 的迭代规划。 按固定时段预测技术的演化汇总整个系统的应用技术及其演化。 分辨与汇总整个系统在不同阶段必须遵循的标准。 把业务约束映射到各个子系统必要时附加 IT 业务约束。 四、子系统架构的设计与实现 通过上述各主要过程我们已经实现了一个重要的总体架构基线。所有的子系统设计都 是在这个庞大的架构基线约束下展开至此首席架构师逐渐淡出让位于子系统架构设计 师。子系统架构设计师的任务是继续分解、细化、设计各个子系统。在这个阶段将会考虑 更加细节的问题为后来的构件设计与单元设计作准备我们需要考虑的问题如下 规划给该子系统的功能是否可行 在整个子系统的范围内又能分解成什么子功能集 在整个子系统的范围内又能把哪些子功能合并到某些构件中去 这些构件与子功能集是如何通过接口与子系统衔接的 事实上子系统架构设计本身就是一个完整的系统设计所区别的是视野集中到子系统的 范围内这个阶段的活动如下 校验与确认前期与该子系统相关的业务架构与产品架构的信息必要的时候补充相 应的信息。 增补与本子系统相关的术语字典确保所有的相关人员对术语有相同的认知。 把整个子系统功能进行拆分并且分解到不同的构件节点上构建不同的子功能集 在子系统范围内划分接口与交互规则。 汇总子系统/构件接口信息便于检索与浏览。 规划整个子系统的通信链路、通信路径、通信网络等传输媒介。 把产品架构概念中的子业务职能与构件功能相对应从而确保满足业务要求。 分析子系统/构件在运行起动态协作需要交互的信息。 构建和模拟整个子系统在业务环境下的动态特性规划子系统内部状态变化过程、 触发的事件及约束条件。 详细汇总各个构件间信息传递的过程、内容以及其它辅助信息。 根据初始的数据模型构建子系统相关的更详细的数据物理模型。 根据质量上对子系统的要求并把这些质量要求细化分解量化到各个构件中去。 构建整子系统的构建和演化计划在迭代过程中构建子系统项目规划和更详细地的 迭代规划。 按固定时段预测技术的演化汇总子系统的应用技术及其演化。 分辨与汇总子系统在不同阶段必须遵循的标准。 把业务约束映射到各个构件必要时附加 IT 业务约束。 五、构件与实现单元的设计 进入构件设计阶段也就是进入了详细设计阶段。这个阶段主要的工作就是接口与功能设 计。在迭代模型下这个阶段很大程度上是在迭代过程中完成的由某个设计人员带领全体 开发团队进行分析和设计。 在这个过程中我们应该考虑在小粒度架构中如何使产品需求变更不至于对产品质量造 成影响还需要考虑业务概念模型与产品功能块有相应的追溯和回溯关系。这些问题我们 将会在后面用适当的篇幅进行讨论。 小结 大型复杂项目的成功依赖于合理的项目组织这种组织概念包括人力资源的组织和产品 架构的组织两个方面。敏捷项目管理为这两种合理的组织提供了思维基础为项目的成功提 供了保证。一个典型的例子是 20 世纪 90 年代加拿大空中交通系统项目首席架构师Philippe Kruchten。150 个程序员被组织到 6 个月的长周期迭代中。但是即使在 6 个月的迭代中 1020 人的子系统仍然把任务分解成一连串 6 个为期一个月的小迭代。正是这个项目的 实践成功Philippe Kruchten 才成为 RUP 的首倡者。 敏捷过程的提出直接影响到架构设计的核心思维正是因为敏捷过程的提出才有了架 构驱动、弹性架构和骨架系统这些理念。也直接影响到需求分析方法、项目规划和估计方法 等一系列领域的新思维。甚至推动了业务敏捷以及与之相适应的基于服务的架构的提出。这 些观念的提出又更加推动了软件工程学向更高的层次发展。 下面我们将讨论几个专题但讨论的时候希望研究一种思考问题的方法从而为大家解 决更广阔的问题提供一个思维的平台。这些专题并不是独立存在而是融合在本章所讨论的 各个阶段之中。再一次强调方法和技术是会变化的但优秀的思维方式是永恒的 从MVC框架看MVC架构的设计
从MVC框架看MVC架构的设计 尽管MVC早已不是什么新鲜话题了但是从近些年一些优秀MVC框架的设计上我们还是会发现MVC在架构设计上的一些新亮点。本文将对传统MVC架构中的一些弊病进行解读了解一些优秀MVC框架是如何化解这些问题的揭示其中所折射出的设计思想与设计理念。 MVC回顾 作为一种经典到不能再经典的架构模式MVC的成功有其必然的道理这个道理不同的人会有不同的解读笔者最认同的一种观点是通过把职责、性质相近的成分归结在一起不相近的进行隔离MVC将系统分解为模型、视图、控制器三部分每一部分都相对独立职责单一在实现过程中可以专注于自身的核心逻辑。MVC是对系统复杂性的一种合理的梳理与切分它的思想实质就是“关注点分离”。至于MVC三元素的职责划分与相互关系这里不再赘述下图给出了非常细致的说明。 图1MVC组件的功能和关系[i] View与Controller的解耦mediator二次事件委派 笔者早年开发基于swing的GUI应用时,在架构MVC的实践过程中深刻体会到了view与controller之间的紧密耦合问题。在很多事件驱动的GUI框架里如swing用户对view的任何操作都会触发一个事件然后在listener的响应方法里进行处理。如果让view自己注册成为事件的listener,则必须要在view中加入对controller的引用这不是MVC希望看到的因为这样view和controller就形成了紧密的耦合关系。若将controller注册为listener则事件响应将由controller承担这又会导致controller处理其不该涉及的展现逻辑,造成view和controller难以解耦的原因在于多数的用户请求都包含一定成分的展现逻辑和一定成分的业务逻辑两种逻辑揉合在一个请求里在处理的时候view与controller很难合理地分工。解决这一问题的关键是要在view与controller之间建立一种可以将展现逻辑与业务逻辑进行有效分割的机制在这方面PureMVC[ii]的设计非常值得参考它通过引入mediator二次事件委派机制很好的解决了view与controller之间的紧耦合问题。 Mediator是一种设计模式这种模式在组件化的图形界面框架中似乎有着普遍的应用场景即使是在四人帮的《设计模式》一书中也使用了一个图形界面程序的示例来讲解mediator。mediator的设计用意在于通过一个媒介对象完成一组对象的交互避免对象间相互引用产生复杂的依赖关系。mediator应用于图形界面程序时往往作为一组关系紧密的图形组件的交互媒介完成组件间的协调工作(比如点选某一按钮其他组件将不可用)。在PureMVC中mediator被广泛应用其定位也发生了微妙的变化它不再只是图形组件间的媒介同时也成为了图形组件与command之间的媒介这使得它不再是可选的而是成为了架构中的必需设施。对应到传统MVC架构中mediator就是view与controller之间的媒介当然也依然是view之间的媒介所有从view发出的用户请求都经过了mediator再传递给controller它的出现在一定程度上缓解了view与controller的紧密耦合问题。 当view、mediator和controller三者被定义出来并进行了清晰的职责划分后剩下的问题就是如何将它们串联起来以完成一个用户请求了在这方面事件机制起到了至关重要的作用。事件机制可以让当前对象专注于处理其职责范围内的事务而不必关心超出部分由谁来处理以及怎样处理当前对象只需要广播一个事件就会有对此事件感兴趣的其他对象出来接手下一步的工作当前对象与接手对象之间不存在直接依赖甚至感知不到彼此的存在这是事件机制被普遍认为是一种松耦合机制的重要原因。讲到这里插一句题外话在领域驱动设计Domain-Driven Design里著名的Domain Event模式也是有赖于事件机制的这一特性被创造出来的其用意正是为了保证领域模型的纯净避免领域模型对repository和service的直接依赖。回到PureMVC, 我们来看在处理用户请求的过程中事件机制是如何串联view、mediator和controller的。在PureMVC里当一个用户请求下达时图形组件先在自身的事件响应方法中实现与自身相关的展现逻辑然后收集数据将数据置入一个新的event中将其广播出去这是第一次事件委派。这个event会被一个mediator监听到如果处理该请求需要其他图形组件的协助mediator会协调它们处理应由它们承担的展现逻辑然后mediator再次发送一个event(这次的event在PureMVC里称之为notification)这个event会促使某个command执行完成业务逻辑的计算这是第二次事件委派。在两次事件委派中第一次事件委派让当事图形组件完成“处理其职责范围内的展现逻辑”后得以轻松“脱身”免于被“协调其他图件处理剩余展现逻辑”和“选择并委派业务对象处理业务逻辑”所拖累。而“协调其他图形组件处理剩余展现逻辑”显然是mediator的职责于是第一次广播的事件被委派给了mediator. mediator在完成图形组件的协调工作后并不会插手“选择并委派业务对象处理业务逻辑”的工作这不是它的职责因此第二次事件委派发生了一个新的event由mediator广播出去后被某个command响应到由command完成了最后的工作——“选择并委派业务对象处理业务逻辑”。 图2mediator二次事件委派机制 总结起来PureMVC是通过在view与controller之间引入mediator让view与controller变成间接依赖用户请求从view到mediator再从mediator到controller均以事件方式委派mediator二次事件委派的组合可以说是一种“强力”的解耦机制它实现了view与controller之间的完全解耦。 从Controller到Command自然粒度的回归 目前很多平台的主流MVC框架在设计上都引入了command模式command模式的引入改变了传统框架的结构受冲击最大的就是controller。在过去传统的MVC架构里一个controller可能有多个方法每个方法往往对应一个user action,因此一个 controller往往对应多个user action而在基于command的MVC架构里一个command往往只对应一个user action。传统MVC架构里将一个user action委派到某个controller的某个方法的过程在基于command的MVC架构里变成了将useraction与command一一绑定的过程。如果说传统controller的管理方式是在user action与model之间建立“集中式”的映射那么基于command的管理方式就是在user action与model之间建立“点对点式”的直连映射。 图3从基于Controller到基于Command的架构演进 主流MVC框架向command转型是有原因的除了command自身的优势之外一个非常重要的原因就是由于缺少合理的组织依据controller的粒度很难拿捏。controller不同于view与modelview与model都有各自天然的粒度组织依据view的组织粒度直接承袭用户界面设计model的组织粒度则是依据某种分析设计思想如OOA/D进行领域建模的结果controller需要同时协调view与model但是view与model的组织结构和粒度都是不对等的这就使得controller面临一个“在多大视图范围内沟通与协调多少领域对象”的问题由于找不出合理的组织依据设计者在设计controller时往往感到无所适从。相比之下command则完全没有controller的困惑因为command有一个天然的组织依据这就是user action。针对一个user action设计一个command然后将两者映射在一起是一件非常自然而简单的事情。不过需要说明的是这并不意味着所有command的粒度是一样的因为不同的user action所代表的业务量是不同的因此也决定了command是有“大”有“小”的。遵循良好的设计原则对某些较“大”的command进行分解从中抽离出一些可复用的部分封装成一些较“小”的command是值得推荐的。很多MVC框架就定义了一些相关的接口和抽象类用于支持基于组合模式的命令拼装。 不管是基于controller还是基于commandMVC架构中界定的“协调view与model交互”的控制器职责是不会变的都需要相应的组件和机制去承载与实现。在基于command的架构里command承担了过去controller的部分职责从某种意义上说 command是一种细粒度的controller但是command的特性是偏“被动”的。一方面它对于view和model的控制力比controller弱化了很多 比如一般情况下command是不会直接操纵view的。另一方面它不知道自己与什么样的user action映射在了一起也不知道自己会在何种情况下被触发执行。支撑command的运行需要额外的注册、绑定和触发机制是这些机制加上command一起实现了controller的职责。由于现在多数基于command的MVC框架都实现并封装了这些重要的机制所以从某种意义上说是这些框架自身扮演了controller角色。 小结 本文主要分析了过去传统MVC架构中存在的两大弊病view与controller的紧密耦合以及controller粒度难以把控的问题介绍了一些MVC框架是如何应对这些问题的这些设计方案所体现出的优秀设计思想是非常值得学习的。 [i] 图片来源http://java.sun.com/blueprints/patterns/MVC-detailed.html [ii] PureMVC是一种MVC框架最初使用ActionScript 3实现现在在多种语言平台上有实现版本。官方站点http://puremvc.org/ 更多精彩博文 领域驱动设计(Domain Driven Design)参考架构详解 关于垂直切分Vertical Sharding的粒度 企业应用集成与开源ESB产品ServiceMix和Mule介绍 论基于数据访问的集合类Data Access Based Collection和领域事件(Domain Event)模式 关于系统异常设计的再思考 前车之覆后车之鉴——开源项目经验谈 前车之覆后车之鉴 ——开源项目经验谈 本文发表于《程序员》2005年第2期 随着开源文化的日益普及“参与开源”似乎也变成了一种时尚。一时间似乎大家都乐于把自己的代码拿出来分享了。就在新年前夕我的一位老朋友、一位向来对开源嗤之以鼻的J2EE架构师竟然也发布了一个开源的J2EE应用框架姑且称之为“X框架”不得不令我惊叹开源文化的影响力之强大。 可惜开源并非免费的午餐把源码公开就意味着要承受众目睽睽的审视。仅仅几天之后国内几位资深的J2EE架构师就得出一个结论细看之下X框架不管从哪个角度都只能算一个失败的开源项目。究竟是什么原因让一个良好的愿望最终只能得到一个失败的结果本文便以X框架为例点评初涉开源的项目领导者常犯的一些错误指出投身开源应当遵循的一些原则为后来的开源爱好者扫清些许障碍。 成熟度 打开X框架在SourceForge的项目站点我们立刻可以看到在“Development Status”一栏赫然写着“5 – Production/Stable”。也就是说作者认为X框架已经成熟稳定可以交付用户使用。那么现在对其进行评估便不应该有为时过早之嫌。可是X框架真的已经做好准备了吗 打开从SourceForge下载的X框架的源码包笔者不禁大吃一惊压缩包里真的只有源码——编译、运行整个项目所需的库文件全都不在其中。从作者自己的论坛得知该项目需要依赖JBoss、JDOM、Castor、Hibernate等诸多开源项目笔者只好自己动手下载了这些项目好一番折腾总算是在Eclipse中成功编译了整个项目。 不需要对开源文化有多么深刻的了解只要曾经用过一些主流的开源产品你就应该知道一个开源软件至少应该同时提供源码发布包和二进制发布包源码包中至少应该有所有必需的依赖库文件或者把依赖库单独打包发布、完整的单元测试用例对于Java项目通常是Junit测试套件、以及执行编译构建的脚本对于Java项目通常是Ant脚本或者Maven脚本但这些内容在X框架的发布包中全都不见踪影。用户如果想要使用这个框架就必须像笔者一样手工下载所有的依赖库然后手工完成编译和构建而且构建完成之后也无从知晓其中是否有错误存在因为没有单元测试。这样的发布形式算得上是“Production/Stable”吗 开源必读便捷构建 开源软件应该提供最便捷的构建方式让用户可以只输入一条命令就完成整个项目的编译、构建和测试并得到可运行的二进制程序。对于Java项目这通常意味着提供完整的JUnit测试套件和Ant脚本。你的潜在用户可能会在一天之内试用所有类似的开源软件如果一个软件需要他用半天时间才能完成构建、而且还无从验证正确性、无从着手编写他自己的测试用例这个软件很可能在第一时间被扔到墙角。 从SourceForge的项目页面可以看到X框架的授权协议是Apache License V2.0APL。然而在它的发布包中笔者没有看到任何形式的正式授权协议文本。众所周知SourceForge的项目描述是可以随时修改的X框架本身的授权协议就曾经是GPL如果发布包中没有一份正式的授权协议文本一旦作者修改了SourceForge的项目描述用户又该到哪里去寻找证据支持自己的合法使用呢 在X框架的源码中大部分源文件在开始处加上了APL的授权声明但有一部分源码很是令人担心。例如UtilCache这个类开始处没有任何授权声明而JavaDoc中则这样声明作者信息 author a hrefmailto:jonesdeofbiz.orgDavid E. Jones/a 也就是说这个类的源码来自另一个开源项目Ofbiz。值得一提的是Ofbiz一直是“商业开源”的倡导者它的授权协议相当严格。凡是使用Ofbiz源码必须将它的授权协议一并全文复制。像X框架这样复制Ofbiz源码、却删掉了授权协议的行为实际上已经构成了对Ofbiz的侵权。 另外作者打包用的压缩格式是RAR而这个压缩格式对于商业用户是收费的。对于一个希望在商业项目中应用的框架项目来说选择这样一个压缩格式实在算不得明智。而且笔者在源码包中还看到了好几个.jbx文件这是JBuilder的项目描述文件。把这些JBuilder专用的文件放在源码包中又怎能让那些买不起或是不想买JBuilder的用户放心呢更何况出于朋友的关心笔者还不得不担心X框架的作者是否会收到Borland公司的律师信呢。 开源必读授权先行 在启动一个开源项目时第一件大事就是要确定自己的授权协议并在最醒目的地方用最正式的方式向所有人声明——当然在此之前你必须首先了解各种开源授权协议。譬如说GPLLinux采用的授权协议要求在软件之上的扩展和衍生也必须继承GPL因此这种协议对软件的商业化应用很不友好相反APL则允许用户将软件的扩展产物私有化便于商业应用却不利于开发者社群的发展。作为一个开源项目的领导者对于各种授权协议的利弊是不可不知的。 除了源码本身的授权协议之外软件需要使用的类库、IDE、解压工具等等都需要考虑授权问题。开源绝对不仅仅意味着“免费使用”开源社群的人们有着更加强烈的版权意识和法律意识。如果你的开源软件会给用户带来潜在的法律麻烦它离着被抛弃的命运也就不远了。 可以看到不管从法律的角度还是从发布形式的角度X框架都远够不上“Production/Stable”的水准——说实在的以它的成熟度顶多只能算是一个尚未计划周全的开源项目。虽然作者在自己的网站上大肆宣传但作为一个潜在的用户我不得不冷静地说即便X框架的技术真的能够吸引我但它远未成熟的项目形态决定了它根本无法在任何有实际意义的项目中运用。要让商业用户对它产生兴趣作者需要做的工作还很多。 我刚才说“即便X框架的技术真的能够吸引我”这算得上是一个合理的假设吗下面就让我们进入这个被作者寄予厚望的框架内部看看它的技术水平吧。 整体架构 在X框架的宣传页面上我们看到了这样的宣传词 X框架解决了以往J2EE开发存在的诸多问题EJB难用、J2EE层次复杂、DTO太乱、Struts绕人、缓存难做性能低等。X框架是Aop/Ico[注应为“IoC”此处疑似笔误]的实现优异的缓存性能是其优点。 下面是X框架的整体架构图 可以看到在作者推荐的架构中EJB被作为业务逻辑实现的场所而POJO被用于实现Façade。这是一个好的技术架构吗笔者曾在一篇Blog中这样评价它[1] 让我们先回想一下使用EJB的理由是什么常见的答案有可分布的业务对象声明性的基础设施服务例如事务管理。那么如果在EJB的上面再加上一层POJO的Façade显然你不能再使用EJB的基础设施了因为完整的业务操作也就是事务边界将位于POJO Façade的方法这里所以你必须重新——以声明性的方式——实现事务管理、安全性管理、remoting、缓存等基础设施服务。换句话说你失去了 session bean的一半好处。另一方面“可分布的业务对象”也不复存在因为POJO本身是不能——像EJB那样——分布的这样你又失去了session bean的另一半好处。 继续回想使用基于POJO的轻量级架构的理由是什么常见的答案有易于测试便于移植“开发-发布”周期短。而如果仅仅把POJO作为一层Façade把业务逻辑放在下面的EJB那么你仍然无法轻易地测试业务逻辑移植自然也无从谈起了并且每次修改EJB之后必须忍受漫长的发布周期。即便是仅仅把EJB作为O/R mapping而不是业务逻辑的居所你最多只能通过DAO封装获得比较好的业务可测性但“修改-发布”的周期仍然很长因为仍然有entity bean存在。也就是说即使是往最好的方面来说这个架构至少损失了轻量级架构的一半优点。 作为一个总结X框架即便是在使用得最恰当的情况下它仍然不具备轻量级架构的全部优点至少会对小步前进的敏捷开发造成损害因为EJB的存在并且没有Spring框架已经实现的基础设施例如事务管理、remoting等必须重新发明这些轮子另一方面它也不具备EJB的任何优点EJB的声明性基础设施、可分布业务对象等能力它全都不能利用。因此可以简单地总结说X框架是一个这样的架构它结合了EJB和轻量级架构两者各自的短处却抛弃了两者各自的长处。 在不得不使用EJB的时候一种常见的架构模式是用session bean作为Façade用POJO实现可移植、可测试的业务逻辑。这种模式可以结合EJB和POJO两者的长处。而X框架推荐的架构模式虽然乍看起来也是依葫芦画瓢效果却恰恰相反正可谓是“取其糟粕、去其精华”。 开源必读架构必须正确 在开源软件的初始阶段功能可以不完善代码可以不漂亮但架构思路必须是正确的。即使你没有完美的实现参与开源的其他人可以帮助你但如果架构思路有严重失误谁都帮不了你。从近两年容器项目的更迭就可以看出端倪PicoContainer本身只有20个类、数百行代码但它有清晰而优雅的架构因此有很多人为它贡献外围的功能Avalon容器尽管提供了完备的功能但架构的落伍迫使Apache基金会只能将其全盘废弃。 所以如果你有志于启动一个开源项目尤其是框架性的项目务必先把架构思路拿出来给整个社群讨论。只要大家都认可你的架构你就有机会得到很多的帮助反之恐怕你就只能得到无尽的嘲讽了。 技术细节 既然整体架构已经无甚可取之处那么X框架的实现是否又像它所宣称的那样能够解决诸多问题呢既然X框架号称是“AOP/IoC的实现”我们就选中这两项技术看看它们在X框架中的实现和应用情况。 IoC X框架宣称自己是一个“基于IoC的应用框架”。按照定义框架本身就具有“业务代码不调用框架框架调用业务代码”的特性因此从广义上来说所有的框架必然是基于IoC模式的。所以在框架这里“基于IoC”通常是特指“对象依赖关系的管理和组装基于IoC”也就是Martin Fowler所说的Dependency Injection模式[2]由容器统一管理组件的创建和组装组件本身不包含依赖查找的逻辑。那么X框架实现IoC的情况又如何呢 我们很快找到了ContainerWrapper这个接口其中指定了一个POJO容器核心应该具备的主要功能 public interface ContainerWrapper { public void registerChild(String name); public void register(String name, Class className); public void register(String name, Class className, Parameter[] parameters); public void register(String name, Object instance); public void start(); public void stop(); public Collection getAllInstances(); public Object lookup(String name); } 在这个接口的默认实现DefaultContainerWrapper中这些功能被转发给PicoContainer的对应方法。也就是说X框架本身并没有实现组件容器的功能这部分功能将被转发给其他的IoC组件容器例如PicoContainer、Spring或HiveMind等来实现。在ContainerWrapper接口的注释中我们看到了一句颇可玩味的话 /** * 封装了Container解耦具体应用系统和PicoContainer关系。 了解IoC容器的读者应该知道在使用PicoContainer或Spring等容器时绝大多数POJO组件并不需要对容器有任何依赖它们只需要是最普通的JavaBean只需要实现自己的业务接口。既然对容器没有依赖自然也不需要“解耦”。至于极少数需要获得生命周期回调、因此不得不依赖容器的组件让它们依赖PicoContainer和依赖X框架难道有什么区别吗更何况PicoContainer是一个比X框架更成熟、更流行的框架为什么用户应该选择X框架这么一个不那么成熟、不那么流行的框架夹在中间来“解耦”呢 不管怎么说至少我们可以看到X框架提供了组件容器的核心功能。那么IoC或者说Dependency Injection在X框架中的应用又怎么样呢众所周知引入IoC容器的目标就是要消除应用程序中泛滥的工厂包括Service Locator由容器统一管理组件的创建和组装。遗憾的是不论在框架内部还是在示例应用中我们仍然看到了大量的工厂和Service Locator。例如作者引以为傲的缓存部分具体的缓存策略即Cache接口的实现对象就是由CacheFactory负责创建的并且使用的实现类还是硬编码在工厂内部 public CacheFactory() { cache new LRUCache(); 也就是说如果用户需要改变缓存策略就必须修改CacheFactory的源代码——请注意这是一个X框架内部的类用户不应该、也没有能力去修改它。换句话说用户实际上根本无法改变缓存策略。既然如此那这个CacheFactory又有什么用呢 开源必读开放-封闭原则 开源软件应该遵守开放-封闭原则Open-Close PrincipleOCP对扩展开放对修改封闭。如果你希望为用户提供任何灵活性必须让用户以扩展例如派生子类或配置文件的方式使用不能要求甚至不能允许用户修改源代码。如果一项灵活性必须通过修改源码才能获得那么它对于用户就毫无意义。 在示例应用中我们同样没有看到IoC的身影。例如JdbcDAO需要使用数据源即DataSource对象它就在构造子中通过Service Locator主动获取这个对象 public JdbcDAO() { ServiceLocator sl new ServiceLocator(); dataSource (DataSource) sl.getDataSource(JNDINames.DATASOURCE); 同样的情况也出现在JdbcDAO的使用者那里。也就是说虽然X框架提供了组件容器的功能却没有至少是目前没有利用它的依赖注入能力仅仅把它作为一个“大工厂”来使用。这是对IoC容器的一种典型的误用用这种方式使用容器不仅没有获得“自动管理依赖关系”的能力而且也失去了普通Service Locator“强类型检查”的优点又是一个“取其糟粕、去其精华”的设计。 开源必读了解你自己 当你决定要在开源软件中使用某项技术时请确定你了解它的利弊和用法。如果仅仅为了给自己的软件贴上“基于xx技术”的标签而使用一种自己不熟悉的技术往往只会给你的项目带来负面的影响。 AOP 在X框架的源码包中我们找到了符合AOP-Alliance API的一些拦截器例如用于实现缓存的CacheInterceptor。尽管——毫不意外地——没有找到如何将这些拦截器织入weave in的逻辑或配置文件但我们毕竟可以相信这里的确有AOP的身影。可是甫一深入这个“基于AOP的缓存机制”内部笔者却又发现了更多的问题。 单从CacheInterceptor的实现来看这是一个最简单、也最常见的缓存拦截器。它拦截所有业务方法的调用并针对每次方法调用执行下列逻辑 IF 需要缓存 key (根据方法签名生成key); IF (cache.get(key) null) value (实际调用被拦截方法); cache.put(key, value); RETURN (cache.get(key)); ELSE RETURN (实际调用被拦截方法); 看上去很好基于AOP的缓存实现就应该这么做……可是清除缓存的逻辑在哪里如果我们把业务方法分为“读方法”和“写方法”两种那么这个拦截器实际上只照顾了“读方法”的情况。而“写方法”被调用时会改变业务对象的状态因此必须将其操作的业务对象从缓存中清除出去但这部分逻辑在CacheInterceptor中压根不见踪影。如果缓存内容不能及时清理的话用户从缓存中取出的信息岂不是完全错误的吗 被惊出一身冷汗之后笔者好歹还是从几个Struts action也就是调用POJO Façade的client代码中找到了清除缓存的逻辑。原来X框架所谓“基于AOP的缓存机制”只实现了一条腿“把数据放入缓存”和“从缓存中取数据”的逻辑确实用拦截器实现了但“如何清除失效数据”的逻辑还得散布在所有的客户代码中。AOP原本就是为了把缓存这类横切性crosscutting的基础设施逻辑集中到一个模块管理像X框架的这个缓存实现不仅横切性的代码仍然四下散布连缓存逻辑的相关性和概念完整性都被打破了岂不是弄巧成拙么 开源必读言而有信 如果你在宣传词中承诺了一项特性请务必在你的软件中完整地实现它。不要仅仅提供一个半吊子的实现更不要让你的任何承诺放空。如果你没有把握做好一件事就不要承诺它。不仅对于开源软件对于任何软件开发这都是应该记住的原则。 更有趣的是X框架的作者要求领域模型对象继承Model基类并声称这是为了缓存的需要——事实也的确如此CacheInterceptor只能处理Model的子对象。但只要对缓存部分的实现稍加分析就会发现这一要求完全是作者凭空加上的用于缓存对象的Cache接口允许放入任何Object而Model尽管提供了setModified()、setCacheable()等用于管理缓存逻辑的方法却没有任何代码调用它们。换句话说即便我们修改CacheInterceptor使其可以缓存任何Object对X框架目前的功能也不会有任何影响。既然如此又为什么要给用户凭空加上这一层限制呢 退一万步说即使我们认为X框架今后会用Model的方法来管理缓存逻辑这个限制仍然是理由不足的。毕竟目前X框架还仅仅提供了缓存这一项基础设施infrastructure而已。如果所有基础设施都用“继承一个基类”的套路来实现当它真正提供企业级应用所需的所有基础设施时Model类岂不是要变得硕大无朋用户的领域对象岂不是再也无法移植到这个框架之外况且“由领域对象判断自己是否需要缓存”的思路本身也是错误的如果不仅要缓存领域对象还要缓存String、Integer等简单对象该怎么办如果同一个领域对象在不同的方法中需要不同的缓存策略又该怎么办X框架的设计让领域对象背负了太多的责任而这些责任原本应该是通过AOP转移到aspect中的。在X框架这里AOP根本没有发挥它应有的效用。 开源必读避免绑定 开源软件尤其是框架类软件应该尽量避免对你的用户造成绑定。能够在POJO上实现的功能就不要强迫用户实现你的接口能够通过接口实现的功能就不要强迫用户继承你的基类。尤其是Java语言只允许单根继承一旦要求用户的类继承框架基类那么前者就无法再继承其他任何基类这是一种非常严重的绑定不论用户和框架设计者都应当极力避免。 写在最后 看完这篇多少有些尖刻的批评恐怕读者难免要怪责我“不厚道”——毕竟糟糕的开源软件堪比恒河沙数为什么偏要选中X框架大加挞伐呢在此我要给各位读者、各位有志于开源的程序员一个最后、却是最重要的建议 开源必读切忌好大喜功 开源是一件长期而艰巨的工作对于只能用业余时间参与的我们更是如此。做开源务必脚踏实地做出产品首先在小圈子里内部讨论然后逐渐扩大宣传的圈子。切勿吹大牛、放卫星把“未来的愿景”当作“今天的承诺”来说——因为一旦工作忙起来谁都不敢保证这个愿景到哪天才能实现。 国人还有个爱好凡事喜欢赶个年节“献礼”或是给自己绑上个“民族软件”的旗号这更是开源的大忌。凡是做过政府项目的程序员想必都对“国庆献礼”、“新年献礼”之类事情烦不胜烦轮到自己做开源项目时又何苦把自己套进这个怪圈里呢当然如果你的开源项目原本就是做给某些官老爷看的那又另当别论。 所以我的这位朋友怕也不能怪我刻薄要不是他紧赶着拿出个远未完善的版本“新年献礼”要不是他提前放出“AOP/IoC”的卫星要不是他妄称这个框架“代表民族软件水平”或许我还会夸他的代码颇有可看之处呢。有一句大家都熟悉的老话笔者私以为所有投身开源者颇可借鉴在此与诸位共勉 长得丑不是你的错…… [1] 这篇Blog的原文请看http://gigix.blogdriver.com/gigix/474041.html。 [2] 关于IoC模式和Dependency Injection模式详见Martin Fowler的《Dependency Injection与模式IoC容器》一文。中译本发表于《程序员》2004年第3期。 软件的架构与设计模式之什么是架构 什么是软件系统的架构Architecture一般而言架构有两个要素·它是一个软件系统从整体到部分的最高层次的划分。一个系统通常是由元件组成的而这些元件如何形成、相互之间如何发生作用则是关于这个系统本身结构的重要信息。详细地说就是要包括架构元件Architecture Component、联结器
Connector、任务流
Task-flow。所谓架构元素也就是组成系统的核心砖瓦而联结器则描述这些元件之间通讯的路径、通讯的机制、通讯的预期结果任务流则描述系统如何使用这些元件和联结器完成某一项需求。·建造一个系统所作出的最高层次的、以后难以更改的商业的和技术的决定。在建造一个系统之前会有很多的重要决定需要事先作出而一旦系统开始进行详细设计甚至建造这些决定就很难更改甚至无法更改。显然这样的决定必定是有关系统设计成败的最重要决定必须经过非常慎重的研究和考察。计算机软件的历史开始于五十年代历史非常短暂而相比之下建筑工程则从石器时代就开始了人类在几千年的建筑设计实践中积累了大量的经验和教训。建筑设计基本上包含两点一是建筑风格二是建筑模式。独特的建筑风格和恰当选择的建筑模式可以使一个独一无二。下面的照片显示了
中美洲古代玛雅建筑Chichen-Itza大金字塔九个巨大的石级堆垒而上九十一级台阶象征着四季的天数夺路而出塔顶的神殿耸入云天。所有的数字都如日历般严谨风格雄浑。难以想象这是石器时代的建筑物。图1、位于墨西哥Chichen-Itza在玛雅语中chi意为嘴chen意为井的古玛雅建筑。摄影作者 软件与人类的关系是架构师必须面对的核心问题也是自从软件进入历史舞台之后就出现的问题。与此类似地自从有了建筑以来建筑与人类的关系就一直是建筑设计师必须面对的核心问题。
英国首相丘吉尔说我们
构造建筑物然后建筑物构造我们We shape our buildings, and afterwards our buildings shape us。英国下议院的会议厅较狭窄无法使所有的下议院议员面向同一个方向入座而必须分成两侧入座。丘吉尔认为议员们入座的时候自然会选择与自己政见相同的人同时入座而这就是英国政党制的起源。Party这个词的原意就是方、面。政党起源的关键就是建筑物对人的影响。在软件设计界曾经有很多人认为功能是最为重要的形式必须服从功能。与此类似地在建筑学界现代主义建筑流派的开创人之一Louis Sullivan也认为形式应当服从于功能Forms follows function。几乎所有的软件设计理念都可以在浩如烟海的建筑
学历史中找到更为遥远的历史回响。最为著名的当然就是模式理论和XP理论。架构的目标是什么正如同软件本身有其要达到的目标一样架构设计要达到的目标是什么呢一般而言软件架构设计要达到如下的目标·可靠性Reliable。软件系统对于用户的商业经营和管理来说极为重要因此软件系统必须非常可靠。·安全行
Secure。软件系统所承担的交易的
商业价值极高系统的
安全性非常重要。·可扩展性Scalable。软件必须能够在用户的使用率、用户的数目增加很快的情况下保持合理的性能。只有这样才能适应用户的市场扩展得可能性。 ·可定制化Customizable。同样的一套软件可以根据客户群的不同和市场需求的变化进行调整。·可扩展性Extensible。在新技术出现的时候一个软件系统应当允许导入新技术从而对现有系统进行功能和性能的扩展·可维护性Maintainable。软件系统的维护包括两方面一是排除现有的
错误二是将新的软件需求反映到现有系统中去。一个易于维护的系统可以有效地降低技术支持的花费·客户体验Customer Experience。软件系统必须易于使用。·市场时机
Time to Market。软件用户要面临同业竞争软件提供商也要面临同业竞争。以最快的速度争夺市场先机非常重要。架构的种类根据我们关注的角度不同可以将架构分成三种·逻辑架构、软件系统中元件之间的关系比如用户界面数据库外部系统接口商业逻辑元件等等。比如下面就是笔者亲身经历过的一个软件系统的逻辑架构图图2、一个逻辑架构的例子 从上面这张图中可以看出此系统被划分成三个逻辑层次即表象层次商业层次和数据持久层次。每一个层次都含有多个逻辑元件。比如
WEB服务器层次中有
HTML服务元件、Session服务元件、
安全服务元件、
系统管理元件等。·物理架构、软件元件是怎样放到硬件上的。比如下面这张物理架构图描述了一个分布于北京和上海的
分布式系统的物理架构图中所有的元件都是物理设备包括网络分流器、代理服务器、
WEB服务器、应用服务器、报表服务器、整合服务器、
存储服务器、主机等等。图3、一个物理架构的例子 ·系统架构、系统的非功能性特征如可扩展性、可靠性、强壮性、灵活性、性能等。系统架构的设计要求架构师具备软件和硬件的功能和性能的过硬知识这一工作无疑是架构设计工作中最为困难的工作。此外从每一个角度上看都可以看到架构的两要素元件划分和设计决定。 首先一个软件系统中的元件首先是逻辑元件。这些逻辑元件如何放到硬件上以及这些元件如何为整个系统的可扩展性、可靠性、强壮性、灵活性、性能等做出贡献是非常重要的信息。其次进行软件设计需要做出的决定中必然会包括逻辑结构、物理结构以及它们如何影响到系统的所有非功能性特征。这些决定中会有很多是一旦作出就很难更改的。根据作者的经验一个基于数据库的系统架构有多少个数据表就会有多少页的架构设计文档。比如一个中等的
数据库应用系统通常含有一百个左右的数据表这样的一个系统设计通常需要有一百页左右的架构设计文档。架构师软体设计师中有一些技术水平较高、经验较为丰富的人他们需要承担软件系统的架构设计也就是需要设计系统的元件如何划分、元件之间如何发生相互作用以及系统中逻辑的、物理的、系统的重要决定的作出。这样的人就是所谓的架构师Architect。在很多公司中架构师不是一个专门的和正式的职务。通常在一个开发小组中最有经验的程序员会负责一些架构方面的工作。在一个部门中最有经验的项目经理会负责一些架构方面的工作。但是越来越多的公司体认到架构工作的重要性并且在不同的组织层次上设置专门的架构师位置由他们负责不同层次上的逻辑架构、物理架构、系统架构的设计、配置、维护等工作。 OO系统设计师之路--设计模型系列(1)--软件架构和软件框架