Designing an Interface with Multiple Implementations in Mind
When designing software interfaces intended to support multiple concrete implementations, one of the most common pitfalls is allowing familiarity with a specific implementation to shape the abstraction too early. A good interface should reflect the shared essence of all intended implementations — not just the convenient features of the one you already know well.
This is easier said than done. Our understanding is inevitably colored by our experiences, and when those experiences are limited or skewed, the designs we produce often carry those limitations forward. This became vividly clear to me in a completely different context — during a visit to Potsdam, Germany.
A Lesson from Potsdam’s “Chinese” Tea House
On a visit to Potsdam, I stopped by the so-called “Chinese Tea House” in the park of Sanssouci Palace. I’ve spent a lot of time in China — visiting numerous cities, historic sites, and cultural landmarks — so I was curious to see how 18th-century Europeans interpreted Chinese design.

What I found, though, did not resemble a Chinese tea house in the slightest. Instead, it was a fascinating and deeply flawed interpretation — an ornate, whimsical structure decorated with gold leaf and figurines wearing vaguely “Eastern” attire. It was, in essence, a German tea house dressed up in exotic motifs. It had little to do with Chinese architecture or cultural sensibilities, and everything to do with how Europeans of the time imagined China should look.
This dissonance isn’t surprising when you consider the context: the sovereign who commissioned the tea house had never left Germany. He reportedly decorated his residence with paintings of Venice’s St. Mark’s Square — accurate reproductions painted by artists who had likely seen it with their own eyes. In contrast, those tasked with designing the Chinese Tea House had never been to China. At best, their knowledge might have come from second-hand accounts, brief travels through trading ports, or seeing items like porcelain in a wealthy merchant’s home. Perhaps one artist on the team had actually visited Chinese towns — though probably never set foot in a palace or the Forbidden City.
Their version of “China” was imagined through the narrow lens of limited experience and filtered through layers of interpretation. The result was a stylized fantasy, not a faithful reproduction. The building reflects far more about 18th-century German aesthetics than it does about Chinese architecture.
Bridging This Lesson to Software Design
This same principle applies when designing software interfaces intended to support multiple systems. If you base your abstraction on your understanding of just one backend — for example, Azure Event Hubs — it will likely reflect that backend’s assumptions and peculiarities. You might expose partition IDs or offset semantics that are essential in Event Hubs, but irrelevant or even misleading for something like Azure Service Bus.
Service Bus, with its support for queues, topics, sessions, and dead-lettering, has an entirely different model. If your interface doesn’t account for these differences from the start, you may find it difficult — if not impossible — to integrate it cleanly. Worse, your implementation might mislead users about the capabilities or guarantees of each system.
Just like the artists of Potsdam’s tea house, designing in ignorance of the real characteristics of the other “cultures” leads to poor mimicry. You may build something that looks general but is, in fact, deeply specific — an abstraction that’s skewed toward the system you know best, peppered with awkward accommodations for others.
Design from Genuine Understanding
To avoid this, you must spend time with each system you intend to support. Explore not just their APIs but their conceptual models. How do they handle message acknowledgment? What are their failure modes? What delivery guarantees do they offer? Don’t assume equivalence where there is none, and don’t impose the design of one onto another.
Once you’ve explored the landscape, only then is it time to design the interface. With a full view in hand, you can identify the genuine commonalities — and isolate the meaningful differences. Perhaps both systems can expose a ReceiveAsync() method returning a normalized message type, and an AcknowledgeAsync() that abstracts away the underlying mechanism. You might delegate system-specific functionality to a secondary interface or configuration, keeping the primary interface clean and minimal.
Conclusion
Abstraction is powerful — but only when it’s informed. If your knowledge is narrow or one-sided, your interface will reflect that bias. Much like the misinterpreted Chinese Tea House in Potsdam, it may end up being more of a stylized echo than a faithful bridge between worlds.
Designing a truly reusable interface demands more than technical cleverness; it demands curiosity, empathy, and a willingness to step outside the familiar. You can’t do justice to something you don’t understand — and that’s as true in code as it is in architecture.
