-
Notifications
You must be signed in to change notification settings - Fork 56
Description
If you try to set a durable entity state to a non-serializable object, for example a rich class, the methods of that class would not be available and your entity/orchestration would fail with an error. The simplest example of this is the ShoppingEntity
/ShoppingOrchestration
example in the samples/
directory:
ShoppingEntity/index.ts
:
import * as df from "durable-functions";
import { CartItem, ShoppingCart, Operations } from "../Shopping/data";
module.exports = df.entity<ShoppingCart>(function (context) {
const cart = context.df.getState(() => new ShoppingCart());
switch (context.df.operationName) {
case Operations.ADD_ITEM:
const cartItem = context.df.getInput<CartItem>();
cart.addItem(cartItem);
break;
case Operations.REMOVE_ITEM:
const itemToRemove = context.df.getInput<string>();
cart.remoteItem(itemToRemove);
break;
case Operations.GET_VALUE:
const value = cart.getCartValue();
context.df.return(value);
break;
}
context.df.setState(cart);
});
ShoppingOrchestration/index.ts
:
import * as df from "durable-functions";
import { items, Operations } from "../Shopping/data";
module.exports = df.orchestrator(function* (context) {
const entityId = new df.EntityId("ShoppingEntity", "shoppingCart");
yield context.df.callEntity(entityId, Operations.ADD_ITEM, {
itemId: items[0].itemId,
quantity: 1,
});
yield context.df.callEntity(entityId, Operations.ADD_ITEM, {
itemId: items[2].itemId,
quantity: 2,
});
yield context.df.callEntity(entityId, Operations.ADD_ITEM, {
itemId: items[1].itemId,
quantity: 10,
});
const cartValue: number = yield context.df.callEntity(
entityId,
Operations.GET_VALUE
);
console.log(cartValue);
});
ShoppingCart
class definition:
export class ShoppingCart {
#items: CartItem[];
constructor() {
this.#items = [];
}
public addItem(item: CartItem): void {
this.#items.push(item);
}
public remoteItem(itemId: string): void {
const index = this.#items.findIndex((item) => item.itemId === itemId);
this.#items = this.#items.slice(index, index + 1);
}
public getCartValue(): number {
return this.#items.reduce(
(value, item) =>
value + item.quantity * items.find((i) => i.itemId === item.itemId).price,
0
);
}
}
Attempting to call the above ShoppingOrchestration
orchestration actually doesn't work, and fails because cart.addItem()
isn't a function (it is lost after serialization/deserialization). For obvious reasons, entity states can only be JSON-serializable values, since they have to be serialized/deserialized to be written to storage.
What I would recommend:
- Make sure we clearly document this limitation about entities
- Remove the shopping sample from our repo.
Alternatively, we could try to think about how we could make a rich class use-case work. For example, we could have a way for a user to provide a class constructor to the state initializer, and call that constructor every time we retrieve the state. This may still not cover every use-case though. More investigation and design would be required to see what makes sense here and if it's worth it.