Recently, I released an open-source sample project called MovieNight.

In the past couple of weeks, I got lots of questions regarding the application architecture, so I’ve decided to write this blog post. I’ll describe the different components at play and the relationships between them as well as talk about some of the architecture decisions I’ve made along the way.

Photo by Chris Lawton on Unsplash

I’ll do my best to highlight the more interesting parts of the code base, but there’s so much I can write about in a single post. If you find the topic interesting, I recommend that you’ll clone the project and explore the code base by yourself.

You can find the source code of MovieNight right here:

Before we begin #1

This blog post assumes knowledge on the following topics: Kotlin, RxJava, dependency injection, and testing. I did my best to add links for further reading throughout the post. If you come across a subject you’re not familiar with I encourage you to take a pause and read about it a bit. So we could stay on the same page.

Before we begin #2

Take everything you’re going to read with a grain of salt. Architecture, by its nature, is dynamic and ever-evolving. There is no such thing as “the perfect architecture”, there are always several solutions to every problem.

Keep in mind that every architecture decision is a trade-off.

Now, grab yourself a cup of coffee/tea/beer(?), make sure you’ve got the source code in front of you, and let us begin.

A Clean approach

MovieNight architecture is heavily influenced by Uncle Bob’s clean architecture. Understanding the principles behind the clean approach is key for our guided tour.

Although clean architecture can be a bit overwhelming at first look, it’s pretty simple to comprehend once you understand what’s it all about.

The core principles of the clean approach can be summarized as followed:

The application code is separated into layers.

These layers define the separation of concerns inside the code base.

These layers define the separation of concerns inside the code base. The layers follow a strict dependency rule.

Each layer can only interact with the layers below it.

Each layer can only interact with the layers below it. As we move toward the bottom layer — the code becomes generic.

The bottom layers dictate policies and rules, and the upper layers dictate implementation details such as the database, networking manager, and UI.

Consider the following abstract example:

Dependencies flow between the layers.

Layer C has access to layers B and A.

has access to and Layer B has access to everything inside layer A but is unaware of anything inside layer C . In fact, layer B doesn’t even know that layer C exists.

has access to everything inside but is unaware of anything inside . In fact, doesn’t even know that exists. Layer A is at the very bottom, she’s unaware of anything outside of her scope, and as we’ll see soon enough, her ignorance truly is a bliss.

That’s it.

Every other architectural detail exists only to serve these core principles.

Why use the clean approach?

Did you ever had the “pleasure” of replacing in-memory, synchronized data store with an asynchronous, fully-fledged database? 😰

Requirements will change. The design will change.

Basically, all the implementation details are heavily prone to change.

By dividing the code into layers we can “push” these details into the upper layers, and by following the dependency rule, we’re able to isolate them from the core functionality of our application.

This isolation allows us to write code that is more testable and independent of any external factors, so when a change “hits” we can react to it quickly and efficiently without breaking too many walls.

Now that we’re on the same page, we can take a look at MovieNight actual architecture layers.

MovieNight architecture layers

The application consists of three layers:

The domain layer, the data layer, and the presentation layer.

Looking at MovieNight’s high-level project structure, you’ll see that each layer is represented by a module in the project.

MovieNight high-level project structure.

Like everything in this article, this project structure is just one way of doing things. I like it because it helps me avoid accidentals “leaks” between the layers as I must specify in each module build.gradle file which other modules (i.e., layers) it depends on.

Here’s a high-level chart of the layers and what they hold:

MovieNight architecture layers.

If this chart looks a bit overwhelming right now — don’t sweat it! 😄

We’ll explore each layer implementation details, and soon enough, everything will (hopefully) make sense.

Let’s dive into each layer, starting from the very bottom — The domain layer.

The Domain layer

The domain layer is the baseline of the application. Its purpose is to describe what the application is and what it can do (soon we’ll see concrete examples to this statement).

Remember the third clean approach principle we discussed? As we move toward the bottom layer — the code becomes generic.

Every piece of code here is as generic as code can be.

Concrete implementations and details belong to the upper layers.

Being at the bottom, the domain layer is unaware of anything else in the application to the point that the code here has nothing to do with the Android framework, it’s just “pure” Kotlin. Why? Because it’s irrelevant to the purpose of the domain layer.

This layer holds mostly domain entities, interfaces and special classes called use cases.

Domain entities

