💰 Stop Trusting the Balance Column

Event-based ledgers beat drifting account totals when refunds and retroactive changes hit.

Welcome to GigaElixir Gazette, your 5-minute digest of Elixir ecosystem news that actually matters 👋.

. WEEKLY PICKS .

🔄 BullMQ Now Runs Natively in Elixir for Redis-Backed Job Queues: The popular Node.js queue library is fully ported to Elixir with complete interoperability. Same Lua scripts, same Redis data structures—jobs added from Node.js process in Elixir workers and vice versa. Supports priorities, delays, retries with exponential backoff, and batch operations. Built on GenServers for seamless OTP integration while maintaining cross-platform compatibility with Node.js and Python implementations.

🧪 MquickjsEx Embeds JavaScript in Elixir Without Node.js Dependencies: Run JavaScript inside your Elixir process using MQuickJS engine via NIFs. No subprocess spawning, no IPC overhead—just 64KB heap (configurable down to 10KB) for sandboxed LLM-generated code execution. Bidirectional function calls let you expose Elixir functions to JS and vice versa. Perfect for executing AI-generated scripts with controlled tool access and zero filesystem or network permissions.

âš¡ Building ngrok-Like Tunnels With Phoenix Channels and Rust CLI: Developer breaks down multiplexing HTTP over single WebSocket using Phoenix PubSub and ETS correlation tables. Each request gets unique ID stored in ETS, forwards through Channel to Rust CLI, proxies to localhost, returns response via same connection. Phoenix handles reconnection and heartbeats automatically. The BEAM's real-time primitives make complex tunneling architecture surprisingly straightforward compared to building equivalent system from scratch in other ecosystems.

🚫 Stop Reaching for Redis When ETS Ships With Elixir: Developer ditches Redis for native ETS caching, measures 9x faster reads (49ms vs 446ms for 1,000 lookups) and 40% CPU reduction. Physics explains it—Redis serializes data, sends over network, deserializes back. ETS offers direct memory access with zero serialization. For distributed needs, consistent hashing maps keys to specific nodes before calling, eliminating Redis Cluster complexity while utilizing Elixir nodes you already pay for.

📊 Elixir Survey 2025 Results Drop With 1,018 Participants: Biggest community survey reveals Phoenix remains most admired web framework for third consecutive year in Stack Overflow rankings. Survey covers adoption trends, tooling preferences, deployment patterns, and pain points across 45 questions. Community insights show continued growth in LiveView usage, increasing adoption in fintech and real-time systems, and emerging patterns around AI/ML integration via Nx and Bumblebee libraries.

Your Balance Column Made You a Part-Time Accountant Until Double-Entry Finally Clicked

Someone who performed Saturday expects their cut by Tuesday. Seemed straightforward until we paid an organizer Sunday, customer requested refund Monday, and that refund had to come out of next week's payment with clear explanation. Meanwhile Tipalti charges $2.10 per payment. Fifty individual payouts meant $105 in fees bleeding the business model dry.

The instinct was simple balance tracking. Account has $500 balance, pay them $500, done. Worked beautifully until one invoice needed to show: Event ticket sales +$500, Tips +$45, Service fee split +$30, Card reader purchase -$75, Meta ad spend -$120, Net: $380. All itemized on the same statement, tracking across currencies, with retroactive adjustments when refunds hit after payments already went out. The balance column couldn't tell us why someone got paid what they got paid.

Stripe Connect, Interac E-Transfer. Everything assumed money flows one direction. But clubs run multiple shows where different accounts get paid, tips flow in during events, ad charges go out mid-week, and refunds subtract from organizers already paid days ago.

Things clicked after reading Square's payments blog on double-entry tradeoffs. Every money movement creates balanced journal entries. Cash increases (debit), accounts_payable increases (credit). Books always balance. Instead of maintaining a balance that drifts and lies, store every money movement as an event. Want to know what someone's owed? Sum the events. Refund after payment? Just another event.

Event revenue becomes: debit cash, credit accounts_payable. Refund reverses it: debit accounts_payable, credit cash. Tips, service fees, ad charges—each gets the appropriate pattern. The beauty emerges when complexity hits. Refunds are natural reverse entries with no special logic. Calculating net payout queries the accounts_payable ledger for credits minus debits—simple arithmetic on immutable history. Multi-currency? Just another ledger attribute. Retroactive adjustments? Another transaction in the chain that future queries automatically incorporate.

Here's what nobody tells you: the transaction_id foreign key felt like over-engineering. Extra column on every business table? Seemed excessive during development. Then support tickets started with "why did I get $287.43?" and we could trace: this payout → 12 source transactions → 47 individual tips → original orders. Answer in 30 seconds. Before double-entry? "Let me investigate and get back to you" meant reconstructing state from logs and educated guessing. That foreign key pays for itself every single day.

States prevent double-payments (pending → waiting_for_payout → settled). Batching groups pending transactions by currency, payment destination, and account. Each group becomes one payout. Fifty individual payouts cost $105, one batched payment costs $2.10. Create_pending_payout_transactions updates existing pending payouts rather than duplicating, so job retries produce identical results.

Remember, for payment systems:

  1. Status beats ledger math – Developers reason about status = 'pending' without understanding debits and credits

  2. Idempotency prevents midnight pages – Update existing pending payouts rather than inserting new ones; job retries can't double-pay someone

  3. Batch by payment destination – GBP to UK bank, USD to US bank need separate batches even for same account

  4. Foreign keys save support hours – Direct path from payout to original orders answers "why this amount?" in seconds not hours

. TIRED OF DEVOPS HEADACHES? .

Deploy your next Elixir app hassle-free with Gigalixir and focus more on coding, less on ops.

We're specifically designed to support all the features that make Elixir special, so you can keep building amazing things without becoming a DevOps expert.

See you next week,

Michael

P.S. Forward this to a friend who loves Elixir as much as you do 💜