{
  "slug": "Eliminating-Code-Duplication-in-Go-with-go-generate--A-Practical-Guide-13d40dfa6df1",
  "title": "Eliminating Code Duplication in Go with go:generate: A Practical Guide",
  "subtitle": "When building data-intensive applications in Go, you often find yourself writing repetitive boilerplate code.",
  "excerpt": "When building data-intensive applications in Go, you often find yourself writing repetitive boilerplate code.",
  "date": "2025-12-02",
  "tags": [
    "Go"
  ],
  "readingTime": "5 min",
  "url": "https://medium.com/@mobinshaterian/eliminating-code-duplication-in-go-with-go-generate-a-practical-guide-13d40dfa6df1",
  "hero": "https://cdn-images-1.medium.com/max/800/1*JH7o7Rgim1HK8qJ5OfLjDQ.png",
  "content": [
    {
      "type": "heading",
      "level": 2,
      "text": "The Problem"
    },
    {
      "type": "paragraph",
      "html": "When building data-intensive applications in Go, you often find yourself writing repetitive boilerplate code. A common scenario is handling multiple data transformation layers:"
    },
    {
      "type": "list",
      "ordered": true,
      "items": [
        "Converting GraphQL filters to database query parameters",
        "Converting database records to API response models",
        "Maintaining count queries with identical filter logic"
      ]
    },
    {
      "type": "paragraph",
      "html": "This creates a maintenance nightmare. Every time you add a new field to your domain model, you must update three or more places in your codebase. The logic is identical — only the struct types differ."
    },
    {
      "type": "paragraph",
      "html": "Consider this pattern:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "A <strong>filter conversion function</strong> that maps GraphQL filter inputs to database query parameters for list operations",
        "An <strong>identical conversion function</strong> for count operations (same logic, different parameter struct)",
        "A <strong>model conversion function</strong> that transforms database records to GraphQL response models"
      ]
    },
    {
      "type": "paragraph",
      "html": "That’s 50% duplication before you even write real business logic.<br>The Traditional Solutions and Their Limitations"
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*JH7o7Rgim1HK8qJ5OfLjDQ.png",
      "alt": "Eliminating Code Duplication in Go with go:generate: A Practical Guide",
      "caption": "",
      "width": 1024,
      "height": 1024
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Option 1: Accept the Duplication"
    },
    {
      "type": "paragraph",
      "html": "The simplest approach, but:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Maintenance becomes exponential as your domain grows",
        "Bugs fixed in one place need fixing in multiple places",
        "New developers don’t understand why the code is repeated",
        "Refactoring becomes a multi-hour task"
      ]
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Option 2: Use Reflection"
    },
    {
      "type": "paragraph",
      "html": "Reflection-based mappers like <code>mapstructure</code> or custom reflection utilities:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "// Slow and loses type safetyresult, _ := mapstructure.Decode(source, &destination)"
    },
    {
      "type": "paragraph",
      "html": "Drawbacks:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Runtime overhead (reflection is slow)",
        "Loss of compile-time type checking",
        "Harder to debug",
        "Performance issues at scale",
        "Complex error handling"
      ]
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Option 3: Generic Interfaces (Strategy Pattern)"
    },
    {
      "type": "paragraph",
      "html": "Create an interface that multiple structs implement:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "type FilterSetter interface {\n    SetStringField(name string, value *string)\n    SetIntField(name string, value *int)\n}"
    },
    {
      "type": "paragraph",
      "html": "Drawbacks:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Still requires massive switch statements in each implementation",
        "No reduction in boilerplate — just reorganization",
        "Difficult to add new field types without changing the interface",
        "Still a lot of manual code to write and maintain"
      ]
    },
    {
      "type": "heading",
      "level": 2,
      "text": "The Elegant Solution: go:generate"
    },
    {
      "type": "paragraph",
      "html": "Go’s built-in code generation tool provides the perfect balance between maintainability, performance, and type safety. Instead of fighting duplication, you <strong>generate</strong> the repetitive code from a single source of truth."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "How go:generate Works"
    },
    {
      "type": "paragraph",
      "html": "Go includes a <code>go:generate</code> A command that runs arbitrary tools during the build process. The typical workflow:"
    },
    {
      "type": "list",
      "ordered": true,
      "items": [
        "Define your data model in code (your source of truth)",
        "Write a generator program that reads this model",
        "Run <code>go generate</code> to create type-safe, optimized code",
        "The generated code is committed to version control (optional but recommended)"
      ]
    },
    {
      "type": "paragraph",
      "html": "<strong>Why this is better:</strong>"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "✅ <strong>Zero runtime overhead</strong> — generated code is optimized at compile time",
        "✅ <strong>Type-safe</strong> — full IDE support, compile-time checking",
        "✅ <strong>Single source of truth</strong> — maintain mapping in one place",
        "✅ <strong>Version control friendly</strong> — generated code is plain Go",
        "✅ <strong>Easy to review</strong> — generated code is human-readable",
        "✅ <strong>No magic</strong> — explicit, transparent transformation"
      ]
    },
    {
      "type": "heading",
      "level": 2,
      "text": "A Real-World Implementation"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Step 1: Define Your Mapping Configuration"
    },
    {
      "type": "paragraph",
      "html": "Create a generator tool that contains your data transformation definitions:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "type FieldMapping struct {\n    FilterName\n    string // Name in the filter input\n    SqlcFieldOut string // Name in SQL parameter struct\n    FieldType string // \"string\", \"int64\", \"float64\", \"time\"\n    MinField string // For range queries\n    MaxField string // For range queries\n}\ntype ModelFieldMapping struct {\n    DBField string // Name in database record\n    ModelField string // Name in API response model\n    FieldType\n    string\n    ConvertFunc string // Conversion\n    function to apply\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Step 2: List Your Fields Once"
    },
    {
      "type": "paragraph",
      "html": "Instead of duplicating field mappings across multiple functions, list them once:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "var fields = []FieldMapping{\n    {\n        FilterName: \"Name\", SqlcFieldOut: \"Name\", FieldType: \"string\"\n    },\n    {\n        FilterName: \"CreatedAt\", SqlcFieldOut: \"StartCreatedAt\", FieldType: \"time\", MaxField:\n        \"EndCreatedAt\"\n    },\n    //...etc\n}\nvar modelFields = []ModelFieldMapping{\n    {\n        DBField: \"Name\", ModelField: \"Name\", ConvertFunc: \"utils.NullStringToPointer\"\n    },\n    {\n        DBField: \"CreatedAt\", ModelField: \"CreatedAt\", ConvertFunc: \"utils.NullTimeToTime\"\n    },\n    //...etc\n}"
    },
    {
      "type": "paragraph",
      "html": "This is your <strong>single source of truth</strong>. All transformation logic derives from these definitions."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Step 3: Create a Template-Based Generator"
    },
    {
      "type": "paragraph",
      "html": "Use Go’s <code>text/template</code> package to generate the boilerplate:"
    },
    {
      "type": "code",
      "lang": "javascript",
      "code": "const template =\n`// Code generated; DO NOT EDIT.package servicefunc (s *Service) GraphqlFilterToListParams(filter *model.Filter) sqlc.ListParams {    params := sqlc.ListParams{}        if filter != nil {{{- range .FilterFields}}{{- if eq .FieldType \"string\"}}        params.{{.SqlcFieldOut}} = utils.ToNullString(filter.{{.FilterName}}){{- else if eq .FieldType \"time\"}}        params.{{.SqlcFieldOut}} = utils.ToNullTime(filter.{{.FilterName}})        params.{{.MaxField}} = utils.ToNullTime(filter.{{.MaxField}}){{- end}}{{- end}}    }    return params}`"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Step 4: Wire It Into Your Build"
    },
    {
      "type": "paragraph",
      "html": "Add a <code>go:generate</code> directive to your service file:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "package service//go:generate go run../../../cmd/gen/generator.go"
    },
    {
      "type": "paragraph",
      "html": "Run the generator:"
    },
    {
      "type": "code",
      "lang": "text",
      "code": "go generate ./..."
    },
    {
      "type": "paragraph",
      "html": "This creates optimized, type-safe code automatically."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Benefits in Practice"
    },
    {
      "type": "heading",
      "level": 3,
      "text": "Before: Maintaining 3 Functions"
    },
    {
      "type": "code",
      "lang": "text",
      "code": "Service.go\n├── graphqlFilterToListProductParams()     [200 lines]\n├──\ngraphqlFilterToCountProductParams()    [195 lines - 95% duplicate]\n└── dbGsmToGraphQLProduct()\n[150 lines]"
    },
    {
      "type": "paragraph",
      "html": "Every change requires updating multiple functions."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "After: Maintaining 1 Configuration"
    },
    {
      "type": "code",
      "lang": "text",
      "code": "Generator\n└── fields = []FieldMapping{...}     [1 definition]\n└── modelFields =\n[]ModelFieldMapping{...}Generated:\n└── filter_mapper.gen.go              [auto-generated from single\nsource]    \n├── GraphqlFilterToListProductParams()    \n├── GraphqlFilterToCountProductParams()    \n└──\ndbGsmToGraphQLProduct()"
    },
    {
      "type": "paragraph",
      "html": "Add a field once, regenerate, done."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Maintenance Scenarios"
    },
    {
      "type": "paragraph",
      "html": "<strong>Adding a new field:</strong>"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Before: Edit 3+ functions, increase the chance of bugs",
        "After: Add one line to your configuration, run <code>go generate</code>"
      ]
    },
    {
      "type": "paragraph",
      "html": "<strong>Fixing a conversion bug:</strong>"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Before: Fix in all places it appears",
        "After: Fix in the generator template, regenerate"
      ]
    },
    {
      "type": "paragraph",
      "html": "<strong>Changing field mapping rules:</strong>"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Before: Manual refactor across multiple functions",
        "After: Update the configuration, regenerate"
      ]
    },
    {
      "type": "paragraph",
      "html": "<strong>Code review:</strong>"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Before: Review duplicate logic in multiple places",
        "After: Review the generator template once"
      ]
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Real-World Performance"
    },
    {
      "type": "paragraph",
      "html": "The generated code is indistinguishable from handwritten code:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "// Generated code - no reflection, pure assignmentfunc (s *Service) dbToGraphQL(db *sqlc.Record)\n*model.Record {    return &model.Record{        Field1: utils.NullStringToPointer(db.Field1),\nField2: utils.NullTimeToTime(db.Field2),        // ... etc    }}"
    },
    {
      "type": "paragraph",
      "html": "This compiles to identical machine code as handwritten code. Zero overhead."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Best Practices"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "1. Generate Only Repetitive Code"
    },
    {
      "type": "paragraph",
      "html": "Use generators for boilerplate that follow predictable patterns. Don’t generate business logic."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "2. Keep Generators Simple"
    },
    {
      "type": "paragraph",
      "html": "Your generator tool should be ~100–200 lines. If it’s getting complex, you might be over-engineering."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "3. Store Generated Files"
    },
    {
      "type": "paragraph",
      "html": "Commit generated files to version control so:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Others can see what code is generated",
        "CI can verify that the generation is up-to-date",
        "Bisecting git history works correctly"
      ]
    },
    {
      "type": "heading",
      "level": 2,
      "text": "4. Make Generation Part of CI/CD"
    },
    {
      "type": "paragraph",
      "html": "Add a check to verify the generated code is current:"
    },
    {
      "type": "code",
      "lang": "bash",
      "code": "# Generate codego generate ./...# Verify no files changed\ngit diff --exit-code"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "5. Document Your Mapping Configuration"
    },
    {
      "type": "paragraph",
      "html": "Add comments to your field definitions explaining any non-obvious mappings:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "var fields = []FieldMapping{\n    // Database uses snake_case, GraphQL uses camelCase\n    {\n        FilterName: \"StatusCode\", SqlcFieldOut: \"status_code\",...\n    },\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Conclusion"
    },
    {
      "type": "paragraph",
      "html": "Code generation isn’t new or exotic. Go’s <code>go:generate</code> makes it accessible for everyday problems. When you find yourself copying and pasting code patterns, consider:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Is this pattern repetitive?",
        "Will it need to change frequently?",
        "Does it follow a predictable structure?"
      ]
    },
    {
      "type": "paragraph",
      "html": "If yes to all three, <strong>generate it</strong>."
    }
  ]
}