These are the basic building blocks of our application and the ones that are least likely to change when something external changes. In the case of MovieNight, you’ll find here classes such as MovieEntity and VideoEntity that describe the basic data structures we work with. Here’s the entities package:

MovieNight domain entities.

This is part of what I meant when I wrote that the domain layer describes what the application is and what it can do: Looking at the entities package it’s pretty clear what kind of data MovieNight is dealing with — movies, movies reviews, etc.

In our application case there is nothing fancy here, these classes just act as data containers, so I choose to create them as Kotlin data classes.

Use cases

Also known as interactors, a use case encapsulates a single, very specific task that can be performed. These use cases will later be used by the upper layers. Take a look at the use cases package:

MovieNight use cases.

Again, we describe the application. Just by looking at the files names its becomes obvious what the application can do: We can browse popular movies, we can manage favorite movies list, and we can search for movies.

All the use cases extend the abstract UseCase class:

UseCase.kt

And here is a more concrete use case that fetches popular movies:

GetPopularMovies.kt

There are few things worth mentioning here:

The output of all use cases is an Observable .

Not much else to say here. Observables allow us to write code in a more functional and reactive way, which I like.

Not much else to say here. Observables allow us to write code in a more functional and reactive way, which I like. All use cases must receive a Transformer object in their constructor.

The Transformer class is just a simple ObservableTransformer. Using a Transformer allows us to dynamically control on which threads the use case “runs.” Which makes it especially useful when writing tests, for example: when we fetch the list of popular movies we want it to run a worker thread and not clog the main thread, but when testing we want the code to run synchronously. It worth noting that other then for testing purposes, I can’t think of any good reason to run the use cases on the main thread.

The Transformer class is just a simple ObservableTransformer. Using a Transformer allows us to dynamically control on which threads the use case “runs.” Which makes it especially useful when writing tests, for example: when we fetch the list of popular movies we want it to run a worker thread and not clog the main thread, but when testing we want the code to run synchronously. It worth noting that other then for testing purposes, I can’t think of any good reason to run the use cases on the main thread. We can pass optional data into the use case.

Sometimes, we need some data passed into the use case, Kotlin optional and default values are useful here as we don’t have to specify null values when the data is not needed.

As you can see, GetPopularMovies use case received a MoviesRepository object when created, and that leads us to the next type of residents of the domain layer — interfaces.

Interfaces

The domain layer interfaces dictate the contract the upper layers must follow. These abstractions ensure that the application core functionality will hold true, regardless of any implementation details changes. Let’s take a look at GetPopularMovies use case again: when invoked, the use case returns an Observable that gets data from MoviesRepository.

Here’s MovieRepository implementation:

MoviesRepository.kt

It’s just an interface. Why? Because the implementation details of the repository are not relevant to the functionality of GetPopularMovies use case.

Take a look at GetPopularMovies code at line 5: does it matter if moviesRepository.getMovies() returns data from remote API or a local database? No, as long as the actual implementation implements the MoviesRepoitory interface — GetPopularMovies will work just fine!

Before we continue with our tour, let’s take a moment to talk about testing.

Unit testing the use cases

The “ignorance” of the domain layer allows us to test our use cases easily.

Here’s GetPopularMovies unit test:

Unit testing GetPopularMovies.

Look at line 4: Since MoviesRepository is just an interface, I can easily mock its behavior using Mockito and return Observable with stub data when getMovies() is called.

Remember, use cases receives a Transformer when created? At line 5 I’m using TestTransformer to ensure synchronous execution of the use case so I can test more easily.

At Lines 7, 8 I’m completing the test by evaluating the data emitted by the use case and making sure it’s the same data I mocked on line 4.

Summarizing the domain layer

The domain layer sits at the very bottom of our code base. Here, we define our domain entities, interfaces and use cases for the upper layers to use. We keep things generic as much as we could to protect the core functionality from changes, leaving the hassle of dealing with implementations details to the upper layers.

Ok. let’s move up to the data layer. 🆙

The data layer

Just above the domain layer, we can find the data layer. Its purpose is to provide all the data the application needs to function.

We’re no longer at the generic wonderland of the domain layer. Here, we’ll find concrete implementations of the different data providers MovieNight is using. We can also see a new special type of classes in here — Mappers, but we’ll get to them later.

Keep in mind that the presentation layer is above the data layer, so the data providers have no knowledge regarding how and when they are invoked, which is a good thing.

Data providers implementation details

