Recently, I found myself in a position where an application was heavily reliant on a state object. This is fairly typical for single page applications (SPAs) and can pose a challenge when your state object’s schema changes significantly and you have users that have data saved under an old schema.
In this post, I’ll explore a proof-of-concept solution I put together to explore the topic. While I’m sure there are schema migration tools in existence already, I figured this would be an interesting and educational exploration of the topic!
Let’s say I’ve created an app in which there’s a user and that user can enter their pet type and breed. Upon launching the MVP, my state object looks something like this:
const state = {
person: {
name: 'Edgar',
pets: {
type: 'dog',
name: 'Daffodil',
},
},
};
This works great for the MVP, but soon I realize I don’t want the pets
property to live under the person
property, but rather I want it to be it’s own propert under state
. In other words, my ideal state might look like this:
const state = {
person: {
name: 'Edgar',
},
pets: {
type: 'dog',
name: 'Daffodil',
},
};
While I’d like to simply be able to make this change in my SPA, I’m concerned that existing app users have my original schema saved somewhere (e.g., local storage, nosql, a JSON string, etc.). If I load that old data but my app expects the new schema, I may try to access properties in the wrong place (e.g., state.pets.type
versus state.person.pets.type
), causing issues.
Schema migration isn’t a new concept; it’s been used for quite some time to migrate database tables between different versions of applications. In this post, I’m going to use the same basic concepts behind schema migrations to migrate JavaScript objects.
Let’s define an array of migrations to run. Each migration will have a from
, to
, up
, and down
property. The from
and to
props will represent the lower and higher version respectively, and the up
and down
props will be functions that move a schema from the from
version to the to
version and vice-versa. That may sound a bit confusing, but I think it’ll make a bit more sense in the context of our person/pets example.
Let’s write the first migration.
const migrations = [
{
from: '1.0',
to: '1.1',
up: schema => {
const newSchema = {
version: '1.1',
person: {
name: schema.person.name,
},
pets: {
...schema.person.pets,
},
};
return newSchema;
},
down: schema => {
const newSchema = {
version: '1.0',
person: {
...schema.person,
pets: { ...schema.pets },
},
};
return newSchema;
},
},
];
If we have a version “1.0” schema, the up
method of this object will convert that schema to “1.1”. Conversely, if we have a version “1.1” schema, the down
method will convert that schema to “1.0”.
#javascript #programming