Design patterns in Go: Facade

01 Jan 2021

There are often cases, when the service objects that you add to your codebase grows huge. That’s not a bad thing actually, especially if those are SOLID designed components, the only problem that still remains is how do you use them?

The first thing that comes to mind, is that to use them as is, but there could be oftern scenarions, where this kind of usage will repeat around the codebase. There is where a Facade can be useful

Problem

The problem of bloating service objects is quite common, as well as their usage. Let’s imagine that you are working on an ecommerce app, that is connected to a payment service, fulfilment service and order tracking service.

Now there might be handlers that will call only payment and fulfilment service (for instance), other handlers will call all of the services. In both cases the payment and order management service will be called, thus this logic can be encapsulated to a separate class, and then used by client.

Solution

This is where Facade can be very handy. The basic idea for this design pattern is to encapsulate repeating code for some specific processes, and expose it via an interface.

Taking the case we discussed before, let’s see how this can be used in code. Let’s define the payments service struct:

type PaymentsService struct {
	AuthToken string
}

func NewPaymentsService(token string) *PaymentsService {
	return &PaymentsService{token}
}

func (ps PaymentsService) Execute(amount int, sender string) error {
	fmt.Printf("Transact $%d from `%s` to company account ID\n", amount, sender)
	return nil
}

then the fulfillment service:

type FulfilmentService struct {
	AuthToken string
}

func NewFulfilmentService(token string) *FulfilmentService {
	return &FulfilmentService{token}
}

func (fs FulfilmentService) CreatePackage(products []Product) string {
	packageId := strconv.Itoa(rand.Intn(3500-2000) + 2000)
	fmt.Println("Creating new package with ID:", packageId)

	return packageId
}

and the order tracking service of course:

type OrderTrackingService struct {
	AuthToken string
}

func NewOrderTrackingService(token string) *OrderTrackingService {
	return &OrderTrackingService{token}
}

func (ots OrderTrackingService) TrackPackage(id string) error {
	// Subscribing via webhooks, for instance
	fmt.Println("Subscribed to track package with ID:", id)
	return nil
}

These services are pretty straightforward, and are created to illustrate how the whole system should be integrated, without going too deep in implementation. Next thing we want, is the facade, which we will define as package manager:

type Order struct {
	Products []Product
	BuyerID  string
	Amount   int
}

type Product struct {
	Name string
}

type PackageManager struct {
}

func (sm PackageManager) PayAndPreparePackage(order Order) (string, error) {
	var err error

	paymentService := NewPaymentsService(os.Getenv("PAYMENT_SERVICE_TOKEN"))
	err = paymentService.Execute(order.Amount, order.BuyerID)
	if err != nil {
		return "", err
	}

	fulfilmentService := NewFulfilmentService(os.Getenv("FULFILMENT_SERVICE_TOKEN"))
	packageId := fulfilmentService.CreatePackage(order.Products)

	return packageId, err
}

Here I also added the Order and Product struct, with which the PackageManager operates.

The client that will use this facade, will have the following logic:

	order := Order{
		Products: []Product{
			Product{"iPhone"},
			Product{"iWatch"},
		},
		BuyerID: "customer_account_id",
		Amount:  200,
	}

	manager := PackageManager{}
	packageId, err := manager.PayAndPreparePackage(order)
	if err != nil {
		panic("Unable to create package: " + packageId)
		return
	}

	ots := NewOrderTrackingService(os.Getenv("ORDER_TRACKING_SERVICE_TOKEN"))
	err = ots.TrackPackage(packageId)
	if err != nil {
		panic("Unable to track package: " + packageId)
		return
	}

If we will need to encapsulate all services, we will create a separate facade for that purpose.

Conclusion

The facades are extremely useful to remove repeatable code. The implementation might differ, for instance, a facade can hold all the service objects that it operates with as instance fields, which could be injected on facade construction. The main idea, still, is that it should encapsulate repeatable logic that is around the codebase, in order to make it cleaner.