Architecture Overview¶
System Diagram¶
┌─────────────────────────────────────────────────────────────────┐
│ 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, PUBLIC) │
│ │
│ JWT validation · CORS · Reverse proxy │
└─────────┬─────────────────────┬────────────────────────────────┘
│ internal HTTP │ internal HTTP
▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐
│ auth-service │ │ catalog-service │ │ ai-service │
│ (Private) │ │ (Private) │ │ (Private) │
│ │ │ │ │ optional │
│ families │ │ rooms │ │ │
│ users │ │ bookcases │ │ tagging │
│ JWT │ │ books │ │ dedup │
│ refresh tokens │ │ ISBN lookup │ │ recommend. │
└────────┬─────────┘ └────────┬──────────┘ └───────┬───────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ auth_db │ │catalog_db│ │ ai_db │
│ (Neon) │ │ (Neon) │ │ (Neon) │
└──────────┘ └──────────┘ └──────────┘
Bounded Contexts¶
Auth Context (auth-service + auth_db)¶
Owns everything about who can access the system:
- Family account lifecycle (create, update)
- User lifecycle (create, invite, change role, deactivate)
- Authentication: password hashing, JWT issuance, refresh token rotation
- Authorization roles: admin | editor | viewer
The auth-service is the only issuer of JWTs. All other services validate tokens using the shared JWT_SECRET_KEY but never issue new ones.
Catalog Context (catalog-service + catalog_db)¶
Owns everything about what books exist and where they are: - Physical location hierarchy: rooms → bookcases → sections → shelves - Bibliographic records (title, author, ISBN, metadata) - Owned book copies (which copy is on which shelf, reading status, position) - ISBN metadata ingestion via Open Library and Google Books - Audit log of book movements and status changes - Export (CSV, JSON)
This service intentionally fuses Location + Catalog + Ingestion into one service to keep book creation and shelf assignment in a single ACID transaction.
Gateway Context (api-gateway)¶
No domain logic, no database. Acts as: - The single entry point for all client traffic - JWT validator (edge security) - CORS policy enforcer - Reverse proxy to internal services
AI Context (ai-service + ai_db) — Optional¶
Optional service for AI-powered features. Communicates with catalog-service to enrich books with tags and detect duplicates.
Data Flow Example: Adding a Book via ISBN¶
1. User points camera at barcode → frontend decodes ISBN via @zxing/browser
2. Frontend → POST /v1/records/isbn-lookup?isbn=9788845292613
Gateway validates JWT, proxies to catalog-service
3. Catalog checks local isbn_cache
Cache miss → queries Open Library → data found → saves to cache
4. Catalog returns BibliographicRecord data to frontend
Frontend pre-fills title, author, publisher, cover
5. User selects location (room → bookcase → section → shelf → position)
and clicks Save
6. Frontend → POST /v1/books/
Body: { bibliographic_record_id, shelf_id, position, reading_status }
7. Catalog creates OwnedBook + updates audit_log in a single transaction
Returns OwnedBook response (no title/author — those are on the record)
8. Frontend joins OwnedBook to BibliographicRecord in memory using
joinBooksToRecords() → displays full book card to user
Code Architecture (Per Service)¶
Each microservice follows Clean / Hexagonal Architecture:
Presentation Layer
└── app/api/v1/endpoints/ FastAPI route handlers
app/api/v1/schemas/ Pydantic request/response models
Application Layer
└── app/application/use_cases/ Business logic orchestration
app/application/services/ Domain services (token, email, etc.)
Domain Layer
└── app/domain/entities/ Pure Python domain entities
app/domain/repositories/ Abstract repository interfaces
app/domain/exceptions.py Domain exceptions
Infrastructure Layer
└── app/infrastructure/repositories/ SQLAlchemy implementations
app/infrastructure/external/ HTTP clients (Open Library, Google Books)
app/infrastructure/database.py AsyncSession setup
Rules that must be upheld:
- The domain layer has zero knowledge of HTTP, SQLAlchemy, or external APIs
- Use cases accept and return domain entities, not HTTP schemas
- All domain logic goes in use cases or domain entities — never in endpoints
- No logic in
__init__.pyfiles - Every endpoint calls exactly one use case
Security Model¶
Authentication¶
- Stateless JWTs; no session store needed for normal requests
- Refresh tokens stored server-side in
auth_db(enables revocation on logout) - Access token lifetime: 30 minutes
- Refresh token lifetime: 30 days (rotated on use)
Authorization¶
- JWT payload encodes
family_idandrole— no extra DB lookup per request - Every catalog-service query filters by
family_idfrom the token (family isolation) - Roles enforced at the use-case level, not the endpoint level
Public vs Protected Endpoints¶
| Endpoint | Auth |
|---|---|
POST /v1/auth/register |
Public |
POST /v1/auth/login |
Public |
POST /v1/auth/refresh |
Public |
GET /health |
Public |
| Everything else | Bearer JWT required |
Network Isolation¶
- Private Services on Render are not reachable from the internet — only from other services in the same Render region
- The gateway is the only service with a public URL
- Databases (Neon) are accessible only via authenticated connection strings — not exposed on a public port
Technology Decisions¶
| Decision | Choice | Rationale |
|---|---|---|
| Framework | FastAPI | Async-first, OpenAPI/Swagger built-in, excellent type hint support |
| ORM | SQLAlchemy (async) | Async-capable, declarative schema, works with PostgreSQL and asyncpg |
| Database | PostgreSQL 16 | ACID transactions required for catalog operations (book + position atomicity) |
| Auth | JWT + server-side refresh tokens | Stateless access, revocable refresh, no session store needed |
| Service communication | HTTP (no message broker) | Keeps deployment footprint minimal; synchronous calls are sufficient for this scale |
| One DB per service | ✅ | Prevents tight coupling; enables independent scaling and schema evolution |
| Clean Architecture | ✅ | Clear separation of concerns; domain logic is testable without database |
| Frontend state | TanStack Query | Server state normalized in cache; eliminates most useState and useEffect patterns |
Deployment Environments¶
| Environment | Infrastructure | When Used |
|---|---|---|
| Local | Docker Compose | Daily development — full stack in one command |
| Production | Render + Neon | Live system — family users |
| Staging | (not configured) | Can be added by duplicating Render services with separate env vars |