Concerned? Separate the concerns!

Transforming Coupang’s legacy architecture for Android — part 1

Coupang Engineering
Coupang Engineering Blog
18 min readMay 21, 2018

--

By Joris Abale

Series index

This is part 1 of a series on “Transforming Coupang’s legacy architecture for Android.”

Android robot

This post is also available in Korean.

Android has come a long way since its early days. Google and the developer community played a significant part to transform the open platform visually, conceptually, and functionally. And as Android evolved, so did the Coupang app. In this series of posts, we will share a brief insight into how Coupang engineering made transitions in the architecture from its initial days of using the early Android SDKs, conventions, and patterns to the new, and becoming one of the most used apps in Korea.

Need for separation of concerns

When we first sat down to explore our options for improving the architecture in 2016, we had been building the Coupang app for many years and it had become increasingly multifunctional and complex due to the ever-changing Android framework. We had many teams of developers, ample lines of code, God Class or Object, and 10 million users. The codebase was difficult to maintain due to the size, and functionalities and responsibilities was focused on a single object. We needed a customer-centric yet optimal modernization method.

Among the early versions of Android apps, having a God Activity for UI was a common anti-pattern so Google had introduced fragments as a way to address this issue. We decided to follow Google’s recommendations and start by decomposing the God Class and using fragments for the UI-level or OS-level interactions. This was our first attempt at applying the separation of concerns principle and was, in a way, a modified use of a model-view-controller (MVC) pattern.

The use of fragments was great first. But over time, we added more and more features and faced new challenges.

  • Communication between the fragment and activity were oftentimes poor.
  • Because each fragment has its own lifecycle, we experienced complex unforeseen errors.
  • Fragments didn’t do the same job as a view controller but sometimes depended on views.
  • Unit testing fragments on Android was difficult.
  • Fragments started to become the God Fragments.

It started to cause more problems than it solved. We had built up technical debt and using fragments wasn’t enough to pay it down. We needed to separate the concerns, views, models, and business logics, and create code that is easier to maintain and test.

Choosing the architectural pattern

The process of choosing a pattern to implement separation of concerns can be a difficult one because there isn’t a perfect architecture and there are many factors that influence the decision. We decided that the best approach was to simply compare the main patterns considering the following objectives.

  • Increasing quality of code
  • Increasing integrity and reliability of the app through unit testing
  • Increasing readability and maintainability of code
  • Using industry-recognized standards and patterns

Architectural pattern 1: MVC

The MVC pattern separates an app into three main components: model, view, and controller. The model component is primarily concerned with storing the data and business logics. The view component renders the visual elements of the app and represents what is stored in the model. The controller component processes the incoming requests and tells the model and view how to behave.

A general model-view-controller (MVC) architectural pattern
Figure 1. MVC pattern

MVC is mostly monolithic and not ideal for Android where activities and fragments sometimes play the role of a view and controller.

Architectural pattern 2: MVP

The model-view-presenter (MVP) pattern is a derivation of the MVC pattern and a more typical architecture in modern Android development. The key difference with the MVC pattern is that the presenter is responsible for fetching data from the view and updating the visual elements on behalf of the view. One presenter can have only one view. The view and model acts like they are separated.

The model-view-presenter (MVP) architectural pattern
Figure 2. MVP pattern

MVP is easier to test, maintain, and scale. It doesn’t require further libraries or frameworks and can be easily integrated to the existing codebase. MVP is relatively easy to use, especially for those who have experienced the MVC pattern.

However, complexity of the pattern can be higher for smaller features. It can also trigger excessive callbacks between components unless reactive programming is used, like RxJava.

Architectural pattern 3: MVVM

The model-view-view model (MVVM) pattern distinctly separates the UI and the application logic. The key difference is that instead of the controller of MVC or the presenter of MVP, it has a two-way data binder between the view and the view model.

Image of the model-view-viewmodel (MVVM) architectural pattern
Figure 3. MVVM pattern

