Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,078 changes: 551 additions & 527 deletions API.html

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cursor.md
Original file line number Diff line number Diff line change
Expand Up @@ -6060,6 +6060,7 @@ redocly build-docs openapi.yaml --theme.openapi.pathInMiddlePanel -o API.html
The documentation has been updated with the property descriptions for the `Location` type. Would you like me to make any other improvements to the OpenAPI specification?


>>>>>>> dev
## Generate code

* Generate code
Expand Down
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
77 changes: 41 additions & 36 deletions src/main/java/se/citerus/dddsample/domain/model/cargo/Cargo.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,47 @@
import java.util.List;
import java.util.Objects;

/**
* A Cargo. This is the central class in the domain model,
* and it is the root of the Cargo-Itinerary-Leg-Delivery-RouteSpecification aggregate.
*
* A cargo is identified by a unique tracking id, and it always has an origin
* and a route specification. The life cycle of a cargo begins with the booking procedure,
* when the tracking id is assigned. During a (short) period of time, between booking
* and initial routing, the cargo has no itinerary.
*
* The booking clerk requests a list of possible routes, matching the route specification,
* and assigns the cargo to one route. The route to which a cargo is assigned is described
* by an itinerary.
*
* A cargo can be re-routed during transport, on demand of the customer, in which case
* a new route is specified for the cargo and a new route is requested. The old itinerary,
* being a value object, is discarded and a new one is attached.
*
* It may also happen that a cargo is accidentally misrouted, which should notify the proper
* personnel and also trigger a re-routing procedure.
*
* When a cargo is handled, the status of the delivery changes. Everything about the delivery
* of the cargo is contained in the Delivery value object, which is replaced whenever a cargo
* is handled by an asynchronous event triggered by the registration of the handling event.
*
* The delivery can also be affected by routing changes, i.e. when the route specification
* changes, or the cargo is assigned to a new route. In that case, the delivery update is performed
* synchronously within the cargo aggregate.
*
* The life cycle of a cargo ends when the cargo is claimed by the customer.
*
* The cargo aggregate, and the entire domain model, is built to solve the problem
* of booking and tracking cargo. All important business rules for determining whether
* or not a cargo is misdirected, what the current status of the cargo is (on board carrier,
* in port etc), are captured in this aggregate.
*
*/
/// A Cargo. This is the central class in the domain model,
/// and it is the root of the Cargo-Itinerary-Leg-Delivery-RouteSpecification aggregate.
///
/// ## Identity and Life Cycle
///
/// - A cargo is identified by a unique tracking id
/// - It always has an origin and a route specification
/// - The life cycle begins with the booking procedure, when the tracking id is assigned
/// - During a (short) period between booking and initial routing, the cargo has no itinerary
/// - The life cycle ends when the cargo is claimed by the customer
///
/// ## Routing
///
/// - The booking clerk requests a list of possible routes, matching the route specification,
/// and assigns the cargo to one route
/// - The route to which a cargo is assigned is described by an itinerary
/// - A cargo can be re-routed during transport, on demand of the customer
/// - When re-routing occurs, a new route is specified and requested
/// - The old itinerary (being a value object) is discarded and a new one is attached
/// - If a cargo is accidentally misrouted, it should notify the proper personnel
/// and trigger a re-routing procedure
///
/// ## Delivery Status
///
/// - When a cargo is handled, the status of the delivery changes
/// - Everything about the delivery is contained in the Delivery value object
/// - The Delivery is replaced whenever a cargo is handled by an asynchronous event
/// triggered by the registration of the handling event
/// - The delivery can also be affected by routing changes (when the route specification
/// changes, or the cargo is assigned to a new route)
/// - In that case, the delivery update is performed synchronously within the cargo aggregate
///
/// ## Business Rules
///
/// The cargo aggregate, and the entire domain model, is built to solve the problem
/// of booking and tracking cargo. All important business rules for determining:
///
/// - Whether or not a cargo is misdirected
/// - What the current status of the cargo is (on board carrier, in port, etc.)
///
/// are captured in this aggregate.
@Entity(name = "Cargo")
@Table(name = "Cargo")
public class Cargo implements DomainEntity<Cargo> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,33 @@
import static se.citerus.dddsample.domain.model.cargo.RoutingStatus.*;
import static se.citerus.dddsample.domain.model.cargo.TransportStatus.*;

