qhwa

qhwa

Hi all!

I’m excited to share with you my recent work on making business rules easier to maintain.

Tablex is an implementation of Decision Table in Elixir. A decision table can make rules more obvious to human brains. Hence we can easily spot the mistakes in rules. Since they are simple markups, Elixir developers can transfer the responsibility of maintaining the domain business rules to operation teammates.

Tablex is heavily inspired by Decision Model and Notation (DMN) and borrowed its ideas of decision tables and friendly expression language. While DMN uses XML to present the decision table, Tablex uses easier-to-read plain texts, such as:

F score   || result
1 >=90    || A
2 60..89  || B
3 -       || C

What is it related to Elixir?

As a library, Tablex has the following abilities:

  • Parsing the above text into a rich Elixir struct, %Tablex.Table{...}.
  • Compiling the above text into corresponding Elixir code that uses pattern matching. (doc)
  • Allowing employing functions in the output definition to be your domain rule engine. (doc)
  • Allowing displaying decision tables in HTML pages with the help of TablexView

In a word, it allows you to work with your business rules conveniently.

Let’s see a more real-life example:

The following Elixir code defines what settings for a given place (identified by city and area id) are:

default_settings = %{
  feature1: [
    param1: 5,
    param2: 0
  ],
  feature2: [
    param1: 0.6,
    param2: 60
  ],
  feature3: [
    feature3_1: [
      param1: 0.75,
      param2: ["SOMETHING"]
    ],
    feature3_2: [
      param1: 0.75,
      param2: ["SOMETHING"]
    ]
  ],
  feature4: false,
  feature5: false,
  feature6: [
    feature6_1: true,
    feature6_2: true,
    feature6_3: false
  ],
  feature7: "provider1",
  feature8: false
}

case target do
  %{city: "Vancouver"} ->
    %{default_settings | feature8: true}

  %{area_id: 2053} ->
    default_settings
    |> put_in([:feature6, :feature6_3], true)
    |> put_in([:feature8], true)
    |> put_in([:feature3, :feature3_1, :param2], ["SOMETHING", "ANOTHER"])
    |> put_in([:feature3, :feature3_2, :param2], ["SOMETHING", "ANOTHER"])

  %{area_id: area_id} when area_id in [7839, 2407, 21094, 62124] ->
    default_settings
    |> put_in([:feature4], true)
    |> put_in([:feature8], true)

  %{area_id: area_id} when area_id in [23982, 2407, 2462, 2179] ->
    %{default_settings | feature7: "provider2"}

  %{area_id: area_id} when area_id in [2060, 2413, 2407, 2417, 2396, 2419] ->
    default_settings
    |> put_in([:feature8], true)
    |> put_in([:feature6, :feature6_3], true)

  _ ->
    default_settings
end

With Decision Table, we can describe almost the same logic in a tabular text block:

====
R                           || 1           2         3                  4                     5                     6
target.city                 || -           Vancouver -                  -                     -
target.area_id              || -           -         2053               7389,2407,21094,62124 23982,2407,2462,2179  2060,2413,2407,2417,2396,2419
====
feature1.param1             || 5           -         -                  -                     -                     -
feature1.param2             || 0           -         -                  -                     -                     -
feature2.param1             || 0.6         -         -                  -                     -                     -
feature2.param2             || 60          -         -                  -                     -                     -
feature3.feature3_1.param1  || 0.75        -         SOMETHING,ANOTHER  -                     -                     -
feature3.feature3_1.param2  || [SOMETHING] -         -                  -                     -                     -
feature3.feature3_2.param1  || 0.75        -         -                  -                     -                     -
feature3.feature3_2.param2  || [SOMETHING] -         SOMETHING,ANOTHER  -                     -                     -
feature4                    || Y           -         -                  Y                     -                     -
feature5                    || N           Y         -                  -                     -                     -
feature6.feature6_1         || Y           -         -                  -                     -                     -
feature6.feature6_2         || Y           -         -                  -                     -                     -
feature6.feature6_3         || N           -         Y                  -                     -                     Y
feature7                    || provider1   -         -                  -                     provider2            -
feature8                    || N           -         Y                  Y                     -                     Y

I said “almost the same” because the settings differ between the Elixir code and the table when the area id is 2407. The maintainer of the code intended to set multiple settings for area #2407 with different clauses of pattern matching. But he didn’t know that only the first matched clause was hit. This mistake was quickly spotted when the rules were present on a table.

Also, here we use R, the reverse merge hit policy so that settings will be merged with all hit rules from right to left. So rules #6, #5, #4, and #1 will all impact the final result.

The textural table is easy to read and can be further visualized with HTML:


(generated by TablexView)

Roadmap

1.0

  • formatter
  • expression specification

1.1

  • more data types (Date, Time, and DateTime)

Current status

It’s still a very early stage for Tablex, although it has been used in production. Currently, the latest version is 0.3.1.

I hope you can find this helpful to you. Feedback is welcome! Thanks!

Showing Posts 1 to 3

qhwa

qhwa OP

New version released today: v0.2.0-alpha.1, with many exciting changes:

  • An optimizer is added for optimizing the rules, for example, to remove duplicated or unreachable rules.
  • A formatter that can format the table text so that the style is persistent.
  • Some APIs for getting or updating rules, so that the rules can be programmably managed without handwriting tables.
  • Add support for structs as decision arguments (PR), thanks to László Hegedüs
  • Improvement on development, such as allowing development on Elixir 1.14, and adding CI, thanks to James Every

I’m very excited about what is ahead. Feedback is very welcome!

qhwa

qhwa OP

New version released: v0.2.0. Compared to v0.2.0-alpha.1, there are many changes:

  • A better support for Emojis :grinning:
  • Bugfixes for the formatter
  • Bugfixes for the optimizer
qhwa

qhwa OP

New version released: v0.3.1, with some improvements and bugfixes:

  • [BREAKING] Tablex no longer alters the case of a variable name. For example, myFoo will stay as myFoo. In contrast, previous versions will convert it to my_foo.
  • Performance is significantly improved when generating Elixir code from tables.
— All posts loaded —

Where Next? Top

Trending in Announcing Top

restlessronin
The repo is at GitHub - cyberchitta/openai_ex: Community maintained Elixir library for OpenAI API · GitHub. Docs are at OpenaiEx User Gu...
152 12366 136
New
type1fool
WebAuthnLiveComponent WebAuthnComponents See this post about renaming the package. Passwordless authentication for Phoenix LiveView app...
New
GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
ausimian
Castle is a package I put together to assist with building Elixir releases capable of being live-upgraded via the ‘official’ release hand...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
drozdzynski
Storex is a frontend store with state management handled on the backend. It allows you to update the store state both from the frontend a...
New

Other Trending Topics Top

budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
budgie
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with. But I don’t use it anymore. Serverless was the promis...
New
Null-logic-0
What IDE or editor are you using for Elixir development? Personally, I use Zed, and I really like it, but sometimes I wish there were a ...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews