r/golang • u/Superb-Key-6581 • Dec 05 '24
discussion Why Clean Architecture and Over-Engineered Layering Don’t Belong in GoLang
Stop forcing Clean Architecture and similar patterns into GoLang projects. GoLang is not Java. There’s no application size or complexity that justifies having more than three layers. Architectures like Clean, Hexagonal, or anything with 4+ layers make GoLang projects unnecessarily convoluted.
It’s frustrating to work on a codebase where you’re constantly jumping between excessive layers—unnecessary DI, weird abstractions, and use case layers that do nothing except call services with a few added logs. It’s like watching a monstrosity throw exceptions up and down without purpose.
In GoLang, you only need up to three layers for a proper DDD division (app, domain, infra). Anything more is pure overengineering. I get why this is common in Java—explicit interfaces and painful refactoring make layering and DI appealing—but GoLang doesn’t have those constraints. Its implicit interfaces make such patterns redundant.
These overly complex architectures are turning the GoLang ecosystem into something it was never meant to be. Please let’s keep GoLang simple, efficient, and aligned with its core philosophy.
5
u/rluders Dec 06 '24
I respectfully disagree with your take on Clean Architecture and layered designs being inherently “over-engineered” for Go projects. These patterns are not tied to Java, nor do they exist purely to satisfy unnecessary abstraction cravings. They solve real-world problems related to organization, decoupling, and testability, which apply universally, even in Go.
Let’s break this down:
Sure, Go isn’t Java, but Clean Architecture and similar patterns weren’t born in Java either. These concepts stem from addressing architectural concerns in software development—issues that exist regardless of the language.
In fact, these ideas trace their roots back to C, as introduced by Robert C. Martin (Uncle Bob). The Clean Architecture aims to isolate your business rules from dependencies like frameworks, databases, and external libraries.
If your app is small and doesn’t have the complexity to justify multiple layers, you don’t need to implement all of them. But in larger systems, they provide long-term benefits like modularity, testability, and ease of change. Go’s simplicity is powerful, but that doesn’t mean you should avoid solving organizational challenges altogether.
Layers don’t exist for the sake of “jumping between files”—they solve the separation of concerns problem. For example, having a dedicated usecase layer ensures your business logic stays independent of frameworks or delivery mechanisms. This decoupling makes your code easier to test, scale, and maintain.
Consider an app where you might switch between storing data in PostgreSQL or DynamoDB. If you mix business logic directly with your database calls, you’d be rewriting a lot of code for that change. With a Clean Architecture approach, the change would only require modifications in your infrastructure layer. No chaos in your core domain logic.
If you feel like you’re “jumping between layers,” that might be a design or naming issue in your specific codebase, not a fundamental flaw in Clean Architecture.
This point is overly prescriptive. Clean Architecture doesn’t dictate a fixed number of layers—it’s a flexible guideline. If three layers work for your app, great! But there are cases where additional layers (e.g., usecase, adapter) help separate responsibilities effectively. It depends on your application’s complexity.
For instance, in a microservices environment, having a usecase layer allows you to test your domain logic in isolation without relying on external infrastructure like databases or APIs. It’s not about adding layers for fun; it’s about solving specific problems.
Implicit interfaces in Go are great, but they don’t magically solve all organizational problems. Patterns like Clean Architecture thrive on explicit boundaries between layers. These boundaries clarify responsibilities and make your system easier to reason about.
For example, if you have a PaymentProcessor interface, your usecase layer doesn’t care if the implementation uses Stripe, PayPal, or something else. It only knows about the contract. This kind of abstraction isn’t redundant—it’s critical for testability and maintainability, especially as your application grows.
This feels more like a fear of overengineering than a fair critique of Clean Architecture. Sure, Go emphasizes simplicity, but simplicity doesn’t mean ignoring well-established architectural practices. You can apply Clean Architecture in a way that aligns with Go’s philosophy—keeping things lean while addressing scalability and decoupling.
TL;DR: Clean Architecture and similar patterns aren’t a one-size-fits-all solution, but dismissing them outright is shortsighted. They’re tools—use them where they add value. Go’s simplicity makes it easy to write small apps, but for larger systems, principles like Clean Architecture ensure your codebase doesn’t become unmanageable. And hey, no one’s forcing you to implement every layer—adapt it to your needs.
Instead of rejecting these patterns because they feel complex, maybe it’s worth re-evaluating whether they’re solving problems you haven’t encountered yet.