Mastering Golang - Workspaces

My journey into mastering Golang

I've been using Golang for a while now. It started off with me just wanting to learn something new, something snappy, something to quickly develop smaller size projects or scripts. I've come to really like this language, more than I thought at the beginning.

I recognize that Go can be a powerful tool, and I want to try and dive even deeper into its nuances and the whole ecosystem built around it.

This is my journey into mastering Go. And I'm going to try to document as much as I can, both for my future self, my colleagues and for other people that can benefit from the lessons I will learn, and the headaches I'll get from it.


Workspaces

I've been aware of Golang's Workspaces for a while, even used them at some point. But now is the time to dive a bit deeper, and setup my project using this feature.

So we start of by creating our workspace:

go work init

This command creates a go.work file, which for now just contains the version of Go.

// go.work
go 1.21.6

According to the documentation, workspaces can be useful when we have multiple dependent modules on which we are working on at the same time. In this case, we want to avoid having to constantly push our new commits, potentially waiting for pipelines, only to test how our modules interact, and we also want to avoid having to use the replace directive.

Let's try and setup an example where we have our Golang application and a library, which is used by our main program.

We start by creating our two modules workspace-app and workspace-lib

.
├── go.work
├── LICENSE
├── README.md
├── workspace-app
│  └── go.mod
└── workspace-lib
   └── go.mod

And then we run:

go work use workspace-app/ workspace-lib/

Now our go.work contains the following:

go 1.21.6

use (
    ./workspace-app
    ./workspace-lib
)

Now let's write some code for both the library and the application:

# Tree workspace-lib
.
├── go.mod
└── pkg
   └── greeting.go

# Tree workspace-app
.
├── cmd
│  └── greeter
│     └── main.go
├── go.mod
└── go.sum
// pkg/greeting.go
package greeting

type Language int8

const (
    English Language = iota
    Italian
    German
)

func Greet(l Language) string {
    g := map[Language]string{
        English: "Hello!",
        Italian: "Ciao!",
        German:  "Hallo!",
    }
    return g[l]
}
// cmd/greeter/main.go
package main

import (
    "fmt"

    "go.lorenzomilicia.dev/go-master/workspace-lib/pkg/greeting"
)

func main() {
    g := greeting.Greet(greeting.Italian)
    fmt.Println(g)
}

Having pushed the workspace-lib to the remote repository, the application is able to get the package from it and then run the code correctly.

What if I now want to do some changes to the library, and at the same time test the dependency on the app, all without having to push to the remote and by 'pretending' like if I was importing the code from it?

Let's say we added Polish as a new Language :

const (
    English Language = iota
    Italian
    German
    Polish
)

func Greet(l Language) string {
    g := map[Language]string{
        English: "Hello!",
        Italian: "Ciao!",
        German:  "Hallo!",
        Polish:  "Cześć!",
    }
    return g[l]
}

What happens if we try and greet with the newly added language from the workspace-app?

$ go run cmd/greeter/main.go
Cześć!

Wow! Why does that work? We added the new Language only in our local code, yet our application already speaks the new language! So wait, what happens if we comment the workspace-lib from our go.work file?

$ go run cmd/greeter/main.go
# command-line-arguments
cmd/greeter/main.go:10:31: undefined: greeting.Polish

Just like we were expecting.

Conclusion

Golang's workspaces are a neat feature, that comes bundled with all the tools available with the basic installation. It avoids the need to alter the code and the imports during the development process, and therefore reduces mistakes in the code we're pushing to our remote repository and/or deployment system.


Links

Official Go Workspaces Guide

Workspace-lib repository

Workspace-app repository

Inspiration

Maps instead of switch statements