/**
* The actual transportation of the cargo, as opposed to
* the customer requirement (RouteSpecification) and the plan (Itinerary).
*
*/
/// The actual transportation status of the cargo, as opposed to
/// the customer requirement ({@link RouteSpecification}) and the plan ({@link Itinerary}).
///
/// ## Purpose
///
/// Delivery represents the current state of cargo transportation, including:
///
/// - Current transport status (on board carrier, in port, claimed, etc.)
/// - Routing status (routed, misrouted, not routed)
/// - Last known location and current voyage
/// - Estimated time of arrival
/// - Next expected handling activity
///
/// ## Updates
///
/// The delivery status is updated:
///
/// - **Synchronously** when routing changes (route specification or itinerary changes)
/// - **Asynchronously** when handling events are registered
///
/// ## Misdirection Rules
///
/// A cargo is considered misdirected if:
///
/// - It is in a location that's not in the itinerary
/// - A cargo with no itinerary cannot be misdirected
/// - A cargo that has received no handling events cannot be misdirected
@Embeddable
public class Delivery implements ValueObject<Delivery> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@

import java.util.Objects;

/**
* A handling activity represents how and where a cargo can be handled,
* and can be used to express predictions about what is expected to
* happen to a cargo in the future.
*
*/
/// A handling activity represents how and where a cargo can be handled.
///
/// ## Purpose
///
/// HandlingActivity is used to express predictions about what is expected
/// to happen to a cargo in the future, based on the current itinerary and
/// handling history.
///
/// ## Components
///
/// A handling activity specifies:
///
/// - **Type** - the expected handling event type (LOAD, UNLOAD, RECEIVE, CLAIM, CUSTOMS)
/// - **Location** - where the activity is expected to occur
/// - **Voyage** - optionally, which voyage is involved (for LOAD/UNLOAD activities)
@Embeddable
public class HandlingActivity implements ValueObject<HandlingActivity> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,25 @@
import java.util.Collections;
import java.util.List;

