Confessions of an iOS Dev: EP1 — Breaking Principles (and fixing them)

TheiOSDude
5 min readOct 25, 2021
Photo by Shalone Cason on Unsplash

A huge part of maturing in Software Engineering, is realising that mistakes can happen, and thats fine, but it’s how you overcome them that counts.

I’m not here to discuss how we got there, i’m here to talk about the remediation we applied, as a team.

A shopping app we are working on, has a shopping cart functionality. The requirements, simply put, where;

  • Persist locally (no server side storage) 💽
  • Max capacity of 9 items.🛒
  • To look like this screenshot 😍

Cart was built very early on in the project, and we was churning code out….. fast. As an engineering team, we elected to use Realm as our Data Storage provider, as we had a data storage vision greater than just Cart, for which Realm would be sufficient for.

“Right, ‘Carts’ in sprint, lets go, quick service implementation in Core (our Shared pot of code integrated via SPM) bring it into the app and we can push the cart work onwards”

If you was to draw a diagram, it would look like this:

We look back on it now, and we itch, inside, with bats nawing at our feet. As a bunch of excited engineers equivalent to youths in a sweet shop, we pushed, integrated, and forgot about it — afterall, we had a working cart, right?

Let’s think about the Dependency Inversion Principle here from the SOLID principles we should all live and breathe daily.

Dependency Inversion, if adhered to, should allow us to swap out an underlying class, with little recourse. Think, if you use Alamofire/Realm, and it’s shutdown/sunsetted tomorrow, how easy could you swap that implementation out.

Well, for our CartService, we have no protocol here and CartService is heavily built into Realm, returned Realm objects, which naturally, bled into our client app. I’ll show some examples:

CartViewModel {private var cart = List<SharkCartItem>()}

This is where it begun, `List` is a type provided by Realm, for which we was passing around like candy across our project, alongside our Realm entities, tightly coupling our Ecom project, to Realm.

This isn’t just a case of Realm being a transitive dependency in our Client app (remember, Cart Service was plonked into our Core package), the underlying issue was because the Cart object(s) are not being mapped out to entities of our own type which had no bearing on Realm.

You could feel the tight coupling:

So you wake up, and decide it’s now or never (quite literally). We had aspirations to remove Realm altogether, as it was no longer fit for our use-case for which it was originally intended, but you couldn’t just remove Realm in our project, and swap CartService inner workings with say, a UserDefaults implementation.

So, how far did this damage go inside our client app?

A search inside the client Xcode project for “import RealmSwift” revealed some 25+ files! 25 Files needing this dependency, for which our Core layer was originally intended to be built to hide us from. 💀 Our shopping app shouldn't care at all about Realm, this is part of the Dependency Inversion principle. It just cares for the high level abstraction (a protocol).

I ran a session with a few of the engineers here to decide best course of action.

Cart Service= An abstract service, who’s implementation can be substituted with little to no change in the client app.

Execution

First step; our client app needed to not know of Realm, at all, that implementation can happen inside Core, and Core alone. Being in a separate module, the power of access modifiers comes in very handy.

We made all our Realm Entities, declared in our Core layer, internal, to disallow them from being used inside our ecom app.

Changing our realm entities from public to internal

We created new Classes, all with 0 care for Realm. They was as good as identical to the Realm entities, except, not a Realm element.

Lovely, now to map from our internal Realm entities to our public entities.

Fantasitc. No breaking changes in Core so far. The next step was to update our Cart Service, public methods, to return these new public entities, mapped from the Realm objects, inside the Cart Service.

As above, you can see, our use of Realm itself doesn’t change, but we make use of our mapping, and changed our return type. ⏫

Excellent. But, at this point, the clients CartViewModel takes a CartService type as a dependency, and our CartService is intentionally final. How do we stuff a Mock Cart Service into CartViewModel, say for testing?

Protocol-Oriented Programming

It doesn't need a great introduction, all our services except our CartService are bound by a protocol, a way for us to easily inject different conforming dependencies, without our ViewModel giving a hoot about the implementation underneath. Here, for Cart Service, we just took our public methods, and thrown then into a protocol and made our CartService adhere to it.

How it looks now:

Now this is better, our client app cares not for `CartService` Implementation, and our DI can now just take any class conforming to Cart Service’s Protocol.

We can now mock easily, its behaviour, and equally, swap out the Realm Implementation at a later date, and without changing the Cart Service Protocol definition, our client app just needs a SPM update and FUCK IT — SHIP IT.

How would you have tackled this sort of problem? Anything you’d change in your app and want to bash ideas together? Do let us know.

--

--

TheiOSDude

Lead iOS Engineer  — Advanced Github downloader — Serial StackOverflow browser