$30 off During Our Annual Pro Sale. View Details »

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. :-)

    View Slide

  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?

    View Slide

  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.

    View Slide

  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.

    View Slide

  5. 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
    Okay, what about this?

    View Slide

  6. ... 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.)

    View Slide

  7. created_at 2013-11-22 09:00:01
    paid_at NULL
    cancelled_at NULL
    shipped_at 2013-11-22 09:00:01
    Now what about... Wait...

    View Slide

  8. 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?

    View Slide

  9. created_at 2013-11-22 09:00:01
    paid_at NULL
    cancelled_at NULL
    shipped_at NULL
    In any case, we can think of all these attributes...

    View Slide

  10. 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

    View Slide

  11. Placed
    This is “Placed”, our first state (or Starting State).

    View Slide

  12. Placed
    Paid
    Mark as Paid
    We can move to “Paid”, by “Marking as Paid”.
    This “Mark as Paid” is an event or a Transition.

    View Slide

  13. 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.)

    View Slide

  14. Placed
    Paid
    Shipped
    Shipped
    Mark as Paid
    Ship
    Note that you can’t jump from “Placed” to “Shipped”. We’re modeling the possible transitions.

    View Slide

  15. 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.

    View Slide

  16. 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.

    View Slide

  17. status “paid”
    You might be asking why we didn’t just have a “status” database column earlier.
    We can!

    View Slide

  18. status “paid”
    “shipped”
    “cancelled”
    That status column can contain “paid”, “shipped”, “cancelled”...

    View Slide

  19. status “paid”
    “shipped”
    “cancelled”
    We can keep a history by automatically creating an audit record whenever any transition
    happens.

    View Slide

  20. 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.

    View Slide

  21. 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.

    View Slide

  22. 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.

    View Slide

  23. 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.

    View Slide

  24. 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.

    View Slide

  25. 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.

    View Slide

  26. Placed
    Paid
    Shipped
    Cancelled
    Shipped
    Shipped
    Mark as Paid
    Cancel
    Cancel
    Ship

    View Slide

  27. if order.status == ‘placed’
    || (order.status == ‘paid’
    && !order.has_refunds_owing?)
    order.cancel!
    end
    And so this...

    View Slide

  28. if order.can_cancel?
    order.cancel!
    end
    ... Becomes this.

    View Slide

  29. View Slide

  30. 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.

    View Slide

  31. Fin.
    @damncabbage
    http://robhoward.id.au
    Thanks. :-)

    View Slide