Skip to content

Event Catalog

DEPRECATED: This manual event catalog has been superseded by EventCatalog. View the live catalog at: https://staging-connect-eventcatalog.forma3d.be Source: docs/03-architecture/eventcatalog/

This document provides a comprehensive reference for all events in the Forma3D Connect system — both cross-service events that travel over Redis/BullMQ and internal events that stay within a single service process.


Architecture Overview

The system uses a two-layer event architecture:

Layer Transport Scope Library
Cross-service BullMQ (Redis) Between microservices @forma3d/service-common BullMqEventBus
Internal NestJS EventEmitter2 Within a single service process @nestjs/event-emitter

Every microservice uses EventEmitterModule.forRoot() for internal coordination and EventBusModule.forRootAsync() for cross-service messaging. Two bridge services in each app connect the layers:

  • EventPublisherService — listens to internal EventEmitter2 events via @OnEvent(...) and publishes them to BullMQ so other services can react.
  • EventSubscriberService — subscribes to incoming BullMQ events from other services and re-emits them as internal EventEmitter2 events for local modules.

Internal vs Cross-Service Event Naming

Internal event names (EventEmitter2) and cross-service event names (BullMQ queue) may differ. The bridge services handle the translation:

Internal Name (EventEmitter2) Cross-Service Name (BullMQ) Used In
printjob.completed print-job.completed Print Service → Order Service
printjob.failed print-job.failed Print Service → Order Service
printjob.status-changed print-job.status-changed Print Service → Order Service
printjob.cancelled print-job.cancelled Print Service → Order Service
(direct publish) integration.simplyprint-changed Print Service → Order Service
shipment.created shipment.created Shipping Service → Order Service
sendcloud.shipment.status_changed shipment.status-changed Shipping Service → Order Service
(direct publish) integration.sendcloud-changed Shipping Service → Order Service
order.created order.created Order Service → Print Service
order.ready-for-fulfillment order.ready-for-fulfillment Order Service → Shipping Service
order.cancelled order.cancelled Order Service → Print Service, Shipping Service

uml diagram


Cross-Service Events (BullMQ / Redis)

These 15 events travel between microservices via dedicated BullMQ queues. Each event type = one Redis queue.

Source: libs/service-common/src/lib/events/event-types.ts

Delivery Guarantees

Property Value
Delivery At-least-once
Ordering Per-queue FIFO (no cross-queue ordering)
Retries 3 attempts with exponential backoff (1s, 2s, 4s)
Concurrency 5 workers per queue per service instance
Dead letter Failed events retained (removeOnFail: 5000)
Completed cleanup removeOnComplete: 1000
Idempotency Required for all handlers
Transport BullMQ (Redis)
Library @forma3d/service-common BullMqEventBus

Base Event Interface

All cross-service events extend this base:

interface ServiceEvent {
  eventId: string;           // UUID v4 — unique event identifier for idempotency
  eventType: string;         // Queue name (e.g. 'order.created')
  source: string;            // Source service (e.g. 'order-service')
  tenantId: string;          // Tenant identifier for multi-tenancy
  timestamp: string;         // ISO 8601
  correlationId?: string;    // For distributed tracing
}

Note: The eventId and source fields were added following a CloudEvents evaluation. While full CloudEvents compliance was deemed unnecessary for internal BullMQ transport, these two fields provide the practical benefits of unique event identification (for idempotency checks) and source tracing (for debugging cross-service flows). See the research document for the full analysis.

uml diagram


Order Events (cross-service)

Publisher: Order Service (apps/order-service) Bridge: apps/order-service/src/events/event-publisher.service.ts

BullMQ Queue SERVICE_EVENTS Constant Internal Event Listened Internal Constant
order.created ORDER_CREATED order.created ORDER_EVENTS.CREATED
order.ready-for-fulfillment ORDER_READY_FOR_FULFILLMENT order.ready-for-fulfillment ORDER_EVENTS.READY_FOR_FULFILLMENT
order.cancelled ORDER_CANCELLED order.cancelled ORDER_EVENTS.CANCELLED

order.created

Published when a new order is ingested from Shopify (including via backfill).

