» 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.
First service is integrated successfully! 🎉!
Loading...
> code result goes here