当前位置: 首页 > news >正文

架构设计 例子和实践 系统设计说明书

架构设计 例子和实践 系统设计说明书(架构、概要、详细)目录结构演进架构中的领域驱动设计Web架构设计经验分享软件架构设计从MVC框架看MVC架构的设计领域驱动设计(Domain Driven Design)参考架构详解关于垂直切分Vertical Sharding的粒度企业应用集成与开源ESB产品ServiceMix和Mule介绍论基于数据访问的集合类(Data Access Based Collection)和领域事件(Domain Event)模式关于系统异常设计的再思考前车之覆,后车之鉴——开源项目经验谈软件的架构与设计模式之什么是架构OO系统设计师之路–设计模型系列(1)–软件架构和软件框架

系统设计说明书(架构、概要、详细)目录结构

虽然这些文档一般来说公司都是有模板的,但我写这些文档以来基本上是每写一次就把目录结构给改一次,应该说这是因为自己对这些文档的理解开始加深,慢慢的越来越明白这些文档的作用和其中需要阐述的东西,觉得这三份文档主要阐述了一个系统的设计和实现过程,从系统分解为层次、层次内的模块以及相互的接口、模块分解为对象以及对象的接口、实现这些对象接口的方法。这次又整了一份,^_^,欢迎大家指正。

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

【 直播】淘宝开放平台应用审核全过程

相关赞助商

参与赢在淘宝活动,解析淘宝开放平台,让商业创意和技术完美地合二为一!现在点击浏览淘宝开发者社区

顶层标题:

  1. 计划背景
  2. 从DDD开始
  3. 增量计划中的DDD过程
  4. 进化的领域模型
  5. 代码级别的演进
  6. 演进架构中DDD的一些教训
  7. 附录:具体示例

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时我们使用的两个重要原则是:

  1. 领域模型归属于业务;
  2. 领域模型需要一个权威的业务源。

领域模型的业务所有权在入门中就进行了解释,但在这里才发挥作用。这意味着技术团队的关键角色是聆听并理解,而不是解释什么可能、什么不可能。需求抽象要求将概念性的领域模型映射到具体的功能需求上,并在存在不匹配的地方对业务代表提出异议或进行询问。接着,存在不匹配的地方要么改变领域模型,要么在更高层次上解决功能需求(“你想用此功能达成什么效果?”)。

对领域模型来说,权威的业务源是我们组织的性质所明确需要的。我们正在构建一个独立的软件平台,它需要满足很多编辑团队的需求,编辑团队不一定以同样的方式来看世界。Guardian不实行许多公司实行的“命令和控制”结构;编辑台则有很多自由,也能以他们认为合适的方式去开发自己的网站版面,并设定预期的读者。因此,不同的编辑对领域模型会有略微不同的理解和观点,这有可能会破坏单一的统一语言。我们的解决办法是确定并加入业务代表,他们在整个编辑台都有责任。对我们来说,这是生产团队,是那些处理日常构建版面、指定布局等技术细节的人。他们是文字编辑依赖的超级用户,作为专家工具建议,因此技术团队认为他们是领域模型的持有者,而且他们保证软件中大部分的一致性。他们当然不是唯一的业务代表,但他们是与技术人员保持一致的人员。

3.2.2 与DDD一起计划的问题

不幸的是,我们发现了在计划过程中应用DDD特有的挑战,尤其是在持续计划的敏捷环境中。这些问题是:

  1. 本质上讲,我们正在将软件写入新的、不确定商业模式中;
  2. 绑定到一个旧模型;
  3. 业务人员“入乡随俗”。

我们反过来讨论下这些问题……

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.article com.gu.r2.model.content.video com.gu.r2.model.content.audio 

我们将内容聚集细分为多个子包,因为内容对象往往有很多聚集特定的支持类(这里的简化图中没有显示)。所有以标签为基础的对象往往要更为简单,所以我们将它们放在了一个包里,而没有引入额外的复杂性。

不过不久之后,我们认识到上述包结构会给我们带来问题,我们打算修改它。看看我们前端应用的包结构示例,了解一下我们如何组织控制器,就能阐述清楚这一问题:

 com.gu.r2.frontend.controller.page com.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.model com.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意义上的实体,而不是业务所有和业务定义的领域对象。

