楼主: Sky-Tiger

Domain-driven design with Java EE 6

[复制链接]
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
11#
 楼主| 发表于 2009-5-24 23:14 | 只看该作者
PDOs are nontrivial by definition; they are designed to reflect concepts from the target domain. Unit tests of PDOs are essential for the overall quality of the system. In addition, you are forced to use the PDOs via the public API, which forces you to think outside the box. Unit testing turns out to be a good usability check for a PDO's interface "fluency." Any inconveniences are directly exposed to you during the tests. And good PDOs are easy to test -- a form of quality assurance.

With minimal effort, JPA persistence can be used for testing. You need to create only the EntityManager, using the Persistence class and the EntityManagerFactory, as shown in Listing 8.
Listing 8. PDO tests with local EntityManager

public class LoadTestWithPersistence {

    private EntityManager em;
    private EntityTransaction et;

    @Before
    public void setUp() throws Exception {
        this.em = Persistence.createEntityManagerFactory("test").
createEntityManager();
        this.et = this.em.getTransaction();
    }

    @Test
    public void mixedLoad(){
        Load load = new Load.Builder().
                withStandardItem(5).
                withLightweightItem(1).
                withBulkyItem(1).
                build();
        int actual = load.getShippingCosts();
        int expected = (5*5) + (5-1) + (1*5+5);
        assertEquals(expected,actual);
        this.et.begin();
        this.em.persist(load);
        this.et.commit();
        this.em.clear();
        Long id = load.getId();
        this.et.begin();
        Load found = this.em.find(Load.class, id);
        this.et.commit();
        assertEquals(expected,found.getShippingCosts());
        assertEquals(load.getNumberOfOrderItems(),found.getNumberOfOrderItems());
        //...
    }
//...
}

Local unit tests could even take on characteristics of integration tests. Rudimentary tests with an EntityManager are at least useful for finding object/relational-mapping problems. For more-complex applications, unit tests without persistence become increasingly bloated, and creating the test fixture can get overly complex. In such cases a local test database can make it much easier to populate the test data once and reuse it for several test runs.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
12#
 楼主| 发表于 2009-5-24 23:14 | 只看该作者
Facades or gateways?

PDOs are passive artifacts. It's impossible to access them directly without an execution context. The next problem, then, is the stateless nature of most Java EE applications. After a method invocation of a transaction boundary (such as a service or service facade) all JPA entities (PDOs) become detached. The client loses its state. This forces you to transport the whole context back and forth between the client and the server, which leads to the following problems:

    * Heavily interconnected PDOs become hard to merge. Even for fine-grained changes, the whole graph of objects must be transported back to the server. It is not always possible to merge the graph automatically and even consistently.
    * Because the transport of the context becomes too expensive and merging the functionality on the server side becomes increasingly complex, the transaction boundary (such as a service facade) is extended with additional methods to manage each particular excerpt of the whole graph. This leads in general to an explosion of hard-to-maintain methods (a.k.a. God facades), a great deal of redundancy, and procedural code.
    * Dependent PDOs cannot be lazily loaded in detached state in the presentation tier. The server must know in advance which subgraph of interconnected objects needs to be transported to the client. Dedicated methods (for example, getCustomerWithAddress()) are introduced to overcome the problem.
    * Every change of the client's state must be merged with the server.
    * For some business logic, such as additional queries, a PDO may require access to the EntityManager. The EntityManager, however, is only available on the server and not the client.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
13#
 楼主| 发表于 2009-5-24 23:15 | 只看该作者
The solution to these problems is to create a perfect "anti service facade" -- a gateway. Instead of hiding the PDOs behind a facade, just try to expose them to the adjacent layer as conveniently for the UI as possible. Allow the user to modify the PDOs directly without any indirection.

This solution contradicts the common J2EE principle that encapsulation is the only way to achieve maintainability. But that principle is only true for perfect abstractions and encapsulations, which are hard to find in real-world projects. The inverse strategy works even better for some use cases. Just get rid of any layer that is probably leaky anyway and expose the business logic directly to the presentation tier. Every change in the persistence layer's structure becomes immediately visible in the UI, which makes the implementation of feature requests easy.

Your presentation is coupled to the particular implementation of the business logic, but the concrete implementation is already encapsulated. JPA abstracts from the particular provider, and EJBs are nothing other than annotated POJOs. So the technology is pretty well hidden already. The concrete state and implementation of domain-specific logic are well encapsulated too -- that's the PDO's main responsibility.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
14#
 楼主| 发表于 2009-5-24 23:16 | 只看该作者
Transaction handling

