I'm using Clean Architecture in my project. Now for instance I have an Interface Adapter like so:
protocol UserRepositoryInterface {
func getSingle(id: String) -> Single<User?>
func getAll() -> Single<[User]>
}
class UserRepositoryAdapter: UserRepositoryInterface {
private let api: ApiService
private let mapper: UserMapper
init(api: ApiService,
mapper: UserMapper) {
self.api = api
self.mapper = mapper
}
func getSingle(id: String) -> Single<User?> {
return getAll()
.map { $0.first(where: { $0.id == id }) }
}
func getAll() -> Single<[User]> {
return api
.getUserList()
.compactMap { [weak self] in self?.mapper.map(from: $0) }
}
}
And a simple Use Case like this:
protocol GetAllUsers {
func get() -> Single<[User]>
}
class GetAllUsersUseCase {
private let userRepo: UserRepositoryInterface
init(userRepo: UserRepositoryInterface) {
self.userRepo = userRepo
}
func get() -> Single<[User]> {
return userRepo.getAll()
}
}
The code is in Swift, but it can be in any other languages such as C#, C++, Java, Kotlin, etc. The question is still the same. Judging from the code, the GetAllUsersUseCase
is just acting as a middle man between the InterfaceAdapter
and ViewModel
. Should I even use it because it is doing nothing else and should be considered a smell? Shouldn't the ViewModel
use the UserRepositoryInterface
directly? However, I've read this on https://refactoring.guru/smells/middle-man:
When to Ignore
Don’t delete middle man that have been created for a reason:
A middle man may have been added to avoid interclass dependencies.
Some design patterns create middle man on purpose (such as Proxy or Decorator).
Is this just one of the instances where we can tolerate the code smell like mentioned above (and thus I should still use the Use Case)?
Thanks.
A use-case in CA in an implementation of the "input port" concept. Jumping from view model straight to domain repository interface would be as if you declare you want to pass user request to repository as is, therefore applying business validations inside repository.
Such validations should, of course, not take place in infrastructure layer. Instead, they should occur inside domain entities, which should be managed inside application layer (and not inside presentation layer); or inside those exact objects managing mentioned domain entities, namely application services or use-cases.
Another reason for sticking to use-case objects is the possibility you would need to enrich flow of getting all users. Although currently requirements can be simple, at some point in the future it might be necessary to, for example, filter out some users based on some criteria. Such scaling is best met when you have a use-case object where you can, for example, add a SelectedUsers
specification and pass it to repository method. Specifications are not to exist in view model, but in application layer.