不愿意在领域对象中加入业务逻辑会导致贫血的领域模型,如果不加以制止还会使混乱的服务对象激增——就像我们等会儿看到的一样。作为一个团队,现在我们来检视一下创建的服务对象,看看它们实际上是否包含业务逻辑。我们还有一个严格的规则,就是开发人员不能在模型中创建新的对象类型,这对业务来说并不意味着什么。

作为团队,我们在项目开始时还被实体对象给弄糊涂了,而且这种困惑也与持久化有关。在我们的应用中,大部分实体与内容有关,而且大部分都被持久化了。但当实体不被持久化,而是在运行时由工厂或资源库创建的话,有时候还是会混淆。

一个很好的此类例子就是“标签合成的页面”。我们在数据库中持久化了编辑创建的所有页面的外观,但我们可以自动生成从标记组合(比如USA+Economics或Technology+China)聚集内容的页面。由于所有可能的标记组合总数是个天文数字,我们不可能持久化所有的这些页面,但系统还必须能生成页面。在渲染标记组合页面时,我们必须在运行时实例化尚未持久化的新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 PageDAO<Page> pageDAO;  private final PagesRelatedBySectionDAO pagesRelatedBySectionDAO;  public PageRepository(PageDAO<Page> pageDAO,         EditorialPagesInThisSectionDAO pagesInThisSectionDAO,          PagesRelatedBySectionDAO pagesRelatedBySectionDAO) {      this.pageDAO = pageDAO;      this.pagesRelatedBySectionDAO = pagesRelatedBySectionDAO; }  public List<Page> getAudioPagesForPodcastSeriesOrderedByPublicationDate(Series series, int maxNumberOfPages) {      return pageDAO.getAudioPagesForPodcastSeriesOrderedByPublicationDate(series, maxNumberOfPages);  }  public List<Page> getLatestPagesForSection(Section section, int maxResults) {     return pagesRelatedBySectionDAO.getLatestPagesForSection(section, maxResults);  }

我们的资源库有业务请求:获取PublicationDate请求的特定播客系列的页面。获取特定版面的最新页面。我们可以看看这里使用的业务领域语言。它不仅仅是一个数据访问对象,它本身就是一个领域对象,跟页面或文章是领域对象一样。

我们花了一段时间才明白,把资源库看成是领域对象有助于我们克服实现领域模型的技术问题。我们可以在模型中看到,标签和内容是一种双向的多对多关系。我们使用Hibernate作为ORM工具,所以我们对其进行了映射,Tag有如下方法:

public List<Content> getContent();

Content有如下方法:

 public List<Tag>  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架构生命周期能够帮到你

architecture_life_cycle

所设计的架构需要在1-10倍的增长下,通过简单的增加硬件容量就能够胜任,而在5-10倍的增长期间,请着手下一个版本的架构设计,使之能承受下一个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

我们一定要使用数据库吗,不一定,雷鸣告诉我们搜索不一定需要数据库,云风告诉我们,游戏不一定需要数据库,那么什么时候我们才需要数据库呢,为什么不干脆用文件来代替他呢?
首先我们需要先承认,数据库也是对文件进行操作。我们需要数据库,主要是使用

           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

相关文章:

JSON相关知识点

JSON是工作中经常会遇到的一种数据结构&#xff0c;下面来讲讲与他相关的一些知识点。 JSON简介&#xff1a; JSON: JavaScript Object Notation(JavaScript 对象表示法) JSON 是存储和交换文本信息的语法,类似 XML。 JSON 比 XML 更小、更快&#xff0c;更易解析。 JSONObj…...

TestNG使用总结

TestNG简介&#xff1a; TestNG是一个测试框架&#xff0c;其灵感来自JUnit和NUnit&#xff0c;但同时引入了一些新的功能&#xff0c;使其功能更强大&#xff0c;使用更方便。 TestNG相较于Junit的优点&#xff1a; 可指定执行顺序&#xff0c; dependsOnMethods 属性来应对…...

idea常用技巧收集

idea相比eclipse的优点我在这里就不赘述了&#xff0c;更多参考&#xff1a;idea官网,本文重点讲下自己在idea使用过程中常用的一些技巧&#xff0c;以后随时更新…… 主要分成三大块&#xff1a; 1. 系统设置 2. 快捷键 3. 其他设置 系统设置 主题风格设置&#xff1a;默…...

