Procházet zdrojové kódy

Introduced OR mapping for the Cargo->(DeliveryHistory)->HandlingEvent relation, instead of explicitly deciding whether or not to fetch handling events when loading a particular cargo.

peter_backlund před 18 roky
rodič
revize
b18e0291c1

+ 23
- 20
dddsample/src/main/java/se/citerus/dddsample/domain/Cargo.java Zobrazit soubor

@@ -36,7 +36,7 @@ public final class Cargo implements Entity<Cargo> {
36 36
   private Location origin;
37 37
   private Location destination;
38 38
   private Itinerary itinerary;
39
-  private DeliveryHistory deliveryHistory = DeliveryHistory.EMPTY_DELIVERY_HISTORY;
39
+  private DeliveryHistory deliveryHistory;
40 40
 
41 41
   /**
42 42
    * @param trackingId tracking id
@@ -44,7 +44,9 @@ public final class Cargo implements Entity<Cargo> {
44 44
    * @param destination destination location
45 45
    */
46 46
   public Cargo(final TrackingId trackingId, final Location origin, final Location destination) {
47
-    Validate.noNullElements(new Object[] {trackingId, origin, destination});
47
+    Validate.notNull(trackingId);
48
+    Validate.notNull(origin);
49
+    Validate.notNull(destination);
48 50
 
49 51
     this.trackingId = trackingId;
50 52
     this.origin = origin;
@@ -77,35 +79,31 @@ public final class Cargo implements Entity<Cargo> {
77 79
   }
78 80
 
79 81
   /**
80
-   * @return Final destination.
82
+   * @return Destination of the cargo.
81 83
    */
82 84
   public Location destination() {
83 85
     return this.destination;
84 86
   }
85 87
 
86 88
   /**
87
-   * @return Delivery history.
89
+   * @return The delivery history. Never null.
88 90
    */
89 91
   public DeliveryHistory deliveryHistory() {
90
-    return this.deliveryHistory;
92
+    return nullSafe(this.deliveryHistory, DeliveryHistory.EMPTY_DELIVERY_HISTORY);
91 93
   }
92 94
 
93 95
   /**
94
-   * @return The itinerary.
96
+   * @return The itinerary. Never null.
95 97
    */
96 98
   public Itinerary itinerary() {
97
-    if (this.itinerary == null) {
98
-      return Itinerary.EMPTY_ITINERARY;
99
-    } else {
100
-      return this.itinerary;
101
-    }
99
+    return nullSafe(this.itinerary, Itinerary.EMPTY_ITINERARY);
102 100
   }
103 101
 
104 102
   /**
105 103
    * @return Last known location of the cargo, or Location.UNKNOWN if the delivery history is empty.
106 104
    */
107 105
   public Location lastKnownLocation() {
108
-    final HandlingEvent lastEvent = deliveryHistory.lastEvent();
106
+    final HandlingEvent lastEvent = deliveryHistory().lastEvent();
109 107
     if (lastEvent != null) {
110 108
       return lastEvent.location();
111 109
     } else {
@@ -128,11 +126,11 @@ public final class Cargo implements Entity<Cargo> {
128 126
   public void attachItinerary(final Itinerary itinerary) {
129 127
     Validate.notNull(itinerary);
130 128
 
131
-    // Decouple the old itinerary from this cargo 
129
+    // Decouple the old itinerary from this cargo
132 130
     itinerary().setCargo(null);
133 131
     // Couple this cargo and the new itinerary
134 132
     this.itinerary = itinerary;
135
-    itinerary().setCargo(this);
133
+    this.itinerary.setCargo(this);
136 134
   }
137 135
 
138 136
   /**
@@ -146,7 +144,7 @@ public final class Cargo implements Entity<Cargo> {
146 144
   /**
147 145
    * @param deliveryHistory Cargo delivery history
148 146
    */
149
-  public void setDeliveryHistory(final DeliveryHistory deliveryHistory) {
147
+  void setDeliveryHistory(final DeliveryHistory deliveryHistory) {
150 148
     Validate.notNull(deliveryHistory);
151 149
     this.deliveryHistory = deliveryHistory;
152 150
   }
@@ -163,23 +161,23 @@ public final class Cargo implements Entity<Cargo> {
163 161
    * @return <code>true</code> if the cargo has been misdirected,
164 162
    */
165 163
   public boolean isMisdirected() {
166
-    final HandlingEvent lastEvent = deliveryHistory.lastEvent();
167
-    if (itinerary == null || lastEvent == null) {
164
+    final HandlingEvent lastEvent = deliveryHistory().lastEvent();
165
+    if (lastEvent == null) {
168 166
       return false;
169 167
     } else {
170
-      return !itinerary.isExpected(lastEvent);
168
+      return !itinerary().isExpected(lastEvent);
171 169
     }
172 170
   }
173 171
 
174 172
   /**
175
-   * Does not take into account the possibility of the cargo havin been
173
+   * Does not take into account the possibility of the cargo having been
176 174
    * (errouneously) loaded onto another carrier after it has been unloaded
177 175
    * at the final destination.
178 176
    *
179 177
    * @return True if the cargo has been unloaded at the final destination.
180 178
    */
181 179
   public boolean isUnloadedAtDestination() {
182
-    for (HandlingEvent event : deliveryHistory.eventsOrderedByCompletionTime()) {
180
+    for (HandlingEvent event : deliveryHistory().eventsOrderedByCompletionTime()) {
183 181
       if (HandlingEvent.Type.UNLOAD.equals(event.type())
184 182
         && destination.equals(event.location())) {
185 183
         return true;
@@ -214,6 +212,11 @@ public final class Cargo implements Entity<Cargo> {
214 212
     return trackingId.hashCode();
215 213
   }
216 214
 
215
+  // Utility for Null Object Pattern - should be moved out of this class
216
+  private <T> T nullSafe(T actual, T safe) {
217
+    return actual == null ? safe : actual;
218
+  }
219
+
217 220
   Cargo() {
218 221
     // Needed by Hibernate
219 222
   }

+ 6
- 2
dddsample/src/main/java/se/citerus/dddsample/domain/DeliveryHistory.java Zobrazit soubor

@@ -11,11 +11,11 @@ import java.util.*;
11 11
  */
12 12
 public final class DeliveryHistory implements ValueObject<DeliveryHistory> {
13 13
 
14
-  private final Set<HandlingEvent> events;
14
+  private Set<HandlingEvent> events;
15 15
 
16 16
   public static final DeliveryHistory EMPTY_DELIVERY_HISTORY = new DeliveryHistory(Collections.EMPTY_SET);
17 17
 
18
-  public DeliveryHistory(final Collection<HandlingEvent> events) {
18
+  DeliveryHistory(final Collection<HandlingEvent> events) {
19 19
     this.events = new HashSet<HandlingEvent>(events);
20 20
   }
21 21
 
@@ -98,4 +98,8 @@ public final class DeliveryHistory implements ValueObject<DeliveryHistory> {
98 98
     return events.hashCode();
99 99
   }
100 100
 
101
+  DeliveryHistory() {
102
+    // Needed by Hibernate
103
+  }
104
+
101 105
 }

+ 7
- 28
dddsample/src/main/java/se/citerus/dddsample/repository/CargoRepositoryHibernate.java Zobrazit soubor

@@ -2,7 +2,6 @@ package se.citerus.dddsample.repository;
2 2
 
3 3
 import org.springframework.stereotype.Repository;
4 4
 import se.citerus.dddsample.domain.Cargo;
5
-import se.citerus.dddsample.domain.DeliveryHistory;
6 5
 import se.citerus.dddsample.domain.Itinerary;
7 6
 import se.citerus.dddsample.domain.TrackingId;
8 7
 
@@ -18,41 +17,21 @@ public class CargoRepositoryHibernate extends HibernateRepository implements Car
18 17
   HandlingEventRepository handlingEventRepository;
19 18
 
20 19
   public Cargo find(TrackingId tid) {
21
-    Cargo cargo = (Cargo) getSession().
22
-            createQuery("from Cargo where trackingId = :tid").
20
+    // Query for id and then perform a standard load()
21
+    // to use metadata-defined query and lazy proxy access
22
+    Long id = (Long) getSession().
23
+            createQuery("select id from Cargo where trackingId = :tid").
23 24
             setParameter("tid", tid).
24 25
             uniqueResult();
25
-    if (cargo == null) {
26
-      return null;
27
-    }
28
-    /*  There's no OR-mapped relation between the cargo delivery history and its handling events
29
-        because the handling events are in a different aggregate.
30
-
31
-        TODO: investigate the following scenario to motivate this construction 
32
-        If we had to use pessimistic locking on cargo,
33
-        it might be cumbersome to map the relation between cargo and handling event,
34
-        since we want to be able to insert handling events regardless of locking status on cargo.
35
-
36
-
37
-        If this extra database call were a problem, you might want to use a different model.
38
-        For example, you could calculate the effect/status of the cargo and store it separate from the
39
-        handling events. */
40
-
41
-    /*
42
-        TODO:
43
-        the decision whether or not to include the delivery history when loading cargo
44
-        seems to belong in the service layer, which defines use cases.
45
-     */
46
-    DeliveryHistory deliveryHistory = new DeliveryHistory(handlingEventRepository.findEventsForCargo(tid));
47
-    cargo.setDeliveryHistory(deliveryHistory);
48 26
 
49
-    return cargo;
27
+    return (Cargo) getSession().load(Cargo.class, id);
50 28
   }
51 29
 
52 30
   public void save(Cargo cargo) {
53 31
     getSession().persist(cargo);
54 32
 
55
-    // Delete orphaned itineraries
33
+    // Delete orphaned itineraries - conceptually the responsibility
34
+    // of the Cargo aggregate
56 35
     final List<Itinerary> orphans = getSession().
57 36
       createQuery("from Itinerary where cargo = null").
58 37
       list();

+ 6
- 0
dddsample/src/main/resources/se/citerus/dddsample/domain/Cargo.hbm.xml Zobrazit soubor

@@ -12,6 +12,12 @@
12 12
     <component name="trackingId" unique="true" update="false">
13 13
       <property name="id" column="tracking_id"/>
14 14
     </component>
15
+    <component name="deliveryHistory" lazy="true" update="false">
16
+      <set name="events" lazy="true" cascade="none">
17
+        <key column="cargo_id"/>
18
+        <one-to-many class="se.citerus.dddsample.domain.HandlingEvent"/>
19
+      </set>
20
+    </component>
15 21
     <many-to-one name="origin" column="origin_id" cascade="none" fetch="join" update="false" foreign-key="origin_fk"/>
16 22
     <many-to-one name="destination" column="destination_id" cascade="none" fetch="join" foreign-key="destination_fk"/>
17 23
     <one-to-one name="itinerary" property-ref="cargo" cascade="all" fetch="join"/>