{
"$type": "site.standard.document",
"canonicalUrl": "https://build.ms/2022/6/22/model-view-controller-store/",
"description": "Introducing Boutique, a new architecture that reimagines MVC for SwiftUI by adding a Store layer to handle state management and data flow in a familiar, approachable way.",
"path": "/2022/6/22/model-view-controller-store/",
"publishedAt": "2022-06-22T12:00:00.000Z",
"site": "at://did:plc:b6eke66r3vbmnegg73qgprl6/site.standard.publication/3mmypfmg4sx2d",
"tags": [
"Databases",
"Engineering",
"iOS",
"Open Source",
"Swift",
"SwiftUI"
],
"textContent": "This Twitter thread offers a concise high level 13-tweet summary of this post's announcements, but you miss out on a lot of important detail that I highly recommend reading if you plan to start using the libraries I've developed and introduce in this post, or if you'd like to read an interesting technical walkthrough.\n\nThis post was updated on August 22, 2022 to reflect the v2 release of Boutique and related API changes.\n\n---\n\nApple has never provided a blessed architecture for SwiftUI, and many developers have spent thousands of hours filling the gaps with their own ideas. A familiar approach for many developers is to take the MVVM pattern many people adopted in their UIKit/AppKit apps and translate it to the needs of SwiftUI. That can work well enough, but you begin to see some cracks in the architecture when you need to manage state and data flow due to how heavily SwiftUI leans on having a single source of truth. Others have taken the path of integrating powerful libraries such as The Composable Architecture which provide you with the tools to reason about your entire application. TCA takes inspiration from redux, more specifically The Elm Architecture, two patterns that are rather incredible in how they allow you to define your entire application as a tree of state. But TCA's great power comes with ~~great responsibility~~ a very high learning curve, which can make it difficult to learn personally. TCA's goals are much more of a fit for solving problems with a very high level of complexity, which may not be necessary for every app.\n\nWhen it comes to developer ergonomics and program provability MVVM and TCA live on opposite ends of the spectrum. MVVM is lenient and depends on convention while TCA apps are built with rigidity in mind to guarantee correctness. This post isn't meant to be a tour of architectures though, I've mentioned those two patterns to show that there's a spectrum of how we develop software, and I believe there's room for something in between.\n\n---\n\nIf SwiftUI shows us anything it's that declarative programming is the future of software development, and we need an approach to software development that leans into the strengths of the paradigm without being overly constrictive. What's needed isn't a whole new architecture, but to bring together tried and true patterns with a new concept, the Store. <!--preview-snippet--> The Store is a very minimal yet powerful layer, which means the rest of your application's code can remain almost entirely unchanged. Your apps are yours, they are your expression of creativity, and you shouldn't have to change how you write code to turn your ideas into software.\n\nThe Store will be the heart of your app's data, and thanks to the declarative nature of SwiftUI an app's user interface is driven by its data. You can think the Store as the storage for your model objects, in SwiftUI this would be your single source of truth. If you model your data correctly then your user interface will always do what you expect it to do. That relationship between data and user interface is why Views having a single source of truth is so important.\n\nI've built a batteries-included Store that comes with everything you'll need out of the box called Boutique to be the foundation for that data. Boutique does no behind the scenes magic and doesn't resort to shenanigans like runtime hacking to achieve a great developer experience. Our Store is extremely simple and has no complicated abstractions, you'll be surprised how little there is to learn. There entire API surface is only one public property, an array of items, and only three functions, add(), remove(), and removeAll(). Once you experience Boutique it becomes a must-have feature for any state-driven app you build. We'll dive much deeper into how the Store works under the hood to make this possible later in this post, build a Model View Controller Store app to see how it all comes together.\n\nBoutique's Store is a dual-layered memory and disk cache which lets you build apps that update in real time with full offline storage and an incredibly simple API in only a few lines of code. That may sound a bit fancy, but all it means is that when you save an item into the Store, it also saves that item to disk. This persistence is powered under the hood by Bodega, an actor-based library I've developed for saving data or Codable objects to disk. In this post we'll use Boutique to show how the Store integrates into your apps.\n\nThe pattern and concepts are familiar and straightforward whether you've been a programmer for a few months or have been writing code for decades. After integrating this architecture into multiple apps without a hitch over the last few months I feel confident sharing it, and open sourcing the libraries that make it easy to build realtime offline-ready apps with just a few lines of code.\n\nModel View Controller Store\n\nModel View Controller Store is a rethinking of an architecture familiar to iOS developers MVC, for SwiftUI. While this post discusses SwiftUI, all of these techniques are applicable for building a UIKit or AppKit app. You can build apps across many platforms using this pattern, in fact the C from MVCS is inspired by Data Controllers rather than ViewControllers, but in this post we'll focus on SwiftUI. Your app's Models and Views will require no changes to your mental model when you think about a Model or View in a SwiftUI app, but we'll be adding the Store to our apps. The combination of MVC and a Store bound together by a simple API allows a developer to give their app a straightforward and well-defined data architecture to create an app that's incredibly easy to reason about.\n\nThe best way to explain Model View Controller Store is to show you what it is. The idea is so small that you may even be an expert before this post is over, there's actually very little to learn. Model View Controller Store doesn't require you to change your apps, and I'm going to use Boutique for the Store in our app.\n\nYou may prefer use Boutique on its own without Controllers, Model View Store is a perfectly acceptable architecture that some people will prefer. While I prefer having a data controller to mediate data and interactions between different parts of the app, especially as apps get more complex, it's not a prerequisite to using Boutique's Store in your app. We'll touch on using Boutique to create a Model View Store architecture in the Store section of the post, and discuss what the Store can bring to any app.\n\n---\n\nLet's Build An App\n\nTo demonstrate Model View Controller Store in practice we'll walk through a simple app that's representative of most apps people work on. You can find the full source code available on Github, but we'll also walk through a condensed version below.\n\n<video autoplay controls playsinline muted loop style=\"height: 500px; border-radius: 36px; display: block; margin-left: auto; margin-right: auto;\">\n <source src=\"./app-demo.mov\" type=\"video/mp4\">\n</video>\n<br/>\n\nThis app will allow us to\n\n1. Query an API for a red panda image.\n2. Favorite the red panda the API sent us, saving the image and associated metadata to our Store.\n3. See that when we save the red panda to our Store, the state changes are rendered across two separate views.\n4. Remove a red panda, deleting the image and associated metadata from our Store.\n5. See that when we remove the red panda to our Store the state change is rendered due to the state change.\n6. If we relaunch the app all of our favorited red pandas will be there because the Store not only saves data in-memory but also cached on disk.\n\n---\n\nModel\n\nYou can assume our app has a RemoteImage model to represent a remote image resource, we'll use it when we fetch and save our red panda images.\n\n<br/>\n\n---\n\nStore\n\nWe'll create our Store, using the Store provided by Boutique, though if you'd like to build your own it's possible too.[^1] The Store has a readonly array of items and only three operations to add(), remove(), and removeAll(). What that means is modeling our data is easy, and our views will always render what we expect due to that data being well-modeled. The Store's items property is a @Published property, and because of that our Views update in realtime without having to build any observable behaviors, it's almost magical to watch. \n\nTo create a Store all you need is a storage and a cacheIdentifier.\n\n- The storage parameter is of the type StorageEngine, a data storage mechanism provided out of the box by Bodega. A StorageEngine can be a database, but it doesn't have to be. Bodega provides two built-in StorageEngine options, DiskStorageEngine and SQLiteStorageEngine. DiskStorageEngine works by saving your data as files to the file system, and SQLiteStorageEngine creates an SQLite database as the underlying storage mechanism. If you use either of these you'll never have to think about how the data is being stored, but Boutique v2 introduces the concept of BYOD (Bring Your Own Database), allowing you to build your own StorageEngine. You can create a CoreDataStorageEngine, a RealmStorageEngine, or even a CloudKitStorageEngine that matches your app's backend schema, the possibilities are endless. Bodega's documentation provides much more context, but for the purposes of our app we'll use the suggested default, SQLiteStorageEngine.\n\n The cacheIdentifier is a KeyPath<Model, String> that your model must provide. That may seem unconventional at first, so let's break it down. Much like how protocols enforce a contract, the KeyPath is doing the same for our model. To be added to our Store and saved to disk our models must conform to Codable & Equatable, both of which are reasonable constraints given the data has to be serializable and searchable. But what we're trying to avoid is making our models have to conform to a specialized caching protocol, we want to be able to save any ol' object you already have in your app. Instead of creating a protocol like Storable, we instead as",
"title": "Model View Controller Store: Reinventing MVC for SwiftUI with Boutique"
}