realm / realm-dotnet

Realm is a mobile database: a replacement for SQLite & ORMs
https://realm.io
Apache License 2.0
1.23k stars 159 forks source link

Research and Document Model alternatives #398

Closed AndyDentFree closed 8 years ago

AndyDentFree commented 8 years ago

This issue created to host a discussion of pros and cons as mentioned in the weekly meeting 20160216.

We need to work out what ways to use Realm with Model objects people will find idiomatic and provide samples or at least documentation showing them.

Andy's idea is to work out very rough private tests using different approaches, so we can prove there are no side-effects or corner cases (especially with notifications). We can then over time decide what of these might be promoted to be cleaned up and made into public tutorials.

In the .NET world there is certainly at least a portion of the community who are staunch believers in a formal ViewModel. However, a lot (most?) of the discussion on how to structure your code comes from people writing server apps using ASP.NET. eg this SO thread

cmelchior commented 8 years ago

I am not sure these two ideas conflict, there is nothing that prevents you from mapping a RealmObject used in the DB layer to a ViewModel to show in the UI, and the reverse going back?

jpsim commented 8 years ago

How "view models" work with Realm in cocoa is fairly flexible, we don't enforce or even strongly recommend doing it one particular way. One thing you can do is have "views" on your actual Realm models: e.g. just computed properties:

class Person: Object {
  dynamic var firstName = ""
  dynamic var lastName = ""
  var displayName: String { return "\(firstName) \(lastName)" }
}

Another thing some users do, in a view-model-y way is to have setters for these wrapper properties:

class Person: Object {
  dynamic var firstName = ""
  dynamic var lastName = ""
  var fullName: String {
    get { return "\(firstName) \(lastName)" }
    set { /* naively split fullName into firstName and lastName */ }
  }
}

Or what some users do in the spirit of "separation of concerns" or avoiding "leaking implementation details" is to wrap all their Realm models with value types:

class RealmPerson: Object {
  dynamic var firstName = ""
  dynamic var lastName = ""
}
struct Person {
  let firstName: String
  let lastName: String
  init(realmPerson: RealmPerson) {
    firstName = realmPerson.firstName
    lastName = realmPerson.lastName
  }
}

which we actually discourage because you lose many of Realm's features when doing this since you're essentially detaching Realm objects. AFAICT we don't actively discourage this approach in our docs, but we probably should. When we see users doing this in support tickets, then we try to "show them the light".

All of this applies to the Objective-C side of things too.

I'm not sure if this is what you were asking, but hopefully this helped. We explain more of this in https://realm.io/docs/swift/latest/#models

AndyDentFree commented 8 years ago

Where do the contents of a GUI list of all People come from?

For example, looking at Sam's just-published unidirectional data flow he extends Realm to fill the Store role. That makes sense to me.

astigsen commented 8 years ago

You should think of the Realm as your Model. Both in abstract terms, with the model being the aggregate of all the objects and types in the Realm, and in practical terms where you can interact with the Realm object itself.

Where do the contents of a GUI list of all People come from?

That would be a Results<Person>, which you would get from your model (Realm) by calling something like realm.objects(Person).

looking at Sam's just-published unidirectional data flow he extends Realm to fill the Store role.

Extending the realm is actually a bit troublesome as it breaks the separation of concerns. Suddenly all views and components have to be aware of the entire model, rather than just the subset that they are responsible for. Ideally a view listing all People should only need to contain a reference to the Results<Person> it is displaying, and nothing else, making it easy to reuse and compose with other views.

jpsim commented 8 years ago

@astigsen the only thing stopping that in Sam's unidirectional data flow app is the lack of dependency injection due to the use of storyboards, and unrelated to Realm. But this is unrelated to this topic.

astigsen commented 8 years ago

the only thing stopping that in Sam's unidirectional data flow app is the lack of dependency injection due to the use of storyboards, and unrelated to Realm.

Yes, but also the way the code was structured. By putting on all the actions (modifying operations) on the Store, rather than on the individual Model classes, you enforce a hard dependency on the entire Model which breaks the Separation of Concerns.

astigsen commented 8 years ago

I just posted a PR to Sam's example code to facilitate discussion on this: https://github.com/samritchie/TimeTracker/pull/1

AndyDentFree commented 8 years ago

Thanks very much @astigsen for clarifying your viewpoint and the need to avoid the entire app knowing the model.

