Events & Business Logic Domain Architecture
Events & Business Logic Domain Architecture
Key Takeaways
- Shoutout is a complex BPM-orchestrated workflow — CIB Seven 2.0 manages the full lifecycle from offer creation → purchase → celebrity recording → media generation (FFmpeg/Mux) → admin review → fan delivery. In-flight BPM instances are a migration constraint.
- Inventory is the product catalog hub — 36 Flyway migrations, 7+ core entities (InventoryItem, Purchase, Entitlement, PurchaseCode, MembershipDiscount). All purchasable items across the platform (shoutouts, classes, events, subscriptions) reference inventory.
- Class-Catalog is the most complex service in this domain — 32 migrations, 15+ entities covering courses, sections, sessions, office hours, appointments, tickets, and learning credits (CEU/PDU). Contains a deprecated Arlo LMS integration.
- Gen 1 services are fully replaceable — peeq-meet-and-greet-bpm (Camunda 7.17, Jitsi) and peeq-custom-tixr (Tixr ticketing) are Gen 1 with no Gen 2 activity. Onsite-event Gen 2 is a minimal superset of peeq-onsite-event Gen 1.
- No brand-specific backend logic in any events domain service — confirms H11 pattern across now 5 domains.
Migration Decision Question
What event/shoutout workflows need to be preserved in Gen 3?
Migration Verdict
Verdict: Consolidate Complexity: L Key Constraint: CIB Seven BPM orchestrates shoutout fulfillment — in-flight process instances must drain or migrate. Inventory is a cross-cutting dependency for billing, subscriptions, and class-catalog. Dependencies: Stripe (purchase flow), wallet (coin credit), email (delivery notifications), media (Mux video), purchase-request-bpm (purchase approval), SSE (real-time updates)
Per-Service Verdicts
| Service | Verdict | Rationale |
|---|---|---|
| Shoutout + Shoutout-BPM | Consolidate → single service | BPM logic can be simplified to state machine; FFmpeg/Mux processing stays |
| Inventory | Upgrade | Central product catalog — too many dependents to rewrite; upgrade in place |
| Class-Catalog | Upgrade | Complex schema (32 migrations), unique domain — upgrade, remove Arlo dead code |
| Onsite-Event | Upgrade | Simple (2 migrations) — minimal effort to upgrade |
| peeq-meet-and-greet-bpm | Retire | Gen 1 Camunda 7.17, uses deprecated Jitsi, no Gen 2 replacement needed |
| peeq-custom-tixr | Retire | Gen 1 Tixr integration, no evidence of active use |
| peeq-onsite-event | Retire | Gen 1, replaced by Gen 2 onsite-event |
| peeq-shoutout-db | Retire | Gen 1 DB repo, replaced by Gen 2 shoutout Flyway |
Services Inventory
| Service | Gen | Deployed? | Stack | Purpose |
|---|---|---|---|---|
| shoutout | 2 | Yes (4/4 tenants) | Java 21 / SB 3.5.4 | Shoutout offer/order management + media generation |
| shoutout-bpm | 2 | Yes (4/4) | Java 21 / SB 3.5.4 / CIB Seven 2.0 | Shoutout fulfillment workflow orchestration |
| inventory | 2 | Yes (4/4) | Java 21 / SB 3.5.4 | Central product catalog + entitlements |
| class-catalog | 2 | Yes (4/4) | Java 21 / SB 3.5.4 | Course/class management + learning credits |
| onsite-event | 2 | Yes (4/4) | Java 21 / SB 3.5.4 | In-person event check-in |
Not in Production
| Service | Status | Notes |
|---|---|---|
| peeq-meet-and-greet-bpm (Gen 1) | Inactive | Camunda 7.17, Jitsi video conference deployment |
| peeq-custom-tixr (Gen 1) | Inactive | Tixr ticketing integration |
| peeq-onsite-event (Gen 1) | Replaced | Gen 2 onsite-event is superset |
| peeq-shoutout-db (Gen 1) | Replaced | Schema managed by Gen 2 shoutout service |
API Surface
Shoutout Service (GraphQL — 15+ operations)
Offer Management: createShoutoutOffer, updateShoutoutOffer, deleteShoutoutOffer, getShoutoutOffers, getShoutoutOffersByCelebrity Order Management: createShoutoutOrder, getShoutoutOrders, getShoutoutOrdersByFan, getShoutoutOrdersByCelebrity, updateShoutoutOrderStatus Media: uploadShoutoutVideo, getShoutoutVideo, generateShoutoutMedia Admin: adminGetShoutoutOrders, adminUpdateShoutoutOrder
Shoutout-BPM Service (REST + RabbitMQ — no GraphQL)
REST Endpoints: Process start/status/callback endpoints for CIB Seven BPM Process: ShoutoutFulfillment.bpmn — video recording → review → approval/rejection with expiration timers Message Handlers: Receives shoutout order events, publishes fulfillment status updates
Inventory Service (GraphQL — 11+ controllers)
Item Management: createInventoryItem, updateInventoryItem, getInventoryItem, getInventoryItems, getInventoryItemsByType Purchase Operations: createPurchase, getPurchase, getPurchasesByUser, getPurchasesByItem Entitlements: createEntitlement, getEntitlements, getEntitlementsByUser, validateEntitlement Purchase Codes: createPurchaseCode, validatePurchaseCode, redeemPurchaseCode Membership Discounts: createMembershipDiscount, getMembershipDiscounts, validateMembershipDiscount Exchange Rates: getExchangeRate, setExchangeRate (USD ↔︎ Peeq coin conversion)
Class-Catalog Service (GraphQL — 20+ operations)
Course Management: createCourse, updateCourse, getCourse, getCourses, getCoursesByCategory Section Management: createSection, updateSection, getSections Session Management: createSession, updateSession, getSessions, getSessionsBySection Attendee Tracking: registerAttendee, getAttendees, getAttendeesBySession, updateAttendeeStatus Office Hours: createOfficeHours, getOfficeHours, bookAppointment, getAppointments Tickets: createTicket, getTickets, validateTicket Learning Credits: issueLearningCredit, getLearningCredits, getLearningCreditsByUser (CEU/PDU tracking)
Onsite-Event Service (GraphQL — minimal)
Event Check-In: checkInAttendee, getCheckIns, getCheckInsByEvent
Who Calls This Domain? Who Does It Call?
graph LR
subgraph "Events Domain"
SH[Shoutout]
SBP[Shoutout-BPM]
INV[Inventory]
CC[Class-Catalog]
OE[Onsite-Event]
end
subgraph "Callers"
FE1[peeq-mono]
FE2[frontends]
PBP[Purchase-Request-BPM]
STR[Stripe]
end
subgraph "Called Services"
MUX[Mux API]
FFMPEG[FFmpeg]
EMAIL[Email]
SSE[SSE]
WAL[Wallet]
KC[Keycloak]
TAGS[Tags]
TRACK[Tracking]
end
FE1 -->|GraphQL| SH & INV & CC & OE
FE2 -->|GraphQL| SH & INV & CC & OE
PBP -->|RabbitMQ| INV
STR -->|RabbitMQ| INV
SH -->|REST| MUX
SH -->|Process| FFMPEG
SH -->|Keycloak Admin| KC
SBP -->|RabbitMQ| SH & EMAIL & WAL
INV -->|GraphQL| TAGS
INV -->|REST| TRACK
CC -->|Keycloak Admin| KC
CC -->|GraphQL| TAGS
SH -.->|RabbitMQ| SBP & SSE & EMAIL
INV -.->|RabbitMQ| SSE
CC -.->|RabbitMQ| SSE
API Backward Compatibility Constraints
- GraphQL schema changes in inventory would break both frontends AND purchase-request-bpm (critical cross-cutting dependency)
- Shoutout offer/order GraphQL schemas are consumed by both frontends — schema changes require coordinated deployment
- Class-catalog learning credit endpoints are referenced by frontend CEU/PDU display — breaking changes affect certificate generation
- Inventory
getInventoryItemsByTypeis called by multiple services to discover purchasable products — interface must remain stable
Data Model
Shoutout Schema (23 Flyway migrations, 6 entities)
erDiagram
shoutout_offers {
uuid id PK
uuid celebrity_user_id
string title
string description
int price_in_coins
string status
int max_duration_seconds
date created_on
}
shoutout_orders {
uuid id PK
uuid offer_id FK
uuid fan_user_id
uuid celebrity_user_id
string instructions
string recipient_name
string occasion
string status
date created_on
date completed_on
date expires_on
}
shoutout_videos {
uuid id PK
uuid order_id FK
string mux_asset_id
string mux_playback_id
string source_url
int duration_seconds
string status
}
shoutout_media {
uuid id PK
uuid order_id FK
string type
string url
string status
}
shoutout_reviews {
uuid id PK
uuid order_id FK
uuid reviewer_id
string decision
string notes
date reviewed_on
}
shoutout_notifications {
uuid id PK
uuid order_id FK
string type
string status
date sent_on
}
shoutout_offers ||--o{ shoutout_orders : "purchased as"
shoutout_orders ||--o| shoutout_videos : "recorded"
shoutout_orders ||--o{ shoutout_media : "generated"
shoutout_orders ||--o| shoutout_reviews : "reviewed"
shoutout_orders ||--o{ shoutout_notifications : "notified"
Inventory Schema (36 Flyway migrations, 7+ entities)
erDiagram
inventory_items {
uuid id PK
string type
string name
string description
int price_in_coins
uuid celebrity_user_id
string status
string metadata
date created_on
}
purchases {
uuid id PK
uuid inventory_item_id FK
uuid user_id
int quantity
int total_coins
string status
date purchased_on
}
entitlements {
uuid id PK
uuid purchase_id FK
uuid user_id
uuid inventory_item_id FK
string type
string status
date granted_on
date expires_on
}
purchase_codes {
uuid id PK
string code UK
uuid inventory_item_id FK
string type
int discount_percent
int max_uses
int current_uses
date valid_from
date valid_until
}
membership_discounts {
uuid id PK
string membership_type
uuid inventory_item_id FK
int discount_percent
string status
}
exchange_rates {
uuid id PK
string from_currency
string to_currency
decimal rate
date effective_from
}
inventory_item_images {
uuid id PK
uuid inventory_item_id FK
string url
int display_order
}
inventory_items ||--o{ purchases : "bought"
inventory_items ||--o{ entitlements : "grants"
inventory_items ||--o{ purchase_codes : "discounted by"
inventory_items ||--o{ membership_discounts : "discounted by"
inventory_items ||--o{ inventory_item_images : "has"
purchases ||--o{ entitlements : "creates"
Class-Catalog Schema (32 Flyway migrations, 15+ entities)
erDiagram
courses {
uuid id PK
string title
string description
uuid celebrity_user_id
string status
string category
int ceu_credits
int pdu_credits
date created_on
}
sections {
uuid id PK
uuid course_id FK
string title
int sequence_order
date start_date
date end_date
}
sessions {
uuid id PK
uuid section_id FK
string title
string type
date scheduled_at
int duration_minutes
string meeting_url
string status
}
attendees {
uuid id PK
uuid session_id FK
uuid user_id
string status
date registered_on
date attended_on
}
tickets {
uuid id PK
uuid user_id
uuid session_id FK
string ticket_code
string status
date issued_on
}
office_hours {
uuid id PK
uuid celebrity_user_id
string title
string description
int duration_minutes
int max_attendees
string recurrence_rule
}
appointments {
uuid id PK
uuid office_hours_id FK
uuid user_id
date scheduled_at
string status
}
learning_resources {
uuid id PK
uuid course_id FK
string title
string type
string url
int sequence_order
}
learning_credits {
uuid id PK
uuid user_id
uuid course_id FK
string credit_type
decimal credits_earned
date issued_on
string certificate_url
}
courses ||--o{ sections : contains
sections ||--o{ sessions : contains
sessions ||--o{ attendees : "attended by"
sessions ||--o{ tickets : "ticketed"
courses ||--o{ learning_resources : includes
courses ||--o{ learning_credits : "awards"
office_hours ||--o{ appointments : "booked"
Onsite-Event Schema (2 Flyway migrations, 2 entities)
erDiagram
onsite_events {
uuid id PK
string name
string location
date event_date
}
onsite_check_ins {
uuid id PK
uuid event_id FK
uuid user_id
date checked_in_at
}
onsite_events ||--o{ onsite_check_ins : "tracks"
Data Migration Specifics
- Schema transformation: Inventory and class-catalog schemas are mature (36 and 32 migrations respectively) — mostly additive. Shoutout schema (23 migrations) has workflow state embedded.
- Foreign key dependencies: All entities FK to Keycloak UUIDs for user references. Inventory items are referenced by shoutout offers (price in coins), class-catalog (course pricing), and purchase-request-bpm (purchase records).
- Data volume: Unknown (no DB access). Inventory’s 36 migrations and position as product catalog hub suggest significant row counts. Class-catalog’s 32 migrations suggest active evolution.
- Stateful data: Shoutout orders have in-progress workflow state (celebrity hasn’t recorded yet, pending review). Inventory purchases track fulfillment status. Class-catalog sessions have scheduled future dates with registered attendees.
In-Flight State
- Shoutout BPM instances: Active CIB Seven process instances managing shoutout fulfillment. Each instance tracks state through: order created → celebrity notified → video recorded → media generated → admin review → approved/rejected → fan delivery. Instances have expiration timers — if celebrity doesn’t record within deadline, the order expires and coins are refunded. Migration must drain active instances or implement state migration.
- RabbitMQ messages: Shoutout publishes order lifecycle events consumed by shoutout-bpm, email, SSE, and wallet. Inventory publishes purchase events. Queue drain needed during migration.
- FFmpeg processing: Shoutout service runs FFmpeg locally for video compositing (watermarks, intros). Long-running video jobs could be in-flight during migration.
- Scheduled sessions: Class-catalog has future-dated sessions with registered attendees. These represent commitments that must be preserved.
- Mux asset uploads: Shoutout videos uploaded to Mux may be in processing state (Mux webhook callbacks pending).
External Integrations
| Integration | Service | Purpose | Migration Impact |
|---|---|---|---|
| Mux | Shoutout | Video asset upload + playback | API key migration, webhook URL update |
| FFmpeg | Shoutout | Local video compositing (watermarks, intros) | Binary dependency on deployment image |
| Keycloak | Shoutout, Class-Catalog | User profile lookup, admin API | Same as identity domain |
| Arlo LMS | Class-Catalog | Learning management sync (deprecated) | Remove dead integration code |
| Tixr | peeq-custom-tixr (Gen 1) | Ticketing integration | Already inactive — no migration needed |
| Jitsi | peeq-meet-and-greet-bpm (Gen 1) | Video conferencing | Already inactive — no migration needed |
Inter-Service Communication
Synchronous (REST/GraphQL)
| From | To | Protocol | Purpose |
|---|---|---|---|
| Shoutout | Keycloak | Admin API | Celebrity/fan profile lookup |
| Shoutout | Mux | REST | Video upload/status |
| Inventory | Tags | GraphQL | Tag lookup for product categorization |
| Inventory | Tracking | REST | Short link creation |
| Class-Catalog | Keycloak | Admin API | User info retrieval |
| Class-Catalog | Tags | GraphQL | Course categorization |
| Purchase-Request-BPM | Inventory | RabbitMQ→GraphQL | Purchase fulfillment triggers entitlement creation |
Asynchronous (RabbitMQ)
| Publisher | Message | Consumer | Exchange |
|---|---|---|---|
| Shoutout | ShoutoutOrderCreated | Shoutout-BPM, Email, SSE | shoutout |
| Shoutout | ShoutoutOrderCompleted | Email, Wallet, SSE | shoutout |
| Shoutout | ShoutoutOrderExpired | Wallet (refund), Email | shoutout |
| Shoutout | ShoutoutVideoUploaded | Shoutout-BPM | shoutout |
| Shoutout | ShoutoutReviewDecision | Email, SSE | shoutout |
| Shoutout-BPM | ShoutoutFulfillmentStarted | Shoutout | shoutout-bpm |
| Shoutout-BPM | ShoutoutFulfillmentCompleted | Shoutout, Email | shoutout-bpm |
| Shoutout-BPM | ShoutoutExpired | Shoutout, Wallet | shoutout-bpm |
| Inventory | PurchaseCreated | SSE, Email | inventory |
| Inventory | EntitlementGranted | SSE | inventory |
| Inventory | PurchaseCodeRedeemed | SSE | inventory |
| Class-Catalog | SessionScheduled | SSE, Email | class-catalog |
| Class-Catalog | AttendeeRegistered | SSE | class-catalog |
| Class-Catalog | LearningCreditIssued | SSE, Email | class-catalog |
Gen 1 → Gen 2 Comparison
| Aspect | Gen 1 | Gen 2 | Gap |
|---|---|---|---|
| Shoutout | N/A (Gen 2 only) | Full BPM workflow + media gen | No Gen 1 equivalent |
| Inventory | N/A (Gen 2 only) | Central product catalog | No Gen 1 equivalent |
| Class-Catalog | N/A (Gen 2 only) | Courses + learning credits | No Gen 1 equivalent |
| Onsite-Event | peeq-onsite-event (Java 11/SB 2.6.8) | Gen 2 (Java 21/SB 3.5.4) | Gen 2 is minimal superset |
| Meet & Greet | peeq-meet-and-greet-bpm (Camunda 7.17) | No Gen 2 equivalent | Feature deprecated — Jitsi replaced by Zoom webinar |
| Ticketing | peeq-custom-tixr (Tixr API) | No Gen 2 equivalent | Tixr integration inactive |
| Framework | SPQR GraphQL, javax.persistence | Spring Native GraphQL, jakarta.persistence | Standard Gen 1→2 migration |
| BPM | Camunda 7.17 CE | CIB Seven 2.0 | CIB Seven is fork of Camunda 7 |
| Test Coverage | ~0 | Very low (2-3 test files per service) | Both generations lack testing |
Key finding: Most events domain services (shoutout, inventory, class-catalog) have NO Gen 1 predecessor — they were built directly as Gen 2. Only onsite-event and meet-and-greet have Gen 1 versions, and meet-and-greet has been deprecated.
Shoutout Fulfillment Workflow
The shoutout workflow is the most complex BPM process in this domain:
stateDiagram-v2
[*] --> OfferCreated: Celebrity creates offer
OfferCreated --> OrderPlaced: Fan purchases (coins deducted)
OrderPlaced --> CelebrityNotified: Email + push notification
CelebrityNotified --> Recording: Celebrity records video
CelebrityNotified --> Expired: Timer expires (deadline passed)
Recording --> VideoUploaded: Upload to Mux
VideoUploaded --> MediaGeneration: FFmpeg compositing
MediaGeneration --> PendingReview: Admin review queue
PendingReview --> Approved: Admin approves
PendingReview --> Rejected: Admin rejects
Approved --> FanDelivery: Email + in-app notification
FanDelivery --> [*]: Complete
Rejected --> CelebrityNotified: Re-record requested
Expired --> RefundProcessed: Coins returned to fan wallet
RefundProcessed --> [*]: Cancelled
BPM Implementation: CIB Seven 2.0 BPMN process definition with service tasks, user tasks, timer events, and exclusive gateways. The BPM engine maintains process instance state in its own database tables.
Modernization Implications
Consolidation Opportunities
Shoutout + Shoutout-BPM → single service: The BPM separation was necessary for Camunda’s deployment model, but Gen 3 could implement the workflow as a state machine within the shoutout service itself. The BPMN process has ~10 states — manageable without a full BPM engine.
Inventory stays separate: As the product catalog hub referenced by billing, subscriptions, class-catalog, and shoutout, inventory must remain a standalone service. Its 36-migration schema and 11+ GraphQL controllers make it a core dependency.
Class-Catalog stays separate: Unique domain complexity (15+ entities, learning credits, office hours) justifies standalone service. Remove deprecated Arlo LMS integration during upgrade.
Onsite-Event could merge into class-catalog or inventory: With only 2 entities and minimal logic, onsite-event could be absorbed. However, the overhead of merging may not justify the effort given its simplicity.
BPM Strategy Decision
The shoutout-bpm service raises a strategic question about BPM engine dependency: - Current: CIB Seven 2.0 (fork of Camunda 7 CE, community support ending) - Option A: Replace with simple state machine in application code (~10 states, manageable) - Option B: Upgrade to Camunda 8 (major architecture change — Zeebe broker) - Option C: Keep CIB Seven, accept maintenance risk
Recommendation: Option A for shoutout (simple enough). Evaluate purchase-request-bpm separately (Session 4 already analyzed — similar complexity). If both BPM processes are <15 states each, eliminate BPM engine dependency entirely.
Dead Code Removal
- Arlo LMS integration in class-catalog: Deprecated, should be removed
- peeq-meet-and-greet-bpm: Archive (Gen 1 Camunda 7.17, Jitsi dependency)
- peeq-custom-tixr: Archive (Gen 1 Tixr integration, inactive)
- peeq-onsite-event: Archive (replaced by Gen 2)
- peeq-shoutout-db: Archive (schema managed by Gen 2 service)
Migration Risks
- In-flight shoutout orders: Celebrity may have recorded but not yet reviewed, or fan is waiting for delivery. Must implement drain period or state migration.
- FFmpeg dependency: Binary dependency tied to Docker image. Gen 3 could externalize to a media processing queue (align with content/media service pattern).
- Inventory as migration bottleneck: Multiple services depend on inventory GraphQL API. Must maintain backward compatibility during any migration phase.
- Learning credit data: CEU/PDU certificates reference historical course completions. Data must be preserved with referential integrity.
Last updated: 2026-01-30 — Session 6 Review by: 2026-07-30 Staleness risk: Medium — BPM engine decision and class-catalog evolution may change