Domain-driven design with Java EE 6

[复制链接]
查看11 | 回复9 | 2017-9-26 13:06:30 | 显示全部楼层 |阅读模式
For Java EE applications based on type-dependent algorithms, a domain-driven design that leverages object-orientation is better than a procedurally implemented service-oriented architecture. Adam Bien explains how and why object-oriented persistence makes your domain-driven application's code more efficient, maintainable, and testable. Level: Advanced
In "Lean service architectures with Java EE 6," I discussed how to implement a maintainable service-oriented architecture (SOA) that is based on anemic persistent objects and transient, stateless services. In this follow-up article, you'll learn about an architectural approach that is the exact opposite of that. What ties together the two architectures is that both can be implemented with Java EE 6 and, in particular, Enterprise JavaBeans (EJB).
As I'll explain in this article, you can build domain-driven applications with "real" objects -- objects that comprise encapsulated state and well-defined behavior. Whereas SOA is stateless and tries to hide a component's implementation, domain-driven applications are mostly stateful and aim to expose the domain objects to the user as directly and conveniently as possible.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Procedural vs domain-driven design
The vast majority of J2EE and Java EE applications are built in a procedural way. The business logic is decomposed into tasks and resources, which map, respectively, to services and anemic, persistent entities. Such an approach works surprisingly well until type-specific behavior for domain objects must be realized. Then the lack of object orientation in the persistence layer can cause problems.
Attempts to implement object-oriented algorithms with procedural techniques end up in many instanceof checks and/or lengthy if-statements. Such type checks are required because in the SOA world, domain objects are anemic, so inheritance doesn't pay off. Even if inheritance is used to design the domain model, object orientation's most powerful feature -- polymorphic behavior -- isn't leveraged at all.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
The anemic domain model forces you to implement type checks outside the anemic entities, in services or service facades. To make their internal state accessible to the services and service facades, the anemic structures (technically they are not objects, by definition) must expose their internal state to the outside world too -- through field accessors (getters/setters) -- requiring a lot of plumbing.
In data-driven use cases such as master data management (CRUD), you can implement workflow-related, procedural behavior efficiently with service facades and few anemic entities. A domain-driven architecture, on the other hand, is perfect for more-complex, type-dependent algorithms in more-interactive applications. Most nontrivial Java EE projects require both approaches.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Essential ingredients of domain-driven design
The crucial artifact in any domain-driven application is a domain object.A domain object can be considered a real-world artifact in the givenproject context. The challenge is to grasp the context and not try tomodel a perfect, theoretical world. Pragmatism rules. Domain objectsare just cohesive classes with encapsulated state and related behavior.They use inheritance naturally, where appropriate.


Domainobjects represent important concepts in the target domain and so mustoften be persistent. The Java Persistence API (JPA) turns out to bequite flexible for mapping rich domain objects to relational tables.The more complex the logic you need to realize, the more easilyobject-oriented persistence can be maintained and developed.

The real issue with complex logic realized with anemic structures are type distinctions in the service layer. Massive if-statementsmight be necessary to differentiate among entity types. Everyintroduction of a new subclass, or even a change in the existingbusiness logic, requires you to find, enhance, and test these typechecks. For example, the computation of shipping costs dependent onweight and the OrderItem type would look like Listing 1.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Listing 1. Procedural approach to switching between entity types
@Stateless
public class ShipmentService {
public final static int BASIC_COST = 5;
@PersistenceContext
private EntityManager em;
public int getShippingCosts(int loadId) {
Load load = em.find(Load.class, loadId);
return computeShippingCost(load);
}
int computeShippingCost(Load load){
int shippingCosts = 0;
int weight = 0;
int defaultCost = 0;
for (OrderItem orderItem : load.getOrderItems()) {

LoadType loadType = orderItem.getLoadType();

weight = orderItem.getWeight();

defaultCost = weight * 5;

switch (loadType) {

case BULKY:

shippingCosts += (defaultCost + 5);

break;

case LIGHTWEIGHT:

shippingCosts += (defaultCost - 1);

break;

case STANDARD:

shippingCosts += (defaultCost);

break;

default:

throw new IllegalStateException("Unknown type: " + loadType);

}
}
return shippingCosts;
}
}
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
In Listing 1, all the logic resides in a service or service facadeand consists mainly of type checks. The service must differentiateamong the different types to apply type-dependent computationalalgorithms.

The same logic can be expressed with only a fraction of code in an object-oriented way, as shown in Listing 2. You can even
eliminate the service, because the logic is implemented in the domain object itself.