初识spring RestTemplate

《spring实战》读书笔记之初识spring RestTemplate。首先需要先了解下什么是REST。 REST&#xff1a;官方解释&#xff0c;表述性状态转移。 感觉还是不知所云&#xff0c;参考下怎样用通俗的语言解释REST&#xff0c;以及RESTful&#xff1f; 即URL定位资源&#xff0c;用H…...

ZOJ - 2112 Dynamic Rankings 动态主席树 主席树+树状数组

题目链接&#xff1a;https://vjudge.net/problem/ZOJ-2112 题意&#xff1a;能修改的查询第k大 学习博客&#xff1a;https://blog.csdn.net/WilliamSun0122/article/details/77885781 理解&#xff1a;修改pos位置x - >y的话&#xff0c;影响的是【pos&#xff0c;n】&…...

Gym - 101889I Imperial roads 树链剖分+最小生成树之必选一边

题目链接&#xff1a;https://vjudge.net/problem/Gym-101889I 题意&#xff1a;n个点&#xff0c;m条边&#xff0c;q次询问&#xff0c;每次必须选一边&#xff0c;求最小生成树的权值 题解&#xff1a;我们先跑一边最小生成树&#xff0c;然后按照最小边建树&#xff0c;必…...

HDU - 6188 Duizi and Shunzi 贪心

Nike likes playing cards and makes a problem of it. Now give you n integers, ai(1≤i≤n) We define two identical numbers (eg: 2,2) a Duizi, and three consecutive positive integers (eg: 2,3,4) a Shunzi. Now you want to use these integers to form Shunzi and …...

CodeForces - 545D Queue 贪心 排序

题目链接&#xff1a;点击查看 Little girl Susie went shopping with her mom and she wondered how to improve service quality. There are n people in the queue. For each person we know time ti needed to serve him. A person will be disappointed if the time he …...

牛客网 Applese 走方格

题目链接&#xff1a;点击查看 题解&#xff1a;我们容易发现到当n,m都为奇数时&#xff0c;是回不到原点的&#xff0c;因为你无论哪个方向走一去一回就是两步&#xff0c;所以n和m必然有一个偶数&#xff0c;那至于我们怎么走呢&#xff0c;看下图&#xff0c;注意的是n1,m2…...

POJ - 2406 Power Strings next数组应用循环节

题目链接&#xff1a;点击查看 Language:Default Power Strings Time Limit: 3000MS Memory Limit: 65536KTotal Submissions: 61784 Accepted: 25534Description Given two strings a and b we define a*b to be their concatenation. For example, if a "abc" and…...

数论一些小总结

1.对于任意一个素数p, n!中含有p的个数为 (n/p n/p^2 n/p^3 ......). 2.c(n,k) n! / ( k! * (n-k)! ). 3.c(n,k)(n-k1)/k*c(n,k-1). 4.任意一个数n可以写成若干个素数的乘积&#xff0c;即 p1^a1 * p2^a2*......*pn^an&#xff0c; 它的的约数的个数为 (a11)*(a21)*..…...

接口测试那些事儿

什么是接口&#xff1f; 首先&#xff0c;在讲接口测试之前&#xff0c;我们先要搞清楚接口类型的概念。 接口&#xff1a;可能是系统与系统&#xff08;包括服务与服务&#xff09;之间的调用&#xff0c;像A系统&#xff08;服务&#xff09;给B系统&#xff08;服务&#x…...

mysql重置Root密码

方法一: 在my.ini的[mysqld]字段加入&#xff1a; skip-grant-tables 重启mysql服务&#xff0c;这时的mysql不需要密码即可登录数据库 然后进入mysql mysql>use mysql; mysql>更新 user set passwordpassword(新密码) WHERE Userroot; mysql>flush privileges; 运…...

每天一个adb命令:wm命令详解

wm命令可以用于获取屏幕分辨率、像素密度等。 前提&#xff1a;Android4.3及以上 usage: wm [subcommand] [options]wm size [reset|WxH]wm density [reset|DENSITY]wm overscan [reset|LEFT,TOP,RIGHT,BOTTOM]wm size: return or override display size.wm density: overrid…...

