Alexander Steiner
Home
Blog

Rewriting the most important database layer after 10 years of production use

I started developing the first app version of SchoolOrganizer at the end of 2013. I was very young I taught everything I know at the time myself. So 2 years later, in 2015, I started over. Same idea, new bundle id and AppStore presents. A real 2.0 of the app.

SchoolOrganizer: An app for primarily German students to organize their school day: Timetable, homework, exams, grades and more.

2015: First datamodel

I wanted to get the data model mostly right early on in the development to reduce the hassle of migration. At the time I already learned that distributed migrations are difficult to get right.

I decided to use CoreData as a first party Apple framework (In hindsight I should have considered bare SQLite). And with that set I modeled my whole object graph as close to the reality as possible: A term can have 1 or 2 repeating weeks, each week has days with lessons and so on.

Also homework, exams, grades and all the other entities like subjects, rooms, and teachers. I even considered the relation for a future feature, which I never shipped so far: Grades for teachers as joke.

2019: iCloud synchronisation

Since then my app grew, new features where added and I also added a few new fields to the model here and there. Most of it still worked and I hand’t much hassle.

Except 1 thing: Users requested cloud synchronisation between devices.

Early 2019 was a time where Apple deprecated their first CoreData+iCloud framework. CloudKit existed but only without any abstractions. Over a few month I tried to implement a CloudKit synchornisation but without much experience and my complex model, this didn’t lead to anything.

Then with iOS 13, Apple introduce a first party solution for CoreData synchronisation over CloudKit: NSPersistentCloudKitContainer. It enabled me to ship a cloud synchronisation without investing a lot of time into it, which I didn’t have with a full time job.

2019-2025: Issues arise

Over the years I dealed with a lot of issues regarding my own way of implementing NSPersistentCloudKitContainer . I learned a lot, had exchanges with Apple and got a pretty good grasp of how it works and what doesn’t work.

One of the fundamentally difficult things with sync is keeping any object graph in sync: Don’t create missing objects because they could still be pending downloading.

I optimized my code, simplified a few of my object relations, added guardrails and custom cleanup logic.

But 1 part I become very frustrated with was my initial modeling of the timetable (Term → Week → Day → Lesson). I was very error prone regarding missing week or day objects during sync. I was also not every flexible in regards to expanding the timetable use-cases users where requesting.

So I became clear to me that something needed to change.

2026: Timetable 2.0

The timetable is the most important part of my app, I knew I couldn’t mess this up in any way. It also took longer than I expected which pushed the publishing way close to the beginning of the new school year in Germany and I didn’t want the new release to hit in the midst of my busiest time of the year.

I’ve done a lot of prototyping, experiments and of corse discussions with AI about a robust and expandable new model of the timetable.

I wanted to allow as many repeating weeks as possible, not just 2. Also it should behave much more like a calendar with one-time events like a school wide event. And it should be much more reliable during syncs.

The solution was a much thiner model with only the term having as many timetable entries as required. Each entry stores information about the repeating interval, dates and times.

A new data model also required a whole new business logic layer. I used that chance to centralize as much as possible and make it more testable than before. As nearly every part of the app interacts with the timetable data, be it be next lesson calculation, exam times or widget logic, It touched a lot of my code.

The new data model required transforming the old data into the new format.

As I mentioned before, migrating local user data which is shared between devices is very error prone. When 2 devices run the migration at the same time, they don’t know from each other which would lead to a lot of duplicate entries.

So I decided to have a lazy-migration: The user can start the migration on their own. That reduces the risk of the app running the migration automatically after the update is installed. That meant I need to support both data models at the same time. Luckily I could branch out the timetable UI easily and implement the new model in separate SwiftUI views.

I spent a few month re-implementing all of the existing feature set, just in a modern way. I modernized the app code and model a lot.

Present

Together with the release, I posted a marketing post in German to summarize the past 10 years a bit more from a feature perspective.

And now the update is live since 2 weeks. A big part of my users have already migrated and all critical bugs are fixed.

I think my app is in a good shape to survive for the next 10 years.

Published: August 11, 2026
Tagged with: