Fork me on GitHub
#architecture
<
2023-04-10
>
pavlosmelissinos12:04:09

How do you decide when it's the right time to introduce an abstraction? Are there better guidelines than e.g. DRY (which, even though is a nice indicator, as we all probably know, it's neither a necessary nor sufficient condition) I think I have a good sense and control of it when I'm working on stuff I own 100% but it's harder to communicate this to business users when designing an interface and that often results in duplicate configurations or API components with overlapping logic, etc. If anyone else shares that experience I'd love to hear/read about it.

pavlosmelissinos13:04:19

One example is implementation details leaking to APIs. If the user doesn't care what kind of storage is used by the application, anything about postgres or datomic or whatever DBMS is being used must stay within the confines of the implementation. Even if the implementation requires a storage type, handle it in the background. If it makes sense to use one or the other depending on the case, present the business value of each and choose internally, otherwise just use a sensible default db. In any case, don't force a business user to make a technical decision.

vemv13:04:58

> How do you decide when it's the right time to introduce an abstraction? I'd say, when its absence really hurts and can be seen all over the place, in terms of complexity, brittleness, etc > If the user doesn't care what kind of storage is used by the application, anything about postgres or datomic or whatever DBMS is being used must stay within the confines of the implementation. Hiding the db is a minefield IMO. It's sensible to abstract over a particular RDBMS vendor, but pretending datomic and postgresql are the same thing can give you the worst of both worlds In specific terms, datomic-based and relational/in-place architerctures are typically vastly different. Neither of them are 'dumb storage' for objects.

👍 1
pavlosmelissinos13:04:15

Right, I was trying to think of a non-trivial example but they are indeed very different. Perhaps S3 vs GCP Cloud Storage would be a better one.

vemv13:04:38

...One idea I like a lot (which however is more preached than practiced) is having a pure, functional core, exclusively centered in the domain and business logic. That would be the DB-agnostic part. You'd have non-DB-agnostic code invoking that functional core. Ideally, one would be able to throw away the non-agnostic parts in case of a big refactoring, while the core would remain untouched Emphasis on throwing away! No refactoring, no abstractions. In an ideal layered architecture, the impure part is thin and therefore disposable

vemv13:04:07

> Perhaps S3 vs GCP Cloud Storage would be a better one. Probably it's a gradient from "don't abstract this" to "seems nice to have a defprotocol to it". For this example, an abstraction seems sensible for S3 but where does it end? In an extreme example, you might end up creating a multi-cloud abstraction for all sorts of cloud-computing functionality. (there's a Ruby gem like that...) Perhaps we can say "it depends on the context"? 🙂

👍 3
pavlosmelissinos13:04:27

> I'd say, yhen its absence really hurts and can be seen all over the place, in terms of complexity, brittleness, etc So it might be a good idea to stay away from abstractions until it's clear that they're necessary (of course it depends on the context). > a pure, functional core, exclusively centered in the domain and business logic Huh, that's kind of the opposite direction I've been going. On one hand I hear what you're saying; the core of an application should be stable, a strong foundation for the rest of the application (outer layers). On the other hand, you can't have business logic right at the core (for instance in order to build formulas with business value you need to use some more generic abstractions, e.g. statistics stuff) and APIs should also be stable, so they can't have logic that has to change often or is unrelated with the business logic. Perhaps the thin, impure layer should be right underneath the API. :thinking_face: I'm not sure how all that should be balanced, especially when the application already has some abstractions in place but the business needs are gradually drifting to a different direction.

👀 2
Noah Bogart14:04:20

I rewatch this video every time I ask this question: https://www.youtube.com/watch?v=8bZh5LMaSmE

👀 1
Noah Bogart14:04:07

Sandi Metz works primarily in ruby and is talking to Ruby folks about OOP but the way she emphasizes small objects that know nothing about each other and holding off on making an abstraction until it becomes absolutely necessary has been helpful to me in my clojure journey

👌 2
pavlosmelissinos14:04:37

Sounds promising! I'll take a look, thanks 🙂

andre.stylianos16:04:51

ccccccifckhkhtrhdndngklfhertkhrhevkchhkvlkkv

😹 8
👀 1
andre.stylianos16:04:00

Ignore that, just my cat walking over my keyboard while I was reading the thread :face_with_rolling_eyes:

andre.stylianos17:04:40

In general terms I agree with what's been said so far, but I'd like to also add another, though somewhat related, point; hold off on adding an abstraction until you actually have a decent understanding of your use cases and why the abstraction is needed. Taking the DB thing for example; not only, as vemv (hello 👋) said, that can be a minefield but also a lot of people add multiple layers of abstractions on top of that "just because" as if switching a working product from one storage to another was something that happened frequently. Most cases I saw of those just added up a ton of complexity for no benefit. To sum it up, I'd probably ask not only "if" an abstraction is needed, but "why" it's needed

👍 2
👋 1
2
Rupert (All Street)20:04:07

Clojure provides multiple way to do abstractions: • multimethods • protocols • high order functions Using any of the above with a dependency injection framework gives you a lot of flexibility to change implementations in future. All functions are already abstract to a certain degree (e.g. inc and dec are interchangable - no protocol or multimethod required!):

(map inc (range 10))
(map dec (range 10))
If you are happy for usage and implementation to be tightly coupled - little abstraction is required. If usage and implementation require decoupling then abstraction is the way to go. In some (perhaps many) situations abstractions can actually be very low cost in Clojure code (i.e. very minimal extra code and very low performance impact) - so if abstraction provides benefit there is little downside.

Rupert (All Street)20:04:09

Abstraction often follows https://en.wikipedia.org/wiki/Conway%27s_law: Abstractions are often added at the intersections of departments/teams to keep them decoupled.

👍 2