Skip to content

Data Migration

Since Manul objects are persistent, you need to consider to how to migrate existing data when class is changed. In tranditional language, migration is a tideous and risky. In Manul, it’s made simple and safe.

Suppose you have a product class:

class Product(
var name: string,
var price: double
)

Now suppose we want to add a stock field to the class:

class Product(
var name: string,
var price: double,
var stock: int
)

In this case, you don’t need to do anything because Manul automatically the stock field of existing products with 0.

Then, we want to handle a more complex case, we want to add a status field.

enum Category {
ELECTROINUCS,
CLOTHING,
OTHERS
}
class Product(
var name: string,
var price: double,
var stock: int,
var category: Category
)

If you try to deploy the code above, the server will reject it because it doesn’t know how to fill the category field in existing products. To solve the problem, you need to declare an initializer function:

class Product(
var name: string,
var price: double,
var stock: int,
var category: Category
) {
fn __category__() -> Category {
return Category.OTHERS
}
}

The initializer function must be named __{field}__ and returns the initial value.

Suppose we need to support multi-currency in the system and the price field need to change to a Money type.

enum Currency {
USD,
EURO,
CNY,
}
value class Money(
val amount: price,
val currency: Currency
)
class Product(
var name: string,
var price: Money,
var stock: int,
var category: Category
)

Deploying this code directly would lead to failure because the server needs to know how to handle the type conversion for the price field. You need to provide a converter function:

class Product(
var name: string,
var price: Money,
var stock: int,
var category: Category
) {
fn __price__(price: double) -> Money {
return Money(price, Currency.USD)
}
}

The converter function has name __{field}__ and a single parameter with type of the original field type and returns a value of the new type.

Suppose we need a to a major refactor of the Product class by introducing SKU:

class Product(
var name: string,
var category: Category
) {
class SKU(
var variant: string,
var price: Money,
var stock: int
)
}

In the new design, a product can have multiple variants, each represented by an SKU child object. Additionally, price and stock are moved the SKU object.

Deploying this new code will NOT lead to rejection. However it causes data lose because the price and stock fields will be gone.

To solve this issue, we need to add a run function to creates an SKU that stores the original stock and price for each existing product:

class Product(
var name: string,
var category: Category
) {
priv deleted price: Money?
priv deleted stock: int
class SKU(
var variant: string,
var price: Money,
var stock: int
)
fn __run__() {
if (children.length == 0) {
SKU(price!!, stock!!)
}
}
}

Note that we created two deleted fields in Product that stores the original values of the removed fields.

In traditional languages, when an update involves schema change is published, rollback is nearly impossible because rollback code is easy but rollback data is extremely hard.

In Manul, rollback is is achieved with a simple command manul revert which rollback both the code and the data.