/**
* An itinerary.
*
*/
/// An itinerary represents the planned route for transporting cargo.
///
/// ## Structure
///
/// An itinerary consists of one or more legs, where each leg represents:
///
/// - A voyage from one location to another
/// - Load and unload locations
/// - Load and unload times
///
/// ## Validation
///
/// The itinerary can validate if a handling event is expected:
///
/// - RECEIVE events must occur at the first leg's load location
/// - LOAD events must match a leg's load location and voyage
/// - UNLOAD events must match a leg's unload location and voyage
/// - CLAIM events must occur at the last leg's unload location
/// - CUSTOMS events are always considered expected
public class Itinerary implements ValueObject<Itinerary> {

private List<Leg> legs = Collections.emptyList();
Expand Down
19 changes: 16 additions & 3 deletions src/main/java/se/citerus/dddsample/domain/model/cargo/Leg.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,22 @@

import java.time.Instant;

/**
* An itinerary consists of one or more legs.
*/
/// A leg represents a single segment of an itinerary.
///
/// ## Components
///
/// Each leg defines:
///
/// - **Voyage** - the carrier service used for this segment
/// - **Load location** - where cargo is loaded onto the carrier
/// - **Unload location** - where cargo is unloaded from the carrier
/// - **Load time** - when cargo is scheduled to be loaded
/// - **Unload time** - when cargo is scheduled to be unloaded
///
/// ## Usage
///
/// An itinerary consists of one or more legs that together form
/// the complete route from origin to destination.
@Entity(name = "Leg")
@Table(name = "Leg")
public class Leg implements ValueObject<Leg> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,23 @@
import java.time.Instant;
import java.util.Objects;

/**
* Route specification. Describes where a cargo origin and destination is,
* and the arrival deadline.
*
*/
/// Route specification describes the customer's requirements for cargo transportation.
///
/// ## Components
///
/// A route specification contains:
///
/// - **Origin** - where the cargo journey begins
/// - **Destination** - where the cargo must arrive
/// - **Arrival deadline** - the latest acceptable arrival time
///
/// ## Validation
///
/// A route specification can validate if an itinerary satisfies the requirements:
///
/// - The itinerary must start at the specified origin
/// - The itinerary must end at the specified destination
/// - The itinerary's final arrival date must be before the arrival deadline
@Embeddable
public class RouteSpecification extends AbstractSpecification<Itinerary> implements ValueObject<RouteSpecification> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

import se.citerus.dddsample.domain.shared.ValueObject;

/**
* Routing status.
*/
/// Routing status indicates whether a cargo has been assigned a route.
///
/// ## Status Values
///
/// - **NOT_ROUTED** - No route has been assigned to the cargo
/// - **ROUTED** - A route has been assigned and satisfies the route specification
/// - **MISROUTED** - A route has been assigned but does not satisfy the route specification
public enum RoutingStatus implements ValueObject<RoutingStatus> {
NOT_ROUTED, ROUTED, MISROUTED;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

import java.util.Objects;

/**
* Uniquely identifies a particular cargo. Automatically generated by the application.
*
*/
/// Uniquely identifies a particular cargo.
///
/// The tracking ID is:
///
/// - Automatically generated by the application
/// - Used to track cargo throughout its journey
/// - The identity of the Cargo entity
public final class TrackingId implements ValueObject<TrackingId> {

private String id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

import se.citerus.dddsample.domain.shared.ValueObject;

/**
* Represents the different transport statuses for a cargo.
*/
/// Represents the different transport statuses for a cargo.
///
/// ## Status Values
///
/// - **NOT_RECEIVED** - Cargo has not yet been received at the origin
/// - **IN_PORT** - Cargo is currently in a port location
/// - **ONBOARD_CARRIER** - Cargo is on board a carrier (voyage)
/// - **CLAIMED** - Cargo has been claimed by the customer
/// - **UNKNOWN** - Transport status cannot be determined
public enum TransportStatus implements ValueObject<TransportStatus> {
NOT_RECEIVED, IN_PORT, ONBOARD_CARRIER, CLAIMED, UNKNOWN;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,28 @@
import java.time.Instant;
import java.util.Objects;

/**
* A HandlingEvent is used to register the event when, for instance,
* a cargo is unloaded from a carrier at some location at a given time.
*
* The HandlingEvent's are sent from different Incident Logging Applications
* some time after the event occurred and contain information about the
* {@link se.citerus.dddsample.domain.model.cargo.TrackingId}, {@link se.citerus.dddsample.domain.model.location.Location}, timestamp of the completion of the event,
* and possibly, if applicable a {@link se.citerus.dddsample.domain.model.voyage.Voyage}.
*
* This class is the only member, and consequently the root, of the HandlingEvent aggregate.
*
* HandlingEvent's could contain information about a {@link Voyage} and if so,
* the event type must be either {@link Type#LOAD} or {@link Type#UNLOAD}.
*
* All other events must be of {@link Type#RECEIVE}, {@link Type#CLAIM} or {@link Type#CUSTOMS}.
*/
/// A HandlingEvent registers when a cargo is handled, for instance,
/// when a cargo is unloaded from a carrier at some location at a given time.
///
/// ## Event Sources
///
/// HandlingEvents are sent from different Incident Logging Applications
/// some time after the event occurred and contain information about:
///
/// - {@link se.citerus.dddsample.domain.model.cargo.TrackingId} - which cargo was handled
/// - {@link se.citerus.dddsample.domain.model.location.Location} - where the event occurred
/// - Completion time - when the event actually happened
/// - Registration time - when the message was received
/// - {@link se.citerus.dddsample.domain.model.voyage.Voyage} - optionally, if applicable
///
/// ## Aggregate Root
///
/// This class is the only member, and consequently the root, of the HandlingEvent aggregate.
///
/// ## Event Types and Voyage Requirements
///
/// - **Events requiring a voyage**: {@link Type#LOAD}, {@link Type#UNLOAD}
/// - **Events prohibiting a voyage**: {@link Type#RECEIVE}, {@link Type#CLAIM}, {@link Type#CUSTOMS}
@Entity(name = "HandlingEvent")
@Table(name = "HandlingEvent")
public final class HandlingEvent implements DomainEvent<HandlingEvent> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,18 @@ public HandlingEventFactory(final CargoRepository cargoRepository,
* @param completionTime when the event was completed, for example finished loading
* @param trackingId cargo tracking id
* @param voyageNumber voyage number
* @param unlocode United Nations Location Code for the location of the event
* @param unLocode United Nations Location Code for the location of the event
* @param type type of event
* @throws UnknownVoyageException if there's no voyage with this number
* @throws UnknownCargoException if there's no cargo with this tracking id
* @throws UnknownLocationException if there's no location with this UN Locode
* @return A handling event.
*/
public HandlingEvent createHandlingEvent(Instant registrationTime, Instant completionTime, TrackingId trackingId, VoyageNumber voyageNumber, UnLocode unlocode, HandlingEvent.Type type)
public HandlingEvent createHandlingEvent(Instant registrationTime, Instant completionTime, TrackingId trackingId, VoyageNumber voyageNumber, UnLocode unLocode, HandlingEvent.Type type)
throws CannotCreateHandlingEventException {
final Cargo cargo = findCargo(trackingId);
final Voyage voyage = findVoyage(voyageNumber);
final Location location = findLocation(unlocode);
final Location location = findLocation(unLocode);

try {
if (voyage == null) {
Expand Down Expand Up @@ -78,10 +78,10 @@ private Voyage findVoyage(VoyageNumber voyageNumber) throws UnknownVoyageExcepti
return voyage;
}

private Location findLocation(final UnLocode unlocode) throws UnknownLocationException {
final Location location = locationRepository.find(unlocode);
private Location findLocation(final UnLocode unLocode) throws UnknownLocationException {
final Location location = locationRepository.find(unLocode);
if (location == null) {
throw new UnknownLocationException(unlocode);
throw new UnknownLocationException(unLocode);
}

return location;
Expand Down
Loading
Loading