Development Guide

Table of Contents

  1. Development Environment Setup
  2. Project Structure
  3. Development Workflow
  4. Coding Standards
  5. Testing Guidelines
  6. Debugging
  7. Contributing
  8. Common Tasks

Development Environment Setup

Prerequisites

Required Software

# Go 1.24+
wget https://go.dev/dl/go1.24.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.24.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin

# Docker & Docker Compose
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
sudo usermod -aG docker $USER

# PostgreSQL Client
sudo apt-get install postgresql-client

# Make
sudo apt-get install build-essential

# Git
sudo apt-get install git

Development Tools

# Air for hot reload
go install github.com/air-verse/air@latest

# Migrate for database migrations
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest

# Swagger for API docs
go install github.com/swaggo/swag/cmd/swag@latest

# GolangCI-Lint for linting
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2

# Mockery for test mocks
go install github.com/vektra/mockery/v2@latest

Local Setup

1. Clone Repository

git clone https://github.com/your-org/ms-project.git
cd ms-project

2. Install Dependencies

# Go dependencies
go mod download
go mod tidy

# Verify installation
go mod verify

3. Environment Configuration

# Copy environment template
cp .env.example .env

# Edit with your local settings
vim .env

# Required variables for local development
DATABASE_URL=postgresql://postgres:password@localhost:5432/ms_project_dev?sslmode=disable
REDIS_HOST=localhost
REDIS_PORT=6379
JWT_SECRET=local-development-secret-min-32-characters
NODE_ENV=development
PORT=8080

4. Database Setup

# Start PostgreSQL (using Docker)
docker run -d \
  --name ms-project-postgres \
  -e POSTGRES_DB=ms_project_dev \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=password \
  -p 5432:5432 \
  postgres:15-alpine

# Run migrations
make migrate-up

# Seed development data (optional)
make seed-dev

5. Start Redis

docker run -d \
  --name ms-project-redis \
  -p 6379:6379 \
  redis:7-alpine

6. Run Application

# Using Air for hot reload
air

# Or using go run
go run cmd/server/main.go

# Or using make
make run-dev

7. Verify Setup

# Health check
curl http://localhost:8080/health

# API status
curl http://localhost:8080/api/v1/status

Project Structure

ms-project/
├── cmd/                    # Application entrypoints
│   ├── server/            # Main API server
│   │   └── main.go
│   └── migration/         # Migration tool
│       └── main.go
├── internal/              # Private application code
│   ├── config/           # Configuration management
│   │   └── config.go
│   ├── database/         # Database connection
│   │   └── connection.go
│   ├── dto/              # Data Transfer Objects
│   │   ├── project.go
│   │   └── task.go
│   ├── enums/            # Enumerations
│   │   └── status.go
│   ├── errors/           # Custom error types
│   │   └── errors.go
│   ├── events/           # Event system
│   │   └── emitter.go
│   ├── handlers/         # HTTP handlers
│   │   ├── project_handler.go
│   │   └── task_handler.go
│   ├── middleware/       # HTTP middleware
│   │   ├── auth.go
│   │   └── logger.go
│   ├── models/           # Domain models
│   │   ├── base.go
│   │   ├── project.go
│   │   └── task.go
│   ├── routes/           # Route definitions
│   │   └── routes.go
│   ├── services/         # Business logic
│   │   ├── project_service.go
│   │   └── task_service.go
│   └── utils/            # Utility functions
│       └── ulid.go
├── migrations/            # Database migrations
│   ├── 001_initial.up.sql
│   └── 001_initial.down.sql
├── tests/                 # Test files
│   ├── integration/      # Integration tests
│   ├── unit/            # Unit tests
│   └── testutil/        # Test utilities
├── docs/                 # Documentation
├── scripts/              # Utility scripts
├── .env.example          # Environment template
├── .gitignore
├── Dockerfile
├── Makefile
├── go.mod
├── go.sum
└── README.md

Development Workflow

Git Workflow

Branch Strategy

main            # Production-ready code
├── develop     # Integration branch
    ├── feature/[name]     # New features
    ├── bugfix/[name]      # Bug fixes
    ├── hotfix/[name]      # Emergency fixes
    └── release/[version]  # Release preparation

