Thinking in data &amp; functions just takes some practice. Traditionally imperative code can often be reimagined in terms of reducing over a command sequence, or interpreting some more sophisticated representation. The benefits for implementation, testing, debugging, refactoring, etc can be massive.<p>Consider your chain of examples for &quot;Upload something to S3, if X then call some web service, otherwise log and call something else, etc etc etc.&quot; You could represent this as a sequence of steps which could be interpreted by a central side-effecting loop. Let&#x27;s call it an &quot;action plan&quot;, in this case for your &quot;upload&quot; operation:<p><pre><code>    (defn upload-plan [req]
      [{:action :upload
        :file (get-in req [:params :doc])
        :max-size 2000
        :name :doc}
       {:action :notify
        :message (fn [state] (get-in state [:data :doc]))
        :target [:somebody]}])
</code></pre>
Now you can process this plan centrally:<p><pre><code>    (defmulti take-action (fn [state {:keys [action]}] action))

    (defmethod take-action :upload ...)
    (defmethod take-action :notify ...)

    (defn execute [plan]
      (let [{:keys [status]} (reduce take-action {:status :ok, :data {}} plan)]
        (when (not= status :ok)
          (log &quot;omg!&quot;))))
</code></pre>
While this is initially a tad more verbose, there&#x27;s great reasons to do this sort of thing.<p>For example, now have a central place to mock all side effects! You could substitute a different execute method during testing, or automatically log the &quot;action&quot; object that failed.<p>More importantly, you can develop each of your &quot;plan&quot; functions independently. You can reuse them, re-order them, etc.<p>You can also do hypotheticals, for example: &quot;Which plan would cause less network round trips?&quot; can be answered before committing to a plan.<p>You can _record_ plans. Instrument your execute method to record and save plans to a file, then you can just check them right in as test cases.<p>You can optimize plans. If five different functions all notify the same target of the same thing, your central execution method can filter subsequent calls. Or you could separate your &quot;optimization&quot; from your &quot;interpretation&quot; and you can ask &quot;After accounting for idempotence, which operations will be performed?&quot;<p>The list goes on and on...