读书笔记: Microsot .NET企业级应用架构设计
Microsot .NET企业级应用架构设计
Dino Esposito Andrea Saltarello 编 陈黎夫 译 人民邮电出版社
第一部分 设计原则
第1章 当代架构师和架构
完美的设计不是包罗万象无所不有,而完整自治不可精简。
软件工程的目的是控制复杂性,而不是增加复杂性
人们对软件系统的普遍理解是指为完成某类特定功能的一系列组件的组合和集成
验证架构的方法就是通过各类型的测试:单元测试,集成测试,验收测试。
*墨菲法则是对现实最深刻的描述。若现实世界中某件事情重复发生,就会被法则收录。
本章的黑菲法则:
1.为一个起不上进度的软件项目增加人手,只能让其更加落后于进度
2.程序的复杂性会一直增加,直到负责维护的程序员力不从心为止。
3.若建筑师按照程序员写程序那样造房子,那么史上出现的第一只啄木鸟也许会毁掉整个文明
第2章 UML必要知识
所有的模型都不甚完备,有些模型却有些用处
UML图表共分为两大类:结构和行为
结构:
类图:给出系统中的类型,接口和关系。
组件图:给出组件及其之间的依赖关系,
组合结构图:以结构化的方式,给出类型的内部结构,
部署图:给出组件与硬件资源的映射关系,
对象图:给出系统在某一时刻的状态,
包图:给出类型逻辑上的分组关系
行为:
活动图:给出活动中的操作流程,
通信图:给出对象之间的交互,
交互概述图:给出顺序和活动,
顺序图:给出对象之间的交互,
状态机图:给出对象在发生事件之后状态的变化,
时间图:给出一段时间内对象的行为,
用例图:给出某个参与者在系统中执行哪些操作。
UML是一种通用的建模语言,可以应用到各种不同的领域中,仅限于大体的描述。
领域特定语言专门为特定领域和场景设计,它是一种定制的,直击要害的专门语言。
墨菲法则:
1.真正的程序从不添加注释,若代码难以编写,那么也会难以阅读
2.程序员90%的错误来自于其他程序员提供的数据。
3.编写符合需求的程序员和在水面行走一样简单,前提是需求已确定,水面已结冰。
第3章 设计原则与模式
敏捷开发最主要的优势是敏捷方法带来的团队内部以及团队和客户之间持续的交流。前一次的迭代中犯下的错误,可以在下一次的迭代中修复。
导致变化的不是感性的决定,而是理性的思考。
高内聚 低耦合
分离关注点:模块化和信息隐藏。它还可以应用到软件架构中,如面向服务架构SOA,用服务来表示关注点,分层架构也基于分离关注点原则构造
在《设计模式》中有句话:设计应该着眼于解决目前的问题,但同时也要足够灵活地能够适应未来可能出现的问题和需求。
面向对象设计的基本原则可以总结成3条:找到合适的对象,尽量降低耦合和尽量保证代码重用。
基于接口,而不是实现编程。
应该使用接口还是抽象类呢?在那些不支持多继承的面向对象语言中,推荐使用接口,方便以后的扩展。
尽量使用对象组合,而不是类型继承。
模块应该对扩展开放,但对修改关闭。
里氏替换原则:子类应该可以替代其基类使用。
依赖倒置原则也叫IOC:高层次组件不应该依赖于低层次组件,二者均应依赖于接口。抽象不应该依赖于细节,细节应该依赖于抽象。
设计模式不是你一开始就选用的,最合适的设计模式通常在重构的过程中会渐渐浮出水面。
常见的架构模式有针对应用程序建模的分层和SOA,针对表现层的MVC,针对业务层的Domain Model,和服务层,网络相关的点对点(peer to peer)等。
应用使用结构体还是类? 结构体不可被继承,类是引用类型分配在堆上由垃圾收集器管理,结构体是值类型分配在栈上离开作用区域上消亡。
规则建议应该尽可能使用类,除非所占的存储空间低于16字节,或类型是不可变化的。
安全开发生命周期SDL基石:分层,组件化 ,角色。
6种主要的类型的威胁:用户身份欺诈,篡改,抵赖,信息泄露,拒绝服务,权限提升
若想设计出好的软件,普通的设计原则就够了,你并不特别需要模式。时至今日,重复发明轮子绝对谈不上是什么好事。
墨菲法则:
1.软件可以正常工作的概率与其所需要的代码行数成反比
2.Bug出现的几率与正在查看该软件的人数及这些人的重要程序成正比
3.专家就是那些最后一刻赶到,陪众人一起挨骂的人。
第二部分 系统设计
第4章 业务层
任何人都要以写出计算机能够理解的代码,而只有优秀的程序员才能写出别人可以理解的代码。
业务对象不过是领域实体的实现。
业务逻辑层的输入和输出不一定是业务对象,很多时候,更倾向于使用dto在层之间交换数据。
当已经有了数百个业务对象时,或许并不应该仅为了设计上的干净而让这个数字加倍 (增加了相应的dto),在这种情况下,dto通常就是业务对象
我们应该尽可能地降低物理层的数目。添加一个新的物理层之前,必须要经过仔细的成本收益分析,这样做的代价,大多在于增加了复杂性,而优势一般体现在安全性,可伸缩性以及容错性上。
远程软件最好不要提供太过详细的接口(粗粒度接口-一般使用门面facada模式),而进程内的软件,最好有细粒度的接口
业务逻辑在其他层中的适当重复是可以接受的,但不应当受到鼓励。
将业务逻辑从存储过程中提出来:便于重构,方便调度和测试,还能提高数据库的可移植性。
若感觉更应该关注于操作,可以选择事务脚本模式,
若以数据表的形式考虑更方便,那么选择表模式,
若领域模型与数据模型非常相似,那么活动记录模式可能更适合使用。
若建模的过程需要从各个相关对象开始,并划分成众多子系统,则可以选择领域模型模式
命令模式允许表现层通过工厂来间接实例化命令对象,这样就让整个设计有更强的扩展性。若想使用通用掊口,必须考虑如何处理返回值,一个被广泛接受的解决方案是定义一些只读成员,在RUN方法结束前把返回值存放于这些只读成员中供外界使用。
静态方法和实例方法从效率角度没有任何区别,只不过是设计上的选择而已,在设计时,你可以遵守如下的规则:若某个方法不需要与类中的其他成员交互,那么使用表态方法。该规则可以由FxCop来强制保证。若某个方法或属性和类是一个整体,那么应该是静态的,若某个方法或属性和特定的实例紧密相关,则不应该是表态的。
若需要用数据迁移对象在进程之间传递数据,那么数据迁移类必须标记为可序列化。
活动记录基于数据表中的行,而不像表模块那样基于数据表。也就是说,活动记录对数据有行级别的粒度,而表模块关注的是整个数据表。
活动记录得到了很多提供商的支持,如linqtosql castle activerecord。
在设计领域逻辑时,若选用了过程方法勤务活动记录模式,那么实际上采取的是以数据为核心的做法。以数据为核心表示系统设计的基础是在数据模型的角度,因此驱动业务模型的并不是业务本身,而业务中使用的数据。
领域驱动设计是一种考虑问题的方法:一种将问题领域放在所有事情中最首要位置的方法,基于真实实体和行为和其包含并管理的数据,将系统通过软件实体表示。
严格根据流程建模并适当提高数据库复杂性要比强制漂亮的数据库设计并使用随意的中间层好很多。
若没有orm工具,如nhibernate和ef,那么将很难实现领域模型模式。
领域模型和活动记录的实体有什么区别呢?区别在于领域模型是完全与数据库无关的,则活动记录实体表示的是数据库中的一行。
将持久化的代码放在领域模型之外需要一些动态的代码注入。换句话说,虽然你避免了将一些非业务相关的特性和属性混入到了领域对象中,不过同时这些功能在执行时仍要插入到对象中。一个常见的做法就是使用动态代理,可以在运行时为该类型添加缺失的功能。
第5章 服务层
在领域模型模式中,我们大都将服务层看做是业务层的一部分。以降低表现层与业务层的耦合。数据通过DTO来传递。
服务层实际上并不执行任务具体的工作,其功能在于组织各个业务对象,非常了解领域模型。
服务层仅了解一系统可用的交互方式,并关注一些具体细节,包括必要的事务,资源管理,协调,数据转换等。
服务层的职责类似于看门人,除非有特殊需要,否则没有必要将角色检查放在业务逻辑层中实现。
应用逻辑包含处理问题的工作流,且实现于服务层中,与业务怪分离开来。应用逻辑实现了用例,实现过程中使用到了领域逻辑所提供的功能。
SOA并不是服务,SOA是有关设计服务的原则:
边界清晰
服务自治:体现在每个服务都是独立于其所处,所被使用的系统而部署,管理和控制的。
使用契约而不是类:服务间应该基于消息通信,。
兼容性基于策略
第6章 数据访问层
数据访问层是系统中唯一可以操作数据库的一层,系统的其他部分也不应该包含任何有关数据库的信息。
工作单元就是逻辑业务上的事务,它包含一系列的数据库调用。
乐观锁与悲观锁???
若需要在工厂中添加额外的逻辑,以便判断工厂应该返回哪种具体类型,那么该做法就逐渐深化成了更完备的插件模型。
插件模式建议你从外部配置源中读取类型信息。
通常,业务事务将由服务层的类组织。业务的另一个花哨的名字叫工作单元(unit of work).
传言1: 存储过程要比sql代码执行效率高。
传言2: 存储过程要比sql代码更加安全。
传言3: 存储过程可用来阻挡sql注入攻击。
第7章 表现层
表现层有两个主要组件组成:用户界面和表现层逻辑。
验证,格式化,添加样式和可用性等应该是UI组件的属性,而不是表现层的逻辑或职责。
一个设计良好的表现层会让开发者在开发周期中更容易地切换到不同的UI,更加重要的是,这也让你可以实现UI动态加载或生成,并能完美配合底层运行时。
表现层必须保证图形化的任意修改都不会影响到数据流和表现层逻辑。
表现层尽可能不依赖于UI技术。
控制器将由视图触发并执行某个操作,最终对模型进行修改。若模型是业务逻辑层,那么控制器最终将调用业务逻辑层中的方法或服务。若模型是对业务逻辑层的抽象得到的一个组件,那么控制器将调用模型上的某个公开方法。
最后的思考
1 凡是无绝对
2 需求是超越一切的存在
3 根据接口编程
4 操作简单,但不过于简单。
5 继承是为了多态,而不是重用。
6 首先考虑可维护性
7 事后优化 过早地优化是所有软件的罪恶根源。