Easier insurance pricing calculators with a Ruby DSL

Bruno Almeida
Getsafe
5 min readAug 17, 2022

--

In this post I’m gonna talk about a problem that we had at Getsafe and how using a DSL made it easier to work with.

I’m not going to explain our implementations details as it’s very specific to our case. I’m going to focus on our problem and which benefits using DSL brought to us.

What are Pricing calculators?

To calculate the price for any insurance, we need to receive some data from the user such as age, address, deductible amount, etc, and run against a series of internal rules. Each risk to be covered by the insurance can have multiple factors that impact the price. The sum of all risks is the final price for the customer.

You may understand it better once you see the DSL code.

The Problem with the current approach

Conditions and prices should never change once we sell an insurance.

But things actually change: governmental regulations, risk factor, inflation among other factors. That means we need to update our insurance products from time to time. And on every update a whole new set of classes needed to be created.

As different teams work on different products, it was really difficult to enforce a single code structure or style for those classes.

After many products with many updates, our pricing calculation classes became hard to work with.

To be easier to understand, I adapted a real, but simple, class example for the dental insurance:

The monthly net price is: base_value + (missing_teeth * missing_teeth_multiplier). The base_value is a fixed value that depends on the insured person age, missing_teeth is a value provided by the insured person and missing_teeth_multiplier is a fixed value.

You don't need to understand the following class. Just wanted to provide an example of how a price calculator was implemented before introducing the DSL and pointing out that due to the lack of restrictions it could have taken any form.

In this class that has only a three variables to calculate the final price, but some are way more complex. And when you have many insurances with many variables with different teams working on it, they can get very long and it’s hard to have a standard between them.

Many times when updating a product, only one or two methods changes. So we inherit from the previous version and override only the methods that should change. It works fine, you don’t repeat code, it’s easy to implement and it fits the way new generations from the same product change.

But it starts to getting hard to debug. A fix in a previous generation can impact all the others.

What we needed

We needed a new structure that matched the following criteria:

  • Easy to read. Only by looking at the class, you could have a good overview on how that insurance was calculated and what are the rules for it.
  • Universal. That all insurance price calculators could use the same structure.
  • Opinionated. It supports a certain set of features and provides one exact way to achieve something, leaving no room for custom code.
  • Flexible. At the same time that we need to enforce, we also need to be flexible to handle some edge cases (yes, it’s controverse)

There is the last criteria, which is the most important one: We want to allow non-developers to create new Price Calculators (or at least understand it when reading), and to allow that we need a very simple way to express that without requiring programming language knowledge.

As Ruby is a very flexible language, there are many ways to solve most problems. But creating a DSL is the best step towards creating a custom syntax for non-developers. So, that was the approach we took!

What is a DSL?

DSL stands for Domain specific languages. With it you can practically create a new programming language syntax.

Even if you don’t know what DSL is, you can be sure you already used when programming Ruby. For example:

The attr_accessor is a default method in plain Ruby, and as you can see, it adds two new instance methods to the Dog class. The actual implementation is here: https://apidock.com/ruby/Module/attr_accessor

We can agree that this is much simpler to read than manually creating these two new methods:

DSL is widely used in the Ruby community. Rails uses them for almost everything, that is even hard to think in a single example. But one very familiar is validation on Models:

These examples are common and seem to fit the Ruby way, but some others not so much. With new methods that you may forget that you are working with Ruby.

Like Rspec:

Ruby DSL is a great way to improve readability and maintenance, reduce duplication and it can look pretty awesome when well implemented!

The final result

As you can see, there are no “plain ruby” methods anymore. All the data you need to set in order to retrieve the price is create and exposed by Pricing::Calculators::Dsl::Syntax.

Easy to read

The example only contains a few methods, but comparing with the previous version you can already see how much easier it is to read and understand how the price is calculated.

Universal

We came up with a group of methods that would handle majority of our pricing calculators, which was not so hard considering that even when some pricing calculators have lots of rules, they are very similar in the end.

Opinionated

It’s still Ruby, so you can override any method if you want to. But it’s not gonna be simple and it’s not gonna look good.

And this is not only about the code. As creating specific cases is not simple, this also impact one step before the code, when we are creating a new product and defining how a price will be calculated.

Flexible

It’s still Ruby, so you can still do almost anything.

If you want to change the default behaviour or create one edge case for risk price for example, you would need to have a pretty good knowledge about how Pricing::Calculators::Dsl::Syntax was implemented.

This is very discouraging to not follow the default unless really necessary.

Conclusion

First of all, DSL is not a silver bullet. For us it made total sense as it’s a feature that we work over and over and have the same logic in many places. For some cases it may simply be over-engineering and does not ever pays off.

Our case may seem different compared to attr_accessor that only adds two methods, but the way they are implemented, is the same. So if you are comfortable creating something as simple as attr_accessor and extending/including modules, you can do much more.

--

--