Shop It Docs
Developer Resourcestraining

Training Module Backend Documentation

Internal architecture, data model, cache contracts, rate limits, and queue/runtime behavior for training enrollment.

Training - Backend Documentation

Audience: Backend developers and system integrators Scope: Admin + customer/mobile training surfaces, schema, cache, rate limit, order integration, and worker/runtime contracts.

1. Backend Scope and Boundaries

Training backend is a feature-structured NestJS domain with three surfaces:

  • Admin surface: training, session, cohort, waitlist, and enrollment administration
  • Customer/mobile surface: enrollment request, enrollment detail/list, session access, cancellation request
  • Internal async/runtime surface: order integration + BullMQ training worker contracts

Primary boundaries:

  • Commerce dependency: training enrollments are tied to product rows with productType = "training"
  • Cart bypass: training products are intentionally excluded from cart and use dedicated enrollment flow
  • Order ownership: enrollment request creates training order + payment initiation synchronously; payment success finalizes enrollment/access
  • Shared entitlement: product_access is upserted for paid training completion (same entitlement table used by other digital products)

2. Module Composition (Aggregate + Leaf)

2.1 Training module layout

  • TrainingModule
    • shared/helper provider: TrainingSharedHelperService
    • imports: DatabaseModule, RedisModule, OrderModule, ProductAccessModule, TrainingProcessorModule
  • TrainingAdminAggregateModule
    • leaf modules:
      • TrainingAdminModule
      • SessionAdminModule
      • CohortAdminModule
      • TrainingEnrollmentAdminModule
  • TrainingCustomerAggregateModule
    • leaf modules:
      • TrainingCustomerModule
      • ProductAccessModule
  • TrainingProcessorModule
    • BullMQ queue registration: QueueName.TRAINING
    • processor: TrainingProcessor
    • provides TrainingSharedHelperService directly to avoid circular module import with TrainingModule

2.2 API route ownership

SurfacePrefixControllerSwagger Tag
Admin training/api/admin/trainingTrainingAdminControllerTraining (Admin)
Admin sessions/api/admin/training/:trainingId/sessionsSessionAdminControllerTraining Session (Admin)
Admin cohorts/api/admin/training/:trainingId/cohortsCohortAdminControllerTraining Cohort (Admin)
Admin enrollments/api/admin/training/enrollmentsTrainingEnrollmentAdminControllerTraining Enrollment (Admin)
Customer/mobile/api/training and /api/mobile/trainingTrainingCustomerControllerTraining Enrollment (Mobile)

2.3 Mobile composition

mobile.module.ts mounts TrainingCustomerAggregateModule under path: "mobile" via RouterModule.register(...). Customer controller path stays @Controller("training"), so mobile-prefixed routes are produced without embedding mobile in controller paths.


3. Data Model (Drizzle / PostgreSQL)