interface OrderCreatedEvent extends ServiceEvent {
  eventType: 'order.created';
  // eventId, source, tenantId, timestamp, correlationId inherited from ServiceEvent
  orderId: string;
  lineItems: Array<{
    lineItemId: string;
    productSku: string;
    quantity: number;
  }>;
}

Flow: OrdersService.createFromShopify() → EventEmitter order.createdEventPublisherService → BullMQ Subscribers: Print Service (EventSubscriberService → re-emits as internal order.created)

order.ready-for-fulfillment

Published when all print jobs for an order are complete.

interface OrderReadyForFulfillmentEvent extends ServiceEvent {
  eventType: 'order.ready-for-fulfillment';
  orderId: string;
}

Flow: OrchestrationService.markOrderReadyForFulfillment() → EventEmitter order.ready-for-fulfillmentEventPublisherService → BullMQ Subscribers: Shipping Service (EventSubscriberService → re-emits as internal orchestration.order.ready_for_fulfillmentSendcloudService)

order.cancelled

Published when an order is cancelled.

interface OrderCancelledEvent extends ServiceEvent {
  eventType: 'order.cancelled';
  orderId: string;
}

Flow: OrdersService.cancelOrder() → EventEmitter order.cancelledEventPublisherService → BullMQ Subscribers: Print Service (EventSubscriberService → re-emits as order.cancelled), Shipping Service (EventSubscriberService → re-emits as order.cancelled)


Publisher: Print Service (apps/print-service) Bridge: apps/print-service/src/events/event-publisher.service.ts

BullMQ Queue SERVICE_EVENTS Constant Internal Event Listened Internal Constant
print-job.completed PRINT_JOB_COMPLETED printjob.completed PRINT_JOB_EVENTS.COMPLETED
print-job.failed PRINT_JOB_FAILED printjob.failed PRINT_JOB_EVENTS.FAILED
print-job.status-changed PRINT_JOB_STATUS_CHANGED printjob.status-changed PRINT_JOB_EVENTS.STATUS_CHANGED
print-job.cancelled PRINT_JOB_CANCELLED printjob.cancelled PRINT_JOB_EVENTS.CANCELLED

Note: Internal event names use printjob.* (no hyphen) while cross-service names use print-job.* (with hyphen). The EventPublisherService listens via PRINT_JOB_EVENTS.* constants and translates to SERVICE_EVENTS.* constants for BullMQ.

interface PrintJobCompletedEvent extends ServiceEvent {
  eventType: 'print-job.completed';
  printJobId: string;
  orderId: string;
  lineItemId: string;
}

Subscribers: Order Service (EventSubscriberService → fetches full PrintJob from DB → re-emits as internal printjob.completed with PrintJobCompletedEventOrchestrationService.handlePrintJobCompleted())

interface PrintJobFailedEvent extends ServiceEvent {
  eventType: 'print-job.failed';
  printJobId: string;
  orderId: string;
  lineItemId: string;
  errorMessage: string;
}

Subscribers: Order Service (EventSubscriberService → fetches full PrintJob from DB → re-emits as internal printjob.failed with PrintJobFailedEventOrchestrationService.handlePrintJobFailed())

interface PrintJobStatusChangedEvent extends ServiceEvent {
  eventType: 'print-job.status-changed';
  printJobId: string;
  orderId: string;
  lineItemId: string;
  previousStatus: string;
  newStatus: string;
}

Subscribers: Order Service (EventSubscriberService → forwarded to orchestration)

interface PrintJobCancelledEvent extends ServiceEvent {
  eventType: 'print-job.cancelled';
  printJobId: string;
  orderId: string;
  lineItemId: string;
}

Subscribers: Order Service (EventSubscriberService → fetches full PrintJob from DB → re-emits as internal printjob.cancelled with PrintJobCancelledEventOrchestrationService.handlePrintJobCancelled())


Shipment Events (cross-service)

Publisher: Shipping Service (apps/shipping-service) Bridge: apps/shipping-service/src/events/event-publisher.service.ts

