# Patterns d'Architecture - ChartBastan ## Vue d'ensemble ChartBastan utilise une architecture **client-serveur** avec: - **Frontend:** Application React/Next.js (SPA avec SSR) - **Backend:** API REST FastAPI - **Communication:** REST API via HTTP - **Architecture asynchrone:** RabbitMQ pour tâches lourdes --- ## Partie 1: Frontend Architecture (chartbastan/) ### Type d'Architecture: Component-Based avec App Router **Framework:** Next.js 16 App Router **Caractéristiques Principales:** #### 1. Server Components vs Client Components **Server Components (par défaut):** - Exécutés sur le serveur - Rendu HTML côté serveur - Accès direct aux ressources serveur - Pas d'hydratation nécessaire - Utilisés pour: layouts, pages, data fetching initial **Client Components (directive 'use client'):** - Exécutés sur le client - Hydratation côté client - Interactivité utilisateur (events, state) - Utilisés pour: formulaires, interactions, state local **Exemple:** ```typescript // Server Component (par défaut) export default async function MatchPage() { const matches = await fetchMatches(); // Exécuté sur serveur return ; } // Client Component "use client"; export function MatchList({ matches }: { matches: Match[] }) { const [selected, setSelected] = useState(null); // State local return
setSelected(matches[0])} />; } ``` #### 2. App Router Structure **Routing:** Système de fichiers dans `src/app/` **Conventions:** - `page.tsx` - Page principale de la route - `layout.tsx` - Layout partagé pour la route et ses enfants - `loading.tsx` - UI de chargement (automatic streaming) - `error.tsx` - UI d'erreur (error boundary) - `not-found.tsx` - UI pour 404 - `route.ts` - API routes (backend intégré) **Exemple de structure:** ``` src/app/ ├── layout.tsx # Layout racine ├── page.tsx # Page d'accueil (/) ├── login/ │ └── page.tsx # Page de login (/login) ├── matches/ │ ├── page.tsx # Liste des matchs (/matches) │ ├── [id]/ │ │ └── page.tsx # Détail d'un match (/matches/123) │ └── loading.tsx # Loading pour /matches └── api/ └── auth/ └── route.ts # API route POST /api/auth ``` #### 3. Data Fetching Strategy **Approche Hybride:** **Server-Side Rendering (SSR):** - Data fetch dans Server Components - Cache avec React Cache - Revalidation avec revalidatePath - Avantages: SEO, premier rendu rapide **Client-Side Fetching:** - React Query pour data interactive - Cache local et synchronisation - Refetch automatique - Optimistic updates **Exemple:** ```typescript // Server Component: SSR import { fetchMatches } from '@/lib/data'; export default async function MatchesPage() { const matches = await fetchMatches(); // Cache automatique return ; } // Client Component: React Query "use client"; import { useQuery } from '@tanstack/react-query'; export function MatchList({ initialData }) { const { data } = useQuery({ queryKey: ['matches'], queryFn: fetchMatches, initialData, // Hydratation depuis SSR refetchInterval: 30000, // Refetch toutes les 30s }); } ``` #### 4. State Management **Zustand (Global State):** - Stores légers et simples - Pas de Provider/Context nécessaires - Sélecteurs pour optimisation - DevTools intégrés **Exemple:** ```typescript // store/user.ts import { create } from 'zustand'; interface UserStore { user: User | null; setUser: (user: User) => void; } export const useUserStore = create((set) => ({ user: null, setUser: (user) => set({ user }), })); // Utilisation export function UserProfile() { const user = useUserStore((state) => state.user); return
{user?.name}
; } ``` **React Query (Server State):** - Cache et synchronisation des données serveur - Refetch, mutation, invalidation automatique - Gestion optimiste - Loading et error states **Exemple:** ```typescript const { data, isLoading, error } = useQuery({ queryKey: ['matches'], queryFn: () => fetch('/api/matches').then(r => r.json()), staleTime: 5000, // Données fraîches pendant 5s }); if (isLoading) return ; if (error) return ; return ; ``` #### 5. Middleware **Next.js Middleware:** - Authentification (better-auth) - Protection de routes - Redirections - Headers et cookies **Exemple:** ```typescript // middleware.ts import { auth } from "@/lib/auth"; export default auth((req) => { const isLoggedIn = !!req.auth; const isOnDashboard = req.nextUrl.pathname.startsWith("/dashboard"); if (!isLoggedIn && isOnDashboard) { return Response.redirect(new URL("/login", req.url)); } }); export const config = { matcher: ["/dashboard/:path*", "/api/auth/:path*"], }; ``` --- ### Architecture des Composants #### 1. Component Hierarchy **Composition de composants:** - Pages → Layouts → Sections → Components → UI Elements **Exemple:** ``` MatchPage (Page) └── MatchLayout (Layout) ├── MatchHeader (Section) │ └── MatchTitle (Component) ├── MatchContent (Section) │ ├── MatchInfo (Component) │ ├── MatchStats (Component) │ │ └── StatCard (UI Element) │ └── MatchPredictions (Component) │ └── PredictionCard (UI Element) └── MatchFooter (Section) ``` #### 2. Component Patterns **Container/Presentational Pattern:** ```typescript // Container: Logique et state export function MatchContainer() { const { data, isLoading } = useMatch(matchId); const { predict } = usePrediction(); if (isLoading) return ; return ; } // Presentational: UI pure export function MatchView({ match, onPredict }: Props) { return (
onPredict(match.id)} />
); } ``` **Compound Components Pattern (shadcn/ui):** ```typescript Open Title ``` #### 3. Custom Hooks **Logique réutilisable:** ```typescript // hooks/use-matches.ts export function useMatches() { return useQuery({ queryKey: ['matches'], queryFn: fetchMatches, }); } // hooks/use-auth.ts export function useAuth() { const { user } = useUserStore(); return { isAuthenticated: !!user, user }; } ``` --- ## Partie 2: Backend Architecture (backend/) ### Type d'Architecture: REST API / Layered Architecture **Framework:** FastAPI **Caractéristiques Principales:** #### 1. Layered Architecture **Couches:** 1. **API Layer** (`app/api/`) - Endpoints REST - Validation des requêtes - Sérialisation des réponses 2. **Service Layer** (`app/services/`) - Logique métier - Orchestration des opérations - Pas de dépendances directes aux modèles 3. **Repository/Model Layer** (`app/models/`) - Accès aux données - Opérations CRUD - Abstraction de la base de données 4. **ML Layer** (`app/ml/`) - Services de machine learning - Analyse de sentiment - Calcul de prédictions 5. **Scraper Layer** (`app/scrapers/`) - Collecte de données externes - Scraping Twitter/Reddit/RSS - Normalisation des données **Exemple de flux:** ``` Request: GET /api/matches/1 API Layer (matches.py) ↓ Validate request ↓ Call service Service Layer (match_service.py) ↓ Get match from repository ↓ Calculate predictions (ML service) ↓ Calculate energy (sentiment service) ↓ Aggregate data Repository Layer (match.py) ↓ Query database ↓ Return model API Layer ↓ Serialize with Pydantic ↓ Return response ``` #### 2. Dependency Injection **FastAPI Depends:** - Injection de dépendances automatique - Test facile avec mocks - Gestion des cycles de vie **Exemple:** ```python # main.py app = FastAPI() # Dependency: Database session def get_db(): db = SessionLocal() try: yield db finally: db.close() # Dependency: Service def get_match_service(db: Session = Depends(get_db)): return MatchService(db) # Usage in endpoint @app.get("/matches/{match_id}") async def get_match( match_id: int, service: MatchService = Depends(get_match_service) ): return service.get_match(match_id) ``` #### 3. Pydantic Validation **Schémas de validation:** ```python # schemas/match.py from pydantic import BaseModel class MatchCreate(BaseModel): home_team: str away_team: str scheduled_date: datetime league: str class MatchResponse(BaseModel): id: int home_team: str away_team: str scheduled_date: datetime predictions: List[PredictionResponse] class Config: orm_mode = True ``` **Utilisation:** ```python @app.post("/matches", response_model=MatchResponse) async def create_match( match_data: MatchCreate, db: Session = Depends(get_db) ): # Validation automatique par Pydantic match = Match(**match_data.dict()) db.add(match) db.commit() return match ``` #### 4. Async/Await **Opérations asynchrones:** ```python # scrapers/twitter_scraper.py async def scrape_tweets(match_id: int): # Opération async (non-bloquante) tweets = await twitter_client.search_tweets(...) return tweets # main.py @app.post("/scrape/{match_id}") async def scrape_match(match_id: int): tweets = await scrape_tweets(match_id) return {"count": len(tweets)} ``` #### 5. Background Tasks & Workers **Background Tasks (FastAPI):** ```python from fastapi import BackgroundTasks @app.post("/predict/{match_id}") async def predict_match( match_id: int, background_tasks: BackgroundTasks ): # Task en arrière-plan background_tasks.add_task( calculate_prediction, match_id ) return {"message": "Prediction started"} ``` **RabbitMQ Workers (Indépendants):** ```python # workers/scraping_worker.py def consume_scraping_tasks(): connection = pika.BlockingConnection(...) channel = connection.channel() channel.queue_declare(queue='scraping') for method_frame, properties, body in channel.consume('scraping'): task = json.loads(body) scrape_match(task['match_id']) channel.basic_ack(delivery_tag=method_frame.delivery_tag) ``` --- ## Architecture Intégrée (Frontend + Backend) ### Communication Pattern **REST API Communication:** ``` Frontend Component ↓ React Query (Client) ↓ HTTP Fetch Backend API Endpoint ↓ Pydantic Validation ↓ Service Layer ↓ Repository Layer ↓ Database (SQLAlchemy) ``` **Example Flow:** ```typescript // Frontend: src/services/matches.ts export function useMatches() { return useQuery({ queryKey: ['matches'], queryFn: () => fetch('/api/matches').then(r => r.json()), }); } ``` ```python # Backend: app/api/matches.py @app.get("/matches", response_model=List[MatchResponse]) async def get_matches(service: MatchService = Depends(get_match_service)): return service.get_all_matches() ``` ```python # Backend: app/services/match_service.py class MatchService: def get_all_matches(self): matches = self.db.query(Match).all() # Add predictions, energy calculations return matches ``` --- ## Architecture Asynchrone (RabbitMQ) ### Pattern Producer-Consumer **Queue Architecture:** ``` Frontend Request ↓ Backend API (Producer) ↓ Publish to Queue RabbitMQ ↓ Message Worker (Consumer) ↓ Process Task ↓ Update Database Database Update ↓ Frontend Polling/Websocket ``` **Example Tasks:** 1. **Scraping Task:** - Producer: API endpoint when user requests scrape - Consumer: Scraping worker - Task: Scrape Twitter/Reddit for match 2. **Analysis Task:** - Producer: Scraping worker after data collected - Consumer: Sentiment analysis worker - Task: Analyze sentiment of collected tweets/posts 3. **Prediction Task:** - Producer: Analysis worker after sentiment calculated - Consumer: Prediction worker - Task: Calculate prediction based on energy --- ## Architecture de Base de Données ### Phase 1: SQLite **Schema:** - Frontend: `chartbastan.db` (Drizzle ORM) - Backend: Même database (SQLAlchemy) **Tables:** - users (authentification) - matches (données de matchs) - predictions (prédictions utilisateurs) - tweets (données Twitter) - posts (données Reddit) - energy_scores (énergie collective) ### Phase 2: PostgreSQL **Avantages:** - Meilleure performance pour grandes quantités de données - Concurrency supérieure - Support avancé des requêtes - Scalabilité horizontale **Migration:** - Même schema SQLite → PostgreSQL - Alembic pour migrations - Drizzle pour frontend --- ## Patterns de Sécurité ### Frontend Security **better-auth:** - JWT tokens - Session management - Password hashing (bcryptjs) - CSRF protection **Next.js Security:** - Middleware pour auth - Protected routes - Secure cookies (httpOnly) ### Backend Security **FastAPI Security:** - JWT authentication - Pydantic validation - CORS configuration - SQL injection prevention (SQLAlchemy) **Example:** ```python # middleware/auth.py from fastapi import Security, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials security = HTTPBearer() async def get_current_user( credentials: HTTPAuthorizationCredentials = Security(security) ): token = credentials.credentials user = verify_token(token) if not user: raise HTTPException(status_code=401, detail="Invalid token") return user @app.get("/protected") async def protected_route(user = Depends(get_current_user)): return {"user": user} ``` --- ## Patterns de Performance ### Frontend Performance **Code Splitting:** - Automatic code splitting (Next.js) - Dynamic imports for heavy components - Route-based splitting ```typescript // Dynamic import const MatchDetail = dynamic(() => import('@/components/MatchDetail'), { loading: () => , ssr: false, }); ``` **Optimistic Updates:** ```typescript const mutation = useMutation({ mutationFn: predictMatch, onMutate: async (newPrediction) => { // Cancel pending queries await queryClient.cancelQueries(['predictions']); // Optimistic update queryClient.setQueryData(['predictions'], (old) => [...old, newPrediction]); }, onError: (err, newPrediction, context) => { // Rollback on error queryClient.setQueryData(['predictions'], context.previousPredictions); }, }); ``` **Streaming & Suspense:** ```typescript export default async function Page() { return ( }> ); } ``` ### Backend Performance **Async Operations:** ```python # Multiple async operations tweets = await scrape_twitter(match_id) posts = await scrape_reddit(match_id) rss = await scrape_rss(match_id) # Process in parallel tweets, posts, rss = await asyncio.gather( scrape_twitter(match_id), scrape_reddit(match_id), scrape_rss(match_id) ) ``` **Connection Pooling:** ```python # SQLAlchemy engine = create_engine( DATABASE_URL, pool_size=10, max_overflow=20, pool_pre_ping=True ) ``` **Caching:** ```python from functools import lru_cache @lru_cache(maxsize=128) def get_prediction_model(): # Expensive operation, cached return load_model() ``` --- ## Résumé de l'Architecture **Frontend (Next.js):** - App Router (Server + Client Components) - State: Zustand + React Query - Data Fetching: SSR + Client-side - UI: Tailwind + shadcn/ui **Backend (FastAPI):** - REST API Layered - Service-oriented - Async/await - RabbitMQ workers **Integration:** - REST API communication - Shared database (SQLite) - Async processing with queues **Architecture appropriée pour:** - Application web moderne - Real-time data - Scaling horizontal - Maintenance facile