Inheritance in EF Core (TBH, TPT, TPC) is a nice feature for entities which have many in common. But I am often arguing with a colleague about the design of business logic layer.
Entities:
public class FruitEntity {}
public class AppleEntity : FruitEntity {}
public class PearEntity : FruitEntity {}
We use the Repository Pattern to access the data (data access layer).
Now how to design the Business Logic Layer?
My approach is simple, separate and make a Service for each Entity.
AppleService
public class AppleService
{
private readonly IRepository<AppleEntity> _appleRepository;
public AppleService(IRepository appleRepository) {
_appleRepository = appleRepository ?? throw new ArgumentNullException();
}
public async Task<AppleEntity> GetAsync(Guid appleId)
{
//more business logic
return await _appleRepository.GetAsync(appleId);
}
}
PearService
public class PearService
{
private readonly IRepository<AppleEntity> _pearRepository;
public PearService (IRepository pearRepository) {
_pearRepository = pearRepository?? throw new ArgumentNullException();
}
public async Task<PearEntity> GetAsync(Guid pearId)
{
//more business logic
return await _pearRepository.GetAsync(pearId);
}
}
In my opinion: Its clearly separated and if we want to change any of the services we don't have any overlap between Pear and Apple use cases. So its all about single responsibility principle. Cons: One of the arguments of my colleague is the overhead while testing. We need to test every method which are nearly identical. To avoid the test overhead we could make an abstract class for the services. Alternative design of the business logic layer:
public abstract class FruitService{}
public class AppleService : FruitService{}
public class PearService : FruitService{}
I guess most of the code will be delegates. Therefore the test overhead will be smaller, best case half of all.
Are there any other styles, pattern, designs I could or should approach?
If you have common behaviour between your services nothing would speak against using inheritance, if it avoids code duplication.
The fact that i see "DomainServices" acting directly on your Entities, hints me towards an Pattern from Martin Fowler called the "Transaction Script". For simple use cases, like storing and retrieving data, maybe some validation logic, the pattern is perfect. But, as he notes in his book, duplication is a common problem using this pattern.
In order to remove these duplications, you can use inheritance and i see nothing wrong with that (keep in mind the fragile base class problem).
Alternatively, you can try to use "composition over inheritance" to factor out common logic, e.g. by introducing a separate class referenced by both your AppleService
and your PearService
.
If you have more sophisticated logic, you should try to create dedicated domain objects and introduce a "Data Mapping Layer". Although this layer can become quite complicated, it opens the door for advanced concepts like polymorphism and keeps you databaseĀ“s table structure clearly separated from your domain objects. This is widely known as domain-driven design, and it serves best only if you have complex domain logic to work on.
TLDR: The showed pattern is a transaction-script-like approach and, besides being simple and straightforward, it is vulnerable to code duplication, since every "script" is focused on a single transaction. To remove duplications, refactor them using either inheritance (as suggested by the OP), or composition. If you have more sophisticated domain logic, try to avoid working directly on your entities and switch to domain objects with an additional data mapping layer.