BullMQ Queue SERVICE_EVENTS Constant Internal Event Listened Internal Constant
shipment.created SHIPMENT_CREATED shipment.created SHIPMENT_EVENTS.CREATED
shipment.status-changed SHIPMENT_STATUS_CHANGED sendcloud.shipment.status_changed SENDCLOUD_WEBHOOK_EVENTS.SHIPMENT_STATUS_CHANGED

Note: The shipment.status-changed cross-service event is triggered by the internal sendcloud.shipment.status_changed event (emitted by SendcloudWebhookService and SendcloudReconciliationService). The EventPublisherService translates between the naming conventions.

shipment.created

interface ShipmentCreatedEvent extends ServiceEvent {
  eventType: 'shipment.created';
  shipmentId: string;
  orderId: string;
  trackingNumber: string | null;
  trackingUrl: string | null;
  carrier: string | null;
}

Flow: SendcloudService.createShipment() → EventEmitter shipment.createdEventPublisherService → BullMQ Subscribers: Order Service (EventSubscriberServiceFulfillmentService creates Shopify fulfillment)

shipment.status-changed

interface ShipmentStatusChangedEvent extends ServiceEvent {
  eventType: 'shipment.status-changed';
  shipmentId: string;
  orderId: string;
  previousStatus: string;
  newStatus: string;
}

Flow: SendcloudWebhookService.processStatusChange() or SendcloudReconciliationService → EventEmitter sendcloud.shipment.status_changedEventPublisherService → BullMQ Subscribers: Order Service (EventSubscriberService → updates order shipment status)


GridFlock Events (cross-service)

Publisher: GridFlock Service (apps/gridflock-service) Bridge: apps/gridflock-service/src/events/event-publisher.service.ts

BullMQ Queue SERVICE_EVENTS Constant Internal Event Listened
gridflock.mapping-ready GRIDFLOCK_MAPPING_READY gridflock.mapping-ready
gridflock.pipeline-failed GRIDFLOCK_PIPELINE_FAILED gridflock.pipeline-failed

gridflock.mapping-ready

Published when the full pipeline completes (STL → slice → upload → mapping) or an existing mapping is found.

interface GridflockMappingReadyEvent extends ServiceEvent {
  eventType: 'gridflock.mapping-ready';
  orderId: string;
  lineItemId: string;
  sku: string;                      // e.g. "GRID-2x3-MAG-NONE"
}

Subscribers: Order Service (EventSubscriberServiceOrchestrationService.handleGridflockMappingReady())

gridflock.pipeline-failed

interface GridflockPipelineFailedEvent extends ServiceEvent {
  eventType: 'gridflock.pipeline-failed';
  orderId: string;
  lineItemId: string;
  sku: string;
  errorMessage: string;
  failedStep: 'stl-generation' | 'slicing' | 'simplyprint-upload' | 'mapping-creation';
}

Subscribers: Order Service (EventSubscriberService → marks line item as FAILED)


Inventory Events (cross-service)

Publisher: Order Service (apps/order-service) Bridge: apps/order-service/src/events/event-publisher.service.ts

BullMQ Queue SERVICE_EVENTS Constant Internal Event Listened Internal Constant
inventory.stock-replenishment-scheduled STOCK_REPLENISHMENT_SCHEDULED (direct publish)
inventory.stock-batch-completed STOCK_BATCH_COMPLETED (direct publish)

inventory.stock-replenishment-scheduled

Published when the stock replenishment scheduler creates a print job for stock building. One event per print job in the batch.

interface StockReplenishmentScheduledEvent extends ServiceEvent {
  eventType: 'inventory.stock-replenishment-scheduled';
  printJobId: string;
  fileId: string | null;
  purpose: 'STOCK';
  stockBatchId: string;
}

Flow: StockReplenishmentService.evaluateAndSchedule()BullMQEventBus.publish() → BullMQ Subscribers: Print Service (EventSubscriberService → creates print job via SimplyPrint)

inventory.stock-batch-completed

Published when all print jobs in a stock batch have completed, triggering atomic stock increment.

interface StockBatchCompletedEvent extends ServiceEvent {
  eventType: 'inventory.stock-batch-completed';
  stockBatchId: string;
  productMappingId: string;
  totalJobs: number;
}

