https://kotlinlang.org logo
#android-architecture
Title
# android-architecture
l

Lilly

03/19/2021, 12:38 PM
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

Alex Prince

03/19/2021, 1:52 PM
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

Javier

03/19/2021, 3:00 PM
For me caching or not is a implementation detail
l

Lilly

03/19/2021, 3:36 PM
@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

Javier

03/19/2021, 3:37 PM
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

Lilly

03/19/2021, 3:43 PM
So you say that there should never be the option to cache data from domain layer?
a

Alex Prince

03/19/2021, 3:44 PM
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

Lilly

03/19/2021, 3:45 PM
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

Javier

03/19/2021, 3:45 PM
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

Alex Prince

03/19/2021, 3:46 PM
that would be my usual stance as well
j

Javier

03/19/2021, 3:46 PM
I don't know what you mean about domain data
a

Alex Prince

03/19/2021, 3:46 PM
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

Javier

03/19/2021, 3:47 PM
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

Lilly

03/19/2021, 3:48 PM
Thats not the problem, I got this. Let me explain...
a

Alex Prince

03/19/2021, 3:48 PM
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

Lilly

03/19/2021, 3:49 PM
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

Javier

03/19/2021, 3:51 PM
Personally, in data, I would fetch from remote, and there I just save to cache
a

Alex Prince

03/19/2021, 3:51 PM
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

Lilly

03/19/2021, 3:52 PM
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

Javier

03/19/2021, 3:52 PM
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

Alex Prince

03/19/2021, 3:53 PM
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

Lilly

03/19/2021, 3:53 PM
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

Javier

03/19/2021, 3:54 PM
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

Lilly

03/19/2021, 3:55 PM
@Javier this is a use case object right?
j

Javier

03/19/2021, 3:56 PM
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

Alex Prince

03/19/2021, 3:57 PM
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

Lilly

03/19/2021, 4:03 PM
@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

Javier

03/19/2021, 4:08 PM
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

Lilly

03/19/2021, 4:19 PM
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

Javier

03/19/2021, 4:31 PM
GetUserImpl
should be in data
For me that is not domain logic, they are implementation details
l

Lilly

03/19/2021, 4:34 PM
Hmm but where do you exactly place you business logic, if you have only interfaces and models in domain layer?
j

Javier

03/19/2021, 4:37 PM
there can be logic but it is not related to data sources
l

Lilly

03/19/2021, 4:39 PM
lets say you have to transform your fetched data, where would you place this logic?
j

Javier

03/19/2021, 4:42 PM
mappers from data models to domain models are in data
and viceversa too, domain to data are in data
l

Lilly

03/19/2021, 4:46 PM
Then your whole business logic relies in data layer. You don't follow Clean Architecture?
j

Javier

03/19/2021, 5:15 PM
In what part of clean architecture says data sources are part of domain layer
l

Lilly

03/19/2021, 5:16 PM
No part, sources belong to data layer thats right but you put your business logic also in data layer
j

Javier

03/19/2021, 5:22 PM
For me all I mentioned here are implementation details, not business logic
l

Lilly

03/19/2021, 5:23 PM
But you said youhave your business logic in mappers which also rely in data so busincess logic relies in data, right?
j

Javier

03/19/2021, 5:23 PM
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

Lilly

03/19/2021, 5:24 PM
Mappers in domain and datasources in data layer, thats my point of view
j

Javier

03/19/2021, 5:24 PM
message has been deleted
then domain mappers know about data models
so you are breaking clean
l

Lilly

03/19/2021, 5:24 PM
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

Alex Prince

03/19/2021, 5:25 PM
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

Javier

03/19/2021, 5:28 PM
Why I am going to create data source interfaces in domain
They are data for me
a

Alex Prince

03/19/2021, 5:29 PM
you're not, all your domain knows about is a getData(id):DomainData
l

Lilly

03/19/2021, 5:29 PM
true @Alex Prince
j

Javier

03/19/2021, 5:30 PM
And I am adding unnecessary interfaces adding layers than I will have to test but give 0 benefits
No sense for me
a

Alex Prince

03/19/2021, 5:32 PM
it all depends on what the use case is, if it's a straight forward use case yes
j

Javier

03/19/2021, 5:46 PM
I can have a datasource interface if it will have multiple implementations
a

Alex Prince

03/19/2021, 5:47 PM
yep, you could do it that way too
15 Views