> Define a plugin interface for every side-effecting part of your code, and pass in safe plugins when you want to test it.

I think we're on the same page so far

> the core of your application should be pure objects, exposing only methods and no data.

I'll put my thumb on the scales here and say that classes should be all-behaviour or all-data. In that context, he's talking about structuring the behaviour classes in your application. Take the following (I don't recommend it because it violates DIP, but that's not the point I want to make yet):

UserService -> UserStore { User getUser(uid) {..} }

Since getUser is exposed as a method, you could swap out UserStore for PostgresUserDatabase or anything else that exposes getUser(). But if you reached directly into UserStore's HashMap (exposed data), then you're stuck with HashMap storage and can't replace it with a database. As for User itself (all-data) I don't think it's a big deal if you read its data directly (e.g. user.name.firstName). Some folks insist on a getter there, but I say if you're doing the architecture well, it should be no problem to change it to a getter later on if that's what you actually need. I think that is all that is meant by "exposing only methods and no data".

> you have to pass the actions in

I think I get where you're going with this, but first

> This is a bonkers, topsy-turvy world

Topsy-turvy synonyms: reversed ... upside-down ... inverted? Yes, inverted! I, for one, vote that we rename DIP to BTTDWP, the "Bonkers topsy-turvy dependency world principle."

I suspect that Uncle Bob used a particular convention for his arrows and didn't define them in this talk, which is why you find it bonkers. The arrows are dependencies in the "who-knows-about-whom" sense, not the "who-invokes-whom" sense. The call-chain still goes in the expected ('forward') direction, but it's the knows-about arrows that are flipped in BTTDWP.

Here's an (incorrect) 3-part system, prefixed with (W)eb, (B)usiness and (D)atabase, for a typical getUser() flow.

WUserController -> BUserService -> DUserStore

There's no risk here of BUserService accidentally coupling to Web stuff (unless BUserService decides to return a WUser instead of a BUser.) But there is every risk that BUserService will treat DUserStore like a concrete database. BUserService will start doing things like opening Hikari connection pools and beginning/ending SQL transactions, and then you'll never be able to test it in isolation.

All BTTDWP means here is to flip the 'knows-about' arrows so that the concrete, outside bits (which Uncle Bob keeps calling plugins) know about the B classes, instead of the other way around:

WUserController -> BUserService <- DUserStore

The call chain still flows in the original direction! This is done with interfaces in mainstream OO:

WUserController -> BUserService -> IUserStore
<- DUserStore

I think that's how I'd push back on "you have to pass the actions in".

> If you could, you would just write {pure data processing routine}, test it, use it in your side-effecting procedures, and go about your day.

Yes this is right. Your side-effecting procedures are typically the Database or other plugins, and your data processing is typically your business logic. Calling into the business logic from the plugin means that BTTDWP is already satisfied and you don't have to reverse any arrows.

> And since you want those to be encapsulated, you can't let them leak out, you have to pass the actions in, so that you can do them in your core.

I don't think leakage is a problem in the above W->B<-D setup. If you're standing in W, and you call B for something that's stored in D, the usual W->B->(I)D call-chain happens, but because B doesn't know about D (hidden behind I), it can't possibly leak D-specific stuff out to W. It's completely up to B as to what it wants to return/leak back to W.

FWIW, I believe Uncle Bob slightly misspoke from 52:20:

"I don't want your application to have outgoing dependencies" <- Correct
"Your application is composed of business rules, and those business rules are the family jewels." <- Correct
"You keep those jewels in a nice little bag and you don't let people look in." <- Misleading
"You keep them isolated; you don't let the frameworks touch them." <- Possibly misleading / needs context

"Looking in" is fine. He's just spent 30 minutes saying that all the (know-about) arrows point in, right!?!? And you certainly don't do the reverse: the family jewels aren't allowed to "look out" at the database or the web.

I think he clarified it slightly in the next sentence, though: "you don't let the frameworks touch them". I think he meant that you do not put framework code into your business logic classes - you shouldn't see any Spring or Postgres or Hikari in the imports sections of your PayrollCalculator ("PayrollCalculator shouldn't wear the Spring wedding ring"). But Spring can ask your PayrollCalculator for the numbers it needs to return back out to the web client, which I would consider somewhat "looking in".

It would be out of character for me to not plug Haskell at this point - so I will say just this: That pesky IO monad thing that everyone keeps complaining about? That's just another incarnation of this, but it's less forgiving to newcomers because it results in a compile-time error (rather than untestable code that gets monkey-patched with shudder Mockito.) IO can call pure, but pure can't call IO. That's all it is! It just happens to be more important in a lazy setting where the compiler will lift and lower expressions in and out of each other and only evaluate expressions insofar as a caller is demanding results.