Restore ISingleResultSpecificationOfT - #196
Conversation
I'm having second thoughts on the SingleResultSpecification classes... On one hand it does conform to the existing Specification base classes and it avoids the nessecity of defining the generic type twice. On the other hand, it creates the situation where developers can create ISingleResultSpecifications in multiple ways, which may lead to inconsistent code. interfaceIRepository<T>{// doesn't work for SingleCustomerSpecification_3TSingle(ISingleResultSpecification<T>spec);// works for all specifications alternativesTSingleWithoutGenericSingleResultSpecification<Spec>(Specspec)whereSpec:ISingleResultSpecification,ISpecification<T>;}// option 1 -- uses the new SingleResultSpecification base classclassSingleCustomerSpecification_1:SingleResultSpecification<Customer>{publicSingleCustomerSpecification_1(intid){Query.Where(s =>s.Id==id);}}// option 2 -- need to define the generic type twiceclassSingleCustomerSpecification_2:Specification<Customer>,ISingleResultSpecification<Customer>{publicSingleCustomerSpecification_2(intid){Query.Where(s =>s.Id==id);}}// option 3 -- cleanest declaration imoclassSingleCustomerSpecification_3:Specification<Customer>,ISingleResultSpecification{publicSingleCustomerSpecification_3(intid){Query.Where(s =>s.Id==id);}}Reviewing the code above, it seems to me that it might be better to remove the What do you guys think? |
devbased
commented
Dec 30, 2021
I don't understand what is the point of publicinterfaceIReadRepositoryBase<T>whereT:class{// other methodsTask<T?>GetBySpecAsync(ISpecification<T>specification,CancellationTokencancellationToken=default);}publicabstractclassRepositoryBase<T>:IRepositoryBase<T>whereT:class{// other methodspublicvirtualasyncTask<T?>GetBySpecAsync(ISpecification<T>specification,CancellationTokencancellationToken=default){returnawaitApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);}} |
The
For the first point, consider the example below. Using the // The first word in this specification is plural, to indicate multiple customers are returnedpublicclassCustomersWithAddressesAndPurchaseHistory:Specification<Customer>{publicCustomersWithAddressesAndPurchaseHistory(boolincludeAddress,boolincludePurchaseHistory){// Query logic}}// The first word in the specification name is singular, but this is hard to detect.// The ISingleResultSpecification marker interface is more obvious, which helps to understand the intent.publicclassCustomerWithAddressesAndPurchaseHistory:Specification<Customer>,ISingleResultSpecification{publicCustomerWithAddressesAndPurchaseHistory(intid,boolincludeAddress,boolincludePurchaseHistory){// Query logic}}For the second point, consider the example below. When used in combination with carefully designed repository interfaces, one can generate compile time errors for when a developer accidentally uses the wrong specification. Imagine you have two specifications with similar names and untrivial constructor parameters, where one returns a list of entities and the other a single entity. We can then use the compiler to generate errors if one accidentally picked the wrong specification. interfaceIRepository<T>{TList(ISpecification<T>spec);TSingle<Spec>(Specspec)whereSpec:ISingleResultSpecification,ISpecification<T>;}Of course, finding good naming conventions and/or well chosen constructors also help in avoiding these kind of mistakes. And even though some might argue that marker interfaces shouldn't be used because they don't actually do anything, I still believe the examples shown here here illustrate that using marker this interfaces is in fact beneficial. |
The generic |
devbased
commented
Dec 30, 2021
@vittorelli Thank you for explanation. But is it really a specification responsibility to define resulting object? Would you create marker interfaces for TwoResults, ThreeResults, NResults, IAsyncEnumerableResult or ListResult, or other complex type specifications? If so, should ISomeResultSpecification create that result and not just mark that it 'wants' it? IMHO Specification builds query and not materializes it (or tells you how to materialize). Sorry if this is not a right place for such discussion 😅 |
Hi, The As for the additional base class EDIT: Just to clarify, I don't have a strong opinion here. If the community wants it OOTB, sure we can add it. |
vittorelli
commented
Dec 31, 2021
Ok, I agree. Better to keep the package clean and leave it up to end users if they want to use I've updated the PR so it only restores the generic interface so as to not have breaking changes in the next NuGet release. |
fiseni
commented
Dec 31, 2021
Thank you @vittorelli. We appreciate your help. |
The ISingleResultSpecificationOfT seems to be have been removed in 73693ac while being included in the current Nuget package. I assume this was accidental?
Furthermore, I've added two base classes SingleResultSpecification based on the existing Specification classes.