this repo has no description

:sparkles: creating request id middleware and passing context to functions and removed otel

+2 -13
cmd/app/main.go
··· 2 2 3 3 import ( 4 4 "context" 5 - "errors" 6 5 "os" 7 6 "os/signal" 8 7 9 - "github.com/Tulkdan/payment-gateway/internal/lib" 10 8 "github.com/Tulkdan/payment-gateway/internal/providers" 11 9 "github.com/Tulkdan/payment-gateway/internal/service" 12 10 "github.com/Tulkdan/payment-gateway/internal/web" ··· 23 21 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) 24 22 defer stop() 25 23 26 - otelShutdown, err := lib.SetupOTelSDK(ctx) 27 - if err != nil { 28 - return 29 - } 30 - 31 - defer func() { 32 - err = errors.Join(err, otelShutdown(context.Background())) 33 - }() 34 - 35 24 providers := providers.NewUseProviders([]providers.Provider{ 36 25 providers.NewBraintreeProvider(getEnv("BRAINTREE_URL", "localhost:8001")), 37 26 providers.NewStripeProvider(getEnv("STRIPE_URL", "localhost:8002")), ··· 48 37 }() 49 38 50 39 select { 51 - case err = <-srvErr: 40 + case <-srvErr: 52 41 return 53 42 case <-ctx.Done(): 54 43 stop() 55 44 } 56 45 57 - err = server.Shutdown() 46 + server.Shutdown() 58 47 }
-20
go.mod
··· 3 3 go 1.24.4 4 4 5 5 require github.com/google/uuid v1.6.0 6 - 7 - require ( 8 - github.com/felixge/httpsnoop v1.0.4 // indirect 9 - github.com/go-logr/logr v1.4.3 // indirect 10 - github.com/go-logr/stdr v1.2.2 // indirect 11 - go.opentelemetry.io/auto/sdk v1.1.0 // indirect 12 - go.opentelemetry.io/contrib/bridges/otelslog v0.12.0 // indirect 13 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect 14 - go.opentelemetry.io/otel v1.37.0 // indirect 15 - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.13.0 // indirect 16 - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 // indirect 17 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 // indirect 18 - go.opentelemetry.io/otel/log v0.13.0 // indirect 19 - go.opentelemetry.io/otel/metric v1.37.0 // indirect 20 - go.opentelemetry.io/otel/sdk v1.37.0 // indirect 21 - go.opentelemetry.io/otel/sdk/log v0.13.0 // indirect 22 - go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect 23 - go.opentelemetry.io/otel/trace v1.37.0 // indirect 24 - golang.org/x/sys v0.33.0 // indirect 25 - )
-35
go.sum
··· 1 - github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 2 - github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 3 - github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 4 - github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 5 - github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 6 - github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 7 - github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 8 1 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 9 2 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 10 - go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 11 - go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 12 - go.opentelemetry.io/contrib/bridges/otelslog v0.12.0 h1:lFM7SZo8Ce01RzRfnUFQZEYeWRf/MtOA3A5MobOqk2g= 13 - go.opentelemetry.io/contrib/bridges/otelslog v0.12.0/go.mod h1:Dw05mhFtrKAYu72Tkb3YBYeQpRUJ4quDgo2DQw3No5A= 14 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= 15 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= 16 - go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= 17 - go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= 18 - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.13.0 h1:yEX3aC9KDgvYPhuKECHbOlr5GLwH6KTjLJ1sBSkkxkc= 19 - go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.13.0/go.mod h1:/GXR0tBmmkxDaCUGahvksvp66mx4yh5+cFXgSlhg0vQ= 20 - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iLZEPtdV7vie80Yyqf7oikJLjQ/myi0= 21 - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU= 22 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 h1:SNhVp/9q4Go/XHBkQ1/d5u9P/U+L1yaGPoi0x+mStaI= 23 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0/go.mod h1:tx8OOlGH6R4kLV67YaYO44GFXloEjGPZuMjEkaaqIp4= 24 - go.opentelemetry.io/otel/log v0.13.0 h1:yoxRoIZcohB6Xf0lNv9QIyCzQvrtGZklVbdCoyb7dls= 25 - go.opentelemetry.io/otel/log v0.13.0/go.mod h1:INKfG4k1O9CL25BaM1qLe0zIedOpvlS5Z7XgSbmN83E= 26 - go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= 27 - go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= 28 - go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= 29 - go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= 30 - go.opentelemetry.io/otel/sdk/log v0.13.0 h1:I3CGUszjM926OphK8ZdzF+kLqFvfRY/IIoFq/TjwfaQ= 31 - go.opentelemetry.io/otel/sdk/log v0.13.0/go.mod h1:lOrQyCCXmpZdN7NchXb6DOZZa1N5G1R2tm5GMMTpDBw= 32 - go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= 33 - go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= 34 - go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= 35 - go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= 36 - golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 37 - golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-96
internal/lib/openTelemetry.go
··· 1 - package lib 2 - 3 - import ( 4 - "context" 5 - "errors" 6 - "time" 7 - 8 - "go.opentelemetry.io/otel" 9 - "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" 10 - "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" 11 - "go.opentelemetry.io/otel/log/global" 12 - "go.opentelemetry.io/otel/propagation" 13 - "go.opentelemetry.io/otel/sdk/log" 14 - "go.opentelemetry.io/otel/sdk/trace" 15 - ) 16 - 17 - // setupOTelSDK bootstraps the OpenTelemetry pipeline. 18 - // If it does not return an error, make sure to call shutdown for proper cleanup. 19 - func SetupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, err error) { 20 - var shutdownFuncs []func(context.Context) error 21 - 22 - // shutdown calls cleanup functions registered via shutdownFuncs. 23 - // The errors from the calls are joined. 24 - // Each registered cleanup will be invoked once. 25 - shutdown = func(ctx context.Context) error { 26 - var err error 27 - for _, fn := range shutdownFuncs { 28 - err = errors.Join(err, fn(ctx)) 29 - } 30 - shutdownFuncs = nil 31 - return err 32 - } 33 - 34 - // handleErr calls shutdown for cleanup and makes sure that all errors are returned. 35 - handleErr := func(inErr error) { 36 - err = errors.Join(inErr, shutdown(ctx)) 37 - } 38 - 39 - // Set up propagator. 40 - prop := newPropagator() 41 - otel.SetTextMapPropagator(prop) 42 - 43 - // Set up trace provider. 44 - tracerProvider, err := newTracerProvider() 45 - if err != nil { 46 - handleErr(err) 47 - return 48 - } 49 - shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) 50 - otel.SetTracerProvider(tracerProvider) 51 - 52 - // Set up logger provider. 53 - loggerProvider, err := newLoggerProvider() 54 - if err != nil { 55 - handleErr(err) 56 - return 57 - } 58 - shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown) 59 - global.SetLoggerProvider(loggerProvider) 60 - 61 - return 62 - } 63 - 64 - func newPropagator() propagation.TextMapPropagator { 65 - return propagation.NewCompositeTextMapPropagator( 66 - propagation.TraceContext{}, 67 - propagation.Baggage{}, 68 - ) 69 - } 70 - 71 - func newTracerProvider() (*trace.TracerProvider, error) { 72 - traceExporter, err := stdouttrace.New( 73 - stdouttrace.WithPrettyPrint()) 74 - if err != nil { 75 - return nil, err 76 - } 77 - 78 - tracerProvider := trace.NewTracerProvider( 79 - trace.WithBatcher(traceExporter, 80 - // Default is 5s. Set to 1s for demonstrative purposes. 81 - trace.WithBatchTimeout(time.Second)), 82 - ) 83 - return tracerProvider, nil 84 - } 85 - 86 - func newLoggerProvider() (*log.LoggerProvider, error) { 87 - logExporter, err := stdoutlog.New() 88 - if err != nil { 89 - return nil, err 90 - } 91 - 92 - loggerProvider := log.NewLoggerProvider( 93 - log.WithProcessor(log.NewBatchProcessor(logExporter)), 94 - ) 95 - return loggerProvider, nil 96 - }
+10 -2
internal/providers/braintree.go
··· 2 2 3 3 import ( 4 4 "bytes" 5 + "context" 5 6 "encoding/json" 6 7 "net/http" 7 8 ··· 37 38 PaymentMethod BraintreeChargePaymentMethod `json:"paymentMethod"` 38 39 } 39 40 40 - func (b BraintreeProvider) Charge(request *domain.Payment) (*domain.Provider, error) { 41 + func (b BraintreeProvider) Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) { 41 42 body := b.createChargeBody(request) 42 - response, err := http.Post(b.Url+"/charges", "application/json", bytes.NewBuffer(body)) 43 + req, err := http.NewRequestWithContext(ctx, http.MethodPost, b.Url+"/charges", bytes.NewBuffer(body)) 43 44 if err != nil { 44 45 return nil, err 45 46 } 46 47 48 + req.Header.Set("Content-Type", "application/json") 49 + req.Header.Set("transaction-id", ctx.Value("request-id").(string)) 50 + 51 + response, err := http.DefaultClient.Do(req) 52 + if err != nil { 53 + return nil, err 54 + } 47 55 return b.responseCharge(response) 48 56 } 49 57
+4 -1
internal/providers/braintree_test.go
··· 1 1 package providers_test 2 2 3 3 import ( 4 + "context" 4 5 "encoding/json" 5 6 "net/http" 6 7 "net/http/httptest" ··· 17 18 t.Run("should make request to url", func(t *testing.T) { 18 19 id, _ := uuid.Parse("2ee70bcb-5cb9-4412-a35f-c2a15fb88ef1") 19 20 cardId, _ := uuid.Parse("ed6ecd4c-81d5-4e63-bb12-99439ae559e7") 21 + ctx := context.WithValue(t.Context(), "request-id", uuid.New().String()) 22 + 20 23 serverResponse := &providers.BraintreeChargeResponse{ 21 24 Id: id, 22 25 CreatedAt: time.Now().Format("YYYY-MM-DD"), ··· 54 57 } 55 58 56 59 provider := providers.NewBraintreeProvider(server.URL) 57 - response, err := provider.Charge(charge) 60 + response, err := provider.Charge(ctx, charge) 58 61 59 62 if err != nil { 60 63 t.Fatalf("got an error but didn't want one %q", err)
+6 -5
internal/providers/provider.go
··· 1 1 package providers 2 2 3 3 import ( 4 + "context" 4 5 "errors" 5 6 "time" 6 7 ··· 10 11 var thirtySecondTimout = 30 * time.Second 11 12 12 13 type Provider interface { 13 - Charge(request *domain.Payment) (*domain.Provider, error) 14 + Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) 14 15 } 15 16 16 17 type UseProviders struct { ··· 29 30 } 30 31 } 31 32 32 - func (p *UseProviders) Payment(payment *domain.Payment) (*domain.Provider, error) { 33 + func (p *UseProviders) Payment(ctx context.Context, payment *domain.Payment) (*domain.Provider, error) { 33 34 var err error = nil 34 35 35 36 for _, provider := range p.providers { 36 37 select { 37 - case data := <-charge(payment, provider): 38 + case data := <-charge(ctx, payment, provider): 38 39 return data, nil 39 40 case <-time.After(p.timeout): 40 41 err = errors.New("Timeout") ··· 45 46 return nil, err 46 47 } 47 48 48 - func charge(charge *domain.Payment, provider Provider) chan *domain.Provider { 49 + func charge(ctx context.Context, charge *domain.Payment, provider Provider) chan *domain.Provider { 49 50 ch := make(chan *domain.Provider) 50 51 51 52 go func() { 52 - response, err := provider.Charge(charge) 53 + response, err := provider.Charge(ctx, charge) 53 54 if err != nil { 54 55 close(ch) 55 56 return
+5 -4
internal/providers/provider_test.go
··· 1 1 package providers_test 2 2 3 3 import ( 4 + "context" 4 5 "testing" 5 6 "time" 6 7 ··· 14 15 Response *domain.Provider 15 16 } 16 17 17 - func (s *SpyProvider) Charge(request *domain.Payment) (*domain.Provider, error) { 18 + func (s *SpyProvider) Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) { 18 19 time.Sleep(s.Timeout) 19 20 s.Calls++ 20 21 ··· 29 30 payment, _ := domain.NewPayment(1000, "R$", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1}) 30 31 31 32 useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, 15*time.Millisecond) 32 - data, err := useProvider.Payment(payment) 33 + data, err := useProvider.Payment(context.Background(), payment) 33 34 34 35 if err != nil { 35 36 t.Fatal("Got error. didn't want one") ··· 50 51 payment, _ := domain.NewPayment(1000, "R$", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1}) 51 52 52 53 useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, 15*time.Millisecond) 53 - data, err := useProvider.Payment(payment) 54 + data, err := useProvider.Payment(context.Background(), payment) 54 55 55 56 if err != nil { 56 57 t.Fatal("Got error. didn't want one") ··· 71 72 payment, _ := domain.NewPayment(1000, "R$", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1}) 72 73 73 74 useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, 5*time.Millisecond) 74 - data, err := useProvider.Payment(payment) 75 + data, err := useProvider.Payment(context.Background(), payment) 75 76 76 77 if data != nil { 77 78 t.Fatalf("Got data but didn't expected one, got %q", data)
+11 -2
internal/providers/stripe.go
··· 2 2 3 3 import ( 4 4 "bytes" 5 + "context" 5 6 "encoding/json" 6 7 "net/http" 7 8 ··· 33 34 Card StripeChargeCard `json:"card"` 34 35 } 35 36 36 - func (b StripeProvider) Charge(request *domain.Payment) (*domain.Provider, error) { 37 + func (b StripeProvider) Charge(ctx context.Context, request *domain.Payment) (*domain.Provider, error) { 37 38 body := b.createChargeBody(request) 38 - response, err := http.Post(b.Url+"/transactions", "application/json", bytes.NewBuffer(body)) 39 + req, err := http.NewRequestWithContext(ctx, http.MethodPost, b.Url+"/transactions", bytes.NewBuffer(body)) 40 + if err != nil { 41 + return nil, err 42 + } 43 + 44 + req.Header.Set("Content-Type", "application/json") 45 + req.Header.Set("transaction-id", ctx.Value("request-id").(string)) 46 + 47 + response, err := http.DefaultClient.Do(req) 39 48 if err != nil { 40 49 return nil, err 41 50 }
+4 -1
internal/providers/stripe_test.go
··· 1 1 package providers_test 2 2 3 3 import ( 4 + "context" 4 5 "encoding/json" 5 6 "net/http" 6 7 "net/http/httptest" ··· 17 18 t.Run("should make request to url", func(t *testing.T) { 18 19 id, _ := uuid.Parse("2ee70bcb-5cb9-4412-a35f-c2a15fb88ef1") 19 20 cardId, _ := uuid.Parse("ed6ecd4c-81d5-4e63-bb12-99439ae559e7") 21 + ctx := context.WithValue(t.Context(), "request-id", uuid.New().String()) 22 + 20 23 serverResponse := &providers.StripeChargeResponse{ 21 24 Id: id, 22 25 CreatedAt: time.Now().Format("YYYY-MM-DD"), ··· 54 57 } 55 58 56 59 provider := providers.NewStripeProvider(server.URL) 57 - response, err := provider.Charge(charge) 60 + response, err := provider.Charge(ctx, charge) 58 61 59 62 if err != nil { 60 63 t.Fatalf("got an error but didn't want one %q", err)
+1 -1
internal/service/payment_service.go
··· 22 22 return nil, err 23 23 } 24 24 25 - _, err = p.providers.Payment(payment) 25 + _, err = p.providers.Payment(ctx, payment) 26 26 if err != nil { 27 27 return nil, err 28 28 }
+15
internal/web/middleware/transactionId.go
··· 1 + package middleware 2 + 3 + import ( 4 + "context" 5 + "net/http" 6 + 7 + "github.com/google/uuid" 8 + ) 9 + 10 + func WithRequestId(next http.HandlerFunc) http.HandlerFunc { 11 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 + ctx := context.WithValue(r.Context(), "request-id", uuid.New().String()) 13 + next.ServeHTTP(w, r.WithContext(ctx)) 14 + }) 15 + }
+4 -9
internal/web/server.go
··· 8 8 9 9 "github.com/Tulkdan/payment-gateway/internal/service" 10 10 "github.com/Tulkdan/payment-gateway/internal/web/handler" 11 - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" 11 + "github.com/Tulkdan/payment-gateway/internal/web/middleware" 12 12 ) 13 13 14 14 type Server struct { 15 15 port string 16 - router http.Handler 16 + router *http.ServeMux 17 17 server *http.Server 18 18 19 19 paymentsService *service.PaymentService ··· 29 29 func (s *Server) ConfigureRouter() { 30 30 mux := http.NewServeMux() 31 31 32 - handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) { 33 - handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc)) 34 - mux.Handle(pattern, handler) 35 - } 36 - 37 32 paymentsHandler := handler.NewPaymentsHandler(s.paymentsService) 38 33 39 - handleFunc("POST /payments", paymentsHandler.Create) 34 + mux.HandleFunc("POST /payments", middleware.WithRequestId(paymentsHandler.Create)) 40 35 // r.HandleFunc("POST /refunds", func(http.ResponseWriter, *http.Request) {}) 41 36 // r.HandleFunc("GET /payments/{id}", func(w http.ResponseWriter, r *http.Request) { 42 37 // id := r.PathValue("id") 43 38 // }) 44 39 45 - s.router = otelhttp.NewHandler(mux, "/") 40 + s.router = mux 46 41 } 47 42 48 43 func (s *Server) Start(ctx context.Context) error {