» Go: Building Event-Driven Microservices with Kafka » 3. Consumer: Trend Service » 3.4 Service Integration

Service Integration

Let‘s call the Trend Service to show trends from the bookstore Web Service.

Add config items in service/web/infrastructure/config/config.go:

@@ -5,9 +5,10 @@ package config
 
 // Config is the global configuration.
 type Config struct {
-       App ApplicationConfig `json:"app" yaml:"app"`
-       DB  DBConfig          `json:"db" yaml:"db"`
-       MQ  MQConfig          `json:"mq" yaml:"mq"`
+       App    ApplicationConfig   `json:"app" yaml:"app"`
+       DB     DBConfig            `json:"db" yaml:"db"`
+       MQ     MQConfig            `json:"mq" yaml:"mq"`
+       Remote RemoteServiceConfig `json:"remote" yaml:"remote"`
 }
 
 // DBConfig is the configuration of databases.
@@ -27,3 +28,8 @@ type MQConfig struct {
        Brokers []string `json:"brokers" yaml:"brokers"`
        Topic   string   `json:"topic" yaml:"topic"`
 }
+
+// RemoteServiceConfig is the configuration of remote services.
+type RemoteServiceConfig struct {
+       TrendURL string `json:"trend_url" yaml:"trend_url"`
+}

Put in config values in service/web/config.yml:

@@ -8,3 +8,5 @@ mq:
   brokers:
     - localhost:9094
   topic: "lr-book-searches"
+remote:
+  trend_url: "http://localhost:8081/trends"

Fetch remote HTTP services in infrastructure/remote/service.go:

/*
Package remote provides remote service communication funcs.
*/
package remote

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"time"
)

const defaultTimeout = 5 * time.Second

// HttpFetch fetches resources from a http URL.
func HttpFetch(ctx context.Context, url string, result interface{}) error {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return err
	}
	client := &http.Client{
		Timeout: defaultTimeout,
	}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal(body, result)
	if err != nil {
		return err
	}
	return nil
}

Provide the call func for the trend service in service/web/application/executor/book_operator.go:

@@ -10,6 +10,7 @@ import (
 
        "literank.com/event-books/domain/model"
        "literank.com/event-books/infrastructure/mq"
+       "literank.com/event-books/infrastructure/remote"
        "literank.com/event-books/service/web/domain/gateway"
 )
 
@@ -50,3 +51,12 @@ func (o *BookOperator) GetBooks(ctx context.Context, offset int, query string) (
        }
        return books, nil
 }
+
+// GetTrends gets a list of trends from the remote service
+func (o *BookOperator) GetTrends(ctx context.Context, trendURL string) ([]*model.Trend, error) {
+       var trends []*model.Trend
+       if err := remote.HttpFetch(ctx, trendURL, &trends); err != nil {
+               return nil, err
+       }
+       return trends, nil
+}

Integrate the trend service results in service/web/adapter/router.go:

@@ -5,6 +5,7 @@ package adapter
 
 import (
        "fmt"
+       "log"
        "net/http"
        "strconv"
 
@@ -13,6 +14,7 @@ import (
        "literank.com/event-books/domain/model"
        "literank.com/event-books/service/web/application"
        "literank.com/event-books/service/web/application/executor"
+       "literank.com/event-books/service/web/infrastructure/config"
 )
 
 const (
@@ -22,18 +24,20 @@ const (
 
 // RestHandler handles all restful requests
 type RestHandler struct {
+       remoteConfig *config.RemoteServiceConfig
        bookOperator *executor.BookOperator
 }
 
-func newRestHandler(wireHelper *application.WireHelper) *RestHandler {
+func newRestHandler(remote *config.RemoteServiceConfig, wireHelper *application.WireHelper) *RestHandler {
        return &RestHandler{
+               remoteConfig: remote,
                bookOperator: executor.NewBookOperator(wireHelper.BookManager(), wireHelper.MessageQueueHelper()),
        }
 }
 
 // MakeRouter makes the main router
-func MakeRouter(templates_pattern string, wireHelper *application.WireHelper) (*gin.Engine, error) {
-       rest := newRestHandler(wireHelper)
+func MakeRouter(templates_pattern string, remote *config.RemoteServiceConfig, wireHelper *application.WireHelper) (*gin.Engine, error) {
+       rest := newRestHandler(remote, wireHelper)
        // Create a new Gin router
        r := gin.Default()
        // Load HTML templates from the templates directory
@@ -56,11 +60,18 @@ func (r *RestHandler) indexPage(c *gin.Context) {
                c.String(http.StatusNotFound, "failed to get books")
                return
        }
+       trends, err := r.bookOperator.GetTrends(c, r.remoteConfig.TrendURL)
+       if err != nil {
+               // It's not a must-have, just log the error
+               log.Printf("Failed to get trends: %v", err)
+               trends = make([]*model.Trend, 0)
+       }
        // Render the HTML template named "index.html"
        c.HTML(http.StatusOK, "index.html", gin.H{
-               "title": "LiteRank Book Store",
-               "books": books,
-               "q":     q,
+               "title":  "LiteRank Book Store",
+               "books":  books,
+               "trends": trends,
+               "q":      q,
        })
 }

Pass in config items in service/web/main.go:

@@ -25,7 +25,7 @@ func main() {
        }
 
        // Build main router
-       r, err := adapter.MakeRouter(c.App.TemplatesPattern, wireHelper)
+       r, err := adapter.MakeRouter(c.App.TemplatesPattern, &c.Remote, wireHelper)
        if err != nil {
                panic(err)
        }

Render the trends in service/web/adapter/templates/index.html:

@@ -38,17 +38,19 @@
         <!-- Trends Section -->
         <div class="mb-8">
             <h2 class="text-2xl font-bold mb-4">Trends</h2>
-            <div class="grid grid-cols-3 gap-4">
-                <!-- Trend items can be dynamically generated here -->
-                <div class="bg-white p-4 rounded-md border-gray-300 shadow">
-                    Book 1
-                </div>
-                <div class="bg-white p-4 rounded-md border-gray-300 shadow">
-                    Book 2
-                </div>
-                <div class="bg-white p-4 rounded-md border-gray-300 shadow">
-                    Book 3
-                </div>
+            <div class="grid grid-cols-5 gap-2">
+                {{range .trends}}
+                    <div class="bg-white p-4 rounded-md border-gray-300 shadow mt-2">
+                        <div>#<b><a href="/?q={{.Query}}">{{.Query}}</a></b></div>
+                        {{range .Books}}
+                            <div class="font-serif border-t py-1 mt-1">
+                                {{.Title}}
+                                <span class="italic text-sm text-gray-500">by</span>
+                                <span class="font-mono italic text-sm">{{.Author}}</span>
+                            </div>
+                        {{end}}
+                    </div>
+                {{end}}
             </div>
         </div>

Restart your web and trend services, you should see something like this on the index page.

Trends

First service is integrated successfully! 🎉!