1. go
  2. /basics
  3. /packages

Understanding Go Packages and Modules

Packages are the building blocks of Go applications. This guide covers everything you need to know about working with packages and modules in Go.

Package Basics

Package Declaration

// File: user.go
package users

// Package-level variables
var (
    defaultRole = "user"
    MaxUsers    = 1000  // Exported
)

// Exported type
type User struct {
    ID   string
    Name string
    role string  // Unexported
}

// Exported function
func NewUser(name string) *User {
    return &User{
        Name: name,
        role: defaultRole,
    }
}

Package Organization

myapp/
├── go.mod
├── go.sum
├── main.go
├── users/
│   ├── user.go
│   ├── validation.go
│   └── database.go
└── config/
    ├── config.go
    └── defaults.go

Package Visibility

Exported vs Unexported

package math

// Exported (public) - starts with uppercase
func Calculate(x int) int {
    return multiply(x, 2)
}

// Unexported (private) - starts with lowercase
func multiply(x, y int) int {
    return x * y
}

// Exported type with both exported and unexported fields
type Rectangle struct {
    Width  float64  // Exported
    Height float64  // Exported
    area   float64  // Unexported
}

Using Packages

package main

import (
    "fmt"
    "myapp/users"
    "myapp/config"
)

func main() {
    user := users.NewUser("Alice")
    fmt.Printf("Created user: %v\n", user)
    
    // Can't access unexported members
    // user.role = "admin"  // Compilation error
}

Package Documentation

Package Comments

// Package users provides user management functionality.
// It handles user creation, validation, and persistence.
package users

// User represents a system user with basic information.
type User struct {
    ID   string
    Name string
}

// NewUser creates a new user with the given name.
// It returns nil if the name is invalid.
func NewUser(name string) *User {
    if name == "" {
        return nil
    }
    return &User{Name: name}
}

Example Tests

package users_test

func Example() {
    user := users.NewUser("Alice")
    fmt.Printf("User: %v\n", user)
    // Output: User: {ID: Name:Alice}
}

func ExampleNewUser() {
    user := users.NewUser("Bob")
    fmt.Printf("Name: %s\n", user.Name)
    // Output: Name: Bob
}

Package Initialization

Init Function

package database

import "log"

var db *Database

func init() {
    // Initialize package
    var err error
    db, err = Connect()
    if err != nil {
        log.Fatal(err)
    }
}

func GetDB() *Database {
    return db
}

Multiple Init Functions

package config

// First init function
func init() {
    loadDefaults()
}

// Second init function
func init() {
    loadEnvironment()
}

// Third init function
func init() {
    validateConfig()
}

Package Dependencies

Import Statements

package main

import (
    // Standard library
    "fmt"
    "log"
    
    // Third-party packages
    "github.com/gin-gonic/gin"
    
    // Local packages
    "myapp/users"
    "myapp/config"
)

Import Aliases

import (
    // Alias import
    mmath "math"
    
    // Dot import (avoid in production code)
    . "fmt"
    
    // Blank import
    _ "github.com/lib/pq"
)

Package Management

Go Modules

# Initialize a new module
go mod init myapp

# Add dependencies
go get github.com/gin-gonic/gin

# Update dependencies
go get -u

# Tidy dependencies
go mod tidy

Version Selection

// go.mod
module myapp

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/lib/pq v1.10.9
)

Best Practices

1. Package Organization

myapp/
├── cmd/                    # Command-line applications
│   └── server/
│       └── main.go
├── internal/              # Private application code
│   ├── auth/
│   └── database/
├── pkg/                   # Public library code
│   ├── models/
│   └── utils/
└── api/                   # API definitions
    └── proto/

2. Package Naming

// Good package names
package users
package database
package config

// Avoid
package utils     // Too generic
package common    // Too generic
package helper    // Too generic

3. Interface Definition

// Good: Define interfaces where they are used
package client

type Service interface {
    Get(id string) (*Response, error)
    List() ([]*Response, error)
}

// Implementation can be in a different package
package server

type service struct{}

func (s *service) Get(id string) (*client.Response, error) {
    // Implementation
}

Common Patterns

1. Options Pattern

package server

type Option func(*Server)

func WithPort(port int) Option {
    return func(s *Server) {
        s.port = port
    }
}

func New(options ...Option) *Server {
    s := &Server{
        port: 8080,  // Default
    }
    
    for _, opt := range options {
        opt(s)
    }
    
    return s
}

2. Builder Pattern

package query

type Builder struct {
    table  string
    where  string
    limit  int
}

func NewBuilder() *Builder {
    return &Builder{}
}

func (b *Builder) Table(name string) *Builder {
    b.table = name
    return b
}

func (b *Builder) Build() string {
    // Build query
    return fmt.Sprintf("SELECT * FROM %s", b.table)
}

3. Factory Pattern

package storage

type Store interface {
    Save(data []byte) error
    Load() ([]byte, error)
}

func NewStore(kind string) Store {
    switch kind {
    case "file":
        return &FileStore{}
    case "memory":
        return &MemoryStore{}
    default:
        return &NullStore{}
    }
}

Testing Packages

Table Tests

package users

import "testing"

func TestValidateUser(t *testing.T) {
    tests := []struct {
        name    string
        user    User
        wantErr bool
    }{
        {
            name: "valid user",
            user: User{Name: "Alice"},
            wantErr: false,
        },
        {
            name: "invalid user",
            user: User{Name: ""},
            wantErr: true,
        },
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := tt.user.Validate()
            if (err != nil) != tt.wantErr {
                t.Errorf("Validate() error = %v, wantErr %v", 
                    err, tt.wantErr)
            }
        })
    }
}

Mocks and Interfaces

package users

type Repository interface {
    Save(user *User) error
    Find(id string) (*User, error)
}

// Mock implementation for testing
type MockRepository struct {
    users map[string]*User
}

func NewMockRepository() *MockRepository {
    return &MockRepository{
        users: make(map[string]*User),
    }
}

func (m *MockRepository) Save(user *User) error {
    m.users[user.ID] = user
    return nil
}

Next Steps

  1. Learn about interfaces
  2. Explore testing
  3. Study project layout
  4. Practice with dependency management

Additional Resources