Structuring and validating business rules in JavaScript

Grigorii Chudnov
4 min readMar 17, 2015

Business rules

Any business is controlled by a collection of rules. Rules might have been established by the organization itself or reflect legislation, regulation and external standards. Rules cover such matters as interactions with customers and suppliers, minimum prices, maximum discounts, support decision making.
The software system developed or acquired by an organization should ensure that the data it manages or processes comply with all relevant rules to which the organization is subject.

Business rule — a statement that defines or constrains some aspect of the business[1].

Rules documenting and structuring

The rules in a software system need to be documented and structured to enable stakeholders to approve, change and review them for relevance and correctness.
Rules might be expressed in program code, implemented in the database or with a rules engine. Often, these rules are not easily accessible or understandable by anyone other than the application developers.

Implementation problems

In program code rules are often expressed as `if-statements` where some action is being performed only if a logical condition is `true` or `false`:

if (condition) then
{ executed if the condition is `true` }
else
{ executed if the condition is `false` }

As the condition becomes more and more complex, these rules cannot be modified without a considerable effort: the set of rules spans the whole application, the rule code is intermixed with non-rule code and it is not always immediately obvious which is which. The same rule may show up in different, and possibly contradictory forms.

Specification design pattern

A `specification` is a predicate that determines whether another object does or does not satisfy some criteria [2]. Specifications could be combined with other specifications using “AND”, “OR” and “NOT” operations.

In the diagram, Composite Specification provides the abstract `IsSatisfiedBy` method, that should be overridden in a derived class. This method takes a candidate object and returns a boolean value which indicates whether the candidate object satisfies some criteria or not.

Business rules can be expressed as specifications and chained together to express more complex rules.

Example

Consider the following scenario:

On entering a train station, passengers are required to insert a ticket into a slot beside a closed barrier. An entry barrier opens if the ticket meets all of the following criteria:

  1. it is valid for travel from that station;
  2. it has not expired;
  3. it has not already been used for the maximum number of journeys allowed.

We need to implement the logic a barrier can use for ticket validation. Building a solution, we will use the bspec library and ES6 promises.

A ticket has the following properties:

var ticket = {
stations: [ 'Riva' ],
expires_at: new Date(2015, 2, 6),
max_journeys: 30,
cur_journeys: 11
};
  • `stations`— an array of stations the ticket can be used to travel from;
  • `expires_at`— the date the ticket expires;
  • `max_journeys`— the maximum number of journeys allowed;
  • `cur_journeys`— the current number of journeys used.

First, import the specification object:

var Spec = require('bspec').PromiseSpec;

Check whether the ticket has expired:

var isTicketExpired = function isTicketExpired(ticket) {
return Promise.resolve(new Date() > ticket.expires_at);
};

Verify if the ticket has been used for the maximum number of journeys allowed:

var isMaxJourneys = function isMaxJourneys(ticket) {
return Promise.resolve(ticket.cur_journeys >= ticket.max_journeys);
};

A function to find-out if the ticket is valid for travel from a station:

var isValidFromStation = function isValidFromStation(name, ticket) {
return Promise.resolve(ticket.stations.indexOf(name) !== -1);
};

Finally, create a composite rule for the station barrier:

var barrierSpec = Spec(isValidFromStation.bind(null, 'Riva'))
.and(Spec(isTicketExpired).not())
.and(Spec(isMaxJourneys).not());

Here we wrap every function defined earlier into the `Spec` object.

The resulting logic is expressed as a composition of specifications, combined by logical operators `AND` and `NOT`.

The composite specification is satisfied when the ticket is valid to travel from the ‘Riva’ station, the ticket hasn't expired and it hasn't been used for the maximum number of journeys.

To verify that a ticket satisfies the created specification, invoke the `isSatisfiedBy` function on the composite specification and pass it the `ticket` object:

barrierSpec.isSatisfiedBy(ticket)
.then(function(result) {
// ...
})
.catch(function(err) {
// ...
});

When `isSatisfiedBy` is invoked, it evaluates the chained predicates and returns a boolean value via Promise. The `result` indicates whether the given ticket comply with all specified rules.

In the scenario we assumed the ticket holds all the data required for a reasoning. In a different case, a ticket might have only an ID and validation implemented on the server side. It requires to update only the logic inside `isTicketExpired`, `isMaxJourneys` and `isValidFromStation` functions in order to invoke the remote services. Specification composition remains the same.

Conclusion

Being a very simple, domain-oriented a highly maintainable, specifications allow you to create a complex and customizable business logic.

However, specification pattern main focus is the implementation rather than documentation of business rules. The expressions used to express each rule might be not the ones that business stakeholders will easily understand.

Source Code

The complete source code can be found in the bspec github repo.

References

[1] Witt, Graham (2012). Writing Effective Business Rules. Morgan Kaufmann; 1 edition
[2] Evans, Eric (2004). Domain Driven Design. Addison-Wesley.

--

--