소스 검색

Split routing service into two parts: "our" part with model translation, and remote service part for graph calculations. Still a bit sketchy though.

More flesh on RouteSpecification, with some ideas around factory methods for specifications.

Renamed a few service methods to match ubiquotous language more closely.

Pushed DTO assembly outside of service layer and into MVC controllers, pending creation of dedicated remoting layer. Service layer now uses pure domain model classes.  

CarrierMovementId implements ValueObject.
peter_backlund 18 년 전
부모
커밋
a54840a55a
21개의 변경된 파일350개의 추가작업 그리고 166개의 파일을 삭제
  1. 7
    2
      dddsample/src/main/java/se/citerus/dddsample/domain/CarrierMovementId.java
  2. 76
    4
      dddsample/src/main/java/se/citerus/dddsample/domain/RouteSpecification.java
  3. 10
    3
      dddsample/src/main/java/se/citerus/dddsample/domain/SampleLocations.java
  4. 6
    0
      dddsample/src/main/java/se/citerus/dddsample/domain/Specification.java
  5. 5
    0
      dddsample/src/main/java/se/citerus/dddsample/domain/UnLocode.java
  6. 2
    2
      dddsample/src/main/java/se/citerus/dddsample/repository/LocationRepositoryInMem.java
  7. 15
    37
      dddsample/src/main/java/se/citerus/dddsample/service/CargoServiceImpl.java
  8. 82
    0
      dddsample/src/main/java/se/citerus/dddsample/service/GraphTraversalService.java
  9. 2
    1
      dddsample/src/main/java/se/citerus/dddsample/service/InMemTransactionManager.java
  10. 7
    3
      dddsample/src/main/java/se/citerus/dddsample/service/RoutingService.java
  11. 25
    60
      dddsample/src/main/java/se/citerus/dddsample/service/RoutingServiceImpl.java
  12. 49
    13
      dddsample/src/main/java/se/citerus/dddsample/web/CargoAdminController.java
  13. 13
    0
      dddsample/src/main/resources/context-routing.xml
  14. 1
    2
      dddsample/src/main/resources/context-service.xml
  15. 5
    1
      dddsample/src/main/resources/context-web.xml
  16. 10
    0
      dddsample/src/main/webapp/WEB-INF/dispatch-servlet.xml
  17. 1
    0
      dddsample/src/main/webapp/WEB-INF/web.xml
  18. 2
    2
      dddsample/src/test/java/se/citerus/dddsample/service/CargoServiceTest.java
  19. 7
    5
      dddsample/src/test/java/se/citerus/dddsample/service/RoutingScenarioTest.java
  20. 20
    24
      dddsample/src/test/java/se/citerus/dddsample/service/RoutingServiceTest.java
  21. 5
    7
      dddsample/src/test/java/se/citerus/dddsample/web/CargoTrackingControllerTest.java

+ 7
- 2
dddsample/src/main/java/se/citerus/dddsample/domain/CarrierMovementId.java 파일 보기

@@ -7,7 +7,7 @@ import org.apache.commons.lang.builder.HashCodeBuilder;
7 7
 /**
8 8
  * Identifies a particular carrier movement, such as a flight number.
9 9
  */
