Neovim for Go Backend Development: Monorepo, Microservices, Infrastructure

Neovim for Go Backend Development: Monorepo, Microservices, Infrastructure

A comprehensive guide to mastering Neovim for large-scale Go backend development. Navigate monorepos, manage microservices, mix Go with infrastructure-as-code, and orchestrate multiple languages seamlessly. Learn how senior Go engineers use Neovim for complex distributed systems.

By Omar Flores

Neovim for Go Backend Development: Monorepo, Microservices, Infrastructure

Managing Complex Go Systems: From Monorepos to Distributed Infrastructure


🎯 Introduction: The Complexity of Modern Go Development

Let me start with an honest observation: Most Go developers are building more complex systems than they realize.

They’re not just writing Go services. They’re orchestrating:

  • Multiple services (authentication, payments, notifications, worker queues)
  • Multiple languages (Go for services, Bash for scripts, Python for utilities, YAML for configs)
  • Infrastructure-as-code (Terraform, Kubernetes manifests, Docker configs)
  • Multiple repositories or monorepo structure (dependencies, shared packages, service boundaries)
  • Complex workflows (local development, CI/CD, staging, production deployment)

Traditional IDEs (GoLand, VS Code) struggle here because:

❌ Monorepo navigation becomes sluggish
❌ Multiple language support requires 20+ extensions
❌ LSP coordination across Go, YAML, Terraform, Bash is messy
❌ Context switching between services kills focus
❌ GUI overhead becomes noticeable in large projects
❌ Resource usage balloons with multiple services running

Neovim, designed for exactly this scenario, excels.

What This Guide Is About

This is not β€œlearn Go in Neovim” or β€œbasic Neovim setup.”

This is: How to use Neovim as your central hub for complex Go backend systems, from monorepo navigation to infrastructure orchestration.

We will cover:

βœ… Monorepo organization - Navigate 50+ services efficiently
βœ… Multi-module workspaces - Go workspaces in Neovim
βœ… LSP coordination - gopls + terraform-ls + yamlls + bashls
βœ… Service navigation - Jump between services instantly
βœ… Integrated workflows - Edit code β†’ run tests β†’ deploy
βœ… Mixed-language projects - Go + Terraform + YAML + Bash
βœ… Debugging distributed systems - Delve across multiple services
βœ… Profiling at scale - Performance analysis on complex systems
βœ… Team workflows - Sharing Neovim configs across teams

The perspective: How a senior Go engineer thinks about editor infrastructure for complex systems.


πŸ—οΈ Part 1: Understanding Monorepo Architecture in Neovim

Before configuring Neovim, understand what you’re building.

Monorepo Structure

A typical Go backend monorepo looks like:

backend-platform/
β”œβ”€β”€ go.work                  # Go workspace file
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ auth-service/
β”‚   β”‚   β”œβ”€β”€ go.mod
β”‚   β”‚   β”œβ”€β”€ main.go
β”‚   β”‚   β”œβ”€β”€ handlers/
β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”œβ”€β”€ tests/
β”‚   β”‚   └── Dockerfile
β”‚   β”œβ”€β”€ api-gateway/
β”‚   β”‚   β”œβ”€β”€ go.mod
β”‚   β”‚   β”œβ”€β”€ main.go
β”‚   β”‚   └── ...
β”‚   β”œβ”€β”€ payment-service/
β”‚   β”œβ”€β”€ notification-service/
β”‚   └── worker-service/
β”œβ”€β”€ shared/
β”‚   β”œβ”€β”€ go.mod
β”‚   β”œβ”€β”€ models/
β”‚   β”œβ”€β”€ database/
β”‚   β”œβ”€β”€ auth/
β”‚   └── utils/
β”œβ”€β”€ infrastructure/
β”‚   β”œβ”€β”€ terraform/
β”‚   β”‚   β”œβ”€β”€ main.tf
β”‚   β”‚   β”œβ”€β”€ aws/
β”‚   β”‚   β”œβ”€β”€ kubernetes/
β”‚   β”‚   └── modules/
β”‚   β”œβ”€β”€ kubernetes/
β”‚   β”‚   β”œβ”€β”€ base/
β”‚   β”‚   β”œβ”€β”€ overlays/
β”‚   β”‚   └── manifests/
β”‚   └── docker/
β”œβ”€β”€ scripts/
β”‚   β”œβ”€β”€ deploy.sh
β”‚   β”œβ”€β”€ setup-local.sh
β”‚   β”œβ”€β”€ run-tests.sh
β”‚   └── migrate-db.sh
β”œβ”€β”€ docs/
└── Makefile

