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

Maintaining consistency in distributed systems with an effect machine

Maintaining consistency in distributed systems with an effect machine

In the beginning there was nothing. So the programmers said “let there be a monolith”.

And a monolith was created, but it was coupled and not scalable, and the programmers saw that it was not good.

Then they said “let there be microservices”. And microservices were created.

At first it was good, but with microservices came chaos and problems, like: consistency, testing and concurrency issues.

And the programmers thought “What if we could isolate all the side effects leveraging idempotency and functional programming to solve these issues?”.

So they said “let there be a side effect machine”. And a side effect machine was created.

And It was very good. A magic new world was out there.

Marcelo Piva

August 31, 2019
Tweet

More Decks by Marcelo Piva

Other Decks in Programming

Transcript

  1. Why not imperative? (defn block! [card datomic producer] (when (logic.card/can-block?

    card) (let [blocked-card (logic.card/block card)] (datomic.card/update! blocked-card datomic) (producer.card/status-changed! blocked-card producer))))
  2. Why not imperative? (defn block-all! [customer cards datomic producer] (doseq

    [card cards] (block! card datomic producer)) (cards-blocked-notify! customer producer))
  3. Why not imperative? (defn block-all! [customer cards datomic producer] (doseq

    [card cards] (block! card datomic producer)) (cards-blocked-notify! customer producer))
  4. Why not imperative? (defn block-all! [customer cards datomic producer] (doseq

    [card cards] (block! card datomic producer)) (cards-blocked-notify! customer producer))
  5. (defn run-effects! [message] (let [[datoms messages] (handler message) [error db-t]

    (persist! datoms)] (case error :db-error (abort! error) :idempotency-error (send-messages! messages db-t) nil (send-messages! messages db-t))))
  6. (let [[datoms messages] (handler message) [error db-t] (persist! datoms)] (case

    error :db-error (abort! error) :idempotency-error (send-messages! messages db-t) nil (send-messages! messages db-t)))
  7. (let [[datoms messages] (handler message) [error db-t] (persist! datoms)] (case

    error :db-error (abort! error) :idempotency-error (send-messages! messages db-t) nil (send-messages! messages db-t)))
  8. (let [[datoms messages] (handler message) [error db-t] (persist! datoms)] (case

    error :db-error (abort! error) :idempotency-error (send-messages! messages db-t) nil (send-messages! messages db-t)))
  9. (let [[datoms messages] (handler message) [error db-t] (persist! datoms)] (case

    error :db-error (abort! error) :idempotency-error (send-messages! messages db-t) nil (send-messages! messages db-t)))
  10. (let [[datoms messages] (handler message) [error db-t] (persist! datoms)] (case

    error :db-error (abort! error) :idempotency-error (send-messages! messages db-t) nil (send-messages! messages db-t)))
  11. (let [[datoms messages] (handler message) [error db-t] (persist! datoms)] (case

    error :db-error (abort! error) :idempotency-error (send-messages! messages db-t) nil (send-messages! messages db-t)))
  12. (let [[datoms messages] (handler message) [error db-t] (persist! datoms)] (case

    error :db-error (abort! error) :idempotency-error (send-messages! messages db-t) nil (send-messages! messages db-t)))
  13. {:effects/datoms [{:db/id [:card/id "some-uuid"] :card/status :status/blocked}] :effects/requests [{:url :some-url :method

    :post}] :effects/messages [{:topic :card-status-changed :message {:card/status :status/blocked}}]}
  14. {:effects/datoms [{:db/id [:card/id "some-uuid"] :card/status :status/blocked}] :effects/requests [{:url :some-url :method

    :post}] :effects/messages [{:topic :card-status-changed :message {:card/status :status/blocked}}]}
  15. (s/defschema BlockCard {:messages [(s/one StatusChanged)] :datoms [(s/one BlockedCard)]}) (defn block

    :- (s/either t.effects/BlockCard t.effects/Empty) [card] (if (logic.card/can-block? card) (let [blocked-card (logic.card/block card)] (-> e/empty-effects (effect.card/update blocked-card) (effect.card/status-changed blocked-card)))) e/empty-effects))
  16. (defn block-all [customer cards] (-> (apply e/merge (map block cards))

    (effect.card/card-blocked-notify customer))) Easier to compose
  17. (deftest block-card-test (is (= {:effects/datoms [{:db/id [:card/id "id"] :card/status :card/blocked}]}

    :effects/messages [{:topic :card-status-changed :message {:card/status :status/blocked}}] (block card)))) Easier to test
  18. (defn block [card] (if (logic.card/can-block? card) (let [blocked-card (logic.card/block card)]

    (-> e/empty-effects (effect.card/update blocked-card) (effect.card/status-changed blocked-card)))) e/empty-effects))
  19. (defn block [card] (if (logic.card/can-block? card) (let [blocked-card (logic.card/block card)]

    (-> e/empty-effects (effect.card/update blocked-card) (effect.card/status-changed blocked-card)))) e/empty-effects))
  20. (s/defn run-effects! [effects :- Effects+Id components] (-> effects add-idempotency-check (run-datoms!

    components) (run-requests! components) after-run-requests (run-messages! components)))