Moved to google.golang.org/genproto/googleapis/api/annotations

Fixes #52
This commit is contained in:
Valerio Gheri
2017-03-31 18:01:58 +02:00
parent 024c5a4e4e
commit c40779224f
2037 changed files with 831329 additions and 1854 deletions

View File

@@ -0,0 +1,137 @@
// Package cargo contains the heart of the domain model.
package cargo
import (
"errors"
"strings"
"time"
"github.com/pborman/uuid"
"github.com/go-kit/kit/examples/shipping/location"
)
// TrackingID uniquely identifies a particular cargo.
type TrackingID string
// Cargo is the central class in the domain model.
type Cargo struct {
TrackingID TrackingID
Origin location.UNLocode
RouteSpecification RouteSpecification
Itinerary Itinerary
Delivery Delivery
}
// SpecifyNewRoute specifies a new route for this cargo.
func (c *Cargo) SpecifyNewRoute(rs RouteSpecification) {
c.RouteSpecification = rs
c.Delivery = c.Delivery.UpdateOnRouting(c.RouteSpecification, c.Itinerary)
}
// AssignToRoute attaches a new itinerary to this cargo.
func (c *Cargo) AssignToRoute(itinerary Itinerary) {
c.Itinerary = itinerary
c.Delivery = c.Delivery.UpdateOnRouting(c.RouteSpecification, c.Itinerary)
}
// DeriveDeliveryProgress updates all aspects of the cargo aggregate status
// based on the current route specification, itinerary and handling of the cargo.
func (c *Cargo) DeriveDeliveryProgress(history HandlingHistory) {
c.Delivery = DeriveDeliveryFrom(c.RouteSpecification, c.Itinerary, history)
}
// New creates a new, unrouted cargo.
func New(id TrackingID, rs RouteSpecification) *Cargo {
itinerary := Itinerary{}
history := HandlingHistory{make([]HandlingEvent, 0)}
return &Cargo{
TrackingID: id,
Origin: rs.Origin,
RouteSpecification: rs,
Delivery: DeriveDeliveryFrom(rs, itinerary, history),
}
}
// Repository provides access a cargo store.
type Repository interface {
Store(cargo *Cargo) error
Find(id TrackingID) (*Cargo, error)
FindAll() []*Cargo
}
// ErrUnknown is used when a cargo could not be found.
var ErrUnknown = errors.New("unknown cargo")
// NextTrackingID generates a new tracking ID.
// TODO: Move to infrastructure(?)
func NextTrackingID() TrackingID {
return TrackingID(strings.Split(strings.ToUpper(uuid.New()), "-")[0])
}
// RouteSpecification Contains information about a route: its origin,
// destination and arrival deadline.
type RouteSpecification struct {
Origin location.UNLocode
Destination location.UNLocode
ArrivalDeadline time.Time
}
// IsSatisfiedBy checks whether provided itinerary satisfies this
// specification.
func (s RouteSpecification) IsSatisfiedBy(itinerary Itinerary) bool {
return itinerary.Legs != nil &&
s.Origin == itinerary.InitialDepartureLocation() &&
s.Destination == itinerary.FinalArrivalLocation()
}
// RoutingStatus describes status of cargo routing.
type RoutingStatus int
// Valid routing statuses.
const (
NotRouted RoutingStatus = iota
Misrouted
Routed
)
func (s RoutingStatus) String() string {
switch s {
case NotRouted:
return "Not routed"
case Misrouted:
return "Misrouted"
case Routed:
return "Routed"
}
return ""
}
// TransportStatus describes status of cargo transportation.
type TransportStatus int
// Valid transport statuses.
const (
NotReceived TransportStatus = iota
InPort
OnboardCarrier
Claimed
Unknown
)
func (s TransportStatus) String() string {
switch s {
case NotReceived:
return "Not received"
case InPort:
return "In port"
case OnboardCarrier:
return "Onboard carrier"
case Claimed:
return "Claimed"
case Unknown:
return "Unknown"
}
return ""
}

View File

