# Selling Knives vs Running a Bakery
Understanding the difference between writing a Library and writing an Application/Service fundamentally changes how you code.
A Library is like selling a high-end chef's knife. You don't know who is buying it, where they are cooking, or what ingredients they will cut. The knife cannot scream out loud, it cannot forcefully change the temperature of the kitchen, and it must never break. It must be perfectly predictable and purely functional.
A Service (Application) is the Chef running the Kitchen. The Chef controls the environment variables, the Chef coordinates multiple tasks (concurrency/goroutines), the Chef logs the mistakes, and if the entire building catches fire, the Chef decides to evacuate (os.Exit).
Many developers ruin their software by writing Library code as if it were Service code.
# Level 1: Configuration Secrets (Beginner)
A Service is allowed to read from its environment (os.Getenv, .env files, command line flags). It "owns" the machine.
A Library has no right to read the global environment. If an open-source library automatically looks for an environment variable named API_KEY, it forces the application developer to magically know this hidden requirement.
// ❌ UGLY LIBRARY DESIGN
package translator
func Translate(text string) string {
// Bad! The library is secretly stealing off the host OS!
key := os.Getenv("GOOGLE_TRANSLATE_KEY")
// ...
}
// ✅ GREAT LIBRARY DESIGN
package translator
// Good! The library boldly asks for what it needs via an argument!
func Translate(text, apiKey string) string {
// ...
}Code should be honest. If a library needs a credential, it should be a required parameter, not a hidden global string.
# Level 2: Logging and Panicking (Intermediate)
If an application tries to connect to its core Database on startup and fails, calling log.Fatal() or panic() is the correct decision. The Service cannot survive without a database.
If a Library fails to parse a JSON file, and calls panic(), it just violently shattered the entire Application that was relying on it.
// ❌ A LIBRARY BEHAVING LIKE A DICTATOR
func ParseConfig(file []byte) {
if string(file) == "" {
fmt.Println("ERROR: File is empty!") // Pollutes stdout!
os.Exit(1) // Murders the host app!
}
}
// ✅ A LIBRARY BEHAVING LIKE A TOOL
func ParseConfig(file []byte) error {
if len(file) == 0 {
// Just return the problem. The Application will decide
// if it wants to log it, ignore it, or panic!
return errors.New("parse_config: file is empty")
}
return nil
}Never use fmt.Print, log.Fatal, or os.Exit inside reusable library code. Return errors.
# Level 3: Concurrency Management (Advanced)
Imagine an application developer using your PDF compression library. They want to compress 500 files, so they meticulously write a worker pool with a strict limit of 10 Goroutines to prevent their server from crashing.
If your Library secretly runs go worker() 100 times internally behind the scenes, you bypass the caller's limits and you blow up their server's RAM.
The Go Mantra: "Leave Concurrency to the Caller"
Libraries should generally be synchronous. They should do exactly what they say, take exactly as long as they need, and block the thread until finished.
// If the user wants it to run in the background,
// they can easily do it themselves!
go func() {
err := library.DownloadLargeFile("file.zip")
if err != nil {
log.Println("Download failed", err)
}
}()By staying synchronous, the library remains predictable. If a Library must launch Goroutines, it must accept a context.Context so the Application can gracefully cancel the library's hidden background workers in an emergency.
# Level 4: API Stability & Versioning (Expert)
In a private Service, if you want to rename type User struct to type Account struct, you just hit "Find and Replace" in VS Code. It is entirely internal. No one notices.
In an open-source Library, if thousands of users import your code, a renaming refactor literally breaks their build on their next compilation. This introduces the strict laws of Semantic Versioning (SemVer).
- Patch (v1.0.1): You fixed a bug internally. The external API didn't change at all.
- Minor (v1.1.0): You added a brand new function. Old code still behaves identically.
- Major (v2.0.0): You renamed a function, changed an argument type, or deleted a feature. You broke backward compatibility.
The Expert Contract: As a Library maintainer, you are strictly forbidden from breaking API signatures during a v1 lifecycle. If you make a mistake on Day 1, you must support that mistake until you decide to launch a full v2 module. This forces Library authors to think 100x harder about their struct designs than Service authors!