Go embedding by example

User avatar

banters

I recently learned about Go Embedding of structs and interfaces and I was quite impressed by how powerful it can be. So in the process of learning and exploring I thought it might be good to share what I have learned with a small and hopefully an easy example.

I have divided this example into 3 simple projects which will explain the use case and how embedding can be a really powerful programming paradigm.

The first project is where we define an interface with two methods:

package types

import "context"

type Client interface {
	MethodA(ctx context.Context, message string)

	MethodB(ctx context.Context, message string)
}

And then we also define a service which is used to initialize a client that will implement these methods

package types

import (
	"context"
)

type Service struct {
	client Client
}

func NewService(client Client) *Service {
	return &Service{client: client}
}

func(s *Service) MethodA(ctx context.Context, message string) {
	s.client.MethodA(ctx, message)
}

func(s *Service) MethodB(ctx context.Context, message string) {
	s.client.MethodB(ctx, message)
}

This means that when we initialize using NewService using a client then it will use the client implemented methods.

Now the third project (pardon my ordering, second will come in a bit) is a library which has an implementation of first.Client

package third

import (
	"context"
	"fmt"
	"github.com/shrimalmadhur/playground/interfaces/first/types"
)

type Client struct {
	 client types.Client
}

func (c Client) MethodA(ctx context.Context, message string) {
	fmt.Println(fmt.Sprintf("client impl message MethodA: %s", message))
}

func (c Client) MethodB(ctx context.Context, message string) {
	fmt.Println(fmt.Sprintf("client impl message MethodB: %s", message))
}

func NewClient() Client {
	return Client{}
}

Here is the final second project which brings it all together and where we will see actual power of embedding.

It has a file called client.go which contains

import (
	"context"
	"fmt"
	cl "github.com/shrimalmadhur/playground/interfaces/third"
)

type Client struct {
	cl.Client
}

func (s Client) MethodA(ctx context.Context, message string) {
	fmt.Println(fmt.Sprintf("Override MethodA message: %s", message))
}

func NewClient() Client {
	sdkClient := cl.NewClient()
	return Client {
		sdkClient,
	}
}

Now what you see here is

type Client struct {
	cl.Client
}

This above syntax is called embedding where you import and override the methods of the actual client if you want.

In the NewClient function we initialize the the sdkClient with third.NewClient which will import the two method using Go embedding. But here we have an option to override any of the MethodA or MethodB, so we override MethodA to see what happens.

To run all of this here is our main.go file

package main

import (
	"context"
	"github.com/shrimalmadhur/playground/interfaces/first/types"
	"github.com/shrimalmadhur/playground/interfaces/second/client"
)


func main()  {
	ctx := context.Background()

	myClient := client.NewClient()

	service := types.NewService(myClient)

	service.MethodA(ctx, "hello")

	service.MethodB(ctx, "hello")
}

So now here is what it prints

Override MethodA message: hello
client impl message MethodB: hello

We can see in the above print messages that the first line has been printed using the overridden MethodA in the main package and the second line is printed using the client library in the second package. This way you can import a client package and then override the interface methods as needed.

I hope this makes it easy to understand Go embedding. Any feedback is welcome.

You can see the full code here.

#programming#go#golang#go-embedding
  • Loading comments...