Flow: InventoryService.handleStockJobCompleted()BullMQEventBus.publish() → BullMQ Subscribers: (informational — consumed by monitoring/logging)


Integration Events (cross-service)

Purpose: Notify consumer services when third-party integration credentials change so they can re-initialize their API clients immediately, without requiring a container restart.

Unlike other cross-service events, these are published directly via BullMQEventBus.publish() from *ConnectionService classes (not bridged from EventEmitter2 via EventPublisherService).

BullMQ Queue SERVICE_EVENTS Constant Publisher Subscriber(s)
integration.simplyprint-changed INTEGRATION_SIMPLYPRINT_CHANGED Print Service Order Service
integration.sendcloud-changed INTEGRATION_SENDCLOUD_CHANGED Shipping Service Order Service

integration.simplyprint-changed

Published when a user connects or disconnects the SimplyPrint integration via Settings > Integrations.

interface IntegrationChangedEvent extends ServiceEvent {
  eventType: 'integration.simplyprint-changed' | 'integration.sendcloud-changed';
  action: 'connected' | 'disconnected';
}

Flow: SimplyPrintConnectionService.saveConnection() or .disconnect()BullMQEventBus.publish() → BullMQ Subscribers: Order Service (EventSubscriberServiceSimplyPrintInitializerService.reinitialize() or .disable())

integration.sendcloud-changed

Published when a user connects or disconnects the Sendcloud integration via Settings > Integrations.

// Same IntegrationChangedEvent interface as above

Flow: SendcloudConnectionService.saveConnection() or .disconnect()BullMQEventBus.publish() → BullMQ Subscribers: Order Service (EventSubscriberServiceSendcloudInitializerService.reinitialize() or .disable())

Architecture Note: These events bypass the EventEmitter2 → EventPublisherService bridge pattern used by other cross-service events. The *ConnectionService publishes directly to BullMQ because the credential change is already being handled locally (the owning service re-initializes its own API client inline), so there is no need for an internal event — only downstream services need to be notified.


Internal Events (EventEmitter2)

These events stay within a single service process. They are not transmitted over Redis. Each service defines its own event constants and event classes.

uml diagram


Order Service — Internal Events

Source: apps/order-service/src/orders/events/order.events.ts

ORDER_EVENTS

Constant Event Name Emitter Subscribers
CREATED order.created OrdersService.createFromShopify() OrchestrationService, EventPublisherService → BullMQ, EventsGateway → Socket.IO, PushService → Web Push
STATUS_CHANGED order.status_changed OrdersService.updateStatus() EventsGateway → Socket.IO, PushService → Web Push
CANCELLED order.cancelled OrdersService.cancelOrder() CancellationService, EventPublisherService → BullMQ, EventsGateway → Socket.IO
READY_FOR_FULFILLMENT order.ready-for-fulfillment OrchestrationService.markOrderReadyForFulfillment() FulfillmentService, EventPublisherService → BullMQ, EventsGateway → Socket.IO
FULFILLED order.fulfilled FulfillmentService EventsGateway → Socket.IO
FAILED order.failed OrchestrationService EventsGateway → Socket.IO

Source: apps/order-service/src/print-jobs/events/print-job.events.ts

These are re-emitted by the EventSubscriberService when print-job events arrive from BullMQ. The subscriber fetches the full PrintJob from the database and constructs proper domain events, ensuring internal handlers receive the complete entity.

Constant Event Name Emitter Subscribers
CREATED printjob.created PrintJobsService.createSinglePrintJob() EventsGateway → Socket.IO
STATUS_CHANGED printjob.status-changed PrintJobsService.updateStatus(), EventSubscriberService (from BullMQ) OrchestrationService, EventsGateway → Socket.IO, PushService → Web Push
COMPLETED printjob.completed EventSubscriberService (from BullMQ print-job.completed) OrchestrationService, EventsGateway → Socket.IO
FAILED printjob.failed EventSubscriberService (from BullMQ print-job.failed) OrchestrationService, NotificationsService, EventsGateway → Socket.IO
CANCELLED printjob.cancelled EventSubscriberService (from BullMQ print-job.cancelled) OrchestrationService, EventsGateway → Socket.IO
RETRY_REQUESTED printjob.retry-requested PrintJobsService.retryJob() (event log only)

