SERVICES ARCHITECTURE

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

Services Architecture: SpaceService vs UnifiedDataService

Date: November 4, 2025
Status: ✅ Clarified - Working Together, Not Competing


🎯 Core Principle

SpaceService and UnifiedDataService are complementary, not competing. They work together in a layered architecture:

┌─────────────────────────────────────────────────────────┐
│              UI Components (Consumers)                   │
│  (Dashboard, Space Map, Launch Station, etc.)           │
└─────────────────────┬───────────────────────────────────┘
                      │
                      │ Reads from UnifiedDataService
                      │ Writes through SpaceService
                      │
        ┌─────────────┴──────────────┐
        │                            │
        ▼                            ▼
┌───────────────────┐      ┌──────────────────────┐
│ UnifiedDataService│      │    SpaceService      │
│  (Aggregator)     │──────│  (Domain Service)    │
│                   │ Uses │                      │
│ - Aggregates data │──────│ - CRUD operations    │
│ - Computed views  │      │ - Sync operations    │
│ - Single entry    │      │ - Space management   │
│   point for UI    │      │ - Universe management│
└───────────────────┘      └──────────────────────┘
        │                            │
        │                            │
        └────────────┬───────────────┘
                     │
                     ▼
        ┌────────────────────────┐
        │  storage-system        │
        │  (Flash Persistence)    │
        └────────────────────────┘

📊 Service Responsibilities

SpaceService (Domain Service)

Purpose: Manages Space and Universe operations (CRUD, sync, business logic)

Responsibilities:

Key Methods:

class SpaceService {
  async initializeSpace(userId: string, spaceSlug?: string): Promise<Space>
  async createUniverse(data: { name: string; description?: string }): Promise<Universe>
  async updateUniverse(universeId: string, updates: Partial<Universe>): Promise<Universe>
  async deleteUniverse(universeId: string): Promise<void>
  async loadUniverses(): Promise<void>
  async clearAllData(): Promise<void>
  
  // Signals for reactive UI
  readonly currentSpace: Signal<Space | null>
  readonly universes: Signal<Universe[]>
}

When to Use:


UnifiedDataService (Aggregator/Coordinator)

Purpose: Single source of truth for ALL platform data (aggregates from multiple services)

Responsibilities:

Key Methods:

class UnifiedDataService {
  async loadSpaceData(spaceSlug: string): Promise<void>
  async refreshData(): Promise<void>
  
  // Computed views for UI
  readonly universesWithContent: Signal<UniverseWithContent[]>
  readonly pagesWithMetadata: Signal<WorldWithMetadata[]>
  readonly modulesWithAssets: Signal<ModuleWithAssets[]>
  readonly statistics: Signal<DataStatistics>
}

When to Use:


🔄 How They Work Together

Current Implementation (Working)

// UnifiedDataService uses SpaceService internally
class UnifiedDataService {
  private readonly spaceService = inject(SpaceService);
  
  private async loadUniverses(): Promise<Universe[]> {
    // ✅ Delegates to SpaceService
    await this.spaceService.loadUniverses();
    return this.spaceService.universes() as Universe[];
  }
}

Recommended Pattern

// ✅ WRITE: Use SpaceService (domain operations)
async createUniverse() {
  const universe = await this.spaceService.createUniverse({ name: 'My Universe' });
  await this.unifiedData.refreshData(); // Sync UI after write
}

// ✅ READ: Use UnifiedDataService (aggregated views)
readonly universes = this.unifiedData.universesWithContent; // Includes computed properties
readonly statistics = this.unifiedData.statistics;

⚠️ Current Issues & Recommendations

Issue 1: Duplicate Space Loading

Problem: UnifiedDataService.loadSpace() loads directly from storage instead of using SpaceService:

// ❌ CURRENT (Line 546)
private async loadSpace(spaceSlug: string): Promise<Space | null> {
  const space = await this.storage.get<Space>(`space_${spaceSlug}`);
  return space || null;
}

Recommendation: Use SpaceService.currentSpace() instead:

// ✅ RECOMMENDED
private async loadSpace(spaceSlug: string): Promise<Space | null> {
  // Ensure SpaceService is initialized first
  const currentSpace = this.spaceService.currentSpace();
  if (currentSpace && currentSpace.spaceSlug === spaceSlug) {
    return currentSpace;
  }
  
  // If not initialized, SpaceService should have initialized it
  // Return current space from SpaceService
  return this.spaceService.currentSpace();
}

Issue 2: Signal Synchronization

Problem: Both services maintain their own universes signals:

// SpaceService
private readonly _universes = signal<Universe[]>([]);

// UnifiedDataService  
private readonly _universes = signal<Universe[]>([]);

Current Solution: UnifiedDataService copies from SpaceService after load (line 558)

Recommendation: UnifiedDataService should use SpaceService's signals directly via computed:

// ✅ RECOMMENDED
readonly universes = computed(() => this.spaceService.universes());

✅ Best Practices

1. Write Operations

Always use domain services (SpaceService, PagesService, etc.):

// ✅ CORRECT
await this.spaceService.createUniverse({ name: 'My Universe' });
await this.unifiedData.refreshData(); // Sync UI

// ❌ WRONG
await this.unifiedData.createUniverse({ name: 'My Universe' }); // UnifiedDataService doesn't have this

2. Read Operations

Use UnifiedDataService for aggregated views:

// ✅ CORRECT
readonly universes = this.unifiedData.universesWithContent; // Includes page counts, etc.
readonly statistics = this.unifiedData.statistics;

// ✅ ALSO CORRECT (for direct access)
readonly currentSpace = this.spaceService.currentSpace();
readonly universes = this.spaceService.universes(); // Raw universes

3. Data Refresh

After write operations, refresh UnifiedDataService:

// Create universe
await this.spaceService.createUniverse({ name: 'New Universe' });

// Refresh aggregated views
await this.unifiedData.refreshData();

🎯 Summary

Aspect SpaceService UnifiedDataService
Role Domain Service Aggregator/Coordinator
Focus Space/Universe operations All platform data
Writes ✅ Yes (CRUD operations) ❌ No (read-only)
Reads ✅ Yes (direct access) ✅ Yes (aggregated views)
Computed Basic signals Rich computed views
Used By Both services and components UI components primarily
Storage Uses storage-system directly Uses storage-system via services

They work together, not competing! 🚀


📝 Recommended Improvements

  1. ✅ UnifiedDataService should use SpaceService.currentSpace() instead of loading directly
  2. ✅ UnifiedDataService should use computed signals from SpaceService instead of copying
  3. ✅ Clear separation: Writes → SpaceService, Reads → UnifiedDataService
  4. ✅ Documentation: Add JSDoc comments clarifying the relationship

Related Documentation