Files
zod-backend/ALLOWANCE_SCHEDULING_SYSTEM.md
2026-01-28 16:03:41 +03:00

8.5 KiB

Allowance Scheduling System (Backend)

This document captures the complete allowance scheduling feature as implemented in the backend. It is intended as a long-term reference for how the system works, why it was built this way, and how to operate/extend it safely.

Goals and Scope

  • Allow parents/guardians to create a recurring allowance schedule for a child.
  • Credit the allowance automatically based on the schedule.
  • Scale safely with multiple workers and avoid duplicate credits.
  • Keep cron lightweight and offload work to RabbitMQ workers.

Non-goals in the current implementation:

  • UI changes
  • Analytics/reporting views
  • Advanced scheduling (e.g., custom weekdays)

High-Level Flow

  1. Parent creates a schedule via API.
  2. Cron runs every 5 minutes and enqueues due schedules into RabbitMQ.
  3. Workers consume queue messages, credit the allowance, and update the next run.
  4. Idempotency is enforced in the database to prevent duplicate credits.

Data Model

Table: allowance_schedules

Purpose: Stores the schedule definition (amount, frequency, status, next run).

Key fields:

  • guardian_id, junior_id: who funds and who receives.
  • amount: the allowance amount.
  • frequency: DAILY / WEEKLY / MONTHLY.
  • status: ON / OFF.
  • next_run_at, last_run_at: scheduling metadata.

Constraints:

  • Unique (guardian_id, junior_id) ensures one schedule per child.

Entity: src/allowance/entities/allowance-schedule.entity.ts

Table: allowance_credits

Purpose: Audit log and idempotency guard for each credit run.

Key fields:

  • schedule_id: which schedule executed.
  • transaction_id: the resulting transaction (nullable).
  • amount, run_at, credited_at.

Idempotency:

  • Unique (schedule_id, run_at) prevents duplicates even with multiple workers.

Entity: src/allowance/entities/allowance-credit.entity.ts

Table: cron_runs

Purpose: shared audit table for all cron jobs (not just allowance).

Key fields:

  • job_name: unique identifier for the cron.
  • status: SUCCESS / FAILED.
  • processed_count: number of schedules processed.
  • error_message: failure reason.
  • started_at, finished_at.

Entity: src/cron/entities/cron-run.entity.ts

API (Schedule Creation)

Endpoint:

  • POST /guardians/me/allowances/:juniorId

Input DTO:

  • amount (required, numeric, positive)
  • frequency (required, enum)
  • status (required, enum)

DTO: src/allowance/dtos/request/create-allowance-schedule.request.dto.ts

Response DTO:

  • AllowanceScheduleResponseDto with schedule fields.

DTO: src/allowance/dtos/response/allowance-schedule.response.dto.ts

Business validation:

  • Child must belong to guardian.
  • No duplicate schedule for the same guardian+child.

Service: src/allowance/services/allowance.service.ts

Cron Producer (Queue Enqueue)

Cron job:

  • Runs every 5 minutes.
  • Batches schedules (cursor-based) to avoid large load.
  • Enqueues each schedule to RabbitMQ.

Cron: src/cron/tasks/allowance-schedule.cron.ts

Batch behavior:

  • Batch size = 100
  • Uses a cursor (nextRunAt, id) for stable pagination.
  • Prevents re-reading the same rows.

Locking:

  • BaseCronService uses cache lock to ensure only one instance runs.
  • Each cron run is logged to cron_runs with status and processed count.

Lock: src/cron/services/base-cron.service.ts

Queue Publisher

Queue publisher:

  • Asserts queue + retry/DLQ exchanges.
  • Enqueues jobs with message id (for traceability).

Service: src/allowance/services/allowance-queue.service.ts

Queue names:

  • allowance.schedule (main)
  • allowance.schedule.retry (retry with TTL)
  • allowance.schedule.dlq (dead-letter queue)

Constants: src/allowance/constants/allowance-queue.constants.ts

Worker Consumer (Transfers)

Worker:

  • Consumes allowance.schedule queue.
  • Validates schedule is due and active.
  • Creates allowance_credits record for idempotency.
  • Transfers money via cardService.transferToChild.
  • Updates last_run_at and next_run_at.

Worker: src/allowance/services/allowance-worker.service.ts

Transfer:

  • Uses existing logic in card.service.ts for balance updates and transaction creation.

Service: src/card/services/card.service.ts

Idempotency details

  1. Worker inserts allowance_credits row first.
  2. Unique constraint blocks duplicates.
  3. If transfer fails, the credit row is removed so the job can retry.

This makes multiple workers safe.

Retry + DLQ Strategy

