Notes: Go Design Patterns - Mediator Pattern
This blog contains the implementation of Mediator design pattern by using ServiceCaller as a mediator between different service calls
Table of contents
No headings in the article.
Mediator pattern is used to reduce communication complexity between multiple objects or classes. This pattern provides a mediator class that typically handles all the communications between different classes and supports the easy maintainability of the code by loose coupling. This is a behavioral pattern.
In our example, we are going to use ServiceCaller
object to decouple communication between different services. ServiceCaller
contains the default go HTTP client. We can replace it with whatever client we want without affecting the code on the objects' communication side.
type ServiceCaller struct {
httpClient *http.Client
}
Here we have two external services USER_DETAIL_SERVICE
and USER_POST_SERVICE
. And to handle these services, we have created two handlers in our current service UserServiceHandler
and PostServiceHandler
. These handlers wrap ServiceCaller
to be used as a mediator.
type UserServiceHandler struct {
sc ServiceCaller
}
type PostServiceHandler struct {
sc ServiceCaller
}
In UserServiceHandler
we have GetAllUsers
method which calls USER_DETAIL_SERVICE
to get the data of all the users.
func (h *UserServiceHandler) GetAllUsers() {
resp, err := h.sc.Get(USER_DETAIL_SERVICE + "/users")
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Printf("%#v\n\n", string(resp))
}
}
Likewise, PostServiceHandler
has SavePost
which saves posts by calling USER_POST_SERVICE
.
func (h *PostServiceHandler) SavePost(payload string) {
resp, err := h.sc.Post(USER_POST_SERVICE+"/posts", payload)
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Printf("%#v\n\n", string(resp))
}
}
Now to communicate with the external services, we use ServiceCaller
object as the mediator. ServiceCaller
takes care of all the logic required to make an external call (e.g. HTTP methods) and returns the service response in the required format. In our implementation, we have created Get
and Post
methods for GET and POST method calls respectively.
Complete working code where you can see the complete result:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
)
// ServiceCaller is a mediator
type ServiceCaller struct {
httpClient *http.Client
}
func NewServiceCaller() *ServiceCaller {
return &ServiceCaller{
httpClient: http.DefaultClient,
}
}
func (sc *ServiceCaller) Get(url string) ([]byte, error) {
resp, err := sc.httpClient.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}
func (sc *ServiceCaller) Post(url, JSONPayload string) ([]byte, error) {
postData := bytes.NewBuffer([]byte(JSONPayload))
resp, err := sc.httpClient.Post(url, "application/json", postData)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}
// ServiceCallers to call different services wrapping our mediator
// service URIs
const (
USER_DETAIL_SERVICE = "https://jsonplaceholder.typicode.com"
USER_POST_SERVICE = "https://jsonplaceholder.typicode.com"
)
type UserServiceHandler struct {
sc ServiceCaller
}
func (h *UserServiceHandler) GetAllUsers() {
resp, err := h.sc.Get(USER_DETAIL_SERVICE + "/users")
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Printf("%#v\n\n", string(resp))
}
}
type PostServiceHandler struct {
sc ServiceCaller
}
func (h *PostServiceHandler) SavePost(payload string) {
resp, err := h.sc.Post(USER_POST_SERVICE+"/posts", payload)
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Printf("%#v\n\n", string(resp))
}
}
func main() {
serviceCaller := NewServiceCaller()
// can use constructor instead
userServiceHandler := UserServiceHandler{
sc: *serviceCaller,
}
postServiceHandler := PostServiceHandler{
sc: *serviceCaller,
}
payload := `{"title": "btree.dev", "body": "mediator pattern", "userId": 1}`
postServiceHandler.SavePost(payload)
userServiceHandler.GetAllUsers()
}