Repositories and Domain Models in Swift & Kotlin

Eric Silverberg
Dec 18, 2020 · 4 min read

What comes after an API response?

Image for post
Image for post
This repository could probably use a refactoring, but those domain models sure look tasty. Photo by Marc Noorman on Unsplash

APIs are like the engines of apps — every app has them, and they provide the fuel that makes the app work. But what happens after an API call completes? How is the data managed, munged, and manipulated before it is (ultimately) rendered in a View on iOS or Android?

***

Perry Street Software has been publishing apps since 2010, and in this series of blog posts, we introduce the Clean MVVM architecture for consistent cross-platform code in Swift and Kotlin.

The center of our abstraction — Repositories and Domain Models — is the part that should also be most closely aligned from a code perspective. In our Clean architecture diagram, Repository and Domain Model classes are represented by yellow 🟡 .

Image for post
Image for post

Both classes should be written in pure Java/Kotlin/Swift, and not have any knowledge of views, view models, or device and platform-specific implementations. Ideally these classes can be declared in Swift Packages or Java libraries and not the main app itself; that way tests can be run without an emulator.

Critically, Repositories have API classes injected. Repositories provide an abstraction to a local or remote API and map the responses from that API.

Image for post
Image for post
The code on both platforms, closely aligned

Models

Coming as no surprise, models are simple data structures used throughout our application (e.g. a Profile). Model objects are the only part of the inner layer that can be exposed to the outer layer (Views and APIs). Repositories map the API responses (probably JSON) into model objects.

On iOS, models should typically be defined as Structs; on Android, sealed data classes. Models should be immutable, and have no inputs beyond the constructor. Parsing of models is done via Codable on iOS or something like Moshi on Android.

Models should expose only primitive types (Strings, Ints, Doubles, Enums), and should not have any dependencies.

Repositories

Repositories are like an ORM that live on top of databases and network APIs. According to Google, you can consider repositories to be mediators between different data sources, such as persistent models, web services, and caches. Eric Evans, in his book “Domain Driven Design”, describes repositories as having the following advantages:

“They present clients with a simple model for obtaining persistent objects and managing their life cycle.”

“They decouple application and domain design from persistence technology, multiple database strategies, or even multiple data sources.”

“They communicate design decisions about object access.”

“They allow easy substitution of a dummy implementation, for use in testing (typically using an in-memory collection)”

The key to implementing repositories successfully is to ensure we are using the right Reactive primitives when exposing various types.

Most of your architectural time will be spent writing and testing Repositories.

The three essential operations on Repositories

The table below recommends primitives when accessing properties, i.e. values that should be always set and may change over time; retrieving values, either from the network or from a local store; and modifying state, such as a create, update or delete operation.

When accessing configuration parameters, typically stored in a PrefsStore (UserDefaults on iOS; SharedPreferences on Android), we allow ourselves to expose the Boolean or Int value directly; otherwise all else is a Reactive primitive!

Example: MatchRepository

To continue our example of a swipe-based matching feature, consider a MatchRepository:

In this example, you can see we expose the boolean values from our PSSPrefsStoreImplementing/IPrefsStore directly. Otherwise, the operation that goes out to the network for some data — getMatchStack — returns a SignalProducer<MatchStack, Error>/Single<MatchStack> All other methods that record or change state, such as adding or deleting a rating, return SignalProducer<Void, Error>/Completable.

Next up

We have reached the center of the Clean MVVM architecture. From here, thanks to Dependency injection, we have references to outer-layer API interfaces/protocols, which we will explore next.

More in the series

About the author

Eric Silverberg is the CEO and founder of Perry Street Software, publisher of two of the world’s largest LGBTQ+ dating apps on iOS and Android.

Perry Street Software Engineering

Code + design from SCRUFF + Jack’d

Eric Silverberg

Written by

CEO, Perry Street Software. Developer. 🏳️‍🌈

Perry Street Software Engineering

The engineering blog of Perry Street Software, which publishes two of the world’s largest LGBTQ+ dating apps, SCRUFF and Jack’d

Eric Silverberg

Written by

CEO, Perry Street Software. Developer. 🏳️‍🌈

Perry Street Software Engineering

The engineering blog of Perry Street Software, which publishes two of the world’s largest LGBTQ+ dating apps, SCRUFF and Jack’d

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store