Chapter 11: The Architect's Blueprint
The Friday afternoon sun slanted low through the archive windows, illuminating dust motes dancing in the air. Ethan found Eleanor standing atop a rolling ladder, carefully rearranging a high shelf of leather-bound ledgers.
"You're reorganizing?" Ethan asked, setting a paper bag on the desk.
"I am correcting," Eleanor said, sliding a heavy volume into place with a satisfied sigh. "The previous archivist filed these by 'Color of Binding' rather than 'Year of Acquisition.' It made for a beautiful shelf, but a difficult search. Finding anything required looking everywhere."
She climbed down, dusting off her hands. "What did you bring?"
"Pistachio croissants. And a cortado."
Eleanor’s expression softened into a genuine smile. "You remembered the cortado. Thank you, Ethan." She gestured to the chair opposite her. "Please, sit. It’s a perfect afternoon for a warm drink."
Ethan opened his machine. "I built that inventory system you suggested. It works perfectly. The atomics are fast, the mutexes are safe. I'm ready to deploy."
Eleanor peered at the screen, leaning in with interest. "Let’s take a look. Not just at the logic, but at the home you’ve built for it."
She looked at the file explorer on the left.
/my-project
├── main.go
├── user.go
├── product.go
├── db.go
├── server.go
├── utils.go
└── types.go
"I see," Eleanor said, studying the screen. "It reminds me of when I first started organizing this archive. Putting everything in one place feels accessible at first, doesn't it? Everything is right there."
"Yeah, exactly. No hunting through folders."
"But imagine if this library had no sections," she gestured around the room. "Just one massive pile of books in the center of the floor. As the collection grows, finding what you need becomes a burden. In Go, the file system is the architecture. We need to talk about packages."
The Encapsulation Boundary
"In many languages," Eleanor explained gently, "folders are just for organization. In Go, a folder is a Package. It is a boundary. Think of it like a secure reading room."
She opened a new terminal. "Every file in the same directory must declare the same package name at the top. They live together, they share everything. But to the outside world? They only show what they want to show."
"You mean public and private?"
"In Go, we say Exported and Unexported. And the beauty is, there are no keywords like public or private. There is only the Case Rule."
She typed a quick example:
package users
// Exported (Public) - Starts with Capital Letter
type User struct {
Name string // Exported field
id int // Unexported field (private)
}
// Exported function
func New(name string) *User {
return &User{
Name: name,
id: generateID(), // We can call unexported functions inside the package
}
}
// Unexported function (Private) - Starts with lowercase
func generateID() int {
return 42 // Simplified
}
"If it starts with a Capital Letter, other packages can see it. If it starts with lowercase, it is visible only inside this package. It is simple and elegant. You don't need to look for a configuration file to know the API. You just look at the capitalization."
Ethan nodded. "Okay. So I should group things into packages. I'll make a models package and a controllers package."
Eleanor raised a hand, not to stop him, but to guide him. "Careful. That is a habit from other frameworks. In Go, we try not to group by kind—putting all models together or all controllers together. We group by responsibility."
She sketched a diagram on her notepad.
BAD ARCHITECTURE (Group by Kind):
/models
- user.go
- product.go
/controllers
- user.go
- product.go
GOOD ARCHITECTURE (Group by Domain):
/users
- user.go
- handler.go
- repository.go
/products
- product.go
- handler.go
"See the difference? If you group by domain, everything about 'Users' lives in package users. It is self-contained. It tells a story about what your software does, not just how it's built."
The Internal Fortress
"Now," Eleanor continued, "you will eventually have code that you need to share between your own packages—helpers or core logic—but you don't want the rest of the world to import. Like a special crypto helper for your users and products."
"I'd make a shared package?"
"You could. But then anyone on the internet who imports your library can use your shared package. It becomes part of your public promise. You can never change it without breaking them."
She tapped the screen. "Go gives us a wonderful tool for this: internal."
She rearranged Ethan's project structure on the screen:
/my-project
├── go.mod
├── main.go
├── /internal
│ └── /platform
│ └── crypto.go <-- Only visible to my-project!
├── /users
│ ├── service.go <-- Can import internal/platform
│ └── user.go
└── /products
└── product.go
"The Go compiler enforces this specifically. Any package inside a directory named internal can only be imported by code rooted in the parent directory. It allows you to write necessary but messy details that no one outside your project can ever touch."
"So internal is just for us?"
"For us alone. It gives you the freedom to refactor later without fear. It is a safety net."
The "Utils" Question
Ethan stared at the new structure on the screen, a sense of order settling in his mind. Then he pointed to his file list. "So... what about utils.go? It has string formatting, some math stuff, a random number generator."
Eleanor smiled, a look of sympathetic recognition. "Ah, the utils package. We all create one eventually. It’s a natural impulse to want a place for the odds and ends. But think of it like a junk drawer. Once you start putting things there, you never stop, and you never find them again."
"So delete it?"
"Distribute it," she corrected gently. "Find them a proper home. If a function formats user names, put it in users. If it calculates tax, put it in billing. A package name should describe what it provides, not just that it contains 'stuff'."
She took a bite of the croissant and nodded in approval. "This is delicious, Ethan."
She brushed a crumb from the notebook. "Structure isn't just sorting files. It's designing the system to prevent dependency cycles. If package A needs package B and package B needs package A, Go will refuse to compile. A flat directory hides those cycles. A structured project exposes them so you can fix them."
Ethan started creating a users directory. "No more junk drawers."
"A place for everything, and everything in its place," Eleanor agreed warmly. "And capitalize your exports. You are writing a library, Ethan, not a diary. Let the world read it."
Key Concepts from Chapter 11
Packages: The fundamental unit of code organization in Go. Every directory is a package. All files in a directory must declare the same package name.
Exported vs. Unexported:
-
Capital Letter (e.g.,
User): Exported (Public). Visible to other packages. -
Lowercase Letter (e.g.,
user): Unexported (Private). Visible only within the same package.
Domain-Driven Structure: Prefer organizing packages by domain (e.g., users, orders, billing) rather than by layer (e.g., models, controllers, utils). This keeps related logic together.
The internal Directory: A special directory name recognized by the Go toolchain. Code inside internal can only be imported by code rooted in the parent directory. It protects your implementation details from external users.
Package Naming:
- Short, concise, lowercase (e.g.,
net,http,json). - Avoid generic names like
utils,common, orhelpers. - The name should describe what the package provides.
Circular Dependencies: Go does not allow import cycles (A imports B, B imports A). Good package structure prevents this by forcing unidirectional dependencies.
go.mod: The file that defines your module and manages dependencies. It sits at the root of your project.
Next chapter: Error Handling—where Eleanor explains why Go doesn't have exceptions, and Ethan learns to treat errors as values.
Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.
Top comments (0)