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 Concept | Objectuve Implementation | Location |
|---|---|---|
| Aggregate Root | Model inheriting PublicRecord with owned associations (e.g., Goal, Community, User, AiEmployee, FeedbackPost) | app/models/ |
| Entity | Model with identity (id, public_id) and lifecycle | app/models/ |
| Value Object | Immutable data (candidates — not yet extracted) | Fields on models |
| Application Service | Interaction class (Interaction::Base) | app/interactions/ |
| Domain Service | Service object with domain logic | app/services/ |
| Repository | PublicRecord.public_find(id), ActiveRecord scopes | app/models/ |
| Domain Event | Background job triggered by interaction (implicit) | app/jobs/ |
| Anti-Corruption Layer | ClerkJwtVerifier, ClerkUserSync for external auth | app/services/ |
| Published Language | GraphQL schema | app/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 onUserDetail; 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::ProcessRevenueCatWebhookreuses thebilling.payment_processeddomain event andBilling::Subscribershandler; 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
| Document | Purpose |
|---|---|
| 01 — Bounded Contexts | The 11 domains, their aggregates, entities, and services |
| 02 — Ubiquitous Language | Glossary of domain terms mapped to code |
| 03 — Context Map | How domains interact (with diagrams) |
| 04 — Domain Events | Current implicit events and proposed explicit events |
| 05 — Application Services | Every interaction mapped to its context and classification |
| 06 — Modularization Guide | 3-phase plan for incremental extraction |
Last updated: 2026-08-10