func (s *S) TestCargoFromHongkongToStockholm(chk *C) { dbname := "dddsample_test" var err error session, err := mgo.Dial("127.0.0.1") if err != nil { panic(err) } defer session.Close() session.SetMode(mgo.Monotonic, true) session.DB(dbname).DropDatabase() var ( cargoRepository, _ = repository.NewMongoCargo(dbname, session) locationRepository, _ = repository.NewMongoLocation(dbname, session) voyageRepository, _ = repository.NewMongoVoyage(dbname, session) handlingEventRepository = repository.NewMongoHandlingEvent(dbname, session) ) handlingEventFactory := cargo.HandlingEventFactory{ CargoRepository: cargoRepository, VoyageRepository: voyageRepository, LocationRepository: locationRepository, } routingService := &stubRoutingService{} cargoEventHandler := &stubCargoEventHandler{} cargoInspectionService := inspection.NewService(cargoRepository, handlingEventRepository, cargoEventHandler) handlingEventHandler := &stubHandlingEventHandler{cargoInspectionService} var ( bookingService = booking.NewService(cargoRepository, locationRepository, handlingEventRepository, routingService) handlingEventService = handling.NewService(handlingEventRepository, handlingEventFactory, handlingEventHandler) ) var ( origin = location.CNHKG // Hongkong destination = location.SESTO // Stockholm arrivalDeadline = time.Date(2009, time.March, 18, 12, 00, 00, 00, time.UTC) ) // // Use case 1: booking // trackingID, err := bookingService.BookNewCargo(origin, destination, arrivalDeadline) chk.Assert(err, IsNil) c, err := cargoRepository.Find(trackingID) chk.Assert(err, IsNil) chk.Check(c.Delivery.TransportStatus, Equals, cargo.NotReceived) chk.Check(c.Delivery.RoutingStatus, Equals, cargo.NotRouted) chk.Check(c.Delivery.IsMisdirected, Equals, false) chk.Check(c.Delivery.Itinerary.IsEmpty(), Equals, true) chk.Check(c.Delivery.ETA, Equals, time.Time{}) chk.Check(c.Delivery.NextExpectedActivity, Equals, cargo.HandlingActivity{}) // // Use case 2: routing // itineraries := bookingService.RequestPossibleRoutesForCargo(trackingID) itinerary := selectPreferredItinerary(itineraries) c.AssignToRoute(itinerary) cargoRepository.Store(c) chk.Check(c.Delivery.TransportStatus, Equals, cargo.NotReceived) chk.Check(c.Delivery.RoutingStatus, Equals, cargo.Routed) chk.Check(c.Delivery.IsMisdirected, Equals, false) chk.Check(c.Delivery.Itinerary.IsEmpty(), Equals, false) chk.Check(c.Delivery.ETA, Not(Equals), time.Time{}) chk.Check(c.Delivery.NextExpectedActivity, Equals, cargo.HandlingActivity{Type: cargo.Receive, Location: location.CNHKG}) // // Use case 3: handling // err = handlingEventService.RegisterHandlingEvent(toDate(2009, time.March, 1), trackingID, "", location.CNHKG, cargo.Receive) chk.Check(err, IsNil) // Ensure we're not working with stale cargo. c, err = cargoRepository.Find(trackingID) chk.Check(c.Delivery.TransportStatus, Equals, cargo.InPort) chk.Check(c.Delivery.LastKnownLocation, Equals, location.CNHKG) chk.Check(c.Delivery.Itinerary.IsEmpty(), Equals, false) err = handlingEventService.RegisterHandlingEvent(toDate(2009, time.March, 3), trackingID, voyage.V100.Number, location.CNHKG, cargo.Load) chk.Check(err, IsNil) c, err = cargoRepository.Find(trackingID) chk.Check(c.Delivery.TransportStatus, Equals, cargo.OnboardCarrier) chk.Check(c.Delivery.LastKnownLocation, Equals, location.CNHKG) chk.Check(c.Delivery.IsMisdirected, Equals, false) chk.Check(c.Delivery.Itinerary.IsEmpty(), Equals, false) chk.Check(c.Delivery.CurrentVoyage, Equals, voyage.V100.Number) chk.Check(c.Delivery.NextExpectedActivity, Equals, cargo.HandlingActivity{Type: cargo.Unload, Location: location.USNYC, VoyageNumber: voyage.V100.Number}) // // Here's an attempt to register a handling event that's not valid // because there is no voyage with the specified voyage number, // and there's no location with the specified UN Locode either. // noSuchVoyageNumber := voyage.Number("XX000") noSuchUNLocode := location.UNLocode("ZZZZZ") err = handlingEventService.RegisterHandlingEvent(toDate(2009, time.March, 5), trackingID, noSuchVoyageNumber, noSuchUNLocode, cargo.Load) chk.Check(err, NotNil) // // Cargo is incorrectly unloaded in Tokyo // err = handlingEventService.RegisterHandlingEvent(toDate(2009, time.March, 5), trackingID, voyage.V100.Number, location.JNTKO, cargo.Unload) chk.Check(err, IsNil) c, err = cargoRepository.Find(trackingID) chk.Check(c.Delivery.LastKnownLocation, Equals, location.JNTKO) chk.Check(c.Delivery.TransportStatus, Equals, cargo.InPort) chk.Check(c.Delivery.Itinerary.IsEmpty(), Equals, false) chk.Check(c.Delivery.CurrentVoyage, Equals, voyage.Number("")) chk.Check(c.Delivery.NextExpectedActivity, Equals, cargo.HandlingActivity{}) // Cargo is now misdirected chk.Check(c.Delivery.IsMisdirected, Equals, true) // // Cargo needs to be rerouted // rs := cargo.RouteSpecification{ Origin: location.JNTKO, Destination: location.SESTO, ArrivalDeadline: arrivalDeadline, } // Specify a new route, this time from Tokyo (where it was incorrectly unloaded) to Stockholm c.SpecifyNewRoute(rs) cargoRepository.Store(c) chk.Check(c.Delivery.RoutingStatus, Equals, cargo.Misrouted) chk.Check(c.Delivery.NextExpectedActivity, Equals, cargo.HandlingActivity{}) // Repeat procedure of selecting one out of a number of possible routes satisfying the route spec newItineraries := bookingService.RequestPossibleRoutesForCargo(trackingID) newItinerary := selectPreferredItinerary(newItineraries) c.AssignToRoute(newItinerary) cargoRepository.Store(c) chk.Check(c.Delivery.RoutingStatus, Equals, cargo.Routed) // // Cargo has been rerouted, shipping continues // // Load in Tokyo err = handlingEventService.RegisterHandlingEvent(toDate(2009, time.March, 8), trackingID, voyage.V300.Number, location.JNTKO, cargo.Load) chk.Check(err, IsNil) c, err = cargoRepository.Find(trackingID) chk.Check(c.Delivery.LastKnownLocation, Equals, location.JNTKO) chk.Check(c.Delivery.TransportStatus, Equals, cargo.OnboardCarrier) chk.Check(c.Delivery.IsMisdirected, Equals, false) chk.Check(c.Delivery.CurrentVoyage, Equals, voyage.V300.Number) chk.Check(c.Delivery.NextExpectedActivity, Equals, cargo.HandlingActivity{Type: cargo.Unload, Location: location.DEHAM, VoyageNumber: voyage.V300.Number}) // Unload in Hamburg err = handlingEventService.RegisterHandlingEvent(toDate(2009, time.March, 12), trackingID, voyage.V300.Number, location.DEHAM, cargo.Unload) chk.Check(err, IsNil) c, err = cargoRepository.Find(trackingID) chk.Check(c.Delivery.LastKnownLocation, Equals, location.DEHAM) chk.Check(c.Delivery.TransportStatus, Equals, cargo.InPort) chk.Check(c.Delivery.IsMisdirected, Equals, false) chk.Check(c.Delivery.CurrentVoyage, Equals, voyage.Number("")) chk.Check(c.Delivery.NextExpectedActivity, Equals, cargo.HandlingActivity{Type: cargo.Load, Location: location.DEHAM, VoyageNumber: voyage.V400.Number}) // Load in Hamburg err = handlingEventService.RegisterHandlingEvent(toDate(2009, time.March, 14), trackingID, voyage.V400.Number, location.DEHAM, cargo.Load) chk.Check(err, IsNil) c, err = cargoRepository.Find(trackingID) chk.Check(c.Delivery.LastKnownLocation, Equals, location.DEHAM) chk.Check(c.Delivery.TransportStatus, Equals, cargo.OnboardCarrier) chk.Check(c.Delivery.IsMisdirected, Equals, false) chk.Check(c.Delivery.CurrentVoyage, Equals, voyage.V400.Number) chk.Check(c.Delivery.NextExpectedActivity, Equals, cargo.HandlingActivity{Type: cargo.Unload, Location: location.SESTO, VoyageNumber: voyage.V400.Number}) // Unload in Stockholm err = handlingEventService.RegisterHandlingEvent(toDate(2009, time.March, 15), trackingID, voyage.V400.Number, location.SESTO, cargo.Unload) chk.Check(err, IsNil) c, err = cargoRepository.Find(trackingID) chk.Check(c.Delivery.LastKnownLocation, Equals, location.SESTO) chk.Check(c.Delivery.TransportStatus, Equals, cargo.InPort) chk.Check(c.Delivery.IsMisdirected, Equals, false) chk.Check(c.Delivery.CurrentVoyage, Equals, voyage.Number("")) chk.Check(c.Delivery.NextExpectedActivity, Equals, cargo.HandlingActivity{Type: cargo.Claim, Location: location.SESTO}) // Finally, cargo is claimed in Stockholm. This ends the cargo lifecycle from our perspective. err = handlingEventService.RegisterHandlingEvent(toDate(2009, time.March, 16), trackingID, voyage.V400.Number, location.SESTO, cargo.Claim) chk.Check(err, IsNil) c, _ = cargoRepository.Find(trackingID) chk.Check(c.Delivery.LastKnownLocation, Equals, location.SESTO) chk.Check(c.Delivery.TransportStatus, Equals, cargo.Claimed) chk.Check(c.Delivery.IsMisdirected, Equals, false) chk.Check(c.Delivery.CurrentVoyage, Equals, voyage.Number("")) chk.Check(c.Delivery.NextExpectedActivity, Equals, cargo.HandlingActivity{}) }
func main() { var ( addr = envString("PORT", defaultPort) rsurl = envString("ROUTINGSERVICE_URL", defaultRoutingServiceURL) dburl = envString("MONGODB_URL", defaultMongoDBURL) dbname = envString("DB_NAME", defaultDBName) httpAddr = flag.String("http.addr", ":"+addr, "HTTP listen address") routingServiceURL = flag.String("service.routing", rsurl, "routing service URL") mongoDBURL = flag.String("db.url", dburl, "MongoDB URL") databaseName = flag.String("db.name", dbname, "MongoDB database name") inmem = flag.Bool("inmem", false, "use in-memory repositories") ctx = context.Background() ) flag.Parse() var logger log.Logger logger = log.NewLogfmtLogger(os.Stderr) logger = &serializedLogger{Logger: logger} logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC) // Setup repositories var ( cargos cargo.Repository locations location.Repository voyages voyage.Repository handlingEvents cargo.HandlingEventRepository ) if *inmem { cargos = repository.NewInMemcargo() locations = repository.NewInMemLocation() voyages = repository.NewInMemVoyage() handlingEvents = repository.NewInMemHandlingEvent() } else { session, err := mgo.Dial(*mongoDBURL) if err != nil { panic(err) } defer session.Close() session.SetMode(mgo.Monotonic, true) cargos, _ = repository.NewMongoCargo(*databaseName, session) locations, _ = repository.NewMongoLocation(*databaseName, session) voyages, _ = repository.NewMongoVoyage(*databaseName, session) handlingEvents = repository.NewMongoHandlingEvent(*databaseName, session) } // Configure some questionable dependencies. var ( handlingEventFactory = cargo.HandlingEventFactory{ CargoRepository: cargos, VoyageRepository: voyages, LocationRepository: locations, } handlingEventHandler = handling.NewEventHandler( inspection.NewService(cargos, handlingEvents, nil), ) ) // Facilitate testing by adding some cargos. storeTestData(cargos) fieldKeys := []string{"method"} var rs routing.Service rs = routing.NewProxyingMiddleware(*routingServiceURL, ctx)(rs) var bs booking.Service bs = booking.NewService(cargos, locations, handlingEvents, rs) bs = booking.NewLoggingService(log.NewContext(logger).With("component", "booking"), bs) bs = booking.NewInstrumentingService( kitprometheus.NewCounter(stdprometheus.CounterOpts{ Namespace: "api", Subsystem: "booking_service", Name: "request_count", Help: "Number of requests received.", }, fieldKeys), metrics.NewTimeHistogram(time.Microsecond, kitprometheus.NewSummary(stdprometheus.SummaryOpts{ Namespace: "api", Subsystem: "booking_service", Name: "request_latency_microseconds", Help: "Total duration of requests in microseconds.", }, fieldKeys)), bs) var ts tracking.Service ts = tracking.NewService(cargos, handlingEvents) ts = tracking.NewLoggingService(log.NewContext(logger).With("component", "tracking"), ts) ts = tracking.NewInstrumentingService( kitprometheus.NewCounter(stdprometheus.CounterOpts{ Namespace: "api", Subsystem: "tracking_service", Name: "request_count", Help: "Number of requests received.", }, fieldKeys), metrics.NewTimeHistogram(time.Microsecond, kitprometheus.NewSummary(stdprometheus.SummaryOpts{ Namespace: "api", Subsystem: "tracking_service", Name: "request_latency_microseconds", Help: "Total duration of requests in microseconds.", }, fieldKeys)), ts) var hs handling.Service hs = handling.NewService(handlingEvents, handlingEventFactory, handlingEventHandler) hs = handling.NewLoggingService(log.NewContext(logger).With("component", "handling"), hs) hs = handling.NewInstrumentingService( kitprometheus.NewCounter(stdprometheus.CounterOpts{ Namespace: "api", Subsystem: "handling_service", Name: "request_count", Help: "Number of requests received.", }, fieldKeys), metrics.NewTimeHistogram(time.Microsecond, kitprometheus.NewSummary(stdprometheus.SummaryOpts{ Namespace: "api", Subsystem: "handling_service", Name: "request_latency_microseconds", Help: "Total duration of requests in microseconds.", }, fieldKeys)), hs) httpLogger := log.NewContext(logger).With("component", "http") mux := http.NewServeMux() mux.Handle("/booking/v1/", booking.MakeHandler(ctx, bs, httpLogger)) mux.Handle("/tracking/v1/", tracking.MakeHandler(ctx, ts, httpLogger)) mux.Handle("/handling/v1/", handling.MakeHandler(ctx, hs, httpLogger)) http.Handle("/", accessControl(mux)) http.Handle("/metrics", stdprometheus.Handler()) errs := make(chan error, 2) go func() { logger.Log("transport", "http", "address", *httpAddr, "msg", "listening") errs <- http.ListenAndServe(*httpAddr, nil) }() go func() { c := make(chan os.Signal) signal.Notify(c, syscall.SIGINT) errs <- fmt.Errorf("%s", <-c) }() logger.Log("terminated", <-errs) }