We're building an iOS app using Realm as our model / database but we want to design the client so it can easily accommodate changes in the REST-ful API that may occur in the future. Lets say we're developing an app for sports competition organizations that accommodates different events. Each event has different types of event types based on what sports are being played. Right now the API only returns Soccer, Baseball, and Football but in the future it might expand to include Basketball too. Later it might eliminate Baseball. I've designed the Realm objects so that Events are decoupled from Event Types using a one-to many relationship like this:
class EventTypeGroup: Object {
dynamic var name = ""
let eventTypes = List<EventType>()
}
class EventType: Object {
dynamic var name = ""
dynamic var descriptionText = ""
}
An EventTypeGroup is the class describing the event types (in this case which sports) will be played at the event. I used this design because dictionaries aren't supported in Realm where we could store an event type with an associated set of properties.
In order to make the model adaptable to future changes in the API in case sports for a particular organization are added or removed, I used an abstract factory pattern like below. This way an event cannot be created without using an enum in keeping with modern Swift design principles. The problem I'm having is, assuming we only check for changes in the API to the event types (sports) once upon the user opening the app, how do we change the model with the app already open? Will the database need to be migrated if these fields change?
protocol EventTypeGroupFactory {
func createEventTypeGroup(List<EventType>) -> EventTypeGroup
}
protocol EventTypeFactory {
func createEventTypes() -> List<EventType>
}
class SportEventGroupFactory: EventTypeGroupFactory {
func createEventTypeGroup(withEventTypes: List<EventType>) ->
EventTypeGroup {
//implement logic to create an EventTypeGroup for the SportEventGroup
}
}
class SportEventTypeFactory: EventTypeFactory {
EventTypeGroup {
func createEventType() -> EventType {
//implement logic to create an EventType for the SportEventType
}
}
class EventTypeGroup: Object {
let eventTypes = List<Int>
enum EventType {
}
}
class EventType: Object {
var type: Int?
name: String?
description: String?
}
class Event: Object {
static enum EventType
init(eventTypeWithRawValue:) {
}
}
Also, how will I refer to the different variations of the classes in the code I write now if I don't know how they'll be defined. I'm guessing the abstract factory pattern may not be the best way to deal with this but am not sure what other options I should consider or how to approach the issue of making types easily extensible in a model based on API changes.
You are overcomplicating it, I think. Just add a string property called "eventType" to your Event model.
For example, normally, if you didn't need to keep things dynamic, you might do something like this:
enum EventType {
case soccer
case baseball
case football
}
// Your Event model
struct Event {
var date: Date
var eventType: EventType // a static type :)
}
But in your case, instead you can do something like this:
// Your Event model without any enums
struct Event {
var date: Date
var eventType: String // a dynamic type :(
}
Property eventType
can then be "soccer" or "baseball" or "football". (But the compiler cannot help you catch errors now.) As for your persistent storage, just have a field there of type eventType
and store the string.
Dynamic types make me sad given how nicely static Swift is, but it gets you what you want. Just make sure to think about edge cases. To not end up with undefined behavior, think ahead about what your app is supposed to do if, for example, you end up with event types on disk that are no longer being supported by your REST API.
For example, say you have an /eventTypes
endpoint, so that your app's users can add events and categorize them accordingly, and it's been returning "soccer", "baseball" and "football" and your users have been adding these types of events and you have been storing them on disk (in Realm or CoreData or whatever). But then one day someone on the backend (or through the backend) renames "football" to "american football", and let's hope no one renames "soccer" to "football" too. (And so now you can't tell if a thing was renamed or removed and another added.) Do you then take the union of the event types your /eventTypes
endpoint returns and what you find on disk? Do you let users add old event types that still live on disk but are not longer supported by your REST API or only display them?
With active users, you will likely end up with these kinds of edge cases if your backend folks rename event types or remove event types (as opposed to simply adding them). Just discuss with your stakeholders what the behavior should be.