Pure domain logic is the foundational core of any enterprise application, decoupled from infrastructure primitives, persistence layers, network sockets, and UI rendering pipelines. In alignment with Clean Architecture and Domain-Driven Design (DDD) principles, the domain core must be expressed through pure, deterministic functions or immutable aggregates.
Determinism renders pure business logic an optimal environment for Test-Driven Development. The absence of external I/O permits executing thousands of specifications in milliseconds in-memory, instantaneously verifying domain invariants.
In complex enterprise platforms, pricing policies, tariff calculations, and discount qualification criteria continually evolve. Without strict TDD discipline, a calculation engine rapidly degrades into an unmaintainable tangle of nested if-else and switch-case branches, where introducing a new promotional rule compromises existing edge-case invariants. TDD enables the evolutionary emergence of resilient architectures (such as the Strategy or Chain of Responsibility patterns) guarded continuously by automated executable specifications.
Developing domain algorithms via TDD follows an evolutionary design trajectory: transitioning from rudimentary calculations to a decoupled, composable rule-evaluation architecture.
+-----------------------------------------------------------------------+
| DOMAIN CORE EVOLUTION PATTERN |
+-----------------------------------------------------------------------+
| |
| [Input Context: Cart Items, Promotions, Temporal Clock Snapshot] |
| │ |
| ▼ |
| +-------------------------------------+ |
| | 1. Input Invariant Validation | |
| +-------------------------------------+ |
| │ |
| ▼ |
| +-------------------------------------+ |
| | 2. Base Amount Calculation (Subtotal)| |
| +-------------------------------------+ |
| │ |
| ▼ |
| +-------------------------------------+ |
| | 3. Discount Strategy Pipeline | |
| | (Rules: Fixed -> Percent -> Cap) | |
| +-------------------------------------+ |
| │ |
| ▼ |
| [Final Settlement: Total in Cents, Applied Discount Allocations] |
| |
+-----------------------------------------------------------------------+When designing monetary algorithms, eliminating IEEE 754 floating-point inaccuracies is non-negotiable:
Clock), preventing nondeterministic calls to new Date().Consider a concrete real-world challenge: incrementally implementing a robust order calculation engine, PricingEngine, supporting multi-item base totals, percentage promotional codes, fixed vouchers, temporal coupon expiration, and a defensive basket-level discount cap.
We begin with our initial failing test (RED): the engine must compute subtotal values for valid cart items and immediately reject invalid line prices or negative item quantities.
import { describe, it, expect } from 'vitest';
import { PricingEngine, PricingError } from './pricing-engine';
describe('PricingEngine - Step 1: Base Calculation', () => {
const defaultClock = () => new Date('2026-03-01T10:00:00Z');
it('should accurately calculate base cost for valid cart items', () => {
const engine = new PricingEngine(defaultClock);
const result = engine.calculate({
items: [
{ productId: 'item-1', unitPriceCents: 1000, quantity: 2 }, // 2000 cents
{ productId: 'item-2', unitPriceCents: 500, quantity: 1 }, // 500 cents
],
promotions: [],
});
expect(result.subtotalCents).toBe(2500);
expect(result.totalCents).toBe(2500);
expect(result.discountCents).toBe(0);
});
it('should throw PricingError when encountering negative unit price or zero quantity', () => {
const engine = new PricingEngine(defaultClock);
expect(() =>
engine.calculate({
items: [{ productId: 'item-1', unitPriceCents: -100, quantity: 1 }],
promotions: [],
})
).toThrow(PricingError);
});
});We implement the minimal code required to satisfy the specification (GREEN).
export class PricingError extends Error {
constructor(message: string) {
super(message);
this.name = 'PricingError';
}
}
export interface CartItem {
readonly productId: string;
readonly unitPriceCents: number;
readonly quantity: number;
}
export interface PricingCalculationResult {
readonly subtotalCents: number;
readonly discountCents: number;
readonly totalCents: number;
}
export interface PricingContext {
readonly items: readonly CartItem[];
readonly promotions: readonly unknown[];
}
export class PricingEngine {
constructor(private readonly clock: () => Date) {}
public calculate(context: PricingContext): PricingCalculationResult {
let subtotalCents = 0;
for (const item of context.items) {
if (item.unitPriceCents < 0 || item.quantity <= 0) {
throw new PricingError('Invalid item price or quantity');
}
subtotalCents += item.unitPriceCents * item.quantity;
}
return {
subtotalCents,
discountCents: 0,
totalCents: subtotalCents,
};
}
}The Step 1 test suite executes cleanly (PASS).
Next, we establish business rules governing promotional vouchers:
validUntil.describe('PricingEngine - Step 2: Promotions & Date Validation', () => {
const currentDate = new Date('2026-03-01T12:00:00Z');
const clock = () => currentDate;
it('should correctly apply valid percentage promotions', () => {
const engine = new PricingEngine(clock);
const result = engine.calculate({
items: [{ productId: 'item-1', unitPriceCents: 10000, quantity: 1 }],
promotions: [
{
code: 'SPRING10',
type: 'PERCENTAGE',
value: 10,
validUntil: new Date('2026-04-01T00:00:00Z'),
},
],
});
expect(result.subtotalCents).toBe(10000);
expect(result.discountCents).toBe(1000);
expect(result.totalCents).toBe(9000);
});
it('should ignore promotions whose expiration date has lapsed', () => {
const engine = new PricingEngine(clock);
const result = engine.calculate({
items: [{ productId: 'item-1', unitPriceCents: 10000, quantity: 1 }],
promotions: [
{
code: 'EXPIRED',
type: 'FIXED',
value: 2000,
validUntil: new Date('2026-02-01T00:00:00Z'), // Expired 1 month prior
},
],
});
expect(result.discountCents).toBe(0);
expect(result.totalCents).toBe(10000);
});
});Executing Vitest triggers a failure (FAIL). We update our domain model to achieve GREEN.
export interface PercentagePromotion {
readonly code: string;
readonly type: 'PERCENTAGE';
readonly value: number; // 0 - 100
readonly validUntil: Date;
}
export interface FixedPromotion {
readonly code: string;
readonly type: 'FIXED';
readonly value: number; // In cents
readonly validUntil: Date;
}
export type Promotion = PercentagePromotion | FixedPromotion;
export interface PricingContext {
readonly items: readonly CartItem[];
readonly promotions: readonly Promotion[];
}
export class PricingEngine {
constructor(private readonly clock: () => Date) {}
public calculate(context: PricingContext): PricingCalculationResult {
let subtotalCents = 0;
for (const item of context.items) {
if (item.unitPriceCents < 0 || item.quantity <= 0) {
throw new PricingError('Invalid item price or quantity');
}
subtotalCents += item.unitPriceCents * item.quantity;
}
const now = this.clock();
let totalDiscountCents = 0;
for (const promo of context.promotions) {
if (promo.validUntil.getTime() < now.getTime()) {
continue; // Expired voucher
}
if (promo.type === 'PERCENTAGE') {
const discount = Math.round((subtotalCents * promo.value) / 100);
totalDiscountCents += discount;
} else if (promo.type === 'FIXED') {
totalDiscountCents += promo.value;
}
}
const totalCents = Math.max(0, subtotalCents - totalDiscountCents);
return {
subtotalCents,
discountCents: totalDiscountCents,
totalCents,
};
}
}All four specifications across Steps 1 and 2 pass (PASS).
We formulate advanced business restrictions:
PricingError.describe('PricingEngine - Step 3: Complex Rules & Caps', () => {
const now = new Date('2026-03-01T12:00:00Z');
const clock = () => now;
it('should restrict aggregated discounts to a defensive threshold of 50% of subtotal', () => {
const engine = new PricingEngine(clock);
const result = engine.calculate({
items: [{ productId: 'item-1', unitPriceCents: 10000, quantity: 1 }], // 10000 cents
promotions: [
{
code: 'SUPERFIXED',
type: 'FIXED',
value: 8000, // Attempts 80% discount
validUntil: new Date('2026-04-01T00:00:00Z'),
},
],
});
expect(result.subtotalCents).toBe(10000);
expect(result.discountCents).toBe(5000); // Capped at 50%
expect(result.totalCents).toBe(5000);
});
it('should reject requests combining multiple percentage promotional codes', () => {
const engine = new PricingEngine(clock);
expect(() =>
engine.calculate({
items: [{ productId: 'item-1', unitPriceCents: 10000, quantity: 1 }],
promotions: [
{
code: 'SALE10',
type: 'PERCENTAGE',
value: 10,
validUntil: new Date('2026-04-01T00:00:00Z'),
},
{
code: 'SALE20',
type: 'PERCENTAGE',
value: 20,
validUntil: new Date('2026-04-01T00:00:00Z'),
},
],
})
).toThrow('Multiple percentage promotions are not allowed');
});
});Tests fail. We expand the calculation routine to fulfill both constraints:
public calculate(context: PricingContext): PricingCalculationResult {
let subtotalCents = 0;
for (const item of context.items) {
if (item.unitPriceCents < 0 || item.quantity <= 0) {
throw new PricingError('Invalid item price or quantity');
}
subtotalCents += item.unitPriceCents * item.quantity;
}
const now = this.clock();
let percentagePromosCount = 0;
for (const promo of context.promotions) {
if (promo.type === 'PERCENTAGE') {
percentagePromosCount++;
}
}
if (percentagePromosCount > 1) {
throw new PricingError('Multiple percentage promotions are not allowed');
}
let rawDiscountCents = 0;
for (const promo of context.promotions) {
if (promo.validUntil.getTime() < now.getTime()) {
continue;
}
if (promo.type === 'PERCENTAGE') {
rawDiscountCents += Math.round((subtotalCents * promo.value) / 100);
} else if (promo.type === 'FIXED') {
rawDiscountCents += promo.value;
}
}
// Defensive Cap: maximum 50% discount
const maxAllowedDiscount = Math.floor(subtotalCents * 0.5);
const finalDiscountCents = Math.min(rawDiscountCents, maxAllowedDiscount);
const totalCents = subtotalCents - finalDiscountCents;
return {
subtotalCents,
discountCents: finalDiscountCents,
totalCents,
};
}All 6 test cases across Steps 1 to 3 are passing (PASS).
While our implementation passes all functional criteria, it violates the Open/Closed Principle (OCP): introducing new promo mechanics (category-specific discounts, volume thresholds) necessitates modifying the core calculate method. We refactor into a polymorphic Strategy architecture under the safety of our comprehensive green test suite.
export class PricingError extends Error {
constructor(message: string) {
super(message);
this.name = 'PricingError';
}
}
export interface CartItem {
readonly productId: string;
readonly unitPriceCents: number;
readonly quantity: number;
}
export interface PercentagePromotion {
readonly code: string;
readonly type: 'PERCENTAGE';
readonly value: number;
readonly validUntil: Date;
}
export interface FixedPromotion {
readonly code: string;
readonly type: 'FIXED';
readonly value: number;
readonly validUntil: Date;
}
export type Promotion = PercentagePromotion | FixedPromotion;
export interface PricingCalculationResult {
readonly subtotalCents: number;
readonly discountCents: number;
readonly totalCents: number;
}
export interface PricingContext {
readonly items: readonly CartItem[];
readonly promotions: readonly Promotion[];
}
// 1. Discount Strategy Contract
export interface DiscountStrategy {
calculateDiscount(subtotalCents: number, promo: Promotion, now: Date): number;
}
class PercentageDiscountStrategy implements DiscountStrategy {
calculateDiscount(subtotalCents: number, promo: Promotion, now: Date): number {
if (promo.type !== 'PERCENTAGE' || promo.validUntil.getTime() < now.getTime()) {
return 0;
}
return Math.round((subtotalCents * promo.value) / 100);
}
}
class FixedDiscountStrategy implements DiscountStrategy {
calculateDiscount(subtotalCents: number, promo: Promotion, now: Date): number {
if (promo.type !== 'FIXED' || promo.validUntil.getTime() < now.getTime()) {
return 0;
}
return promo.value;
}
}
// 2. Domain Invariant Validator
class PricingValidator {
public static validate(context: PricingContext): void {
for (const item of context.items) {
if (item.unitPriceCents < 0 || item.quantity <= 0) {
throw new PricingError('Invalid item price or quantity');
}
}
const percentageCount = context.promotions.filter(
(p) => p.type === 'PERCENTAGE'
).length;
if (percentageCount > 1) {
throw new PricingError('Multiple percentage promotions are not allowed');
}
}
}
// 3. Engine Facade
export class PricingEngine {
private readonly strategies: ReadonlyMap<Promotion['type'], DiscountStrategy>;
private static readonly MAX_DISCOUNT_RATIO = 0.5; // 50% Cap
constructor(private readonly clock: () => Date) {
this.strategies = new Map<Promotion['type'], DiscountStrategy>([
['PERCENTAGE', new PercentageDiscountStrategy()],
['FIXED', new FixedDiscountStrategy()],
]);
}
public calculate(context: PricingContext): PricingCalculationResult {
PricingValidator.validate(context);
const subtotalCents = context.items.reduce(
(sum, item) => sum + item.unitPriceCents * item.quantity,
0
);
const now = this.clock();
let accumulatedDiscountCents = 0;
for (const promo of context.promotions) {
const strategy = this.strategies.get(promo.type);
if (strategy) {
accumulatedDiscountCents += strategy.calculateDiscount(subtotalCents, promo, now);
}
}
// Apply defensive boundary limit
const maxDiscountAllowed = Math.floor(subtotalCents * PricingEngine.MAX_DISCOUNT_RATIO);
const finalDiscountCents = Math.min(accumulatedDiscountCents, maxDiscountAllowed);
const totalCents = Math.max(0, subtotalCents - finalDiscountCents);
return {
subtotalCents,
discountCents: finalDiscountCents,
totalCents,
};
}
}Running Vitest confirms 100% test suite pass rate. The engine's structural architecture was redesigned, polymorphism introduced, and validation decoupled—without altering a single line in our test specifications.
Storing monetary figures as floats (19.99). The expression 0.1 + 0.2 in JavaScript yields 0.30000000000000004. In financial logic, precision drift results in reconciliation failures and auditing discrepancies. Always operate on whole integer units (cents, minor units).
Invoking const now = new Date() directly inside algorithmic functions ruins deterministic test execution. A test asserting coupon expiration that passes today will fail months later. Always inject a clock provider clock: () => Date via constructor or functional parameters.
Mutating input cart objects (e.g., setting item.discountApplied = true). This produces hidden side effects across React component trees and breaks state predictability. Domain functions must remain pure routines returning freshly constructed immutable records.
Attempting to construct an abstract AST rule interpreter on day one. Until specifications demonstrate concrete structural complexity and duplication, write the simplest straightforward code. Architectural abstractions must crystallize organically during the Refactor stage.
Author the simplest deterministic test without side effects. Validate boundary inputs (empty collections, invalid numbers).
Write direct, straightforward logic in a single function to achieve green test status.
Introduce test cases verifying temporal windows, threshold limits (Caps), and conflicting modifier combinations.
Decompose branching conditional logic into strategy classes or reducer functions under test coverage.
Exhaustively verify zero division, integer overflow thresholds, and cent-exact rounding mechanics.