I’ve decided not to sweat trying to sell my applications (or even make them available) through the App Store. That is pretty freeing, really. It means I can stop trying to solve everyone’s problems and just focus on solving my problems. Given this new clarity, I’ve been thinking about what my problems really are, and the one that keeps coming up is: unreliable connectivity to the wider world. We’re mitigating the power aspect of that by rebuilding our house off-grid, but the same lines that carry power around here carry the Internet, and that means that cloud services could become unavailable at any time for like, no reason.
For some applications I’ve written, the state isn’t all that important. Converting between unit systems or angular measurements can be ephemeral state that never leaves the device. For others, the state is important but passing the state from session to session gets handled via file, so a file system based synchronization tool is sufficient. However, I’ve got a couple of apps, now, that fit the common model of “one bucket of data per user” that a lot of iOS tutorials assume is the way everything works, and for this model, SwiftData plus iCloud is great: you define your schema in the application’s code, and the frameworks just take care of creating and modifying a local store (SQLite) and a remote store (iCloud) and then if the same user runs the app on two different devices that are actually connected to the Internet, then It Just Works.
This is, frankly, amazing. It is super cool, making this happen with no backend / server code required. It even works when a device temporarily disconnects (cell phone goes into a dead zone, work happens, reconnect). But. The synchronization part only works when the devices can talk to iCloud. Where I live, that can sometimes take a while. Like, days.
Surely, I think, there must be a way to synchronize this data over the local network? Possibly, possibly, but it’s a very different architecture, and it’d be very complex to swap between bouncing through iCloud and doing peer-to-peer stuff. One or the other, really.
Enter PowerSync. This is a package that does the synchronization stuff. It does require a server to be the source of truth and which every client communicates with to send and receive updates. This is logical, and it’s the same logical shape as iCloud, in general. Looking closer reveals just how much stuff the operating system is handling under the covers when you’re using SwiftData + iCloud. Check out the self-hosted server instructions. There’s a database to be created (sure, let’s just call it Postgres and stick it on a Raspberry Pi in a closet with a decent amount of RAM, for now). Then, there’s a MongoDB cache to be created (okay, we’ll run Docker containers and have one for the db, one for the cache, and one for PowerSync). Then there’s going to be (maybe? or maybe it’s running in the same container as PowerSync?) an application server running authentication so that PowerSync knows which user’s data is getting synchronized.
I get it. All this stuff is actually necessary, but with iCloud, Apple just does it for you.
Once you’ve got a local server up and working, though, you integrate the PowerSync client into your app with one of the swell client SDK bundles, and you’re off to self-hosted synchronization land. That’s pretty sweet.
But realistically, now, how much time do I reckon my iPhone, my iPad, and my Mac are going to be out of sync because PG&E has hosed the cable box? Or because a tree just took out the power lines? If I’m actually concerned about it, I already know how to persist SwiftData state into a document and I know how to synchronize documents over the local network using Syncthing, and that doesn’t require any Raspberry Pi running any number of Docker containers.
I think, if I were still looking at doing web-based applications where the user interacts with a thin client making REST requests, then maybe this would be something I’d look at. After all, once you’ve set up an application server, a load balancer, and a database in a data center somewhere (even if that somewhere is your own attic) then a couple more application servers (cache, synchronization) don’t represent much more overhead. But if, as I have done, you go completely local first, then it’s a bit much.