The Challenge

Traditional IDE approach:
- Open GoLand project at root
- Try to navigate to auth-service
- IDE indexes entire monorepo
- Takes 30+ seconds to load
- Every file change triggers full re-index
- Opening second service requires new IDE window
- Context switching is painful

Neovim approach:
- Start Neovim at root
- Neovim knows go.work structure
- gopls understands module boundaries
- Navigate between services instantly
- Edit code, run tests, deploy all in terminal
- All in one cohesive workflow

πŸ“‚ Part 2: Go Workspace Management in Neovim

Go 1.18 introduced workspaces. This is how you leverage them in Neovim.

Go Workspace Configuration

# backend-platform/go.work

go 1.21

use (
  ./services/auth-service
  ./services/api-gateway
  ./services/payment-service
  ./services/notification-service
  ./services/worker-service
  ./shared
)

This tells go that all these modules are part of the same workspace. Neovim’s gopls understands this:

When editing: services/auth-service/handlers/auth.go
gopls can:
  - Find definition in shared/ package
  - Refactor shared code atomically
  - Run tests across all modules
  - Update imports across modules

Neovim Configuration for Workspaces

-- ~/.config/nvim/lua/go-workspace.lua

local lspconfig = require('lspconfig')

lspconfig.gopls.setup({
  cmd = { 'gopls', 'serve' },
  filetypes = { 'go', 'gomod', 'gowork', 'gotmpl' },

  -- Find workspace root
  root_dir = function(fname)
    local go_root = lspconfig.util.root_pattern(
      'go.work',  -- Check for go.work first (workspace)
      'go.mod',   -- Fall back to go.mod (single module)
      '.git'      -- Fall back to git root
    )(fname)
    return go_root
  end,

  on_attach = function(client, bufnr)
    local opts = { noremap = true, silent = true, buffer = bufnr }

    -- Workspace-aware keybindings
    vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)          -- Definition
    vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts)      -- Implementation
    vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts)          -- All references
    vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)                -- Documentation
    vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)      -- Rename atomically
    vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, opts) -- Code actions
  end,

  capabilities = require('cmp_nvim_lsp').default_capabilities(),

  settings = {
    gopls = {
      -- Enable workspace-wide analysis
      analysisProgressReporting = true,
      staticcheck = true,
      usePlaceholders = true,
      completeUnimported = true,

      -- Code lenses (show test coverage, benchmarks)
      codelens = {
        generate = true,
        test = true,
        gc_details = true,
      },

      -- Inlay hints for types
      hints = {
        assignVariableTypes = true,
        compositeLiteralFields = true,
        compositeLiteralTypes = true,
        constantValues = true,
        functionTypeParameters = true,
        parameterNames = true,
        rangeVariableTypes = true,
      },

      -- Diagnostic settings
      diagnosticsDelay = '100ms',
      analysisDelay = '500ms',
    },
  },
})
-- ~/.config/nvim/lua/go-monorepo-nav.lua

-- List all services in workspace
vim.api.nvim_create_user_command('GoServices', function()
  vim.cmd(':Telescope find_files cwd=services')
end, { desc = 'Find files in services/' })