Training schema source of truth: packages/db/src/schema/training/*.

3.1 Core tables

TablePurposeNotable constraints/indexes
trainingTraining metadata linked to productunique product_id, indexes on category_id, status; title is sourced from products.title via product_id
training_sessionSession units for a training productindexes on training_id, status, session_order
training_cohortCapacity-based batch/cohortunique (training_id, cohort_name), checks on max_seats > 0, enrolled_count >= 0
training_cohort_sessionCohort-session join with schedule overridecomposite PK (cohort_id, session_id)
training_enrollment_form_configOptional per-training form schemaunique training_id
training_waitlistOverflow queue for full cohortsunique (user_id, cohort_id), indexes on FK columns
training_enrollmentUser enrollment state and access metadataindexes on user_id, product_id, order_id, cohort_id, session_id, status; partial unique index training_enrollment_enrolled_unique on (user_id, product_id) where status='enrolled'

3.2 Enums

EnumValues
training_statusdraft, published, hidden, archived
training_session_typerecorded, live, both
training_session_statusscheduled, live, completed, cancelled
training_cohort_statusopen, closed, full, cancelled
training_waitlist_statuswaiting, promoted, expired, cancelled
training_access_typelifetime, time_limited
training_enrollment_statuspending, enrolled, cancelled

3.3 Referential integrity highlights

  • training.product_id -> products.id (cascade)
  • training_session.training_id -> products.id (cascade)
  • training_cohort.training_id -> products.id (cascade)
  • training_waitlist.user_id -> customers.id (cascade)
  • training_waitlist.cohort_id -> training_cohort.id (cascade)
  • training_enrollment.user_id -> customers.id (cascade)
  • training_enrollment.product_id -> products.id (cascade)
  • training_enrollment.order_id -> orders.id (set null)
  • training_enrollment.cohort_id -> training_cohort.id (set null)
  • training_enrollment.session_id -> training_session.id (set null)

4. Runtime Rules and Domain Invariants

4.1 Product and training validation

TrainingSharedHelperService.assertTrainingProduct(...) enforces:

  • product exists
  • productType === "training"
  • product is sellable and published

4.2 Enrollment invariants

TrainingCustomerService.enroll(...) enforces:

  • no active duplicate enrollment for (userId, productId) except reusable pending row
  • optional cohort/session must belong to training
  • required form fields must satisfy configured formSchema.fields[*].required
  • advisory lock is used for concurrent same-user/product enrollment writes
  • database enforces one terminal enrolled row per (user_id, product_id) via partial unique index
  • pending lifecycle remains compatible with order_id IS NULL (no num_nonnulls enforcement on pending rows)

4.3 Status transitions and guardrails

  • Training status transition map:
    • draft -> published
    • published -> hidden
    • hidden -> published
    • archived -> (none)
  • Cohort status transition map:
    • open -> closed/full/cancelled
    • closed -> open/cancelled
    • full -> open/cancelled
    • cancelled -> (none)
  • Customer cancellation:
    • pending cancellation rejected
    • enrolled -> cancelled allowed
    • already cancelled is idempotent

4.4 Capacity and seat-count adjustments

  • Cohort enrolledCount increments when enrollment becomes enrolled
  • For payment-success transitions, increment happens in order processor while promoting pending enrollment to enrolled.
  • Cohort enrolledCount decrements on cancellation (bounded with greatest(..., 0))
  • Cohort/session assignment validates all session IDs belong to same training and disallows duplicate session IDs in payload

4.5 Session access invariants

For GET /training/:trainingId/sessions/:sessionId/content:

  • user must have enrolled training enrollment
  • session must belong to training
  • access resolution order:
    1. recording available + valid recording URL -> accessibleVia = "recording"
    2. else if session status is live -> accessibleVia = "live"
    3. else throw domain error (TRAINING_SESSION_NOT_LIVE or TRAINING_RECORDING_NOT_AVAILABLE)

5. Caching Strategy

5.1 Keyspaces

Key prefixUsed by
training:admin:list:Admin training list
training:admin:detail:Admin training detail
training:session:admin:list:Admin session list by training
training:session:customer:list:Customer accessible sessions
training:cohort:admin:list:Admin cohort list
training:cohort:waitlist:Cohort waitlist list
training:enrollment:list:Customer enrollment list
training:enrollment:detail:Customer enrollment detail
training:enrollment:admin:list:Admin enrollment list
training:enrollment:admin:detail:Admin enrollment detail
training:product-access:user:Product access rows by user

All keys are deterministic via CacheKeyUtil.build(...) and segment normalization.

5.2 Invalidation patterns

Invalidation is prefix-based via invalidatePattern(prefix*).

Common invalidations:

  • enrollment writes -> enrollment list/detail + customer session list keyspaces
  • session writes -> training session admin list keyspace
  • cohort writes -> cohort list and waitlist keyspaces
  • payment success -> enrollment + product-access keyspaces (order processor)

5.3 TTL

  • General training cache TTL is REDIS_CACHE_TTL_SECONDS.
  • Redis failures degrade gracefully: services log warning and continue with DB path.

6. Rate Limiting Strategy

Controllers use Redis sliding-window ZSET logic:

  1. remove expired members
  2. add request marker (UUIDv7 or timestamp+UUIDv7)
  3. count window
  4. set key expiry
  5. reject with RateLimitExceededException when threshold exceeded

Rate-limit env/config:

  • admin controllers: TRAINING_ADMIN_RATE_LIMIT, TRAINING_ADMIN_RATE_WINDOW_SECONDS
  • customer controller: ORDER_CHECKOUT_RATE_LIMIT, ORDER_CHECKOUT_RATE_WINDOW_SECONDS
  • discovery/customer-content path: CONTENT_CUSTOMER_ITEM_RATE_LIMIT, CONTENT_CUSTOMER_ITEM_RATE_WINDOW_SECONDS

Rate-limit key prefixes:

  • admin: rl:training:admin:
  • customer: rl:training:customer:

If Redis rate-limit check fails, request is allowed and warning is logged (graceful degradation).


7. Order and Queue Runtime

7.1 Primary enrollment runtime (current production path)

TrainingCustomerService.enroll(...) creates training artifacts synchronously:

  • pending training_enrollment row (new or reused pending enrollment)
  • training order row (orderType = "training", paymentStatus = "pending", orderStatus = "payment_pending")
  • pending payment record + payment initiation payload
  • async_requests row (scope = "training_enrollment") for deterministic idempotency replay
  • unpaid-order cancellation window: ORDER_AUTO_CANCEL_MINUTES (default 30)

After redirect verification, order jobs finalize payment state. On payment success, OrderProcessor upserts training enrollment access fields plus product access. Training enrollment finalization is owned by this order-processor path so seat-count and access updates stay in one runtime flow.

7.2 Training queue contract (internal worker surface)

packages/jobs defines queue/job contracts:

  • queue: QueueName.TRAINING ("training")
  • jobs:
    • TrainingJob.CREATE_ENROLLMENT
    • TrainingJob.CANCEL_ENROLLMENT

Payloads:

  • CreateTrainingEnrollmentPayload:
    • requestId, correlationId, userId, productId, cohortId, sessionId, formData, paymentMethod
  • CancelTrainingEnrollmentPayload:
    • requestId, correlationId, enrollmentId, userId, reason?, cancelledBy

7.2.1 Outbox cleanup relationship

Training payment and lifecycle notifications that are persisted through shared outbox_events are cleaned by the order maintenance cleanup job.

Retention behavior:

  • completed outbox rows older than OUTBOX_RETENTION_DAYS are deleted.
  • failed outbox rows older than OUTBOX_FAILED_RETENTION_DAYS are deleted.
  • pending rows are never deleted by cleanup.

This retention behavior is implemented in order maintenance runtime (orders_maintenance queue) for shared outbox rows that use standard terminal statuses (completed/failed). Dispatch ownership remains target-queue scoped.

7.3 Training processor behavior

TrainingProcessor logs with [start], [success], [skip], [failure] patterns.

  • processCreateEnrollment:
    • calls OrderService.createTrainingOrder(...)
    • returns { success, orderId }
  • processCancelEnrollment:
    • idempotency check against completed async_requests
    • advisory lock on enrollment ID
    • validates ownership for user-initiated cancellation
    • updates enrollment + cohort seat count in one transaction
    • marks async_requests completed when request id is present

8. Error Contracts (Training)

TRAINING_* error codes from common/types/error-codes.ts:

CodeMeaning
TRAINING_ENROLLMENT_NOT_FOUNDEnrollment row not found
TRAINING_PRODUCT_REQUIREDProduct type is not training
TRAINING_PRODUCT_UNAVAILABLETraining product not sellable/published
TRAINING_NOT_FOUNDTraining row not found
TRAINING_SLUG_DUPLICATESlug conflict
TRAINING_FORM_CONFIG_NOT_FOUNDMissing form config
TRAINING_PRODUCT_CREATION_FAILEDProduct/training creation failed
TRAINING_INVALID_STATUS_TRANSITIONInvalid training lifecycle transition
TRAINING_SESSION_NOT_FOUNDSession row not found
TRAINING_SESSION_MISMATCHSession does not belong to training
TRAINING_SESSION_CREATE_FAILEDSession creation failed
TRAINING_SESSION_DELETE_FAILEDSession delete failed
TRAINING_SESSION_NOT_LIVELive session access attempted before live state
TRAINING_RECORDING_NOT_AVAILABLERecording not available
TRAINING_ALREADY_ENROLLEDDuplicate active enrollment
TRAINING_INVALID_FORM_DATARequired form data missing/invalid
TRAINING_ENROLLMENT_CANCEL_NOT_ALLOWEDCancellation forbidden for current state
TRAINING_COHORT_NOT_FOUNDCohort row not found
TRAINING_COHORT_MISMATCHCohort does not belong to training
TRAINING_COHORT_CREATE_FAILEDCohort creation failed
TRAINING_COHORT_DUPLICATE_NAMEDuplicate cohort name within training
TRAINING_COHORT_INVALID_STATUS_TRANSITIONInvalid cohort status transition
TRAINING_COHORT_CANCELLEDCohort already cancelled
TRAINING_COHORT_CLOSEDCohort closed for enrollment
TRAINING_COHORT_FULLCohort full/no seat
TRAINING_COHORT_CAPACITY_EXCEEDEDmaxSeats lower than enrolledCount
TRAINING_WAITLIST_NOT_FOUNDWaitlist row not found
TRAINING_WAITLIST_MISMATCHWaitlist row not in cohort
TRAINING_WAITLIST_DUPLICATEDuplicate waitlist entry
TRAINING_COHORT_SESSION_ASSIGN_FAILEDSession assignment payload/state invalid

Related cross-module training flow errors:

  • CART_TRAINING_ENROLLMENT_FLOW
  • ORDER_TRAINING_FLOW_REQUIRED

9. Performance and Resilience Notes

  • Enrollment, cancellation, and order artifact updates use DB transactions for atomicity.
  • Enrollment and cancellation paths use advisory locks to prevent conflicting concurrent writes.
  • Expensive list/detail endpoints are cache-aside with deterministic keys.
  • Query projections are explicit; no SELECT * patterns in training services.
  • Independent reads in admin enrollment detail use Promise.all (order + user + training metadata).

10. Backend Diagrams

10.1 Admin composition

10.2 Enrollment runtime (customer -> order -> artifact)

10.3 Cancellation runtime

11. File Map

ConcernFile PathPurpose
Admin trainingapps/api/src/modules/training/admin/*Training CRUD
Admin sessionapps/api/src/modules/training/admin/session/*Session management
Admin cohortapps/api/src/modules/training/admin/cohort/*Cohort management
Admin enrollmentapps/api/src/modules/training/admin/enrollment/*Enrollment management
Customer enrollmentapps/api/src/modules/training/customer/*Enrollment requests
Processingapps/api/src/modules/training/processing/*BullMQ processor
DB schemapackages/db/src/schema/training/*Table definitions

12. Environment Variables

VariableDefaultDescription
TRAINING_ADMIN_RATE_LIMIT-Rate limit for admin training operations
TRAINING_ADMIN_RATE_WINDOW_SECONDS-Rate limit window for admin training
ORDER_CHECKOUT_RATE_LIMIT10Rate limit for checkout requests
ORDER_CHECKOUT_RATE_WINDOW_SECONDS60Rate limit window for checkout
CONTENT_CUSTOMER_ITEM_RATE_LIMIT30Rate limit for customer content access
CONTENT_CUSTOMER_ITEM_RATE_WINDOW_SECONDS60Rate limit window for content access
ORDER_AUTO_CANCEL_MINUTES30Minutes before unpaid orders are auto-cancelled

Time fields in this module are stored as timezone-aware values and should be handled as ISO-8601 instants by API consumers.


See Also