idea插件开发入门

前言&#xff1a;最近想研究一款自动在idea中定位缺陷及发送JIRA的快捷工具&#xff0c;方便提升报自动化脚本的bug的效率。因为idea插件学习是必不可少了&#xff0c;沉淀小结如下。 idea插件开发入门插件用途工程创建配置文件Action实现开发语法常用对象常用方法运行效果打包…...

读王安石变法

今天早上读到王安石变法&#xff0c;王安的变法确实充满理想化。以现代金融的办法进行国家的经济改革。但最终却并未走向成功&#xff0c;其中值得我们反思。思想太过超前&#xff0c;在没有实际土壤的环境下&#xff0c;再好的策略都难免不可能实现&#xff0c;这让我想起来摩…...

2017 ICPC Asia Urumqi I. A Possible Tree 带权并查集

题目链接&#xff1a;https://nanti.jisuanke.com/t/40520 题解&#xff1a;因为他们都是联通的且只有唯一路径&#xff0c;所以不用管之前怎么连的&#xff0c;直接按照他给的查询&#xff0c;带权并查集判断即可 #include <bits/stdc.h> using namespace std; const …...

基于数据驱动的接口自动化测试解决方案

总结一下我么项目中使用的基于数据驱动的接口自动化测试解决方案&#xff0c;仅供大家参考。1.接口框架设计结构 2.接口测试脚本设计原则 3.持续集成 这块用jenkins就可以了&#xff0c;就不介绍了&#xff0c;目前我们项目的集成规则介绍一下&#xff1a; 1.脚本job与应用对…...

C++设计模式

管理变化&#xff0c; 提高复用 两种手段&#xff1a;分解 抽象 八大原则&#xff1a;https://blog.csdn.net/mmk27_word/article/details/108521903 重构技法&#xff1a; 静态 -> 动态 早绑定 -> 晚绑定 继承 -> 组合 编译时依赖 -> 运行时依赖 紧耦合 ->…...

spring boot深入及启动原理探究

围绕spring boot 的优点&#xff0c;本文我们来探究一下spring boot具体是如何实现这些特性的。 自动配置&#xff1a;针对很多Spring应用程序和常见的应用功能&#xff0c;Spring boot能自动提供相关配置&#xff1b;起步依赖&#xff1a;告诉Spring boot需要什么功能,它就能引…...

JfreeChart柱状图饼图

JfreeChart画出柱状图饼图的代码片段及详细的注释&#xff0c;附件为JfreeChart中文API一览表&#xff0c;和生成的柱状图&#xff0c;饼图图片 import java.awt.Font; import java.io.FileOutputStream; import java.io.IOException; import org.jfree.chart.C…...

自动装配的底层实现