Listing 2. Polymorphic business logic inside a domain object, without type checks
@Entity
public class Load {
@OneToMany(cascade = CascadeType.ALL)
private List orderItems;
@Id
private Long id;
protected Load() {
this.orderItems = new ArrayList();
}
public int getShippingCosts() {
int shippingCosts = 0;
for (OrderItem orderItem : orderItems) {

shippingCosts += orderItem.getShippingCost();
}
return shippingCosts;
}
//...
}
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
In Listing 2, computation of the total costs is realized inside a rich domain object -- the persistent domain object (PDO) -- not inside a service. Object-oriented persistence makes the code more concise and so also easier to understand. Actual computation of type-specific costs is performed in concrete subclasses such as the BulkyItem class in Listing 3, which makes testing the computation outside the EntityManager much easier.
Listing 3. Type-specific computation in a subclass
@Entity
public class BulkyItem extends OrderItem{
public BulkyItem() {
}
public BulkyItem(int weight) {
super(weight);
}
@Override
public int getShippingCost() {
return super.getShippingCost() + 5;
}
}
We can change the computation of the shipping cost of a BulkyItem without touching the remaining classes. And it's easy to introduce a new subclass without affecting the computation of the total costs in the Load class.
The creation of a PDO graph is easier and more intuitive, compared to the traditional getter/setter-driven approach shown in Listing 4.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Listing 4. The procedural way of object construction
Load load = new Load();
OrderItem standard = new OrderItem();
standard.setLoadType(LoadType.STANDARD);
standard.setWeight(5);
load.getOrderItems().add(standard);
OrderItem light = new OrderItem();
light.setLoadType(LoadType.LIGHTWEIGHT);
light.setWeight(1);
load.getOrderItems().add(light);
OrderItem bulky = new OrderItem();
bulky.setLoadType(LoadType.BULKY);
bulky.setWeight(1);
load.getOrderItems().add(bulky);
Instead of creating the anemic objects independently and wiring them together afterwards, you can take a concise object-oriented approach using the Builder pattern, as shown in Listing 5.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Listing 5. Concise PDO construction with the Builder pattern
Load build = new Load.Builder().
withStandardItem(5).
withLightweightItem(1).
withBulkyItem(1).
build();
The Builder pattern lets us construct the Load domain object with concrete OrderItems in convenient way. Method chaining saves some superfluous lines of code and allows autocompletion support in common IDEs. In addition, consistency checks are performed in the build() method, so it's not necessary to create empty Load objects.
Builder is implemented as a static inner class, as shown in Listing 6.
回复

使用道具 举报

千问 | 2017-9-26 13:06:30 | 显示全部楼层
Listing 6. Builder realized as a static inner class inside a PDO
@Entity
public class Load {
@OneToMany(cascade = CascadeType.ALL)
private List orderItems;
@Id
private Long id;
protected Load() {
this.orderItems = new ArrayList();
}
public static class Builder {
private Load load;
public Builder() {

this.load = new Load();
}
public Builder withBulkyItem(int weight) {

this.load.add(new BulkyItem(weight));

return this;
}
public Builder withStandardItem(int weight) {

this.load.add(new StandardItem(weight));

return this;
}
public Builder withLightweightItem(int weight) {

this.load.add(new LightweightItem(weight));

return this;
}
public Load build() {

if (load.orderItems.size() == 0) {

throw new IllegalStateException("...");

}

return this.load;
}
}
void add(OrderItem item) {
this.orderItems.add(item);
}
public int getShippingCosts() {
int shippingCosts = 0;
for (OrderItem orderItem : orderItems) {

shippingCosts += orderItem.getShippingCost();
}
return shippingCosts;
}
public OrderItem lightest(){
if(this.orderItems == null || this.orderItems.size() == 0)

return null;
Collections.sort(this.orderItems, new WeightComparator());
return this.orderItems.iterator().next();
}
public OrderItem heaviest(){
if(this.orderItems == null || this.orderItems.size() == 0)

return null;
Collections.sort(this.orderItems, new WeightComparator());
Collections.reverse(orderItems);
return this.orderItems.iterator().next();
}
public OrderItem dropHeaviest(){
OrderItem heaviest = heaviest();
if(heaviest != null)

drop(heaviest);
return heaviest;
}
public OrderItem dropLightest(){
OrderItem lightest = lightest();
if(lightest != null)

drop(lightest);
return lightest;
}
public Load drop(OrderItem orderItem){
this.orderItems.remove(orderItem);
return this;
}
public Long getId() {
return id;
}
public int getNumberOfOrderItems(){
return this.orderItems.size();
}
}
回复

使用道具 举报

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

本版积分规则

主题

0

回帖

4882万

积分

论坛元老

Rank: 8Rank: 8

积分
48824836
热门排行