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

UseCase responsibilities #94

Open kryanod opened 8 years ago

kryanod commented 8 years ago

What can subclasses of UseCase do? What can't they do? In this project use cases are atomic tasks used to simply obtain data, but what about decision making, where does that belong? Example 1: I have a collection of items and lazy loading. Is there one UseCase handling all pagination logic and calling necessary repository methods, or are there multiple UseCases and something (presenter?) orchestrating them? Example 2: after executing a UseCase, based on its response we make a decision on what UseCase to call next. Again, where is this decision made?

Trikke commented 8 years ago

In example 1 : i'd say that pagination is view logic, and that there is one UseCase that is able to call the repository and has arguments to get data from the pagination logic in the View Layer to return the correct results.

In example 2 : If you have logic where you execute one UseCase and depending on the results execute another UseCase, then you have'll have to create one UseCase to handle that decision making. And probably abstract some logic as well, because it will be used for multiple UseCases.

Example ( a bit of a dumb one, but just to describe the scenario) : UseCase 1 > Login User via Api UseCase 2 > Get the Search results for that User

The problem is that is UseCase 1 is successful, we need to execute UseCase 2, if not, there's no need for that. The solution is not to call those 2 UseCases but create UseCase 3 (login user and if ok, get the search results). It's not because you already have atomic UseCases for certain actions, that you cannot create a new one to handle a specific business task. Now depending on the complexity, you might abstract things like "Login User via Api" to a separate "Api Manager" which both UseCase 1 and 3 may call, so there isn't any specific duplicate logic in both Use Cases.

fbcbl commented 8 years ago

Hey @Trikke and @AlexTimonin !

Please I could lend some advice on this pagination/use case scenario.

Are you saying that the view should know about which is the reference to the next page? Or should it be the responsibility of the presenter? (That is orchestrating everything?)

So is the flow something like this?

screen shot 2016-01-29 at 15 24 25

I am a bit unsure if this is the way to do this.

Thank you.

Trikke commented 8 years ago

Hey @fabiocarballo ,

It should be the responsibility of the Presenter. The only thing the view does is handle the event for when the next page should be loaded. The Presenter is then notified and handles the logic with pagination details.

So :

Did this help?

trungie commented 8 years ago

@Trikke Very clear, however can you outline exactly the difference and responsibilities of the user case vs the repository?

Trikke commented 8 years ago

@trungie the responsibility of the repository is store and retrieve data that is used in the Domain layer. How it does that doesn't matter to the Domain layer. So it is an abstraction between the actual implementation of the store (sql, plain text,...) and the business part of your app.

A Use Case is part of this business bit of your app. You can have a lot of those, and sometimes even for the smallest, most idiotic things. Your business rules (read how your app works) reside in the Domain layer and usually in these Use Cases. Lets make a quick, dumbed down, example:

We create an app to get the top 5 GitHub projects of today (this sentence is our actual business logic). So we have a screen with a button "get projects". That's the only thing our app will do, for now. The user presses that button, the View notifies the Presenter that the button is pressed and it needs to do something. The Presenter will then have or create a Use Case. This Use Case's only purpose is to enforce our business rule from above, getting the top 5 GitHub projects of today. In the Data layer, we have a Repository that is able to fetch and store the GitHub projects. The Repository will also have parameters like "amountOfProjectsToFetch" and "projectTimeFilter". The first one is to specify the amount of projects to retrieve from the Github API, and the second one exist to indicate a timeframe to filter these projects. (reminder, i'm making this up as we go along, the Github API in reality will work differently) Now back to our Use Case. It will create an instance of the Repository and will call the method to retrieve the projects with the parameters "5" and "TODAY". So the Presenter calls the Use Case and it will run, returning the top 5 GitHub projects for today. The Presenter will then re-render the View and show these.

The idea to take away here is that it isn't the Presenter or the Repository that gets to specify how many projects we need to get from Github. There isn't such a method in the Repository, it only gets lists of projects from the GitHub API with parameters. It doesn't case that our app wants the top 5 GitHub projects. The other way around, the Use Case doesn't care HOW we get these top 5 GitHub projects. It only cares that there is a way to do it, and that it can dictate WHAT to get.

Now imagine you want to expand your app and show a button to get the top 10 GitHub projects of this year. You only need to add a new Use Case that does this, and have your Presenter execute that one on the right button press.

Now imagine this last feature is a paying feature. This is business logic. So we modify the last Use Case to also check if the current User has payed for this feature, and block access to it if needed. It would either return the top 10 GitHub projects or notify the Presenter that the user cannot, and should see a payment dialog.

(keep in mind, this is all very simple and might seem like a lot of code for a small app. But soon your app grows and grows, and you keep adding new features and rules. Then this architecture will start making more and more sense as testability and usability remain simple)

rshah commented 8 years ago

@Trikke Thanks for making great explanation.

Now imagine this last feature is a paying feature. This is business logic. So we modify the last Use Case to also check if the current User has payed for this feature, and block access to it if needed. It would either return the top 10 GitHub projects or notify the Presenter that the user cannot, and should see a payment dialog.

I still have some questions about sentence above.

  1. Do we really check inside the UseCase or do check it outside before calling the real UseCase?
  2. If we check inside the real UseCase, do we call another UseCase to check for allowed features? Or just calling a repository for example FeatureRepository.getUserAllowedFeature(currentUser) and do the checking based on the result(in this case we pass more that one repository to main UseCase).
Trikke commented 8 years ago

@rshah

  1. Yes, we check in the UseCase. The checking is "business logic", so it is part of a UseCase. But it might be another component doing the actual checking, remember that.
  2. No, UseCases are control flow components. A Feature not anything data related, so it doesn't belong in a Repository. A Repository is a Pattern for data-retrieval. You don't store "Features" on disk or anywhere, you'd use these just to tell you what a User can do with your app. Generally what you'd have is some kind of FeatureManager in the Domain Layer. This Manager would contain the rules for Features in your app. So a UseCase would have an instance of this and check if whatever the UseCase wants to do is possible.

A quick example :

Requirement of App

The user can get a list of Top 10 Github Projects, but he has to have the paying status of "Premium Member" in your app. If the user is not, he must be shown a payment dialog.

So the control flow would be

  1. User opens the Activity "Top10Projects"
  2. Presenter wants to load that lists and asks the UseCase "TopGithubProjects" for data
  3. The UseCase is our business logic, so it's only concern is the requirement above
  4. It will ask the FeatureManager if we are able to run the Feature "ViewTop10Projects"
  5. The FeatureManager checks if the User is a "Premium Member".
  6. Either it returns a success, and the UseCase can proceed and get that list from the Repository. Or it will return with a failure. And then the UseCase can react to this and let the Presenter know it is not possible and must show a dialog.

Please read up some more on programming patterns. The ones shown here in this sample are not the only ones, and will not cover all your requirements. So don't treat this as "this is the final example on how to do it, and i only will need this". There's a lot more out there, explore!

ahmedmsvb commented 7 years ago

Hi @Trikke,

I have two UseCases: ResetUserPassword:

Both UseCases share the same first part "GetUserIdFromPreferences -> QueryUserFromDB".

I'm trying to apply the same concept in my app (have a common place for "GetUserIdFromPreferences -> QueryUserFromDB") to avoid repeating myself.

Is it correct to have something like a common method: Observable<User> getLoggedInUser(PreferenceRepository preferenceRepository, final UserRepository userRepository) {} placed in a new class public class UserManager {}

Or, there's a better method to implement?