-- Quick jump to service
vim.api.nvim_create_user_command('GoService', function(opts)
  local service = opts.args
  vim.cmd(':edit services/' .. service .. '/main.go')
end, {
  nargs = 1,
  complete = function()
    -- Auto-complete service names
    local services = vim.fn.glob('services/*/main.go', false, true)
    local names = {}
    for _, path in ipairs(services) do
      local service_name = path:match('services/([^/]+)/')
      table.insert(names, service_name)
    end
    return names
  end,
  desc = 'Jump to service main.go',
})

-- Jump to shared package
vim.api.nvim_create_user_command('GoShared', function(opts)
  local package = opts.args or ''
  vim.cmd(':edit shared/' .. package)
end, { nargs = '?', desc = 'Jump to shared package' })

-- List all packages in workspace
vim.api.nvim_create_user_command('GoPackages', function()
  vim.cmd(':Telescope live_grep cwd=.')
end, { desc = 'Search packages in workspace' })

Usage:

:GoService auth-service         β†’ Open auth-service/main.go
:GoService payment-service      β†’ Open payment-service/main.go
:GoShared models                β†’ Open shared/models
:GoServices                     β†’ Find files in all services
:GoPackages                     β†’ Search across entire workspace

πŸ”— Part 3: Cross-Service Refactoring and Navigation

The real power of Neovim + Go workspaces is atomic cross-service refactoring.

Refactoring Shared Code

Scenario:

You have a shared payment model used by multiple services:

shared/models/payment.go         β†’ Defines Payment type
services/payment-service/...     β†’ Uses Payment
services/api-gateway/...         β†’ Uses Payment
services/billing-service/...     β†’ Uses Payment

You need to rename Payment.Amount β†’ Payment.AmountCents:

Workflow:

1. Open shared/models/payment.go
   vim shared/models/payment.go

2. Position cursor on 'Amount' field

3. Rename across entire workspace
   <leader>rn

4. gopls finds ALL references across all services
   - Updates payment-service
   - Updates api-gateway
   - Updates billing-service
   - Updates all tests
   - All atomically

5. No manual search/replace needed

Finding Implementations

Scenario:

You have an interface PaymentProcessor:

shared/payments/processor.go     β†’ Defines interface
services/payment-service/...     β†’ Implements via Stripe
services/billing-service/...     β†’ Implements via PayPal

Find all implementations:

vim shared/payments/processor.go
# Cursor on 'PaymentProcessor' interface

:Telescope lsp_implementations
# Shows:
#   - StripeProcessor (payment-service)
#   - PayPalProcessor (billing-service)
#   - MockProcessor (tests)

Dependency Analysis

-- ~/.config/nvim/lua/go-dependencies.lua

-- Show who imports this package
vim.api.nvim_create_user_command('GoImporters', function()
  vim.lsp.buf.references(nil, { on_list = function(options)
    vim.fn.setloclist(0, options.items)
    vim.cmd('lopen')
  end })
end, { desc = 'Show all packages importing this' })

-- Show what this package imports
vim.api.nvim_create_user_command('GoImports', function()
  vim.cmd(':terminal go list -f "{{join .Imports \\\"\\n\\\"}}" ./...')
end, { desc = 'Show imports for current package' })

-- Visualize module dependency graph
vim.api.nvim_create_user_command('GoDependencyGraph', function()
  vim.cmd(':terminal go mod graph | head -20')
end, { desc = 'Show dependency graph' })

πŸ”€ Part 4: LSP Coordination Across Multiple Languages

Real backend systems use multiple languages. Neovim handles this elegantly.

Multi-Language LSP Setup

-- ~/.config/nvim/after/plugin/lspconfig-multi.lua

local lspconfig = require('lspconfig')

-- Go
lspconfig.gopls.setup({
  -- ... go config from earlier
})

-- Terraform (infrastructure code)
lspconfig.terraformls.setup({
  cmd = { 'terraform-ls', 'serve' },
  filetypes = { 'terraform', 'tf' },
  root_dir = lspconfig.util.root_pattern('.terraform', '.git'),
  single_file_support = true,
})

