Inversion of control was in part introduced to fix the problem of writing tests. How can we write tests, focussed on a small part of our application, if every dependency is instantiated inside the part we are trying to test? Simple. We introduced interfaces so that we were in control of the implementation.
The goal of writing tests is to ensure your code remains stable when you add or change existing features. In order to reduce the costs of writing tests we started to use interfaces. This enabled us to ignore other, irrelevant parts of the application within a test.
So an interface is just a contract that contains the signature of methods, properties, events or indexers. It does not contain any implementation. This gives us the freedom to choose the implementation that fits our test the best.
Interfaces and Domain-Driven Design
In an application that is Domain-Driven Design centric you have a domain layer. This part is the heart of your application. It contains state and behaviour and makes sure it remains consistent at all times. It should be storage agnostic and have the least dependencies in your application. The domain does not know about the application layer, it goes inwards. The UI knows the application layer and the application layer knows the domain layer.
Domain is the heart of the application
When you are writing tests that involve the heart of your application it makes no sense to fake it. You could ask yourself the question: What am I actually testing? You should be testing your implementation.
Thus, you shouldn't use any interface inside your domain model. Although any interfaces is maybe a bit exorbitant. What I really mean is: you do not want interfaces like
IAggregateRoot. As you would give the opportunity for the wrong part of your application to be faked.
An interface defines signatures but no implementations. If a class (or struct) implements an interface it is a can-do relationship.
Order : IAggregateRoot An Order can-do IAggregateRoot is meaningless. Inheritance on the other hand defines an is-a relationship.
Order : AggregateRoot An Order is-an AggregateRoot.
As interfaces do not contain any implementation details it would be difficult to reuse certain methods or even properties. What if you want to use a private setter for a property and define it in an interface? It is not possible. As a result the property would be really read-only. Entity Framework Core up until version 2.1 did not support writing to a property when it only had a getter available.
Attributes on interfaces
Another problem with interfaces, although it is not a good idea, is that attributes are not inherited when you implement an interface. If you use a RequiredAttribute to indicate its value is required it will silently fail in your classes.
The yellow circle indicates the boundary of the application service
Let's focus on this last part: orchestrating. What would be the consequence of faking this part? You would lose one of the main responsibilities. What you really want to test is the orchestration logic. So that you can fetch the needed information, manipulate it and that your unit of work completes its action successfully. If an email needs to be send you want to be sure the correct code is called the right amount of times with the correct parameters. You don't want to be able to fake this part. What you really want is to fake all its dependencies and verify that these dependencies were all called with the right parameters. So interfaces between layers do make sense, but inside the application layer you might want to avoid them.
In conclusion: think about the impact of using interfaces. Are you using them on the right class? Do you want this class to have another implementation via inversion of control? And if tests are not one of your concerns, think about reusability because properties defined on an interface need to be re-implemented all the time...