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:
- ✅ Space initialization and creation
- ✅ Universe CRUD operations
- ✅ Space/Universe updates
- ✅ Sync queue management
- ✅ Backend synchronization
- ✅ Data validation and business rules
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:
- ✅ Write operations: Creating, updating, deleting universes
- ✅ Space initialization: On app startup or space switch
- ✅ Direct access: When you need the current space or universe management
UnifiedDataService (Aggregator/Coordinator)
Purpose: Single source of truth for ALL platform data (aggregates from multiple services)
Responsibilities:
- ✅ Aggregates data from SpaceService, PagesService, FlowsService, etc.
- ✅ Provides computed views (universesWithContent, pagesWithMetadata)
- ✅ Filters data by space context
- ✅ Provides unified access for UI components
- ✅ Statistics and analytics
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:
- ✅ Read operations: Displaying data in UI components
- ✅ Aggregated views: When you need data with computed properties
- ✅ Cross-tool data: When you need pages + flows + modules together
- ✅ Statistics: Dashboard analytics and counts
🔄 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
- ✅ UnifiedDataService should use SpaceService.currentSpace() instead of loading directly
- ✅ UnifiedDataService should use computed signals from SpaceService instead of copying
- ✅ Clear separation: Writes → SpaceService, Reads → UnifiedDataService
- ✅ Documentation: Add JSDoc comments clarifying the relationship