Panoramica dell’architettura¶
Diagramma di sistema¶
┌─────────────────────────────────────────────────────────────────┐
│ BROWSER / MOBILE │
└──────────────────────────────┬─────────────────────────────────┘
│ HTTPS
▼
┌─────────────────────────────────────────────────────────────────┐
│ jinbocho-fe (React 18 SPA — Render Static Site) │
│ │
│ features/ (TanStack Query) → lib/api.ts (ky) → gateway :8000 │
└──────────────────────────────┬──────────────────────────────────┘
│ HTTPS
▼
┌─────────────────────────────────────────────────────────────────┐
│ api-gateway (FastAPI — Render Web Service, PUBBLICO) │
│ │
│ Validazione JWT · CORS · Reverse proxy │
└─────────┬─────────────────┬──────────────────────────────┘
│ HTTP interno │ HTTP interno
▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐
│ auth-service │ │ catalog-service │ │ ai-service │
│ (Privato) │ │ (Privato) │ │ (Privato) │
│ │ │ │ │ opzionale │
│ famiglie │ │ stanze │ │ │
│ utenti │ │ librerie │ │ tag │
│ JWT │ │ libri │ │ dedup │
│ refresh token │ │ ricerca ISBN │ │ raccomand. │
└────────┬───────┘ └────────┬─────────┘ └───────┬───────┘
│ │ │
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌────────┐
│ auth_db│ │catalog_db│ │ ai_db │
│ (Neon) │ │ (Neon) │ │ (Neon) │
└────────┘ └──────────┘ └────────┘
Bounded Context¶
Contesto Auth (auth-service + auth_db)¶
Gestisce tutto ciò che riguarda chi può accedere al sistema:
- Ciclo di vita dell’account famiglia (creazione, aggiornamento)
- Ciclo di vita degli utenti (creazione, invito, cambio ruolo, disattivazione)
- Autenticazione: hashing delle password, emissione JWT, rotazione dei refresh token
- Ruoli di autorizzazione: admin | editor | viewer
L’auth-service è l’unico emettitore di JWT. Tutti gli altri servizi validano i token usando il JWT_SECRET_KEY condiviso ma non ne emettono di nuovi.
Contesto Catalog (catalog-service + catalog_db)¶
Gestisce tutto ciò che riguarda quali libri esistono e dove si trovano: - Gerarchia fisica delle posizioni: stanze → librerie → sezioni → scaffali - Record bibliografici (titolo, autore, ISBN, metadati) - Copie fisiche dei libri (quale copia è su quale scaffale, stato di lettura, posizione) - Acquisizione metadati ISBN tramite Open Library e Google Books - Log di audit dei movimenti dei libri e dei cambi di stato - Esportazione (CSV, JSON)
Questo servizio fonde intenzionalmente Posizione + Catalog + Acquisizione in un unico servizio per mantenere la creazione di un libro e l’assegnazione allo scaffale in un’unica transazione ACID.
Contesto Gateway (api-gateway)¶
Nessuna logica di dominio, nessun database. Agisce come: - Unico punto di ingresso per tutto il traffico client - Validatore JWT (sicurezza al confine) - Applicatore della policy CORS - Reverse proxy verso i servizi interni
Contesto AI (ai-service + ai_db) — Opzionale¶
Servizio opzionale per funzionalità basate su AI. Comunica con catalog-service per arricchire i libri con tag e rilevare duplicati.
Flusso dati: aggiunta di un libro tramite ISBN¶
1. L’utente punta la fotocamera al codice a barre → il frontend decodifica l’ISBN tramite @zxing/browser
2. Frontend → POST /v1/records/isbn-lookup?isbn=9788845292613
Il gateway valida il JWT, fa il proxy al catalog-service
3. Il catalog controlla la cache locale isbn_cache
Cache miss → interroga Open Library → dati trovati → salva in cache
4. Il catalog restituisce i dati BibliographicRecord al frontend
Il frontend pre-compila titolo, autore, editore, copertina
5. L’utente seleziona la posizione (stanza → libreria → sezione → scaffale → posizione)
e clicca Salva
6. Frontend → POST /v1/books/
Body: { bibliographic_record_id, shelf_id, position, reading_status }
7. Il catalog crea OwnedBook + aggiorna audit_log in una singola transazione
Restituisce la risposta OwnedBook (senza titolo/autore — quelli sono sul record)
8. Il frontend collega OwnedBook a BibliographicRecord in memoria usando
joinBooksToRecords() → mostra la scheda completa del libro all’utente
Architettura del codice (per servizio)¶
Ogni microservizio segue la Clean / Hexagonal Architecture:
Livello di Presentazione
└── app/api/v1/endpoints/ Handler delle route FastAPI
app/api/v1/schemas/ Modelli Pydantic richiesta/risposta
Livello Applicativo
└── app/application/use_cases/ Orchestrazione della logica di business
app/application/services/ Servizi di dominio (token, email, ecc.)
Livello di Dominio
└── app/domain/entities/ Entità di dominio in puro Python
app/domain/repositories/ Interfacce astratte dei repository
app/domain/exceptions.py Eccezioni di dominio
Livello Infrastrutturale
└── app/infrastructure/repositories/ Implementazioni SQLAlchemy
app/infrastructure/external/ Client HTTP (Open Library, Google Books)
app/infrastructure/database.py Setup AsyncSession
Regole da rispettare sempre:
- Il livello di dominio ha zero conoscenza di HTTP, SQLAlchemy o API esterne
- I casi d’uso accettano e restituiscono entità di dominio, non schema HTTP
- Tutta la logica di dominio va nei casi d’uso o nelle entità di dominio — mai negli endpoint
- Nessuna logica nei file
__init__.py - Ogni endpoint chiama esattamente un caso d’uso
Modello di sicurezza¶
Autenticazione¶
- JWT stateless; nessun session store necessario per le richieste normali
- Refresh token archiviati lato server in
auth_db(abilita la revoca al logout) - Durata del token di accesso: 30 minuti
- Durata del refresh token: 30 giorni (ruotato all’uso)
Autorizzazione¶
- Il payload JWT codifica
family_iderole— nessuna query DB aggiuntiva per richiesta - Ogni query del catalog-service filtra per
family_iddal token (isolamento della famiglia) - I ruoli vengono applicati a livello del caso d’uso, non a livello dell’endpoint
Endpoint pubblici vs protetti¶
| Endpoint | Auth |
|---|---|
POST /v1/auth/register |
Pubblico |
POST /v1/auth/login |
Pubblico |
POST /v1/auth/refresh |
Pubblico |
GET /health |
Pubblico |
| Tutto il resto | JWT Bearer richiesto |
Isolamento di rete¶
- I Servizi Privati su Render non sono raggiungibili da internet — solo dagli altri servizi nella stessa regione Render
- Il gateway è l’unico servizio con URL pubblico
- I database (Neon) sono accessibili solo tramite stringhe di connessione autenticate — non esposti su una porta pubblica
Scelte tecnologiche¶
| Decisione | Scelta | Motivazione |
|---|---|---|
| Framework | FastAPI | Async-first, OpenAPI/Swagger integrato, eccellente supporto ai type hint |
| ORM | SQLAlchemy (async) | Async-capable, schema dichiarativo, funziona con PostgreSQL e asyncpg |
| Database | PostgreSQL 16 | Transazioni ACID richieste per le operazioni del catalog (atomicità libro + posizione) |
| Auth | JWT + refresh token lato server | Accesso stateless, refresh revocabile, nessun session store necessario |
| Comunicazione tra servizi | HTTP (nessun message broker) | Minimizza il footprint del deploy; le chiamate sincrone sono sufficienti a questa scala |
| Un DB per servizio | ✅ | Previene l’accoppiamento stretto; abilita scaling indipendente e evoluzione degli schema |
| Clean Architecture | ✅ | Chiara separazione delle responsabilità; la logica di dominio è testabile senza database |
| Stato del frontend | TanStack Query | Stato server normalizzato in cache; elimina la maggior parte dei pattern useState e useEffect |
Ambienti di deployment¶
| Ambiente | Infrastruttura | Quando usato |
|---|---|---|
| Locale | Docker Compose | Sviluppo quotidiano — stack completo con un comando |
| Produzione | Render + Neon | Sistema live — utenti della famiglia |
| Staging | (non configurato) | Può essere aggiunto duplicando i servizi Render con variabili d’ambiente separate |