We recently released a brand new version of our SharingGRDB library that provides a suite of tools that provide a viable alternative to using SwiftData. The tools are ergonomic, performant, and give you direct access to the SQLite database powering your app’s state without abstracting it from you. And these tools were made possible to another library that we just open sourced called StructuredQueries, which brings a type-safe and schema-safe Swift API to constructing SQL statements.
Now it’s time to put all of these tools to the test by building a real world, complex application from scratch that makes heavy use of all of the powers that SQL has to offer. Today we are beginning our new “Modern Persistence” series, and this is the 3rd time we’ve had a “modern” series. Previously we discussed “Modern SwiftUI”, where we showed by putting the upfront work in domain modeling and controlling dependencies allows one to accomplish a lot of powerful things in an app that would otherwise be very difficult. And then we did the same with UIKit in our “Modern UIKit” series.
This time, “Modern Persistence” refers to the best practices in bringing complex persistence and querying strategies into your app. We want to be able to have an external data source be the source of truth for user’s data, such as a SQLite database, but we also want the benefits of:
…holding that data in your features in a seamless manner so that you can perform nuanced logic around that data, and perform updates to the data that are then persisted to the data source.
And ideally there wouldn’t be unnecessary restrictions of where and how we hold onto this data in our features. Some people like to hold onto the data directly in their views or controllers, and others like to keep the data in a separate domain model. We should be free to choose where we want the data, and most likely in real world, complex apps we will probably have a mixture of both.
You also want to be able to listen for changes to the data source so that if someone else writes to the database your features are immediately updated.
And if all of that wasn’t complex enough, we further want to accomplish all of this in a super short, succinct syntax with as few of concepts as possible.
Oh, and one last thing that is important to us and many of our viewers: we want the whole thing to be very testable too.
So, we have some huge challenges ahead of us, and in order to explore these ideas we are going to build a new app from scratch. It will be a reminders app inspired by the default reminders app that comes with iOS. It seems like a simple app to build, but there are some very interesting and complex queries that one needs to be able to execute to implement the app’s features. And luckily for us, during our last series of episodes creating a query builder from scratch we already designed some of the basics types for this reminders app. But don’t worry if you didn’t follow along with those episodes, we will be doing everything from the beginning in this series.
And of course many may be wondering, why aren’t we using SwiftData for this? Isn’t SwiftData a “modern” persistence framework? Well, it definitely is, but there are some drawbacks to using SwiftData that we think we can fix:
First of all, SwiftData is a proprietary Apple technology, and any app built with it is necessarily tied to Apple’s platforms. That may not bother you, but at the end of the day SQLite is a cross platform library installed on billions of devices, and so we would hope it’s possible to create a portable persistence strategy.
SwiftData is also built on top of references types, but typically we prefer to model core domain types as value types since they bring a lot of guarantees and simplicity to the table.
Further, SwiftData can be difficult to test, not only because it is built on reference types, but also because most of its powerful tools only work when used directly in views, and that means you are forced to test with the UI testing framework, which is very slow and very flakey.
And finally, SwiftData so fully abstracts away the SQL from you that it can be very difficult to query for complex subsets of data that are a cinch in SQL. And it’s even possible to write queries in Swift data that crash at runtime but will be compile time errors in our libraries.
So, while SwiftData is a modern and powerful framework, we don’t think it’s for everyone, and our SharingGRDB library offers a lot of the same ergonomics without these caveats. And so that is why we are excited to embark on our “Modern Persistence” series.
Let’s get started!