OpenRegister Service Architecture
Overview
OpenRegister follows a service-oriented architecture with clear separation of concerns. This document describes the responsibilities of each service and how they interact.
Core Principles
- Thin Controllers: Controllers validate HTTP requests and delegate to services
- Service Responsibility: Services contain business logic and return structured data
- No Cross-Dependencies: Services should not depend on each other circularly
- Independent Testing: Each service can test its own functionality
- Clear Boundaries: SOLR, LLM, and data persistence are separate concerns
Service Hierarchy
Controllers (HTTP Layer)
↓
Services (Business Logic)
↓
Mappers/External APIs (Data/Integration Layer)
Services Documentation
VectorEmbeddingService
Location: lib/Service/VectorEmbeddingService.php
Purpose: Handles all vector embedding operations using LLM providers.
Responsibilities:
- Generate embeddings for text using multiple LLM providers
- Store embeddings in database (
oc_openregister_vectorstable) - Perform semantic similarity searches using multiple backends
- Test embedding configurations without saving settings
- Manage embedding generators for different providers
Provider Support:
- OpenAI:
text-embedding-ada-002,text-embedding-3-small,text-embedding-3-large - Fireworks AI: Custom OpenAI-compatible API with various models
- Ollama: Local models with custom configurations
Key Methods:
generateEmbedding(string $text, ?string $provider): array- Generate embedding with saved settingsgenerateEmbeddingWithCustomConfig(string $text, array $config): array- Generate with custom configtestEmbedding(string $provider, array $config, string $testText): array- Test configurationsemanticSearch(string $query, int $limit, array $filters): array- Semantic searchstoreVector(...)- Store embedding in database
Dependencies:
- LLPhant library for embedding generation
- Database connection for vector storage
- SettingsService for reading configuration (not for testing)
Independence:
- ✅ Independent of SOLR - Can operate without search infrastructure
- ✅ No chat dependencies - Only handles embeddings
- ✅ Standalone testing - Tests don't require settings storage
ChatService
Location: lib/Service/ChatService.php
Purpose: Handles all LLM chat operations with RAG (Retrieval Augmented Generation).
Responsibilities:
- Process chat messages with context retrieval
- Generate AI responses using configured LLM providers
- Manage conversation history and summarization
- Generate conversation titles automatically
- Test LLM chat configurations without saving settings
- Handle agent-based context retrieval and filtering
Provider Support:
- OpenAI: GPT-4, GPT-4o-mini, and other chat models
- Fireworks AI: Llama, Mistral, and other open models
- Ollama: Local LLM deployments
RAG Capabilities:
- Semantic search using VectorEmbeddingService
- Keyword search using GuzzleSolrService (optional)
- Hybrid search combining both approaches
- Agent-based filtering and context retrieval
- View-based filtering for multi-tenancy
Key Methods:
processMessage(int $conversationId, string $userId, string $userMessage): array- Process chat with RAGtestChat(string $provider, array $config, string $testMessage): array- Test configurationgenerateConversationTitle(string $firstMessage): string- Generate titleensureUniqueTitle(string $baseTitle, string $userId, int $agentId): string- Ensure uniqueness
Dependencies:
- LLPhant library for LLM interactions
- VectorEmbeddingService for semantic search
- GuzzleSolrService for keyword search (optional)
- ConversationMapper, MessageMapper, AgentMapper for data persistence
- SettingsService for reading configuration (not for testing)
Independence:
- ✅ Independent testing - Tests don't require settings storage
- ✅ Optional SOLR - Can work without keyword search
- ✅ Flexible RAG - Can use semantic-only, keyword-only, or hybrid search
SettingsService
Location: lib/Service/SettingsService.php
Purpose: Store and retrieve application settings ONLY. Does NOT contain business logic.
Responsibilities:
- Store and retrieve settings from Nextcloud's
IAppConfig - Provide default values for unconfigured settings
- Manage settings categories: RBAC, Multitenancy, Retention, SOLR, LLM, Files, Objects
- Get available options (groups, users, tenants) for settings UI
- Rebase operations (apply default owners/tenants to existing objects)
- Cache management statistics and operations
What This Service Does NOT Do:
- ❌ Test LLM connections (use VectorEmbeddingService or ChatService)
- ❌ Test SOLR connections (use GuzzleSolrService)
- ❌ Generate embeddings (use VectorEmbeddingService)
- ❌ Execute chat operations (use ChatService)
- ❌ Perform searches (use appropriate search services)
Settings Categories:
- Version: Application name and version information
- RBAC: Role-based access control configuration
- Multitenancy: Tenant isolation and default tenant settings
- Retention: Data retention policies for objects, logs, trails
- SOLR: Search engine configuration and connection details
- LLM: Language model provider configuration (OpenAI, Fireworks, Ollama)
- Files: File processing and vectorization settings
- Objects: Object vectorization and metadata settings
- Organisation: Default organisation and auto-creation settings
Key Methods:
getSettings(): array- Get all settings with defaultsupdateSettings(array $data): array- Update all settingsgetLLMSettingsOnly(): array- Get LLM settingsupdateLLMSettingsOnly(array $data): array- Update LLM settingsgetSolrSettingsOnly(): array- Get SOLR settingsupdateSolrSettingsOnly(array $data): array- Update SOLR settings- (Similar methods for other setting categories)
Dependencies:
- IAppConfig for settings storage
- IConfig for system configuration
- Various mappers for available options (groups, users, tenants)
Architecture Pattern:
- ✅ Persistence layer only - No business logic
- ✅ Default values - Always returns valid configuration
- ✅ Validation - Ensures settings have correct types and structures
SettingsController
Location: lib/Controller/SettingsController.php
Purpose: Thin HTTP layer that validates requests and delegates to services.
Responsibilities:
- Validate HTTP request parameters
- Delegate settings CRUD operations to SettingsService
- Delegate LLM testing to VectorEmbeddingService and ChatService
- Delegate SOLR testing to GuzzleSolrService
- Return appropriate JSONResponse with correct HTTP status codes
- Handle HTTP-level concerns (authentication, CSRF)
Architecture Pattern:
- ✅ Thin controller - Minimal logic, delegates to services
- ✅ Validation only - Checks required parameters exist
- ✅ Service delegation - Business logic in services
- ✅ Error handling - Catches service errors and returns HTTP responses
Service Interactions
Testing LLM Embeddings
HTTP Request
↓
SettingsController::testEmbedding()
↓ (validates parameters)
VectorEmbeddingService::testEmbedding()
↓ (business logic)
VectorEmbeddingService::generateEmbeddingWithCustomConfig()
↓ (creates generator)
LLPhant EmbeddingGenerator
↓ (API call)
OpenAI/Fireworks/Ollama API
Testing LLM Chat
HTTP Request
↓
SettingsController::testChat()
↓ (validates parameters)
ChatService::testChat()
↓ (business logic)
LLPhant OpenAIChat
↓ (API call)
OpenAI/Fireworks/Ollama API
Processing Chat with RAG
HTTP Request
↓
ChatController::processMessage()
↓
ChatService::processMessage()
↓ (retrieves context)
VectorEmbeddingService::semanticSearch()
↓ (combines with keywords if needed)
GuzzleSolrService::search() [optional]
↓ (generates response)
LLPhant OpenAIChat
Storing Settings
HTTP Request
↓
SettingsController::updateLLMSettings()
↓ (validates parameters)
SettingsService::updateLLMSettingsOnly()
↓ (persists to storage)
IAppConfig::setValueString()
Key Architectural Decisions
1. No Temporary Config Storage
Problem: Testing LLM configurations required saving test config, testing, then restoring original.
Solution: Services accept config as parameters for testing, no settings manipulation needed.
// OLD - Temporary config storage (WRONG)
$original = $settingsService->getLLMSettings();
$settingsService->updateLLMSettings($testConfig);
$result = $service->doSomething();
$settingsService->updateLLMSettings($original);
// NEW - Direct config passing (CORRECT)
$result = $service->testWithConfig($testConfig);
2. Service Independence
Problem: Services were reading from inconsistent config keys or environment variables.
Solution: Each service reads from its designated config key or accepts config as parameters.
- VectorEmbeddingService: Reads from
vector_embeddingskey OR accepts custom config - ChatService: Reads from
llmkey OR accepts custom config - Testing: Always uses custom config (no settings dependency)
3. Clear Delegation
Problem: Controllers contained business logic for testing.
Solution: Controllers validate and delegate, services contain business logic.
Controller Responsibilities:
- Validate HTTP parameters
- Call appropriate service method
- Return HTTP response
Service Responsibilities:
- Implement business logic
- Return structured data (arrays)
- Handle errors with try/catch
4. SOLR and LLM Separation
Problem: LLM services were coupled to SOLR infrastructure.
Solution:
- VectorEmbeddingService is completely independent of SOLR
- ChatService optionally uses SOLR for keyword search
- Hybrid search is implemented in ChatService, not as a dependency
Testing Best Practices
Testing Service Methods
Services should be testable without HTTP layer:
// Good - Service can be tested directly
$vectorService = new VectorEmbeddingService($db, $settings, $logger);
$result = $vectorService->testEmbedding('openai', [
'apiKey' => 'test-key',
'model' => 'text-embedding-3-small'
], 'Test text');
$this->assertTrue($result['success']);
Testing Controllers
Controllers should only validate and delegate:
// Good - Controller test mocks services
$mockService = $this->createMock(VectorEmbeddingService::class);
$mockService->expects($this->once())
->method('testEmbedding')
->willReturn(['success' => true]);
$controller = new SettingsController($mockService, ...);
$response = $controller->testEmbedding();
$this->assertEquals(200, $response->getStatus());
Summary
Service Responsibilities
| Service | Purpose | Tests Its Own | Depends On |
|---|---|---|---|
| VectorEmbeddingService | Generate and search embeddings | ✅ Yes | Database, LLPhant |
| ChatService | LLM chat with RAG | ✅ Yes | VectorEmbedding, optional SOLR |
| SettingsService | Store/retrieve settings | ❌ No (persistence only) | IAppConfig |
| SettingsController | HTTP validation and delegation | ❌ No (thin layer) | All services |
Key Takeaways
- ✅ Services contain business logic and can test themselves
- ✅ Controllers validate and delegate, no business logic
- ✅ SettingsService only handles persistence, not testing
- ✅ Testing uses custom config parameters, not temporary storage
- ✅ SOLR and LLM are independent, ChatService can optionally combine them
- ✅ Each service has clear, documented responsibilities