Just before starting a new contract I was reading Greg Lhotellier's medium post about MVC-RS, based on a talk he gave at Swift Conf 2016. The article made a lot of sense, it was one of those "Oh, of course!" moments.
Once I started the new project it was the perfect time to try it out, specifically the 'S' part (I couldn't do the 'R', I use storyboards, sue me!). I was using MVVM, rather than MVC, but I was pretty sure that wasn't going to be a problem (turned out, it wasn't, phew). But the specifics of the project gave me pause for thought when I got around to implementing the store.
In Greg's article he uses the example of a car store, and went into more depth that anything I could do as an intro, so I'd advise you go read it. This blog post will (ha! "will", as if it's a given and not a vainglorious hope) build on the idea.
Store, store, store!
Let's look at something based on a real world example, an App that displays data from a BTLE heart rate monitor.
The heart rate monitor will publish heart rate data to the app, and the app will have two tabs (or two pages available in a hamburger menu if your designers hate you.)
First tab: Live data. The app will read the sensor data and display it on screen. When the user stops reading the data the app will save the data. Second tab: History view. A list of saved data sessions.
Lets start with our model, just to get everything straight in terms of what I want
Store, store, store, store,... store?
I need to decide on ownership. Somewhere in my future I will have a store, but who owns it? The view controller? I decided against that, fine for MVC, but not for MVVM. The view model? Yeah, ok, let's give that a try.
And in the ViewController I can do the following
This is workable. If you're using dependency injection like above you can provide a different view model that conforms to the HeartRateViewModelling protocol for testing. This will work well with the 'R' in MVC-RS. Otherwise, if you're using storyboards, you'll need to do something different, like so
This isn't as good, but I've made the viewModel mutable to achieve the same testability. It works in practice, even if it makes me feel a little dirty.
Sink or swim
So here's where I first hit a stumbling block, we're getting heart rate data from a Bluetooth sensor. Congratulations to us, we've identified our store. But I'm also saving that data (to some backend somewhere) to preserve saved sessions, so I've a second store for, ehhhh, storing data.
Also, I can't write to the bluetooth sensor, it exists only to produce heart rate data, not to save it. Technically it's still a store, just a read only one.
The backend is, however, something I'm going to write to as I get live data, so it's a write-only store. But I will read from it in the second use case: displaying recorded sessions in the history tab. I want to conserve some sense that it is read only in the history tab. What to do...?
In short, I went with the following idea. My HeartRateStore should be a wrapper for two separate stores, a read-only source and a write-only sink.
First of all, we define our read-only and write-only sources and sinks
Nothing new here, we've just split out the original HeartRateStoring protocol. Now our responsibilities are separated. We like separated responsibilities, don't we precious?
We can define a read-only sensor type and a read-write backend type
Now we rewrite HeartRateStoring to have optional source and sinks. Why optional? Because in the case of the history view, for example, we know we only display the data, we don't want to write it, so it's not hard to envision use cases when we only ever care about a source or a sink.
Now when we create our view models, either at initialisation of the view controllers or injecting later if using storyboards we can create the two view models
Even though HeartRateSensor and HeartRateBackend are a data source/sink, they are actually stores. But honestly, SinkStore, StoreSink, StoreSource or SourceStore just means the naming is getting out of hand. I'd rather stick with source and sink.
The beauty of this solution for the store (which one? gaaaaaaaah! The parent store, ok?) is you can create a nice pipe from unrelated sources and sinks. Read from one place, like Realm, BTLE device, whatever, and write it out somewhere else entirely different, Firebase, iCloud who knows. And of course, we have enforced which stores are read-only, write-only or, if we wanted, we could have a read-write store like so
This has worked well for me in practice (more than I can say about the rest of the crazy ideas I write about) and I'm hugely grateful to Greg for his great article that kicked it all off. Now I just need a name. MVC-RS is snappy, but now I need a read-only store, write-only store prefix. I don't think I'm going to get much support for MVVM-RROSRWS, though.