??xml version="1.0" encoding="utf-8" standalone="yes"?> 先跑下题聊聊java. q两q越来越多搞Java的h跑去学动态语aQruby, groovy, scala之类的?原因在于q些动态语a“表达能力”更好。是的,q些语言更灵z,更精点{不隄解,后出的编E语a更們于合理,毕竟它有前h的宝늻验可以借鉴。java的一些设计的o(h)病,比如checked exception, Ҏ(gu)型支持的不完备。但׃“表辑֊”来_(d)我觉得java的表辑֊不强不弱正正好好。Java的定位本来就是大规模企业U应用,q度灉|的编E语a不易l护?/strong>新型的动态语a們于过度灵z,虽然支持快速开发,但是我相信在多hQ甚臛_十h合作开发的目中,如果对语a非常灉|的特性不加以限制Q随着E序的规模一点点增大Q维护的隑ֺ?x)陡然加大。而一旦通过convention限制使用语言的一些特性,限制来限制去不就又成java?jin)么Q好吧,好吧Q我知道q会(x)是个有争议的话题Q我是不相信用ruby能做个ERP出来Q五q之后也不会(x)有。这不是q篇文字要讨论的重点Q我惌的是Qjava在表达能力方面已l让一些h不满意了(jin)Q而XML呢?XML的表达能力比java差了(jin)N多个数量U!q年_(d)骑三轮R回收?sh)脑的都知道E序设计要有Ҏ(gu),换句话说Q能适应变化。于是一个简单的梦想是Q来?jin)新需求,׃用改code, Ҏ(gu)xml配置p满好?jin)。是的,我也q么希望Q尤其在我睡着的时候。以Javaq样的面向对?+ 一些动态特性(reflectionQdynamic proxy甚至aspectJQ的语言都很隑ց到如此灵zȝ应对变化Qxml怎么?x)更?gu)做到呢?很多人喜ƢXML的简单直观。比如我们可以在SWING上面装一层基于XML的界面引擎,于是GUI可以简单地写成q种风格Q?/p> 它有一个好处就是易于扩展,比如现在又想加一个checked box,只要在里面插入一?lt;checkbox id=…?>p?jin)。我承认它的易于扩展。但是如果你单封装一下java APIQ不也是一行code的事儿么Q无非就是panel.addChild(new CheckedBox(attr_1, attr_2))之类的这么一句code。难道这׃单,不直观了(jin)Q程序一涉及(qing)到xml,涉?qing)到IO, 解析XMLQ这都是额外的工作量Q况且xml中的“对象”(我很难承认complex type是对象Q跟Java里的对象Ҏ(gu)不是一码事Q从xml里解析出来的表示数据cd的type最后还是要生成对应特定的java对象Q这都是ȝ(ch)事儿?多写code的坏处远不止写的时候费事儿Q所写的每行code最后都是要l护的,qcode很strong, 我还嫌code太长Q滚鼠标滚轮儿篏手指_(d)多个文gN儿篏q?strong>MQ但凡xml改一两行能解决的问题Q不用xml,qjava也是一两行的工作量Q只要稍微花点心(j)思封装好APIp?/strong>不用XML可以省不开发和l护的工作。理x况下Q改改xmlp应对新需求。但现实和理xL有差距的Q一旦xml的schema不以应对变化,我们得改xml, 改schemaQ改解析的codeQ改业务逻辑的codeQ这个没法避免)(j)。这不是没事儿找病么Q?strong>xml是extendable markup language. 它真能比javaqextendableQ?/strong>扯EQ?/p>
当然XML有它合理的应用场景,我能惛_的是四个斚wQ一定有漏掉的情况,Ƣ迎朋友们补充?/p>
一Q?存储?wi)状数据Qv码三层以上。三层或三层以下qjava合成也挺方便的)(j)Q二Q?配置信息Q三、描q标准协议(比如wsdlQ; 四,wrap数据以便于传输等(比如soap) 在应用品中Q自p计xml来辅助应用系l的场景不应该很多?/strong> 另有一U用xml的误区是Q把xml暴露l用戯他自己“扩展”。我觉得Q?凡是暴露l客L(fng)东西都是UIQ^时Swing界面Qweb界面是GUIQ暴露给客户改的xml是UI. 也许有的Q一旦弄GUIQ就有一堆麻?ch)事儿,比如i18n啥的Q让用户自己配置配置xmlp改动面不是挺好么,q不用考虑i18n?jin)。问题是Q用xml不是“不用”考虑国际化,而是Ҏ(gu)没法考虑国际化?/p>
<wallet><money>100</money></wallet> q个国看懂Q对中国人就得用<钱包><?gt;100</?gt;</钱包> q么做很明显用户体验很差。另外,q也一定程度上暴露?jin)你的实现方式,L(fng)客户知道Q哦Q原来你是用xml存数据的Qschema是q个样子?/p>
Ajax刚铺天盖地的时候,都用xml传数据,后来来多人用Json. Spring和Hibernate{一堆框架在几年前都用巨大的XML来配|,现在来多{向annotation(当然Q这个有一定争?。XML热度减退Q大家趋于理性是好事。XML当然很有用,但是E序员们该能x楚什么是"使用", 什么是"滥用". Domain Driven DesignZ一些简单,重要Q务实的理念。下面根据自q理解ȝDDD的基本理论?/p> 1. 深刻理解Domain知识 DDDZdomainq行设计。前提是深刻理解领域知识Q需要持l学?fn)。另一个相关的实践是,“统一的语a”(ubiquitous languageQ。领域专家所用的术语是准的Q那么程序员讨论domain相关的问题时Q也应该使用q种术语。ƈ且在写code的时候,也用这U术语。这样做的好处是Q可以自然地把domain的东西map到code里,保持code反应domain?/p> 2. 分层 E序需要分层,q是个简单的道理, 但需要真正Ş成意识?/p> 分别说说Domain Driven Design涉及(qing)的四个layer. 2.1 Infrastructure?/p> 主要从技术上对上面的各层提供支持。比如传送消息,持久化。UI层也可以分出更基的component作ؓ(f)infrastructure?/p> 2.2 UI?/p> UI层的作用是显CZ息以?qing)解释用L(fng)输入命o(h)。重要的是UI层没有domain knowledge. 2.3 application?/p> application层的作用是描qC品的外部功能Q它可以协调UI和Domain层的互动?strong>application层应该很?/strong>。它没有domain knowledge. 2.4 Domain?/p> Domain层是最重要的层。封装所有的业务逻辑?/p> DDD也从另一个角度分解Y件。一个Y件的l成部分Qbuilding blockQ包括association, entity, value object, service, module。其中比较强调的是entity, value object和service. service比较Ҏ(gu)理解Q通常它是无状态的。它可以存在于application层,domain层或者infrastructure层。比较不Ҏ(gu)区分的是entity和value object. entity的是它的idQ而不是属性。强调它生命周期的l性和同一?continuity and identity), ?strong>value object是用来“描q”entity?/strong>。value object在很多时候适合是immutable的。这里需要注意的是,如果你会(x)使用hibernate,你会(x)知道hibernate里的value object通常不能reference多个entityQ换句话_(d)能reference多个entity的,通常是entity. 而在DDD的理ZQ?路线"是一个value object。而它可以referenceq接"路线"的city(entity)和高速公?entity)?/p> 3. Domain Object生命周期 DDD的一个重要理论是关于aggregate. domain object之间的关pd果非常复杂,在domain knowledge涉及(qing)比较复杂的rule的时候,Ҏ(gu)造成不一致的情况。解军_法是Q把一些domain object放一个组里,让某个domain object作ؓ(f)根(aggregate rootQ? 所有对q些domain object的访问都要通过q个aggregate, 那么l持一致性的工作Q就限制在这个aggregate root里了(jin)?/p> 另一个相关理论是factory和repository. factory是用来“创建”对象的Q是一个从无到有的q程。而repository是用来retrieve对象的,也就是说Q对象客观存在,只是没有攑֜内存中,repository是把数据从某个地方Q通常是数据库Q拿出来Qconstruct成对象的。一个常见的设计是,让repository暴露retrieve和create的apiQ但是create的具体工作代理给factory来做Q也是_(d)factory通常被聚合在repository中?/p> 1: <panel id=?span style="color: #ff0000">xyz?width=?span style="color: #ff0000">360?heigh=?span style="color: #ff0000">720”…?span style="color: #0000ff">>
2:
3: <label id=?span style="color: #ff0000">label_1?attr_1=?span style="color: #ff0000">xxx?attr_2=?span style="color: #ff0000">yyy?span style="color: #0000ff">/>
4:
5: <textarea id=?span style="color: #ff0000">textarea_1?attr_1=?span style="color: #ff0000">xxx?attr_2=?span style="color: #ff0000">yyy?span style="color: #0000ff">/>
6:
7: </panel>
]]>Layered Architecture
]]>
Eric Evans has formulated what domain-driven design (DDD) is. Martin Fowler is a great supporter and advocate of DDD. These are remarkable names and it is almost certain they are supporting something worth. And I’m not here to argue with that. Maybe I’m trying to justify the way I’ve been writing software, or maybe I’m trying just to clear things and be constructive. Let’s see.
What is at the core of domain-driven design ?the abstract notions of the domain, the domain model, the ubiquitous language. I’ll not go into details with that ?for those interested there is wikipedia (with lots of references to read in the footer). This is all very good in theory, and the domain-driven way of building software should appeal to everyone ?after all that software is being built for the benefit of that domain, not for the benefit of architects, developers or QAs.
But now comes the practical part ?how to implement DDD? I’ll answer that question in its contemporary context ?that is, using frameworks like spring and hibernate. And I’ll justify their usage. Spring is a non-invasive dependency-injection framework. DI is also strongly supported by Fowler, and is considered a good way to implement DDD. Hibernate is one way to use objects when working with a relational database. Another way is to use JDBC and construct the objects manually, but that is tedious. So hibernate doesn’t influence the architecture part ?it is an utility (very powerful one, of course).
Throughout this post I’ll use hibernate and spring as “given? although they may be changed with any DI framework and any ORM or other persistence mechanism that relies on objects.
The accepted way to implement DDD with spring and hibernate is:
@Configurable
to make the domain objects eligible for dependency injection (they are not instantiated by spring, so they need this special approach) Implementation and description of this approach is shown in this extensive article. Another example (without spring) is http://dddsample.sourceforge.net/. I’ll discuss both later.
The alternative to this approach is the anemic domain model. It is considered an anti-pattern, but at the same time is very common and often used. The features of the anemic data model are simple ?the domain objects have no business logic inside them ?they are only data holders. Instead, the business logic is placed in services.
The reason this is considered an anti-pattern is because, first, this seems like a procedural approach. It is breaking the encapsulation, because the internal state of the object is, well, not internal at all. Second, as the domain object is the center of the design, it is harder to change it if its operations don’t belong to it, but to a number of stateless service classes instead. And domain-driven design is aimed at medium-to-large scale applications, which change a lot and need an easy way to make changes fast, without breaking other functionality. Thus it is important to have all the functionality of the object within the object itself. This also makes sure that there is less duplication of code.
So, instead of having a service calculate the price: ProductServiceImpl.calculatePrice(complexProduct)
we should simply have ComplexProduct.calculatePrice()
. And so whenever the domain experts say that the price calculation mechanism changes, the place to change it is exactly one and is the most straightforward one.
When simple operations are regarded, this looks easy. However, when one domain objects needs another domain object to do its job, it becomes more complicated. With the anemic data model this is achieved by simply injecting another Service into the current one and calling its methods. With the proposed DDD it is achieved by passing domain objects as arguments.
In my view, the domain object, which is also the hibernate entity, has its dependencies already set. But not by spring, because spring can’t know which exactly domain object to inject. They are “injected?by hibernate, because it knows exactly which (identified by primary key) domain object should be placed in another domain object. So, a little odd example ?if a Product
has rotten and has to dispense smell in the warehouse, it has to call, for example, warehouse.increaseSmellLevel(getSmellCoeficient())
. And it has its precise Warehouse
without any interference from spring.
Now, here comes another point where I disagree. Most sources (including the two linked above) state that repositories / DAOs should be injected in the domain objects. No, they shouldn’t. Simply calling “save?or “update?doesn’t require knowledge of the internal state of the object. Hibernate knows everything anyway. So we are just passing the whole object to the repository.
Let’s split this in two ?business logic and infrastructure logic. The domain object should not know anything of the infrastructure. That might mean that it should not know it is being saved somewhere. Does a product care of how it is stored ? No ?it’s the storage mechanism that’s “interested? And here are the practical disadvantages:
I’ll open a bracket here about a proposed solution in one of the above articles for making duplication of code, and boilerplate code easier to handle. Code generation is suggested. And I think code-generation is a sin. It moves the inability to get rid of duplicated or very similar code and abstract it, to tools. The most striking example is generating ProductDAO, CategoryDAO, WarehouseDAO, etc, etc. Generated code is hard to manage, cannot be extended and relies heavily on external metadata, which is definitely not an object-oriented approach.
Speaking of the repository, in the proposed examples each domain object should have a repository, which in turn will call the persistence mechanism. What do we get then:
User presses “save?in the UI > UI calls save on the service (in order to have transaction support) > Service calls save on the domain object > domain object calls save on the repository > the repository calls save on the persistence mechanism > the persistence mechanism saves the object.
Is it just me, or calling the domain object is redundant here. It is a pass-through method that adds nothing. And since a lot of functionality is related to CRUD (yes, even in big business applications), this looks quite bad to me.
And finally, I find the @Configurable
approach a hack. It does some magic in the background, which isn’t anything of the common language features (and is not a design pattern), and in order to understand how it happens you need a great deal of experience.
So, to summarize the big mess above
=================================================
一个有见地的回复:(x)
For one I think that technical issues should be separated from the functional domain. That means that domain objects should not be polluted by (technical) annotations or persistence methods (seperation of concerns). Dependency Injection (technical) should be outside the core domain. Persistence methods should be in a repository gateway, which (at least the interface, functional) is part of the domain. You know that you have a repository (functiuonal, within the domain), but not the way it’s implemented (technical). That’s outside the domain.
Just as the services (functionality) which calls them are part of the domain. These services are called from the user interface or other components outside this particular domain.
Transactional boundaries and session management is not domain functionality and should be outside the domain in a separate (thin) layer, which calls the services or domain objects within the domain.
DTO’s are useful for communicating outside the domain. In simple applications (with session management in the UI) the DO’s can be used, but I would not recommend it. I prefer the use of DTO’s which are assembled within the transaction boundary, and can be used outside the transaction (and session) boundary as to avoid potential LazyInitializationExceptions, which I am, unfortunately, very familiar with.
Dependency injection should not be in Domain Objects. If you want to use something like the AgeCalculator mentioned above, just call the user.getAge() method wich Alex suggested.
This method contains something like
class User {
public int getAge() {
AgeFactory.getInstance().getAgeCalculator().calculateAge(this);
}
}
The AgeFactory is a singleton, which has the correct calculator injected. You can use Spring for that.
class AgeFactory {
AgeFactory instance = new AgeFactory();
AgeCalculator ageCalculator = null;
public AgeFactory getInstance() {
return instance;
}
public void setAgeCalculator(AgeCalculator ageCalculator) {
this.ageCalculator = ageCalculator;
}
public AgeCalculator getAgeCalculator() {
return this.ageCalculator;
}
}
The implementation of the repository gateway objects can be done in the same way.
Usage of the repository (in a service) can be something like this
class ServiceImpl implements Service {
public void save(User user) {
RepositoryFactory.getInstance().getUserRepository().save(user);
}
}
These factories must be within the domain, because the domein objects must use them. But the dependency injection of these factories can be done outside the core domain (if you don’t use annotations). This way the core functional domain is not polluted with the technical issues of dependency injection.
Finally about annotations, I hate them, because it’s a magic black box. I rather use xml configuration files because they are probably easier to understand. Furthermore without annotations you can have the configuration files outside the core domain, and can use mock objects which are created and set on the factory without dependency injection within testcases. You can even choose to use dependency injection within part of the unit tests and without DI in other unit tests.