-- YAML (Kubernetes manifests, Docker compose)
lspconfig.yamlls.setup({
  settings = {
    yaml = {
      schemas = {
        kubernetes = 'infrastructure/kubernetes/**/*.yaml',
        docker = 'docker-compose.yaml',
        github = '.github/workflows/*.yaml',
      },
      format = { enable = true },
      validate = true,
    },
  },
})

-- Bash/Shell (deployment scripts)
lspconfig.bashls.setup({
  filetypes = { 'sh', 'bash' },
})

-- JSON (configs, manifests)
lspconfig.jsonls.setup({
  settings = {
    json = {
      schemas = {
        {
          fileMatch = { 'go.mod' },
          url = 'schema://go.mod',
        },
      },
    },
  },
})

-- Lua (Neovim config)
lspconfig.lua_ls.setup({
  settings = {
    Lua = {
      runtime = { version = 'LuaJIT' },
      diagnostics = { globals = { 'vim' } },
      workspace = {
        library = vim.api.nvim_get_runtime_file('', true),
      },
    },
  },
})

Language-Aware Keybindings

-- ~/.config/nvim/lua/go-mixed-languages.lua

-- Detect file type and run appropriate tools
local function smart_format()
  local filetype = vim.bo.filetype
  local opts = { noremap = true, silent = true }

  if filetype == 'go' then
    vim.lsp.buf.format({ async = false })
  elseif filetype == 'terraform' or filetype == 'tf' then
    vim.cmd(':terminal terraform fmt %')
  elseif filetype == 'yaml' then
    vim.cmd(':terminal prettier --write %')
  elseif filetype == 'json' then
    vim.cmd(':terminal jq . % > %.tmp && mv %.tmp %')
  elseif filetype == 'sh' or filetype == 'bash' then
    vim.cmd(':terminal shfmt -i 2 -w %')
  end
end

-- Smart validation
local function smart_validate()
  local filetype = vim.bo.filetype

  if filetype == 'go' then
    vim.cmd(':terminal go vet ./...')
  elseif filetype == 'terraform' then
    vim.cmd(':terminal terraform validate')
  elseif filetype == 'sh' then
    vim.cmd(':terminal shellcheck %')
  elseif filetype == 'yaml' then
    vim.cmd(':terminal yamllint %')
  end
end

-- Smart test runner
local function smart_test()
  local filetype = vim.bo.filetype

  if filetype == 'go' then
    vim.cmd(':terminal go test -v ./...')
  elseif filetype == 'terraform' then
    vim.cmd(':terminal terraform plan')
  elseif filetype == 'sh' then
    vim.cmd(':terminal bash -n %')
  end
end

vim.keymap.set('n', '<leader>f', smart_format, { noremap = true })
vim.keymap.set('n', '<leader>v', smart_validate, { noremap = true })
vim.keymap.set('n', '<leader>t', smart_test, { noremap = true })

πŸ”„ Part 5: Integrated Development Workflow (Local β†’ CI/CD β†’ Production)

A senior Go developer thinks in terms of workflows, not individual commands.

The Complete Development Cycle

-- ~/.config/nvim/lua/go-workflow.lua

local M = {}

-- Phase 1: Local Development
function M.local_dev()
  print("Starting local development...")
  vim.cmd(':terminal docker-compose up -d')  -- Start services
  vim.cmd(':terminal go run ./services/api-gateway/main.go')
end

-- Phase 2: Testing
function M.test_all()
  print("Running full test suite...")
  vim.cmd(':terminal go test -v -race -coverprofile=coverage.out ./...')
  vim.cmd(':terminal go tool cover -html=coverage.out')
end

function M.test_current()
  local file = vim.fn.expand('%')
  vim.cmd(':terminal go test -v ' .. vim.fn.fnamemodify(file, ':h'))
end

-- Phase 3: Linting and Quality
function M.quality_check()
  print("Running quality checks...")
  vim.cmd(':terminal ' ..
    'golangci-lint run ./... && ' ..
    'go mod tidy && ' ..
    'go vet ./...'
  )
end

-- Phase 4: Build
function M.build_service()
  local service = vim.fn.input('Service to build: ')
  vim.cmd(':terminal go build -o bin/' .. service .. ' ./services/' .. service)