One challenge remains for the gateway: the implementation of consistent and concise transaction handling. In a SOA, transaction handling is straightforward: a transaction is started and committed before and after every invocation of service facade's (the boundary's) method. This strategy works well, because the whole implementation of the business logic resides behind the service facade and is executed atomically.

The gateway, however, exposes the PDOs directly to the presentation tier, so it loses the control over the PDOs, and of transactions in particular. Furthermore, allowing the user to access and modify the PDOs directly makes centralized transaction management with a stateless service facade impossible. You can modify the detached PDOs directly, and a gateway lacks fine-grained merge() methods.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
15#
 楼主| 发表于 2009-5-24 23:16 | 只看该作者
The solution is to introduce state on the server side. A stateful gateway can keep the PDOs attached with an EntityManager declared as PersistenceContext.EXTENDED, as shown in Listing 9. The EntityManager needs a transaction only as a trigger to flush the changes to the database, which can be started by a method that overrides the class default.
Listing 9. A stateful gateway implementation

@Stateful
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
@Local(OrderGateway.class)
public class OrderGatewayBean implements OrderGateway{

    @PersistenceContext(type=PersistenceContextType.EXTENDED)
    EntityManager em;

    private Load current;

    public Load find(long id){
       this.current = this.em.find(Load.class, id);
       return this.current;
    }

    public Load getCurrent() {
        return current;
    }

    public void create(Load load){
        this.em.persist(load);
        this.current = load;
    }

    public void remove(long id){
        Load ref = this.em.getReference(Load.class, id);
        this.em.remove(ref);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void save(){
        //nothing to do
    }

    public void update(){
        this.em.refresh(this.current);
    }
   
    @Remove
    public void closeGate(){
        
    }
}

The implementation of the gateway is simple. It is a stateful session bean with an EXTENDED EntityManager. All incoming transactions are suspended with TransactionAttributeType.NOT_SUPPORTED on the class level. This is only possible with an EXTENDED EntityManager, which can only be injected into a stateful session bean. An EntityManager with a default configuration injected to a stateless session bean would throw javax.persistence.TransactionRequiredException in most of its methods invoked without an active transaction.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
16#
 楼主| 发表于 2009-5-24 23:16 | 只看该作者
The save() method overrides the class default with the REQUIRES_NEW transaction attribute, which forces the container to start a new transaction. This in turn causes the EntityManager to flush all attached entities into the database and eventually to send the commit to the database.

The gateway is stateful, so it can maintain client-specific state -- and a reference to a PDO. We cache the root Load PDO to make it more easily accessible for the gateway clients. The reference to the Load PDO is maintained in the find() and create() methods. The merge() method is not needed, because there are no detached PDOs to merge. All changed PDOs are automatically synchronized with the database at the end of the transaction.

The gateway is stateful, so there is 1:1 relation between the client and the gateway. This can be only achieved by injecting the gateway into a stateful Web component. If you are using JavaServer Faces (JSF), you can set up this relation by injecting the gateway into a session-scoped backing bean, as shown in Listing 10.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
17#
 楼主| 发表于 2009-5-24 23:17 | 只看该作者
Listing 10. Declaration of a stateful (session-scoped) backing bean for gateway injection

<managed-bean>
    <managed-bean-name>SessionBean1</managed-bean-name>
    <managed-bean-class>.....SessionBean1</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

The Web container stores a session-scoped backing bean in the javax.servlet.http.HttpSession, so the relation between the user session (an open browser window) and the gateway instance is correctly maintained already. You need only inject the session bean into a session-scoped backing bean using the @EJB annotation:

public class SessionBean1 {
    @EJB
    private OrderGateway orderGateway;

The gateway can claim a significant amount of memory. The total amount depends on the number of attached entities inside a user session. It might become a problem in applications with many concurrent sessions, so you should free the resources as soon as possible. The gateway's lifecycle is directly dependent on the lifecycle of the HttpSession. Destruction of the HttpSession should cause the immediate destruction of the gateway. You can achieve this easily by overriding the backing bean's destroy() method and invoking a @Remove annotated method:

@Override
public void destroy() {
    //closeGateway is annotated with @Remove
    this.orderGateway.closeGateway();
}

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
18#
 楼主| 发表于 2009-5-24 23:17 | 只看该作者
Rethinking J2EE best practices

The idea of a gateway became possible only recently, with the introduction of Java EE 5. In the J2EE world such a pattern wouldn't make any sense, because of the lack of inheritance and detaching capabilities in Container-Managed Persistence (CMP) 2.0.

The attempt to introduce such a pattern could become really interesting in real-world projects. You will probably experience some resistance. In the past, one of the important measurement points of a scalable architecture was the degree of coupling and number of layers. Such a metric applied to a gateway pattern would produce rather poor results. A gateway tries neither to encapsulate the PDOs nor to introduce another layer of indirection. The opposite is true: a gateway is glue between layers, because it tries to make access to the domain objects as easy as possible.

A gateway is the reaction to the "Leaky Abstraction" principle. No matter how hard you try to encapsulate a nontrivial implementation, it will always be leaky. It is therefore smarter to expose an already well-designed entity directly, instead trying to encapsulate it further. This is especially true for UI-driven projects. The customer drives the requirements from the UI perspective, so the implementation of a feature will affect the presentation tier first. Such changes are not local to the presentation tier and will hit the adjacent layers as well. The leakier the boundary between layers, the more beneficial will be the effects of introducing a gateway into your architecture.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
19#
 楼主| 发表于 2009-5-24 23:17 | 只看该作者
The gateway's responsibilities

A gateway exposes PDOs directly to the presentation layer. The most important participant is the PDO and the UI itself. The gateway is just an intermediary and provides only the execution context for the PDOs. A gateway is responsible for:

