Workspaces: desenvolvendo múltiplos módulos juntos

1. Introdução aos Workspaces no Go

Desenvolvedores Go frequentemente enfrentam o desafio de gerenciar múltiplos módulos interdependentes localmente. Antes dos workspaces, a solução comum era usar a diretiva replace no go.mod para apontar para diretórios locais — uma abordagem funcional, mas que poluía o arquivo de dependências e exigia alterações manuais constantes.

Um workspace no Go é um ambiente que permite desenvolver múltiplos módulos simultaneamente, resolvendo dependências locais de forma transparente. O coração dessa funcionalidade é o arquivo go.work, que define quais módulos fazem parte do workspace e como eles se relacionam.

A estrutura básica de um go.work é simples:

go 1.22

use (
    ./lib
    ./cmd
)

2. Configurando seu Primeiro Workspace

Criar um workspace é direto com o comando go work init. Suponha que temos dois módulos: lib e cmd.

// Estrutura de diretórios
/meu-projeto
├── lib/
│   └── go.mod
└── cmd/
    └── go.mod

Para criar o workspace:

$ cd /meu-projeto
$ go work init ./lib ./cmd

Isso gera um arquivo go.work:

go 1.22

use (
    ./lib
    ./cmd
)

O comando go work use adiciona novos módulos ao workspace existente:

$ go work use ./api

As diretivas principais do go.work são:

  • use: lista os diretórios dos módulos locais
  • replace: substitui dependências específicas (similar ao go.mod)
  • directory: caminho base para resolução de módulos

3. Estrutura de Projetos com Workspaces

Vamos construir um exemplo prático. Criaremos um módulo utilitário lib e um módulo principal cmd que o consome.

lib/go.mod:

module github.com/exemplo/lib

go 1.22

lib/strings.go:

package lib

import "strings"

func ToUpper(s string) string {
    return strings.ToUpper(s)
}

func Reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

cmd/go.mod:

module github.com/exemplo/cmd

go 1.22

require github.com/exemplo/lib v0.0.0

cmd/main.go:

package main

import (
    "fmt"
    "github.com/exemplo/lib"
)

func main() {
    texto := "golang workspaces"
    fmt.Println(lib.ToUpper(texto))
    fmt.Println(lib.Reverse(texto))
}

Com o workspace configurado, o Go resolve automaticamente github.com/exemplo/lib para o diretório local ./lib, sem precisar de replace no go.mod.

A diferença crucial: o go.work gerencia a resolução local sem modificar os arquivos go.mod individuais, mantendo-os limpos para publicação.

4. Desenvolvimento e Iteração com Workspaces

A principal vantagem dos workspaces aparece durante o desenvolvimento ativo. Podemos editar código em lib e ver os resultados imediatamente em cmd sem rebuilds manuais.

$ # No diretório raiz do workspace
$ go build ./cmd
$ go test ./...

O comando go build dentro do workspace resolve as dependências localmente. Se você modificar a função Reverse em lib, o próximo build em cmd refletirá automaticamente a mudança.

Antes dos workspaces, cada módulo precisava de um replace manual:

// go.mod sem workspace
replace github.com/exemplo/lib => ../lib

Com workspaces, isso não é mais necessário. O workspace substitui completamente essa abordagem.

5. Gerenciamento de Dependências e Versões

Workspaces gerenciam dependências externas compartilhadas de forma inteligente. Se dois módulos no workspace usam a mesma biblioteca externa, o Go resolve para uma única versão.

// go.work
go 1.22

use (
    ./lib
    ./cmd
    ./api
)

replace github.com/exemplo/lib => ./lib

Para controlar versões de dependências locais vs. publicadas, use o go.work para desenvolvimento local e mantenha o go.mod para versões publicadas.

O comando go work sync sincroniza as dependências entre todos os módulos do workspace:

$ go work sync

Isso garante que todos os módulos usem as mesmas versões das dependências compartilhadas.

6. Trabalhando em Equipe com Workspaces

A decisão de versionar ou não o go.work depende do fluxo de trabalho da equipe:

Versionar o go.work (recomendado para times pequenos):
- Todos usam a mesma estrutura de módulos
- Facilita onboarding de novos membros
- CI/CD pode usar o mesmo workspace

Ignorar no .gitignore (recomendado para projetos grandes):
- Cada desenvolvedor tem sua estrutura local
- Evita conflitos de caminhos absolutos
- CI/CD usa replace ou módulos publicados

Exemplo de .gitignore:

go.work
go.work.sum

Para CI/CD, uma estratégia comum é:

# .github/workflows/ci.yml (exemplo)
steps:
  - uses: actions/setup-go@v5
    with:
      go-version: '1.22'
  - run: go work init ./lib ./cmd
  - run: go build ./cmd
  - run: go test ./...

7. Migrando de replace para Workspaces

Projetos existentes frequentemente usam replace no go.mod. A migração é simples:

Antes:

// cmd/go.mod
module github.com/exemplo/cmd

go 1.22

require github.com/exemplo/lib v0.0.0

replace github.com/exemplo/lib => ../lib

Depois:
1. Remova os replace do go.mod
2. Crie o go.work com go work init ./cmd ./lib

$ go work init ./cmd ./lib
// go.work
go 1.22

use (
    ./cmd
    ./lib
)
// cmd/go.mod (limpo)
module github.com/exemplo/cmd

go 1.22

require github.com/exemplo/lib v0.0.0

Limitações: workspaces não substituem replace em cenários onde você precisa substituir dependências de dependências transitivas. Nesses casos, ainda use replace no go.work.

8. Considerações Finais e Boas Práticas

Armadilhas comuns:

  • Caminhos absolutos: evite usar caminhos absolutos no go.work; prefira relativos ao diretório do workspace
  • Módulos duplicados: se um módulo aparece em use e também como dependência externa, o workspace prioriza a versão local
  • Esquecer o go.work: sem o arquivo, os módulos tentarão resolver dependências externamente

Performance: workspaces com muitos módulos (10+) podem ter builds mais lentos. Considere dividir em workspaces menores.

Boas práticas:

  1. Mantenha o go.work na raiz do projeto
  2. Use go work sync regularmente
  3. Documente a estrutura do workspace para a equipe
  4. Considere versionar o go.work para projetos pequenos
  5. Teste sempre com go test ./... no workspace completo

Os workspaces representam uma evolução significativa na experiência de desenvolvimento Go, eliminando a necessidade de manipulação manual de replace e permitindo iteração mais rápida entre módulos interdependentes.

Referências