Workspaces and Multi-Module Development

Overview

Go workspaces, introduced in Go 1.18, solve the challenge of developing multiple related modules simultaneously. With go.work, you can work on a main project and its dependencies together without modifying go.mod files.

The Problem Workspaces Solve

Without Workspaces

When developing multiple modules together, you’d need replace directives:

// main-app/go.mod
module github.com/company/main-app

replace github.com/company/shared-lib => ../shared-lib

Problems: - Must edit go.mod (can accidentally commit) - Each developer has different paths - CI/CD requires cleanup

With Workspaces

$ go work init main-app shared-lib
// go.work
go 1.26

use (
    ./main-app
    ./shared-lib
)

Benefits: - No go.mod modifications - Works for entire team - Automatically ignored by Git

Creating a Workspace

Initial Setup

# Directory structure
workspace/
├── go.work           # Workspace file
├── main-app/         # Main application
   ├── go.mod
   └── main.go
└── shared-lib/       # Library module
    ├── go.mod
    └── lib.go
# Create workspace
$ cd workspace
$ go work init ./main-app ./shared-lib

The go.work File

go 1.26

use (
    ./main-app
    ./shared-lib
)

All commands (go build, go test, etc.) now use local modules.

Workspace Commands

go work init

Create a new workspace:

# Initialize with modules
$ go work init ./module1 ./module2

# Initialize empty workspace
$ go work init

go work use

Add modules to workspace:

# Add single module
$ go work use ./new-module

# Add multiple modules
$ go work use ./mod1 ./mod2

# Recursively find and add all modules
$ go work use -r .

go work edit

Programmatically modify workspace:

# Add a use directive
$ go work edit -use ./new-module

# Remove a use directive
$ go work edit -dropuse ./old-module

# Set Go version
$ go work edit -go 1.26

go work sync

Sync workspace modules with their dependencies:

$ go work sync

This updates each module’s go.mod to match workspace requirements.

Multi-Module Development Workflow

Project Structure

company-workspace/
├── go.work
├── api/
│   ├── go.mod          # module github.com/company/api
│   ├── api.go
│   └── api_test.go
├── core/
│   ├── go.mod          # module github.com/company/core
│   └── core.go
├── cli/
│   ├── go.mod          # module github.com/company/cli
│   └── main.go
└── internal-tools/
    ├── go.mod          # module github.com/company/tools
    └── tools.go

Setting Up

$ cd company-workspace
$ go work init ./api ./core ./cli ./internal-tools

Development Flow

# Changes in core/ are immediately available to cli/
$ cd cli
$ go build  # Uses local core package

# Run tests across all modules
$ cd ..
$ go test ./...

# Build all binaries
$ go build ./cli/...

Replace vs Workspace

When to Use replace

Use Case Approach
Permanent fork replace in go.mod
CI workaround replace in go.mod
Single dev experiment replace in go.mod

When to Use Workspace

Use Case Approach
Multi-module development go.work
Team development go.work
Monorepo-style work go.work

Workspace with Replace

You can combine both. go.work supports replace:

go 1.26

use (
    ./main-app
    ./shared-lib
)

replace github.com/external/dep => ./custom-fork

Best Practices

1. Git Ignore go.work

# .gitignore
go.work
go.work.sum

Each developer creates their own workspace.

2. Document Setup

Include setup instructions in README:

## Development Setup

1. Clone all repositories:
   ```bash
   git clone github.com/company/api
   git clone github.com/company/core
   git clone github.com/company/cli
  1. Create workspace:

    go work init ./api ./core ./cli

    ```

3. CI Without Workspaces

In CI, build each module independently:

# GitHub Actions
jobs:
  test:
    strategy:
      matrix:
        module: [api, core, cli]
    steps:
      - uses: actions/checkout@v4
      - name: Test
        working-directory: ${{ matrix.module }}
        run: go test ./...

4. Shared Workspace for Monorepos

For monorepos, commit go.work:

monorepo/
├── go.work           # Committed
├── services/
│   ├── api/
│   ├── worker/
│   └── gateway/
└── packages/
    ├── auth/
    └── database/

Common Patterns

Microservices Development

workspace/
├── go.work
├── services/
│   ├── user-service/
│   ├── order-service/
│   └── payment-service/
└── shared/
    ├── proto/
    ├── middleware/
    └── database/
$ go work init
$ go work use -r .

Library Development

Testing a library change across consumers:

workspace/
├── go.work
├── my-library/          # The library
├── app-using-library/   # Consumer 1
└── another-consumer/    # Consumer 2

Troubleshooting

Module Not Found in Workspace

# Verify workspace includes module
$ cat go.work

# Re-add module
$ go work use ./missing-module

Wrong Version Used

Workspace modules take priority. To use published version:

# Temporarily disable workspace
$ GOWORK=off go build

Workspace Mode Detection

Check if workspace is active:

$ go env GOWORK
/path/to/go.work

Summary

Command Purpose
go work init Create workspace
go work use Add modules
go work edit Modify workspace
go work sync Sync dependencies
GOWORK=off Disable workspace

Exercises

  1. Create a workspace with two modules where one depends on the other
  2. Make changes to the dependency and verify the main module sees them
  3. Add a third module to an existing workspace