Creating a Feature

# 1. Create feature branch
git checkout develop
git pull origin develop
git checkout -b feature/add-notifications

# 2. Make changes
# ... code changes ...

# 3. Commit with conventional commits
git add .
git commit -m "feat: add notification system for task updates"

# 4. Push and create PR
git push origin feature/add-notifications

Commit Message Format

<type>(<scope>): <subject>

<body>

<footer>

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation
  • style: Code style
  • refactor: Code refactoring
  • test: Testing
  • chore: Maintenance

Examples:

feat(tasks): add priority filtering
fix(projects): resolve date validation issue
docs(api): update endpoint documentation
refactor(services): extract common validation logic

Development Commands

# Common make commands
make help           # Show available commands
make run           # Run application
make test          # Run all tests
make test-unit     # Run unit tests only
make test-integration # Run integration tests
make lint          # Run linter
make fmt           # Format code
make build         # Build binary
make docker-build  # Build Docker image
make migrate-up    # Run migrations
make migrate-down  # Rollback migrations
make swagger       # Generate Swagger docs
make coverage      # Generate test coverage
make clean         # Clean build artifacts

Coding Standards

Go Code Style

File Structure

package services

import (
    // Standard library imports
    "context"
    "fmt"
    "time"

    // External imports
    "github.com/gofiber/fiber/v2"
    "gorm.io/gorm"

    // Internal imports
    "ms-project/internal/models"
    "ms-project/internal/dto"
)

Naming Conventions

// Constants - PascalCase
const MaxRetryAttempts = 3

// Variables - camelCase
var defaultTimeout = 30 * time.Second

// Functions/Methods - PascalCase for exported, camelCase for private
func CreateProject(dto CreateProjectDTO) (*Project, error) {}
func validateInput(input string) error {}

// Interfaces - PascalCase with "er" suffix
type ProjectCreator interface {
    Create(project *Project) error
}

// Structs - PascalCase
type ProjectService struct {
    db *gorm.DB
    cache CacheService
}

// Struct fields - PascalCase for exported, camelCase for private
type Project struct {
    ID          string    // Exported
    Name        string    // Exported
    isValidated bool      // Private
}

Error Handling

// Always check errors immediately
result, err := someFunction()
if err != nil {
    return nil, fmt.Errorf("failed to execute someFunction: %w", err)
}

// Custom errors
type ValidationError struct {
    Field   string
    Message string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("validation error on field %s: %s", e.Field, e.Message)
}

// Error wrapping
if err := db.Create(&project).Error; err != nil {
    return fmt.Errorf("failed to create project: %w", err)
}

Context Usage

// Always accept context as first parameter
func (s *ProjectService) GetProject(ctx context.Context, id string) (*Project, error) {
    // Use context for timeouts
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    // Pass context to database operations
    var project Project
    err := s.db.WithContext(ctx).First(&project, id).Error

    return &project, err
}

Defer Usage

func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close() // Always defer cleanup

    // Process file...
    return nil
}

Documentation

Package Documentation

// Package services provides business logic implementations
// for the MS-Project application. It contains service layers
// that coordinate between handlers and repositories.
package services

Function Documentation

// CreateProject creates a new project with the given details.
// It validates the input, generates a unique code, and persists
// the project to the database.
//
// Parameters:
//   - ctx: Context for request cancellation
//   - dto: Data transfer object containing project details
//
// Returns:
//   - *Project: The created project
//   - error: Error if creation fails
func CreateProject(ctx context.Context, dto CreateProjectDTO) (*Project, error) {
    // Implementation
}

Struct Documentation

// ProjectService handles business logic for project operations.
// It coordinates between the HTTP handlers and the data layer,
// implementing validation, authorization, and business rules.
type ProjectService struct {
    db           *gorm.DB        // Database connection
    cache        CacheService    // Caching layer
    eventEmitter EventEmitter    // Event publishing
}

Testing Guidelines

Unit Testing

Test File Structure

// project_service_test.go
package services_test

import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
)