Source: apps/order-service/src/orchestration/orchestration.service.ts

ORCHESTRATION_EVENTS

Constant Event Name Emitter Subscribers
ORDER_READY_FOR_FULFILLMENT order.ready-for-fulfillment OrchestrationService.markOrderReadyForFulfillment() FulfillmentService, EventPublisherService → BullMQ
ORDER_PARTIALLY_COMPLETED order.partially-completed OrchestrationService.markOrderPartiallyCompleted() (logging only)
ORDER_ALL_JOBS_FAILED order.all-jobs-failed OrchestrationService.markOrderFailed() (logging only)

Order Revival: When PRINT_JOB_EVENTS.STATUS_CHANGED indicates a terminal→active transition (e.g., CANCELLED→QUEUED), the OrchestrationService.handlePrintJobStatusChanged() reverts the order from FAILED/PARTIALLY_COMPLETED/CANCELLED back to PROCESSING and logs an order.revived event to the EventLog. This allows orders to recover automatically when print jobs are requeued.

Source: apps/order-service/src/sendcloud/events/shipment.events.ts

SHIPMENT_EVENTS (within Order Service)

Constant Event Name Emitter Subscribers
CREATED shipment.created EventSubscriberService (from BullMQ shipment.created) FulfillmentService.handleShipmentCreated(), PushService
LABEL_READY shipment.label-ready (not used — label events handled in Shipping Service) PushService
FAILED shipment.failed (not used — errors handled in Shipping Service) (logging only)
UPDATED shipment.updated (not used — updates come via BullMQ shipment.status-changed) (logging only)

Source: apps/order-service/src/fulfillment/events/fulfillment.events.ts

FULFILLMENT_EVENTS (within Order Service)

Constant Event Name Emitter Subscribers
CREATED fulfillment.created FulfillmentService.createFulfillment() (logging only)
FAILED fulfillment.failed FulfillmentService.handleFulfillmentError() NotificationsService.handleFulfillmentFailed()
RETRYING fulfillment.retrying FulfillmentService (on retry) (logging only)

uml diagram

Source: apps/print-service/src/print-jobs/events/print-job.events.ts

Constant Event Name Emitter Subscribers
CREATED printjob.created PrintJobsService.createSinglePrintJob() (internal logging)
STATUS_CHANGED printjob.status-changed PrintJobsService.updateJobStatus() EventPublisherService → BullMQ (print-job.status-changed)
COMPLETED printjob.completed PrintJobsService.updateJobStatus() EventPublisherService → BullMQ (print-job.completed)
FAILED printjob.failed PrintJobsService.updateJobStatus() EventPublisherService → BullMQ (print-job.failed), NotificationsService
CANCELLED printjob.cancelled PrintJobsService.cancelJob() EventPublisherService → BullMQ (print-job.cancelled)
RETRY_REQUESTED printjob.retry-requested PrintJobsService.retryJob() (internal only)

Source: apps/print-service/src/simplyprint/simplyprint.service.ts

SIMPLYPRINT_EVENTS

Constant Event Name Emitter Subscribers
JOB_STATUS_CHANGED simplyprint.job-status-changed SimplyPrintService (webhook/polling), SimplyPrintReconciliationService PrintJobsService.handleSimplyPrintStatusChange()

Reconciliation: The SimplyPrintReconciliationService (print-service only) periodically polls SimplyPrint to catch missed status changes. It checks the printer's actual state (e.g., OPERATIONAL = completed, PRINTING = still active) to correctly determine job status. Events from reconciliation flow through the same simplyprint.job-status-changedPrintJobsServicePRINT_JOB_EVENTS.*EventPublisherService → BullMQ pipeline.

Source: apps/print-service/src/fulfillment/events/fulfillment.events.ts

FULFILLMENT_EVENTS (consumed from cross-service)