end

function M.build_docker()
  local service = vim.fn.input('Service to docker: ')
  vim.cmd(':terminal docker build -f docker/Dockerfile.' .. service .. ' -t ' .. service .. ':latest .')
end

-- Phase 5: Deploy
function M.deploy_staging()
  print("Deploying to staging...")
  vim.cmd(':terminal ' ..
    'kubectl apply -f infrastructure/kubernetes/overlays/staging && ' ..
    'kubectl rollout status deployment/api-gateway'
  )
end

function M.deploy_production()
  print("Deploying to production...")
  vim.cmd(':terminal ' ..
    'kubectl apply -f infrastructure/kubernetes/overlays/production && ' ..
    'kubectl rollout status deployment/api-gateway'
  )
end

-- Setup keybindings
vim.keymap.set('n', '<leader>wd', M.local_dev, { noremap = true, silent = true })
vim.keymap.set('n', '<leader>ta', M.test_all, { noremap = true, silent = true })
vim.keymap.set('n', '<leader>tc', M.test_current, { noremap = true, silent = true })
vim.keymap.set('n', '<leader>qc', M.quality_check, { noremap = true, silent = true })
vim.keymap.set('n', '<leader>bs', M.build_service, { noremap = true, silent = true })
vim.keymap.set('n', '<leader>bd', M.build_docker, { noremap = true, silent = true })
vim.keymap.set('n', '<leader>ds', M.deploy_staging, { noremap = true, silent = true })
vim.keymap.set('n', '<leader>dp', M.deploy_production, { noremap = true, silent = true })

return M

Complete Workflow:

Monday morning:
nvim backend-platform/

<leader>wd          β†’ Start all services locally
Edit code...
<leader>ta          β†’ Run full test suite
<leader>qc          β†’ Quality checks (lint, vet, tidy)
<leader>bs api-gateway  β†’ Build specific service
<leader>bd api-gateway  β†’ Build Docker image
<leader>ds          β†’ Deploy to staging
<leader>dp          β†’ Deploy to production

All without leaving Neovim
All with consistent keybindings
All integrated into one workflow

πŸ” Part 6: Debugging Distributed Systems

Debugging multi-service systems requires orchestrating Delve across services.

Setup Delve for Microservices

-- ~/.config/nvim/lua/go-debug-distributed.lua

local dap = require('dap')
local dapui = require('dapui')

-- Configure Delve
dap.adapters.go = {
  type = 'executable',
  command = 'dlv',
  args = { 'dap' },
}

dap.configurations.go = {
  -- Debug current service
  {
    type = 'go',
    name = 'Debug Current Service',
    mode = 'debug',
    request = 'launch',
    program = '${fileDirname}',
    env = {},
    args = {},
    showLog = false,
  },

  -- Debug specific service
  {
    type = 'go',
    name = 'Debug auth-service',
    mode = 'debug',
    request = 'launch',
    program = '${workspaceFolder}/services/auth-service',
    showLog = false,
  },

  {
    type = 'go',
    name = 'Debug api-gateway',
    mode = 'debug',
    request = 'launch',
    program = '${workspaceFolder}/services/api-gateway',
    showLog = false,
  },

  {
    type = 'go',
    name = 'Debug payment-service',
    mode = 'debug',
    request = 'launch',
    program = '${workspaceFolder}/services/payment-service',
    showLog = false,
  },

  -- Debug tests
  {
    type = 'go',
    name = 'Debug Test',
    mode = 'test',
    request = 'launch',
    program = '${fileDirname}',
    args = { '-test.run', 'TestMyFunction' },
    showLog = false,
  },

  -- Attach to running service
  {
    type = 'go',
    name = 'Attach to Service',
    mode = 'local',
    request = 'attach',
    processId = require('dap.utils').pick_process,
    showLog = false,
  },
}

-- DAP UI
dapui.setup()

dap.listeners.after.event_initialized['dapui_config'] = function()
  dapui.open()
