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

Where to combine methods from different repositories? #276

Open mr746866 opened 6 years ago

mr746866 commented 6 years ago

My question is better asked through an example.

Suppose I have 2 repositories, UserRepository and ChatRepository, like below.

public interface UserRepository
{
    /**
     * Fetches Accounts of all the users who are friends with the authenticated user.
     * @return an Observable that emits all the Accounts of friends in a List.
     */
    Observable<List<Account>> allFriendAccounts();
}
public interface ChatRepository
{
    /**
     * Fetches the number of messages sent by a peer that are unseen by the authenticated user, by the peer's userId.
     * @param peerUserId the userId of the peer.
     * @return an Observable that emits the number of unseen messages sent by the peer as a Long.
     */
    Observable<Long> unseenMessageCount(final String peerUserId);
}

The Account class is a model inside the domain layer, contains account information associated with a user and exposes them through methods like getUserId(), getUserName(), getPhotoUrl() which are self explanatory.

I also have separate UseCases for those repository methods named AllFriendAccountsUseCase and UnseenMessageCountUseCase.

Now suppose, I need to show a list of friends in my presentation layer, each item of which shows the name and photo of a friend as well as the number of unseen messages from him/her, similar to the list shown below.

chat_list

Obviously I'll need to do at least 2 things.

  1. Create a new model class which contains name and photo of a user as well as unseen message count. Possibly create separate model classes for domain and presentation layer.

  2. Use both UserRepository and ChatRepository methods to construct a list, containing objects of the class described in 1. (In short, get the Accounts of the friends using allFriendAccounts(), and then call unseenMessageCount(final String peerUserId) for each Account, using Account.getUserId() as parameter.)

I can think of 2 approaches, each described below.

Approach 1:

  1. Create a model class in domain layer, for example named ChatItem, containing user name, photo url and unseen message count.

  2. Create a new UseCase, for example named GetChatItems, which uses both UserRepository and ChatRepository methods internally to build an Observable<List<ChatItem>>.

  3. Create a model class in presentation layer, for example named ChatItemModel, corresponding to the ChatItem in domain layer. Also create the mapper to transform ChatItem to ChatItemModel and adapter class as needed.

Problem: ChatItem seems like a presentation detail and seems out of place inside domain model. Should the domain layer know about how its core models are combined and used in presentation layer?

Approach 2:

  1. Do nothing on domain layer.

  2. Create a model class in presentation layer, for example named ChatItemModel, containing user name, photo url and unseen message count.

  3. Use the existing UseCases, AllFriendAccountsUseCase and UnseenMessageCountUseCase in the presentation layer to build an Observable<List<ChatItemModel>>. Also create the adapter class which uses the emitted list.

Problem: Combining UseCases in presentation layer seems ugly.

Looking for suggestions. Is there an approach 3 that I missed completely?

crjacinro commented 6 years ago

This is a very valid and common scenario. But I prefer approach 2. Presentation layer should handle all presentation logic and I consider your scenario a presentation logic because this is used to display values onto your screen. For me, there's noting wrong in combining multiple use cases in one UI (or presentation). It is very impossible to only have one use case in one UI. So for me, combining use cases in presentation layer should not be a problem.

mishkaowner commented 5 years ago

@mr746866 Get rid of dirty observables away... Use callbacks. Let your presentation layer be object-oriented not procedural. All you need is just a small change in your precious ChatItemModel which is used in presentation-layer. Then, update each of those which has "Unseen-messages" when callback arrives! What if you need fcm to notify your ChatItemModel to display "New arrived messages"? Are you going to merge another Observable into it? or perhaps use a subject? Stop right there. Rx is for streaming not for screaming.

St4B commented 5 years ago

Imho, the first approach is better. The second approach seems to add business logic in presentation layer, which is something that we do not want. If business logic does not live in domain layer then it is not transferable. Also by coupling presentation with business logic, it would be harder to change presentation layer.