Constant Event Name Subscribers
FAILED fulfillment.failed NotificationsService.handleFulfillmentFailed()

Shipping Service — Internal Events

uml diagram

Source: apps/shipping-service/src/sendcloud/events/shipment.events.ts

SHIPMENT_EVENTS

Constant Event Name Emitter Subscribers
CREATED shipment.created SendcloudService.createShipment() FulfillmentService.handleShipmentCreated(), EventPublisherService → BullMQ
LABEL_READY shipment.label-ready SendcloudService.createShipment() (internal only)
FAILED shipment.failed SendcloudService (on error) (logging only)
UPDATED shipment.updated SendcloudService (on status update) (logging only)

Source: apps/shipping-service/src/sendcloud/sendcloud-webhook.service.ts

SENDCLOUD_WEBHOOK_EVENTS

Constant Event Name Emitter Subscribers
SHIPMENT_STATUS_CHANGED sendcloud.shipment.status_changed SendcloudWebhookService.processStatusChange(), SendcloudReconciliationService EventPublisherService → BullMQ (shipment.status-changed)

Reconciliation: The SendcloudReconciliationService (shipping-service only) periodically polls SendCloud to catch missed status changes. Events from reconciliation flow through the same sendcloud.shipment.status_changedEventPublisherService → BullMQ pipeline.

Source: apps/shipping-service/src/orchestration/orchestration.service.ts

ORCHESTRATION_EVENTS (Shipping Service)

Constant Event Name Re-emitted From Subscribers
ORDER_READY_FOR_FULFILLMENT orchestration.order.ready_for_fulfillment BullMQ order.ready-for-fulfillment via EventSubscriberService SendcloudService.handleOrderReadyForFulfillment()

Source: apps/shipping-service/src/orders/events/order.events.ts

ORDER_EVENTS (Shipping Service)

Constant Event Name Re-emitted From Subscribers
CANCELLED order.cancelled BullMQ order.cancelled via EventSubscriberService (no active handler yet)

Source: apps/shipping-service/src/fulfillment/events/fulfillment.events.ts

FULFILLMENT_EVENTS

Constant Event Name Emitter Subscribers
COMPLETED fulfillment.completed FulfillmentService.createFulfillment() (logging only)
FAILED fulfillment.failed FulfillmentService.handleFulfillmentError() NotificationsService.handleFulfillmentFailed()

GridFlock Service — Internal Events

uml diagram

The GridFlock Service has no dedicated internal event constants file. Events are emitted directly by GridflockPipelineService:

Event Name Emitter Subscribers
gridflock.mapping-ready GridflockPipelineService.processOrderLineItem() EventPublisherService → BullMQ
gridflock.pipeline-failed GridflockPipelineService.processOrderLineItem() EventPublisherService → BullMQ

The service also uses a local BullMQ queue (gridflock-generation) for CPU-intensive STL generation jobs. This is a job queue, not an event queue — it processes generate-baseplate and generate-plate-set jobs via GridflockProcessor.


Service Ownership

Each external integration's reconciliation/backfill is owned by exactly one service:

Integration Owner Service Reconciliation Service Webhook Handler
Shopify Order Service ShopifyBackfillService ShopifyWebhookController
SimplyPrint Print Service SimplyPrintReconciliationService SimplyPrintWebhookController
SendCloud Shipping Service SendcloudReconciliationService SendcloudWebhookController

Important: Reconciliation services must only exist in their owning service. Duplicate reconciliation services in other services can cause conflicting status updates (e.g., a completed job being reset to printing).


Gateway — Event Monitoring Only

The API Gateway (apps/gateway) does not publish or consume any events. It serves three roles:

  1. Bull Board Dashboard — read-only monitoring of all 13 BullMQ queues at /admin/queues
  2. Socket.IO Proxy — WebSocket proxy on namespace /events with Redis adapter for horizontal scaling
  3. HTTP Proxy — routes REST requests to downstream services

WebSocket Events (Socket.IO)

The EventsGateway in the Order Service listens to internal EventEmitter2 events and broadcasts them to connected frontend clients via Socket.IO.