end

dap.listeners.before.event_terminated['dapui_config'] = function()
  dapui.close()
end

-- Keybindings
vim.keymap.set('n', '<leader>db', dap.toggle_breakpoint, { noremap = true })
vim.keymap.set('n', '<leader>dc', dap.continue, { noremap = true })
vim.keymap.set('n', '<leader>dn', dap.step_over, { noremap = true })
vim.keymap.set('n', '<leader>di', dap.step_into, { noremap = true })
vim.keymap.set('n', '<leader>do', dap.step_out, { noremap = true })
vim.keymap.set('n', '<leader>dr', dap.repl.open, { noremap = true })
vim.keymap.set('n', '<leader>de', dapui.eval, { noremap = true })

Multi-Service Debugging Scenario

Scenario: API call from api-gateway β†’ payment-service β†’ shared/models

Setup:
1. Terminal 1: Neovim in api-gateway service
   <leader>db    β†’ Set breakpoint in handler
   <leader>dc    β†’ Start debugging

2. Terminal 2: Neovim in payment-service
   <leader>db    β†’ Set breakpoint in processor
   Open split terminal to watch logs

3. Make request:
   curl http://localhost:8080/payments/process

4. Hit breakpoint in api-gateway
   <leader>di    β†’ Step into payment-service
   Hit breakpoint in payment-service
   Inspect goroutine state, database queries, etc.
   <leader>dr    β†’ Open REPL to inspect variables

All in Neovim, all coordinated

πŸ“Š Part 7: Performance Profiling at Scale

Go’s pprof is powerful. Neovim makes it accessible.

Profiling Integration

-- ~/.config/nvim/lua/go-profiling.lua

-- CPU Profiling
vim.api.nvim_create_user_command('GoProfile', function()
  vim.cmd(':terminal ' ..
    'go test -cpuprofile=cpu.prof ./... && ' ..
    'go tool pprof -http=:8080 cpu.prof'
  )
end, { desc = 'Run CPU profiling' })

-- Memory Profiling
vim.api.nvim_create_user_command('GoMemProfile', function()
  vim.cmd(':terminal ' ..
    'go test -memprofile=mem.prof ./... && ' ..
    'go tool pprof -http=:8080 mem.prof'
  )
end, { desc = 'Run memory profiling' })

-- Goroutine Analysis
vim.api.nvim_create_user_command('GoGoroutineProfile', function()
  vim.cmd(':terminal ' ..
    'go test -goroutineprofile=goroutine.prof ./... && ' ..
    'go tool pprof -http=:8080 goroutine.prof'
  )
end, { desc = 'Analyze goroutines' })

-- Benchmark with profiling
vim.api.nvim_create_user_command('GoBenchProfile', function(opts)
  local pkg = opts.args or './...'
  vim.cmd(':terminal ' ..
    'go test -bench=. -benchmem -cpuprofile=bench_cpu.prof -memprofile=bench_mem.prof ' .. pkg
  )
end, { nargs = '?', desc = 'Benchmark with profiling' })

-- Generate Flame Graph
vim.api.nvim_create_user_command('GoFlameGraph', function()
  vim.cmd(':terminal ' ..
    'go test -cpuprofile=cpu.prof ./... && ' ..
    'go tool pprof -proto cpu.prof > cpu.pb.gz && ' ..
    'go-torch --file=flame.svg cpu.prof'
  )
end, { desc = 'Generate flame graph' })

🀝 Part 8: Team Workflows and Shared Configuration

Large teams need consistent Neovim setups.

Distributing Neovim Config

# backend-platform/.nvim-config/init.lua
# Shared configuration in repository

# Team members clone and link:
# ln -s /path/to/backend-platform/.nvim-config ~/.config/nvim/projects/backend
-- ~/.config/nvim/init.lua
-- Personal config

-- Load project-specific config if exists
local project_config = vim.fn.expand('~/.config/nvim/projects/' ..
  vim.fn.fnamemodify(vim.fn.getcwd(), ':t'))

