eShopOnWeb has a couple of its service classes (BasketService and OrderService) implement corresponding interfaces (IBasketService and IOrderService). These interfaces have no other implementations (even in test projects) so the value of including them in the architecture is highly questionable.
IBasketService and its references are shown below… click on the image for a larger view in a new tab.
Single implementation interfaces in my opinion are just pointless. The whole point of using interfaces is that we can swap out multiple implementations without our calling code having to care about which actual concrete class is doing the end work.
When we have only one implementation of an interface, we are paying the cost of having the interface without getting any benefit. It’s easy to extract an interface when it’s needed so there’s no need IMHO to create interfaces ahead of time ‘just in case’.. and always remember YAGNI.
Using interfaces to aid testing
Testing is a special case whereby we might only have one implementation of an interface in our app itself but we have another implementation in our test project to facilitate plugging in mocked or stub test versions of our classes. In this case we still have multiple implementations… whether it’s a good idea to add a load of interfaces to our apps just for testing is debatable and is a question for a different day but we definitely are not in a single implementation scenario.
Using interfaces for DI
We don’t need interfaces to use dependency injection, this works…
Does it make sense to ever have multiple implementations of service classes?
Of course eShopOnWeb is just a reference architecture, it’s not a completed app, if it were perhaps there would be more implementations of IBasketService and IOrderService… BUT still the problem I see with this is that our services classes (and below) are basically our apps. The only thing we usually have above our service layer might be a thin HTTP or console app layer.
Our service classes are the main way into all the use cases and functionality of our app and contains all our business logic and usually already have their dependencies passed in. This means we don’t need multiple implementations for testing.
What about having multiple implementations in our app itself? Well are we really going to have multiple ways to implement our core use cases? For example below in the AddItemToBasket method we..
- Check for existing basket
- If there is none we create one
- Then add the item to the basket
- Then persist it to our datastore
The four steps needed to add an item to basket won’t change, if they do it’s a different use case and will be implemented by a different method.
IMHO the inclusion of the IBasketService and IOrderService interfaces just adds bloat and unnecessary complexity to the architecture.