It seems to me that there is a conflict between clean architecture and the recommendation not to use instanceof. Consider the following code:
class ParentEntity { }
class AEntity extends ParentEntity { List<Integer> entityIds; }
class SaveUseCase { IEntityRepository entityRepository; void save(List<ParentEntity> list) { entityRepository.save(list); } }
class EntityRepository implements IEntityRepository { void save(List<ParentEntity> list) { list.forEach(e -> { if (e instanceOf AEntity) validate((AEntity) e) // Do save e in the database ... } } void validate(AEntity a) { List<ParentEntity> list = a.getEntityIds().stream().map(id -> get(id)) // Do some checking based on the value of list ... } ParentEntity get(int id) { // Do get ParentEntity with identifier id from the database ... } }
The code has a usecase which calls the save method in the repository. The save method first checks the object only if the object is of type AEntity
and then saves the object.
The problem is the use of instanceof in the save
method of EntityRepository
. If we want to prevent using instanceof, one solution is to make validate
a method of ParentEntity
and do the validation inside AEntity
by overriding it. However, according to the clean architecture we have separated the entities and repositories, so inside entities we do not have access to get
method of the repository, which is required for being able to do the validation.
The workaround to this is to put a reference to IEntityRepository (or at least something like GetUseCase) inside the entity so it can do the validation itself. But, this doesn't seem a very good idea to me, especially if we assume that validation is a logic of the repository and is there only to check, e.g., what other layers give to it as parameters are valid.
So, using clean architecture biases us to using instanceof and using it is not bad in scenarios like the one I mentioned. Am I right or am I misunderstanding something?
Update: I quote some sentences from here, that I think are related to my point of view:
Some forms of validation are more efficient at the database layer, especially when referential integrity checks are needed (e.g. to ensure that a state code is in the list of 50 valid states).
Some forms of validation must occur in the context of a database transaction due to concurrency concerns, e.g. reserving a unique user name has to be atomic so some other user doesn't grab it while you are processing.
I have seen some developers try to codify all the validation rules in the business layer, and then have the other layers call it to extract the business rules and reconstruct the validation at a different layer. In theory this would be great because you end up with a single source of truth. But I have never, ever seen this approach do anything other than needlessly complicate the solution, and it often ends very badly.
EntityRepository
? Is it intended to be repo for all kind of derivations ofParentEntity
, knowing the whole inheritance hierarchy, or is it a repo intended just for dealing withAEntity
objects? Or are you trying to implement it in a generic fashion forParentEntity
s, just in terms of virtual functions of this class?entityRepository.save(list)
, so, why have the SaveUseCase class at all? Also, it's unclear why your AEntity extends ParentEntity. What is the design purpose of ParentEntity? Furthermore entities in CA are not database entities. 2/2