if vim.fn.filereadable(project_config .. '/init.lua') == 1 then
  dofile(project_config .. '/init.lua')
end

-- Load common team config
local team_config = vim.fn.findfile('.nvim-config/init.lua', '.;')
if team_config ~= '' then
  dofile(team_config)
end

Team Conventions

-- backend-platform/.nvim-config/team-conventions.lua

-- All team members get same:
-- - LSP configurations
-- - Keybindings
-- - Go-specific settings
-- - Service navigation

local conventions = {
  leader = ' ',  -- Space bar

  -- Go development
  test_command = 'go test -v -race ./...',
  lint_command = 'golangci-lint run ./...',
  fmt_command = 'gofmt -w',

  -- Service ports (documented)
  services = {
    ['api-gateway'] = 8080,
    ['auth-service'] = 8081,
    ['payment-service'] = 8082,
  },

  -- Docker compose file location
  docker_compose = 'docker-compose.yaml',

  -- Kubernetes context for staging/prod
  k8s_staging = 'staging-cluster',
  k8s_prod = 'production-cluster',
}

return conventions

Makefile Integration

# backend-platform/Makefile

.PHONY: dev setup lint test build deploy-staging deploy-prod

# Development setup
dev:
	@nvim

# Install dependencies
setup:
	@go mod download
	@cd services/auth-service && go mod download
	@cd services/payment-service && go mod download

# Linting
lint:
	@golangci-lint run ./...

# Testing
test:
	@go test -v -race -coverprofile=coverage.out ./...

# Build all services
build:
	@for service in auth payment api-gateway notification worker; do \
		go build -o bin/$$service ./services/$$service-service; \
	done

# Deploy to staging
deploy-staging:
	@kubectl apply -f infrastructure/kubernetes/overlays/staging

# Deploy to production
deploy-prod:
	@kubectl apply -f infrastructure/kubernetes/overlays/production

Usage from Neovim:

:terminal make test
:terminal make lint
:terminal make build
:terminal make deploy-staging

🎯 Part 9: Real-World Scenario: Building a Payment System

Let’s walk through a realistic complex system.

Project Structure

payment-platform/
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ api-gateway/          # HTTP entry point
β”‚   β”œβ”€β”€ payment-processor/     # Core payment logic
β”‚   β”œβ”€β”€ notification-service/  # Send confirmations
β”‚   └── webhook-handler/       # External webhooks
β”œβ”€β”€ shared/
β”‚   β”œβ”€β”€ models/
β”‚   β”œβ”€β”€ database/
β”‚   └── errors/
β”œβ”€β”€ infrastructure/
β”‚   β”œβ”€β”€ terraform/             # AWS infrastructure
β”‚   β”œβ”€β”€ kubernetes/            # K8s manifests
β”‚   └── docker/                # Docker configs
└── scripts/                   # Deployment scripts

Development Workflow

Day 1: Adding Payment Method Support

Time: 09:00
Task: Add Apple Pay support

Step 1: Open Neovim
nvim payment-platform/

Step 2: Navigate to payment processor
:GoService payment-processor

Step 3: Examine PaymentMethod interface
shared/models/payment.go
<leader>rn    β†’ Find all implementations
# Shows: StripeProcessor, PayPalProcessor, ApplePayProcessor

Step 4: Create Apple Pay processor
services/payment-processor/applepay.go

gopls auto-completes interface methods:
  - Process()
  - Refund()
  - Validate()

Step 5: Add to factory
services/payment-processor/factory.go
<leader>ca    β†’ Code action to add missing methods

Step 6: Write tests
services/payment-processor/applepay_test.go

Step 7: Run tests
<leader>ta    β†’ All tests pass

Step 8: Quality check
<leader>qc    β†’ Lint, vet, format

Step 9: Commit and push
<leader>gc    β†’ Git commit

11:30 - Done. Apple Pay support added.

Day 2: Debugging Webhook Failures

Time: 14:00
Issue: Webhooks failing silently