@@ -0,0 +1,174 @@
package cargo
import (
"time"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
// Delivery is the actual transportation of the cargo, as opposed to the
// customer requirement (RouteSpecification) and the plan (Itinerary).
type Delivery struct {
Itinerary Itinerary
RouteSpecification RouteSpecification
RoutingStatus RoutingStatus
TransportStatus TransportStatus
NextExpectedActivity HandlingActivity
LastEvent HandlingEvent
LastKnownLocation location.UNLocode
CurrentVoyage voyage.Number
ETA time.Time
IsMisdirected bool
IsUnloadedAtDestination bool
}
// UpdateOnRouting creates a new delivery snapshot to reflect changes in
// routing, i.e. when the route specification or the itinerary has changed but
// no additional handling of the cargo has been performed.
func (d Delivery) UpdateOnRouting(rs RouteSpecification, itinerary Itinerary) Delivery {
return newDelivery(d.LastEvent, itinerary, rs)
}
// IsOnTrack checks if the delivery is on track.
func (d Delivery) IsOnTrack() bool {
return d.RoutingStatus == Routed && !d.IsMisdirected
}
// DeriveDeliveryFrom creates a new delivery snapshot based on the complete
// handling history of a cargo, as well as its route specification and
// itinerary.
func DeriveDeliveryFrom(rs RouteSpecification, itinerary Itinerary, history HandlingHistory) Delivery {
lastEvent, _ := history.MostRecentlyCompletedEvent()
return newDelivery(lastEvent, itinerary, rs)
}
// newDelivery creates a up-to-date delivery based on an handling event,
// itinerary and a route specification.
func newDelivery(lastEvent HandlingEvent, itinerary Itinerary, rs RouteSpecification) Delivery {
var (
routingStatus = calculateRoutingStatus(itinerary, rs)
transportStatus = calculateTransportStatus(lastEvent)
lastKnownLocation = calculateLastKnownLocation(lastEvent)
isMisdirected = calculateMisdirectedStatus(lastEvent, itinerary)
isUnloadedAtDestination = calculateUnloadedAtDestination(lastEvent, rs)
currentVoyage = calculateCurrentVoyage(transportStatus, lastEvent)
)
d := Delivery{
LastEvent: lastEvent,
Itinerary: itinerary,
RouteSpecification: rs,
RoutingStatus: routingStatus,
TransportStatus: transportStatus,
LastKnownLocation: lastKnownLocation,
IsMisdirected: isMisdirected,
IsUnloadedAtDestination: isUnloadedAtDestination,
CurrentVoyage: currentVoyage,
}
d.NextExpectedActivity = calculateNextExpectedActivity(d)
d.ETA = calculateETA(d)
return d
}
// Below are internal functions used when creating a new delivery.
func calculateRoutingStatus(itinerary Itinerary, rs RouteSpecification) RoutingStatus {
if itinerary.Legs == nil {
return NotRouted
}
if rs.IsSatisfiedBy(itinerary) {
return Routed
}
return Misrouted
}
func calculateMisdirectedStatus(event HandlingEvent, itinerary Itinerary) bool {
if event.Activity.Type == NotHandled {
return false
}
return !itinerary.IsExpected(event)
}
func calculateUnloadedAtDestination(event HandlingEvent, rs RouteSpecification) bool {
if event.Activity.Type == NotHandled {
return false
}
return event.Activity.Type == Unload && rs.Destination == event.Activity.Location
}
func calculateTransportStatus(event HandlingEvent) TransportStatus {
switch event.Activity.Type {
case NotHandled:
return NotReceived
case Load:
return OnboardCarrier
case Unload:
return InPort
case Receive:
return InPort
case Customs:
return InPort
case Claim:
return Claimed
}
return Unknown
}
func calculateLastKnownLocation(event HandlingEvent) location.UNLocode {
return event.Activity.Location
}
func calculateNextExpectedActivity(d Delivery) HandlingActivity {
if !d.IsOnTrack() {
return HandlingActivity{}
}
switch d.LastEvent.Activity.Type {
case NotHandled:
return HandlingActivity{Type: Receive, Location: d.RouteSpecification.Origin}
case Receive:
l := d.Itinerary.Legs[0]
return HandlingActivity{Type: Load, Location: l.LoadLocation, VoyageNumber: l.VoyageNumber}
case Load:
for _, l := range d.Itinerary.Legs {
if l.LoadLocation == d.LastEvent.Activity.Location {
return HandlingActivity{Type: Unload, Location: l.UnloadLocation, VoyageNumber: l.VoyageNumber}
}
}
case Unload:
for i, l := range d.Itinerary.Legs {
if l.UnloadLocation == d.LastEvent.Activity.Location {
if i < len(d.Itinerary.Legs)-1 {
return HandlingActivity{Type: Load, Location: d.Itinerary.Legs[i+1].LoadLocation, VoyageNumber: d.Itinerary.Legs[i+1].VoyageNumber}
}
return HandlingActivity{Type: Claim, Location: l.UnloadLocation}
}
}
}
return HandlingActivity{}
}
func calculateCurrentVoyage(transportStatus TransportStatus, event HandlingEvent) voyage.Number {
if transportStatus == OnboardCarrier && event.Activity.Type != NotHandled {
return event.Activity.VoyageNumber
}
return voyage.Number("")
}
func calculateETA(d Delivery) time.Time {
if !d.IsOnTrack() {
return time.Time{}
}
return d.Itinerary.FinalArrivalTime()
}

View File

@@ -0,0 +1,121 @@
package cargo
// TODO: It would make sense to have this in its own package. Unfortunately,
// then there would be a circular dependency between the cargo and handling
// packages since cargo.Delivery would use handling.HandlingEvent and
// handling.HandlingEvent would use cargo.TrackingID. Also,
// HandlingEventFactory depends on the cargo repository.
//
// It would make sense not having the cargo package depend on handling.
import (
"errors"
"time"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
// HandlingActivity 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.
type HandlingActivity struct {
Type HandlingEventType
Location location.UNLocode
VoyageNumber voyage.Number
}
// HandlingEvent is used to register the event when, for instance, a cargo is
// unloaded from a carrier at a some location at a given time.
type HandlingEvent struct {
TrackingID TrackingID
Activity HandlingActivity
}
// HandlingEventType describes type of a handling event.
type HandlingEventType int
// Valid handling event types.
const (
NotHandled HandlingEventType = iota
Load
Unload
Receive
Claim
Customs
)
func (t HandlingEventType) String() string {
switch t {
case NotHandled:
return "Not Handled"
case Load:
return "Load"
case Unload:
return "Unload"
case Receive:
return "Receive"
case Claim:
return "Claim"
case Customs:
return "Customs"
}
return ""
}
// HandlingHistory is the handling history of a cargo.
type HandlingHistory struct {
HandlingEvents []HandlingEvent
}
// MostRecentlyCompletedEvent returns most recently completed handling event.
func (h HandlingHistory) MostRecentlyCompletedEvent() (HandlingEvent, error) {
if len(h.HandlingEvents) == 0 {
return HandlingEvent{}, errors.New("delivery history is empty")
}
return h.HandlingEvents[len(h.HandlingEvents)-1], nil
}
// HandlingEventRepository provides access a handling event store.
type HandlingEventRepository interface {
Store(e HandlingEvent)
QueryHandlingHistory(TrackingID) HandlingHistory
}
// HandlingEventFactory creates handling events.
type HandlingEventFactory struct {
CargoRepository Repository
VoyageRepository voyage.Repository
LocationRepository location.Repository
}
// CreateHandlingEvent creates a validated handling event.
func (f *HandlingEventFactory) CreateHandlingEvent(registered time.Time, completed time.Time, id TrackingID,
voyageNumber voyage.Number, unLocode location.UNLocode, eventType HandlingEventType) (HandlingEvent, error) {
if _, err := f.CargoRepository.Find(id); err != nil {
return HandlingEvent{}, err
}
if _, err := f.VoyageRepository.Find(voyageNumber); err != nil {
// TODO: This is pretty ugly, but when creating a Receive event, the voyage number is not known.
if len(voyageNumber) > 0 {
return HandlingEvent{}, err
}
}
if _, err := f.LocationRepository.Find(unLocode); err != nil {
return HandlingEvent{}, err
}
return HandlingEvent{
TrackingID: id,
Activity: HandlingActivity{
Type: eventType,
Location: unLocode,
VoyageNumber: voyageNumber,
},
}, nil
}

View File

@@ -0,0 +1,91 @@
package cargo
import (
"time"
"github.com/go-kit/kit/examples/shipping/location"
"github.com/go-kit/kit/examples/shipping/voyage"
)
// Leg describes the transportation between two locations on a voyage.
type Leg struct {
VoyageNumber voyage.Number `json:"voyage_number"`
LoadLocation location.UNLocode `json:"from"`
UnloadLocation location.UNLocode `json:"to"`
LoadTime time.Time `json:"load_time"`
UnloadTime time.Time `json:"unload_time"`
}
// NewLeg creates a new itinerary leg.
func NewLeg(voyageNumber voyage.Number, loadLocation, unloadLocation location.UNLocode, loadTime, unloadTime time.Time) Leg {
return Leg{
VoyageNumber: voyageNumber,
LoadLocation: loadLocation,
UnloadLocation: unloadLocation,
LoadTime: loadTime,
UnloadTime: unloadTime,
}
}
// Itinerary specifies steps required to transport a cargo from its origin to
// destination.
type Itinerary struct {
Legs []Leg `json:"legs"`
}
// InitialDepartureLocation returns the start of the itinerary.
func (i Itinerary) InitialDepartureLocation() location.UNLocode {
if i.IsEmpty() {
return location.UNLocode("")
}
return i.Legs[0].LoadLocation
}
// FinalArrivalLocation returns the end of the itinerary.
func (i Itinerary) FinalArrivalLocation() location.UNLocode {
if i.IsEmpty() {
return location.UNLocode("")
}
return i.Legs[len(i.Legs)-1].UnloadLocation
}
// FinalArrivalTime returns the expected arrival time at final destination.
func (i Itinerary) FinalArrivalTime() time.Time {
return i.Legs[len(i.Legs)-1].UnloadTime
}
// IsEmpty checks if the itinerary contains at least one leg.
func (i Itinerary) IsEmpty() bool {
return i.Legs == nil || len(i.Legs) == 0
}
// IsExpected checks if the given handling event is expected when executing
// this itinerary.
func (i Itinerary) IsExpected(event HandlingEvent) bool {
if i.IsEmpty() {
return true
}
switch event.Activity.Type {
case Receive:
return i.InitialDepartureLocation() == event.Activity.Location
case Load:
for _, l := range i.Legs {
if l.LoadLocation == event.Activity.Location && l.VoyageNumber == event.Activity.VoyageNumber {
return true
}
}
return false
case Unload:
for _, l := range i.Legs {
if l.UnloadLocation == event.Activity.Location && l.VoyageNumber == event.Activity.VoyageNumber {
return true
}
}
return false
case Claim:
return i.FinalArrivalLocation() == event.Activity.Location
}
return true
}