I have a question regarding Clean Architecture and...
# android-architecture
l
I have a question regarding Clean Architecture and caching. According to the recommendations, the "repository" is responsible for caching so I'm able to cache the fetched data. In my use case (domain layer) I would do some transformations with the data. But what if I would like to also cache the data after transformation in domain layer, how would I do this? The use case shouldn't know anything about the outer layers...
a
all the domain would have to know is "tell the repo to go cache this thing". The route I'd probably go, is just pass in a "cache this thing" higher order function, that way the function declaration itself is essentially the "interface" so, the domain owns the interface, but not the implementation. I may call it something better than "cache" in domain language but, that all depends how that looks.
j
For me caching or not is a implementation detail
l
@Alex Prince so you think it's legit to "request" a cache operation from domain to "repository"? Something like:
Copy code
in repo:
    override fun executeCacheRequest(cacheOperation: () -> Data) {
        cache[CACHE_DATA_KEY] = cacheOperation()
    }
in use case:

val model = repo.fetchData()
repo.executeCacheRequest {
   // do some transformation
}
right? @Javier What does it mean in practices? Can you elaborate pls?
j
If I have an use case which is an interface, it knows nothing about if data sources exists, whatever they are, that includes the cache datasource
Copy code
interface GetUser {
    operator fun invoke(): Flow<Either<Error, User>>
}

// or if there is not a stream of data

interface GetUser {
    suspend operator fun invoke(): Either<Error, User>
}
if the GetUser implementation class uses one or three data sources, caching, or not caching, is irrelevant to the domain logic after all IMO
l
So you say that there should never be the option to cache data from domain layer?
a
usually caching is the repos problem, but, if the domain does actually care for what it needs to do, then it becomes a domain concept
usually I would hide wether it's cached or not behind the repo too, unless there's a compelling reason to need to know in the domain but, really the less external stuff your domain needs to know about the better
l
Currently I see 3 possible options: 1. Introduce a new domain cache layer 2. start a cache request to repo like Alex said 3. domain data cant be cached
j
Yeah, I think caching is a implementation detail, exactly the same that network request. Both are just data sources, local or remote, the domain logic should know nothing about them
a
that would be my usual stance as well
j
I don't know what you mean about domain data
a
IF your domain actually needs to know for domain reasons then expose it but, there aren't a lot of cases where it should need to know most likely
j
Think in this, you have an app which only have a remote data source, and you can fetch an user and delete it too. Your domain logic has to know something about this data source? No, it is an implementation detail. This is the same but for caching
l
Thats not the problem, I got this. Let me explain...
a
yea, I agree, in 99% of cases the domain shouldn't care, but there is that 1% where it might, if it's somehow important to your business logic (but then the question becomes why is it and can it not be)
l
With domain data I mean, that in my use case I make a request to the repo to fetch data which in turn returns with the data. Then further in my use case I transform this data. After transformation (which might be a long operation) I would like to cache this transformed data. I'm looking for a solution to do this
j
Personally, in data, I would fetch from remote, and there I just save to cache
a
so it could be just abstracted as a save at the end of the flow then probably? to a different repo (or a different function anyway) that handles the new datatype?
l
So every time I fetch data from repo. I'm able to pull this from cache but in use case I would always have to go through the transformation operation and that's what bothers me
j
With this is easy, they are interfaces in domain, the implementation, in the data layer, can use any number of datasources, any number of repos (if they are needed), etc.
Copy code
interface GetUser {
    operator fun invoke(): Flow<Either<Error, User>>
}

// or if there is not a stream of data

interface GetUser {
    suspend operator fun invoke(): Either<Error, User>
}
a
I'd almost add a 2nd repo layer, if that makes sense, so, your use case talks to it's repo that gives it the data it needs, that repo does whatever it needs to to get that data (in this case probably grab it from the other, map, cache, return)
l
I want to overcome the problem to always have to tranform the data in domain layer and instead pull it from cache.
@Alex Prince that sounds legit, interesting view
j
Copy code
interface GetUser {
    suspend operator fun invoke(): Either<Error, User>
}

class GetUserImpl(val remote: Remote, val cache: Cache) {
    suspend operator fun invoke(): Either<Error, User> {
        if (cache.hasData) return Either.Right(cache.data.mapToDomain())
        else return remote.fetch().mapToDomain().also { cache.insert(it.mapToWhatever()) }
    }
}
l
@Javier this is a use case object right?
j
it is the implementation of the interface
GetUser
and that implementation can use data sources, or repos, or whatever you need to get it working
a
I did something similar a while back where I had to augment an existing object, in my case it was a user and some permissions data that only a specific domain needed
l
@Javier So if I understand you right you define an interface
Cache
in domain layer, implement it in data layer and in use case you pass make
Cache
available and do your caching. That was also my first thoughts but wasn't sure if this is a common solution and allowed. But then you move your responsibility of cahcing to domain layer 🙂
j
No, there is no Cache interface in domain
For me, datasources are implementation detail, they are in the data layer
Domain layer, in my case, only has use cases interfaces and domain models, there are not even repos
But in the case you want to have repos, and you want them in domain
you can have
Copy code
interface UserRepo {
    fun getUser()...
    fun deleteUser()...
}

class GetUser(private val userRepo: UserRepo) {
   ...
}

class DeleteUser(private val userRepo: UserRepo) {
   ...
}
In both cases, there are no data source logic in domain
l
In both cases, there are no data source logic in domain
Agree. Where does
GetUserImpl
class rely?
@Javier If I put all things together you have interfaces of you ruse cases in domain layer and implement them in data layer right? If yes, your complete business logic (which belongs to domain layer) relies in data layer. This is already business logic:
Copy code
if (cache.hasData) return Either.Right(cache.data.mapToDomain())
	else return remote.fetch().mapToDomain().also {
		cache.insert(it.mapToWhatever())
	}
j
GetUserImpl
should be in data
For me that is not domain logic, they are implementation details
l
Hmm but where do you exactly place you business logic, if you have only interfaces and models in domain layer?
j
there can be logic but it is not related to data sources
l
lets say you have to transform your fetched data, where would you place this logic?
j
mappers from data models to domain models are in data
and viceversa too, domain to data are in data
l
Then your whole business logic relies in data layer. You don't follow Clean Architecture?
j
In what part of clean architecture says data sources are part of domain layer
l
No part, sources belong to data layer thats right but you put your business logic also in data layer
j
For me all I mentioned here are implementation details, not business logic
l
But you said youhave your business logic in mappers which also rely in data so busincess logic relies in data, right?
j
If you use data sources in domain, and if you have mappers in domain, you are breaking clean architecture
How mappers that know domain and data, can be in domain, then they are domain, and they know data, and that is wrong
l
Mappers in domain and datasources in data layer, thats my point of view
j
message has been deleted
then domain mappers know about data models
so you are breaking clean
l
No thats wehy you declare an datasource interface in domain and implement it in data layer
but use it in domain layer to make your requests
a
usually the repo would be returning domain types, so, your mappers live above
at least in that case where you're calling it from the domain
j
Why I am going to create data source interfaces in domain
They are data for me
a
you're not, all your domain knows about is a getData(id):DomainData
l
true @Alex Prince
j
And I am adding unnecessary interfaces adding layers than I will have to test but give 0 benefits
No sense for me
a
it all depends on what the use case is, if it's a straight forward use case yes
j
I can have a datasource interface if it will have multiple implementations
a
yep, you could do it that way too