You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Picture this: It's 3 AM. Production is on fire. You're desperately trying to debug why your HTTP API is returning cached data from *yesterday*. You trace the issue to your HTTP client... which is a singleton... that someone initialized with a 24-hour cache... and there's no way to reset it without restarting the entire service.
15
15
16
-
Sound familiar? No? Well, stick around anyway. By the end of this post, you'll understand why the **Factory pattern** will save your future self from these sleepless nights, and why **Singleton** is often the villain in disguise when building Go HTTP APIs.
16
+
I've been there. Twice, actually. The second time was worse because I *knew* better but had inherited the codebase.
17
17
18
-
Let's dive in with the classic journalist's approach:**Who? What? When? Where? Why?**
18
+
This post is about why the **Factory pattern** beats**Singleton** for HTTP clients in Go. Not because some design pattern book says so, but because I've debugged enough production incidents to have opinions.
19
19
20
20
---
21
21
22
-
## Who Should Care?
23
-
24
-
**You**, if you:
25
-
- Build HTTP APIs in Go (or plan to)
26
-
- Write code that needs to be tested (spoiler: all code does)
27
-
- Have ever struggled to mock a dependency
28
-
- Want your teammates to stop cursing your name in code reviews
29
-
- Enjoy sleeping at night instead of debugging production issues
30
-
31
-
If none of these apply to you, congratulations! You're either a perfect developer or you've achieved enlightenment. For the rest of us mortals, let's continue.
32
-
33
-
---
34
-
35
-
## What Are We Talking About?
22
+
## The Two Approaches
36
23
37
24
### The Singleton Pattern: The "One Ring to Rule Them All" Approach
38
25
@@ -228,13 +215,13 @@ type User struct {
228
215
}
229
216
```
230
217
231
-
"But Tom," I hear you say, "that's more code!" Yes. And your future self will thank you. Here's why...
218
+
"But that's more code!" Yes. About 40 more lines. I'll take 40 lines over a 3 AM debugging session any day.
232
219
233
220
---
234
221
235
-
## When Does This Matter? (The Testing Showdown)
222
+
## Testing: Where Singleton Falls Apart
236
223
237
-
Let's try to test both approaches. This is where things get *spicy*.
224
+
Here's where it gets painful. Try writing tests for both approaches.
### Generating Mocks with Mocktail: Stop Writing Boilerplate!
490
477
491
-
Writing mock implementations by hand works, but let's be honest, it's tedious. For every interface you want to mock, you need to create a struct, implement all methods, handle the function callbacks... it adds up fast.
478
+
Writing mocks by hand gets old fast. I've wasted too many hours writing boilerplate `DoFunc`callbacks.
492
479
493
-
Enter [**Mocktail**](https://github.com/paperballs/mocktail): a mock generator that creates **strongly-typed mocks** using `testify/mock`. Unlike other generators that use string-based method calls (hello, refactoring nightmares!), Mocktail generates typed methods that break at compile time when your interface changes.
480
+
[**Mocktail**](https://github.com/paperballs/mocktail) generates strongly-typed mocks using `testify/mock`. The key difference from other generators: it uses typed methods instead of string-based calls. When your interface changes, compilation fails. No more "forgot to update the mock" bugs.
The key insight: **`TypedReturns` has the same signature as the real method**. If you change `Do`'s return type, Mocktail regenerates with the new signature, and your tests won't compile until you fix them. No more "oops, I forgot to update that mock" bugs in production!
638
+
`TypedReturns` has the same signature as the real method. Change `Do`'s return type, regenerate, and the compiler tells you what broke. I've caught several bugs this way that would have shipped otherwise.
652
639
653
640
#### When to Use What
654
641
@@ -657,13 +644,13 @@ The key insight: **`TypedReturns` has the same signature as the real method**. I
657
644
-**Frequently changing interfaces**: Mocktail's type safety is invaluable
658
645
-**Team projects**: Mocktail ensures consistency
659
646
660
-
The Factory pattern makes mocking possible; Mocktail makes it painless.
647
+
Factory makes mocking possible. Mocktail removes the boilerplate.
661
648
662
649
---
663
650
664
-
## Where Does This Play Out? (Real-World Scenarios)
651
+
## Real Scenarios Where This Matters
665
652
666
-
Let's look at some scenarios where Factory shines and Singleton... doesn't.
I'm not saying Singleton is *always* wrong. Use it for:
799
+
To be fair, Singleton isn't always wrong:
813
800
814
801
-**True singletons**: There really can only be one (e.g., a process-wide metrics registry)
815
802
-**Immutable configuration**: Loaded once at startup, never changed
816
803
-**Resource pools**: Connection pools where you explicitly WANT sharing
817
804
-**Logging**: Usually fine as a singleton (but consider structured logging with context)
818
805
819
-
The key question: **"Will I ever need to vary this behavior per context?"**
806
+
The question I ask myself: "Will I ever need different behavior in different contexts?" If there's any chance the answer is yes, Factory. If it's truly global and immutable, Singleton might be fine.
820
807
821
-
If yes Factory. If truly no Singleton *might* be okay.
822
-
823
-
### The Factory-by-Default Philosophy
824
-
825
-
My recommendation: **Start with Factory, reach for Singleton only when you have a specific, justified reason.**
808
+
In practice, I default to Factory and only reach for Singleton when I have a specific reason.
- You need different configurations for different contexts
1036
-
- You want to test your code with mocks
1037
-
- You're building multi-tenant systems
1038
-
- You need to support feature flags
1039
-
- You want explicit, traceable dependencies
1040
-
- You value your sleep
1041
-
1042
-
### Singleton Might Be Okay When:
1043
-
1044
-
- There truly can only be one (process metrics, runtime config)
1045
-
- The instance is immutable after initialization
1046
-
- You're explicitly building a shared resource pool
1047
-
- You've thought about it and can justify the trade-offs
1048
-
1049
-
### The Golden Rule
1050
-
1051
-
> **Make dependencies explicit. Your tests will thank you. Your teammates will thank you. Future you will thank you.**
1052
-
1053
-
---
1054
-
1055
-
## Conclusion
1056
-
1057
-
The Factory pattern isn't just about creating objects. It's about:
1058
-
-**Flexibility**: Different configurations for different needs
1059
-
-**Testability**: Easy mocking without black magic
1060
-
-**Clarity**: Explicit dependencies you can trace
1061
-
-**Maintainability**: Code that's easier to change
1062
-
1063
-
Singleton has its place, but it's a smaller place than most codebases give it. When building HTTP APIs in Go, the Factory pattern combined with dependency injection is almost always the better choice.
1012
+
## Wrapping Up
1064
1013
1065
-
So the next time you reach for `sync.Once` and a package-level variable, ask yourself: "Will I regret this at 3 AM?"
1014
+
Factory adds some boilerplate. About 40-50 lines for a typical HTTP client setup. In exchange, you get testable code, flexible configuration, and fewer 3 AM incidents.
1066
1015
1067
-
If the answer is "maybe," use a factory instead. Your future self will send you a thank-you note.
1016
+
Singleton isn't evil. It's just overused. Most HTTP clients don't need to be singletons, and the ones that do are rarer than you'd think.
1068
1017
1069
-
Happy coding! And may your tests always be green and your production always be boring.
1018
+
Next time you're about to write `var instance *http.Client` with `sync.Once`, ask yourself if you'll ever need to mock it, configure it differently, or debug it in production. If the answer to any of those is "maybe," use a factory.
1070
1019
1071
1020
---
1072
1021
1073
-
*Got questions? Found a bug in my examples? Want to argue that Singleton is actually fine? Drop a comment below or find me on [GitHub](https://github.com/tommoulard)!*
1022
+
*If you spot a bug in the examples or want to argue that Singleton is fine, find me on [GitHub](https://github.com/tommoulard).*
0 commit comments