    * Keeping the EntityManager open between method calls, so that the already known PDOs do not become detached.
    * As convenient and direct exposure of PDOs as possible.
    * Providing a defined synchronization point (the save() method ) and optional undo method (EntityManager#refresh).
    * Per-reference (local) access to the PDOs. This requires you to run the EJB container in the same JVM as your Web container. This is the default setting in most application servers.
    * Implementation of crosscutting operations and especially query methods.

A gateway, however, relies on some minimum set of PDO qualities as well:

    * PDOs should be designed to be directly accessed in the presentation. Their functionality should be well encapsulated.
    * A PDO's public methods should always leave the domain object in consistent state. This can't be achieved easily with plain getters and setters.

The presentation tier must be able to keep, or at least determine, user's state. Your Web framework should be capable of keeping the reference to the gateway associated with the user's session. This is already a given in JSF, can be easily achieved with Wicket, and is a little bit harder with plain servlets.

使用道具 举报

回复
论坛徽章:
350
2006年度最佳版主
日期:2007-01-24 12:56:49NBA大富翁
日期:2008-04-21 22:57:29地主之星
日期:2008-11-17 19:37:352008年度最佳版主
日期:2009-03-26 09:33:53股神
日期:2009-04-01 10:05:56NBA季后赛大富翁
日期:2009-06-16 11:48:01NBA季后赛大富翁
日期:2009-06-16 11:48:01ITPUB年度最佳版主
日期:2011-04-08 18:37:09ITPUB年度最佳版主
日期:2011-12-28 15:24:18ITPUB年度最佳技术原创精华奖
日期:2012-03-13 17:12:05
20#
 楼主| 发表于 2009-5-24 23:17 | 只看该作者
State, distribution, and scalability

The direct manipulation of domain objects without synchronization and lazy loading requires attached entities and direct per-reference access to the PDOs. Only then can the EntityManager flush the changed entities to the database. Working with always-attached entities in the UI layer also solves the lazy-loading problem. Although the entities are used outside the gateway, they remain attached. You don't need to preload the entities in advance or use fetch joins for that purpose in the gateway. Lazy loading will just work as expected.

Gateways are stateful, so every user gets his or her own gateway instance, which in turn is associated with an EntityManager. Each EntityManager instance maintains its own JPA entity cache. The EntityManagers are configured as PersistenceContext.EXTENDED, so the cache is not cleared after every transaction.

Furthermore, all PDOs will remain attached, in extreme cases for the length of the session. The size of PDOs in memory is hard to estimate in advance, but can be easily measured during a load test. Such tests do not have to be realistic; it's simply important to test the system's behavior under heavy load. A small proof of concept will help you better understand and estimate the application's scalability characteristics and hardware requirements. You can even attach a profiler to the server during the load test to get a sense of the system behavior.

Object-oriented persistence is not limited to use in stateful, server-centric environments. PDOs can be also used behind a stateless, remote service facade. In that case they remain attached for the length of the transaction and therefore the method invocation. After the invocation they become detached and must be reattached after every method call. Lazy loading and automatic synchronization between layers will not work and must be implemented manually. Usually you will not transfer the PDOs to the client. The invocation of business methods may lead to state changes in the PDOs that must be synchronized manually with the server. For that reason a stateless service facade already implies either the use of anemic persistent structures, or dedicated objects just for data transport between layers. Unfortunately this introduces a lot of plumbing and empty copy-logic.

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表