Clean Architecture Without Ceremony
AuthDeck is small, but it’s not trivial. It deals with multiple providers, two OAuth flows, concurrent requests, and expiring tokens. Without some structure, that turns into a tangle of HTTP handlers doing everything at once.
I reached for Clean Architecture, but pragmatically. The goal wasn’t purity—it was making the moving parts obvious. The layout ended up borrowing from a sibling project of mine, and it’s held up well.
The Layout
The split is deliberately modest:
cmd/authdeck/main.go # composition root
internal/
core/entities/ # Token, Provider — no dependencies
core/usecases/ # use cases + interfaces.go (ports & DTOs)
state/ # in-memory read model (token cache, request queue)
infrastructure/ # config, oauth client, browser, forwarder
api/ # HTTP adapter
tui/ # terminal UI adapter
- entities know nothing about HTTP or YAML.
- use cases orchestrate:
TokenServiceresolves, refreshes, and exchanges tokens;ProviderSelectordrives the TUI decision;ProxyServiceforwards requests. - ports live next to the use cases (
interfaces.go):TokenClient,TokenStore,Browser,Forwarder. - adapters implement them, and
mainwires everything together.
Entities depend on nothing. Use cases depend on entities and the read model. Adapters depend inward. That’s the whole rule.
Use Cases as Collections
Not every operation deserves its own type. A use case is a struct that groups related operations—TokenService exposes
Obtain, Authorize, Exchange, and AutoRefresh. One cohesive object, several methods. Splitting each into its own
struct and file is ceremony, not architecture.
Staying Pragmatic
No interfaces for the sake of interfaces. No repository abstraction over a map. No dependency injection framework. The
standard library does the heavy lifting—net/http, context, sync.
The one external dependency that earns its place is a YAML parser, because configuration should be readable by humans.
Clean Architecture isn’t about layers and ceremony—it’s about knowing where things belong. For a small tool, that means just enough separation to stay honest, and not a single abstraction more.





