Recommended sequence for state mutation and persisting state to persistent store

Hi all

I’m scoping out a persistence dependency to use with a TCA prototype and had a question regarding the sequence of state mutation and persistence. There are two options as far as I can see:

  1. New value > write to store via effect > send Action to Store > mutates state

  2. New value > mutate state > Action which persists new value

It’s worth noting the plan is to eventually have multiple devices accessing a common persistent store of data so I’d want mutations to the central repository to be pushed to all connected devices.

Although option 2 would seem to be the more canonical approach (state mutation takes place before Effects), if the app terminates unexpectedly before the persistence takes place there could be data loss. The data being stored is clinical and therefore an accurate persisted record is very important and, as noted above, the single source of truth should (eventually) be the central repository (not decided on the provider here yet). Am I worrying unnecessarily or is there a happy path here?

On a related subject is there any reliable way of detecting an exception/unexpected app termination and persisting unwritten state to store? I’ve implemented some “middleware” that I use to monitor the app lifecycle (as reported by NotificationCenter) but I’m not really sure if that is the correct tool for the job.

1 Like

The way I'm interacting with CoreData is to use a read-only representation in my TCA State. I'm never editing the state manually in the reducer. I send effects to the CoreData stack which I'm also observing. My state is then automatically updated from the observation and I'm only setting the new value in my State. I have then only one source of truth, the CoreData store. It works well in my configuration, as it allows the store to be modified by another process (like CloudKit sync for example).
It's a little more inconvenient than the approach 2), but there are no surprises.

If you don't want or can't write often to the CoreData store, you can also use a temporary stack where you persist the state at high rate. When edition is ending for good, you can persist on the main stack and clear the temporary stack. When you start the app, if there is some data in the temporary stack, you know that something went wrong and maybe try to recover data in some way or another. It depends on your model layout of course. You can also use simple serialization in a file on disk.

About detecting app termination, there are some ways, but I wouldn't bet the CoreData stack is always in shape of saving anything to it at this point.

5 Likes

Thanks for the outline. It’s good to know the strategy is working well for you as it’s likely that I’m going to use this approach on the basis that the clinical information i am working with is critical. Although it’s probably going to incur a performance hit as there’s going to be frequent round trips through the Core Data stack via a fetch request to ensure app state reflects the persistent state, as with all things in life it’s about priorities and compromise.

For now, the plan is to wrap Core Data so the app is ignorant of the persistence framework, passing data to the Store in the form of structs. The hope is that this will keep encapsulation and testability high.

1 Like

Yes, abstracting your storage is a great idea. With CoreData, you dont need to fetch many things. Once you get a grip on your object, you can observe the context for changes (there is a set of notifications for many events, or you can use Combine publishers on the object itself). I would suggest to define a publisher of your object (or its final type) in your storage. That's easy to hook up in TCA (you map it as long lived Effect that sets the value in your root state, and you can then work with it like a regular (read-only) value-type).
For writes, you need to send update requests to your storage, and the new value will pop back into your state via the publisher.
In some way, your CoreData stack looks like a TCA store, with a state and actions.

3 Likes

I'm going to use this setup, thank you for sharing. How do you test modules individually with this setup?

In some way, your CoreData stack looks like a TCA store, with a state and actions.

Thats a very valid observation @tgrapperon . I remember an idea that has been brought up one day in the ReSwift community of using a Realm instance as a de facto store. It has been criticized by team members and ultimately didnt make it to the framework, but maybe the idea of having a bit more abstract/generic approach to the store/state is not actually that bad?

There is a chance, that I’m misunderstanding some fundamental concepts or design decisions, but on the other hand I have an impression, that we’re pretty much dancing around a dogma of having everything always in memory or in some centralized instance of a value type.