func TestProjectService_CreateProject(t *testing.T) {
    // Arrange
    mockDB := new(MockDatabase)
    service := NewProjectService(mockDB)

    dto := CreateProjectDTO{
        Name: "Test Project",
        CustomerID: "customer123",
    }

    mockDB.On("Create", mock.Anything).Return(nil)

    // Act
    project, err := service.CreateProject(dto)

    // Assert
    assert.NoError(t, err)
    assert.NotNil(t, project)
    assert.Equal(t, "Test Project", project.Name)
    mockDB.AssertExpectations(t)
}

Table-Driven Tests

func TestValidateProjectName(t *testing.T) {
    tests := []struct {
        name      string
        input     string
        wantError bool
    }{
        {"valid name", "Project Alpha", false},
        {"empty name", "", true},
        {"too short", "ab", true},
        {"too long", strings.Repeat("a", 256), true},
        {"special chars", "Project @#$", false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := validateProjectName(tt.input)
            if tt.wantError {
                assert.Error(t, err)
            } else {
                assert.NoError(t, err)
            }
        })
    }
}

Integration Testing

Database Testing

func TestProjectRepository_Integration(t *testing.T) {
    // Skip if not integration test
    if testing.Short() {
        t.Skip("Skipping integration test")
    }

    // Setup test database
    db := testutil.SetupTestDB(t)
    defer testutil.TeardownTestDB(db)

    repo := NewProjectRepository(db)

    t.Run("Create and Retrieve", func(t *testing.T) {
        // Create project
        project := &Project{
            Name: "Integration Test",
            CustomerID: "customer123",
        }

        err := repo.Create(project)
        assert.NoError(t, err)
        assert.NotEmpty(t, project.ID)

        // Retrieve project
        retrieved, err := repo.FindByID(project.ID)
        assert.NoError(t, err)
        assert.Equal(t, project.Name, retrieved.Name)
    })
}

API Testing

func TestProjectAPI_CreateProject(t *testing.T) {
    // Setup test server
    app := setupTestApp()

    // Prepare request
    payload := map[string]interface{}{
        "name": "API Test Project",
        "customerId": "customer123",
    }

    body, _ := json.Marshal(payload)
    req := httptest.NewRequest("POST", "/api/v1/projects", bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Authorization", "Bearer " + testToken)

    // Execute request
    resp, err := app.Test(req, -1)

    // Assert
    assert.NoError(t, err)
    assert.Equal(t, 201, resp.StatusCode)

    var result map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&result)
    assert.NotNil(t, result["data"])
}

Test Coverage

# Run tests with coverage
go test -coverprofile=coverage.out ./...

# View coverage report
go tool cover -html=coverage.out

# Coverage requirements
# - Unit tests: > 80%
# - Integration tests: > 60%
# - Critical paths: > 90%

Debugging

Local Debugging

Using Delve

# Install Delve
go install github.com/go-delve/delve/cmd/dlv@latest

# Debug application
dlv debug cmd/server/main.go

# Debug specific test
dlv test ./internal/services -- -test.run TestProjectService

# Common Delve commands
(dlv) break main.go:42        # Set breakpoint
(dlv) continue               # Continue execution
(dlv) next                   # Next line
(dlv) step                   # Step into
(dlv) print variableName     # Print variable
(dlv) locals                 # Show local variables
(dlv) stack                  # Show stack trace

VS Code Configuration

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch Server",
      "type": "go",
      "request": "launch",
      "mode": "auto",
      "program": "${workspaceFolder}/cmd/server",
      "env": {
        "DATABASE_URL": "postgresql://postgres:password@localhost:5432/ms_project_dev"
      },
      "args": []
    },
    {
      "name": "Debug Test",
      "type": "go",
      "request": "launch",
      "mode": "test",
      "program": "${workspaceFolder}/tests/unit",
      "args": ["-test.run", "TestProjectService"]
    }
  ]
}

Logging

Structured Logging

import "github.com/rs/zerolog/log"

// Configure logger
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})

// Log with context
log.Info().
    Str("projectId", project.ID).
    Str("userId", userID).
    Msg("Project created successfully")

// Log errors
log.Error().
    Err(err).
    Str("projectId", projectID).
    Msg("Failed to update project")