public void autowire(Object o, Map<String, String> map) throws Exception { // 获得map 所有key Set<String> keys map.keySet(); // 获得Object中所有属性 // 获得Class对象 Class c o.getClass(); // 获得…...

Java异常打印输出中常见方法的分析

Java异常是在Java应用中的警报器&#xff0c;在出现异常的情况下&#xff0c;可以帮助我们程序猿们快速定位问题的类型以及位置。但是一般在我们的项目中&#xff0c;由于经验阅历等多方面的原因&#xff0c;依然有若干的童鞋在代码中没有正确的使用异常打印方法&#xff0c;导…...

八大设计模式原则

1.依赖倒置原则 高层模块不依赖底层模块&#xff0c;二者都应该依赖抽象 抽象不依赖实现细节&#xff0c;实现细节应该依赖于抽象 这一原则与下面的针对接口编程而不是针对实现编程是一个道理&#xff0c;我们设计一个程序&#xff0c;我们应该先想好我们想要抽象什么&#x…...

每天一个adb命令:input 命令详解

input命令可以用于向键盘发送一些指令&#xff0c;先看看input的官方说明&#xff1a; Usage: input [<source>] <command> [<arg>...]The sources are:mousekeyboardjoysticktouchnavigationtouchpadtrackballstylusdpadtouchscreengamepadThe commands an…...

基于java的开源车牌识别源码

真正的大师,永远都怀着一颗学徒的心&#xff01; 有没有发现昨天断更了一天 年底各种写文档 准备项目验收 刚好也快放假啦 忙完这几天 要回老家过年了 可能要断更一段时间啦 年后会继续的 今天给大家推荐一个开源的车牌识别的demo源码 技术选型 软件架构 B/S 架构&…...

matlab图形功能

//...

程序员着装的改变

是什么力量&#xff0c;让任何地方的程序员都享有「免于体面的自由」&#xff1f; 在今天的社会里&#xff0c;工程师往往代表着知识水平和社会地位。每当普通人听到这个头衔&#xff0c;总会报之以敬仰的目光&#xff1a; 但有一种工程师&#xff0c;虽然也是如假包换的高级技…...

组合数总结

转自&#xff1a;国特震哥 点击查看链接 对于求C(n,m) 1.如果是对于小范围内的n和m&#xff08;不是很难&#xff09;就不说了 差不多用java的大数就可以了 2.当n在1e10^5范围左右&#xff0c;往往是会有取模&#xff0c;设这个数为mod&#xff08;往往mod为质数&#xff0…...

HDU 5667 Sequence 矩阵快速幂 + 费马小定理

olion August will eat every thing he has found. Now there are many foods,but he does not want to eat all of them at once,so he find a sequence. fn⎧⎩⎨⎪⎪1,ab,abfcn−1fn−2,n1n2otherwise He gives you 5 numbers n,a,b,c,p,and he will eat fn foods.B…...

Gym - 100589A Queries on the Tree 树状数组+分块

题目链接&#xff1a;https://vjudge.net/problem/Gym-100589A 题意&#xff1a;n个点&#xff0c;根节点为1的树&#xff0c;两种操作&#xff0c;1 L y 与根节点距离为L的节点权值全部加上y&#xff0c;2 x x子树的权值总和 题解&#xff1a;对于更新操作&#xff0c;因为更…...

CodeForces - 1073C Vasya and Robot

Vasya has got a robot which is situated on an infinite Cartesian plane, initially in the cell (0,0) . Robot can perform the following four kinds of operations: U — move from (x,y) to (x,y1) ;D — move from (x,y)to (x,y−1);L — move from (x,y)to (x−1,…...

webservice学习记录笔记(一)

一、先理解什么是服务 现在的应用程序变得越来越复杂&#xff0c;甚至只靠单一的应用程序无法完成全部的工作。更别说只使用一种语言了。 写应用程序查询数据库时&#xff0c;并没有考虑过为什么可以将查询结果返回给上层的应用程序&#xff0c;甚至认为&#xff0c;这就是数…...

flex获得指定时间段一共多少天

/** * 获得某个时间段 共有多少天 * param start 开始时间 * param end 结束时间 * return * */ public static function getTimeDays( start:Date , end:Date , type:int0):Number { var _re:int 0 ; if(start && end) { var _str:Numbe…...

互联网产品与需求二 评估需求

需求分析之评估需求 一.KANO模型 五个用户需求类型1.必备型需求必备型需求是用户认为产品“必须有”的属性或者功能当其特性不充足&#xff08;不满足用户需求&#xff09;时&#xff0c;用户很不满意当其特性充足&#xff08;满足用户需求&#xff09;时&#xff0c;无所谓满意…...

flex勾选,自动刷新

mxml: <s:CheckBox label"刷新" buttonMode"true" id "frc" selected "{model.autoFresh}" change "{model.startAutoQuery(frc.selected)}"/> <s:Label text"间隔"/> &…...

flex 下拉框验证组件

//继承验证 public class ObjectNullValidator extends Validator { public function ObjectNullValidator() { super(); requiredFieldError "必须填写" ; } private var _invalidCode:String "222"; public static function validateS…...

flex 事件机制详解

事件机制的工作流程 1&#xff1a;关于事件流 当一个事件发生&#xff0c;必然存在一个派发事件的对象&#xff0c;这里称之为目标对象。 当事件发生后flashPlayer生成一个携带数据的对象&#xff0c;然后检查目标对象是否处于显示层中&#xff0c;如果是则遍历从根容器一直到目…...

bzoj1568: [JSOI2008]Blue Mary开公司 李超线段树

题目链接&#xff1a;https://www.lydsy.com/JudgeOnline/problem.php?id1568 李超线段树&#xff1a; 问题&#xff1a; 现在要求你在线动态维护一个二维平面直角坐标系&#xff0c;支持插入一条线段&#xff0c;询问与直线xx0相交的所有线段中交点y的最大/最小值 更新&a…...

10.9日计划

1.完成python的学习:主要针对函数和高级特性这两部分,并将完成的python代码在远程VPS服务器上填写完整。2.完成leetcode算法题目两道&#xff1a;本月的计划主要针对动态规划、深度遍历和广度遍历的题目。3.完成自传的阅读&#xff1a;主要是针对李鸿章传记的阅读。4.完成正常工…...

Appium-java API详解

目前appium-java最新版本是5.0.0-BETA3&#xff0c;因此就拿最新的说明&#xff0c;以Java为例&#xff0c;首先引入java client的依赖&#xff1a;<dependency><groupId>io.appium</groupId><artifactId>java-client</artifactId><version&g…...

MIPS GCC 嵌入式汇编

转载&#xff1a;MIPS GCC 嵌入式汇编 (2007-04-15 16:08:15) 转载▼分类&#xff1a; 转载文章 1. GCC 内嵌汇编的基本格式 asm("assembly code"); 如&#xff1a; asm("syscall"); //触发一个系统调用 如果有多条指令&#xff0c;则需在指令尾部添…...

问题 I: Monitoring Ski Paths 树链剖分+LCA+树状数组+贪心

问题 I: Monitoring Ski Paths 时间限制: 1 Sec 内存限制: 128 MB [提交] [状态] [命题人:admin] 题目描述 Fresh powder on a sunny day: it is a great time to ski! Hardcore skiers flock to a large mountain in the Rockies to enjoy these perfect conditions. The …...

与或非门电路的实现原理。

与或非门电路的实现原理。非门&#xff1a;A输入0时三极管截止&#xff0c;L处的电压近似5V。或门&#xff1a;A处输入高电平&#xff0c;B处输入高电平&#xff0c;则D1与D2均未导通&#xff0c;则L处近似为5V。A或者B为低电平&#xff0c;则导致A或者B一定有一段导通&#xf…...

2019ccpc网络赛 HDU - 6705 path 贪心跑第k小的路径长度

题目链接&#xff1a;https://vjudge.net/problem/HDU-6705 题解&#xff1a;建立源点 汇点 跑A*&#xff0c;到最后也一直超内存也真是菜到家了&#xff0c;A*时间空间怎么也得n^2&#xff0c;这个题原来是个贪心。。。。 官方题解&#xff1a; 先把每条边以 形式放进堆&am…...

ZOJ - 4019 Schrödinger's Knapsack dp

题目链接&#xff1a; http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode3981 题意: 很明显当我们可以继续取物品的时候 我们应该先取小的那一个 这样可以保证得到的价值 最大 dp[i][j] 表示第一组取前i个 第二组取前j个 dp[i][j] max(dp[i][j-1] v2 * 剩…...

HDU - 6153 A Secret 扩展kmp

题目链接&#xff1a;https://vjudge.net/problem/HDU-6153 题意&#xff1a;对于s2的每一个后缀&#xff0c;假设长度为l&#xff0c;在s1出现的次数为k&#xff0c;求l*k的和 题解&#xff1a;我们把两个串都倒过来&#xff0c;变为s1,s2&#xff0c;那么问题就变为&#x…...

Gym - 100513F Ilya Muromets 贪心

Силачом слыву недаром — семерых одним ударом! From the Russian cartoon on the German fairy tale. Ilya Muromets is a legendary bogatyr. Right now he is struggling against Zmej Gorynych, a dragon with n heads numbered …...

log4j学习demo

log4j简介 Log4j是Apache的一个开放源代码项目&#xff0c;是项目中比较常用的日志记录组件。 引入log4j <dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.14</version></dependency> log4…...

UVALive 8266 Network Report Floyd算法

题解&#xff1a;给你几个点和几条边&#xff0c;让你求任意两点之间的最短路&#xff0c;并且自己到达自己的不算&#xff0c;求出路径长度不同的分别有几条 #include<bits/stdc.h> using namespace std; #define INF 0x3f3f3f3fconst int N210; int dis[N][N],ans[N];…...