Replies: 1 comment
-
Follow these rules and you will be fine
Think of the commands recorded in the journal as facts/events that have already happened. It does not make sense to change the type definition of past events. On the other hand, changing the interpretation of a command (the mutation it performs on the model) will allow you to change the schema of the in-memory model Here's a silly but hopefully useful example: //an entity class in version 1.0 of our domain model
public class Product
{
public string Description{get;set;}
public string Name{get;set;}
public int Id {get;set;}
} Don't put the entity directly in the command (rule 2 above): public class BAD_RegisterProduct : Command<ProductsModel>
{
// Bad because we don't want the entity itself serialized to the journal
public Product Product {get; set;}
public override void Execute(ProductsModel model) => model.Register(Product);
} Instead, we should create the entity from parameters during restore: public class RegisterProduct : Command<ProductsModel>
{
//Better because we only persist the inputs required to create a product entity
public readonly int Id;
public readonly string Name;
public readonly string Description;
//call the Product constructor during replay
public void Execute(ProductModel model) => model.Register(new Product(Id, Name, Description));
} With this approach we won't break any serialization but we do have to deal with future schema changes. Let's introduce a change. We have over 4 billion products and will soon run out of 32-bit product ids. It has been decided that we shall use strings. New products will be assigned a random string based on a guid while existing products will have their id converted to a string. Let's see what version 2 of our domain library looks like: public class Product
{
//id field is now a string!
public string Id {get;set;}
//remaining properties go here, same as before...
}
// a new command class with string id instead of int (rule 1 above)
// pretty much the same as before
public class RegisterProductv2 : Command<ProductsModel>
{
public string Id {get;set;}
public string Name {get;set;}
public string Description{get;set;}
public override void Execute(ProductsModel model) => model.Register(new Product(Id, Name, Description));
} Here's the key change, the old command needs to be updated to produce entities with string ids. This is done in the Execute method and does not affect the persistence/serialization at all: public class RegisterProduct : Command<ProductsModel>
{
public int Id {get;set;}
public string Name {get;set;}
public string Description{get;set;}
public override void Execute(ProductsModel model) => model.Register(new Product(Id.ToString(), Name, Description));
} This is a trivial example but it's the approach we use and have found most successful. |
Beta Was this translation helpful? Give feedback.
-
It would be great if there was documentation in data type migration. What happens if I change my type? How would journal restore look like? What should I keep in mind when changing types in the future? Should I explicitly write export / import?
Beta Was this translation helpful? Give feedback.
All reactions