// Debug logging
log.Debug().
    Interface("dto", dto).
    Msg("Processing request")

Request Logging Middleware

func LoggerMiddleware() fiber.Handler {
    return func(c *fiber.Ctx) error {
        start := time.Now()

        // Process request
        err := c.Next()

        // Log request
        log.Info().
            Str("method", c.Method()).
            Str("path", c.Path()).
            Int("status", c.Response().StatusCode()).
            Dur("latency", time.Since(start)).
            Str("ip", c.IP()).
            Msg("Request processed")

        return err
    }
}

Performance Profiling

CPU Profiling

import _ "net/http/pprof"

// Add pprof endpoints
go func() {
    log.Println(http.ListenAndServe("localhost:6060", nil))
}()

// Profile CPU
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

Memory Profiling

# Capture heap profile
go tool pprof http://localhost:6060/debug/pprof/heap

# Analyze allocations
go tool pprof -alloc_space http://localhost:6060/debug/pprof/allocs

Contributing

Pull Request Process

1. Fork and Clone

# Fork on GitHub, then:
git clone https://github.com/your-username/ms-project.git
cd ms-project
git remote add upstream https://github.com/original-org/ms-project.git

2. Create Branch

git checkout -b feature/your-feature

3. Make Changes

  • Write code following coding standards
  • Add tests for new functionality
  • Update documentation

4. Test

# Run tests
make test

# Run linter
make lint

# Check formatting
make fmt

5. Commit

git add .
git commit -m "feat: add amazing feature"

6. Push and PR

git push origin feature/your-feature
# Create PR on GitHub

Code Review Checklist

  • Code follows project style guide
  • Tests are included and passing
  • Documentation is updated
  • No sensitive data in code
  • Error handling is proper
  • Performance impact considered
  • Security implications reviewed
  • Breaking changes documented

Release Process

1. Version Bump

# Update version in go.mod, configs, etc.
make version VERSION=v1.2.0

2. Generate Changelog

# Generate changelog from commits
make changelog

3. Create Release Branch

git checkout -b release/v1.2.0

4. Testing

# Run full test suite
make test-all

# Run integration tests
make test-integration

# Performance tests
make test-performance

5. Tag and Release

git tag v1.2.0
git push origin v1.2.0

Common Tasks

Database Operations

# Create new migration
migrate create -ext sql -dir migrations -seq create_users_table

# Run migrations
make migrate-up

# Rollback last migration
make migrate-down

# Reset database
make db-reset

# Seed development data
make seed-dev

API Documentation

# Generate Swagger docs
swag init -g cmd/server/main.go

# Update after API changes
make swagger

# View docs
open http://localhost:8080/swagger/index.html

Dependency Management

# Add new dependency
go get github.com/some/package

# Update dependencies
go get -u ./...

# Clean unused dependencies
go mod tidy

# Vendor dependencies
go mod vendor

Docker Operations

# Build image
docker build -t ms-project:dev .

# Run container
docker run -p 8080:8080 ms-project:dev

# Docker Compose
docker-compose up -d
docker-compose logs -f
docker-compose down

Benchmarking

// Write benchmark
func BenchmarkCreateProject(b *testing.B) {
    service := NewProjectService(db)
    dto := CreateProjectDTO{Name: "Benchmark"}

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        service.CreateProject(dto)
    }
}

// Run benchmarks
go test -bench=. -benchmem ./...

Troubleshooting

Common Issues

Port Already in Use

# Find process using port
lsof -i :8080

# Kill process
kill -9 <PID>

Database Connection Failed

# Check PostgreSQL status
pg_isready -h localhost -p 5432

# Check credentials
psql -h localhost -U postgres -d ms_project_dev

Module Issues

# Clear module cache
go clean -modcache

# Re-download dependencies
go mod download

Build Failures

# Clean build cache
go clean -cache

# Rebuild
go build -a ./...

Resources

Documentation

Tools

Learning Resources

Conclusion

This development guide provides comprehensive information for:

  • Setting up development environment
  • Following coding standards
  • Writing and running tests
  • Debugging applications
  • Contributing to the project

Follow these guidelines to maintain code quality and consistency across the project.