I'd say that a Realm is an Interface in that it is completely generic. User code takes Model objects such as Person and specialises the Realm with queries or all object retrieval. At that point, a given part of application code knows only a narrow slice of what might be stored in the Realm.

To set up collections of data for views we need code like (in c#) var allPeople = _realm.All<Person>().

In simple app architectures, those few lines of code would often live in a controller object such as an IOS ViewController.

For people who are firmly in the MVVM camp (such as many dotnet folk), you would have a ViewModel which contains the Realm as property and _realm.All<Person>() call.

Again, for a simple app, there might be a single ViewModel class which knows all the Model details. In more sophisticated apps, there will be different ViewModels.

Each of those different ViewModels would only know about one or two Model objects as RealmObject classes with which to compose collections bound to their owning Views.

I think this may be compatible with the idea of extending a Realm in Swift because there can be multiple extensions and there is no need to expose all of the extensions to all the application code.

So summarising for dotnet, we have the following where to put the All call spectrum:

  1. User View code
  2. A common ViewModel
  3. Different ViewModel classes specific to certain Views.

There may be another alternative in Reactive architectures and I think there's an independent dimension of how data binding works but that's for further research.

astigsen commented 8 years ago

For people who are firmly in the MVVM camp (such as many dotnet folk), you would have a ViewModel which contains the Realm as property and _realm.All() call.

What would be the benefit of wrapping it in a ViewModel over just using the Model itself?

AndyDentFree commented 8 years ago

What would be the benefit of wrapping it in a ViewModel over just using the Model itself?

The benefit is arguable, and often argued, mainly about separation of layers.

The need to support this approach, and demonstrate that we do so with at least a public snippet or tutorial, is about a religious issue especially in the .NET space.

I just did a quick search in the ReactiveUI slack channel - 1,225 mentions of "ViewModel" since June 2014. Just in Feb 2016 there are 306 references.

The phrase dead in the water was used, if we didn't support PCL ViewModels. (That's why I got PCL support going last week.)

astigsen commented 8 years ago

Well, it is trivial to support. It is basically just putting a class in between to hold the methods.

But it is really hard to see any benefits, and plenty of downsides (given that you now have to manually relay all events to the View). It would be great to explore if there are any real benefits (which we could then potentially try to support even better).

AndyDentFree commented 8 years ago

The major reasons why I intended to explore these architectural issues with various private samples were

  1. so we could look at them without religious public discussion,
  2. see if we could refine Realm to support them better and,
  3. most importantly, if there were any performance gotchas in these patterns ensure we coped before anyone publicly made an issue.

I agree it's hard to see the benefit and I think some of the hysteria over ViewModels can be explained by the sheer size of boilerplate code required by some ORMs and other frameworks.

AndyDentFree commented 8 years ago

downsides...given that you now have to manually relay all events to the View

Sorry I didn't notice that line earlier. That shouldn't be the case and why I want to get some different binding samples going to dig deep into the exact nuances.

An important point which is not visible in the discussions above is that our RealmQuery (supplied by either Realm.All<Person>() or by a query) is an IQueryable standard .NET type. As such, it's already got the abstract GetEnumerator you would bind to a collection view.

There are a lot of patterns for UI binding and collection behaviours in .NET and we're trying hard to make sure our simple Realm API remains simple by satisfying those patterns. That avoids us having to write the kind of adaptor classes seen in the Java binding.

cmelchior commented 8 years ago

Having different types of model Objects is quite typical at least in the Java world, e.g. in something like "Clean architecture" you might potentially need 3 different Models: Database entities, Use case models, and ViewModels.

As @AndyDentFree points out, the main argument is seperation of concerns, which on Android also translate into easier testability as the inner layers can be unit tested without depending on a device. The big downside as @astigsen points out is a lot of extra code and mental overhead, and it does require you to setup the event propagation yourself, but that is usually mainly why you have chosen that particular architecture in the first place.

But IMO mapping between the different layers is extremely straightforward, albeit a lot of boilerplate. As long as we work with those architectures I don't see any problems with us advocating an easier way of working. Mostly this is about us implementing fine-grained notifications so people just hook into that to drive their event-system (whatever that might be).

https://github.com/android10/Android-CleanArchitecture

AndyDentFree commented 8 years ago

Locked and closed as per TA request.