The sample code for this post can be found here.
The Songster app we’ve been developing over course of my first and second post has been growing steadily. With growth and with additional requirements comes an increase in complexity. Our model-view-presenter pattern along with our view model have helped us keep this complexity in check. With our next requirement, however, we will need to create a new level of abstraction. Our next feature will entail the ability to purchase a song that you’ve searched for. Imagine that Songster is hooked up to a music service that allows you to purchase songs with a user account that you’ve registered with them. With a simple swipe of a search result, we can start the payment process. For the sake of this post, the payment process follows a fairly complex set of business rules. The following diagram illustrates the business logic.
As shown by the diagram, the business logic of purchasing a song involves several steps. We could put the logic for dealing with those steps into our presenter. Our presenter, however, mainly deals with matters related to displaying data and taking in user input. The purchase workflow includes a lot of steps that don’t trigger any UI changes. Adding the logic for this workflow to our presenter would give our presenter an additional responsibility which is something I would like to avoid. I want the presenter only to deal with UI-related changes and not with general business rules. So where could we deal with complex business logic? The answer is, in a business layer.
The use case is our business layer. Its interface defines all of the methods that the presenter needs to call in order to trigger and handle the purchase workflow. Our UseCase interface could look something like this:
void purchaseSong(SongDto song); void login(String username, String password, SongDto songDto);
It provides two methods: the first method triggers the purchase workflow and the second performs the login. Our UseCase has a listener:
interface UseCaseListener { void showLoginView(SongDto songDto); void showPurchaseSuccsessMessage(SongDto song); }
The listener is used to display data on the UI during our purchase workflow. The only things we want to display during the purchase is the login dialog (if the user is not already logged in), and a message that will be displayed at the end of a successful purchase.
The implementation of the UseCase
The first step in purchasing a song is to trigger the workflow with the UseCase’s purchaseSong() method. That method could look like this:
@Override public void purchaseSong(SongDto song) { mSearchRepository.checkout(song, this); }
The purchaseSong() method kicks off our whole workflow which starts by first checking out the selected song. The checkout() method determines whether or not the selected songs requires the user to be logged in or not. Assume the Songster music service has some songs that require a login and others that can be purchased without a login. Once the checkout succeeds, the SearchRepository will call the onCheckoutSuccess() method in our UseCase:
@Override public void onCheckoutSuccess(CheckoutDto checkoutDto, SongDto songDto) { if(checkoutDto.isLoginRequired()){ if(mUserDataRepository.isLoggedIn()){ UserDto loggedInUser = mUserDataRepository.getLoggedInUser(); mSearchRepository.authorizePurchase(loggedInUser, songDto, this); } else { mListener.showLoginView(songDto); } } else { mSearchRepository.purchase(songDto, this); } }
The onCheckoutSuccess() first checks if a login is required for the given song. If so, it checks if the user is already logged in. Depending on the user’s current login status, the purchase will either be authorised or a login view is presented to the user. The onCheckoutSuccess() method is a good example of our UseCase encapsulating more complex business logic that has little to do with our UI. Of course we could perform these calls in our presenter but doing so would bloat our presenter and mix UI-logic with business logic.
Conclusion
If an app contains more complex business logic, it makes sense to move that logic into a business layer. The presenter then delegates the calls that contain the business logic to a UseCase. Depending on the business rules, the UseCase might access a Repository to store and retrieve data and occasionally interact with the presentation layer.
The last few posts saw us adding additional components to our app to meet the requirements. In the scope of this series of blog posts, the Songster app is now feature complete. However, I think that we can clean up our code a bit. That is what I will do in the next post.
Pingback: Noser Blog Data binding and Model-View-ViewModel in Android - Noser Blog