45.8 Reading Great Go Code: Standard Library Patterns to Study

Let’s be honest, most of the code you write in your career will be spent reading other people’s. The good news? With Go, you’re often reading the best code—the standard library. It’s our collective textbook, written by masters of the craft. Studying it isn’t just recommended; it’s a shortcut to writing idiomatic Go yourself. Let’s crack it open. The io.Reader and io.Writer Interfaces: The Universal Adapters If you learn one thing from the standard library, let it be the power of these two interfaces. They are the duct tape and WD-40 of Go, connecting everything without anyone needing to know what “everything” is.

45.7 Error Sentinel vs Sentinel-Free API Design

Right, let’s talk about one of the most quietly contentious design decisions you’ll make in a Go API: how you tell the user, “Hey, something went wrong.” You’ve got two main schools of thought. One is the classic, almost medieval approach of using sentinel errors (ErrSomethingWentWrong). The other is the more modern, pattern-matching-friendly approach of sentinel-free, opaque errors. One isn’t inherently better than the other; they solve different problems. Picking the wrong one for the job is how you build an API that feels like a rusty bear trap for the poor soul trying to use it.

45.6 Dependency Injection Without a Framework

Right, so you want to do dependency injection but you don’t want to pull in some 50-megabyte framework that requires an XML configuration file written in ancient Sumerian. Good. Neither do I. Dependency injection isn’t a framework feature; it’s a design pattern, a way of structuring your code so it doesn’t turn into an un-testable, un-maintainable hairball. The core idea is painfully simple: don’t create your dependencies inside a struct; accept them as parameters. That’s it. You’ve just understood the secret. The rest is just us applying that one rule with varying levels of elegance and ceremony.

45.5 Hexagonal Architecture: Ports and Adapters

Right, let’s talk about Hexagonal Architecture. Forget the fancy name for a second; at its core, it’s a shockingly sane idea: your business logic shouldn’t give a damn about your database, your web framework, or whether your HTTP requests come in via carrier pigeon. It’s about drawing a hard, enforceable line between the what of your application (the domain logic, the juicy bits that make you money) and the how (the boring, often-changing plumbing of databases, APIs, and UIs).

45.4 Domain-Driven Design Concepts Adapted for Go

Look, let’s get one thing straight: Domain-Driven Design isn’t a library you import. You can’t go get it. It’s a way of thinking, a set of principles for taming complexity. And in Go, which prizes simplicity and pragmatism above all else, applying DDD is less about rigidly following every pattern from the book and more about stealing the good ideas and adapting them to fit the language’s ethos. We’re going to focus on the concepts that give you the most bang for your buck without turning your code into an abstract factory factory.

45.3 The Repository Pattern in Go

Right, let’s talk about the Repository pattern. You’ve probably heard of it. It’s the one that’s supposed to save you from the database, acting as a persistent in-memory collection. In Go, its implementation is a beautiful study in pragmatism, where we take a fancy academic pattern and bash it against the rocks of reality until it works for us, not against us. The core idea is simple: you want to decouple your core business logic (the stuff that makes you money) from the nitty-gritty details of how you shove data into a storage system (the stuff that gives you migraines). Your UserService shouldn’t care if its data comes from PostgreSQL, a giant CSV file, or a psychic octopus. The Repository is the mediator. It speaks in terms of your domain objects (User, Order) and translates those into the crude language of SELECT * FROM... or collection.Find().

45.2 Table-Driven Design: Data as Code

Right, let’s talk about one of the most embarrassingly simple yet profoundly powerful ideas in software design: table-driven development. You’ve probably written a function with a gnarly switch or a chain of if/else if statements that made you feel a little dirty afterward. You know the type: func GetSound(animal string) string { if animal == "dog" { return "woof" } else if animal == "cat" { return "meow" } else if animal == "cow" { return "moo" } else if animal == "duck" { return "quack" } // ... and so on for 20 more animals return "what did you just call me?" } This code is verbose, brittle, and a pain to test or extend. It screams “I was written at 4:55 PM on a Friday.” The table-driven approach looks at this mess and asks a better question: “What if the data was the code?”

45.1 Accepting Interfaces, Returning Concrete Types

Let’s talk about one of the most quietly powerful, “if you know, you know” principles in Go: accept interfaces, return structs. It sounds like a bumper sticker, but it’s the secret handshake that separates pleasant-to-use libraries from ones that feel like they’re actively fighting you. The core idea is beautifully simple. Your functions and methods should be liberal in what they accept—ask for the smallest possible interface that gets the job done. But they should be conservative in what they return—give the user the concrete, most useful type you can. This maximizes flexibility for the caller and minimizes confusion about what they’re getting back.

— joke —

...