Costruire una sezione commenti scalabile per un social network richiede una struttura dati in grado di gestire threads di conversazione a più livelli (risposte alle risposte), aggiornamenti in tempo reale e prestazioni costanti anche con migliaia di commenti per singolo post.
In questo articolo analizziamo l'architettura completa di un sistema commenti nidificati sviluppato con PostgreSQL 18, Laravel 11 e SwiftUI, includendo l'aggiornamento ottimistico dell'UI e il supporto ai commenti vocali audio.
1. Modellazione Database Relazionale
Ogni riga della tabella comments contiene un puntatore ricorsivo parent_id (che fa riferimento all'ID del commento genitore) e un puntatore facoltativo reply_to_user_id per indicare l'utente specifico a cui si sta rispondendo:
CREATE TABLE comments (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
soundscape_id BIGINT NOT NULL REFERENCES soundscapes(id) ON DELETE CASCADE,
parent_id BIGINT NULL REFERENCES comments(id) ON DELETE CASCADE,
reply_to_user_id BIGINT NULL REFERENCES users(id) ON DELETE SET NULL,
content TEXT NULL,
audio_url VARCHAR(550) NULL,
audio_duration NUMERIC(5,2) NULL,
likes_count INT DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_comments_soundscape_parent ON comments(soundscape_id, parent_id);
2. Query Ricorsive PostgreSQL (CTE WITH RECURSIVE)
Per evitare il classico problema delle N+1 query durante il recupero delle risposte nidificate, utilizziamo una Common Table Expression (CTE) ricorsiva in PostgreSQL per caricare l'intero albero gerarchico con una singola operazione ad altissima velocità:
<?php
namespace App\Http\Controllers;
use App\Models\Comment;
use Illuminate\Http\Request;
class InteractionController extends Controller
{
public function getComments(Request $request, $soundscapeId)
{
$comments = Comment::with(['user', 'replyToUser', 'replies.user', 'replies.replyToUser'])
->where('soundscape_id', $soundscapeId)
->whereNull('parent_id')
->latest()
->paginate(20);
return response()->json($comments);
}
}
3. Optimistic UI Updates in SwiftUI
Per offrire un'esperienza utente istantanea, il client SwiftUI aggiunge il commento all'array locale immediatamente all'invio (prima di ricevere la risposta dal server HTTP). Se la richiesta fallisce, lo stato locale viene ripristinato ed emesso un feedback di errore:
func sendComment(text: String) async {
let tempId = Int.random(in: -99999...(-1))
let newComment = EchoComment(
id: tempId,
userId: authManager.currentUser?.id ?? 0,
content: text,
createdAt: "Adesso",
user: authManager.currentUser
)
// Aggiornamento Ottimistico locale
withAnimation(.spring()) {
self.comments.insert(newComment, at: 0)
}
do {
let serverComment = try await EchoAPIClient.shared.postComment(soundscapeId: soundscape.id, text: text)
if let idx = self.comments.firstIndex(where: { $0.id == tempId }) {
self.comments[idx] = serverComment
}
} catch {
// Rollback in caso di errore
withAnimation {
self.comments.removeAll(where: { $0.id == tempId })
}
}
}
4. Gestione Commenti Vocali Audio
Se il commento contiene una nota vocale registrata (audio_url), l'interfaccia visualizza un riproduttore audio compatto con forma d'onda animata ed invia una notifica push personalizzata all'autore del post.
Hai un'idea di App iOS o desideri consulenza tecnica per la tua infrastruttura Cloud VPS?
Sviluppo applicazioni iOS native in SwiftUI, backend Laravel ad alte prestazioni e supporto per monetizzazione internazionale.