入门
正如我在开始时所说,你可能在DDD之前遇到过很多想法。事实上,我所说过的每一个Smalltalker(我不是一个,我不敢说)似乎很高兴能够在EJB2等人的荒野岁月之后回归域驱动的方法。
另一方面,如果这些东西是新的怎么办?有这么多不同的方式来绊倒,有没有办法可靠地开始使用DDD?
如果你环顾一下Java领域(对.NET来说并不那么糟糕),实际上有数百个用于构建Web应用程序的框架(JSP,Struts,JSF,Spring MVC,Seam,Wicket,Tapestry等)。从持久性角度(JDO,JPA,Hibernate,iBatis,TopLink,JCloud等)或其他问题(RestEasy,Camel,ServiceMix,Mule等),有很多针对基础架构层的框架。但是很少有框架或工具来帮助DDD所说的最重要的层,即域层。
自2002年以来,我一直参与(现在是一个提交者)一个名为Naked Objects的项目,Java上的开源[12]和.NET上的商业[13]。虽然Naked Objects没有明确地开始考虑领域驱动的设计 - 事实上它早于Evans的书 - 它与DDD的原理非常相似。它还可以轻松克服前面提到的障碍。
您可以将Naked Objects视为与Hibernate等ORM类似。 ORM构建域对象的元模型并使用它来自动将域对象持久保存到RDBMS,而Naked Objects构建元模型并使用它在面向对象的用户界面中自动呈现这些域对象。
开箱即用的Naked Objects支持两个用户界面,一个富客户端查看器(参见图9)和一个HTML查看器(参见图10)。这些都是功能完备的应用程序,需要开发人员只编写要运行的域层(实体,值,存储库,工厂,服务)。
Figure 9: Naked Objects Drag-n-Drop Viewer
我们来看看Claim类的(Java)代码(如屏幕截图所示)。首先,这些类基本上是pojos,尽管我们通常从便捷类AbstractDomainObject继承,只是为了分解注入通用存储库并提供一些帮助方法:
public class Claim extends AbstractDomainObject { ... } Next, we have some value properties: // {{ Description private String description; @MemberOrder(sequence = "1") public String getDescription() { return description; } public void setDescription(String d) { description = d; } // }} // {{ Date private Date date; @MemberOrder(sequence="2") public Date getDate() { return date; } public void setDate(Date d) { date = d; } // }} // {{ Status private String status; @Disabled @MemberOrder(sequence = "3") public String getStatus() { return status; } public void setStatus(String s) { status = s; } // }}
这些是简单的getter / setter,返回类型为String,日期,整数等(尽管Naked Objects也支持自定义值类型)。接下来,我们有一些参考属性:
// {{ Claimant private Claimant claimant; @Disabled @MemberOrder(sequence = "4") public Claimant getClaimant() { return claimant; } public void setClaimant(Claimant c) { claimant = c; } // }} // {{ Approver private Approver approver; @Disabled @MemberOrder(sequence = "5") public Approver getApprover() { return approver; } public void setApprover(Approver a) { approver = a; } // }}
这里我们的Claim实体引用其他实体。实际上,Claimant和Approver是接口,因此这允许我们将域模型分解为模块,如前所述。
实体也可以拥有实体集合。在我们的案例中,Claim有一个ClaimItems的集合:
// {{ Items private List<ClaimItem> items = new ArrayList<ClaimItem>(); @MemberOrder(sequence = "6") public List<ClaimItem> getItems() { return items; } public void addToItems(ClaimItem item) { items.add(item); } // }}
我们还有(Naked Objects调用的)动作,即submit和addItem:这些都是不代表属性和集合的公共方法:
// {{ action: addItem public void addItem( @Named("Days since") int days, @Named("Amount") double amount, @Named("Description") String description) { ClaimItem claimItem = newTransientInstance(ClaimItem.class); Date date = new Date(); date = date.add(0,0, days); claimItem.setDateIncurred(date); claimItem.setDescription(description); claimItem.setAmount(new Money(amount, "USD")); persist(claimItem); addToItems(claimItem); } public String disableAddItem() { return "Submitted".equals(getStatus()) ? "Already submitted" : null; } // }} // {{ action: Submit public void submit(Approver approver) { setStatus("Submitted"); setApprover(approver); } public String disableSubmit() { return getStatus().equals("New")? null : "Claim has already been submitted"; } public Object[] defaultSubmit() { return new Object[] { getClaimant().getApprover() }; } // }}
这些操作会在Naked Objects查看器中自动呈现为菜单项或链接。而这些行动的存在意味着Naked Objects应用程序不仅仅是CRUD风格的应用程序。
最后,有一些支持方法可以显示标签(或标题)并挂钩持久性生命周期:
// {{ Title public String title() { return getStatus() + " - " + getDate(); } // }} // {{ Lifecycle public void created() { status = "New"; date = new Date(); } // }}
之前我将Naked Objects域对象描述为pojos,但您会注意到我们使用注释(例如@Disabled)以及命令式帮助器方法(例如disableSubmit())来强制执行业务约束。 Naked Objects查看器通过查询启动时构建的元模型来尊重这些语义。如果您不喜欢这些编程约定,则可以更改它们。
典型的Naked Objects应用程序由一组域类组成,例如上面的Claim类,以及存储库,工厂和域/基础结构服务的接口和实现。特别是,没有表示层或应用层代码。那么Naked Objects如何帮助解决我们已经确定的一些障碍?
- 实施分层架构:因为我们编写的唯一代码是域对象,域逻辑无法渗透到其他层。实际上,Naked Objects最初的动机之一就是帮助开发行为完整的对象
- 表示层模糊了域层:因为表示层是域对象的直接反映,整个团队可以迅速加深对域模型的理解。默认情况下,Naked Objects直接从代码中获取类名和方法名,因此强烈要求在无处不在的语言中获得命名权。通过这种方式,Naked Objects也支持DDD的模型驱动设计原理
- 存储库模式的实现:您可以在屏幕截图中看到的图标/链接实际上是存储库:EmployeeRepository和ClaimRepository。 Naked Objects支持可插入对象存储,通常在原型设计中,我们使用针对内存中对象存储的实现。当我们转向生产时,我们会编写一个实现数据库的实现。
- 服务依赖项的实现:Naked Objects会自动将服务依赖项注入每个域对象。这是在从对象库中检索对象时,或者首次创建对象时完成的(请参阅上面的newTransientInstance())。事实上,这些辅助方法所做的就是委托Naked Objects提供的名为DomainObjectContainer的通用存储库/工厂。
- 不合适的模块化:我们可以通过正常方式使用Java包(或.NET命名空间)模块化为模块,并使用Structure101 [14]和NDepend [15]等可视化工具来确保我们的代码库中没有循环依赖。我们可以通过注释@Hidden来模块化为聚合,任何聚合对象代表我们可见聚合根的内部工作;这些将不会出现在Naked Objects查看器中。我们可以编写域和基础设施服务,以便根据需要桥接到其他BC。
- Naked Objects提供了许多其他功能:它具有可扩展的体系结构 - 特别是 - 允许实现其他查看器和对象存储。正在开发的下一代观众(例如Scimpi [16])提供更复杂的定制功能。此外,它还提供多种部署选项:例如,您可以使用Naked Objects进行原型设计,然后在进行生产时开发自己的定制表示层。它还与FitNesse [17]等工具集成,可以自动为域对象提供RESTful接口[18]。
下一步
领域驱动的设计汇集了一组用于开发复杂企业应用程序的最佳实践模式。一些开发人员多年来一直在应用这些模式,对于这些人来说,DDD可能只是对他们现有实践的肯定。但对于其他人来说,应用这些模式可能是一个真正的挑战。
Naked Objects为Java和.NET提供了一个框架,通过处理其他层,团队可以专注于重要的部分,即域模型。通过直接在UI中公开域对象,Naked Objects允许团队非常自然地构建一个明确无处不在的语言。随着域层的建立,团队可以根据需要开发更加量身定制的表示层。
那么,下一步呢?
嗯,DDD本身的圣经是埃里克埃文斯的原着,“领域驱动设计”[1],建议阅读所有人。雅虎新闻组DDD [19]也是一个非常好的资源。如果你有兴趣了解Naked Objects的更多信息,你可以搜索我的书“使用Naked Objects的域驱动设计”[20],或者我的博客[21](NO for Java)或Naked Objects网站[13 ](对于.NET而言)。快乐DDD'ing!
References
- [1] Domain Driven Design Community http://domaindrivendesign.org/
- [2] Spring BeanDoc http://spring-beandoc.sourceforge.net/
- [3] Anaemic Domain Model, Martin Fowler http://martinfowler.com/bliki/AnemicDomainModel.html
- [4] FitNesse http://fitnesse.org
- [5] Hexagonal Architecture, Alistair Cockburn http://alistair.cockburn.us/Hexagonal+architecture
- [6] Big Ball of Mud,> Brian Foote & Joseph Yoder http://www.laputan.org/mud/
- [7] Dependency Inversion Principle, Robert Martin http://www.objectmentor.com/resources/articles/dip.pdf
- [8] LINQ http://msdn.microsoft.com/en-us/netframework/aa904594.aspx
- [9] Hades http://hades.synyx.org/
- [10] Apache Wicket Web Framework http://wicket.apache.org
- [11] Magical Number Seven, ±2 http://en.wikipedia.org/wiki/The_Magical Number_Seven,_Plus_or_Minus_Two
- [12] Naked Objects for Java http://nakedobjects.org
- [13] Naked Objects for .NET http://nakedobjects.net
- [14] Structure101 (for Java) http://www.headwaysoftware.com/products/structure101
- [15] NDepend (for .NET) http://www.ndepend.com/
- [16] Scimpi http://scimpi.org
- [17] Tested Objects (FitNesse for Naked Objects) http://testedobjects.sourceforge.net
- [18] Restful Objects (REST for Naked Objects) http://restfulobjects.sourceforge.net
- [19] Yahoo DDD Newsgroup http://tech.groups.yahoo.com/group/domaindrivendesign/
- [20] Domain Driven Design using Naked Objects, Dan Haywood http://pragprog.com/titles/dhnako
- [21] Dan Haywood’s Blog http://danhaywood.com
Related Methods & Tools articles
- Domain-Specific Modeling for Full Code Generation
- Mass Customizing Solutions with Software Factories
- Introduction to the Emerging Practice of Software Product Line Development
More Domain Driven Design Knowledge
- Strategic Design by Eric Evans
- Is Domain-Driven Design more than Entities and Repositories?
- Use of Domain Driven Design in Enterprise Application Development