Source: apps/order-service/src/gateway/events.gateway.ts

Internal Event Socket.IO Event Description
ORDER_EVENTS.CREATED order:created New order notification
ORDER_EVENTS.STATUS_CHANGED order:updated Order status transition
ORDER_EVENTS.READY_FOR_FULFILLMENT order:ready-for-fulfillment Order ready to ship
ORDER_EVENTS.FULFILLED order:fulfilled Shopify fulfillment created
ORDER_EVENTS.CANCELLED order:cancelled Order cancelled
ORDER_EVENTS.FAILED order:failed Order processing failed
PRINT_JOB_EVENTS.CREATED printjob:created Print job queued
PRINT_JOB_EVENTS.STATUS_CHANGED printjob:updated Print job status update
PRINT_JOB_EVENTS.COMPLETED printjob:completed Print job finished
PRINT_JOB_EVENTS.FAILED printjob:failed Print job failed
PRINT_JOB_EVENTS.CANCELLED printjob:cancelled Print job cancelled
(manual) notification General notification

Internal HTTP APIs (Non-Event Communication)

In addition to events, services communicate synchronously via internal HTTP APIs protected by X-Internal-Key header:

Source Target Endpoint Purpose
Order Service Print Service POST /internal/print-jobs Create print jobs for order
Order Service Shipping Service POST /internal/shipments Create shipment for order
Order Service GridFlock Service POST /internal/gridflock/generate-for-order Trigger GridFlock pipeline
Order Service Print Service DELETE /internal/print-jobs/:id Cancel print job
Order Service Shipping Service DELETE /internal/shipments/:orderId Cancel shipment

Event Best Practices

  1. Use constants, not strings: Always use event constant objects (PRINT_JOB_EVENTS, ORDER_EVENTS, SHIPMENT_EVENTS, SENDCLOUD_WEBHOOK_EVENTS, ORCHESTRATION_EVENTS) in @OnEvent() decorators and eventEmitter.emit() calls. Never use hardcoded strings — they cause silent mismatches.
  2. Event Naming: Cross-service events use lowercase with dots as separators (e.g., order.created). Internal events may differ (e.g., printjob.completed vs print-job.completed) — the bridge services handle translation.
  3. Payload Design: Include enough context for consumers to act without additional queries. For print job events, the Order Service subscriber enriches thin cross-service events by fetching the full entity from the database.
  4. Idempotency: All event handlers MUST be idempotent — use eventId to deduplicate replayed events, or check if work is already done before acting.
  5. Error Handling: Always wrap event handlers in try-catch with proper logging.
  6. No duplicate processing: BullMQ at-least-once delivery means handlers may receive the same event twice.
  7. Dead letter monitoring: Monitor failed events in BullMQ dead letter queues via Bull Board (/admin/queues).
  8. Event ordering: Do not rely on cross-queue ordering — events from different queues may arrive in any order.
  9. Bridge pattern: When adding a new cross-service event:
  10. Define the event type in libs/service-common/src/lib/events/event-types.ts
  11. Add the publisher bridge in the owning service's EventPublisherService
  12. Add the subscriber bridge in consuming services' EventSubscriberService
  13. Use constants (not hardcoded strings) in both the publisher @OnEvent() and subscriber eventEmitter.emit()
  14. Internal events stay internal: Not every internal event needs a cross-service counterpart. Only bridge events that other services need to react to.
  15. Single owner per integration: Each external integration (Shopify, SimplyPrint, SendCloud) must have its webhook handler and reconciliation service in exactly one microservice. Duplicate handlers/reconciliation in multiple services causes conflicting updates.
  16. Direct-publish pattern: Integration change events (integration.simplyprint-changed, integration.sendcloud-changed) bypass the EventEmitter2 → EventPublisherService bridge and publish directly to BullMQ from the *ConnectionService. This is appropriate when the owning service handles the change locally and only needs to notify downstream consumers.
  17. Live re-initialization: When integration credentials change, all services must update their API clients immediately. Never rely solely on startup initialization — use the *InitializerService.reinitialize() / .disable() pattern triggered by integration change events.

Complete Event Summary

uml diagram