-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Guards or how to fail a transition #260
Comments
Hi @jeffutter, That's a good question. I would probably do something like this, class Account {
amount = Number;
withdraw(value) {
return this.amount.decrement(value);
}
}
class Error {
message = String;
}
class CashRegister {
balance = Account
error = Error
restrictedWithdrawl(amount) {
if (this.balance.amount.state < amount) {
return this.error.message.set(`You don't have enough money for this`);
} else {
return this.balance.withdraw(amount);
}
}
} There are a few things to consider here,
What do you think about this solution? |
Throwing an error would be one way. You could also have a computed property (which is kinda like your "valid.state") class Balance {
get isValid() { return this.amount.state >= 0; }
} another would be to make it a no-op: withdraw(amount) {
let hypothetical = this.amount.decrement(-1*amount);
if (this.balance.isValid) {
return hypothetical;
} else {
return this;
} Notice how because microstates are immutable we can invoke transitions to see what the result would be given the current arguments before deciding if that's what we actually want to do. Still another way, and perhaps the most micro-state-y, state-machine-y way would be to use a "union" type to represent the different possible states. class Balance {
amount = Number;
initialize(value) {
let amount = Number
if (amount < 0) {
return create(NegativeBalance, value);
else {
return create(PositiveBalance, value);
}
}
deposit(amount) {
return this.amount.increment(amount);
}
}
class PositiveBalance extends Balance {
withdraw(amount) {
return this.amount.decrement(amount);
}
}
class NegativeBalance extends Balance {
//there is no withdraw method at all.
}
let balance = create(Balance, 10);
balance.withdraw(5); //=> PositiveBalance
balance = balance.withdraw(10) //=> NegativeBalance
balance = balance.withdraw(20) //=> TypeError: balance.withdraw is not a function This is cumbersome at the moment, but we have plans to add some functions to define these union types more succicntly. For now though, you have to do it manually. The pattern is:
We're not entirely sure what the syntax will be, but something along the lines of: import { Union } from 'microstates';
const Balance = Union({
positive: class PositiveBalance {
//positive definition goes here.
},
negative: class NegativeBalance {
//negative definition goes here.
}); |
Btw, I really like, @taras's solution here. |
In traditional 'state machines' usually, there is a way to disallow a transition. Perhaps based on business rules, consider the example of a cash register state machine.
You may have a cash register that starts with $100 in it. You may have a 'withdraw' action that lets you take money out. How would you prevent the following scenario:
How do you handle this with microstates? I can possibly think of a couple solutions.
1.) Throw an error. I know it's often not recommended to throw unless it is a truly unexpected situation.
2.) Set some other sub-state to invalid. After every withdrawal, you could check something like
updatedRegister.valid.state
. Internally the 'withdraw' action would update the 'valid' sub-state when you overdraw.Is there a more micro-statey way to address this?
The text was updated successfully, but these errors were encountered: