The structure of CRUD apps

Dustin Getz 2021 June https://twitter.com/dustingetz

Here is a thought experiment to help us understand the essence of a CRUD app. The following pseudocode is the approximate structure, with queries, views, and local state in all the essential places with all the essential interdependencies. The problem is that the code is single-process – e.g. it all runs on the server, or it all runs on the client, and lacks the operational properties of a real CRUD app. The goal is to golf this pseudocode into an operational frontend/backend crud app with backend database, network, efficient rendering and dom maintenance. While introducing as little accidental complexity as possible. How close to the original structure can you get without changing the flow of data? Where is compositional power lost or damaged?

(defn select-widget [& {:keys [value on-change options]}]
  (let [!needle (atom "")]
    [:<> [:input.select-needle {:value value :on-change #(reset! !needle %)}]
     [:select {:selected value :on-change on-change}
      (options @!needle)]]))

(defn email-taken [db email]
  (some? (d/q '[:in ?x $ :find ?e . :where [?e :person/email ?x]] db email)))

(defn example-form [db e]
  (let [!e (d/entity db e)
        !email (atom (:person/email !e))
        !shirt-size (atom (:person/shirt-size !e))]
    [:form
     [:div [:label "email"]
      [:input {:class (if (let [x (:person/email !e)] (email-taken db x)) "invalid" "valid")
               :value @!email :on-change #(reset! !email %)}]]

     [:div [:label "shirt-size"]
      (select-widget
        :value @!shirt-size :on-change #(reset! !shirt-size %)
        :options (fn [needle]
                   (let [gender (:person/gender (datomic.api/entity db e))
                         es (datomic.api/q
                              '[:in $ ?gender :find [?e ...] :where
                                [?e :person/type :person/shirt-size]
                                [?e :person/gender ?gender]]
                              db gender (or needle ""))]
                     (for [e es]
                       [:option (:db/ident (datomic.api/entity db e))]))))]]))
This pseudocode frontend queries the database in a way that is not physically possible due to the network between them. For example, the email field input validator queries the database to see if the email is already taken. The select widget options also query the database inside a for loop that must be efficiently rendered.

The stand-out feature is that — in the specification, and in our minds — query and view compose directly. The usual code constructs make sense, like functions and closures. Operational implementations mostly destroy or damage this property.

When composition is achieved, like with a client-side database, it comes at the cost of something important. The client/server network divide is of course the root of the problem.
When composition is achieved, like with a client-side database, it comes at the cost of something important. The client/server network divide is of course the root of the problem.

So how might we design a solution that has all of these properties at the same time?

Hint: homoiconicity. That's not code, that's a spec. What could we compile it to?