Skip to content

Commit a324c2a

Browse files
committed
Create first handler
1 parent 8b1dad2 commit a324c2a

13 files changed

Lines changed: 304 additions & 22 deletions

File tree

api/go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ go 1.18
44

55
require (
66
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
7+
github.com/davecgh/go-spew v1.1.1
78
github.com/go-redis/redis/v8 v8.11.5
89
github.com/gofiber/fiber/v2 v2.34.0
910
github.com/gofiber/swagger v0.0.1
1011
github.com/joho/godotenv v1.4.0
12+
github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177
1113
github.com/rs/zerolog v1.26.1
1214
github.com/swaggo/swag v1.8.2
1315
go.opentelemetry.io/otel v1.7.0
16+
go.opentelemetry.io/otel/trace v1.7.0
1417
)
1518

1619
require (
@@ -29,7 +32,6 @@ require (
2932
github.com/valyala/bytebufferpool v1.0.0 // indirect
3033
github.com/valyala/fasthttp v1.37.0 // indirect
3134
github.com/valyala/tcplisten v1.0.0 // indirect
32-
go.opentelemetry.io/otel/trace v1.7.0 // indirect
3335
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 // indirect
3436
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
3537
golang.org/x/tools v0.1.10 // indirect

api/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ
7575
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
7676
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
7777
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
78+
github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177 h1:nRlQD0u1871kaznCnn1EvYiMbum36v7hw1DLPEjds4o=
79+
github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177/go.mod h1:ao5zGxj8Z4x60IOVYZUbDSmt3R8Ddo080vEgPosHpak=
7880
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
7981
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
8082
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=

api/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func main() {
2828

2929
app.Use(logger.New())
3030

31-
container := di.NewContainer()
31+
container := di.NewContainer("http-sms")
3232

3333
apiV1 := app.Group("v1")
3434
apiV1.Post("/messages/send", container.MessageHandler().Send)

api/pkg/di/container.go

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,69 @@ import (
55
"os"
66

77
"github.com/NdoleStudio/http-sms-manager/pkg/handlers"
8+
"github.com/NdoleStudio/http-sms-manager/pkg/services"
89
"github.com/NdoleStudio/http-sms-manager/pkg/telemetry"
10+
"github.com/NdoleStudio/http-sms-manager/pkg/validators"
911
"github.com/gofiber/fiber/v2"
1012
"github.com/rs/zerolog"
1113
)
1214

1315
// Container is used to resolve services at runtime
1416
type Container struct {
15-
logger telemetry.Logger
16-
messageHandler *handlers.MessageHandler
17+
projectID string
18+
logger telemetry.Logger
1719
}
1820

1921
// NewContainer creates a new dependency injection container
20-
func NewContainer() (container *Container) {
22+
func NewContainer(projectID string) (container *Container) {
2123
return &Container{
22-
logger: logger().WithService(fmt.Sprintf("%T", container)),
24+
projectID: projectID,
25+
logger: logger().WithService(fmt.Sprintf("%T", container)),
2326
}
2427
}
2528

2629
// Logger creates a new instance of telemetry.Logger
27-
func (container Container) Logger() (l telemetry.Logger) {
28-
container.logger.Debug(fmt.Sprintf("creating %T", l))
30+
func (container Container) Logger() telemetry.Logger {
31+
container.logger.Debug("creating telemetry.Logger")
2932
return logger()
3033
}
3134

35+
// Tracer creates a new instance of telemetry.Tracer
36+
func (container Container) Tracer() (t telemetry.Tracer) {
37+
container.logger.Debug("creating telemetry.Tracer")
38+
return telemetry.NewOtelLogger(
39+
container.projectID,
40+
container.Logger(),
41+
)
42+
}
43+
44+
// MessageHandlerValidator creates a new instance of validators.MessageHandlerValidator
45+
func (container *Container) MessageHandlerValidator() (validator *validators.MessageHandlerValidator) {
46+
container.logger.Debug(fmt.Sprintf("creating %T", validator))
47+
return validators.NewMessageHandlerValidator(
48+
container.Logger(),
49+
container.Tracer(),
50+
)
51+
}
52+
53+
// MessageService creates a new instance of services.MessageService
54+
func (container *Container) MessageService() (service *services.MessageService) {
55+
container.logger.Debug(fmt.Sprintf("creating %T", service))
56+
return services.NewMessageService(
57+
container.Logger(),
58+
container.Tracer(),
59+
)
60+
}
61+
3262
// MessageHandler creates a new instance of handlers.MessageHandler
3363
func (container *Container) MessageHandler() (handler *handlers.MessageHandler) {
34-
if container.messageHandler != nil {
35-
return container.messageHandler
36-
}
37-
3864
container.logger.Debug(fmt.Sprintf("creating %T", handler))
39-
40-
return handlers.NewMessageHandler()
65+
return handlers.NewMessageHandler(
66+
container.Logger(),
67+
container.Tracer(),
68+
container.MessageHandlerValidator(),
69+
container.MessageService(),
70+
)
4171
}
4272

4373
func logger() telemetry.Logger {
Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
11
package handlers
22

3-
import "github.com/gofiber/fiber/v2"
3+
import (
4+
"fmt"
5+
6+
"github.com/NdoleStudio/http-sms-manager/pkg/requests"
7+
"github.com/NdoleStudio/http-sms-manager/pkg/services"
8+
"github.com/NdoleStudio/http-sms-manager/pkg/telemetry"
9+
"github.com/NdoleStudio/http-sms-manager/pkg/validators"
10+
"github.com/davecgh/go-spew/spew"
11+
"github.com/gofiber/fiber/v2"
12+
"github.com/palantir/stacktrace"
13+
)
414

515
// MessageHandler handles message http requests.
616
type MessageHandler struct {
717
handler
18+
logger telemetry.Logger
19+
tracer telemetry.Tracer
20+
validator *validators.MessageHandlerValidator
21+
service *services.MessageService
822
}
923

1024
// NewMessageHandler creates a new MessageHandler
11-
func NewMessageHandler() *MessageHandler {
12-
return &MessageHandler{}
25+
func NewMessageHandler(
26+
logger telemetry.Logger,
27+
tracer telemetry.Tracer,
28+
validator *validators.MessageHandlerValidator,
29+
service *services.MessageService,
30+
) (h *MessageHandler) {
31+
return &MessageHandler{
32+
logger: logger.WithService(fmt.Sprintf("%T", h)),
33+
tracer: tracer,
34+
validator: validator,
35+
service: service,
36+
}
1337
}
1438

1539
// Send a new entities.Message
@@ -20,6 +44,31 @@ func NewMessageHandler() *MessageHandler {
2044
// @Produce json
2145
// @Success 200 {object} responses.MessageResponse
2246
// @Router /messages/send [post]
23-
func (handler *MessageHandler) Send(c *fiber.Ctx) error {
24-
return nil
47+
func (h *MessageHandler) Send(c *fiber.Ctx) error {
48+
ctx, span := h.tracer.StartFromFiberCtx(c)
49+
defer span.End()
50+
51+
ctxLogger := h.tracer.CtxLogger(h.logger, span)
52+
53+
var request requests.MessageSend
54+
if err := c.BodyParser(&request); err != nil {
55+
msg := fmt.Sprintf("cannot marshall [%s] into %T", c.Body(), request)
56+
ctxLogger.Warn(stacktrace.Propagate(err, msg))
57+
return h.responseBadRequest(c, err)
58+
}
59+
60+
if errors := h.validator.ValidateMessageSend(ctx, request); len(errors) != 0 {
61+
msg := fmt.Sprintf("validation errors [%s], while sending payload [%s]", spew.Sdump(errors), c.Body())
62+
ctxLogger.Warn(stacktrace.NewError(msg))
63+
return h.responseUnprocessableEntity(c, errors, "validation errors while sending message")
64+
}
65+
66+
message, err := h.service.Send(ctx, request.ToMessageSendParams())
67+
if err != nil {
68+
msg := fmt.Sprintf("cannot send message with paylod [%s]", c.Body())
69+
ctxLogger.Error(stacktrace.Propagate(err, msg))
70+
return h.responseInternalServerError(c)
71+
}
72+
73+
return h.responseOK(c, "Outgoing message added to queue", message)
2574
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package requests
2+
3+
import "github.com/NdoleStudio/http-sms-manager/pkg/services"
4+
5+
// MessageSend is the payload for sending and SMS message
6+
type MessageSend struct {
7+
From string `json:"from" example:"+18005550199"`
8+
To string `json:"to" example:"+18005550100"`
9+
Content string `json:"content" example:"This is a sample text message"`
10+
}
11+
12+
// ToMessageSendParams converts MessageSend to services.MessageSendParams
13+
func (input MessageSend) ToMessageSendParams() services.MessageSendParams {
14+
return services.MessageSendParams{
15+
From: input.From,
16+
To: input.To,
17+
Content: input.Content,
18+
}
19+
}

api/pkg/services/message.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package services
2+
3+
// MessageSendParams parameters for sending a new message
4+
type MessageSendParams struct {
5+
From string
6+
To string
7+
Content string
8+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,33 @@
11
package services
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/NdoleStudio/http-sms-manager/pkg/entities"
8+
"github.com/NdoleStudio/http-sms-manager/pkg/telemetry"
9+
)
10+
11+
// MessageService is handles message requests
12+
type MessageService struct {
13+
logger telemetry.Logger
14+
tracer telemetry.Tracer
15+
}
16+
17+
// NewMessageService creates a new MessageService
18+
func NewMessageService(
19+
logger telemetry.Logger,
20+
tracer telemetry.Tracer,
21+
) (s *MessageService) {
22+
return &MessageService{
23+
logger: logger.WithService(fmt.Sprintf("%T", s)),
24+
tracer: tracer,
25+
}
26+
}
27+
28+
// Send a new message
29+
func (service *MessageService) Send(ctx context.Context, params MessageSendParams) (*entities.Message, error) {
30+
ctx, span := service.tracer.Start(ctx)
31+
defer span.End()
32+
return nil, nil
33+
}

api/pkg/telemetry/logger.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ type Logger interface {
1111
// WithString creates a new structured logger instance with a key value pair
1212
WithString(key string, value string) Logger
1313

14+
// WithBool creates a new structured logger instance with a key value pair
15+
WithBool(key string, value bool) Logger
16+
1417
// Info logs a new message with information level.
1518
Info(value string)
1619

1720
// Warn logs a new message with warning level.
18-
Warn(value string)
21+
Warn(err error)
1922

2023
// Trace logs a new message with trace level.
2124
Trace(value string)

api/pkg/telemetry/otel_tracer.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package telemetry
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"runtime"
7+
"strings"
8+
9+
"github.com/gofiber/fiber/v2"
10+
"go.opentelemetry.io/otel/attribute"
11+
"go.opentelemetry.io/otel/codes"
12+
"go.opentelemetry.io/otel/trace"
13+
)
14+
15+
type otelTracer struct {
16+
projectID string
17+
logger Logger
18+
}
19+
20+
// NewOtelLogger creates a new Tracer
21+
func NewOtelLogger(projectID string, logger Logger) Tracer {
22+
return &otelTracer{
23+
projectID: projectID,
24+
logger: logger,
25+
}
26+
}
27+
28+
func (tracer *otelTracer) StartFromFiberCtx(_ *fiber.Ctx, name ...string) (context.Context, trace.Span) {
29+
ctx, span := trace.NewNoopTracerProvider().Tracer(tracer.projectID).Start(context.Background(), getName(name...))
30+
defer span.End()
31+
return tracer.Start(ctx, getName(name...))
32+
}
33+
34+
func (tracer *otelTracer) CtxLogger(logger Logger, span trace.Span) Logger {
35+
return logger.
36+
WithString("trace", fmt.Sprintf("projects/%s/traces/%s", tracer.projectID, span.SpanContext().TraceID().String())).
37+
WithString("spanID", span.SpanContext().SpanID().String()).
38+
WithBool("traceSampled", span.SpanContext().IsSampled())
39+
}
40+
41+
func (tracer *otelTracer) Start(c context.Context, name ...string) (context.Context, trace.Span) {
42+
parentSpan := trace.SpanFromContext(c)
43+
ctx, span := parentSpan.TracerProvider().Tracer("").Start(c, getName(name...))
44+
45+
span.SetAttributes(attribute.Key("traceID").String(parentSpan.SpanContext().TraceID().String()))
46+
span.SetAttributes(attribute.Key("SpanID").String(span.SpanContext().SpanID().String()))
47+
span.SetAttributes(attribute.Key("traceFlags").String(parentSpan.SpanContext().TraceFlags().String()))
48+
49+
return ctx, span
50+
}
51+
52+
func (tracer *otelTracer) WrapErrorSpan(span trace.Span, err error) error {
53+
if err == nil {
54+
return nil
55+
}
56+
57+
span.RecordError(err)
58+
span.SetStatus(codes.Error, strings.Split(err.Error(), "\n")[0])
59+
60+
return err
61+
}
62+
63+
func getName(name ...string) string {
64+
if len(name) > 0 {
65+
return name[0]
66+
}
67+
return functionName()
68+
}
69+
70+
func functionName() string {
71+
pc := make([]uintptr, 15)
72+
n := runtime.Callers(4, pc)
73+
frames := runtime.CallersFrames(pc[:n])
74+
frame, _ := frames.Next()
75+
76+
return strings.ReplaceAll(frame.Function, "github.com/NdoleStudio/http-sms-manager/", "")
77+
}

0 commit comments

Comments
 (0)