Both of those ideas aren't criticisms of abstraction though, those are criticisms of doing abstraction badly.
I don't agree. The criticism is that of abstracting the wrong things. The third-party library you're using is already an abstraction of something - and there could be reasons to further abstract that, but often there isn't, and just avoiding coupling, in my opinion is definitely not the right motivation.
To solve the first problem, just... don't add the third-party library's implementation details into your abstractions's interface. Leaking the implementation details defeats the whole purpose of abstraction.
Even if you don't leak implementation details, you're going to "leak concepts" - ideas from the underlying library are likely going to bleed into your domain, even if things like types and interfaces do not.
Some reasons I might choose to abstract would be:
The library is really complex and does a lot more than I need - in that case, I can avoid direct coupling to a complex library by hiding it behind a simple interface.
The library doesn't quite do everything I need - in that case, I can build an abstraction that adds in the missing details, and again avoid direct coupling to something that wasn't quite what I needed in the first place.
On the other hand, why would I abstract something if it's already (more or less) exactly what the project needs? If I put my own very similar units in front of some library units, any ideas of being decoupled is really just an idea - if anything changes, it's practically guaranteed to break my abstraction.
I think that's the case he's talking about in the video.
Every line of code, whether that's your code or library code, adds complexity: every line of code is a liability, so every line of code needs to have a specific, measured reason to exist.
In my experience, the most successful projects are always the ones with less complexity.
So it has to be a conscious, case-by-case decision, in my opinion.
If I put my own very similar units in front of some library units, any ideas of being decoupled is really just an idea.
This is assuming a one-to-one distinct abstraction to concrete library relationship. That type of alignment isn't necessarily the best way to move forward.
In my experience, the most successful projects are always the ones with less complexity.
I think it's more important to evaluate if complexity is managed appropriately. In my view OOD invariably adds complexity in order to manage complexity — it can work but it often isn't a slam dunk.
So it has to be a conscious, case-by-case decision, in my opinion.
That's pretty much a given. Guidelines tend to be a starting point, not some absolute truth.
What ultimately devalued the video for me was the example - why would there be a need for a concrete messaging abstraction? The actual goal is to have the application logic be "ignorant" of the messaging solution that is being used to handle messages. This idea is similar to Persistence Ignorance (PI):
Well, PI means clean, ordinary classes where you focus on the business problem at hand without adding stuff for infrastructure-related reasons.
Jimmy Nilsson, Applying Domain-Driven Design and Patterns 2006, p. 183
"The Application" will only need to send a finite number of message types, and receive a finite number of message types. Worst case each send-type has its own function into the infrastructure and the application exposes a separate function for each receive-type. The application is only interested in providing the data for outgoing messages and extracting the data from the incoming messages. The application really doesn't care what happens on the other side of the application boundary.
Rabbit MQ has to live in the Horrible Outside World (HOW)
The application exists in the Happy Zone (HZ).
The HOW and HZ are separated by the DeMilitarized Zone (DMZ; where the "adapters" live; narrowing API, pattern of usage API)
The HZ can depend on the HZ
The DMZ can depend on the DMZ
The DMZ can depend on the HZ
The HOW is permitted to depend on the HZ
The DMZ may depend on the HOW
The HZ cannot depend on the DMZ
The DMZ is allowed to talk to the HZ by implementing an HZ interface.
The HZ cannot depend on the HOW (… and yet this is exactly what many frameworks encourage you to do — I call it the creepy hug problem — it's nice to be hugged unless the hug is a little bit too long and a little bit too tight by somebody you don't know that well)
The HOW is allowed to talk to the HZ by implementing an HZ interface.
Now again the reason you're abstracting these things is because you want to isolate them. And you might want to isolate them because you don't want to depend on them directly. Again most people say so I can swap it out.
The "swap out" argument is specious but isolation is the prize.
Isolation is the pre-requisite to being able to leverage fast micro-tests — "tests to detect change", i.e. establish rapid feedback as we refactor the code to reduce the volatility in the marginal cost of adding the next feature.
Doesn't this HOW/DMZ/HZ stuff slow us down?
In the beginning perhaps but again J.B. Rainsberger explains The Scam:
The cost of the first few features is actually quite a bit higher than it is doing it the "not so careful way" … eventually you reach the point where the cost of getting out of the blocks quickly and not worrying about the design is about the same as the cost of being careful from the beginning … and after this being careful is nothing but profit.
I don't agree. The criticism is that of abstracting the wrong things. The third-party library you're using is already an abstraction of something - and there could be reasons to further abstract that, but often there isn't, and just avoiding coupling, in my opinion is definitely not the right motivation.
Even if you don't leak implementation details, you're going to "leak concepts" - ideas from the underlying library are likely going to bleed into your domain, even if things like types and interfaces do not.
Some reasons I might choose to abstract would be:
The library is really complex and does a lot more than I need - in that case, I can avoid direct coupling to a complex library by hiding it behind a simple interface.
The library doesn't quite do everything I need - in that case, I can build an abstraction that adds in the missing details, and again avoid direct coupling to something that wasn't quite what I needed in the first place.
On the other hand, why would I abstract something if it's already (more or less) exactly what the project needs? If I put my own very similar units in front of some library units, any ideas of being decoupled is really just an idea - if anything changes, it's practically guaranteed to break my abstraction.
I think that's the case he's talking about in the video.
Every line of code, whether that's your code or library code, adds complexity: every line of code is a liability, so every line of code needs to have a specific, measured reason to exist.
In my experience, the most successful projects are always the ones with less complexity.
So it has to be a conscious, case-by-case decision, in my opinion.
This is assuming a one-to-one distinct abstraction to concrete library relationship. That type of alignment isn't necessarily the best way to move forward.
I think it's more important to evaluate if complexity is managed appropriately. In my view OOD invariably adds complexity in order to manage complexity — it can work but it often isn't a slam dunk.
That's pretty much a given. Guidelines tend to be a starting point, not some absolute truth.
What ultimately devalued the video for me was the example - why would there be a need for a concrete messaging abstraction? The actual goal is to have the application logic be "ignorant" of the messaging solution that is being used to handle messages. This idea is similar to Persistence Ignorance (PI):
"The Application" will only need to send a finite number of message types, and receive a finite number of message types. Worst case each send-type has its own function into the infrastructure and the application exposes a separate function for each receive-type. The application is only interested in providing the data for outgoing messages and extracting the data from the incoming messages. The application really doesn't care what happens on the other side of the application boundary.
Quote
Rabbit MQ has no business being inside the application boundary.
To use J. B. Rainsberger's terminology:
FYI: Mock Roles, not Objects
Quote
The "swap out" argument is specious but isolation is the prize.
Isolation is the pre-requisite to being able to leverage fast micro-tests — "tests to detect change", i.e. establish rapid feedback as we refactor the code to reduce the volatility in the marginal cost of adding the next feature.
Doesn't this HOW/DMZ/HZ stuff slow us down?
In the beginning perhaps but again J.B. Rainsberger explains The Scam:
He acknowledges that "the scam" is initially incredibly seductive but eventually there comes the point where the cost of continuing is higher than the cost of starting again.
So the initial investment is aimed at going well for long enough, so you'll beat fast all the time.