1package main
2
3import (
4	"context"
5	"flag"
6	"fmt"
7	"net/http"
8	"os"
9	"os/signal"
10	"syscall"
11	"time"
12
13	stdprometheus "github.com/prometheus/client_golang/prometheus"
14	"github.com/prometheus/client_golang/prometheus/promhttp"
15
16	"github.com/go-kit/kit/log"
17	kitprometheus "github.com/go-kit/kit/metrics/prometheus"
18
19	"github.com/go-kit/kit/examples/shipping/booking"
20	"github.com/go-kit/kit/examples/shipping/cargo"
21	"github.com/go-kit/kit/examples/shipping/handling"
22	"github.com/go-kit/kit/examples/shipping/inmem"
23	"github.com/go-kit/kit/examples/shipping/inspection"
24	"github.com/go-kit/kit/examples/shipping/location"
25	"github.com/go-kit/kit/examples/shipping/routing"
26	"github.com/go-kit/kit/examples/shipping/tracking"
27)
28
29const (
30	defaultPort              = "8080"
31	defaultRoutingServiceURL = "http://localhost:7878"
32)
33
34func main() {
35	var (
36		addr  = envString("PORT", defaultPort)
37		rsurl = envString("ROUTINGSERVICE_URL", defaultRoutingServiceURL)
38
39		httpAddr          = flag.String("http.addr", ":"+addr, "HTTP listen address")
40		routingServiceURL = flag.String("service.routing", rsurl, "routing service URL")
41
42		ctx = context.Background()
43	)
44
45	flag.Parse()
46
47	var logger log.Logger
48	logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
49	logger = log.With(logger, "ts", log.DefaultTimestampUTC)
50
51	var (
52		cargos         = inmem.NewCargoRepository()
53		locations      = inmem.NewLocationRepository()
54		voyages        = inmem.NewVoyageRepository()
55		handlingEvents = inmem.NewHandlingEventRepository()
56	)
57
58	// Configure some questionable dependencies.
59	var (
60		handlingEventFactory = cargo.HandlingEventFactory{
61			CargoRepository:    cargos,
62			VoyageRepository:   voyages,
63			LocationRepository: locations,
64		}
65		handlingEventHandler = handling.NewEventHandler(
66			inspection.NewService(cargos, handlingEvents, nil),
67		)
68	)
69
70	// Facilitate testing by adding some cargos.
71	storeTestData(cargos)
72
73	fieldKeys := []string{"method"}
74
75	var rs routing.Service
76	rs = routing.NewProxyingMiddleware(ctx, *routingServiceURL)(rs)
77
78	var bs booking.Service
79	bs = booking.NewService(cargos, locations, handlingEvents, rs)
80	bs = booking.NewLoggingService(log.With(logger, "component", "booking"), bs)
81	bs = booking.NewInstrumentingService(
82		kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
83			Namespace: "api",
84			Subsystem: "booking_service",
85			Name:      "request_count",
86			Help:      "Number of requests received.",
87		}, fieldKeys),
88		kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
89			Namespace: "api",
90			Subsystem: "booking_service",
91			Name:      "request_latency_microseconds",
92			Help:      "Total duration of requests in microseconds.",
93		}, fieldKeys),
94		bs,
95	)
96
97	var ts tracking.Service
98	ts = tracking.NewService(cargos, handlingEvents)
99	ts = tracking.NewLoggingService(log.With(logger, "component", "tracking"), ts)
100	ts = tracking.NewInstrumentingService(
101		kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
102			Namespace: "api",
103			Subsystem: "tracking_service",
104			Name:      "request_count",
105			Help:      "Number of requests received.",
106		}, fieldKeys),
107		kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
108			Namespace: "api",
109			Subsystem: "tracking_service",
110			Name:      "request_latency_microseconds",
111			Help:      "Total duration of requests in microseconds.",
112		}, fieldKeys),
113		ts,
114	)
115
116	var hs handling.Service
117	hs = handling.NewService(handlingEvents, handlingEventFactory, handlingEventHandler)
118	hs = handling.NewLoggingService(log.With(logger, "component", "handling"), hs)
119	hs = handling.NewInstrumentingService(
120		kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
121			Namespace: "api",
122			Subsystem: "handling_service",
123			Name:      "request_count",
124			Help:      "Number of requests received.",
125		}, fieldKeys),
126		kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
127			Namespace: "api",
128			Subsystem: "handling_service",
129			Name:      "request_latency_microseconds",
130			Help:      "Total duration of requests in microseconds.",
131		}, fieldKeys),
132		hs,
133	)
134
135	httpLogger := log.With(logger, "component", "http")
136
137	mux := http.NewServeMux()
138
139	mux.Handle("/booking/v1/", booking.MakeHandler(bs, httpLogger))
140	mux.Handle("/tracking/v1/", tracking.MakeHandler(ts, httpLogger))
141	mux.Handle("/handling/v1/", handling.MakeHandler(hs, httpLogger))
142
143	http.Handle("/", accessControl(mux))
144	http.Handle("/metrics", promhttp.Handler())
145
146	errs := make(chan error, 2)
147	go func() {
148		logger.Log("transport", "http", "address", *httpAddr, "msg", "listening")
149		errs <- http.ListenAndServe(*httpAddr, nil)
150	}()
151	go func() {
152		c := make(chan os.Signal)
153		signal.Notify(c, syscall.SIGINT)
154		errs <- fmt.Errorf("%s", <-c)
155	}()
156
157	logger.Log("terminated", <-errs)
158}
159
160func accessControl(h http.Handler) http.Handler {
161	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
162		w.Header().Set("Access-Control-Allow-Origin", "*")
163		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
164		w.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type")
165
166		if r.Method == "OPTIONS" {
167			return
168		}
169
170		h.ServeHTTP(w, r)
171	})
172}
173
174func envString(env, fallback string) string {
175	e := os.Getenv(env)
176	if e == "" {
177		return fallback
178	}
179	return e
180}
181
182func storeTestData(r cargo.Repository) {
183	test1 := cargo.New("FTL456", cargo.RouteSpecification{
184		Origin:          location.AUMEL,
185		Destination:     location.SESTO,
186		ArrivalDeadline: time.Now().AddDate(0, 0, 7),
187	})
188	if err := r.Store(test1); err != nil {
189		panic(err)
190	}
191
192	test2 := cargo.New("ABC123", cargo.RouteSpecification{
193		Origin:          location.SESTO,
194		Destination:     location.CNHKG,
195		ArrivalDeadline: time.Now().AddDate(0, 0, 14),
196	})
197	if err := r.Store(test2); err != nil {
198		panic(err)
199	}
200}
201