android10 / Android-CleanArchitecture

This is a sample app that is part of a series of blog posts I have written about how to architect an android application using Uncle Bob's clean architecture approach.
Apache License 2.0
15.51k stars 3.32k forks source link

How about Realm? #122

Open FranRiadigos opened 8 years ago

FranRiadigos commented 8 years ago

I was just wondering to know your thoughts about the best way to implement Realm Entities.

Realm needs the Android Framework, so I cannot have Realm objects in the Domain Layer, so I'm thinking to create other Realm Entities in the Data Layer and then map Domain objects to Realm objects. Does it make sense?

Also, the Repository Interface must return Observables Realm Entities, because I could retrieve data from the Realm database if the cache is not expired. Can I move the Repository Interfaces to the Data Layer?

Thanks.

android10 commented 8 years ago

I don't like the idea to marry to Realm because of the points you are mentioning, then would be very hard to change and by the way, keep in mind that databases are implementation details.

Trikke commented 8 years ago

Hi, @kuassivi ,

what you are suggesting is pretty much the correct way to implement Realm (or any storage library). You'll just need a few extra steps. This is to ensure boundaries are not broken, and Realm is decoupled from your Repository. The last one should be done, because if you ever get tried of Realm (or they want to make you pay 200$ to use their library, or it starts to suck), it must be easy to rip it out and use another storage library of your choice. But there will be some stuff that realm does, like listadapters and more view stuff, that you wouldn't be able to use.

You are correct that Realm should be part of the Data layer. As this example project shows you should have DataSources for each type of data (cloud, sql, realm, file,...). So create a new DataSource that holds a Realm instance, and have a mapper that maps RealmUser to DataUser.

Getting a user from a repository:

Saving a user in a repository

Now image if you want to replace Realm with CoolStorageLib. Because of the extra mapping to DataUser, you only have delete your UserRealmDataSource. Create a new DataSource that uses the CoolStorageLib. Create a new mapper that maps CoolStorageLibUser to DataUser, and you are done.

You cannot move the Repository Interfaces to the Data Layer because of the boundaries between the layers. Domain has contains the interfaces, and Data contains the implementation.

Just remember, like @android10 said; the database is an implementation detail. Keep that in mind when selecting. Realm has some specific use cases why it is handy, and some will not apply in this kind of architecture. (this explains the reluctance of @android10)

FranRiadigos commented 8 years ago

Thanks.

My approach is similar, but mapping a RealmObject into a Domain Object will make it lose the Live object functionality of Realm.

So, Should I lose functionalities in favor of not breaking boundaries?

yshrsmz commented 8 years ago

Hi

I made an app using Realm with CleanArchitecture. In that app, I decided to break boundaries to take advantage of Realm's "Live object" functionality. So Repositories return RealmObject, UseCase return RealmObject and Presenter use RealmObject to display UI.

From that experience, I strongly recommend you not to use Realm with CleanArchitecture, or use it only inside Data layer.

As you may know, RealmObject cannot cross thread. so your Presenter must know UseCase's executing thread, and UseCase must know Repository's executing thread. This limitation made me sick.

Trikke commented 8 years ago

@kuassivi yes, always respect boundaries. Thats why i put that part in bold in my explanation. Realm has some handy functionality that works in a standard Android workflow, but will not work in this architecture. That is why @android10 is reluctant to implement it. And that handy functionality is probably what attracted you into using Realm, but it doesn't play well with this architecture.

I'd like to point you to a recent article from Uncle Bob. In it he talks about being a Software Architect and the pitfalls in architecture many developers do face. In the example about Realm, it boils down that you shouldn't implement Realm because it has X cool features, or it looks shiny. It is just an implementation detail. It shouldn't matter if you select Realm, or a text file, or sqLite, or noSQL. If Realm works for you because it's good and what is required for your project's scope, then it's fine to use. But if you have to break the architecture to incorporate feature from a framework/library, then that smells. You are then beginning to depend on a framework. And as the Parse shutdown has shown Android developers around the world, you're in a heap of trouble if you are depending on a framework too much and suddenly you have to remove it...

In a video on Vimeo Uncle Bob talks about an application they made. They started out with their database as a simple text file, which was good enough at the start. Over the year the continuously asked "should we change the DB to something else?". The answer was always, "no it's fine for the moment like it is". And after a year or so, the app was finished and they noticed they never had to implement another framework for their DB as the flat text file was sufficient.

Trikke commented 8 years ago

@yshrsmz, the fact that you broke boundaries is the real issue with your situation, not the limitations of Realm. And now you've hit a problem that exists in an external framework that you are using, on which your own code depends too much.

yshrsmz commented 8 years ago

@Trikke True. now I know why the boundaries exist :sweat_smile:

Trikke commented 8 years ago

@yshrsmz i'm glad you are learning, man :smile: Please have a look at the video and article above, if you haven't already. They provide further insight on matters like this. (ie. depending too much on a framework and such)

FranRiadigos commented 8 years ago

@Trikke ok, the Parse shutdown is a good point. It make sense :+1: . @yshrsmz yep, I know about that thread confined problem.

Thanks.

mohamad-amin commented 7 years ago

@Trikke or everybody else who can help I've got another question. Consider we want to be able to select one of datasources (cloud/realm/...) from which we want to get data. How do you design the approach?

I can think of two kind of use cases (GetSomeDataFromCloudUseCase/GetSomeDataFromRealmUseCase) and implement one repository that has access to multiple datasources. Then if we want the result from both databases we can merge the result of two use cases.

Is it correct? Is there any better solution for this situation?

android10 commented 7 years ago

@mohamad-amin

I can think of two kind of use cases (GetSomeDataFromCloudUseCase/GetSomeDataFromRealmUseCase) and implement one repository that has access to multiple datasources.

You are confusing what a use case is because it should not tell anything about implementation details. Maybe you can pass some kind of parameters all the way down to force cache invalidation or any specific data source.