1

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.

stonybrooklyn
  • 122
  • 2
  • 10

1 Answers1

2

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.

willtherussian
  • 106
  • 1
  • 4
  • No dice on the EventType class I added to your response? Also, any particular reason you chose to implement the Event model using a struct rather than a class? – stonybrooklyn Jul 31 '17 at 15:35
  • 1
    Re. structs: structs are safer (in Swift they are passed by value, not by reference) and also faster at runtime (no vtable to maintain). But if you need to subclass and protocols aren't enough, then back to using classes. – willtherussian Aug 02 '17 at 02:17
  • 1
    Re. EventType class: yes, you can use a class or a struct for the eventType property if you need to wrap other data and place the string (the one that will take on values like "baseball" and "football") inside it. The point is that you'd still be using a string instead of an enum. – willtherussian Aug 02 '17 at 02:19
  • Goes without saying, outside your constraints the answer would've either been migrations (e.g. light migrations with CoreData) or a nosql db on the device (e.g. couchbase). And Couchbase Light you could use on both iOS and Android. – willtherussian Oct 03 '17 at 04:57