Medium zalando companies

How do you apply SOLID in a small Java design, and when does it become over-engineering?

The 30 second answer

I treat SOLID as reasons to split or not split code, not a checklist. Single responsibility means one reason to change, tied to one group of people. Open-closed means adding behaviour without editing stable code, but only where change is expected. Liskov means implementations never surprise callers of the interface. Interface segregation keeps interfaces small, and dependency inversion keeps business rules independent of details. Abstractions need a real second case.

Public candidate reviews of Zalando interviews list SOLID next to merge sort and maps. Interviewers rarely want definitions alone; they want to see the principles applied, and to hear when you would not apply them.

The principles, in the author's words. Robert C. Martin's own summary:

  • Single responsibility. "Each software module should have one and only one reason to change." He adds that the principle "is about people": a module should answer to one group of stakeholders. Another wording: "Gather together the things that change for the same reasons. Separate those things that change for different reasons."
  • Open-closed. "A Module should be open for extension but closed for modification."
  • Liskov substitution. "A program that uses an interface must not be confused by an implementation of that interface." Martin stresses that it is about subtyping, not only inheritance.
  • Interface segregation. "Keep interfaces small so that users don't end up depending on things they don't need."
  • Dependency inversion. "Depend in the direction of abstraction. High level modules should not depend upon low level details."

Applied to a search example. A SearchService that parses the request, builds an Elasticsearch query, calls the client, re-ranks and formats JSON has several reasons to change. Split by who asks for changes: query building (search relevance engineers), ranking (data scientists), transport and formatting (API owners). The service depends on a ProductIndex interface rather than the Elasticsearch client (dependency inversion), so a client upgrade or a test fake does not touch ranking. A RankingStrategy interface is open for new strategies in A/B tests without editing the service (open-closed). Every strategy must return the same products it was given, reordered, never dropping or adding items silently (Liskov). Callers that only read suggestions get a small Suggester interface, not the whole index API (interface segregation).

When it becomes over-engineering. An interface with one implementation and no test seam, factories for objects that never vary, and layers that only pass calls through all add indirection without a reason to change behind them. Martin's own answer to "just write simple code" is that he agrees, and that the principles are a way to keep code simple. The useful rule: add an abstraction when there is a second real case, a test that needs a seam, or a stated likely change. The card game question above shows that judgement in a live exercise.

Alternate interview scenarios

"Give an example of a Liskov violation."

A read-only product view that implements a MutableCatalog interface and throws UnsupportedOperationException from update breaks callers that rely on update working. Java's own unmodifiable collections make this trade-off deliberately, and document it as an optional operation; in your own domain, prefer two interfaces, one for reading and one for writing.

"A payment or pricing class has a growing if-else on type. Which principle helps?"

Open-closed with a strategy per type, selected by a map or by a sealed interface with an exhaustive switch. Each new type is a new class, not an edit to a growing method, and each strategy can be tested alone.

Sources (3)
  1. Robert C. Martin: The Single Responsibility Principleblog.cleancoder.com
  2. Robert C. Martin: Solid Relevanceblog.cleancoder.com
  3. JEP 409: Sealed Classesopenjdk.org