A demonstration of Go workspace functionality with cross-module communication patterns using a shared common package.
This project showcases Go's workspace feature (introduced in Go 1.18) with multiple modules that communicate through a shared package. It demonstrates how to structure a multi-module project where modules can depend on each other through a common bridge package.
┌─────────────────────────────────────────────┐
│ main.go (Root) │
│ Orchestrates both apps │
└──────────────┬──────────────┬───────────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ app1 │ │ app2 │
│ module │ │ module │
└─────┬────┘ └──────────┘
│ ▲
│ │
▼ │
┌─────────────────┐ │
│ common package │─┘
│ (in root gwi) │
└─────────────────┘
- main.go → calls both app1 and app2 directly
- app1 → uses common package → calls app2
- app2 → self-contained, no external dependencies
- common → bridges app1 to app2
.
├── go.work # Workspace configuration
├── go.mod # Root module (gwi)
├── main.go # Entry point
├── common/ # Shared bridge package
│ └── call.go # Cross-module communication utilities
├── app1/ # First application module
│ ├── go.mod # Module definition with gwi dependency
│ ├── main.go # App1 entry point
│ └── app/
│ └── app.go # App1 core functionality
└── app2/ # Second application module
├── go.mod # Self-contained module definition
├── main.go # App2 entry point
└── app/
└── app.go # App2 core functionality
- Go 1.25.1 or later (project uses Go 1.25.1)
- Basic understanding of Go modules and workspaces
git clone https://github.com/pivaldi/go-work-inward.git
cd go-work-inward
go run .go run main.goExpected Output:
I'am APP1
I'am APP2
App2 said: I'am APP2
App1:
cd app1
go run main.goApp2:
cd app2
go run main.goBuild all:
# From root directory
go build -o bin/main ./main.go
go build -o bin/app1 ./app1/main.go
go build -o bin/app2 ./app2/main.goRun built binary:
./bin/main- Purpose: Contains the common package and main entry point
- Package:
common- provides cross-module communication utilities - Location: Root directory
- Dependencies: None (provides functionality to other modules)
- Purpose: Demonstrates module that depends on root module
- Key Function:
WhoAmI()- returns app1 identity - Key Function:
WhoIsApp2()- calls app2 through common package - Dependencies: Root
gwimodule (via replace directive) - Pattern: Uses shared common package to communicate with app2
- Purpose: Self-contained module with no external dependencies
- Key Function:
WhoAmI()- returns app2 identity - Dependencies: None
- Pattern: Completely independent, called by others through common package
- Purpose: Bridge package enabling app1 → app2 communication
- Key Function:
WhoIsApp2()- wrapper that calls app2's WhoAmI function - Pattern: Dependency injection bridge pattern
The project demonstrates a sophisticated communication pattern:
-
Direct Calls:
main.gocan call both app1 and app2 directlyapp1.WhoAmI() // Direct call to app1 app2.WhoAmI() // Direct call to app2
-
Indirect Calls via Common: app1 calls app2 through the common package
app1.WhoIsApp2() // app1 → common → app2
The go.work file enables:
- Local Development: Work on multiple modules simultaneously
- No Publishing Required: Test inter-module changes without publishing
- Unified Dependencies: Consistent dependency resolution across modules
- Replace Directives: app1's
go.moduses replace directive to reference local gwi module
In app1/go.mod:
replace gwi => ../
require gwi v0.0.0-00010101000000-000000000000This tells Go to use the local ../ directory instead of fetching from a remote source.
-
Create new module directory:
mkdir app3 cd app3 go mod init gwi/app3 -
Add to workspace:
# From root directory go work use ./app3 -
Add dependencies as needed in the module's
go.mod
When updating the common package:
- Make changes in
common/directory - Changes are immediately available to all modules in workspace
- No need to publish or update versions during development
- Keep
commonpackage focused on cross-cutting concerns - Minimize dependencies in
commonto avoid coupling - Use replace directives for local development
- Document any cross-module contracts in code comments
- Consider module boundaries carefully before adding dependencies
func main() {
fmt.Println(app1.WhoAmI()) // Prints: I'am APP1
fmt.Println(app2.WhoAmI()) // Prints: I'am APP2
fmt.Println(app1.WhoIsApp2()) // Prints: App2 said: I'am APP2
}The third call demonstrates the bridge pattern: app1 → common → app2.
func WhoIsApp2() string {
return app2.WhoAmI() // Calls into app2 module
}This function acts as a bridge, allowing app1 to access app2 functionality without direct coupling.
func WhoIsApp2() string {
return "App2 said: " + common.WhoIsApp2() // Uses bridge
}App1 leverages the common package to communicate with app2, demonstrating loose coupling.