DEV Community

Cover image for Why Clean Architecture Confuses Everyone (And How I Learned to Stop Worrying)
Rômulo Pereira
Rômulo Pereira

Posted on

Why Clean Architecture Confuses Everyone (And How I Learned to Stop Worrying)

I've been writing software for over 10 years now. I've seen codebases that started clean and became unmaintainable. I've inherited legacy systems that somehow still worked despite violating every principle in the book. And I've watched teams argue for hours about folder structures while missing the entire point of clean architecture.

Let me tell you something: the confusion is real, and it's not your fault.

The Framework Problem

When you start a Spring Boot project, the framework practically begs you to do this:

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue
    private Long id;

    @Column(nullable = false)
    private String customerEmail;

    @OneToMany(mappedBy = "order")
    private List<OrderItem> items;
}

@Service
public class OrderService {
    @Autowired
    private OrderRepository repository;

    public Order save(Order order) {
        return repository.save(order);
    }
}
Enter fullscreen mode Exit fullscreen mode

This works. It's fast to write. Your DevOps team is happy because you delivered quickly. But here's the problem: your business logic now knows about JPA, Jackson, and probably a dozen other framework concerns.

Clean architecture says your domain shouldn't know about these things. But Spring Boot is right there, making it so easy to just add one more annotation.

The Package-by-Layer Trap

Most tutorials teach you this structure:

src/main/java/
  ├── controller/
  ├── service/
  ├── repository/
  └── model/
Enter fullscreen mode Exit fullscreen mode

It feels natural. It's what everyone does. But it completely misses the point of clean architecture.

The problem isn't the folders—it's that this structure encourages you to think in terms of technical layers instead of business capabilities.

When you organize by feature instead:

src/main/java/
  ├── order/
  │   ├── domain/
  │   ├── application/
  │   └── infrastructure/
  ├── product/
  └── customer/
Enter fullscreen mode Exit fullscreen mode

Something interesting happens: you start thinking about what the system does rather than what technologies it uses.

What I Learned Building Real Systems

In previous roles, I've worked on high-throughput billing systems and real-time data platforms. I've also architected multi-tenant public administration systems. Here's what actually mattered:

1. Business Logic Should Be Boring

Your core business rules shouldn't care about:

  • How data is stored (PostgreSQL? MongoDB? Firestore?)
  • How requests arrive (REST? GraphQL? Message queue?)
  • Which framework you're using

When I needed to migrate a system from MongoDB to PostgreSQL, the pain level told me everything about the architecture quality. If your business logic is tangled with database annotations, that migration becomes a nightmare.

2. Interfaces Without Purpose Are Noise

Don't create interfaces just because someone said "clean architecture needs ports and adapters." Create them when you actually need to swap implementations or isolate dependencies.

I've seen codebases with repository interfaces that have exactly one implementation, forever. That's not architecture—that's ceremony.

3. The Real Test Is Change

Can you:

  • Switch from PostgreSQL to another database without touching business logic?
  • Change your REST API to use GraphQL without rewriting services?
  • Replace Spring Boot with Micronaut (hypothetically)?

If the answer is "yes," you probably have decent architecture. If the answer is "are you insane?", then the folder structure doesn't matter—you're coupled.

The Practical Middle Ground

Here's what I actually do in production systems:

Keep business rules clean:

public class CancelOrderUseCase {
    private final OrderRepository orderRepository;
    private final PaymentGateway paymentGateway;

    public void execute(Long orderId, String reason) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFound(orderId));

        if (order.isAlreadyShipped()) {
            throw new CannotCancelShippedOrder(orderId);
        }

        if (order.isPaid()) {
            paymentGateway.refund(order.getPaymentId());
        }

        order.cancel(reason);
        orderRepository.save(order);
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice: no @Service, no @Transactional, no JPA annotations. Just business logic.

Adapt at the boundaries:

@Service
@Transactional
public class OrderService {
    private final CancelOrderUseCase useCase;

    public void cancelOrder(Long orderId, String reason) {
        useCase.execute(orderId, reason);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now the Spring stuff lives at the edge. The business logic doesn't know about it.

Stop Overthinking, Start Measuring

Instead of debating folder structures, ask yourself:

  1. How hard is it to test my business logic? If you need to spin up a database or mock 15 dependencies, something's wrong.

  2. How often do framework updates break my code? When Spring Boot 4.0 dropped, how many files did you have to touch?

  3. Can a new developer understand what the system does? If they have to read through controller → service → repository → entity just to understand one feature, your architecture is hiding the business logic.

What Actually Matters

After building systems across different industries—from financial services to public administration and beyond—here's my honest take:

Clean architecture isn't about folders. It's about keeping your business logic independent from the frameworks and tools that deliver it.

You don't need perfect hexagonal architecture on day one. You need to make sure that when requirements change (and they will), you can adapt without rewriting everything.

The confusion exists because we focus on the wrong things. We argue about package names while our domain models are covered in @Entity annotations. We create elaborate folder structures while our business logic lives inside Spring controllers.

Start simple. Keep your business rules clean. Test them without mocking the world. Everything else is just details.

Top comments (0)