10
-public final class CarrierMovementId {
10
+public final class CarrierMovementId implements ValueObject<CarrierMovementId> {
11 11
 
12 12
   private String id;
13 13
 
@@ -38,8 +38,13 @@ public final class CarrierMovementId {
38 38
     return HashCodeBuilder.reflectionHashCode(this);
39 39
   }
40 40
 
41
+  @Override
42
+  public boolean sameValueAs(CarrierMovementId other) {
43
+    return other != null && this.id.equals(other.id);
44
+  }
45
+
41 46
   // Needed by hibernate
42 47
   CarrierMovementId() {
43 48
   }
44
-
49
+  
45 50
 }

+ 76
- 4
dddsample/src/main/java/se/citerus/dddsample/domain/RouteSpecification.java 파일 보기

@@ -1,9 +1,81 @@
1 1
 package se.citerus.dddsample.domain;
2 2
 
3
+import org.apache.commons.lang.Validate;
4
+import org.apache.commons.lang.builder.EqualsBuilder;
5
+import org.apache.commons.lang.builder.HashCodeBuilder;
6
+
7
+import java.util.Date;
8
+
3 9
 /**
4
- *
10
+ * Route specification
5 11
  */
6
-public interface RouteSpecification {
7
-  Location origin();
8
-  Location destination();
12
+public class RouteSpecification implements ValueObject<RouteSpecification>, Specification<Itinerary> {
13
+  private Location origin;
14
+  private Location destination;
15
+  private Date arrivalDeadline;
16
+
17
+  /**
18
+   * Factory method.
19
+   *
20
+   * TODO just a suggestion - could be useful with RouteSpecification.witinOneWeek(cargo) etc
21
+   * 
22
+   * @param cargo cargo
23
+   * @param arrivalDeadline arrival deadline
24
+   * @return A route specification for this cargo and arrival deadline
25
+   */
26
+  public static RouteSpecification forCargo(Cargo cargo, Date arrivalDeadline) {
27
+    Validate.notNull(cargo);
28
+    Validate.notNull(arrivalDeadline);
29
+
30
+    return new RouteSpecification(cargo.origin(), cargo.destination(), arrivalDeadline);
31
+  }
32
+
33
+  private RouteSpecification(Location origin, Location destination, Date arrivalDeadline) {
34
+    this.origin = origin;
35
+    this.destination = destination;
36
+    this.arrivalDeadline = arrivalDeadline;
37
+  }
38
+
39
+  public Location origin() {
40
+    return origin;
41
+  }
42
+
43
+  public Location destination() {
44
+    return destination;
45
+  }
46
+
47
+  @Override
48
+  public boolean isSatisfiedBy(Itinerary itinerary) {
49
+    // TODO implement
50
+    return true;
51
+  }
52
+
53
+  @Override
54
+  public boolean sameValueAs(RouteSpecification other) {
55
+    return other != null && new EqualsBuilder().
56
+      append(this.origin, other.origin).
57
+      append(this.destination, other.destination).
58
+      append(this.arrivalDeadline, other.arrivalDeadline).
59
+      isEquals();
60
+  }
61
+
62
+  @Override
63
+  public boolean equals(Object o) {
64
+    if (this == o) return true;
65
+    if (o == null || getClass() != o.getClass()) return false;
66
+
67
+    RouteSpecification that = (RouteSpecification) o;
68
+
69
+    return sameValueAs(that);
70
+  }
71
+
72
+  @Override
73
+  public int hashCode() {
74
+    return new HashCodeBuilder().
75
+      append(this.origin).
76
+      append(this.destination).
77
+      append(this.arrivalDeadline).
78
+      toHashCode();
79
+  }
80
+  
9 81
 }

+ 10
- 3
dddsample/src/main/java/se/citerus/dddsample/domain/SampleLocations.java 파일 보기

@@ -2,7 +2,9 @@ package se.citerus.dddsample.domain;
2 2
 
3 3
 import java.lang.reflect.Field;
4 4
 import java.util.ArrayList;
5
+import java.util.HashMap;
5 6
 import java.util.List;
7
+import java.util.Map;
6 8
 
7 9
 /**
8 10
  * A few locations for easy testing.
@@ -22,13 +24,14 @@ public class SampleLocations {
22 24
   public static final Location HANGZOU = new Location(new UnLocode("CNHGH"), "Hangzhou");
23 25
   public static final Location NEWYORK = new Location(new UnLocode("USNYC"), "New York");
24 26
 
25
-  public static final List<Location> ALL = new ArrayList<Location>();
27
+  public static final Map<UnLocode, Location> ALL = new HashMap();
26 28
 
27 29
   static {
28 30
     for (Field field : SampleLocations.class.getDeclaredFields()) {
29 31
       if (field.getType().equals(Location.class)) {
30 32
         try {
31
-          ALL.add((Location) field.get(null));
33
+          Location location = (Location) field.get(null);
34
+          ALL.put(location.unLocode(), location);
32 35
         } catch (IllegalAccessException e) {
33 36
           throw new RuntimeException(e);
34 37
         }
@@ -37,7 +40,11 @@ public class SampleLocations {
37 40
   }
38 41
 
39 42
   public static List<Location> getAll() {
40
-    return ALL;
43
+    return new ArrayList(ALL.values());
44
+  }
45
+
46
+  public static Location lookup(UnLocode unLocode) {
47
+    return ALL.get(unLocode);
41 48
   }
42 49
 
43 50
 }

+ 6
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/Specification.java 파일 보기

@@ -0,0 +1,6 @@
1
+package se.citerus.dddsample.domain;
2
+
3
+public interface Specification<T> {
4
+  // TODO replace with Specification project dependency
5
+  boolean isSatisfiedBy(T t);
6
+}

+ 5
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/UnLocode.java 파일 보기

@@ -51,6 +51,11 @@ public final class UnLocode {
51 51
     return HashCodeBuilder.reflectionHashCode(this);
52 52
   }
53 53
 
54
+  @Override
55
+  public String toString() {
56
+    return idString();
57
+  }
58
+
54 59
   UnLocode() {
55 60
     // Needed by Hibernate
56 61
   }

+ 2
- 2
dddsample/src/main/java/se/citerus/dddsample/repository/LocationRepositoryInMem.java 파일 보기

@@ -9,7 +9,7 @@ import java.util.List;
9 9
 public class LocationRepositoryInMem implements LocationRepository {
10 10
 
11 11
   public Location find(UnLocode unLocode) {
12
-    for (Location location : SampleLocations.ALL) {
12
+    for (Location location : SampleLocations.getAll()) {
13 13
       if (location.unLocode().equals(unLocode)) {
14 14
         return location;
15 15
       }
@@ -18,7 +18,7 @@ public class LocationRepositoryInMem implements LocationRepository {
18 18
   }
19 19
 
20 20
   public List<Location> findAll() {
21
-    return SampleLocations.ALL;
21
+    return SampleLocations.getAll();
22 22
   }
23 23
   
24 24
 }

+ 15
- 37
dddsample/src/main/java/se/citerus/dddsample/service/CargoServiceImpl.java 파일 보기

@@ -6,14 +6,9 @@ import org.apache.commons.logging.LogFactory;
6 6
 import org.springframework.transaction.annotation.Transactional;
7 7
 import se.citerus.dddsample.domain.*;
8 8
 import se.citerus.dddsample.repository.CargoRepository;
9
-import se.citerus.dddsample.repository.CarrierMovementRepository;
10 9
 import se.citerus.dddsample.repository.LocationRepository;
11
-import se.citerus.dddsample.service.dto.CargoRoutingDTO;
12 10
 import se.citerus.dddsample.service.dto.CargoTrackingDTO;
13
-import se.citerus.dddsample.service.dto.ItineraryCandidateDTO;
14
-import se.citerus.dddsample.service.dto.assembler.CargoRoutingDTOAssembler;
15 11
 import se.citerus.dddsample.service.dto.assembler.CargoTrackingDTOAssembler;
16
-import se.citerus.dddsample.service.dto.assembler.ItineraryCandidateDTOAssembler;
17 12
 
18 13
 import java.util.ArrayList;
19 14
 import java.util.List;
@@ -22,12 +17,11 @@ public final class CargoServiceImpl implements CargoService {
22 17
 
23 18
   private CargoRepository cargoRepository;
24 19
   private LocationRepository locationRepository;
25
-  private CarrierMovementRepository carrierMovementRepository;
26 20
 
27 21
   private final Log logger = LogFactory.getLog(getClass());
28
-               
22
+
29 23
   @Transactional(readOnly = false)
30
-  public TrackingId registerNew(final UnLocode originUnLocode, final UnLocode destinationUnLocode) {
24
+  public TrackingId registerNewCargo(final UnLocode originUnLocode, final UnLocode destinationUnLocode) {
31 25
     Validate.notNull(originUnLocode);
32 26
     Validate.notNull(destinationUnLocode);
33 27
 
@@ -43,7 +37,7 @@ public final class CargoServiceImpl implements CargoService {
43 37
   }
44 38
 
45 39
   @Transactional(readOnly = true)
46
-  public List<UnLocode> shippingLocations() {
40
+  public List<UnLocode> listShippingLocations() {
47 41
     final List<Location> allLocations = locationRepository.findAll();
48 42
     final List<UnLocode> unlocodes = new ArrayList<UnLocode>(allLocations.size());
49 43
     for (Location location : allLocations) {
@@ -52,8 +46,7 @@ public final class CargoServiceImpl implements CargoService {
52 46
     return unlocodes;
53 47
   }
54 48
 
55
-  // TODO: move to TrackingService, rename this class BookingService, to match course
56
-  // TODO: remove all DTOs from BookingService, only keep DTOs in TrackingService
49
+  // TODO: move track() and notify() to TrackingService, rename this class BookingService
57 50
   @Transactional(readOnly = true)
58 51
   public CargoTrackingDTO track(final TrackingId trackingId) {
59 52
     Validate.notNull(trackingId);
@@ -66,7 +59,6 @@ public final class CargoServiceImpl implements CargoService {
66 59
     return new CargoTrackingDTOAssembler().toDTO(cargo);
67 60
   }
68 61
 
69
-  // TODO: move this to another class?
70 62
   @Transactional(readOnly = true)
71 63
   public void notify(final TrackingId trackingId) {
72 64
     Validate.notNull(trackingId);
@@ -89,45 +81,34 @@ public final class CargoServiceImpl implements CargoService {
89 81
   }
90 82
 
91 83
   @Transactional(readOnly = true)
92
-  public List<CargoRoutingDTO> loadAllForRouting() {
84
+  public List<Cargo> listAllCargos() {
93 85
     final List<Cargo> allCargos = cargoRepository.findAll();
94
-
95
-    final CargoRoutingDTOAssembler assembler = new CargoRoutingDTOAssembler();
96
-    final List<CargoRoutingDTO> dtoList = new ArrayList<CargoRoutingDTO>(allCargos.size());
97
-
98
-    for (Cargo cargo : allCargos) {
99
-      dtoList.add(assembler.toDTO(cargo));
100
-    }
101
-
102
-    return dtoList;
86
+    // TODO: specification pattern might be useful here, too
87
+    return allCargos;
103 88
   }
104 89
 
105 90
   @Transactional(readOnly = true)
106
-  public CargoRoutingDTO loadForRouting(final TrackingId trackingId) {
91
+  public Cargo loadCargoForRouting(final TrackingId trackingId) {
107 92
     Validate.notNull(trackingId);
108 93
 
109 94
     // TODO obtain offline lock
110 95
     final Cargo cargo = cargoRepository.find(trackingId);
111
-    if (cargo == null) {
112
-      return null;
113
-    }
114 96
 
115
-    return new CargoRoutingDTOAssembler().toDTO(cargo);
97
+    return cargo;
116 98
   }
117 99
 
118 100
   @Transactional(readOnly = false)
119
-  public void assignItinerary(final TrackingId trackingId, final ItineraryCandidateDTO dto) {
101
+  public void assignCargoToRoute(final TrackingId trackingId, final Itinerary newItinerary) {
120 102
     Validate.notNull(trackingId);
121
-    Validate.notNull(dto);
103
+    Validate.notNull(newItinerary);
122 104
 
123 105
     final Cargo cargo = cargoRepository.find(trackingId);
124 106
     if (cargo == null) {
125 107
       throw new IllegalArgumentException("Can't assign itinerary to non-existing cargo " + trackingId);
126 108
     }
127 109
 
128
-    final ItineraryCandidateDTOAssembler itineraryCandidateDTOAssembler = new ItineraryCandidateDTOAssembler();
129
-    final Itinerary newItinerary = itineraryCandidateDTOAssembler.fromDTO(dto, carrierMovementRepository, locationRepository);
130
-
110
+    // TODO try to leave as much of orphan deletion as possible to the OR mapper - rethink mapping, or implement transparent callback
111
+    
131 112
     // Delete orphaned itinerary - it's just a value object
132 113
     final Itinerary oldItinerary = cargo.itinerary();
133 114
     cargoRepository.deleteItinerary(oldItinerary);
@@ -137,10 +118,11 @@ public final class CargoServiceImpl implements CargoService {
137 118
     cargo.attachItinerary(newItinerary);
138 119
     cargoRepository.save(cargo);
139 120
 
121
+    logger.info("Assigned cargo " + trackingId + " to new route");
122
+
140 123
     // TODO release offline lock
141 124
   }
142 125
 
143
-
144 126
   public void setCargoRepository(final CargoRepository cargoRepository) {
145 127
     this.cargoRepository = cargoRepository;
146 128
   }
@@ -149,8 +131,4 @@ public final class CargoServiceImpl implements CargoService {
149 131
     this.locationRepository = locationRepository;
150 132
   }
151 133
 
152
-  public void setCarrierMovementRepository(final CarrierMovementRepository carrierMovementRepository) {
153
-    this.carrierMovementRepository = carrierMovementRepository;
154
-  }
155
-
156 134
 }

+ 82
- 0
dddsample/src/main/java/se/citerus/dddsample/service/GraphTraversalService.java 파일 보기

@@ -0,0 +1,82 @@
1
+package se.citerus.dddsample.service;
2
+
3
+import org.springframework.transaction.annotation.Propagation;
4
+import org.springframework.transaction.annotation.Transactional;
5
+import se.citerus.dddsample.domain.*;
6
+import se.citerus.dddsample.repository.CarrierMovementRepository;
7
+import se.citerus.dddsample.repository.LocationRepository;
8
+import se.citerus.dddsample.service.dto.ItineraryCandidateDTO;
9
+import se.citerus.dddsample.service.dto.assembler.ItineraryCandidateDTOAssembler;
10
+
11
+import java.util.ArrayList;
12
+import java.util.Collections;
13
+import java.util.List;
14
+import java.util.Random;
15
+
16
+public class GraphTraversalService {
17
+
18
+  private LocationRepository locationRepository;
19
+  private CarrierMovementRepository carrierMovementRepository;
20
+
21
+  Random random = new Random();
22
+
23
+  @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
24
+  public List<ItineraryCandidateDTO> performHeavyCalculations(String originUnLocode, String destinationUnLocode) {
25
+    List<Location> allLocations = locationRepository.findAll();
26
+    Location origin = locationRepository.find(new UnLocode(originUnLocode));
27
+    Location destination = locationRepository.find(new UnLocode(destinationUnLocode));
28
+    
29
+    allLocations.remove(origin);
30
+    allLocations.remove(destination);
31
+
32
+    final int candidateCount = getRandomNumberOfCandidates();
33
+    final List<ItineraryCandidateDTO> candidates = new ArrayList<ItineraryCandidateDTO>(candidateCount);
34
+    final ItineraryCandidateDTOAssembler assembler = new ItineraryCandidateDTOAssembler();
35
+
36
+    for (int i = 0; i < candidateCount; i++) {
37
+      allLocations = getRandomChunkOfLocations(allLocations);
38
+      final List<Leg> legs = new ArrayList<Leg>(allLocations.size() - 1);
39
+      final Location firstLegTo = allLocations.get(0);
40
+
41
+      final CarrierMovement cm1 = carrierMovementRepository.find(new CarrierMovementId("CAR_002"));
42
+      legs.add(new Leg(cm1, origin, firstLegTo));
43
+
44
+      for (int j = 0; j < allLocations.size() - 1; j++) {
45
+        legs.add(new Leg(getRandomCarrierMovement(), allLocations.get(j), allLocations.get(j + 1)));
46
+      }
47
+
48
+      final Location lastLegFrom = allLocations.get(allLocations.size() - 1);
49
+      legs.add(new Leg(getRandomCarrierMovement(), lastLegFrom, destination));
50
+
51
+      final Itinerary itinerary = new Itinerary(legs);
52
+      candidates.add(assembler.toDTO(itinerary));
53
+    }
54
+
55
+    return candidates;
56
+  }
57
+
58
+  private CarrierMovement getRandomCarrierMovement() {
59
+    // TODO create new CMs on the fly, with better names than "car 1,2,3"
60
+    final CarrierMovementId id = new CarrierMovementId("CAR_00" + (random.nextInt(9) + 1));
61
+    return carrierMovementRepository.find(id);
62
+  }
63
+
64
+  private int getRandomNumberOfCandidates() {
65
+    return 1 + random.nextInt(4);
66
+  }
67
+
68
+  private List<Location> getRandomChunkOfLocations(List<Location> allLocations) {
69
+    Collections.shuffle(allLocations);
70
+    final int total = allLocations.size();
71
+    final int chunk = total > 4 ? (total - 4) + random.nextInt(5) : total;
72
+    return allLocations.subList(0, chunk);
73
+  }
74
+
75
+  public void setLocationRepository(LocationRepository locationRepository) {
76
+    this.locationRepository = locationRepository;
77
+  }
78
+
79
+  public void setCarrierMovementRepository(CarrierMovementRepository carrierMovementRepository) {
80
+    this.carrierMovementRepository = carrierMovementRepository;
81
+  }
82
+}

+ 2
- 1
dddsample/src/main/java/se/citerus/dddsample/service/InMemTransactionManager.java 파일 보기

@@ -7,7 +7,8 @@ import org.springframework.transaction.TransactionStatus;
7 7
 import org.springframework.transaction.support.SimpleTransactionStatus;
8 8
 
9 9
 /**
10
- * 
10
+ * Simple no-op transaction manager to allow for in-memory "persistence"
11
+ * implementation to be plugged in transparently.
11 12
  */
12 13
 public class InMemTransactionManager implements PlatformTransactionManager {
13 14
 

+ 7
- 3
dddsample/src/main/java/se/citerus/dddsample/service/RoutingService.java 파일 보기

@@ -1,16 +1,20 @@
1 1
 package se.citerus.dddsample.service;
2 2
 
3
+import se.citerus.dddsample.domain.Itinerary;
3 4
 import se.citerus.dddsample.domain.RouteSpecification;
4
-import se.citerus.dddsample.domain.TrackingId;
5
-import se.citerus.dddsample.service.dto.ItineraryCandidateDTO;
6 5
 
7 6
 import java.util.List;
8 7
 
9 8
 /**
9
+ * Routing service.
10 10
  *
11 11
  */
12 12
 public interface RoutingService {
13 13
 
14
-  List<ItineraryCandidateDTO> calculatePossibleRoutes(TrackingId trackingId, RouteSpecification routeSpecification);
14
+  /**
15
+   * @param routeSpecification route specification
16
+   * @return A list of itineraries that satisfy the specification. May be an empty list if no route is found.
17
+   */
18
+  List<Itinerary> requestPossibleRoutes(RouteSpecification routeSpecification);
15 19
 
16 20
 }

+ 25
- 60
dddsample/src/main/java/se/citerus/dddsample/service/RoutingServiceImpl.java 파일 보기

@@ -1,92 +1,57 @@
1 1
 package se.citerus.dddsample.service;
2 2
 
3 3
 import org.springframework.transaction.annotation.Transactional;
4
-import se.citerus.dddsample.domain.*;
5
-import se.citerus.dddsample.repository.CargoRepository;
4
+import se.citerus.dddsample.domain.Itinerary;
5
+import se.citerus.dddsample.domain.Location;
6
+import se.citerus.dddsample.domain.RouteSpecification;
6 7
 import se.citerus.dddsample.repository.CarrierMovementRepository;
7 8
 import se.citerus.dddsample.repository.LocationRepository;
8 9
 import se.citerus.dddsample.service.dto.ItineraryCandidateDTO;
9 10
 import se.citerus.dddsample.service.dto.assembler.ItineraryCandidateDTOAssembler;
10 11
 
11 12
 import java.util.ArrayList;
12
-import java.util.Collections;
13 13
 import java.util.List;
14
-import java.util.Random;
15 14
 
16 15
 /**
17
- * Simple routing service implementation that randomly creates a number
18
- * of different itineraries.
16
+ * Our end of the routing service.
17
+ *
19 18
  */
20 19
 public class RoutingServiceImpl implements RoutingService {
21 20
 
21
+  private GraphTraversalService graphTraversalService;
22 22
   private LocationRepository locationRepository;
23
-  private CargoRepository cargoRepository;
24 23
   private CarrierMovementRepository carrierMovementRepository;
25 24
 
26
-  Random random = new Random();
27
-
28 25
   @Transactional(readOnly = true)
29
-  public List<ItineraryCandidateDTO> calculatePossibleRoutes(TrackingId trackingId, RouteSpecification routeSpecification) {
30
-    final Cargo cargo = cargoRepository.find(trackingId);
31
-    if (cargo == null) {
32
-      return Collections.emptyList();
33
-    }
34
-
35
-    List<Location> allLocations = locationRepository.findAll();
36
-
37
-    allLocations.remove(cargo.origin());
38
-    allLocations.remove(cargo.destination());
39
-
40
-    final int candidateCount = getRandomNumberOfCandidates();
41
-    final List<ItineraryCandidateDTO> candidates = new ArrayList<ItineraryCandidateDTO>(candidateCount);
42
-    final ItineraryCandidateDTOAssembler assembler = new ItineraryCandidateDTOAssembler();
43
-
44
-    for (int i = 0; i < candidateCount; i++) {
45
-      allLocations = getRandomChunkOfLocations(allLocations);
46
-      final List<Leg> legs = new ArrayList<Leg>(allLocations.size() - 1);
47
-      final Location firstLegTo = allLocations.get(0);
48
-
49
-      final CarrierMovement cm1 = carrierMovementRepository.find(new CarrierMovementId("CAR_002"));
50
-      legs.add(new Leg(cm1, cargo.origin(), firstLegTo));
51
-
52
-      for (int j = 0; j < allLocations.size() - 1; j++) {
53
-        legs.add(new Leg(getRandomCarrierMovement(), allLocations.get(j), allLocations.get(j + 1)));
54
-      }
55
-
56
-      final Location lastLegFrom = allLocations.get(allLocations.size() - 1);
57
-      legs.add(new Leg(getRandomCarrierMovement(), lastLegFrom, cargo.destination()));
58
-
59
-      final Itinerary itinerary = new Itinerary(legs);
60
-      candidates.add(assembler.toDTO(itinerary));
26
+  public List<Itinerary> requestPossibleRoutes(RouteSpecification routeSpecification) {
27
+    final Location origin = routeSpecification.origin();
28
+    final Location destination = routeSpecification.destination();
29
+
30
+    final List<ItineraryCandidateDTO> candidateDTOs = graphTraversalService.performHeavyCalculations(
31
+      origin.unLocode().idString(),
32
+      destination.unLocode().idString()
33
+    );
34
+    
35
+    final List<Itinerary> itineraries = new ArrayList<Itinerary>(candidateDTOs.size());
36
+
37
+    for (ItineraryCandidateDTO candidateDTO : candidateDTOs) {
38
+      final Itinerary itinerary = new ItineraryCandidateDTOAssembler().fromDTO(
39
+        candidateDTO, carrierMovementRepository, locationRepository
40
+      );
41
+      itineraries.add(itinerary);
61 42
     }
62 43
 
63
-    return candidates;
64
-  }
65
-
66
-  private List<Location> getRandomChunkOfLocations(List<Location> allLocations) {
67
-    Collections.shuffle(allLocations);
68
-    final int total = allLocations.size();
69
-    final int chunk = total > 4 ? (total - 4) + random.nextInt(5) : total;
70
-    return allLocations.subList(0, chunk);
44
+    return itineraries;
71 45
   }
72 46
 
73
-  private int getRandomNumberOfCandidates() {
74
-    return 1 + random.nextInt(4);
75
-  }
76
-
77
-  private CarrierMovement getRandomCarrierMovement() {
78
-    final CarrierMovementId id = new CarrierMovementId("CAR_00" + (random.nextInt(9) + 1));
79
-    return carrierMovementRepository.find(id);
47
+  public void setGraphTraversalService(GraphTraversalService graphTraversalService) {
48
+    this.graphTraversalService = graphTraversalService;
80 49
   }
81 50
 
82 51
   public void setLocationRepository(LocationRepository locationRepository) {
83 52
     this.locationRepository = locationRepository;
84 53
   }
85 54
 
86
-  public void setCargoRepository(CargoRepository cargoRepository) {
87
-    this.cargoRepository = cargoRepository;
88
-  }
89
-
90 55
   public void setCarrierMovementRepository(CarrierMovementRepository carrierMovementRepository) {
91 56
     this.carrierMovementRepository = carrierMovementRepository;
92 57
   }

+ 49
- 13
dddsample/src/main/java/se/citerus/dddsample/web/CargoAdminController.java 파일 보기

@@ -1,32 +1,39 @@
1 1
 package se.citerus.dddsample.web;
2 2
 
3 3
 import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
4
-import se.citerus.dddsample.domain.TrackingId;
5
-import se.citerus.dddsample.domain.UnLocode;
4
+import se.citerus.dddsample.domain.*;
5
+import se.citerus.dddsample.repository.CarrierMovementRepository;
6
+import se.citerus.dddsample.repository.LocationRepository;
6 7
 import se.citerus.dddsample.service.CargoService;
7 8
 import se.citerus.dddsample.service.RoutingService;
9
+import se.citerus.dddsample.service.dto.CargoRoutingDTO;
8 10
 import se.citerus.dddsample.service.dto.ItineraryCandidateDTO;
9 11
 import se.citerus.dddsample.service.dto.LegDTO;
12
+import se.citerus.dddsample.service.dto.assembler.CargoRoutingDTOAssembler;
13
+import se.citerus.dddsample.service.dto.assembler.ItineraryCandidateDTOAssembler;
10 14
 import se.citerus.dddsample.web.command.RegistrationCommand;
11 15
 
12 16
 import javax.servlet.http.HttpServletRequest;
13 17
 import javax.servlet.http.HttpServletResponse;
14
-import java.util.ArrayList;
15
-import java.util.HashMap;
16
-import java.util.List;
17
-import java.util.Map;
18
+import java.util.*;
18 19
 
19 20
 /**
20 21
  * Handles cargo routing and administration.
22
+ *
21 23
  */
22 24
 public final class CargoAdminController extends MultiActionController {
23 25
 
24 26
   private CargoService cargoService;
25 27
   private RoutingService routingService;
28
+  private LocationRepository locationRepository;
29
+  private CarrierMovementRepository carrierMovementRepository;
30
+
31
+  // DTO conversion is pushed out to above the service layer for the time being,
32
+  // pending a dedicated DTO remoting layer  
26 33
 
27 34
   public Map registrationForm(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
28 35
     final Map<String, Object> map = new HashMap<String, Object>();
29
-    final List<UnLocode> unLocodes = cargoService.shippingLocations();
36
+    final List<UnLocode> unLocodes = cargoService.listShippingLocations();
30 37
     final List<String> unLocodeStrings = new ArrayList<String>();
31 38
 
32 39
     for (UnLocode unLocode : unLocodes) {
@@ -40,7 +47,7 @@ public final class CargoAdminController extends MultiActionController {
40 47
   public void register(final HttpServletRequest request, final HttpServletResponse response,
41 48
                        final RegistrationCommand command) throws Exception {
42 49
 
43
-    final TrackingId trackingId = cargoService.registerNew(
50
+    final TrackingId trackingId = cargoService.registerNewCargo(
44 51
       new UnLocode(command.getOriginUnlocode()),
45 52
       new UnLocode(command.getDestinationUnlocode())
46 53
     );
@@ -49,23 +56,43 @@ public final class CargoAdminController extends MultiActionController {
49 56
 
50 57
   public Map list(HttpServletRequest request, HttpServletResponse response) {
51 58
     final Map<String, Object> map = new HashMap<String, Object>();
52
-    map.put("cargoList", cargoService.loadAllForRouting());
59
+    final List<Cargo> allCargos = cargoService.listAllCargos();
60
+
61
+    final CargoRoutingDTOAssembler assembler = new CargoRoutingDTOAssembler();
62
+    final List<CargoRoutingDTO> dtoList = new ArrayList<CargoRoutingDTO>(allCargos.size());
63
+
64
+    for (Cargo cargo : allCargos) {
65
+      dtoList.add(assembler.toDTO(cargo));
66
+    }
67
+
68
+    map.put("cargoList", dtoList);
53 69
     return map;
54 70
   }
55 71
 
56 72
   public Map show(final HttpServletRequest request, final HttpServletResponse response) {
57 73
     final Map<String, Object> map = new HashMap<String, Object>();
58 74
     final TrackingId trackingId = new TrackingId(request.getParameter("trackingId"));
59
-    map.put("cargo", cargoService.loadForRouting(trackingId));
75
+    final Cargo cargo = cargoService.loadCargoForRouting(trackingId);
76
+    final CargoRoutingDTO dto = new CargoRoutingDTOAssembler().toDTO(cargo);
77
+    map.put("cargo", dto);
60 78
     return map;
61 79
   }
62 80
 
63 81
   public Map selectItinerary(final HttpServletRequest request, final HttpServletResponse response) {
64 82
     final Map<String, Object> map = new HashMap<String, Object>();
65 83
     final TrackingId trackingId = new TrackingId(request.getParameter("trackingId"));
66
-    final List<ItineraryCandidateDTO> itineraries = routingService.calculatePossibleRoutes(trackingId, null);
67 84
 
68
-    map.put("itineraryCandidates", itineraries);
85
+    final Cargo cargo = cargoService.loadCargoForRouting(trackingId);
86
+    final RouteSpecification routeSpecification = RouteSpecification.forCargo(cargo, new Date());
87
+    final List<Itinerary> itineraries = routingService.requestPossibleRoutes(routeSpecification);
88
+
89
+    final List<ItineraryCandidateDTO> itineraryCandidates = new ArrayList<ItineraryCandidateDTO>(itineraries.size());
90
+    final ItineraryCandidateDTOAssembler dtoAssembler = new ItineraryCandidateDTOAssembler();
91
+    for (Itinerary itinerary : itineraries) {
92
+      itineraryCandidates.add(dtoAssembler.toDTO(itinerary));
93
+    }
94
+
95
+    map.put("itineraryCandidates", itineraryCandidates);
69 96
     map.put("trackingId", trackingId.idString());
70 97
     return map;
71 98
   }
@@ -85,7 +112,9 @@ public final class CargoAdminController extends MultiActionController {
85 112
     }
86 113
 
87 114
     final ItineraryCandidateDTO selectedItinerary = new ItineraryCandidateDTO(legDTOs);
88
-    cargoService.assignItinerary(trackingId, selectedItinerary);
115
+    final Itinerary itinerary = new ItineraryCandidateDTOAssembler().fromDTO(selectedItinerary, carrierMovementRepository, locationRepository);
116
+
117
+    cargoService.assignCargoToRoute(trackingId, itinerary);
89 118
 
90 119
     response.sendRedirect("list.html");
91 120
   }
@@ -98,4 +127,11 @@ public final class CargoAdminController extends MultiActionController {
98 127
     this.routingService = routingService;
99 128
   }
100 129
 
130
+  public void setLocationRepository(LocationRepository locationRepository) {
131
+    this.locationRepository = locationRepository;
132
+  }
133
+
134
+  public void setCarrierMovementRepository(CarrierMovementRepository carrierMovementRepository) {
135
+    this.carrierMovementRepository = carrierMovementRepository;
136
+  }
101 137
 }

+ 13
- 0
dddsample/src/main/resources/context-routing.xml 파일 보기

@@ -0,0 +1,13 @@
1
+<?xml version="1.0"?>
2
+
3
+<beans xmlns="http://www.springframework.org/schema/beans"
4
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
6
+
7
+  <!-- TODO start an RMI server on boot and wire this across the network -->
8
+  <bean id="graphTraversalService" class="se.citerus.dddsample.service.GraphTraversalService">
9
+    <property name="carrierMovementRepository" ref="carrierMovementRepository"/>
10
+    <property name="locationRepository" ref="locationRepository"/>
11
+  </bean>
12
+
13
+</beans>

+ 1
- 2
dddsample/src/main/resources/context-service.xml 파일 보기

@@ -12,7 +12,6 @@
12 12
   <bean id="cargoService" class="se.citerus.dddsample.service.CargoServiceImpl">
13 13
     <property name="cargoRepository" ref="cargoRepository"/>
14 14
     <property name="locationRepository" ref="locationRepository"/>
15
-    <property name="carrierMovementRepository" ref="carrierMovementRepository"/>
16 15
   </bean>
17 16
 
18 17
   <bean id="handlingEventService" class="se.citerus.dddsample.service.HandlingEventServiceImpl">
@@ -25,8 +24,8 @@
25 24
 
26 25
   <bean id="routingService" class="se.citerus.dddsample.service.RoutingServiceImpl">
27 26
     <property name="locationRepository" ref="locationRepository"/>
28
-    <property name="cargoRepository" ref="cargoRepository"/>
29 27
     <property name="carrierMovementRepository" ref="carrierMovementRepository"/>
28
+    <property name="graphTraversalService" ref="graphTraversalService"/>
30 29
   </bean>
31 30
 
32 31
 </beans>

+ 5
- 1
dddsample/src/main/resources/context-web.xml 파일 보기

@@ -33,5 +33,9 @@
33 33
   <bean id="localeResolver" class="org.springframework.web.servlet.i18n.FixedLocaleResolver">
34 34
     <property name="defaultLocale" value="en"/>
35 35
   </bean>
36
-  
36
+
37
+  <bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
38
+    <property name="sessionFactory" ref="sessionFactory"/>  
39
+  </bean>
40
+
37 41
 </beans>

+ 10
- 0
dddsample/src/main/webapp/WEB-INF/dispatch-servlet.xml 파일 보기

@@ -3,6 +3,14 @@
3 3
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 4
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
5 5
 
6
+  <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
7
+    <property name="interceptors">
8
+      <list>
9
+        <ref bean="openSessionInViewInterceptor"/>
10
+      </list>
11
+    </property>
12
+  </bean>
13
+
6 14
   <bean name="/cargo/track.html" class="se.citerus.dddsample.web.CargoTrackingController">
7 15
     <property name="sessionForm" value="true"/>
8 16
     <property name="commandName" value="trackCommand"/>
@@ -17,6 +25,8 @@
17 25
   <bean name="/admin/*" class="se.citerus.dddsample.web.CargoAdminController">
18 26
     <property name="cargoService" ref="cargoService"/>
19 27
     <property name="routingService" ref="routingService"/>
28
+    <property name="carrierMovementRepository" ref="carrierMovementRepository"/>
29
+    <property name="locationRepository" ref="locationRepository"/>
20 30
   </bean>
21 31
 
22 32
 </beans>

+ 1
- 0
dddsample/src/main/webapp/WEB-INF/web.xml 파일 보기

@@ -28,6 +28,7 @@
28 28
       classpath:context-messaging-jms.xml
29 29
 
30 30
       classpath:context-service.xml
31
+      classpath:context-routing.xml
31 32
       classpath:context-remote.xml
32 33
       classpath:context-web.xml
33 34
     </param-value>

+ 2
- 2
dddsample/src/test/java/se/citerus/dddsample/service/CargoServiceTest.java 파일 보기

@@ -103,13 +103,13 @@ public class CargoServiceTest extends TestCase {
103 103
 
104 104
     replay(cargoRepository, locationRepository);
105 105
     
106
-    TrackingId trackingId = cargoService.registerNew(fromUnlocode, toUnlocode);
106
+    TrackingId trackingId = cargoService.registerNewCargo(fromUnlocode, toUnlocode);
107 107
     assertEquals(expectedTrackingId, trackingId);
108 108
   }
109 109
 
110 110
   public void testRegisterNewNullArguments() {
111 111
     try {
112
-      cargoService.registerNew(null, null);
112
+      cargoService.registerNewCargo(null, null);
113 113
       fail("Null arguments should not be allowed");
114 114
     } catch (IllegalArgumentException expected) {}
115 115
   }

+ 7
- 5
dddsample/src/test/java/se/citerus/dddsample/service/RoutingScenarioTest.java 파일 보기

@@ -3,7 +3,6 @@ package se.citerus.dddsample.service;
3 3
 import junit.framework.TestCase;
4 4
 import se.citerus.dddsample.domain.*;
5 5
 import se.citerus.dddsample.repository.CargoRepository;
6
-import se.citerus.dddsample.service.dto.ItineraryCandidateDTO;
7 6
 
8 7
 import java.util.Date;
9 8
 import java.util.List;
@@ -19,14 +18,17 @@ public class RoutingScenarioTest extends TestCase {
19 18
 
20 19
     TrackingId trackingId = new TrackingId("XYZ123");
21 20
     Cargo cargo = cargoRepository.find(trackingId);
22
-    RouteSpecification routeSpecification = null;
21
+
22
+    Date twoWeeksFromNow = new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 14);
23
+
24
+    RouteSpecification routeSpecification = RouteSpecification.forCargo(cargo, twoWeeksFromNow);
23 25
 
24 26
     /*
25 27
       The routing service calculates a number of possible routes that
26 28
       satisfy the given specification (must arrive in three days, must not
27 29
       cost more than $10,000 etc).
28 30
      */
29
-    List<ItineraryCandidateDTO> itineraryCandidates = routingService.calculatePossibleRoutes(trackingId, routeSpecification);
31
+    List<Itinerary> itineraryCandidates = routingService.requestPossibleRoutes(routeSpecification);
30 32
 
31 33
     /*
32 34
       Someone, or something, selects the most appropriate itinerary and
@@ -64,8 +66,8 @@ public class RoutingScenarioTest extends TestCase {
64 66
   }
65 67
 
66 68
 
67
-  private Itinerary stubbedItinerarySelection(List<ItineraryCandidateDTO> itineraryCandidates) {
68
-    return null;
69
+  private Itinerary stubbedItinerarySelection(List<Itinerary> itineraryCandidates) {
70
+    return itineraryCandidates.get(0);
69 71
   }
70 72
 
71 73
 }

+ 20
- 24
dddsample/src/test/java/se/citerus/dddsample/service/RoutingServiceTest.java 파일 보기

@@ -2,70 +2,66 @@ package se.citerus.dddsample.service;
2 2
 
3 3
 import junit.framework.TestCase;
4 4
 import static org.easymock.EasyMock.*;
5
-import se.citerus.dddsample.domain.Cargo;
6
-import se.citerus.dddsample.domain.CarrierMovement;
7
-import se.citerus.dddsample.domain.CarrierMovementId;
5
+import se.citerus.dddsample.domain.*;
8 6
 import static se.citerus.dddsample.domain.SampleLocations.*;
9
-import se.citerus.dddsample.domain.TrackingId;
10
-import se.citerus.dddsample.repository.CargoRepository;
11 7
 import se.citerus.dddsample.repository.CarrierMovementRepository;
12 8
 import se.citerus.dddsample.repository.LocationRepository;
13
-import se.citerus.dddsample.service.dto.ItineraryCandidateDTO;
14
-import se.citerus.dddsample.service.dto.LegDTO;
9
+import se.citerus.dddsample.repository.LocationRepositoryInMem;
15 10
 
11
+import java.util.Date;
16 12
 import java.util.List;
17 13
 
18 14
 public class RoutingServiceTest extends TestCase {
19 15
 
20 16
   private RoutingServiceImpl routingService;
21
-  private LocationRepository locationRepository;
22
-  private CargoRepository cargoRepository;
23 17
   private CarrierMovementRepository carrierMovementRepository;
24 18
 
25 19
   protected void setUp() throws Exception {
26 20
     routingService = new RoutingServiceImpl();
27
-    locationRepository = createMock(LocationRepository.class);
21
+    LocationRepository locationRepository = new LocationRepositoryInMem();
28 22
     routingService.setLocationRepository(locationRepository);
29
-    cargoRepository = createMock(CargoRepository.class);
30
-    routingService.setCargoRepository(cargoRepository);
23
+
31 24
     carrierMovementRepository = createMock(CarrierMovementRepository.class);
32 25
     routingService.setCarrierMovementRepository(carrierMovementRepository);
26
+
27
+    GraphTraversalService graphTraversalService = new GraphTraversalService();
28
+    graphTraversalService.setCarrierMovementRepository(carrierMovementRepository);
29
+    graphTraversalService.setLocationRepository(locationRepository);
30
+    routingService.setGraphTraversalService(graphTraversalService);
33 31
   }
34 32
 
35 33
   public void testCalculatePossibleRoutes() {
36 34
     TrackingId trackingId = new TrackingId("ABC");
37 35
     Cargo cargo = new Cargo(trackingId, HONGKONG, HELSINKI);
36
+    RouteSpecification routeSpecification = RouteSpecification.forCargo(cargo, new Date());
38 37
 
39
-    expect(locationRepository.findAll()).andStubReturn(getAll());
40
-    expect(cargoRepository.find(isA(TrackingId.class))).andReturn(cargo);
41 38
     expect(carrierMovementRepository.find(isA(CarrierMovementId.class))).
42 39
       andStubReturn(new CarrierMovement(new CarrierMovementId("CM"), CHICAGO, HAMBURG));
43 40
     
44
-    replay(locationRepository, cargoRepository, carrierMovementRepository);
41
+    replay(carrierMovementRepository);
45 42
 
46
-    List<ItineraryCandidateDTO> candidates = routingService.calculatePossibleRoutes(trackingId, null);
43
+    List<Itinerary> candidates = routingService.requestPossibleRoutes(routeSpecification);
47 44
     assertNotNull(candidates);
48 45
     
49
-    for (ItineraryCandidateDTO itinerary : candidates) {
50
-      List<LegDTO> legs = itinerary.getLegs();
46
+    for (Itinerary itinerary : candidates) {
47
+      List<Leg> legs = itinerary.legs();
51 48
       assertNotNull(legs);
52 49
       assertFalse(legs.isEmpty());
53 50
 
54 51
       // Cargo origin and start of first leg should match
55
-      String firstLegStart = legs.get(0).getFrom();
56
-      assertEquals(cargo.origin().unLocode().idString(), firstLegStart);
52
+      assertEquals(cargo.origin(), legs.get(0).from());
57 53
 
58 54
       // Cargo final destination and last leg stop should match
59
-      String lastLegStop = legs.get(legs.size() - 1).getTo();
60
-      assertEquals(cargo.destination().unLocode().idString(), lastLegStop);
55
+      Location lastLegStop = legs.get(legs.size() - 1).to();
56
+      assertEquals(cargo.destination(), lastLegStop);
61 57
 
62 58
       for (int i = 0; i < legs.size() - 1; i++) {
63 59
         // Assert that all legs are conencted
64
-        assertEquals(legs.get(i).getTo(), legs.get(i + 1).getFrom());
60
+        assertEquals(legs.get(i).to(), legs.get(i + 1).from());
65 61
       }
66 62
     }
67 63
 
68
-    verify(locationRepository, cargoRepository, carrierMovementRepository);
64
+    verify(carrierMovementRepository);
69 65
   }
70 66
 
71 67
 }

+ 5
- 7
dddsample/src/test/java/se/citerus/dddsample/web/CargoTrackingControllerTest.java 파일 보기

@@ -13,9 +13,7 @@ import se.citerus.dddsample.domain.*;
13 13
 import static se.citerus.dddsample.domain.SampleLocations.HONGKONG;
14 14
 import static se.citerus.dddsample.domain.SampleLocations.TOKYO;
15 15
 import se.citerus.dddsample.service.CargoService;
16
-import se.citerus.dddsample.service.dto.CargoRoutingDTO;
17 16
 import se.citerus.dddsample.service.dto.CargoTrackingDTO;
18
-import se.citerus.dddsample.service.dto.ItineraryCandidateDTO;
19 17
 import se.citerus.dddsample.web.command.TrackCommand;
20 18
 
21 19
 import java.util.Date;
@@ -116,11 +114,11 @@ public class CargoTrackingControllerTest extends TestCase {
116 114
   }
117 115
 
118 116
   private class EmptyStubCargoService implements CargoService {
119
-    public TrackingId registerNew(UnLocode origin, UnLocode destination) {
117
+    public TrackingId registerNewCargo(UnLocode origin, UnLocode destination) {
120 118
       return null;
121 119
     }
122 120
 
123
-    public List<UnLocode> shippingLocations() {
121
+    public List<UnLocode> listShippingLocations() {
124 122
       return null;
125 123
     }
126 124
 
@@ -131,15 +129,15 @@ public class CargoTrackingControllerTest extends TestCase {
131 129
     public void notify(TrackingId trackingId) {
132 130
     }
133 131
 
134
-    public CargoRoutingDTO loadForRouting(TrackingId trackingId) {
132
+    public Cargo loadCargoForRouting(TrackingId trackingId) {
135 133
       return null;
136 134
     }
137 135
 
138
-    public List<CargoRoutingDTO> loadAllForRouting() {
136
+    public List<Cargo> listAllCargos() {
139 137
       return null;
140 138
     }
141 139
 
142
-    public void assignItinerary(TrackingId trackingId, ItineraryCandidateDTO itinerary) {
140
+    public void assignCargoToRoute(TrackingId trackingId, Itinerary itinerary) {
143 141
     }
144 142
 
145 143
   }