Business logic as a data structure

7 years ago I worked on a big single page application. I was just getting into JavaScript and had no experience looking into tools and frameworks, mostly because there were few of them. That meant everything was written vanilla style. Every single DOM element created or updated was done using plain imperative JavaScript. Something like this:

const h1 = document.createElement('h1')
document.body.appendChild(h1)

window.eventHub.on('changed:title', function () {
  h1.innerText = window.state.title
})

Managing all your UI manually using imperative JavaScript is unheard of today, and with good reason. It is an absolute pain. Luckily there has been a ton of innovation. From templates to virtual dom implementations using JSX that allows us to express dynamic UIs in a declarative manner.

“Declarative code describes the WHAT of the program, while imperative describes the HOW #javascript

In this article we are going to look at what data structures we use every day to write declaratively. Then we are going to see if we can get these benefits when writing our business logic.

Declarative code

So what does declarative code mean? No matter how you explain it someone will probably say, “that is not completely accurate”. But here is an example of declarative code so that we start to tune in on exactly what we are talking about here:

html

<div>
  <h1>Hello</h1>
</div>

What this code illustrates is that we are describing the what, not the how. The imperative how version of this would be:

const div = document.createElement('div')
const h1 = document.createElement('h1')
h1.innerText = 'Hello'
div.appendChild(h1)

Though this is a somewhat pseudo example, it shows how we differentiate declarative and imperative code.

But this was HTML and we write our business logic in JavaScript… is there any declarative code in JavaScript?

JavaScript and declarative code

We do have declarative code in JavaScript. For example when you want to define an object:

{
  name: 'Jon',
  age: 13
}

The imperative version of this would be:

const user = new Object()
user.name = 'jon'
user.age = 13

The same for lists:

['apple', 'banana']

The imperative version of this would be:

const fruits = new Array()
fruits[0] = 'apple'
fruits[1] = 'banana'

Just like our html example declarative code is an abstraction over imperative code. Meaning that declarative code is kind of a recipe for the imperative code to execute.

But why even bother having a declarative abstraction? Well, when you want to know what is in a soup you prefer reading the recipe instead of reading a transcript of everything the cook did to make the soup. It is the same with code. Declarative describes the what, while imperative is the how.

“You want to know what is in the soup. Do you read the transcript of what the cook did, or just read the recipe? #declarative

What is business logic?

So before we can talk about declarative business logic, we have to talk about business logic. When I say business logic I mean two things:

  1. Update the state of the application

  2. Run side effects

No matter what library you use to do these things, they are required for any application and in this article that is what we will define as business logic.

An example using Redux would be a thunk middleware:

function getUser (dispatch) {
  dispatch({type: USER_LOADING})
  ajax.get('/user')
    .then(user => {
      dispatch({type: USER_LOADED, user})
    })
    .catch(error => {
      dispatch({type: USER_ERROR, error})
    })
}

Or using Mobx with a plain method:

class User {
  isLoading = false
  data = null
  error = null
  get () {
    this.isLoading = true
    ajax.get('/user')
      .then(user => {
        this.isLoading = false
        this.data = user
      })
      .catch(error => {
        this.isLoading = false
        this.error = error
      })
  }
}

None of these approaches are declarative, but they do hold the imperative code needed for the application to work.

Adding a declarative layer to this business logic is not straight forward. There are two big challenges:

  1. We do not have any native data structure for it, unlike html where we have a type of XML the browser handles for us

  2. Business logic is often asynchronous, unlike html which is synchronous

Creating a data structure for business logic

Our data structure needs to describe what logic to run, in what order and any conditional execution. So summarized with points:

  1. It needs to structure functions to run, unlike html where we structure elements to display

  2. It needs to handle asynchronous parts of the structure, unlike html where everything is synchronous

  3. It will need to handle conditional diverging execution, unlike html where every element is evaluated

Since we need to run multiple functions in order we can name this data structure a sequence. Our job now is to find declarative syntax in JavaScript to build our data structure handling all three points.

Defining a sequence

So what would we use to declaratively code a sequence of functions? I can not think of anything better than an array:

[
  funcA,
  funcB,
  funcC
]

Each of these functions will hold the imperative logic. By only referencing the functions we keep our sequence declarative.

Asynchronous functions

Sometimes we want our logic to wait for resolvement of other logic. For example we can not set a user until the user has been fetched from the server. When running the sequence a function that returns a promise can indicate its asynchronous nature. So if funcB looked like this:

function funcB () {
  return new Promise(resolve => setTimeout(resolve, 1000))
}

The sequence would run like this:

[
  funcA,
  funcB, // Holds 1000ms
  funcC
]

Diverging execution

Our business logic is filled with if and switch statements. To handle this declaratively we need a different data structure than an array. We do not really have much choice, but it is a good one. An object beautifully describes possible execution paths in a sequence:

[
  funcA,
  funcB, {
    pathA: funcC,
    pathB: funcD
  },
  funcE
]

Running the business logic

So this does of course not just work out of the box. There is nothing in JavaScript that understands this data structure. To make it work we would have to create a tool where we can pass this sequence and it will be run. Luckily we have Function-Tree. A project that does exactly this and more.

It is used by the Cerebral project to run what it calls signals, which you can see an introduction of here:

Being able to define your business logic in a declarative manner is not about reducing lines of code in the project or be clever about how you write code. Developers spend a lot of time reading how something works to understand what it does. Having a declarative abstraction for your business logic will make it easier for you and your colleagues to understand your application both in terms of planning implementation and fixing bugs.

I hope this gave some inspiration and please check out more about Cerebral if you want to explore declarative business logic further.

JS
BLOG