Retry delay:

  • Failed jobs are dead-lettered to retry queue.
  • Retry queue has messageTtl = 10 minutes.
  • After TTL, job is routed back to main queue.

DLQ:

  • If a job fails ALLOWANCE_MAX_RETRIES times (default 5), it is routed to the DLQ.
  • This prevents endless loops and allows manual inspection.

Config:

  • ALLOWANCE_MAX_RETRIES (default 5)

Redis Usage

Redis is used by BaseCronService to enforce a distributed lock for the cron job. This prevents multiple backend instances from enqueuing the same schedules at the same time.

Lock behavior:

  • If the lock key exists, cron exits early.
  • If the lock key is absent, cron sets it with a TTL and proceeds.
  • TTL ensures the lock is released even if a node crashes.

Service: src/cron/services/base-cron.service.ts

RabbitMQ Setup and Behavior

The allowance system uses RabbitMQ for asynchronous processing. Cron publishes due schedules, and workers consume them.

Exchanges and Queues

Main queue:

  • allowance.schedule

Retry setup:

  • Exchange: allowance.schedule.retry.exchange
  • Queue: allowance.schedule.retry
  • Binding key: allowance.schedule
  • TTL: 10 minutes
  • Dead-letter route: back to allowance.schedule

Dead-letter queue (DLQ):

  • Exchange: allowance.schedule.dlq.exchange
  • Queue: allowance.schedule.dlq
  • Binding key: allowance.schedule

Flow Summary

  1. Cron enqueues jobs to allowance.schedule.
  2. Worker consumes jobs from allowance.schedule.
  3. On failure, job is dead-lettered to allowance.schedule.retry.
  4. After 10 minutes, it returns to allowance.schedule.
  5. After max retries, worker publishes the job to allowance.schedule.dlq.

Where it is configured

  • Publisher setup: src/allowance/services/allowance-queue.service.ts
  • Worker consumer: src/allowance/services/allowance-worker.service.ts
  • Queue constants: src/allowance/constants/allowance-queue.constants.ts

Environment Variables

  • RABBITMQ_URL (required for queue/worker)
  • ALLOWANCE_QUEUE_NAME (optional, defaults to allowance.schedule)
  • ALLOWANCE_MAX_RETRIES (optional, defaults to 5)
  • ALLOWANCE_RETRY_DELAY_MS (optional, defaults to 10 minutes)

Example .env snippet

RABBITMQ_URL=amqp://guest:guest@localhost:5672
ALLOWANCE_QUEUE_NAME=allowance.schedule
ALLOWANCE_MAX_RETRIES=5
ALLOWANCE_RETRY_DELAY_MS=600000

Operational Checklist

  • Ensure Redis is running (cron locking).
  • Ensure RabbitMQ is running (queue + workers).
  • Start at least one worker process.
  • Monitor DLQ for failures.

Manual Test Checklist

  1. Create schedule:
    • POST /guardians/me/allowances/:juniorId
    • Valid amount, frequency, status.
  2. Duplicate schedule:
    • Expect ALLOWANCE.ALREADY_EXISTS.
  3. Cron enqueue:
    • Wait for cron interval or manually trigger.
    • Confirm messages appear in RabbitMQ.
  4. Worker:
    • Ensure worker is running.
    • Verify transfers happen and allowance_credits is created.
  5. Failure paths:
    • Simulate transfer failure and verify retry queue behavior.
    • Confirm DLQ after max retries.

Operational Notes

  • For large volumes, scale workers horizontally.
  • Keep cron lightweight; do not perform transfers in cron.
  • Monitor queue depth and DLQ entries.

Known Limitations (Current)

  • Only one schedule per child (guardian+junior unique).
  • No custom weekdays or complex schedules.
  • Retry delay is fixed at 10 minutes (can be configurable).

File Map (Quick Reference)

  • API:

    • src/allowance/controllers/allowance.controller.ts
    • src/allowance/services/allowance.service.ts
    • src/allowance/dtos/request/create-allowance-schedule.request.dto.ts
    • src/allowance/dtos/response/allowance-schedule.response.dto.ts
  • Cron:

    • src/cron/tasks/allowance-schedule.cron.ts
    • src/cron/services/base-cron.service.ts
  • Queue/Worker:

    • src/allowance/services/allowance-queue.service.ts
    • src/allowance/services/allowance-worker.service.ts
    • src/allowance/constants/allowance-queue.constants.ts
  • Repositories:

    • src/allowance/repositories/allowance-schedule.repository.ts
    • src/allowance/repositories/allowance-credit.repository.ts
  • Entities:

    • src/allowance/entities/allowance-schedule.entity.ts
    • src/allowance/entities/allowance-credit.entity.ts