Skip to main content
Issue 04
/
Product Engineering7 min read

The Art of Business Logic Modeling in Custom Web Apps

Multi-role permission systems, financial transaction flows, and supplier workflows — how to model complex real-world business logic before writing a single line of React.

Abdelghani Salama

Product Engineering

The Library Order Management system we built had a deceptively simple brief: 'manage book orders for schools.' It took two discovery sessions to uncover what that actually meant: six distinct user roles, a 30% advance payment rule tied to delivery status, a supplier module for handling missing stock from three different source types, and a report layer that needed to satisfy a tax auditor. This is real business logic. It cannot be invented; it must be modeled.

Every time a developer reaches for a switch statement to handle role logic, a domain model is crying out to be designed.

Abdelghani SalamaProduct Engineering

The first tool is a role-permission matrix. Before any code, we mapped all six roles (Admin, Seller, Logistics, Collector, Supplier Manager, External Supplier) against every entity in the system and every action on that entity. The matrix has 144 cells. Twelve of them were in conflict — two roles could theoretically perform the same destructive action. Discovering that in a spreadsheet is free. Discovering it in production is expensive.

Financial state machines are the second critical model. Money movement in this system was not a simple debit/credit. An order could be: unpaid, 30%-advanced, fully paid, or refunded-partial. Each state had legal implications for the library. We modeled it as a finite state machine with explicit allowed transitions and invalid state guards at the database level via PostgreSQL check constraints. The application layer could not accidentally move money backward.

For the supplier workflow, the challenge was representing three types of missing stock: items not ordered (procurement gap), items ordered but not delivered (supplier gap), and items delivered but wrong edition (quality gap). Each required a different resolution path. We built a discriminated union type in TypeScript that made it impossible to process a missing-stock item without specifying its gap type. The compiler became our domain expert.

The lesson from this project: the most valuable engineering work happens before the first component is mounted. A well-designed domain model compresses weeks of debugging into hours of careful thinking. The 30% advance payment logic took 20 minutes to model and 15 minutes to implement. Without the model, it would have taken two days and three bug fixes.

Filed underArchitectureFull-StackProductCase Study