engineering

Time Is Part of the Business Logic

0:00 / 0:00

When a business rule includes time, the system can act at the relevant boundary instead of polling every day.

4 min read
Yurii Rashkovskii
Yurii Rashkovskii
Founder
Yogurt cups dated July 26 above an electronic shelf label applying a 50% discount on July 25

One day before a product’s best-before date, set its price to 50% of the regular price.

Simple enough. Then we implement it as a daily job.

The Good Old Way

A common design starts with a scheduled job. Once a day, it queries for products whose best-before date is tomorrow and updates the matching prices.

The cron job works, but now the rule comes with a system you have to operate.

A daily SQL job branching into concerns about duplicate-safe execution, late-arriving products, batching, observability, provenance, and failure handling.
The good old way: the business rule becomes an architecture and maintenance problem.

Start with the gap between runs. The job runs at 2 a.m. A July 26 yogurt goes on the shelf at 9 a.m. on July 25. By the time the job runs again, its best-before date is today, not tomorrow. A query for best_before = tomorrow never sees it.

You can widen the query to include overdue products, but then it may repeatedly select prices that are already correct. The update must be idempotent, the query must compare desired and actual state, or the job must track what it completed. Running it every hour only shrinks the gap. Reacting when a product reaches the shelf handles the arrival, but the inventory feed doesn’t emit another event merely because the calendar advances. The application still needs a durable timer for that boundary.

At chain scale, teams often split the updates into batches. Depending on the price system, that introduces choices about batch size, pagination, checkpoints, and products added during a scan. If a batch fails after some shelf labels accept the new price, the job has to decide what to retry.

A careless translation can also turn “set the price to 50% of regular” into “halve the current price.” That operation isn’t idempotent. A retry can turn a $5.98 product into $2.99, then $1.50. A stable operation identity and an idempotent update make an ambiguous retry safe. To determine whether the work finished, the job still needs a trustworthy acknowledgment or a read back from the price system.

A successful cron exit isn’t enough. Did every eligible product change price? Was the inventory feed current? A green job that processed stale input still failed.

When a store manager asks why a yogurt costs $2.99, the answer needs the rule version, regular price, best-before date, shelf status, store calendar, activation time, update request, and observed outcome. Without that provenance, the explanation is scattered across scheduler logs, database rows, and the price system’s history.

At chain scale, the one-line pricing rule now needs a durable timer, batching, retries, deduplication, monitoring, and an audit trail. The schedule has become a substitute for the date already present in the rule.

For one store and one rule, cron may be a tolerable trade. Across thousands of stores, each new dated rule adds another timer, recovery policy, and audit trail. Promotions, recalls, and local calendars then have to agree across jobs that know nothing about one another. Cron still runs. The surrounding coordination is what becomes untenable.

Run It When It Can Match

A conventional database answers the application’s current question. It doesn’t retain that question as a standing instruction. We explored that distinction in Databases Don’t Know Why You’re Asking.

Inferal retains the rule instead of the query. When a product reaches the date boundary, Inferal materializes a price-adjustment instruction for that product lot. An output adapter delivers the instruction to the store’s price system.

For a yogurt dated July 26, Inferal can wait until July 25. It revisits the rule sooner only if a relevant fact changes.

If a July 26 yogurt is first recorded on the shelf on July 25, the rule matches immediately. Correcting its best-before date before activation moves the pending boundary with it.

One Match, One Price Adjustment

On July 25, the yogurt dated July 26 is on the shelf. The rule’s conditions are now true, so Inferal records one result for that product lot: set its price to 50% of the regular price.

That record is how Inferal knows the work happened once. “Once” does not mean the rule runs once for the whole store. It means that each product lot can produce one price adjustment for this rule and this date boundary. Another lot can match the same rule. The same lot cannot produce the same adjustment twice.

There is no retry before the match because there is nothing to try. Inferal is simply waiting for the rule’s conditions to become true.

After the match, the decision and its delivery are separate. If the store’s price system is unavailable, Inferal does not run the rule again. The connector keeps delivering the same recorded price adjustment until the price system reflects it.

That separation is the point. The rule decides what should happen, once per matching product. Delivery makes outside systems reflect that decision, however long that takes. Time determines when the rule becomes applicable, not when a scheduled job happens to look.


Share