Notes: Go Design Patterns - Mediator Pattern

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

ยท

3 min read

Table of contents

No heading

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()
}
ย