Laziness can make things better. In this post is a fuzzy idea I had in regards to creating more explicit implementations of business logic models by deferring action.

The Dream

I dream of a life where I develop systems that are entirely self-contained. There’s no reason for a database. There’s no reason for an external cache. Everything is just sitting there visible and efficient. I would model whatever I want using a single language and everything would fit together very neatly.

The closest that I can get to this experience is using an image based language such as Smalltalk or Common Lisp. In these languages, you can build systems that are alive. This alive-ness allows you to live inside the environment in which you’re building and quickly iterate on different ways to model a system.

The Reality

History has left these close approximations of my dreams behind. You can’t escape using some sort of external service. These services might be databases, a cache, or a third-party that you communicate with. At the bare minimum, you will probably be using some sort of database.

Having to communicate with a database makes things a bit awkward when modelling a system. The data you act upon isn’t immediately available for use. If you were building a system without using a database, every component you need would be right there in memory and ready to go. When you work with a database or any sort of persistence, these components are formed by the data that’s stored in the database. You can no longer work with simple variables and will need to upgrade to using functions that fetch the needed information from the database.

When your business logic grows, components that you previously created may fit together if you’re lucky…but a majority of time, this will lead to inefficient code. You’ll be making calls all over the place to get data for different components that need to communicate with one another.

It’s okay to be lazy

Making database calls all over the place is due to eagerness. Trying to compose a system from pre-existing eager components will only multiply this eagerness and lead to inefficiency.

Let’s be lazy instead. Laziness would mean would do the bare minimum amount of work at the last moment.

An example

To demonstrate the idea of laziness, let’s take a look at modelling a new app that’s gonna become all the rage. This new app is just going to be a clone of the “poke” feature from Facebook where people can poke each other.

Warning: I’m going to give examples in Clojure. To be honest, I haven’t used Clojure much but I feel like it’s the only language I know most suitable to represent my ideas for this article(specifically because of multimethods used in the examples).

Dreaming up an end goal

Our Facebook poke clone can be modelled with just a single function. There may be other supporting functions, but there is only one action being taken in the app: poking.

(defn poke 
  "Poke from one person(from) to another(to)."
  [from to]
  ...)

To demonstrate how this works, let’s create two people: me and bob.

;; Create our friend: Bob
(def bob (make-person))

;; Create ourself
(def me (make-person))

(make-person, as the name suggests, creates a person. We don’t need to worry about what the returned data looks like…we just want to be able to use it for poking.)

Then we’d poke our friend with the following code:

(-> me (poke bob))

Easy peasy lemon squeezy! There’s our model. With only two functions(make-person and poke), we can simulate a poke feature!

…except not really. This isn’t real life. The real world is calling and it’s going to give your code a wake up call.

Knock knock! It’s me, the real world!

In a real application that will be built and used by real people, we wouldn’t have such direct access to me or bob – the components in which our system is acting on. These wouldn’t be variables and we’d have to query a database for their data.

Let’s create a function so that we can retrieve these components:

(defn person-by-id [id]
  (sql/find-by-id :people :id id)) ;; Find the person in the "people" table in the database

(I realize that I’m not providing a database connection to sql/find-by-id. The use of this function is for illustrative purposes.)

We will also need to adapt our poke function to talk to the database as well:

(defn poke [from to]
  ;; Here we'll insert into a "pokes" table that has two columns:
  ;; 1) the id of the person doing the poking and
  ;; 2) the id of the person being poked
  (sql/insert-into :pokes {:from (:id from) :to (:id to)}))

To handle a request from someone to poke something else, we can now do something like this:

(let [you  (person-by-id "myId")
      them (person-by-id "bobsId")]
  (-> you (poke them)))

Being hella lazy

Count the number of database calls in the last example.

…I’ll wait…

Did you count 3 total database calls? If so, congrats! You don’t win anything but I’m happy you participated!

That’s quite a few calls. This is all coming from the eagerness of immediately going to the database to look up the parties involved in poking. Since the saving of the poke record is a necessity, this means we have two extra calls.

Let’s try to simplify this so we only make one call to the database:

(defn poke [from-id to-id]
  (sql/insert-into :pokes {:from from-id :to to-id}))

Now we just need to pass the id’s of those being poked and viola 🎻! You got yourself a real life poking application!

Another knocking at the door…

A few weeks after you wrap up this poking functionality, management is requesting that you’re able to poke by e-mail. Unfortunately, this new requirement invalidates our new version of poke as now we’ll want to accept an e-mail address.

Let’s go back and look at the original poke definition:

(defn poke 
  "Poke from one person(from) to another(to)."
  [from to]
  ...)

It would be nice if we can just provide a simple map to from and to and not worry about whether or not we have the receiver’s id.

Let’s get lazy with how we query. In the above section, we introduced a person-by-id function. I quite liked that because it’s descriptive of how to retrieve that person. Let’s bring that back and adapt it to laziness:

(defn person-by-id [id]
  {:id     id
   :lookup :id})

Likewise, we can also do something similar to suit our new “poke by e-mail” requirement:

(defn person-by-email [email]
  {:email  email
   :lookup :email})

These two methods are going to push any sort of database call to downstream functions. Lazy!

To adapt the poke function to handle these two methods, we can use the power of multimethods to create a record of the poke based on what we know about each person.

;; Define a multimethod that's dispatched based on the lookup
;; method for each person(i.e. id, email)
(defmulti poke 
  (fn [p1 p2] 
    [(:lookup p1) (:lookup p2)]))

;; A poke from and to two people with which we know their id's:
(defmethod poke [:id :id] [from to]
  (sql/insert-into :pokes {:from (:id from) :to (:id to)}))

;; A poke to someone who we only know the email of:
(defmethod poke [:id :email] [from to]
  (sql/query "insert into pokes (from_id, to_id)
              select people.id, ?
              from people
              where people.email = ?"
              [(:id to) (:email from)]))

This results in us being able to poke a person by their id or by their e-mail. Our new highly-requested poke-by-email feature can be implemented as follows:

;; Poke by e-mail
(let [you  (person-by-id "myId")
      them (person-by-email "bob@bob.com")]
  (-> you (poke them)))

Ah! Much nicer. Now poke can do its thing and we don’t need to know anything about the data returned by person-by-id or person-by-email. We can just treat them as two components which would like to communicate.

What do you think?

Please let me know in the comments!