Most of my day-to-day work is in Elm. The combination of a functional language and the Elm Architecture makes many architectural decisions almost invisible (I talk more about that in this blog post). You get a clear separation of concerns, and the language nudges you toward good design by default.
But my work isn’t limited to Elm. I frequently find myself building features that span both frontend and backend—writing new endpoints, and sometimes even designing new database tables. When I step outside the Elm world, I’m reminded that architecture is something I have to be intentional about again.
This post isn’t about which code goes in which layer, or about specific design patterns. Instead, I want to zoom in on a deceptively simple choice: should you just implement the thing you need, or should you start by carving out an abstraction?
Let’s take a concrete example. Suppose I need a SearchCacheRepository—something to store and retrieve cached search results. I could just write a class that does what I need, jumping straight to creating the appropriate tables (and indices) and call it a day. But instead, I find myself reaching for an interface: