Hacker News new | ask | show | jobs
by ThaJay 1677 days ago
The abstraction is the standard case and should invite custom behaviour to be added dynamically. This extra behaviour should be added by the caller / user of the abstraction.

Especially in a CRUD situation, every custom procedure will probably have a few basic steps that stay the same. All you usually need is a pre and a post hook.

1 comments

See, but that's exactly what I'm saying does not work.

It's tempting to think that for e.g. a CRUD situation, pre- and post-hooks are all you need.

And it may be that way at the beginning, although hardly so, except for the simplest applications. But very soon, you start to run into things like (not an exhaustive list, but all are things I actually encountered while trying to do precisely what you're saying, many years ago):

* Transactions, when multiple things have to happen atomically. Your pre-hook must start a transaction, and your post-hook must commit it. But what if there's an error somewhere? Python and JS don't have RAII, so you need some kind of catch block to abort the transaction. Where does that happen?

* Tricky validation, e.g. needing to do queries against the data store to check the request is well-formed. So if you're using an event-based language, your pre-hook also needs to be asynchronous.

* Data transformations (what the user sends will hardly be what needs to end up in your data store), so your pre-hook needs to be able to return a new object.

* Tracing across service calls, so you need to pass some kind of request ID as well to your hooks.

* An "update" request needs to return something (e.g. an ID) to avoid another round trip. But sometimes it needs to return more stuff, so your post hook must be able to return data. Oh, but your ORM or DB usually returns the created object, so you'd like to use that instead of re-fetching in your post-hook, so now your post-hook must accept that as well.

These just keep coming up. The first three in particular are usually guaranteed to happen before the first release of the product, since requirements always change. Soon you have an unmaintainable monstrosity. A little copy-paste is tame in comparison.

Exactly right, transactions being the killer/most common one.

And the proposed solutions of hooks, AOP etc all start down the road of scattering actual logic across files and methods making the code harder to reason about which seems to be the worst part of overeager abstraction.

The library Automapper from C# is another place I run into this a lot. At its core it simply maps from domain to dto properties but you soon end up needing awful unwieldy configs and magic. I'd rather have a few hundred lines of obvious mapping than ever work with automapper again.

Some of those could perhaps be solved by an "around" hook, in addition to pre- and post-hooks, like advice[1] in Emacs. Although in OOP maybe that would be represented by a decorator class (or a subclass) that overrides the method and calls the original method?

[1] https://www.gnu.org/software/emacs/manual/html_node/elisp/Ad...