MVVM is also easy to test, maintain, and scale but requires Google’s data binding library or framework. Also, debugging data binding can be troublesome and the learning curve is steeper than MVC or MVP. Both MVP and MVVM seemed like a good option, but we decided to use the MVP pattern for Android because the data binding library was still a beta version at the time.

Refactoring strategies

Refactoring towards a chosen pattern doesn’t happen overnight, especially when applying a customer-centric mindset. It requires efficient and a continued effort from all stakeholders. Here are some of the steps we took in the real-life to make it happen.

Strong foundations

We wanted to create a strong foundation for development and improve the code quality at the same time. So we used SonarQube and all its features. (SonarQube runs static code analysis and detects bugs, code smells, and security vulnerabilities. It also shows unit test coverages and maps raised issues to industry standards to better the understanding of developers.)

We integrated SonarQube into Coupang’s CI pipeline to generate reports for every build. We used custom lint rules that were devised to our needs. We also weaved our unit testing results to the SonarQube reports. We put policies and procedures in place for tasks like issue assignment and code reviews to improve our code quality.

As a result, we were able to reduce duplicate codes, increase test coverage, and reduce the overall complexity of the app.

Graph showing a decrease in the number of issues in the Coupang app
Figure 4. Decreasing number of issues in the Coupang app

Custom library

After completing the initial cleanup process, we wanted to achieve consistent MVP implementation and reduce boilerplates. Therefore, we created an MVP library to handle binding of the view and the presenter so that the view only needs to override a single createPresenter() method.

For more information about how a library can help to implement MVP, see Mosby in GitHub.

Knowledge-sharing

Sharing the knowledge is important not only for the newly onboarded employees but also for team cohesion. We conducted multiple conferences to share our application methods in depth and also workshops for hands-on experience at refactoring. We also created and shared internal guidelines for the architecture and code.

For more information about the guidelines, see Ribot’s Android guidelines in GitHub.

Google’s guide to architecture

During Google I/O 2017, Google announced a set of recommended guidelines to architect an Android app and a companion set of architecture components. We also adapted the guidelines and components to our MVP architecture upon its release.

One of the components is a lifecycle-aware component which can automatically adjust their behavior based on the current lifecycle state of an activity or fragment. We used this new component by implementing their lifecycle methods to react to the view lifecycles and events like latency and network call pauses.

For example, the following method can be implemented in the presenter.

@OnLifecycleEvent(value = Lifecycle.Event.ON_RESUME)
protected void onResume() {
...
}

This is a very useful way to manage inheritance complexity that arises from adding more and more code to BaseActivity and BaseFragments while adding a consistent characteristic to the activities or fragments. And we value composition over inheritance.

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way and allows data to survive configuration changes such as screen rotations. We used the ViewModel to retain the presenter during configuration changes so that we eliminate the need to recreate the presenter when recreating the view.

The LiveData class is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state. LiveData is mostly used for MVVM components because model and view are not directly coupled in the MVP pattern. However, the presenter component in our MVP pattern can be seen as a lifecycle-aware component.

Conclusion

It took us months of hard work but we were able to refactor most of the codebase using the MVP pattern. We also achieved our target to increase the unit testing coverage to 80%. Modernizing the architecture was not an easy process but marked the first steps in our transition. In the coming posts, we will talk about the further steps we took.

Series index

This is part 1 of a series on “Transforming Coupang’s legacy architecture for Android.” In the following parts of the series, check out how Coupang has secured the reliability from unit tests and how we have modularized our app going a step further.

Part 1 — Concerned? Separate the concerns!

Part 2 — The Android app modularization

Part 3 — Reducing dependencies through repackaging

We’re actively looking for passionate individuals who are not afraid to ask questions, challenge the norm, and make things happen. Check out our open positions or sign up for job alerts!

--

--

Coupang Engineering
Coupang Engineering Blog

We write about how our engineers build Coupang’s e-commerce, food delivery, streaming services and beyond.