Search code examples
iosswiftdesign-patternsmodel-view-controllerdesign-principles

How do I make model in MVC for iOS app dynamic based on changes in REST-ful API?


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.


Solution

  • 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.