Il Grafo Sociale Applicato alla Gestione Eventi
Nel mondo della gestione eventi, conoscere le relazioni tra i partecipanti non è un dettaglio accessorio: è la chiave per organizzare esperienze memorabili. Chi è amico di chi? Quali coppie di ex-partner devono essere separate ai tavoli? Quali colleghi potrebbero voler sedere vicini?
In Play The Event,
queste domande trovano risposta in un grafo sociale completo che modella oltre
52 tipi di relazione raggruppati in 10 categorie, ciascuno con un peso numerico
che va da -1.0 (nemico) a 1.0 (legame familiare più forte).
Questo grafo alimenta funzionalità avanzate come il seating intelligente, il rilevamento
conflitti e i suggerimenti di invito.
Cosa Troverai in Questo Articolo
- I 52 tipi di relazione organizzati in 10 categorie con peso ponderato
- Il sistema di forza delle relazioni (RelationshipStrength) con 9 livelli
- La bidirezionalità automatica e il calcolo del tipo inverso
- Il workflow di stato: PENDING, ACCEPTED, REJECTED
- La community detection per raggruppamento intelligente
- Il conflict detection per evitare scontri ai tavoli
- Il seating intelligente basato sul grafo sociale
- I suggerimenti automatici di invito
- La visualizzazione interattiva con D3.js
52 Tipi di Relazione in 10 Categorie
Il cuore del sistema è l'enum TipoRelazione, un Value Object che definisce
ogni possibile legame tra due utenti. Ogni tipo ha un peso (weight) che ne
indica l'importanza e un nome visualizzazione per l'interfaccia utente.
Le categorie spaziano dalla famiglia nucleare ai rapporti organizzativi, fino alle relazioni
negative.
TIPO PESO NOME VISUALIZZAZIONE
----------------------------------------------------
GENITORE 1.00 Genitore
FIGLIO 1.00 Figlio/a
CONIUGE 1.00 Coniuge
PARTNER 0.95 Partner
GEMELLO 0.95 Gemello/a
FRATELLO 0.90 Fratello
SORELLA 0.90 Sorella
TIPO PESO NOME VISUALIZZAZIONE
----------------------------------------------------
NONNO 0.85 Nonno
NONNA 0.85 Nonna
NIPOTE_DI_NONNI 0.85 Nipote (di nonni)
ZIO 0.70 Zio
ZIA 0.70 Zia
NIPOTE_DI_ZII 0.70 Nipote (di zii)
CUGINO_PRIMO 0.60 Cugino/a di Primo Grado
CUGINO_SECONDO 0.40 Cugino/a di Secondo Grado
CUGINO_TERZO 0.30 Cugino/a di Terzo Grado
TIPO PESO NOME VISUALIZZAZIONE
----------------------------------------------------
SUOCERO 0.70 Suocero
SUOCERA 0.70 Suocera
GENERO 0.75 Genero
NUORA 0.75 Nuora
COGNATO 0.60 Cognato
COGNATA 0.60 Cognata
TIPO PESO NOME VISUALIZZAZIONE
----------------------------------------------------
PADRINO 0.65 Padrino
MADRINA 0.65 Madrina
FIGLIOCCIO 0.65 Figlioccio/a
PATRIGNO 0.60 Patrigno
MATRIGNA 0.60 Matrigna
FIGLIASTRO 0.55 Figliastro
FIGLIASTRA 0.55 Figliastra
FRATELLASTRO 0.55 Fratellastro
SORELLASTRA 0.55 Sorellastra
TIPO PESO NOME VISUALIZZAZIONE
----------------------------------------------------
GENITORE_ADOTTIVO 0.95 Genitore Adottivo
FIGLIO_ADOTTIVO 0.95 Figlio/a Adottivo/a
FRATELLO_ADOTTIVO 0.80 Fratello/Sorella Adottivo/a
FRATELLO_CONSANGUINEO 0.70 Fratello Consanguineo
SORELLA_CONSANGUINEA 0.70 Sorella Consanguinea
BISNONNO 0.65 Bisnonno/a
PRONIPOTE 0.65 Pronipote
PROZIO 0.50 Prozio/a
TIPO PESO NOME VISUALIZZAZIONE
----------------------------------------------------
MIGLIORE_AMICO 0.85 Migliore Amico/a
AMICO_STRETTO 0.80 Amico/a Stretto/a
AMICO 0.50 Amico/a
COMPAGNO_CLASSE 0.40 Compagno/a di Classe
CONOSCENTE 0.30 Conoscente
COLLEGA 0.30 Collega
VICINO 0.25 Vicino/a
TIPO PESO NOME VISUALIZZAZIONE
----------------------------------------------------
DIRIGENTE 0.60 Dirigente
RESPONSABILE 0.50 Responsabile
DIPENDENTE 0.40 Dipendente
COLLABORATORE 0.35 Collaboratore
CONSULENTE 0.35 Consulente
STAGISTA 0.30 Stagista
TIPO PESO NOME VISUALIZZAZIONE
----------------------------------------------------
PRESIDENTE 0.70 Presidente
VICEPRESIDENTE 0.65 Vicepresidente
SEGRETARIO 0.60 Segretario
TESORIERE 0.60 Tesoriere
MEMBRO_DIRETTIVO 0.55 Membro del Direttivo
CONSIGLIERE 0.50 Consigliere
ASSOCIATO 0.40 Associato/Socio
VOLONTARIO 0.35 Volontario
TIPO PESO NOME VISUALIZZAZIONE
----------------------------------------------------
NEMICO -1.00 Nemico/a
RIVALE -0.50 Rivale
EX_CONIUGE -0.30 Ex Coniuge
EX_PARTNER -0.20 Ex Partner
SCONOSCIUTO 0.00 Sconosciuto/a
Questa granularità permette al sistema di distinguere non solo il tipo di legame, ma
anche la sua intensità. Un MIGLIORE_AMICO (0.85) pesa quasi quanto un
FRATELLO (0.90), mentre un CONOSCENTE (0.30) ha un impatto molto
inferiore nelle decisioni algoritmiche.
Peso e Forza delle Relazioni
Ogni relazione ha un peso numerico compreso tra -1.0 e 1.0.
Il sistema traduce questo valore continuo in una scala discreta di 9 livelli di
forza tramite l'enum RelationshipStrength, utilizzata dall'interfaccia
utente per visualizzare indicatori di colore e spessore degli archi nel grafo.
LIVELLO RANGE PESO DESCRIZIONE
-----------------------------------------------------
VERY_STRONG >= 0.80 Molto Forte
STRONG >= 0.60 Forte
MODERATE >= 0.40 Moderata
WEAK >= 0.20 Debole
VERY_WEAK > 0.00 Molto Debole
NEUTRAL == 0.00 Neutrale
SLIGHTLY_NEGATIVE >= -0.30 Leggermente Negativa
NEGATIVE >= -0.60 Negativa
VERY_NEGATIVE < -0.60 Molto Negativa
Il metodo getStrength() dell'enum TipoRelazione esegue la conversione
automatica. Ad esempio, CONIUGE (peso 1.0) restituisce VERY_STRONG,
mentre NEMICO (peso -1.0) restituisce VERY_NEGATIVE. Questo mapping
è fondamentale per la visualizzazione: gli archi VERY_STRONG sono
rappresentati con linee spesse e colori caldi, mentre gli archi NEGATIVE sono
tratteggiati in rosso.
Il peso può anche essere regolato manualmente dall'utente tramite il metodo
regolaPeso(double nuovoPeso). Ad esempio, un organizzatore potrebbe voler abbassare
il peso di una relazione familiare se i due parenti non sono in buoni rapporti, oppure
innalzare il peso di un collega particolarmente stretto.
L'Entità RelazioneUtente
L'Aggregate Root RelazioneUtente rappresenta una singola relazione nel grafo
sociale. Ogni istanza collega due utenti con un tipo, un peso e uno stato. La tabella
relazioni_utenti è indicizzata su entrambi gli ID utente, sul tipo e
sul peso per garantire query performanti.
RelazioneUtente (Aggregate Root)
├── id Long (auto-generated)
├── utenteId1 Long (FK → users)
├── utenteId2 Long (FK → users)
├── tipoRelazione TipoRelazione (enum)
├── peso Double (-1.0 a 1.0)
├── bidirezionale Boolean (auto-calcolato)
├── tipoRelazioneInversa TipoRelazione (auto-calcolato)
├── stato StatoRelazione (PENDING/ACCEPTED/REJECTED)
├── richiestaInviataDaUtenteId Long (chi ha inviato la richiesta)
├── richiestaInviataIl Instant (timestamp invio)
├── rispostaDataIl Instant (timestamp risposta)
├── note String (max 500 caratteri)
├── creatoIl Instant (audit)
├── aggiornatoIl Instant (audit)
└── versione Long (optimistic locking)
VINCOLI:
├── UniqueConstraint(utenteId1, utenteId2) → no duplicati
├── utenteId1 ≠ utenteId2 → no self-relation
└── peso ∈ [-1.0, 1.0] → range validato
La creazione avviene tramite il factory method RelazioneUtente.crea(), che valida
i dati, calcola automaticamente il peso dal tipo di relazione, determina la bidirezionalità
e imposta lo stato iniziale a PENDING.
Bidirezionalità Automatica
Non tutte le relazioni sono simmetriche. Il sistema determina automaticamente se una relazione
è bidirezionale e, in caso contrario, quale sia il tipo inverso. Questo calcolo avviene
nel costruttore di RelazioneUtente tramite due metodi privati.
Relazioni Simmetriche
Alcune relazioni valgono identicamente in entrambe le direzioni: se Alice è amica di Bob, allora Bob è automaticamente amico di Alice con lo stesso tipo e peso.
SIMMETRICHE (bidirezionale = true, tipo inverso = stesso tipo):
Familiari: CONIUGE, PARTNER, GEMELLO, FRATELLO, SORELLA,
FRATELLASTRO, SORELLASTRA, FRATELLO_CONSANGUINEO,
SORELLA_CONSANGUINEA, FRATELLO_ADOTTIVO,
CUGINO_PRIMO, CUGINO_SECONDO, CUGINO_TERZO,
COGNATO, COGNATA
Sociali: AMICO, AMICO_STRETTO, MIGLIORE_AMICO,
CONOSCENTE, COLLEGA, VICINO, COMPAGNO_CLASSE
Negativi: RIVALE, NEMICO, SCONOSCIUTO
Relazioni Direzionali con Tipo Inverso
Altre relazioni hanno una direzione intrinseca: se Alice è genitore di Bob, allora Bob è figlio di Alice. Il sistema calcola automaticamente il tipo inverso per navigare il grafo in entrambe le direzioni.
TIPO DIRETTO → TIPO INVERSO
--------------------------------------------
GENITORE → FIGLIO
FIGLIO → GENITORE
NONNO / NONNA → NIPOTE_DI_NONNI
ZIO / ZIA → NIPOTE_DI_ZII
SUOCERO / SUOCERA → GENERO (o NUORA)
PADRINO / MADRINA → FIGLIOCCIO
PATRIGNO → FIGLIASTRO
MATRIGNA → FIGLIASTRA
GENITORE_ADOTTIVO → FIGLIO_ADOTTIVO
FIGLIO_ADOTTIVO → GENITORE_ADOTTIVO
BISNONNO → PRONIPOTE
PRONIPOTE → BISNONNO
Questo meccanismo è cruciale per la navigazione del grafo: quando il sistema deve
trovare tutti i figli di un utente, non deve cercare solo le relazioni dove
tipoRelazione = GENITORE e l'utente è utenteId1, ma anche
le relazioni dove tipoRelazione = FIGLIO e l'utente è utenteId2.
Stato Relazione: Il Workflow Bilaterale
Ogni relazione attraversa un workflow di conferma bilaterale, simile a una richiesta di amicizia sui social network. Questo garantisce che entrambe le parti acconsentano al collegamento prima che la relazione diventi attiva nel grafo.
┌─────────────────────────────────┐
│ │
▼ │
[CREAZIONE] → PENDING ──── accetta() ──→ ACCEPTED │
│ │
└──── rifiuta() ──→ REJECTED ─────┘
│
└── resetRichiesta()
(reinvia come PENDING)
REGOLE:
├── Solo il DESTINATARIO può accettare o rifiutare
├── Solo le richieste PENDING possono cambiare stato
├── Le richieste REJECTED possono essere reinviate
└── Le relazioni ACCEPTED sono visibili nel grafo
- PENDING (In attesa di conferma): stato iniziale di ogni nuova relazione. Il richiedente ha inviato la richiesta, il destinatario non ha ancora risposto. La relazione non è ancora visibile nel grafo e non influenza algoritmi.
- ACCEPTED (Accettata): la relazione è attiva e visibile a entrambi gli utenti. Viene utilizzata per visualizzazione nel grafo, suggerimenti inviti, rilevamento conflitti e community detection.
- REJECTED (Rifiutata): la richiesta è stata rifiutata dal destinatario. La relazione rimane nel database per tracking, ma non è attiva. Il richiedente può inviare una nuova richiesta in futuro tramite
resetRichiesta().
Distinzione Richiedente / Destinatario
Il campo richiestaInviataDaUtenteId tiene traccia di chi ha inviato la richiesta.
Il destinatario è automaticamente l'altro utente nella coppia. Solo il destinatario
può accettare o rifiutare la richiesta — questo vincolo è validato nel
metodo validaDestinatario() che lancia un'eccezione se l'utente sbagliato tenta
di rispondere.
Il Domain Service: GestoreCollegamentiDomainService
La logica di business che coinvolge più entità è coordinata dal Domain
Service GestoreCollegamentiDomainService. Questo servizio gestisce la creazione
di richieste con validazione dei duplicati e il filtering dei collegamenti per stato.
creaRichiestaCollegamento(richiedenteId, destinatarioId, tipo):
1. VALIDAZIONE: richiedenteId ≠ destinatarioId
→ "Non puoi creare collegamento con te stesso"
2. RICERCA DUPLICATI: cerca relazione esistente (in entrambe le direzioni)
3. SE ESISTE:
├── PENDING → ERRORE "Richiesta già inviata e in attesa"
├── ACCEPTED → ERRORE "Collegamento già esistente"
└── REJECTED → RESET: reinvia come nuova PENDING
(aggiorna tipo, richiedente, timestamp)
4. SE NON ESISTE:
└── CREA: nuova RelazioneUtente con stato PENDING
Il servizio offre anche metodi per filtrare i collegamenti: getCollegamentiAccettati()
restituisce solo le relazioni ACCEPTED, mentre getRichiesteRicevutePending()
e getRichiesteInviatePending() distinguono tra richieste in entrata e in uscita.
Il conteggio contaRichiesteRicevutePending() alimenta i badge di notifica
nell'interfaccia utente.
Community Detection
Una delle funzionalità più potenti del grafo sociale è l'identificazione automatica di comunità — gruppi di persone densamente connesse tra loro. In Play The Event, questo si traduce nell'identificazione automatica di nuclei familiari, gruppi di amici e colleghi di lavoro.
GRAFO DEGLI INVITATI (60 persone)
│
├── CLUSTER 1: Famiglia Sposo (12 persone)
│ ├── Genitori (peso 1.0)
│ ├── Fratelli e cognati (peso 0.6-0.9)
│ ├── Nonni (peso 0.85)
│ └── Zii e cugini (peso 0.3-0.7)
│
├── CLUSTER 2: Famiglia Sposa (14 persone)
│ ├── Genitori (peso 1.0)
│ ├── Sorelle e cognati (peso 0.6-0.9)
│ └── Parenti estesi (peso 0.3-0.7)
│
├── CLUSTER 3: Amici Università (8 persone)
│ ├── Amici stretti (peso 0.8)
│ ├── Compagni di classe (peso 0.4)
│ └── Collegamento inter-cluster: 2 persone
│ collegano Cluster 3 ↔ Cluster 4
│
├── CLUSTER 4: Colleghi Lavoro Sposo (10 persone)
│ ├── Dirigente (peso 0.6)
│ ├── Responsabile (peso 0.5)
│ └── Colleghi (peso 0.3)
│
├── CLUSTER 5: Amiche Sposa (8 persone)
│ └── Migliori amiche (peso 0.85)
│
└── CLUSTER 6: Vicini e Conoscenti (8 persone)
└── Vicini (peso 0.25) + Conoscenti (peso 0.3)
L'algoritmo analizza la densità delle connessioni e i pesi degli archi per determinare
i confini naturali tra i gruppi. I metodi helper dell'enum TipoRelazione facilitano
la classificazione: isFamilyRelation(), isSocialRelation(),
isOrganizationalRelation() e isCoreFamily() permettono di verificare
rapidamente la natura di ogni legame.
La tabella di riferimento TipoRelazioneDB organizza i tipi in 8 categorie
formali: FAMIGLIA_STRETTA, FAMIGLIA_ALLARGATA, ACQUISITI,
SPIRITUALI, SOCIALI, PROFESSIONALI,
ORGANIZZATIVI e NEGATIVI. Ogni tipo ha traduzioni multilingue
(italiano e inglese) e un ordine di visualizzazione per i dropdown dell'interfaccia.
Conflict Detection
Il rilevamento dei conflitti è una funzionalità critica per la pianificazione eventi. Quando un organizzatore invita un gruppo di persone, il sistema analizza automaticamente le relazioni tra loro per identificare coppie con rapporti negativi.
ENDPOINT: GET /api/v1/relationships/conflicts?userIds=1,2,3,4,5
INPUT: Lista di ID utenti (partecipanti all'evento)
OUTPUT: Lista di ConflictAlert con severità
ALGORITMO:
1. Recupera tutte le relazioni dell'organizzatore
2. Filtra: solo relazioni tra utenti nella lista
3. Filtra: solo relazioni con peso < 0 (negative)
4. Per ogni conflitto, calcola la SEVERITA':
SEVERITA' RANGE PESO ESEMPIO
──────────────────────────────────────────
HIGH peso <= -0.70 NEMICO (-1.0)
MEDIUM peso <= -0.30 RIVALE (-0.5)
LOW peso < 0.00 EX_PARTNER (-0.2)
RESPONSE:
ConflictAlert {
userId1, userId2,
user1Name, user2Name,
relationshipType,
weight,
severity (HIGH | MEDIUM | LOW)
}
L'organizzatore riceve una lista di alert visualizzati con colori diversi in base alla
severità: rosso per HIGH (conflitti gravi come NEMICO), arancione per
MEDIUM (conflitti moderati come RIVALE o EX_CONIUGE), e giallo per LOW
(conflitti lievi come EX_PARTNER). Questo permette di prendere decisioni informate sulla
disposizione dei posti.
Use Case: Seating Intelligente
Il caso d'uso più concreto del grafo sociale è l'ottimizzazione della disposizione ai tavoli durante matrimoni, cene aziendali e altri eventi con posti a sedere assegnati. Il sistema combina community detection e conflict detection per proporre una disposizione ottimale.
OBIETTIVI DELL'ALGORITMO:
├── MASSIMIZZARE: relazioni positive allo stesso tavolo
├── MINIMIZZARE: conflitti allo stesso tavolo
├── RISPETTARE: capacità massima per tavolo (8-10 persone)
└── PRESERVARE: nuclei familiari e gruppi di amici
ESEMPIO OUTPUT (8 tavoli da 8 persone):
TAVOLO 1 - "Famiglia Sposo (genitori)"
├── Sposo, Genitori Sposo, Fratello + Cognata
├── Nonni paterni
└── Score medio relazioni: 0.87 (VERY_STRONG)
TAVOLO 2 - "Famiglia Sposa (genitori)"
├── Sposa, Genitori Sposa, Sorella + Cognato
├── Nonni materni
└── Score medio relazioni: 0.85 (VERY_STRONG)
TAVOLO 5 - "Amici Università"
├── 8 amici con peso medio 0.65
└── Score medio relazioni: 0.65 (STRONG)
⚠ CONFLITTO EVITATO:
Marco (EX_CONIUGE, peso -0.3) e Laura
→ Assegnati a tavoli diversi (Tavolo 4 vs Tavolo 6)
→ Distanza fisica: 3 tavoli di separazione
L'algoritmo procede in fasi: prima assegna i cluster identificati dalla community detection come unità base, poi raffina la distribuzione spostando individui per massimizzare lo score complessivo e infine verifica che nessun conflitto rilevato sia rimasto nella stessa area. L'organizzatore può sempre sovrascrivere i suggerimenti e il sistema ricalcola lo score in tempo reale.
Suggerimenti di Invito
Il grafo sociale permette anche di suggerire nuovi invitati in base alle connessioni con i
partecipanti già confermati. L'endpoint GET /api/v1/relationships/suggestions
analizza la rete sociale e identifica le persone meglio connesse con il gruppo esistente.
ENDPOINT: GET /api/v1/relationships/suggestions
?participantIds=1,2,3,4&minConnections=2&limit=10
ALGORITMO:
1. Per ogni partecipante confermato:
└── Recupera tutti i collegamenti ACCEPTED
2. Costruisci mappa candidati:
└── candidatoId → [lista connessioni con partecipanti]
└── ESCLUDI: già partecipanti
└── ESCLUDI: relazioni negative (peso < 0)
3. Per ogni candidato, calcola SCORE:
├── commonConnections = numero connessioni con partecipanti
├── avgWeight = peso medio delle connessioni
└── score = (commonConnections/5 * 0.40) + (avgWeight * 0.60)
^^^^^^^^^^^^^^^^^^^^
normalizzato a [0,1]
4. FILTRA: commonConnections >= minConnections
5. ORDINA: per score decrescente
6. LIMITA: a N risultati
ESEMPIO SUGGERIMENTO:
"Invita anche Marco Rossi"
├── Conosce 3 partecipanti (peso medio: 0.72)
├── Score: (0.6 * 0.40) + (0.72 * 0.60) = 0.672
└── Motivo: "Conosce 3 partecipanti (peso medio: 0.72)"
La formula di scoring bilancia due fattori: il 40% del punteggio è basato sul numero di connessioni comuni (normalizzato a un massimo di 5), mentre il 60% è determinato dalla forza media delle relazioni. Questo significa che una persona con 2 amici stretti (peso 0.80) riceverà un punteggio più alto di una con 3 semplici conoscenti (peso 0.30).
Visualizzazione con D3.js
Il grafo delle relazioni è reso visivamente nell'interfaccia di
Play The Event
tramite una visualizzazione force-directed basata su D3.js. L'endpoint
GET /api/v1/relationships/graph restituisce i dati nel formato nodi + archi
atteso dalla libreria.
GraphDataResponse {
"nodes": [
{
"id": "1",
"name": "Federico Calò (Tu)",
"degree": 12, // numero di connessioni
"cluster": null // community (futuro)
},
{
"id": "2",
"name": "Marco Rossi",
"degree": 5,
"cluster": null
}
],
"links": [
{
"source": "1",
"target": "2",
"weight": 0.80, // forza del legame
"type": "AMICO_STRETTO"
},
{
"source": "1",
"target": "3",
"weight": -0.50,
"type": "RIVALE"
}
]
}
Il GetRelationshipGraphQueryHandler costruisce il grafo partendo dai collegamenti
accettati dell'utente corrente. Per ogni nodo calcola il grado (degree),
ovvero il numero di connessioni, che determina la dimensione del nodo nella visualizzazione.
L'utente corrente è evidenziato con il suffisso "(Tu)" nel nome visualizzato.
Nella visualizzazione D3.js, le proprietà del grafo vengono mappate a elementi visivi:
- Dimensione dei nodi: proporzionale al grado — più connessioni ha un utente, più grande appare il suo nodo
- Colore dei nodi: basato sul cluster di appartenenza — stesso colore per la stessa comunità
- Spessore degli archi: proporzionale al peso — relazioni forti hanno archi spessi, relazioni deboli archi sottili
- Colore degli archi: verde per relazioni positive, rosso tratteggiato per relazioni negative
- Distanza tra nodi: inversamente proporzionale al peso — persone con legami forti sono attratte vicine
- Tooltip interattivi: al passaggio del mouse su un arco si visualizza il tipo di relazione e il peso
Internazionalizzazione
Il sistema delle relazioni supporta la localizzazione completa. La tabella
stati_relazione fornisce descrizioni degli stati in 7 lingue
(IT, EN, FR, ES, DE, SV, PT), mentre la tabella tipi_relazione offre nomi
tradotti in italiano e inglese. Il metodo getDescrizioneLocalizzata(lingua)
restituisce la stringa corretta in base alla lingua dell'utente.
STATO IT EN FR
─────────────────────────────────────────────────────────
PENDING In attesa di Waiting for En attente de
conferma confirmation confirmation
ACCEPTED Accettata Accepted Acceptée
REJECTED Rifiutata Rejected Refusée
La Rubrica degli Invitati Esterni
Oltre al grafo tra utenti registrati, Play The Event
gestisce anche le relazioni tra invitati esterni — persone che non hanno
un account sulla piattaforma ma che l'organizzatore vuole invitare. L'entità
RelazioneInvitatiEsterni modella queste connessioni con un set semplificato di
10 tipi di relazione.
TipoRelazioneTraInvitati:
├── CONIUGE (famiglia)
├── PARTNER (famiglia)
├── GENITORE (famiglia)
├── FIGLIO (famiglia)
├── FRATELLO (famiglia)
├── PARENTE (famiglia)
├── AMICO (sociale)
├── COLLEGA (sociale)
├── CONOSCENTE (sociale)
└── ALTRO
Helper methods:
├── isFamiglia() → true per CONIUGE/PARTNER/GENITORE/FIGLIO/FRATELLO/PARENTE
└── isVicino() → true per tutti i familiari + AMICO
L'entità garantisce un ordine canonico tra i due invitati
(invitato_id_1 < invitato_id_2) per evitare duplicati in direzioni diverse.
Entrambi gli invitati devono appartenere alla rubrica dello stesso utente, validato
nel factory method crea().
Architettura CQRS e API REST
L'intero sistema di relazioni segue il pattern CQRS (Command Query Responsibility Segregation). I comandi modificano lo stato (creazione richiesta, accettazione, rifiuto), mentre le query leggono i dati senza effetti collaterali (grafo, conflitti, suggerimenti).
RelationshipGraphController
│
├── GET /api/v1/relationships/graph
│ └── Restituisce il grafo completo dell'utente
│ Response: GraphDataResponse { nodes[], links[] }
│
├── GET /api/v1/relationships/conflicts?userIds=1,2,3
│ └── Rileva conflitti tra gli utenti specificati
│ Response: ConflictAlertResponse[] {
│ userId1, userId2, user1Name, user2Name,
│ relationshipType, weight, severity
│ }
│
└── GET /api/v1/relationships/suggestions
?participantIds=1,2,3&minConnections=2&limit=10
└── Suggerisce invitati basandosi sulle connessioni
Response: InvitationSuggestionResponse[] {
userId, userName, profileImageUrl,
commonConnections, averageConnectionWeight,
score, reason
}
Tutte le query sono eseguite in transazioni read-only per ottimizzare le performance.
L'autenticazione è gestita tramite JWT, e ogni endpoint verifica l'identità
dell'utente corrente tramite SecurityUtils.getCurrentUserIdOrThrow().
Perché un Grafo Sociale per gli Eventi?
La differenza tra un evento ben organizzato e uno memorabile sta nei dettagli relazionali. Il grafo sociale di Play The Event trasforma dati relazionali in decisioni concrete: dove far sedere le persone, chi invitare, quali conflitti prevenire. Con 52 tipi di relazione, 9 livelli di forza e algoritmi di community detection, il sistema offre all'organizzatore una visione completa della rete sociale dei propri invitati — il tutto attraverso un'interfaccia interattiva basata su D3.js che rende il grafo esplorabile con un semplice drag & drop.
Nel prossimo articolo esploreremo il sistema di gestione documenti e allegati, analizzando come Play The Event organizza contratti, permessi, fatture e altri file associati agli eventi con versionamento e condivisione granulare.







