Skip to content

Domain-Driven Design at Objectuve

Why DDD?

Objectuve is growing. New features touch multiple parts of the system — goals, communities, gamification, AI coaching — and the boundaries between these areas are currently implicit. DDD gives us a shared vocabulary and explicit boundaries so that:

  • New code goes in the right place without guesswork
  • Teams can own domains independently as headcount grows
  • We can extract modules or services later without a rewrite
  • Cross-domain coupling is visible and intentional, not accidental

Our Approach: Pragmatic DDD

We are not doing textbook DDD. We're a monolith, and that's fine. What we're adopting:

  • Bounded Contexts — logical groupings of related models, interactions, and business rules
  • Ubiquitous Language — consistent terminology across code, API, and conversation
  • Aggregate Roots — clear ownership of entity clusters
  • Domain Events — making implicit side effects explicit over time
  • Context Map — documenting how domains depend on each other

What we're not doing (yet): event sourcing, CQRS everywhere, microservices, or separate databases per context.

How Our Architecture Maps to DDD

DDD ConceptObjectuve ImplementationLocation
Aggregate RootModel inheriting PublicRecord with owned associations (e.g., Goal, Community, User, AiEmployee, FeedbackPost)app/models/
EntityModel with identity (id, public_id) and lifecycleapp/models/
Value ObjectImmutable data (candidates — not yet extracted)Fields on models
Application ServiceInteraction class (Interaction::Base)app/interactions/
Domain ServiceService object with domain logicapp/services/
RepositoryPublicRecord.public_find(id), ActiveRecord scopesapp/models/
Domain EventBackground job triggered by interaction (implicit)app/jobs/
Anti-Corruption LayerClerkJwtVerifier, ClerkUserSync for external authapp/services/
Published LanguageGraphQL schemaapp/graphql/

Key architectural patterns already in place

Interactions as Application Services. All business logic lives in app/interactions/. Controllers and GraphQL resolvers are thin — they call an interaction and return results. This is the single best DDD pattern we already follow.

PublicRecord as Aggregate Identity. API-exposed models inherit from PublicRecord, which auto-generates a public_id (URL-safe base64 token). Internal integer IDs never leak to clients. This enforces aggregate identity at the infrastructure level.

Soft Deletion. Core models use acts_as_paranoid for audit trails. Records get deleted_at timestamps rather than being destroyed. This preserves domain history.

Current State (v2.2)

A pragmatic monolith with clear domain separation across 11 bounded contexts (Goal Tracking, Social & Community, User Identity, Gamification, AI Coaching, AI Workforce, Billing, Feedback, Critical Path, Integrations & Connected Apps, Personal Analytics) plus GDPR & Data Rights, Admin, and Content Moderation as cross-cutting concerns. Fifteen milestones of evolution (v1.6–v2.2) have added:

  • Gamification maturity: streaks, freezes, badges, XP progression, partner bonuses, and streak repair hardened via seven seasons of releases; three new domain events (gamification.streak_broken, gamification.streak_advanced, gamification.streak_repaired) published explicitly; user-level streak grace period (1 forgiven gap per 7 streak-days)
  • Onboarding cohesion: first-run flows coordinating User Identity, Goal Tracking, Gamification, and AI Coaching; onboarding interactions now classified in User Identity
  • AI capabilities: AI Coaching (per-user prompts, milestones, insights, weekly digest) with persistent conversation memory (CoachConversation/CoachMessage, 90-day retention) and generation audit log (CoachInteraction); AI Workforce (persistent autonomous agents with memory, budgets, and operator review)
  • Enneagram assessment: personality self-assessment (EnneagramAssessment) added to User Identity; AI Coaching reads Enneagram profile for persona personalization (conformist read)
  • Weekly Digest: email digest preferences (WeeklyDigest::Fetch/SetWeeklyDigestPreferences) stored on UserDetail; digest assembly (WeeklyDigest::AssembleDigestData) orchestrates cross-context reads from Gamification, Goal Tracking, Social, and AI Coaching
  • RevenueCat IAP: second billing provider (App Store / Google Play) added to the Billing context alongside Stripe; Billing::ProcessRevenueCatWebhook reuses the billing.payment_processed domain event and Billing::Subscribers handler; no new cross-context edges introduced
  • Data rights and compliance: GDPR & Data Rights context handling access requests, previews, deletions, and exports across all bounded contexts
  • Revenue integrity: Stripe-backed and RevenueCat-backed Supporter/Teams tiers; GDPR-compliant user data deletion workflows
  • User voice: Feedback context for roadmap input and user community
  • Social graph: Ally write-side (v2.0) with bidirectional request flow and two-layer ally model; goal template library (v1.21); community cold-start features (v1.16)

Business logic lives in interactions (app/interactions/); domain events are published from interactions and subscribed to by context-specific handlers. Eight events are now published explicitly (feedback.post_status_changed, ai_workforce.artifact_approved, billing.payment_processed, gamification.streak_advanced, gamification.streak_broken, gamification.streak_repaired, goal_tracking.habit_checked_in, critical_path.stim_completed); high-value coupling points remain as direct calls and are candidates for Phase 55+ event migration — see 05 — Application Services for the canonical tally (5, all flowing toward Gamification). Critical Path → Gamification (critical_path.stim_completed) was decided 2026-08-09 (Option C: Option B as target state) and migrated per OBJ-2370 — see 03 — Context Map.

Aspirational: enforced domain boundaries via Packwerk, comprehensive event-driven decoupling, and formal Anti-Corruption Layers for all external seams.

Reading Guide

DocumentPurpose
01 — Bounded ContextsThe 11 domains, their aggregates, entities, and services
02 — Ubiquitous LanguageGlossary of domain terms mapped to code
03 — Context MapHow domains interact (with diagrams)
04 — Domain EventsCurrent implicit events and proposed explicit events
05 — Application ServicesEvery interaction mapped to its context and classification
06 — Modularization Guide3-phase plan for incremental extraction

Last updated: 2026-08-10

Loading…