Go Programming Language Tutorial (Part 4)
This tutorial dives into code organization, dependency injection, advanced concurrency patterns,
profiling and performance optimization, logging, and deployment strategies.
1. Advanced Code Organization
Project Structure
Organizing your code properly is crucial for scalability. Here's a typical structure for a production-
grade Go application:
go
Copy code
myapp/
├── cmd/
│ └── myapp/
│ └── main.go
├── pkg/
│ ├── user/
│ │ ├── user.go
│ │ ├── service.go
│ │ └── repository.go
│ └── utils/
│ └── utils.go
├── internal/
│ └── config/
│ └── config.go
├── api/
│ ├── handlers.go
│ └── routes.go
├── go.mod
└── go.sum
Best Practices
1. Use cmd/ for entry points of your application.
2. Place reusable code in pkg/ and restrict internal use with internal/.
3. Group files by domain (e.g., user, auth).
Example: Config Management
File: internal/config/config.go
go
Copy code
package config
import (
"github.com/spf13/viper"
)
type Config struct {
Port string
Database struct {
Host string
Port int
Username string
Password string
Name string
}
}
func LoadConfig() (*Config, error) {
viper.SetConfigFile("config.yaml")
if err := viper.ReadInConfig(); err != nil {
return nil, err
}
var config Config
if err := viper.Unmarshal(&config); err != nil {
return nil, err
}
return &config, nil
}
2. Dependency Injection (DI)
Dependency injection improves testability and maintainability by separating dependencies.
Example: Injecting Repositories
go
Copy code
package main
import "fmt"
// Repository Interface
type Repository interface {
FetchData() string
}
// Implementation of Repository
type DBRepository struct{}
func (db DBRepository) FetchData() string {
return "Data from database"
}
// Service Layer
type Service struct {
repo Repository
}
func (s Service) Process() {
fmt.Println(s.repo.FetchData())
}
func main() {
repo := DBRepository{}
service := Service{repo: repo}
service.Process()
}
3. Advanced Concurrency Patterns
Rate Limiting
Rate limiting is essential for preventing overuse of resources.
Token Bucket Implementation
go
Copy code
package main
import (
"fmt"
"time"
)
type RateLimiter struct {
tokens int
maxTokens int
refillRate time.Duration
refillTimer *time.Ticker
}
func NewRateLimiter(maxTokens int, refillRate time.Duration) *RateLimiter {
rl := &RateLimiter{
tokens: maxTokens,
maxTokens: maxTokens,
refillRate: refillRate,
refillTimer: time.NewTicker(refillRate),
}
go func() {
for range rl.refillTimer.C {
if rl.tokens < rl.maxTokens {
rl.tokens++
}
}
}()
return rl
}
func (rl *RateLimiter) Allow() bool {
if rl.tokens > 0 {
rl.tokens--
return true
}
return false
}
func main() {
rl := NewRateLimiter(5, time.Second)
for i := 0; i < 10; i++ {
fmt.Println("Request", i+1, "allowed:", rl.Allow())
time.Sleep(200 * time.Millisecond)
}
}
4. Profiling and Performance Optimization
Profiling with pprof
Use net/http/pprof for profiling.
Add Profiling to Your App
go
Copy code
package main
import (
"net/http"
_ "net/http/pprof"
)
func main() {
go func() {
http.ListenAndServe(":6060", nil)
}()
// Your application logic
}
Run Profiling
bash
Copy code
go tool pprof http://localhost:6060/debug/pprof/profile
Benchmark Testing
Use the testing package for benchmarks.
go
Copy code
package main
import "testing"
func Add(a, b int) int {
return a + b
}
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
}
Run the benchmark:
bash
Copy code
go test -bench=.
5. Logging
Using log Package
go
Copy code
package main
import (
"log"
"os"
)
func main() {
file, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
logger := log.New(file, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
logger.Println("Application started")
}
Structured Logging with zap
Install:
bash
Copy code
go get go.uber.org/zap
Example:
go
Copy code
package main
import (
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("Starting application",
zap.String("environment", "production"),
zap.Int("version", 1),
)
}
6. Building REST APIs
Use the gorilla/mux router for building REST APIs.
Example API
go
Copy code
package main
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
var users = []User{{ID: 1, Name: "John Doe"}}
func getUsers(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(users)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/users", getUsers).Methods("GET")
http.ListenAndServe(":8080", r)
}
7. Deployment
Cross-Compiling
Go makes it easy to compile for different platforms:
bash
Copy code
GOOS=linux GOARCH=amd64 go build -o myapp
Dockerizing Go Applications
1. Create a Dockerfile:
dockerfile
Copy code
FROM golang:1.19
WORKDIR /app
COPY . .
RUN go build -o main .
CMD ["./main"]
2. Build and run:
bash
Copy code
docker build -t myapp .
docker run -p 8080:8080 myapp
8. Monitoring and Observability
Using Prometheus
Install the prometheus/client_golang library:
bash
Copy code
go get github.com/prometheus/client_golang/prometheus
Add a simple metric:
go
Copy code
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var requestCount = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
)
func main() {
prometheus.MustRegister(requestCount)
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
requestCount.Inc()
w.Write([]byte("Hello, World!"))
})
http.ListenAndServe(":8080", nil)
}
This tutorial provides a roadmap for building production-ready applications in Go. Master these
techniques, and you’ll be well-equipped to handle scalability, maintainability, and performance in your
projects.