The implementation details of the data providers such as the caching mechanism, the database, and the networking manager are all here, so you’ll find references to libraries such as Retrofit and Room. Of course, these libraries are wrapped by classes that correspond to the interfaces defined by the domain layer to hide their existence.

Here’s one example: The MoviesRepository interface from the domain layer becomes MoviesRepositoryImpl (what an awful name!) in the data layer.

Let’s take a look at MoviesRepositoryImpl class:

MoviesRepositoryImpl.kt

Things that worth mentioning here are:

MoviesRepositoryImpl implements the MoviesRepository interface.

So it can be used by the domain use cases.

So it can be used by the domain use cases. This class acts as a factory that can “juggle” between the remote data store and the local data store.

getMovies() implementation checks for the presence of cached data before accessing the API. Other methods such as search() invoke the API directly.

implementation checks for the presence of cached data before accessing the API. Other methods such as invoke the API directly. The data sources implementation is abstracted

Even though they belong on the same layer with MoviesRepositoryImpl, and technically we won’t break the dependencies rule by revealing their implementation, the knowledge of any details regarding the data sources is irrelevant for the functionality of MoviesRepositoryImpl, so we abstract them behind MoviesDataStore interfaces. (and of course, MoviesDataStore is defined inside the domain layer)

Even though they belong on the same layer with MoviesRepositoryImpl, and technically we won’t break the dependencies rule by revealing their implementation, the knowledge of any details regarding the data sources is irrelevant for the functionality of MoviesRepositoryImpl, so we abstract them behind MoviesDataStore interfaces. (and of course, MoviesDataStore is defined inside the domain layer) Mappers are being used

I mentioned the mappers at the beginning of the section, and now it’s time to talk about them.

Mappers

Mappers, as their name suggests, are classes who “knows” how to map class A to class B. All the mappers in MovieNight extends the Mapper abstract class:

Mapper.kt

There’s a whole bunch of convenience methods in here, but the core functionality is pretty straightforward (line 3): Insert class A from one side, get class B from the other side.

Why do we need mappers?

The domain entities, which are the base data structure for our application are defined in the domain layer. They shouldn’t have any knowledge of the “outside world” or their “purity” would be compromised (I hope that you understand why this is a bad thing by now).

The problem is that the data layer contains specific implementations and specific implementation tends to have specific needs.

Retrofit is an obvious example: to allow Retrofit to parse network responses we often use libraries such as GSON. To make the parsing work GSON have a set of annotations we can use to instruct the parser. No annotations mean no parsing but we can’t possibly annotate the domain entities with GSON annotations. The domain layer has no idea what GSON is.

What can we do? We can create a new set of entities, entities that can be annotated and work with Retrofit. These entities will live inside the data layer, far away from the domain entities. One such class is MovieData:

MovieData.kt

It’s hard to tell by the gist but MovieData is very similar to the domain entity, MovieEntity, the only difference between them is that MovieData contains specific implementation code (Retrofit and Room libraries annotations).

So if Retrofit response holds a list of movies, they are now represented by an array of MovieData objects instead of MovieEntity objects. Problem solved? Nope. 😟

There’s another problem: the use cases (domain layer residents) have no idea what MovieData is, they only familiar with MovieEntity! What can we do? We can map MovieData to MovieEntity everytime we cross the boundary between the data layer to the domain layer —> Enter mappers.

Testing the data layer

Most of the tests here are unit tests (Room depends on the Android framework, so I test it using instrumentation), and since we abstract most of the implementations even between residents of the data layer, it’s pretty easy to test the core functionality.

Let’s take a look at a small part of MoviesRepositoryImpl tests suit:

MoviesRepositoryImpl caching mechanism tests.

As we can see, all the data sources used by MoviesRepositoryImpl are abstracted by interfaces, so we easily mock them using Mockito or create a simple implementations for testing purposes to test the caching mechanism without too much of a hassle.

Summarizing the data layer

The data layer sits between the domain layer and the presentation layer. Here, we define our data providers implementation details. We still try to abstract the different components and hide their implementation from one to another to support changes and easily test the core functionality.

Next up — the presentation layer.

The presentation layer

We’ve made it to the top! The presentation layer connects all the different pieces into a single, functioning unit that is the application. Here, we’ll find Activities and Fragments, the UI and presenters under (sort of) an MVP architecture, mappers, and dependency injection framework that’s wires everything across the application.

Presentation layer architecture

