Skip to content

Single‑Function Philosophy for MARS Engine

TL;DR - In MARS Engine every API or library file should answer one question or perform one atomic action. Keep it stateless, explicit, and easy to include elsewhere.

Why keep functions atomic in MARS Engine?

BenefitWhat it looks like in MARS
ClarityYou can open a file and know its only job-for example lib/math/roundPrice always rounds, it never writes JSON.
ComposabilitySingle‑purpose files can be chained with include() or called from other APIs without refactor.
TestabilityPure logic files can be executed from a simple test API.
ScalabilitySmall APIs start instantly and consume less server time-perfect for MARS’s pay‑per‑request model.
ReusabilityThe same helper can be added to any project by copying one file or publishing a snippet to your team.

Core Guidelines

  1. One Responsibility – a file either calculates tax or writes a PDF, never both.
  2. Explicit I/O Contracts – read using param() / request.header(); write using write(). Return a value if it’s a library file, never rely on globals.
  3. Stateless by Default – fetch or persist data with db.query(); don’t cache inside the module.
  4. Pure When Possible – library files should be deterministic and side‑effect free. If you must call the network, wrap that in its own file.
  5. Precise Naming – validateCoupon is better than utils2.
  6. ≤ 40 LOC Rule – when a file creeps past ~40 logical lines of JavaScript split it up.
text
api/
└─ orders/
   ├─ calculateTotal        # library include only
   ├─ applyDiscount
   ├─ validateCoupon
   └─ checkout              # orchestrator API (POST)

lib/
└─ orders/
   ├─ calculateTotal
   ├─ applyDiscount
   └─ validateCoupon

Library file: lib/orders/calculateTotal

js
// Exports exactly one thing – a pure function
exports.calculateTotal = function(items) {
  let sum = 0;
  for (let i = 0; i < items.length; i++)
    sum += +items[i].price || 0;
  return sum;
};

Orchestrator API: api/orders/checkout

js
const { calculateTotal } = include('lib/orders/calculateTotal');
const { applyDiscount }  = include('lib/orders/applyDiscount');
const { validateCoupon } = include('lib/orders/validateCoupon');

const items      = param('items', []);
const couponCode = param('coupon', null);

// fail fast if coupon invalid
if (!validateCoupon(couponCode)) {
  write('status', 'ERR');
  write('message', 'Invalid coupon');
  exit();
}

const subtotal = calculateTotal(items);
const total    = applyDiscount(subtotal, couponCode);

write('total', total); // single responsibility: pricing

Anti‑Patterns

SmellWhy it’s toxic in MARSCure
Kitchen‑Sink APILong files mix DB, auth, email, and HTML-slow cold‑starts, hard rollbacksBreak into libraries + orchestrator
Hidden Side EffectsUpdating DB inside calculateTotal surprises callersMove side effect to its own API (storeOrder)
Generic Nameshelper.js gives no clue in file treeRename to string/slugify
Global StateStoring mutable data on shared between requests breaks concurrencyPass arguments or use DB

When One File Isn't Enough

  • Validation + Persistence – keep validateCoupon pure, then have saveCouponUsage in a second file.
  • External Calls – wrap net requests in their own net‑focused library so the rest of your codebase stays testable.
  • Complex Workflows – use an orchestrator API that only composes pure helpers and side‑effect files.

Key Takeaways

  • Think file‑level single responsibility: each LIB or API does one thing.
  • Pure helpers make MARS Engine faster (cold‑start) and cheaper (less CPU per call).
  • Clear boundaries simplify rollbacks-if sendEmail fails the DB transaction in checkout can still commit safely.

Commit to single‑function files and your MARS Engine projects stay lean, predictable, and fun to maintain.