├── cmd ├── checkout │ └── main.go └── payment │ └── main.go ├── configs └── proto │ └── payment.proto ├── docker-compose.yaml ├── go.mod ├── go.sum └── internal ├── app ├── http │ ├── grpc │ │ ├── payment.go │ │ └── server.go │ └── rest │ │ ├── order.go │ │ └── server.go ├── order │ ├── order.go │ ├── order_test.go │ └── service.go ├── payment │ └── method.go └── storage │ └── inmem │ └── order.go └── pkg ├── pb └── payment.pb.go ├── signal └── signal.go └── tracing ├── context.go ├── middleware.go └── tracer.go /cmd/checkout/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "go.opentelemetry.io/otel/plugin/grpctrace" 9 | 10 | "github.com/italolelis/coffee-shop/internal/app/http/rest" 11 | "github.com/italolelis/coffee-shop/internal/pkg/log" 12 | "github.com/italolelis/coffee-shop/internal/pkg/pb" 13 | "github.com/italolelis/coffee-shop/internal/pkg/signal" 14 | "github.com/italolelis/coffee-shop/internal/pkg/tracing" 15 | "github.com/kelseyhightower/envconfig" 16 | "google.golang.org/grpc" 17 | ) 18 | 19 | type config struct { 20 | LogLevel string `split_words:"true" default:"info"` 21 | API struct { 22 | Addr string `split_words:"true" default:"0.0.0.0:8080"` 23 | ReadTimeout time.Duration `split_words:"true" default:"30s"` 24 | WriteTimeout time.Duration `split_words:"true" default:"30s"` 25 | IdleTimeout time.Duration `split_words:"true" default:"5s"` 26 | ShutdownTimeout time.Duration `split_words:"true" default:"5s"` 27 | } 28 | Payment struct { 29 | Addr string `split_words:"true" required:"true"` 30 | Timeout time.Duration `split_words:"true" default:"2s"` 31 | } 32 | Tracing struct { 33 | Addr string `split_words:"true"` 34 | ServiceName string `split_words:"true"` 35 | } 36 | } 37 | 38 | func main() { 39 | ctx, cancel := context.WithCancel(context.Background()) 40 | defer cancel() 41 | 42 | logger := log.WithContext(ctx) 43 | logger.Sync() 44 | 45 | if err := run(ctx); err != nil { 46 | logger.Fatalw("an error happened", "err", err) 47 | } 48 | } 49 | 50 | func run(ctx context.Context) error { 51 | logger := log.WithContext(ctx) 52 | 53 | // ========================================================================= 54 | // Configuration 55 | // ========================================================================= 56 | var cfg config 57 | if err := envconfig.Process("", &cfg); err != nil { 58 | return fmt.Errorf("failed to load the env vars: %w", err) 59 | } 60 | 61 | log.SetLevel(cfg.LogLevel) 62 | 63 | // ========================================================================= 64 | // Start Tracing Support 65 | // ========================================================================= 66 | logger.Info("initializing tracing support") 67 | tp, close, err := tracing.InitTracer(cfg.Tracing.Addr, cfg.Tracing.ServiceName) 68 | if err != nil { 69 | return fmt.Errorf("failed to setup tracing: %w", err) 70 | } 71 | defer close() 72 | 73 | t := tp.Tracer("main") 74 | ctx = tracing.NewContext(ctx, t) 75 | 76 | // Make a channel to listen for errors coming from the listener. Use a 77 | // buffered channel so the goroutine can exit if we don't collect this error. 78 | var serverErrors = make(chan error, 1) 79 | 80 | // ========================================================================= 81 | // Start REST Service 82 | // ========================================================================= 83 | logger.Infow("diling payment service", "addr", cfg.Payment.Addr) 84 | paymentDiler, err := grpc.DialContext( 85 | ctx, 86 | cfg.Payment.Addr, 87 | grpc.WithInsecure(), 88 | grpc.WithBlock(), 89 | grpc.WithTimeout(cfg.Payment.Timeout), 90 | grpc.WithUnaryInterceptor(grpctrace.UnaryClientInterceptor(t)), 91 | grpc.WithStreamInterceptor(grpctrace.StreamClientInterceptor(t)), 92 | ) 93 | if err != nil { 94 | return fmt.Errorf("failed to dial payment service: %w", err) 95 | } 96 | defer paymentDiler.Close() 97 | 98 | s := rest.NewServer( 99 | rest.Config{ 100 | Addr: cfg.API.Addr, 101 | ReadTimeout: cfg.API.ReadTimeout, 102 | WriteTimeout: cfg.API.WriteTimeout, 103 | IdleTimeout: cfg.API.IdleTimeout, 104 | }, 105 | pb.NewPaymentClient(paymentDiler), 106 | ) 107 | go func() { 108 | logger.Infow("Initializing REST support", "host", cfg.API.Addr) 109 | serverErrors <- s.ListenAndServe(ctx) 110 | }() 111 | 112 | // ========================================================================= 113 | // Signal notifier 114 | // ========================================================================= 115 | done := signal.New(ctx) 116 | 117 | logger.Info("application running") 118 | 119 | // Blocking main and waiting for shutdown. 120 | select { 121 | case err := <-serverErrors: 122 | return fmt.Errorf("server error: %w", err) 123 | case <-done.Done(): 124 | logger.Info("shutdown") 125 | 126 | ctx, cancel := context.WithTimeout(ctx, cfg.API.ShutdownTimeout) 127 | defer cancel() 128 | 129 | if err := s.Stop(ctx); err != nil { 130 | return fmt.Errorf("failed to close probe server gracefully: %w", err) 131 | } 132 | } 133 | 134 | return nil 135 | } 136 | -------------------------------------------------------------------------------- /cmd/payment/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/italolelis/coffee-shop/internal/app/http/grpc" 9 | "github.com/italolelis/coffee-shop/internal/pkg/log" 10 | "github.com/italolelis/coffee-shop/internal/pkg/signal" 11 | "github.com/italolelis/coffee-shop/internal/pkg/tracing" 12 | "github.com/kelseyhightower/envconfig" 13 | ) 14 | 15 | type config struct { 16 | LogLevel string `split_words:"true" default:"info"` 17 | Web struct { 18 | Addr string `split_words:"true" default:"0.0.0.0:8081"` 19 | ShutdownTimeout time.Duration `split_words:"true" default:"5s"` 20 | } 21 | Tracing struct { 22 | Addr string `split_words:"true"` 23 | ServiceName string `split_words:"true"` 24 | } 25 | } 26 | 27 | func main() { 28 | ctx, cancel := context.WithCancel(context.Background()) 29 | defer cancel() 30 | 31 | logger := log.WithContext(ctx) 32 | logger.Sync() 33 | 34 | if err := run(ctx); err != nil { 35 | logger.Fatalw("an error happened", "err", err) 36 | } 37 | } 38 | 39 | func run(ctx context.Context) error { 40 | logger := log.WithContext(ctx) 41 | 42 | // ========================================================================= 43 | // Configuration 44 | // ========================================================================= 45 | var cfg config 46 | if err := envconfig.Process("", &cfg); err != nil { 47 | return fmt.Errorf("failed to load the env vars: %w", err) 48 | } 49 | 50 | log.SetLevel(cfg.LogLevel) 51 | 52 | // ========================================================================= 53 | // Start Tracing Support 54 | // ========================================================================= 55 | logger.Info("initializing tracing support") 56 | tp, close, err := tracing.InitTracer(cfg.Tracing.Addr, cfg.Tracing.ServiceName) 57 | if err != nil { 58 | return fmt.Errorf("failed to setup tracing: %w", err) 59 | } 60 | defer close() 61 | 62 | t := tp.Tracer("main") 63 | ctx = tracing.NewContext(ctx, t) 64 | 65 | // Make a channel to listen for errors coming from the listener. Use a 66 | // buffered channel so the goroutine can exit if we don't collect this error. 67 | var serverErrors = make(chan error, 1) 68 | 69 | // ========================================================================= 70 | // Start GRPC Service 71 | // ========================================================================= 72 | s := grpc.NewServer(grpc.Config{Addr: cfg.Web.Addr}, tp.Tracer("main")) 73 | go func() { 74 | logger.Infow("Initializing GRPC support", "addr", cfg.Web.Addr) 75 | serverErrors <- s.ListenAndServe(ctx) 76 | }() 77 | 78 | // ========================================================================= 79 | // Signal notifier 80 | // ========================================================================= 81 | done := signal.New(ctx) 82 | 83 | logger.Info("application running") 84 | 85 | // Blocking main and waiting for shutdown. 86 | select { 87 | case err := <-serverErrors: 88 | return fmt.Errorf("server error: %w", err) 89 | case <-done.Done(): 90 | logger.Info("shutdown") 91 | 92 | ctx, cancel := context.WithTimeout(ctx, cfg.Web.ShutdownTimeout) 93 | defer cancel() 94 | 95 | s.Stop(ctx) 96 | } 97 | 98 | return nil 99 | } 100 | -------------------------------------------------------------------------------- /configs/proto/payment.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package pb; 4 | 5 | option go_package = ".;pb"; 6 | 7 | message PaymentRequest { 8 | string OrderID = 1; 9 | string Method = 2; 10 | } 11 | 12 | message PaymentConfirmation { 13 | string ID = 1; 14 | string OrderID = 2; 15 | } 16 | 17 | service Payment { 18 | rpc Pay(PaymentRequest) returns (PaymentConfirmation) {}; 19 | } 20 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | jaeger: 2 | image: jaegertracing/all-in-one:latest 3 | ports: 4 | - "5775:5775/udp" 5 | - "6831:6831/udp" 6 | - "6832:6832/udp" 7 | - "5778:5778" 8 | - "16686:16686" 9 | - "14268:14268" 10 | - "9411:9411" 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/italolelis/coffee-shop 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/go-chi/chi v4.1.1+incompatible 7 | github.com/go-chi/render v1.0.1 8 | github.com/golang/protobuf v1.4.1 9 | github.com/google/uuid v1.1.1 10 | github.com/kelseyhightower/envconfig v1.4.0 11 | github.com/stretchr/testify v1.5.1 12 | go.opentelemetry.io/otel v0.4.3 13 | go.opentelemetry.io/otel/exporters/trace/jaeger v0.4.3 14 | go.uber.org/zap v1.15.0 15 | google.golang.org/grpc v1.29.1 16 | google.golang.org/protobuf v1.22.0 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 5 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 6 | github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7 h1:qELHH0AWCvf98Yf+CNIJx9vOZOfHFDDzgDRYsnNk/vs= 7 | github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= 8 | github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI= 9 | github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 10 | github.com/benbjohnson/clock v1.0.0 h1:78Jk/r6m4wCi6sndMpty7A//t4dw/RW5fV4ZgDVfX1w= 11 | github.com/benbjohnson/clock v1.0.0/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= 12 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 13 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 14 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 19 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 20 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 21 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 22 | github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4= 23 | github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= 24 | github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= 25 | github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= 26 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 27 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 28 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 29 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 30 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 31 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 32 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 33 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 34 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 35 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 36 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 37 | github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= 38 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 39 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 40 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 41 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 42 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 43 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= 44 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 45 | github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= 46 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 47 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 48 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 49 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 50 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 51 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 52 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 53 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 54 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 55 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 56 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 57 | github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= 58 | github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= 59 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 60 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 61 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 62 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 63 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 64 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 65 | github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 66 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 67 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 68 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 69 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 70 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 71 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 72 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 73 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 74 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 75 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 76 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 77 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 78 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 79 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 80 | go.opentelemetry.io/otel v0.4.3 h1:CroUX/0O1ZDcF0iWOO8gwYFWb5EbdSF0/C1yosO+Vhs= 81 | go.opentelemetry.io/otel v0.4.3/go.mod h1:jzBIgIzK43Iu1BpDAXwqOd6UPsSAk+ewVZ5ofSXw4Ek= 82 | go.opentelemetry.io/otel/exporters/trace/jaeger v0.4.3 h1:RGMJOkx0RYJIrVd0rp9dV1VauD/yoiq6JSzRQdBr07Y= 83 | go.opentelemetry.io/otel/exporters/trace/jaeger v0.4.3/go.mod h1:ANmtgg9Amz34/eufKYOYHCtBfKb+k+murSEkDNW8FkQ= 84 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= 85 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 86 | go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= 87 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 88 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= 89 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 90 | go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= 91 | go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= 92 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 93 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 94 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 95 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 96 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 97 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 98 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 99 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 100 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= 101 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 102 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 103 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 104 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 105 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 106 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 107 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 108 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 109 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 110 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= 111 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 112 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 113 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 114 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 115 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 116 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 117 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 118 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 119 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 120 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 121 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 122 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 123 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 124 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= 125 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 126 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= 127 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 128 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 129 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 130 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 131 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 132 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 133 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 134 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 135 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 136 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 137 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 138 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 139 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 140 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 141 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 142 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= 143 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 144 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 145 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 146 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 147 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 148 | google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40= 149 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 150 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 151 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 152 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 153 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 154 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 155 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 156 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 157 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 158 | google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03 h1:4HYDjxeNXAOTv3o1N2tjo8UUSlhQgAD52FVkwxnWgM8= 159 | google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 160 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 161 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 162 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 163 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 164 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 165 | google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= 166 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 167 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 168 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 169 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 170 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 171 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 172 | google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY= 173 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 174 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 175 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 176 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 177 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 178 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 179 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 180 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 181 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 182 | gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= 183 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 184 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 185 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 186 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 187 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= 188 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 189 | -------------------------------------------------------------------------------- /internal/app/http/grpc/payment.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/google/uuid" 8 | "github.com/italolelis/coffee-shop/internal/app/payment" 9 | "github.com/italolelis/coffee-shop/internal/pkg/log" 10 | "github.com/italolelis/coffee-shop/internal/pkg/pb" 11 | ) 12 | 13 | type PaymentHandler struct{} 14 | 15 | func (h *PaymentHandler) Pay(ctx context.Context, r *pb.PaymentRequest) (*pb.PaymentConfirmation, error) { 16 | logger := log.WithContext(ctx). 17 | Named("payments"). 18 | With("action", "pay"). 19 | With("order_id", r.OrderID) 20 | 21 | m, err := payment.NewMethodFactory(r.Method) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | orderID, err := uuid.Parse(r.OrderID) 27 | if err != nil { 28 | return nil, fmt.Errorf("failed to parse order id: %w", err) 29 | } 30 | 31 | logger.Debug("processing payment") 32 | c, err := m.Process(payment.OrderRequest{OrderID: orderID}) 33 | if err != nil { 34 | return nil, fmt.Errorf("failed to process payment: %w", err) 35 | } 36 | 37 | // save confirmation in storage 38 | 39 | logger.Debug("payment processed") 40 | return &pb.PaymentConfirmation{ID: c.ID.String(), OrderID: c.OrderID.String()}, nil 41 | } 42 | -------------------------------------------------------------------------------- /internal/app/http/grpc/server.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "time" 8 | 9 | "github.com/italolelis/coffee-shop/internal/pkg/pb" 10 | "go.opentelemetry.io/otel/api/trace" 11 | "go.opentelemetry.io/otel/plugin/grpctrace" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/keepalive" 14 | ) 15 | 16 | type Config struct { 17 | Addr string 18 | } 19 | 20 | // Server represents a GRPC server 21 | type Server struct { 22 | cfg Config 23 | ph *PaymentHandler 24 | g *grpc.Server 25 | } 26 | 27 | // NewServer creates a new Server 28 | func NewServer(cfg Config, tp trace.Tracer) *Server { 29 | return &Server{ 30 | cfg: cfg, 31 | g: grpc.NewServer( 32 | grpc.UnaryInterceptor(grpctrace.UnaryServerInterceptor(tp)), 33 | grpc.StreamInterceptor(grpctrace.StreamServerInterceptor(tp)), 34 | grpc.KeepaliveParams(keepalive.ServerParameters{ 35 | Timeout: 30 * time.Second, 36 | }), 37 | ), 38 | ph: &PaymentHandler{}, 39 | } 40 | } 41 | 42 | // ListenAndServe opens a port on given address 43 | // and listens for GRPC connections 44 | func (s *Server) ListenAndServe(ctx context.Context) error { 45 | lis, err := net.Listen("tcp", s.cfg.Addr) 46 | if err != nil { 47 | return fmt.Errorf("failed to listen on address: %w", err) 48 | } 49 | 50 | pb.RegisterPaymentServer(s.g, s.ph) 51 | 52 | return s.g.Serve(lis) 53 | } 54 | 55 | func (s *Server) Stop(ctx context.Context) { 56 | s.g.GracefulStop() 57 | } 58 | -------------------------------------------------------------------------------- /internal/app/http/rest/order.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | 9 | "github.com/go-chi/chi" 10 | "github.com/go-chi/render" 11 | "github.com/google/uuid" 12 | "github.com/italolelis/coffee-shop/internal/app/order" 13 | "github.com/italolelis/coffee-shop/internal/pkg/log" 14 | ) 15 | 16 | type OrderHandler struct { 17 | srv order.Service 18 | } 19 | 20 | func (h OrderHandler) Checkout(w http.ResponseWriter, r *http.Request) { 21 | var ( 22 | ctx = r.Context() 23 | logger = log.WithContext(ctx).Named("orders").With("action", "checkout") 24 | ) 25 | 26 | var cmd order.CheckoutCommand 27 | if err := json.NewDecoder(r.Body).Decode(&cmd); err != nil { 28 | logger.Errorw("failed to decode payload", "err", err) 29 | 30 | http.Error(w, "failed to decode payload", http.StatusBadRequest) 31 | 32 | return 33 | } 34 | 35 | orderID, err := h.srv.Checkout(ctx, cmd) 36 | if err != nil { 37 | logger.Errorw("failed to checkout order", "err", err) 38 | 39 | http.Error(w, "failed to checkout order", http.StatusInternalServerError) 40 | 41 | return 42 | } 43 | 44 | w.Header().Add("Location", fmt.Sprintf("/orders/%s", orderID)) 45 | w.WriteHeader(http.StatusCreated) 46 | } 47 | 48 | func (h OrderHandler) AddToOrder(w http.ResponseWriter, r *http.Request) { 49 | var ( 50 | ctx = r.Context() 51 | logger = log.WithContext(ctx).Named("orders").With("action", "add-to-order") 52 | ) 53 | 54 | var cmd order.AddToOrderCommand 55 | if err := json.NewDecoder(r.Body).Decode(&cmd); err != nil { 56 | logger.Errorw("failed to decode payload", "err", err) 57 | 58 | http.Error(w, "failed to decode payload", http.StatusBadRequest) 59 | 60 | return 61 | } 62 | 63 | orderID, err := h.srv.AddToOrder(ctx, cmd) 64 | if err != nil { 65 | logger.Errorw("failed to add items to order", "err", err) 66 | 67 | http.Error(w, "failed to add items to order", http.StatusInternalServerError) 68 | 69 | return 70 | } 71 | 72 | w.Header().Add("Location", fmt.Sprintf("/orders/%s", orderID)) 73 | w.WriteHeader(http.StatusCreated) 74 | } 75 | 76 | func (h OrderHandler) GetOrder(w http.ResponseWriter, r *http.Request) { 77 | var ( 78 | ctx = r.Context() 79 | logger = log.WithContext(ctx).Named("orders").With("action", "get-order") 80 | rawOrderID = chi.URLParam(r, "orderID") 81 | ) 82 | 83 | orderID, err := uuid.Parse(rawOrderID) 84 | if err != nil { 85 | http.Error(w, "invalid order id", http.StatusBadRequest) 86 | return 87 | } 88 | 89 | o, err := h.srv.Fetch(ctx, orderID) 90 | if err != nil { 91 | if errors.Is(order.ErrNotFound, err) { 92 | http.Error(w, "couldn't find order", http.StatusNotFound) 93 | return 94 | } 95 | 96 | logger.Errorw("failed to fetch order", "err", err) 97 | http.Error(w, "failed to fetch order", http.StatusInternalServerError) 98 | 99 | return 100 | } 101 | 102 | render.JSON(w, r, o) 103 | } 104 | -------------------------------------------------------------------------------- /internal/app/http/rest/server.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/go-chi/chi" 11 | "github.com/italolelis/coffee-shop/internal/app/order" 12 | "github.com/italolelis/coffee-shop/internal/app/storage/inmem" 13 | "github.com/italolelis/coffee-shop/internal/pkg/pb" 14 | "github.com/italolelis/coffee-shop/internal/pkg/tracing" 15 | ) 16 | 17 | type Config struct { 18 | Addr string 19 | ReadTimeout time.Duration 20 | WriteTimeout time.Duration 21 | IdleTimeout time.Duration 22 | } 23 | 24 | // Server represents a REST server 25 | type Server struct { 26 | s *http.Server 27 | oh *OrderHandler 28 | } 29 | 30 | // NewServer creates a new Server 31 | func NewServer(cfg Config, pc pb.PaymentClient) *Server { 32 | orw := inmem.NewOrderReadWrite() 33 | os := order.NewService(orw, orw, pc) 34 | 35 | return &Server{ 36 | s: &http.Server{ 37 | Addr: cfg.Addr, 38 | ReadTimeout: cfg.ReadTimeout, 39 | WriteTimeout: cfg.WriteTimeout, 40 | IdleTimeout: cfg.IdleTimeout, 41 | }, 42 | oh: &OrderHandler{srv: os}, 43 | } 44 | } 45 | 46 | // ListenAndServe opens a port on given address 47 | // and listens for GRPC connections 48 | func (s *Server) ListenAndServe(ctx context.Context) error { 49 | r := chi.NewRouter() 50 | r.Use(tracing.Tracing) 51 | r.Route("/orders", func(r chi.Router) { 52 | r.Post("/checkout", http.HandlerFunc(s.oh.Checkout)) 53 | r.Post("/", http.HandlerFunc(s.oh.AddToOrder)) 54 | r.Get("/{orderID}", http.HandlerFunc(s.oh.GetOrder)) 55 | }) 56 | 57 | s.s.Handler = r 58 | s.s.BaseContext = func(l net.Listener) context.Context { 59 | return ctx 60 | } 61 | 62 | return s.s.ListenAndServe() 63 | } 64 | 65 | func (s *Server) Stop(ctx context.Context) error { 66 | if err := s.s.Shutdown(ctx); err != nil { 67 | if err = s.s.Close(); err != nil { 68 | return fmt.Errorf("failed to close probe server gracefully: %w", err) 69 | } 70 | } 71 | 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /internal/app/order/order.go: -------------------------------------------------------------------------------- 1 | package order 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "reflect" 9 | "time" 10 | 11 | "github.com/google/uuid" 12 | ) 13 | 14 | type ( 15 | Order struct { 16 | ID uuid.UUID `json:"id" db:"id"` 17 | CreatedAt time.Time `json:"created_at" db:"created_at"` 18 | CustomerName string `json:"customer" db:"customer"` 19 | Items Items `json:"items" db:"items"` 20 | } 21 | 22 | Items []*Item 23 | 24 | Item struct { 25 | Name string `json:"name" db:"name"` 26 | ServingSize string `json:"serving_size" db:"serving_size"` 27 | Price float64 `json:"price" db:"price"` 28 | Qty int `json:"qty" db:"qty"` 29 | } 30 | ) 31 | 32 | func New(customerName string) *Order { 33 | return &Order{ 34 | ID: uuid.NewSHA1(uuid.NameSpaceOID, []byte(customerName)), 35 | CreatedAt: time.Now().UTC(), 36 | CustomerName: customerName, 37 | Items: make([]*Item, 0), 38 | } 39 | } 40 | 41 | func (o *Order) AddItems(items Items) error { 42 | for _, i := range items { 43 | if err := o.AddItem(i); err != nil { 44 | return err 45 | } 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func (o *Order) AddItem(i *Item) error { 52 | if i.Name == "" { 53 | return errors.New("item name can't be empty") 54 | } 55 | 56 | if i.ServingSize == "" { 57 | return errors.New("serving size can't be empty") 58 | } 59 | 60 | for _, existingItem := range o.Items { 61 | if existingItem.Name == i.Name && existingItem.ServingSize == i.ServingSize { 62 | existingItem.Qty += i.Qty 63 | return nil 64 | } 65 | } 66 | 67 | o.Items = append(o.Items, i) 68 | 69 | return nil 70 | } 71 | 72 | func (o *Order) Total() float64 { 73 | var total float64 74 | for _, i := range o.Items { 75 | total += i.Price * float64(i.Qty) 76 | } 77 | 78 | return total 79 | } 80 | 81 | // Value return a driver.Value representation of the order items 82 | func (p Items) Value() (driver.Value, error) { 83 | if len(p) == 0 { 84 | return nil, nil 85 | } 86 | return json.Marshal(p) 87 | } 88 | 89 | // Scan scans a database json representation into a []Item 90 | func (p *Items) Scan(src interface{}) error { 91 | v := reflect.ValueOf(src) 92 | if !v.IsValid() || v.IsNil() { 93 | return nil 94 | } 95 | if data, ok := src.([]byte); ok { 96 | return json.Unmarshal(data, &p) 97 | } 98 | return fmt.Errorf("could not not decode type %T -> %T", src, p) 99 | } 100 | -------------------------------------------------------------------------------- /internal/app/order/order_test.go: -------------------------------------------------------------------------------- 1 | package order 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestOrder_AddItems(t *testing.T) { 12 | t.Parallel() 13 | 14 | tests := []struct { 15 | name string 16 | customerName string 17 | items Items 18 | errorExpected bool 19 | expectedItemQty int 20 | expectedTotal float64 21 | }{ 22 | { 23 | name: "add single item empty name", 24 | customerName: "test", 25 | errorExpected: true, 26 | expectedItemQty: 0, 27 | expectedTotal: 0, 28 | items: Items{ 29 | { 30 | Name: "", 31 | Qty: 2, 32 | ServingSize: "L", 33 | Price: 2.60, 34 | }, 35 | }, 36 | }, 37 | { 38 | name: "add single item empty serving size", 39 | customerName: "test", 40 | errorExpected: true, 41 | expectedItemQty: 0, 42 | expectedTotal: 0, 43 | items: Items{ 44 | { 45 | Name: "latte", 46 | Qty: 2, 47 | ServingSize: "", 48 | Price: 2.60, 49 | }, 50 | }, 51 | }, 52 | { 53 | name: "add two same items", 54 | customerName: "test", 55 | errorExpected: false, 56 | expectedItemQty: 1, 57 | expectedTotal: 7.80, 58 | items: Items{ 59 | { 60 | Name: "cappuccino", 61 | Qty: 2, 62 | ServingSize: "L", 63 | Price: 2.60, 64 | }, 65 | { 66 | Name: "cappuccino", 67 | Qty: 1, 68 | ServingSize: "L", 69 | Price: 2.60, 70 | }, 71 | }, 72 | }, 73 | } 74 | for _, tt := range tests { 75 | tt := tt 76 | t.Run(tt.name, func(t *testing.T) { 77 | t.Parallel() 78 | o := New(tt.customerName) 79 | 80 | err := o.AddItems(tt.items) 81 | if !tt.errorExpected { 82 | require.NoError(t, err) 83 | } 84 | 85 | assert.Equal(t, tt.expectedItemQty, len(o.Items)) 86 | assert.Equal(t, tt.expectedTotal, math.Floor(o.Total()*100)/100) 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /internal/app/order/service.go: -------------------------------------------------------------------------------- 1 | package order 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/google/uuid" 9 | "github.com/italolelis/coffee-shop/internal/pkg/pb" 10 | "github.com/italolelis/coffee-shop/internal/pkg/tracing" 11 | ) 12 | 13 | var ErrNotFound = errors.New("order not found") 14 | 15 | type Reader interface { 16 | FetchByID(context.Context, uuid.UUID) (*Order, error) 17 | } 18 | 19 | type Writer interface { 20 | Add(context.Context, *Order) error 21 | } 22 | 23 | type Service interface { 24 | Checkout(context.Context, CheckoutCommand) (uuid.UUID, error) 25 | AddToOrder(context.Context, AddToOrderCommand) (uuid.UUID, error) 26 | Fetch(context.Context, uuid.UUID) (*Order, error) 27 | } 28 | 29 | type CheckoutCommand struct { 30 | CustomerName string `json:"customer_name"` 31 | PaymentMethod string `json:"payment_method"` 32 | } 33 | 34 | type AddToOrderCommand struct { 35 | CustomerName string `json:"customer_name"` 36 | Items Items `json:"items"` 37 | } 38 | 39 | type ServiceImp struct { 40 | w Writer 41 | r Reader 42 | pc pb.PaymentClient 43 | } 44 | 45 | func NewService(w Writer, r Reader, pc pb.PaymentClient) *ServiceImp { 46 | return &ServiceImp{ 47 | w: w, 48 | r: r, 49 | pc: pc, 50 | } 51 | } 52 | 53 | func (s *ServiceImp) Checkout(ctx context.Context, cmd CheckoutCommand) (uuid.UUID, error) { 54 | ctx, span := tracing.Start(ctx, "service/order/checkout") 55 | defer span.End() 56 | 57 | o, err := s.r.FetchByID(ctx, uuid.NewSHA1(uuid.NameSpaceOID, []byte(cmd.CustomerName))) 58 | if err != nil { 59 | return uuid.Nil, err 60 | } 61 | 62 | _, err = s.pc.Pay(ctx, &pb.PaymentRequest{ 63 | Method: cmd.PaymentMethod, 64 | OrderID: o.ID.String(), 65 | }) 66 | if err != nil { 67 | return uuid.Nil, fmt.Errorf("failed paying order: %w", err) 68 | } 69 | 70 | if err := s.w.Add(ctx, o); err != nil { 71 | return uuid.Nil, fmt.Errorf("failed saving order: %w", err) 72 | } 73 | 74 | return o.ID, nil 75 | } 76 | 77 | func (s *ServiceImp) AddToOrder(ctx context.Context, cmd AddToOrderCommand) (uuid.UUID, error) { 78 | ctx, span := tracing.Start(ctx, "service/order/add-to-order") 79 | defer span.End() 80 | 81 | o := New(cmd.CustomerName) 82 | if err := o.AddItems(cmd.Items); err != nil { 83 | return uuid.Nil, fmt.Errorf("failed adding items to orders: %w", err) 84 | } 85 | 86 | if err := s.w.Add(ctx, o); err != nil { 87 | return uuid.Nil, fmt.Errorf("failed saving order: %w", err) 88 | } 89 | 90 | return o.ID, nil 91 | } 92 | 93 | func (s *ServiceImp) Fetch(ctx context.Context, id uuid.UUID) (*Order, error) { 94 | return s.r.FetchByID(ctx, id) 95 | } 96 | -------------------------------------------------------------------------------- /internal/app/payment/method.go: -------------------------------------------------------------------------------- 1 | package payment 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/google/uuid" 8 | ) 9 | 10 | type OrderRequest struct { 11 | OrderID uuid.UUID 12 | Total float64 13 | } 14 | 15 | type Method interface { 16 | Process(OrderRequest) (*Confirmation, error) 17 | } 18 | 19 | type Confirmation struct { 20 | ID uuid.UUID `json:"id" db:"id"` 21 | OrderID uuid.UUID `json:"order_id" db:"order_id"` 22 | PayedAt time.Time `json:"payed_at" db:"payed_at"` 23 | } 24 | 25 | func NewConfirmation(orderID uuid.UUID) *Confirmation { 26 | return &Confirmation{ID: uuid.New(), OrderID: orderID, PayedAt: time.Now()} 27 | } 28 | 29 | type MethodFactory struct{} 30 | 31 | func NewMethodFactory(method string) (Method, error) { 32 | switch method { 33 | case "credit_card": 34 | return &CreditCard{}, nil 35 | case "apple_pay": 36 | return &ApplePay{}, nil 37 | default: 38 | return nil, errors.New("payment method not supported") 39 | } 40 | } 41 | 42 | type CreditCard struct{} 43 | 44 | func (c *CreditCard) Process(o OrderRequest) (*Confirmation, error) { 45 | // pretend to connect to some credit card provider 46 | return NewConfirmation(o.OrderID), nil 47 | } 48 | 49 | type ApplePay struct{} 50 | 51 | func (a *ApplePay) Process(o OrderRequest) (*Confirmation, error) { 52 | // pretend to connect to apple pay 53 | return NewConfirmation(o.OrderID), nil 54 | } 55 | -------------------------------------------------------------------------------- /internal/app/storage/inmem/order.go: -------------------------------------------------------------------------------- 1 | package inmem 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/google/uuid" 8 | "github.com/italolelis/coffee-shop/internal/app/order" 9 | "github.com/italolelis/coffee-shop/internal/pkg/tracing" 10 | ) 11 | 12 | type OrderReadWrite struct { 13 | mux *sync.RWMutex 14 | orders map[uuid.UUID]*order.Order 15 | } 16 | 17 | func NewOrderReadWrite() *OrderReadWrite { 18 | return &OrderReadWrite{mux: &sync.RWMutex{}, orders: make(map[uuid.UUID]*order.Order, 0)} 19 | } 20 | 21 | func (r *OrderReadWrite) FetchByID(ctx context.Context, id uuid.UUID) (*order.Order, error) { 22 | r.mux.RLock() 23 | defer r.mux.RUnlock() 24 | 25 | ctx, span := tracing.Start(ctx, "storage/order/fetch-by-id") 26 | defer span.End() 27 | 28 | o, ok := r.orders[id] 29 | if !ok { 30 | return nil, order.ErrNotFound 31 | } 32 | 33 | return o, nil 34 | } 35 | 36 | func (r *OrderReadWrite) Add(ctx context.Context, o *order.Order) error { 37 | r.mux.Lock() 38 | defer r.mux.Unlock() 39 | 40 | ctx, span := tracing.Start(ctx, "storage/order/add") 41 | defer span.End() 42 | 43 | existingOrder, ok := r.orders[o.ID] 44 | if !ok { 45 | r.orders[o.ID] = o 46 | return nil 47 | } 48 | 49 | existingOrder.AddItems(o.Items) 50 | 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /internal/pkg/pb/payment.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.22.0 4 | // protoc v3.11.4 5 | // source: payment.proto 6 | 7 | package pb 8 | 9 | import ( 10 | context "context" 11 | proto "github.com/golang/protobuf/proto" 12 | grpc "google.golang.org/grpc" 13 | codes "google.golang.org/grpc/codes" 14 | status "google.golang.org/grpc/status" 15 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 16 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 17 | reflect "reflect" 18 | sync "sync" 19 | ) 20 | 21 | const ( 22 | // Verify that this generated code is sufficiently up-to-date. 23 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 24 | // Verify that runtime/protoimpl is sufficiently up-to-date. 25 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 26 | ) 27 | 28 | // This is a compile-time assertion that a sufficiently up-to-date version 29 | // of the legacy proto package is being used. 30 | const _ = proto.ProtoPackageIsVersion4 31 | 32 | type PaymentRequest struct { 33 | state protoimpl.MessageState 34 | sizeCache protoimpl.SizeCache 35 | unknownFields protoimpl.UnknownFields 36 | 37 | OrderID string `protobuf:"bytes,1,opt,name=OrderID,proto3" json:"OrderID,omitempty"` 38 | Method string `protobuf:"bytes,2,opt,name=Method,proto3" json:"Method,omitempty"` 39 | } 40 | 41 | func (x *PaymentRequest) Reset() { 42 | *x = PaymentRequest{} 43 | if protoimpl.UnsafeEnabled { 44 | mi := &file_payment_proto_msgTypes[0] 45 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 46 | ms.StoreMessageInfo(mi) 47 | } 48 | } 49 | 50 | func (x *PaymentRequest) String() string { 51 | return protoimpl.X.MessageStringOf(x) 52 | } 53 | 54 | func (*PaymentRequest) ProtoMessage() {} 55 | 56 | func (x *PaymentRequest) ProtoReflect() protoreflect.Message { 57 | mi := &file_payment_proto_msgTypes[0] 58 | if protoimpl.UnsafeEnabled && x != nil { 59 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 60 | if ms.LoadMessageInfo() == nil { 61 | ms.StoreMessageInfo(mi) 62 | } 63 | return ms 64 | } 65 | return mi.MessageOf(x) 66 | } 67 | 68 | // Deprecated: Use PaymentRequest.ProtoReflect.Descriptor instead. 69 | func (*PaymentRequest) Descriptor() ([]byte, []int) { 70 | return file_payment_proto_rawDescGZIP(), []int{0} 71 | } 72 | 73 | func (x *PaymentRequest) GetOrderID() string { 74 | if x != nil { 75 | return x.OrderID 76 | } 77 | return "" 78 | } 79 | 80 | func (x *PaymentRequest) GetMethod() string { 81 | if x != nil { 82 | return x.Method 83 | } 84 | return "" 85 | } 86 | 87 | type PaymentConfirmation struct { 88 | state protoimpl.MessageState 89 | sizeCache protoimpl.SizeCache 90 | unknownFields protoimpl.UnknownFields 91 | 92 | ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` 93 | OrderID string `protobuf:"bytes,2,opt,name=OrderID,proto3" json:"OrderID,omitempty"` 94 | } 95 | 96 | func (x *PaymentConfirmation) Reset() { 97 | *x = PaymentConfirmation{} 98 | if protoimpl.UnsafeEnabled { 99 | mi := &file_payment_proto_msgTypes[1] 100 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 101 | ms.StoreMessageInfo(mi) 102 | } 103 | } 104 | 105 | func (x *PaymentConfirmation) String() string { 106 | return protoimpl.X.MessageStringOf(x) 107 | } 108 | 109 | func (*PaymentConfirmation) ProtoMessage() {} 110 | 111 | func (x *PaymentConfirmation) ProtoReflect() protoreflect.Message { 112 | mi := &file_payment_proto_msgTypes[1] 113 | if protoimpl.UnsafeEnabled && x != nil { 114 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 115 | if ms.LoadMessageInfo() == nil { 116 | ms.StoreMessageInfo(mi) 117 | } 118 | return ms 119 | } 120 | return mi.MessageOf(x) 121 | } 122 | 123 | // Deprecated: Use PaymentConfirmation.ProtoReflect.Descriptor instead. 124 | func (*PaymentConfirmation) Descriptor() ([]byte, []int) { 125 | return file_payment_proto_rawDescGZIP(), []int{1} 126 | } 127 | 128 | func (x *PaymentConfirmation) GetID() string { 129 | if x != nil { 130 | return x.ID 131 | } 132 | return "" 133 | } 134 | 135 | func (x *PaymentConfirmation) GetOrderID() string { 136 | if x != nil { 137 | return x.OrderID 138 | } 139 | return "" 140 | } 141 | 142 | var File_payment_proto protoreflect.FileDescriptor 143 | 144 | var file_payment_proto_rawDesc = []byte{ 145 | 0x0a, 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 146 | 0x02, 0x70, 0x62, 0x22, 0x42, 0x0a, 0x0e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 147 | 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x44, 148 | 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x44, 0x12, 149 | 0x16, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 150 | 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x3f, 0x0a, 0x13, 0x50, 0x61, 0x79, 0x6d, 0x65, 151 | 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 152 | 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x18, 153 | 0x0a, 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 154 | 0x07, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x49, 0x44, 0x32, 0x3f, 0x0a, 0x07, 0x50, 0x61, 0x79, 0x6d, 155 | 0x65, 0x6e, 0x74, 0x12, 0x34, 0x0a, 0x03, 0x50, 0x61, 0x79, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 156 | 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 157 | 0x2e, 0x70, 0x62, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 158 | 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x42, 0x06, 0x5a, 0x04, 0x2e, 0x3b, 0x70, 159 | 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 160 | } 161 | 162 | var ( 163 | file_payment_proto_rawDescOnce sync.Once 164 | file_payment_proto_rawDescData = file_payment_proto_rawDesc 165 | ) 166 | 167 | func file_payment_proto_rawDescGZIP() []byte { 168 | file_payment_proto_rawDescOnce.Do(func() { 169 | file_payment_proto_rawDescData = protoimpl.X.CompressGZIP(file_payment_proto_rawDescData) 170 | }) 171 | return file_payment_proto_rawDescData 172 | } 173 | 174 | var file_payment_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 175 | var file_payment_proto_goTypes = []interface{}{ 176 | (*PaymentRequest)(nil), // 0: pb.PaymentRequest 177 | (*PaymentConfirmation)(nil), // 1: pb.PaymentConfirmation 178 | } 179 | var file_payment_proto_depIdxs = []int32{ 180 | 0, // 0: pb.Payment.Pay:input_type -> pb.PaymentRequest 181 | 1, // 1: pb.Payment.Pay:output_type -> pb.PaymentConfirmation 182 | 1, // [1:2] is the sub-list for method output_type 183 | 0, // [0:1] is the sub-list for method input_type 184 | 0, // [0:0] is the sub-list for extension type_name 185 | 0, // [0:0] is the sub-list for extension extendee 186 | 0, // [0:0] is the sub-list for field type_name 187 | } 188 | 189 | func init() { file_payment_proto_init() } 190 | func file_payment_proto_init() { 191 | if File_payment_proto != nil { 192 | return 193 | } 194 | if !protoimpl.UnsafeEnabled { 195 | file_payment_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 196 | switch v := v.(*PaymentRequest); i { 197 | case 0: 198 | return &v.state 199 | case 1: 200 | return &v.sizeCache 201 | case 2: 202 | return &v.unknownFields 203 | default: 204 | return nil 205 | } 206 | } 207 | file_payment_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 208 | switch v := v.(*PaymentConfirmation); i { 209 | case 0: 210 | return &v.state 211 | case 1: 212 | return &v.sizeCache 213 | case 2: 214 | return &v.unknownFields 215 | default: 216 | return nil 217 | } 218 | } 219 | } 220 | type x struct{} 221 | out := protoimpl.TypeBuilder{ 222 | File: protoimpl.DescBuilder{ 223 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 224 | RawDescriptor: file_payment_proto_rawDesc, 225 | NumEnums: 0, 226 | NumMessages: 2, 227 | NumExtensions: 0, 228 | NumServices: 1, 229 | }, 230 | GoTypes: file_payment_proto_goTypes, 231 | DependencyIndexes: file_payment_proto_depIdxs, 232 | MessageInfos: file_payment_proto_msgTypes, 233 | }.Build() 234 | File_payment_proto = out.File 235 | file_payment_proto_rawDesc = nil 236 | file_payment_proto_goTypes = nil 237 | file_payment_proto_depIdxs = nil 238 | } 239 | 240 | // Reference imports to suppress errors if they are not otherwise used. 241 | var _ context.Context 242 | var _ grpc.ClientConnInterface 243 | 244 | // This is a compile-time assertion to ensure that this generated file 245 | // is compatible with the grpc package it is being compiled against. 246 | const _ = grpc.SupportPackageIsVersion6 247 | 248 | // PaymentClient is the client API for Payment service. 249 | // 250 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 251 | type PaymentClient interface { 252 | Pay(ctx context.Context, in *PaymentRequest, opts ...grpc.CallOption) (*PaymentConfirmation, error) 253 | } 254 | 255 | type paymentClient struct { 256 | cc grpc.ClientConnInterface 257 | } 258 | 259 | func NewPaymentClient(cc grpc.ClientConnInterface) PaymentClient { 260 | return &paymentClient{cc} 261 | } 262 | 263 | func (c *paymentClient) Pay(ctx context.Context, in *PaymentRequest, opts ...grpc.CallOption) (*PaymentConfirmation, error) { 264 | out := new(PaymentConfirmation) 265 | err := c.cc.Invoke(ctx, "/pb.Payment/Pay", in, out, opts...) 266 | if err != nil { 267 | return nil, err 268 | } 269 | return out, nil 270 | } 271 | 272 | // PaymentServer is the server API for Payment service. 273 | type PaymentServer interface { 274 | Pay(context.Context, *PaymentRequest) (*PaymentConfirmation, error) 275 | } 276 | 277 | // UnimplementedPaymentServer can be embedded to have forward compatible implementations. 278 | type UnimplementedPaymentServer struct { 279 | } 280 | 281 | func (*UnimplementedPaymentServer) Pay(context.Context, *PaymentRequest) (*PaymentConfirmation, error) { 282 | return nil, status.Errorf(codes.Unimplemented, "method Pay not implemented") 283 | } 284 | 285 | func RegisterPaymentServer(s *grpc.Server, srv PaymentServer) { 286 | s.RegisterService(&_Payment_serviceDesc, srv) 287 | } 288 | 289 | func _Payment_Pay_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 290 | in := new(PaymentRequest) 291 | if err := dec(in); err != nil { 292 | return nil, err 293 | } 294 | if interceptor == nil { 295 | return srv.(PaymentServer).Pay(ctx, in) 296 | } 297 | info := &grpc.UnaryServerInfo{ 298 | Server: srv, 299 | FullMethod: "/pb.Payment/Pay", 300 | } 301 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 302 | return srv.(PaymentServer).Pay(ctx, req.(*PaymentRequest)) 303 | } 304 | return interceptor(ctx, in, info, handler) 305 | } 306 | 307 | var _Payment_serviceDesc = grpc.ServiceDesc{ 308 | ServiceName: "pb.Payment", 309 | HandlerType: (*PaymentServer)(nil), 310 | Methods: []grpc.MethodDesc{ 311 | { 312 | MethodName: "Pay", 313 | Handler: _Payment_Pay_Handler, 314 | }, 315 | }, 316 | Streams: []grpc.StreamDesc{}, 317 | Metadata: "payment.proto", 318 | } 319 | -------------------------------------------------------------------------------- /internal/pkg/signal/signal.go: -------------------------------------------------------------------------------- 1 | package signal 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | ) 9 | 10 | // Notifier holds the context and channels to listen to the notifications 11 | type Notifier struct { 12 | done chan struct{} 13 | sig chan os.Signal 14 | } 15 | 16 | // New creates a new signal notifier, if no signal is supplied, it will use 17 | // the default signals, which are: os.Interrupt and syscall.SIGTERM 18 | func New(ctx context.Context, signals ...os.Signal) *Notifier { 19 | if signals == nil { 20 | // default signals 21 | signals = []os.Signal{ 22 | os.Interrupt, 23 | syscall.SIGTERM, 24 | } 25 | } 26 | 27 | signaler := Notifier{ 28 | done: make(chan struct{}), 29 | sig: make(chan os.Signal), 30 | } 31 | 32 | signal.Notify(signaler.sig, signals...) 33 | 34 | go signaler.listenToSignal(ctx) 35 | 36 | return &signaler 37 | } 38 | 39 | // listenToSignal is a blocking statement that listens to two channels: 40 | // 41 | // - s.sig: is the os.Signal that will the triggered by the signal.Notify once 42 | // the expected signals are executed by the OS in the service 43 | // - ctx.Done(): in case of close of context, the service should also shutdown 44 | func (s *Notifier) listenToSignal(ctx context.Context) { 45 | for { 46 | select { 47 | case <-s.sig: 48 | s.done <- struct{}{} 49 | return 50 | case <-ctx.Done(): 51 | s.done <- struct{}{} 52 | return 53 | } 54 | } 55 | } 56 | 57 | // Done returns the call of the done channel 58 | func (s *Notifier) Done() <-chan struct{} { return s.done } 59 | -------------------------------------------------------------------------------- /internal/pkg/tracing/context.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "context" 5 | 6 | "go.opentelemetry.io/otel/api/global" 7 | "go.opentelemetry.io/otel/api/trace" 8 | ) 9 | 10 | type tracingKeyType int 11 | 12 | const tracingKey tracingKeyType = iota 13 | 14 | func NewContext(ctx context.Context, t trace.Tracer) context.Context { 15 | return context.WithValue(ctx, tracingKey, t) 16 | } 17 | 18 | func WithContext(ctx context.Context) trace.Tracer { 19 | if ctxTracing, ok := ctx.Value(tracingKey).(trace.Tracer); ok { 20 | return ctxTracing 21 | } 22 | 23 | return global.Tracer("main") 24 | } 25 | 26 | func Start(ctx context.Context, name string) (context.Context, trace.Span) { 27 | tr := WithContext(ctx) 28 | return tr.Start(ctx, name) 29 | } 30 | -------------------------------------------------------------------------------- /internal/pkg/tracing/middleware.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go.opentelemetry.io/otel/api/correlation" 7 | "go.opentelemetry.io/otel/api/trace" 8 | "go.opentelemetry.io/otel/plugin/httptrace" 9 | ) 10 | 11 | // Tracing middleware setting a value on the request context 12 | func Tracing(next http.Handler) http.Handler { 13 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 | tr := WithContext(r.Context()) 15 | attrs, entries, spanCtx := httptrace.Extract(r.Context(), r) 16 | 17 | r = r.WithContext(correlation.ContextWithMap(r.Context(), correlation.NewMap(correlation.MapUpdate{ 18 | MultiKV: entries, 19 | }))) 20 | 21 | ctx, span := tr.Start( 22 | trace.ContextWithRemoteSpanContext(r.Context(), spanCtx), 23 | r.URL.String(), 24 | trace.WithAttributes(attrs...), 25 | ) 26 | defer span.End() 27 | 28 | next.ServeHTTP(w, r.WithContext(ctx)) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /internal/pkg/tracing/tracer.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "go.opentelemetry.io/otel/exporters/trace/jaeger" 5 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 6 | ) 7 | 8 | // InitTracer creates a new trace provider instance. 9 | func InitTracer(addr string, serviceName string) (*sdktrace.Provider, func(), error) { 10 | // Create and install Jaeger export pipeline 11 | return jaeger.NewExportPipeline( 12 | jaeger.WithCollectorEndpoint(addr), 13 | jaeger.WithProcess(jaeger.Process{ 14 | ServiceName: serviceName, 15 | }), 16 | jaeger.WithSDK(&sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}), 17 | ) 18 | } 19 | 20 | --------------------------------------------------------------------------------