» Go: Building Event-Driven Microservices with Kafka » 4. Consumer: Recommendation Service » 4.3 Gin API Server

Gin API Server

Add service/recommendation/application/executor/interest_operator.go:

/*
Package executor handles request-response style business logic.
*/
package executor

import (
	"context"

	"literank.com/event-books/service/recommendation/domain/gateway"
	"literank.com/event-books/service/recommendation/domain/model"
)

// InterestOperator handles trend input/output and proxies operations to the interest manager.
type InterestOperator struct {
	interestManager gateway.InterestManager
}

// NewInterestOperator constructs a new InterestOperator
func NewInterestOperator(t gateway.InterestManager) *InterestOperator {
	return &InterestOperator{interestManager: t}
}

// TopTrends gets the top trends order by hits in descending order
func (o *InterestOperator) InterestsForUser(ctx context.Context, userID string) ([]*model.Interest, error) {
	return o.interestManager.ListInterests(ctx, userID)
}

Add service/recommendation/adapter/router.go:

/*
Package adapter adapts to all kinds of framework or protocols.
*/
package adapter

import (
	"net/http"

	"github.com/gin-gonic/gin"

	"literank.com/event-books/service/recommendation/application"
	"literank.com/event-books/service/recommendation/application/executor"
)

const (
	fieldUID = "uid"
)

// RestHandler handles all restful requests
type RestHandler struct {
	interestOperator *executor.InterestOperator
}

func newRestHandler(wireHelper *application.WireHelper) *RestHandler {
	return &RestHandler{
		interestOperator: executor.NewInterestOperator(wireHelper.InterestManager()),
	}
}

// MakeRouter makes the main router
func MakeRouter(wireHelper *application.WireHelper) (*gin.Engine, error) {
	rest := newRestHandler(wireHelper)
	// Create a new Gin router
	r := gin.Default()

	// Define a health endpoint handler
	r.GET("/recommendations", rest.getInterests)
	return r, nil
}

// Get all trends
func (r *RestHandler) getInterests(c *gin.Context) {
	uid := c.Query(fieldUID)
	if uid == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Empty uid is not allowed"})
		return
	}
	trends, err := r.interestOperator.InterestsForUser(c, uid)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get interests for user " + uid})
		return
	}
	c.JSON(http.StatusOK, trends)
}

Start gin server as well in service/recommendation/main.go:

@@ -2,12 +2,15 @@ package main
 
 import (
        "context"
+       "fmt"
        "log"
+       "net/http"
        "os"
        "os/signal"
        "syscall"
 
        "literank.com/event-books/infrastructure/parser"
+       "literank.com/event-books/service/recommendation/adapter"
        "literank.com/event-books/service/recommendation/application"
        "literank.com/event-books/service/recommendation/application/consumer"
        "literank.com/event-books/service/recommendation/infrastructure/config"
@@ -32,13 +35,36 @@ func main() {
        tc := consumer.NewInterestConsumer(wireHelper.InterestManager(), wireHelper.TrendEventConsumer())
        eventConsumer := tc.EventConsumer()
        go func() {
-               // Shutdown signals
-               stopAll := make(chan os.Signal, 1)
-               signal.Notify(stopAll, syscall.SIGINT, syscall.SIGTERM)
+               tc.Start(context.Background())
+
+       }()
+
+       // Build main router
+       r, err := adapter.MakeRouter(wireHelper)
+       if err != nil {
+               panic(err)
+       }
+
+       svr := &http.Server{
+               Addr:    fmt.Sprintf(":%d", c.App.Port),
+               Handler: r,
+       }
+
+       // Shutdown signals
+       stopAll := make(chan os.Signal, 1)
+       signal.Notify(stopAll, syscall.SIGINT, syscall.SIGTERM)
+       go func() {
                <-stopAll
                if err := eventConsumer.Stop(); err != nil {
                        log.Panicf("Failed to close consumer group: %v", err)
                }
+               if err := svr.Shutdown(context.Background()); err != nil {
+                       log.Panicf("Failed to shutdown Gin server: %v", err)
+               }
        }()
-       tc.Start(context.Background())
+
+       // Run the server on the specified port
+       if err := svr.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+               panic(err)
+       }
 }

Run the server and try to hit the URL http://localhost:8082/recommendations?uid=YVM2P with curl.

“YVM2P“ is my cookie uid value. Replace it with yours in the URL.

The result looks like this:

[
  {
    "user_id": "YVM2P",
    "title": "War and Peace",
    "author": "Leo Tolstoy",
    "score": 2
  },
  {
    "user_id": "YVM2P",
    "title": "Pride and Prejudice",
    "author": "Jane Austen",
    "score": 1
  },
  {
    "user_id": "YVM2P",
    "title": "The Odyssey",
    "author": "Homer",
    "score": 1
  }
]
PrevNext