blob: 796c0ebd236abc0df025c66b7eeb04dcb4173536 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.qi4j.samples.cargo.app1.model.cargo;
import org.qi4j.api.common.Optional;
import org.qi4j.api.entity.EntityComposite;
import org.qi4j.api.entity.association.Association;
import org.qi4j.api.injection.scope.Service;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.injection.scope.This;
import org.qi4j.api.mixin.Mixins;
import org.qi4j.api.property.Property;
import org.qi4j.api.value.ValueBuilder;
import org.qi4j.api.value.ValueBuilderFactory;
import org.qi4j.samples.cargo.app1.model.handling.HandlingHistory;
import org.qi4j.samples.cargo.app1.model.location.Location;
import org.qi4j.samples.cargo.app1.system.factories.DeliveryFactory;
/**
* A Cargo. This is the central class in the domain model,
* and it is the root of the Cargo-Itinerary-Leg-Delivery-RouteSpecification aggregate.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* It may also happen that a cargo is accidentally misrouted, which should notify the proper
* personnel and also trigger a re-routing procedure.
* </p>
* <p>
* 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.
* </p>
* <p>
* The delivery can also be affected by routing changes, i.e. when a 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.
* </p>
* <p>
* The life cycle of a cargo ends when the cargo is claimed by the customer.
* </p>
* <p>
* The cargo aggregate, and the entre 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.
* </p>
*/
@Mixins( Cargo.CargoMixin.class )
public interface Cargo
extends EntityComposite
{
TrackingId trackingId();
Location origin();
RouteSpecification routeSpecification();
Itinerary itinerary();
Delivery delivery();
/**
* Specifies a new route for this cargo.
*
* @param routeSpecification route specification.
*/
void specifyNewRoute( final RouteSpecification routeSpecification );
/**
* Attach a new itinerary to this cargo.
*
* @param itinerary an itinerary. May not be null.
*/
void assignToRoute( final Itinerary itinerary );
/**
* Updates all aspects of the cargo aggregate status
* based on the current route specification, itinerary and handling of the cargo.
* <p>
* When either of those three changes, i.e. when a new route is specified for the cargo,
* the cargo is assigned to a route or when the cargo is handled, the status must be
* re-calculated.
* </p>
* <p>
* {@link RouteSpecification} and {@link Itinerary} are both inside the Cargo
* aggregate, so changes to them cause the status to be updated <b>synchronously</b>,
* but changes to the delivery history (when a cargo is handled) cause the status update
* to happen <b>asynchronously</b> since {@link org.qi4j.samples.cargo.app1.model.handling.HandlingEvent}
* is in a different aggregate.
* </p>
*
* @param handlingHistory handling history
*/
void deriveDeliveryProgress( final HandlingHistory handlingHistory );
interface State
{
Association<Location> origin();
Property<RouteSpecification> routeSpecification();
@Optional
Property<Itinerary> itinerary();
@Optional
Property<Delivery> delivery();
}
public abstract class CargoMixin
implements Cargo
{
public static final String TRACKING_ID_PREFIX = "TrackingId:";
@This
private State state;
@Service
private DeliveryFactory deliveryFactory;
@Structure
private ValueBuilderFactory vbf;
private TrackingId trackingId;
public TrackingId trackingId()
{
if( trackingId == null )
{
final String identity = identity().get();
String id = identity.substring( TRACKING_ID_PREFIX.length() );
final ValueBuilder<TrackingId> builder = vbf.newValueBuilder( TrackingId.class );
builder.prototype().id().set( id );
trackingId = builder.newInstance();
}
return trackingId;
}
public Location origin()
{
return state.origin().get();
}
public RouteSpecification routeSpecification()
{
return state.routeSpecification().get();
}
public Itinerary itinerary()
{
return state.itinerary().get();
}
public Delivery delivery()
{
return state.delivery().get();
}
public void specifyNewRoute( final RouteSpecification routeSpecification )
{
state.routeSpecification().set( routeSpecification );
// Handling consistency within the Cargo aggregate synchronously
state.delivery().set( delivery().updateOnRouting( routeSpecification(), itinerary() ) );
}
public void assignToRoute( final Itinerary itinerary )
{
state.itinerary().set( itinerary );
// Handling consistency within the Cargo aggregate synchronously
state.delivery().set( delivery().updateOnRouting( routeSpecification(), itinerary() ) );
}
/**
* Updates all aspects of the cargo aggregate status
* based on the current route specification, itinerary and handling of the cargo.
* <p>
* When either of those three changes, i.e. when a new route is specified for the cargo,
* the cargo is assigned to a route or when the cargo is handled, the status must be
* re-calculated.
* </p>
* <p>
* {@link RouteSpecification} and {@link Itinerary} are both inside the Cargo
* aggregate, so changes to them cause the status to be updated <b>synchronously</b>,
* but changes to the delivery history (when a cargo is handled) cause the status update
* to happen <b>asynchronously</b> since
* {@link org.qi4j.samples.cargo.app1.model.handling.HandlingEvent} is in a different aggregate.
* </p>
* @param handlingHistory handling history
*/
public void deriveDeliveryProgress( final HandlingHistory handlingHistory )
{
// TODO filter events on cargo (must be same as this cargo)
// Delivery is a value object, so we can simply discard the old one
// and replace it with a new
state.delivery().set( deliveryFactory.derivedFrom( routeSpecification(), itinerary(), handlingHistory ) );
}
}
}