Step 1: Open webhook handler
:GoService webhook-handler

Step 2: Set breakpoint on webhook receiver
<leader>db    β†’ Toggle breakpoint

Step 3: Open second service (payment-processor)
Ctrl+w β†’ :GoService payment-processor
Set breakpoint on processor

Step 4: Run test webhook
<leader>tc    β†’ Run webhook tests
Hit first breakpoint

Step 5: Step through code
<leader>dn    β†’ Step over
<leader>di    β†’ Step into payment-processor

Step 6: Inspect state
<leader>dr    β†’ REPL
> print(webhook.PaymentID)
> print(db.CheckPayment(webhook.PaymentID))

Step 7: Find root cause
Database connection string is old

Step 8: Fix and test
Edit config, <leader>ta
Tests pass

16:00 - Fixed

Day 3: Performance Optimization

Time: 10:00
Goal: Reduce payment processing latency

Step 1: Profile current performance
<leader>t     β†’ Run benchmarks
<leader>gb    β†’ Run with benchmarking

Step 2: Identify slow path
Terraform validation, database queries slow

Step 3: Open database layer
:GoShared database

Set up profiling:
<leader>profile

Step 4: Analyze results
go tool pprof identifies query bottleneck

Step 5: Add index to database
infrastructure/terraform/main.tf
<leader>f     β†’ Format terraform
<leader>v     β†’ Validate

Step 6: Re-benchmark
<leader>gb    β†’ 40% faster

Step 7: Deploy to staging
<leader>ds    β†’ Deploy changes
<leader>te    β†’ Test in staging

Step 8: Deploy to production
<leader>dp    β†’ Production deployment
Monitor metrics in split window

πŸš€ Conclusion: Why Neovim Wins for Complex Go Systems

The Core Advantage

Traditional approach:
- Multiple IDE windows (one per service)
- Context switching between services
- LSP conflicts (which Go version? which module?)
- Manual coordination
- Resource heavy

Neovim approach:
- Single editor, entire workspace
- Atomic cross-service refactoring
- gopls understands workspace boundaries
- Coordinated LSPs
- Lightweight

The Scaling Argument

For 5-service monorepo:
GoLand/VS Code: Manageable but sluggish

For 20-service monorepo:
GoLand/VS Code: Slow, IDE overhead noticeable

For 50+ service monorepo:
GoLand/VS Code: Becomes liability
Neovim: Scales perfectly

The Integration Advantage

When you’re managing:

- Go services (50 files)
- Terraform infrastructure (20 files)
- Kubernetes manifests (30 files)
- Deployment scripts (10 files)
- Configuration (YAML, JSON)

Traditional IDEs need:

βœ— Go extension
βœ— Terraform extension
βœ— Kubernetes extension
βœ— YAML extension
βœ— JSON extension
βœ— Bash extension

6+ extensions, all trying to be main language

Conflicts between LSPs
Extension performance issues
Configuration fragmented

Neovim provides:

βœ“ gopls (Go)
βœ“ terraform-ls (Terraform)
βœ“ yamlls (YAML/Kubernetes)
βœ“ bashls (Shell scripts)
βœ“ jsonls (JSON configs)

All coordinated
All in one config file
All with same keybindings

The Senior Developer Perspective

A senior Go engineer working on complex systems asks:

"Can I refactor across all services atomically?"
Neovim: Yes (with go.work)

"Can I navigate 50 services without context switching?"
Neovim: Yes (with service commands)

"Can I debug across services?"
Neovim: Yes (Delve coordination)

"Can I mix Go, Terraform, YAML, Shell in one workflow?"
Neovim: Yes (LSP coordination)

"Does it scale to massive monorepos?"
Neovim: Yes (no GUI overhead)

"Can my team share configuration?"
Neovim: Yes (simple Lua, versioned in repo)

For complex Go systems, Neovim isn’t just an option.

It’s the rational choice.

Tags

#neovim #go #backend #monorepo #microservices #infrastructure-as-code #terraform #workflow #productivity #distributed-systems