Basically everybody knows the KISS principal (keep it simple, stupid). But unfortunately most of the time it get's overwritten by the DRY principle (don't repeat yourself) which gained traction through the Clean code movement.

In general there is no problem with the principal itself, but as with all principals: We need to know when to follow them and when to ignore them!

And we need to ignore the DRY principle when working on the domain (business logic). The business logic is never static. It will always change and we will need to keep track whether our code is still aligned to what is happening in the business. The worst thing you can have there is abstractions.

  1. It's more complicated to understand and read (takes more time and leads to misunderstandings)
  2. It's more work to refactor (because you also need to validate and adapt the other instances where it decoupled logic is used)
  3. It has trickle down effects for tests which also needs to be adapted

Basically every issue you will have when running into premature optimization (which is when you optimize something before it has proven to stay the same in multiple cases).

You could say: Abstraction in the domain is always premature optimization!

But we're developers, we're always trying to optimize our code. So how do we prevent to fall into those traps?

The best way I found is through CQRS (command query responsibility segregation). And that's simply because every action that is taken in the system is handled through a separate command with its separate data and its separate command handler. You are still able to use external services within the command handler but you have one handler which should contain all the business logic and therefore have a barrier to other actions.

You could also put you domain (business logic) into services. But then you might run into the issue where to put the "helpers". Which aren't really domain, but are also services. I saw this too many times that there is a "user service" which handles the domain, the authentication and password generation (application logic), the conversion to read models  and much more in a single class. And the methods there are of course used for multiple actions in the system.

Working with CQRS prevented this in the projects (I've saw) simply because there's one more barrier. It also makes testing your actions way easier, but that's a topic for another time.