|

Introducing NestJS DMN: Parsing Business Rules Without Losing Your Mind (Part 1)

Have you ever tried to manage complex business rules scattered across hundreds of if-else statements in your code? If so, you’ve probably considered buying a one-way ticket to a remote island with no WiFi.

Enter DMN (Decision Model and Notation). It’s a standard that allows business analysts to draw nice tables that somehow dictate what your app should do. The problem? Most DMN engines require a JVM, massive dependencies, or a PhD in XML parsing.

Not anymore. Let me introduce you to @oltionzefi/nestjs-dmn.

What is it?

It’s a small, fully-typed NestJS module that parses DMN 1.3 XML and evaluates decision tables. It is built on the shoulders of giants: dmn-moddle (for XML reading) and feelin (for FEEL/S-FEEL interpreting).

The best part? No native dependencies, no JVM, no weird JSON-format conversions. You just point it at a .dmn file straight out of Camunda Modeler, and you’re off to the races.

The Features We Actually Care About

  1. Full NestJS DI Integration: Everything is an @Injectable. We use the standard forRoot and forRootAsync patterns because we’re civilized developers.
  2. Hit Policies Galore: It handles FIRST, UNIQUE, COLLECT, and RULE ORDER right out of the box.
  3. Pluggable Architecture: Need custom output-cell decoding? Want to map labels to variables differently? You got it.
  4. Types, Types, Types: Zero any in the public API. It even ships with its own dmn-moddle declarations.

Getting Started

First, tell npm where to find my packages (if you haven’t already, add this to your project’s .npmrc):

@oltionzefi:registry=https://npm.pkg.github.com

Then install the magic:

npm install @oltionzefi/nestjs-dmn dmn-moddle feelin

(Note: Make sure you have @nestjs/common (v10 or v11) and reflect-metadata as they are peer dependencies.)

Wiring It Up

Let’s assume you have a fancy DMN file (tier.dmn) that decides if a user gets a “power”, “standard”, or “trial” subscription based on their monthlyMinutes and country.

Just import the module into your app:

import { Module } from '@nestjs/common';
import { DmnModule } from '@oltionzefi/nestjs-dmn';

@Module({
  imports: [DmnModule.forRoot()],
})
export class AppModule {}

And inject the services into your router:

import { Injectable } from '@nestjs/common';
import { DmnEvaluatorService, DmnParserService } from '@oltionzefi/nestjs-dmn';

@Injectable()
export class SubscriptionRouter {
  constructor(
    private readonly parser: DmnParserService,
    private readonly evaluator: DmnEvaluatorService,
  ) {}

  async route(xml: string, input: any) {
    const decisions = await this.parser.parseDmnXml(xml);
    const [decisionId] = Object.keys(decisions);
    
    return this.evaluator.evaluateDecision(decisionId, decisions, input);
  }
}

Boom. router.route(xml, { monthlyMinutes: 2400, country: 'DE' }) will magically spit out { plan: 'power', discount: 0.2 }.

In Part 2, we will dive deeper into advanced configurations and how to extend the module to fit your exact, potentially chaotic, enterprise needs. Stay tuned!