Notice the big headline? Yeah, that’s because this section can easily be its own blog post, but I’m assuming (hoping?) that you’ve got MovieNight source code in front of you. I’ll try to keep this part short and to the point and let you figure out all the different details by yourself. *fist bump* 🤜 🤛

The presentation layer is organized by something that resembles MVP architecture. It’s actually a hybrid between several architecture ideas I’m currently experimenting with 👽.

Views and ViewStates

Unlike in MVP, the views aren’t implementing an interface that’s get invoked by the presenter. Instead, the view is observing changes in a ViewState objects. These ViewStates are delivered by LiveData objects. The LiveData objects being updated, and are part of, a presenter. It’s worth mentioning that a presenter can hold more than one LiveData objects, and the view will register to all of them.

A ViewState is just a data container, holding all the information the view needs to render himself. Let’s look at a simple example; This is the ViewState for the popular movies screen:

PopularMoviesViewState.kt

Simple isn’t it? by “reading” the ViewState the view knows if the loading indicator should be displayed or what Movie objects to show. (Yes, There’s a new Movie object in here).

Here’s how the view, PopularMoviesFragment, handles the ViewState:

PopularMoviesFragment.kt

And as I already mentioned, the ViewState object is being updated by the presenter.

The presenters

The presenters in MovieNight are Android’s ViewModel objects.

I know, it’s a bit confusing. If someone is keeping score add +1 to Google under the “annoying naming conventions” category. But enough ranting, let’s continue. It’s worth noting that the presenters model are actually the domain layer use cases.

Why using ViewModels?

Some will correctly point out that the presenters should stay away from the Android framework. By using ViewModels, I’m “telling” the presenters about Android lifecycle events, and this is not ideal. So why did I choose to do this?

Because everything is a trade-off. I choose to sacrifice a certain degree of abstraction and gain seamless, “battle tested”, lifecycle events handling. It is simple as that.

Let’s take a look at presenter example. Here’s PopularMoviesViewModel:

PopularMoviesViewModel.kt

Many interesting stuff are going on in here, let’s tackle them “in order of appearance”:

Lines 5, 6: These are the LiveData objects the view will observe. One of them is carrying the ViewState object, the other is carrying an optional Throwable. Notice that errorState is a special type of LiveData called SingleLiveEvent, it’s purpose is to send update events only one time, this is really useful during configuration change.

Line 13: addDisposable() is BaseViewModel method that registers a RxJava subscription to CompositeSubscription. The BaseViewModel calls compositeDisposable.clear() when the ViewModel onCleared() is called.

Lines 13–19: Here we can see how the presenter is using the use case as a model:

Line 13: The presenter subscribes to the use case observable.

The presenter subscribes to the use case observable. Line 14: Using a mapper the domain layer entities are mapped into presentation layer entities.

Using a mapper the domain layer entities are mapped into presentation layer entities. Lines 16–19: The LiveData value is updated with a new ViewState object. Notice the usage of the copy() method that copies the object parameters into a brand new object, allowing us to keep the ViewStates immutable. We get the copy() method “for free” by using Kotlin data class for the ViewState.

And this wraps up the presenter section. It’s time to talk about another part of the presentation layer — the dependencies injection.

Dependency injection

As I previously mentioned, the dependency injection (I’ll refer to it as DI from now on) wires everything across the application. The DI is responsible for providing concrete implementations in a code base ruled by abstractions.

MovieNight DI is based on the Dagger2 library.

If you’re familiar with Dagger2, it’s worth mentioning that I’m using SubComponents with custom Scopes to control the scoping of different injections. This allows screen-specific injected objects such as use cases and ViewModel factories to be released from memory when they are no longer needed.

That’s it. DI in general and Dagger2, in particular, are huge subjects that really deserve their own blog post (or two) so I’ll leave it at that.

Time to talk about testing again.

Testing the presenters

Testing the presenters is as simple as invoking an action, and asserting that the ViewState updates match our expectations.

Here’s a small part of PopularMoviesViewModel tests suit:

PopularMoviesViewModelTests.kt

In the example above I’m mocking all the dependencies using Mockito, calling getPopularMovies() method, and finally — verifying my mock observer got invoked with updated ViewState with specific parameters.

And… That’s it!

Phew! That was a long post but, I’m pretty sure that by now, I’ve covered all the things I wanted to write about. This blog post aimed to highlight some of the interesting parts of MovieNight code base and some of the decisions I’ve made.

I hope you found the topics and ideas mentioned here interesting and educating.

Thank you for reading. 😄