`
wsjjasper
  • 浏览: 13629 次
  • 性别: Icon_minigender_1
文章分类
社区版块
存档分类
最新评论

3.基于Spring的应用程序的设计和实现(构建领域对象模型)

阅读更多

 

构建领域对象模型

 

 

一个领域对象模型(DOM)是一系列的对象模型组成的问题领域。(比较拗口)。比较详细的描述可以参考《Patterns of Enterprise Application Architecture》,或者《Domain-Driven Design: Tackling Complexity inthe Heart of Software》。这里只讲下它的大概概念,虽然不打算在这里讲得非常细,但还是会解释为什么以及如何构建领域对象模型。

 

Spirng和领域对象模型

 

也许你会奇怪,为啥在讲spring的时候要搭上这么一个话题。因为spring构建的应用程序中,唯一一个没有被spring管理的就是领域对象模型(虽然spring中有@componet标签可以管理,但是大多数时候我们都是选择在应用程序中来管理)。而之所以这么做的原因就是spring没有必要去管理它!通常,我们在service层或者数据持久层使用new()操作来创建一个领域对象模型。虽然spring支持实例化对象(使用bean 标签,等等),但一般不这么做,因为这些模型和除了其本身外,和外部容器没什么依赖性。于是你就疑问了,那,我们去管他干啥??答案很简单,DOM是应用程序中一个至关重要的部分,它将影响程序的很多由spring管理的的其他内容,确保其正确将是保证程序正确的重要因素。

 

DOM并不仅仅是一个有值的对象

 

要理解DOM,首先得知道的就是它不仅仅是一个有值的的对象(通常被叫做数据传输对象)。数据传输对象通常是用来解决原来EJB对象传输之间的缺陷。用其来传输那些需要被远程调用的对象。

注:官方解释的数据传输对象并不等价于有值对象。具体请参考网站:http://martinfowler.com/bliki/ValueObject.html 

 

DOM是一个基于基本对象来表现问题领域的方式,旨在允许程序员按照普通对象的方式来构建。通常对象只包含一些状态属性,而领域对象模型不仅包含状态属性,也包含一些行为(当然也可以不包含)。另外一个根本的不同点是数据传输对象是用来传输数据的,而领域对象模型是用来抽象现实世界的对象。我们之后会讨论到,领域对象模型将没有一个固定的定义,需要你自己来选择它所代表的属性和方法的颗粒度大小。

 

通常,在一个程序中可能即存在值对象,也存在领域对象。在这种情况下,值对象常用在service层和别的层交互时,比如表现层和数据持久层。这些值对象将在合适的时候被转化为领域对象用来在表现层表现。然而,这种做法在这里我们并不推荐。一个原因是维护工作会因此而变得复杂,因为将值对象转换为领域对象的同时也意味着将其转换为其他值对象。另一个原因是在spring中,数据持久和web框架已经非常成熟,可以直接通过map方式将数据库层对象转换为领域对象,即用于表现,也用于数据层持久化。

 

为何要构建领域对象模型

 

创建一个DOM需要一些前期工作来抽象模型然后准备一些数据层代码来表现这个模型。然而,这些工作都是值得的,因为它将节省你正直实现时调试bug的时间。事实发现,一个好的DOM能使解决业务问题更容易。既然领域对象模型是根据问题领域构建而不是根据程序机制。一个好的DOM也将使得开发人员更好的将业务需求转化为程序代码。

 

构建领域对象模型

 

对于构建领域对象模型,目前有很多争论。一些坚持应当根据数据对象来构建。而另一些则声称:“让业务模型来驱动”。实际中,我们发现取2者的折中反而会得到比较好的效果,既能够得到比较好的表现形式也比较容易去构建。对于小型的项目,比如只有5-6张表的那种,通常只需根据数据库的表一一构建领域模型即可。虽然那些对象不是严格意义上的领域对象,但它们已经足够接近了。事实上,许多小型项目的领域对象模型也的确能够满足数据对象的要求。而对于大型项目,需要花费更多的精力在构建那些抽象现实世界的领域模型。

通常在构建时,需要关注如下三点:

 

-问题领域的结构是如何的

 

-领域对象将如何被使用

 

-底层数据持久该如何构建

 

值得注意的是,我们理想中的模型将尽可能少的影响数据持久的性能。

 

总体而言,也许你的逻辑代码中会返回多个类型对象,但一个DOM却是一个单独颗粒的。比如,假设我们有个进销存系统的订单。通常一个订单是由一个order对象(代表订单)与多个orderLine对象(代表订单的内容条目)。即一个order下面存在多个orderLine,然而这样的设计是没有必要的并且实现起来也比较麻烦。合适的做法是创造一个大颗粒度的领域对象,把这些东西都包含进去。也许在开发过程中你会发现,DOM中包含一些在数据库中压根就不存在的对象。比如,还是刚才的例子,进销存系统中存在一个叫做购物车的东西。(也许用Cart对象和CartItem对象来表示)。这些临时性的数据通常只在用户的session内存中做修改而不入数据库;根据领域模型的构建原则,我们不仅仅构建与数据库一一对应的值对象,而是根据业务领域而来。这是非常值得强调的。

 

所以,在构建一个领域对象模型时,我们得先深入的了解我们的业务/问题领域,确定在这些领域中需要些什么对象,然后根据现实世界的抽象合理的确定颗粒度大小,之后分辨出哪些部分是需要与数据持久层打交道的。时刻牢记在心,我们构建领域对象模型的初衷是设置一些列对象来帮助开发人员和业务人员更好的抽象出我们的问题需求,方便大家的理解和开发。而其他的问题我们会先放在一边,比如性能问题。。。不管怎样,在目前为止,确定问题都已经被设计成了合理的领域对象模型将会对整个程序提供巨大的好处。

 

数据库模型和领域对象模型

 

虽然数据库模型和领域对象模型很相像,甚至获取的结果都一样,但事实上是完全不同的。在构建一个数据库模型时,我们更关注的事那些数据的结构和访问时的性能问题。而构建领域对象时,性能问题就变得比较棘手,其实任何面向对象模型都有这个问题。事实上,如果能在设计数据库时就根据领域对象来设计是最好的。如果性能的确是一个问题的话,我的建议是先构建完领域对象,然后再出于性能的考虑对其进行修改。

 

领域对象模型关系

 

通常会碰到一个常见的误区,构建一些专门用来描述领域对象模型之间关系的领域对象。究其原因是为了表达数据库中多对多的关系。DOM中的关系描述应该被设计成一个更面向对象的方式,通常是一个领域对象中包含其他一个或一组领域对象。一个常见的错误就是根据数据库来设计DOM,关于详细我们将在今后“领域对象模型关系”这一章中详细讨论。

 

是否要封装行为?

 

就目前为止我们还没在任何领域对象中讨论过封装行为,虽然在一开始的定义中有提到。事实上,在构建时,这是一个可选的选项(但很重要)。因为我们更倾向于将业务行为抽离出去,放入service层,这不仅能减小对象的体积,也使得对象更易被重用,维护也更方便。看起来很完美,不是吗?但其实并不完全如此。在一些情况下,将行为设计在领域对象中会有很多好处。在spring的JPetStore代码示例中就有个很好的例子:用户可以获得一个购物车,并且购物车中有一些所购物品。当用户准备订购这些物品时,系统会生成一张订单,但是请注意,光生成一张空订单是不符合业务条件的,订单中还得有订单条目(即购物车中的物品)。将购物车中的物品转换为订单条目是一种特殊行为:1. 它只与领域对象有关。2. 它对其他模块无任何依赖。于是我们很自然的会想到在Order对象中创建一个initOrder()方法,也许有2个参数:账户和购物车。所有根据购物车生成订单的行为都可以被封装在这个方法内,鉴于这个方法只和对象打交道,也不存在何时调用的问题,将其放入领域对象中也不会造成复用问题。所以结论出来了,当业务行为仅与领域模型有关,应该将其放入DOM中。


Spring博客应用实例

 

下面让我们看看DOM模型在spring博客中的实例应用。首先请看下图的领域对象图。

 

 

12-1:博客发布相关的DOM设计

 

 

 

 

12-2: 博客用户和角色相关的DOM设计

 

虽然这个DOM不是很复杂,但是它的确展现了DOM的特点,别急,接下来的三章我们还将继续讨论。

 

 

 

Spring博客DOM的继承

 

Spring博客的核心就是发布博文。发布分2种类型:即发布博文,和发布评论。所以将发布操作中相同的部分抽象成一个接口如下:

 

//Listing 12-3. The BlogPosting Interface
package com.apress.prospring3.springblog.domain;

import java.util.Date;
import java.util.Set;
import com.apress.prospring3.springblog.domain.Attachment;

public interface BlogPosting {
	public String getBody();

	public void setBody(String body);

	public Date getPostDate();

	public void setPostDate(Date postDate);

	public String getSubject();

	public void setSubject(String subject);
}
 

 

然而,为了避免不必要的代码重复(必须分别对博文发布和评论发布进行实现),我们创建一个抽象类AbstractBlogPosting用来被继承。如下:

 

//Listing 12-4. The AbstractBlogPosting Class
package com.apress.prospring3.springblog.domain;

// Import statements omitted
public abstract class AbstractBlogPosting implements BlogPosting {
	protected Long id;
	protected String subject;
	protected String body;
	protected Date postDate;
	protected String createdBy;
	protected DateTime createdDate;
	protected String lastModifiedBy;
	protected DateTime lastModifiedDate;
	protected int version;

	public String getBody() {
		return body;
	}

	public void setBody(String body) {
		this.body = body;
	}

	public Date getPostDate() {
		return postDate;
	}

	public void setPostDate(Date postDate) {
		this.postDate = postDate;
	}

	public String getSubject() {
		return subject;
	}

	public void setSubject(String subject) {
		this.subject = subject;
	}
	// Other setter/getter methods omitted
}
 

 

通过继承这个基类,我们把重复的实现代码都给去掉了。接下来只用关注不同点的实现了:

 

 

//Listing 12-5. The Entry Class
package com.apress.prospring3.springblog.domain;

// Import statements omitted
public class Entry extends AbstractBlogPosting {
	private static final int MAX_BODY_LENGTH = 80;
	private static final String THREE_DOTS = "...";
	private String categoryId;
	private String subCategoryId;
	private Set<EntryAttachment> attachments = new HashSet<EntryAttachment>();
	private Set<Comment> comments = new HashSet<Comment>();

	public Entry() {
	}

	public String getShortBody() {
		if (body.length() <= MAX_BODY_LENGTH)
			return body;
		StringBuffer result = new StringBuffer(MAX_BODY_LENGTH + 3);
		result.append(body.substring(0, MAX_BODY_LENGTH));
		result.append(THREE_DOTS);
		return result.toString();
	}

	public String getCategoryId() {
		return this.categoryId;
	}

	public void setCategoryId(String categoryId) {
		this.categoryId = categoryId;
	}

	public String getSubCategoryId() {
		return this.subCategoryId;
	}

	public void setSubCategoryId(String subCategoryId) {
		this.subCategoryId = subCategoryId;
	}

	public Set<EntryAttachment> getAttachments() {
		return this.attachments;
	}

	public void setAttachments(Set<EntryAttachment> attachments) {
		this.attachments = attachments;
	}

	public Set<Comment> getComments() {
		return this.comments;
	}

	public void setComments(Set<Comment> comments) {
		this.comments = comments;
	}
}
 

 

如上所示,这是一个非常经典的spring程序结构。共同的功能被定义在接口而不是抽象类中,但提供一个抽象类来实现。这样设计的好处是我们可以利用抽象类的默认实现来避免每一个实现类不得不去实现一大堆重复的代码。当然,如果对于Entry类有一个特别的需求话,我们可以直接实现BlogPosting接口。这里要指出的是,要根据接口来定义功能而不是抽象类。还有一点要注意的是,我们没有根据数据库结构来设计程序,换句话说,我们没有定义一张BLOG_POSTING表来储存那些共享的数据(博文和评论)。这么做的原因是我们不想把结构复杂化;而且也体现了不根据数据库来设计领域模型的原则。

 

Spring博客领域对象模型中的行为

 

虽然spring博客的例子比较简单,我们还是在领域对象里封装了一些逻辑。因为博文可能会非常长,为了在显示博文列表方便,我们通常只取文章的一些片段。所以我们创建方法Entry.getShortBody()(注意这个方法没有定义在接口和抽象类里),如下所示:

 

 

//Listing 12-6. Behavior in the Entry Class
package com.apress.prospring3.springblog.domain;

// Import statements omitted
public class Entry extends AbstractBlogPosting {
	private static final int MAX_BODY_LENGTH = 80;
	private static final String THREE_DOTS = "...";

	public String getShortBody() {
		if (body.length() <= MAX_BODY_LENGTH)
			return body;
		StringBuffer result = new StringBuffer(MAX_BODY_LENGTH + 3);
		result.append(body.substring(0, MAX_BODY_LENGTH));
		result.append(THREE_DOTS);
		return result.toString();
	}
	// Codes omitted
}
 

 

你可以看到,我们只取前80个字符作为片段。这是一个非常简单的实现,不过用来说明足够了。

 

领域对象模型关系

 

在12-1中,我们定义了博文和评论之间的关系以及评论和附件的关系。作为spring博客需求的一部分,我们希望能够在发布博客和发布评论时都能上传附件。在数据库里,我们设计表ENTRY_ATTACHMENT_DETAIL 和 COMMENT_ATTACHMENT_DETAIL。一个常见的错误是创建一个领域对象模型来维护他们之间的关系,而不是使用标准的java特性来表现。当存在一个一对一关系是,你可以在一个DOM中引用另外一个DOM。而对一对多或者多对多则是一个DOM中引用另一个DOM的集合。请参考在12-7中Entry类的一些片段:

 

 

//Listing 12-7. Using Set for Domain Object Relationships
package com.apress.prospring3.springblog.domain;

// Import statements omitted
public class Entry extends AbstractBlogPosting {
	private Set<Attachment> attachments = new HashSet<Attachment>(0);

	public Set<Attachment> getAttachments() {
		return this.attachments;
	}

	public void setAttachments(Set<Attachment> attachments) {
		this.attachments = attachments;
	}
	// Codes omitted
}
 

 

不使用额外的对象而是使用一个set来表现关系。是不是很有效?

 

领域对象模型总结

 

在这一章里,我们观察了DOM在Spring博客离得应用,并且我们花费了一些篇幅讨论了领域对象的构建和实现。毫无疑问,这个话题要比我们这里讨论的广泛得多。事实上,如果有时间的话推荐各位可以读一些专门讨论此问题的书籍,比如《Domain-Driven Design: Tackling Complexity in the Heart of Software, Addison-Wesley Professional》。在这我们只是粗略地浏览了问题的表面。不管怎样,也许不使用DOM你的程序也能跑起来,但我们还是强烈建议您的使用,相信它会极大的减小项目的复杂度,后期的维护成本以及更少的BUG。


 

下一节:类型校验和格式转换 http://wsjjasper.iteye.com/blog/1574823

 

分享到:
评论

相关推荐

    基于Spring Boot和MyBatis的银行知识库管理系统的设计与实现

    它使用“约定优于配置”的原则,能够快速启动和运行Spring应用程序。Spring Boot为开发者提供了一系列开箱即用的配置选项,使得我们可以专注于业务逻辑的编写,而不是花费大量时间进行项目配置。 1. **自动配置**: ...

    Spring 3.x企业应用开发实战.pdf

    13. **Spring Boot**:Spring 3.x之后的版本,Spring Boot简化了Spring应用的初始化和配置,引入了起步依赖和自动配置的概念,使得快速开发成为可能。 这些是Spring 3.x版本中的主要知识点,掌握了这些,开发者就能...

    精通Spring4.x+企业应用开发实战 配套光盘(源码+资源)

    5. **MVC框架**:Spring MVC是Spring用于构建Web应用程序的模块,提供模型-视图-控制器架构,支持RESTful风格的URL映射,以及数据绑定和验证功能。 6. **Spring Boot**:虽然不是Spring4.x的直接组成部分,但Spring...

    Spring 3.x企业应用开发实战

    9. **Spring Web Flow**:对于有状态的工作流管理,Spring Web Flow提供了强大的模型,帮助设计和实现复杂的用户交互流程。 通过阅读《Spring 3.x企业应用开发实战》和《Spring MVC 3.0实战指南》,你将能深入理解...

    基于spring boot,spring cloud,axon framework的领域驱动设计的-Taroco.zip

    3. 领域驱动设计(DDD):DDD是一种软件开发方法,强调以业务领域为中心进行建模,通过识别核心业务概念(实体、值对象、聚合等)来创建清晰的模型。在Taroco项目中,DDD可以帮助团队理解和表述复杂的业务规则,提高...

    Spring+3.x企业应用开发实战.pdf

    - Spring MVC是Spring框架的一部分,用于构建Web应用程序。 - 它遵循Model-View-Controller设计模式,提供了模型与视图的分离,以及控制器的抽象。 - DispatcherServlet作为核心组件,负责请求分发。 - 视图解析...

    Spring 3.x 企业应用开发实战

    它简化了Spring应用的启动和配置,通过自动配置和起步依赖,快速构建独立运行的应用。 以上知识点是《Spring 3.x 企业应用开发实战》一书的主要内容,通过学习和实践,开发者可以掌握Spring框架的精髓,有效提升...

    基于spring的酒店管理系统的设计与实现.zip

    《基于Spring的酒店管理系统设计与实现》 在现代信息技术的支持下,酒店管理系统的开发已经成为提升服务质量、优化运营效率的关键。本系统以Spring框架为基础,结合Java编程语言,旨在构建一个高效、灵活且易于维护...

    精通Spring4.x企业应用开发实战

    《精通Spring 4.x企业应用开发实战》这本书深入浅出地介绍了Spring框架的核心技术与实际应用,对于Java开发者来说,Spring框架是构建企业级应用程序的重要工具。以下将详细阐述Spring框架的一些关键知识点。 1. **...

    金融风控数据平台-基于springboot+springcloud设计实现

    1. **SpringBoot**:SpringBoot是由Pivotal团队提供的全新框架,它简化了Spring应用程序的初始搭建以及开发过程。SpringBoot的核心特性是自动配置,它可以自动加载符合约定的Bean,并提供了快速开发Web、数据库连接...

    Spring3_jar.zip

    首先,SpringMVC是Spring框架的一个模块,专门用于构建Web应用程序。它提供了模型-视图-控制器(MVC)的架构模式,使得开发人员能够将业务逻辑、用户界面和数据访问分离开来,提高代码的可维护性和可测试性。...

    集成 Flex, Spring, Hibernate 构建应用程序.pdf

    在IT领域,集成Flex、Spring、Hibernate构建应用程序是一种高级的技术实践,主要应用于复杂的企业级应用开发。这份资料,虽然原始信息有限,但我们可以基于标题、描述以及部分提及的信息,深入探讨这三个技术框架...

    基于J2EE的Struts、Spring、Hibernate框架技术构建Web应用.pdf

    1. **模型(Model)**:负责封装应用程序的数据和业务逻辑,通常与数据库或其他数据源交互。 2. **视图(View)**:呈现数据给用户,负责用户界面的展示。 3. **控制器(Controller)**:处理用户的输入,调用模型...

    springboot+vue基于Javaweb的网上商城购物系统的设计与实现论文.doc

    本论文旨在设计和实现一个基于Spring Boot+Vue.js的网上商城购物系统。该系统采用B/S架构模式,使用Spring Boot框架和Vue.js框架构建前端和后端,并使用MySQL数据库进行数据存储和管理。该系统具有高性能、可扩展性...

    基于OSGi和Spring开发Web应用

    Spring-DM,即Spring Dynamic Modules,是Spring官方在2008年推出的项目,旨在将Spring框架与OSGi模块化平台相结合,为Java开发者提供了一套全面的工具和API,以支持在OSGi环境中开发、部署和管理Spring应用程序。...

    spring-framework-3.2.1--3.x

    Spring框架,作为Java领域最广泛应用的轻量级框架之一,自2003年发布以来,一直以其强大的功能和灵活的设计深受开发者喜爱。本文将围绕"spring-framework-3.2.1.RELEASE"这一版本,详细探讨Spring框架3.x系列的核心...

    spring-framework-5.1.x.zip

    - **Spring Boot**:简化Spring应用的初始化和配置,快速构建可运行的应用程序。 - **Spring Data**:简化数据访问,支持JPA、MongoDB等多种数据存储技术。 - **Spring Security**:提供安全控制,包括身份验证、...

    这个Repo包含基于spring boot开发应用程序的基本功能

    标题提到的是“这个Repo包含基于spring boot开发应用程序的基本功能”,这表明这是一个使用Spring Boot框架创建的应用程序仓库,其中包含了构建Web应用所必需的核心组件和功能。 描述进一步细化了这个仓库的内容,...

    基于Spring框架的在线商城系统开发

    首先,Spring框架是Java企业级应用开发的首选框架,它以其强大的依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Programming,AOP)能力,简化了复杂的应用程序构建。在在线商城系统的开发中...

    基于Spring的Java平台程序架构研究.pdf

    DDD的核心在于领域模型的构建,它指导整个软件设计和实现的过程。 敏捷开发是现代软件开发的另一种重要方法论,强调快速迭代和响应变化。敏捷开发中常用的实践包括测试驱动开发(TDD),它要求开发者先编写测试用例...

Global site tag (gtag.js) - Google Analytics