Is Core Data for your Data Model or your Persistence Model?
Apple says, in their introduction to Core Data:
Core Data, new in Tiger, completes the Cocoa vision for building well-factored applications based on the Model-View-Controller (MVC) pattern by providing a strong, flexible, and powerful data model framework.
Apple says Data Model. As a data model, it fails. Only a few types of data are handled – strings and numbers primarily. If you want colors, “attributed strings” (text with fonts & formatting), images, or anything else, you end up having to go through NSKeyedArchiver and storing generically as binary.
From the Core Data Programming Guide:
To use non-supported types, in the managed object model you define two attributes. One is the attribute you actually want… The other is a “shadow” representation of that attribute.
So immediately you are mixing data and persistence models.
What you end up with is two fields, one persisted, called for example, “ColorArchived”; the other field called “Color” which is not archived and is suitable for the rest of your code to treat as a regular property. But of course now you have to subclass NSManagedObject and in your subclass write code to keep these two fields synchronized. Pain.
Your nice simple data model you thought you were defining is suddenly polluted with items created specifically for persistence. It has “ColorArchived” and “Color” can be moved out of the Core Data model entirely as a property of your class. Crazy because “Color” is the actual data you’re modeling! All that’s left is the persistence model!
A persistence model, not a data model! Core Data is all about persistence, really. It gives you choices about how to persist – such as XML or SQLite. This is a problem though. People are expecting to create a data model.
So, Core Data is about persistence. So it had better be pretty good at that, right? Well, it is. Very good. For version one of your application.
But then comes version two. You want to add some fields. As I previously blogged, Apple & Cocoa give very little help here. Apple says in their Core Data FAQ:
Mapping data from one schema to another is a difficult problem to solve, and Core Data does not provide a generic solution—you must perform the transformation yourself.
Does that sound like a Cocoa solution? Not to me. Cocoa is supposed to make powerful tasks simple. IMHO Core Data is a failure in this regard. It’s great for version one of your application – but it badly drops the ball after that.
You wind up with data model classes that have to read all versions of your schema, and write to the current version. Cocoa could, but doesn’t help here by for example, identifying schema versions, noticing migration is needed, iterating over the old schema and calling your persistence-related code to handle migration to the next schema version. It doesn’t, so while Core Data does the basic persistence well, it’s not a good persistence architechture.
There’s just not enough separation between the persistence model and the data model. Your NSManagedObject subclass starts out as the data, but has to understand the persistence model and be able to deal with all the versions. It shouldn’t have to. Persistence and migration should be separate from the data model; it would be great if migration could happen before your data model even gets loaded. Cocoa would see your persistent store is outdated, run through the migration steps, then load the document object model. That’s how a persistence model should work, but Core Data doesn’t work like that at all.
Another option is to leave everything as NSManagedObject, let that be your persistence model. then create a separate set of classes to be your actual data model. But then you’ve got to write code to translate between the persisted Core Data graph and your object model graph. Which is what Core Data is trying to save you from doing.
I don’t have a good answer here but I do know that I wish Core Data’s separation between persistence model and object model was a little wider.
More to come as I think, no I hope I have figured out migration to the point where it actually works. It even works on the current Leopard build.