Upgrade to Pro — share decks privately, control downloads, hide ads and more …

States (DevOps Sydney Mixer, 2013)

Rob Howard
December 11, 2013

States (DevOps Sydney Mixer, 2013)

Thinking about an Order model as states and possible transitions; Finite State Machines made easy.

Rob Howard

December 11, 2013
Tweet

More Decks by Rob Howard

Other Decks in Technology

Transcript

  1. States Avoiding if order.status == ‘placed’ || (order.status == ‘paid’

    && order.has_refunds_owing?); raise HelpLetMeOutOfHereError Hi! I’m Rob Howard, and I’m here to talk to you about States, or Finite State Machines. It’s not as boring as it sounds. :-)
  2. created_at 2013-11-22 09:00:01 paid_at 2013-11-22 09:00:01 cancelled_at NULL shipped_at NULL

    Okay, who’s written their own Shopping Cart app? Worked on one? Cool. Let’s think of an Order record. Has anyone seen anything like this?
  3. created_at 2013-11-22 09:00:01 paid_at 2013-11-22 09:00:01 cancelled_at NULL shipped_at NULL

    Okay, good. Now, let’s try to guess what the above represents.
  4. created_at 2013-11-22 09:00:01 paid_at 2013-11-22 09:00:01 cancelled_at NULL shipped_at NULL

    ... Paid? ... I’m going with Paid. I think. It’s been created and paid for.
  5. ... Cancelled? created_at 2013-11-22 09:00:01 paid_at 2013-11-22 09:00:01 cancelled_at 2013-11-24

    08:10:01 shipped_at NULL ... Cancelled. Probably. Created, then paid for, then cancelled. (We’re making assumptions about the order the above all happened in.)
  6. created_at 2013-11-22 09:00:01 paid_at NULL cancelled_at NULL shipped_at 2013-11-22 09:00:01

    WHAT. Plainly something got all screwed up. Created and shipped with no payment date?
  7. created_at 2013-11-22 09:00:01 paid_at NULL cancelled_at NULL shipped_at NULL ...

    as STATES, like so. An Order can be Created, Paid, Cancelled or Shipped. Let’s draw some pictures
  8. Placed Paid Mark as Paid We can move to “Paid”,

    by “Marking as Paid”. This “Mark as Paid” is an event or a Transition.
  9. Placed Paid Shipped Shipped Mark as Paid Ship Similarly, we

    can “Ship” an order, transitioning to “Shipped”. In this system, this is an Accepting (or Final) State. The Order cannot and will not transition to any other state from this one. (We indicate this with the double-circle.)
  10. Placed Paid Shipped Shipped Mark as Paid Ship Note that

    you can’t jump from “Placed” to “Shipped”. We’re modeling the possible transitions.
  11. Placed Paid Shipped Shipped Mark as Paid Ship Just like

    a CMS page in a mandatory-review system can’t go from “Draft” to “Published” without an intermediary “Reviewed” state, in this system we can’t go to “Shipped” from “Placed” without “Paid” in the middle.
  12. Placed Paid Shipped Cancelled Shipped Shipped Mark as Paid Cancel

    Cancel Ship From “Placed”, we can also “Cancel”. Notice that we can transition to “Cancelled” from two states, “Placed” and “Paid”. This is important later.
  13. status “paid” You might be asking why we didn’t just

    have a “status” database column earlier. We can!
  14. status “paid” “shipped” “cancelled” We can keep a history by

    automatically creating an audit record whenever any transition happens.
  15. status “paid” “shipped” “cancelled” This greatly simplifies the model; instead

    of having the audit record being created in a model’s save() method (with conditional logic to check if it’s a state transition), we instead have the State Machine component of the model figure out when it needs to dump out a record.
  16. class Order include Workflow workflow do state :placed do event

    :cancel, transition_to: :cancelled event :mark_as_paid, transition_to: :paid end state :paid do event :cancel, transition_to: :cancelled event :ship, transition_to: :shipped end state :cancelled state :shipped end def mark_as_paid halt! 'No valid payment found' if payment_owed? end def cancel halt! 'Purchaser still owed' if refunds_owed? end #... Here’s an example of a state machine library in use. It’s Ruby, but you don’t need to really understand it to get what’s going on. Note the list of states inside workflow do ... end.
  17. class Order include Workflow workflow do state :placed do event

    :cancel, transition_to: :cancelled event :mark_as_paid, transition_to: :paid end state :paid do event :cancel, transition_to: :cancelled event :ship, transition_to: :shipped end state :cancelled state :shipped end def mark_as_paid halt! 'No valid payment found' if payment_owed? end def cancel halt! 'Purchaser still owed' if refunds_owed? end #... First, the “Placed” state, with two possible transitions: “cancel”, or “mark_as_paid”. It has a check (see def mark_as_paid) that prevents the transition from even happening if the conditions aren’t met.
  18. class Order include Workflow workflow do state :placed do event

    :cancel, transition_to: :cancelled event :mark_as_paid, transition_to: :paid end state :paid do event :cancel, transition_to: :cancelled event :ship, transition_to: :shipped end state :cancelled state :shipped end def mark_as_paid halt! 'No valid payment found' if payment_owed? end def cancel halt! 'Purchaser still owed' if refunds_owed? end #... Same with “Paid”. We can’t “Cancel” without having refunded the customer everything they’re owed first.
  19. class Order include Workflow workflow do state :placed do event

    :cancel, transition_to: :cancelled event :mark_as_paid, transition_to: :paid end state :paid do event :cancel, transition_to: :cancelled event :ship, transition_to: :shipped end state :cancelled state :shipped end def mark_as_paid halt! 'No valid payment found' if payment_owed? end def cancel halt! 'Purchaser still owed' if refunds_owed? end #... “Cancelled” and “Shipped” are our Accept/Final States. Neither have any way to transition back to other states. Let’s go back to the diagram.
  20. Placed Paid Shipped Cancelled Shipped Shipped Mark as Paid Cancel

    Cancel Ship By paying attention to the possible transitions, we can worry more about what we can do rather than the current state the Order is in.
  21. PHP Finite, FSM Ruby workflow, state_machine There are libraries for

    pretty much every language. It’s a concept, not a specific library, but these certainly make build FSMful models a lot easier.