Ölçeklenebilir LMS Mimarisi: Çok Kiracılı Model
Küresel pazar Öğrenme Yönetim Sistemi (LMS) geçti 28 milyar dolar 2025'te2030 yılına kadar neredeyse 70 milyarlık bir büyüme öngörüsü var. Moodle, Canvas, Docebo ve Coursera muazzam mimari zorlukları gizliyor: binlerce kuruluşa hizmet vermek eşzamanlı olarak veri izolasyonunu sağlayın, kullanımın yoğun olduğu zamanlarda yatay olarak ölçeklendirin ve aynı anda bağlanan milyonlarca öğrenci için saniyeden kısa gecikme sürelerini koruyun.
La çoklu kiracılık ve tüm bunları mümkün kılan mimari desen. Çok kiracılı bir ÖYS'de, tek bir uygulama örneği, her biri kendi kullanıcısına sahip olan birden fazla kuruluşa (kiracı) hizmet eder, kurslar, içerik ve konfigürasyonlar, ancak temel altyapı paylaşılıyor. Desen seçimi Doğru çoklu kiracılık, operasyonel maliyetleri, güvenliği, performansı ve ölçeklendirme yeteneğini belirler sistemi yeniden yazmaya gerek kalmadan 10 ila 10.000 kiracı.
Serinin bu açılış makalesinde Eğitim Teknolojisi Mühendisliğiderinlemesine analiz edeceğiz LMS platformları için çok kiracılı mimariler, veri izolasyon kalıplarının karşılaştırılması, gerçek zamanlı yetenekler için ölçeklendirme stratejileri, optimize edilmiş veri modelleri ve olaya dayalı modeller. Her konsepte TypeScript ve Python'daki somut kod örnekleri eşlik edecek.
Bu Makalede Neler Öğreneceksiniz?
- Üç temel çoklu kiralama modeli ve her birinin ne zaman kullanılacağı
- Binlerce kiracıyla ölçeklenen bir LMS veri modeli nasıl tasarlanır?
- JWT ve RBAC ile çok kiracılı bağlamlarda kimlik doğrulama ve yetkilendirme
- Kiracı çözümü için REST API ve GraphQL tasarlayın
- Gerçek zamanlı bildirimler ve analizler için olay odaklı mimari
- Kurumsal bir LMS platformunun mikro hizmetlere ayrıştırılması
- Moodle, Canvas, Blackboard ve modern platformların mimarilerinin karşılaştırılması
- Yüksek trafikli LMS için performans kıyaslamaları ve önbelleğe alma stratejileri
EdTech Mühendislik Serisine Genel Bakış
| # | Öğe | Odak |
|---|---|---|
| 1 | Buradasınız - Ölçeklenebilir LMS Mimarisi | LMS platformları için çok kiracılı modeller |
| 2 | Eğitici Video Yayını | Video içeriği dağıtım mimarisi |
| 3 | Öğrenme Analitiği ve xAPI | Öğrenme verilerinin toplanması ve analizi |
| 4 | SCORM ve İçerik Paketleme | Eğitim içeriğine ilişkin standartlar |
| 5 | Yapay Zeka ile Uyarlanabilir Öğrenme | Eğitim kurslarının kişiselleştirilmesi |
| 6 | Platformlarda Oyunlaştırma | Rozetler, skor tablosu ve motivasyon |
| 7 | Gözetmenlik ve Değerlendirme | Güvenli ve dolandırıcılığa karşı çevrimiçi sınavlar |
| 8 | Gerçek Zamanlı İşbirliği | Paylaşılan beyaz tahtalar ve işbirliğine dayalı düzenleme |
| 9 | Mobil ve Çevrimdışı Öğrenme | PWA ve çevrimdışı senkronizasyon |
| 10 | Sanal Öğretmen olarak LLM | Dil modellerinin e-öğrenmeye entegrasyonu |
Öğrenim Yönetim Sistemi Görünümü
Modern bir ÖYS/ÖYS artık izleme sistemine sahip basit bir kurs deposu değildir. Platformlar bugünün entegrasyonu uyarlanabilir video akışı, interaktif değerlendirmeler, tahmine dayalı analitik, gerçek zamanlı işbirliği ve giderek daha sık olarak, yapay zeka tabanlı öğretmenler. Bu işlevsel karmaşıklık şu anlama gelir: doğrudan mimari karmaşıklığa.
LMS platformları, dağıtım modeline göre üç makro kategoriye ayrılır:
- Kendi kendine barındırılan (şirket içi): Moodle, Açık edX. Organizasyon tüm altyapıyı yönetir. Maksimum kontrol, maksimum operasyonel yük.
- Çok kiracılı SaaS: Canvas Cloud, TalentLMS, Docebo. Satıcı her şeyi halleder. Her kiracının mantıksal olarak izole edilmiş kendi alanı vardır.
- PaaS/Hibrit: Blackboard Learn Ultra, Brightspace. Satıcı tarafından yönetilen altyapı, ancak gelişmiş özelleştirme seçenekleri ve özel dağıtımlarla birlikte.
2025-2026'da hakim trend mimariye doğru bulutta yerel çok kiracılı, Şirket içi çözümlerde %7'ye kıyasla SaaS platformlarında yıldan yıla %34 büyüme görüldü. Bu değişim, işletme maliyetlerini azaltma ve pazara sunma süresini hızlandırma ihtiyacından kaynaklanmaktadır yeni özellikler sunuyor ve küçük mühendislik ekipleriyle giderek artan sayıda kuruluşa hizmet veriyor.
Çok Kiracılı ÖYS/ÖYS Platformlarının Temel Zorlukları
- Veri Yalıtımı: Bir üniversitenin verilerine hiçbir zaman başka bir üniversiteden erişilememelidir
- Gürültülü komşu: 50.000 öğrencisi olan bir kiracı, küçük kiracıların performansını düşürmemelidir
- Özelleştirme: Her kiracı farklı markalama, iş akışı ve entegrasyonlar ister
- Uyumluluk: GDPR, FERPA, SOC 2, ikamet ve veri işleme konusunda özel garantiler gerektirir
- Düzgün olmayan ölçeklendirme: Kullanım zirveleri mevsimseldir (dönem başlangıcı, sınavlar) ve kiracıya göre değişir
Çoklu Kiracılığın Üç Modeli
Çok kiracılı modelin seçimi, bir ÖYS/LMS tasarımında en kritik mimari karardır. Her biri farklı izolasyon, maliyet ve karmaşıklık profiline sahip üç temel yaklaşım vardır.
Model 1: Kiracı Başına Veritabanı (Silo Modeli)
Her kiracı özel bir veritabanı alır. Bu yaklaşım en yüksek düzeyde izolasyon sağlar: veriler fiziksel olarak ayrılmıştır, bir kiracının performansı diğerlerini etkilemez ve işlemler yedekleme/geri yükleme bağımsızdır. Büyük hizmet veren platformların tercih ettiği kalıptır. sıkı uyumluluk gereksinimleri olan kuruluş.
- Yalıtım: Maksimum. Verilerin fiziksel olarak ayrılmasını tamamlayın.
- Maliyet: Yüksek. Her veritabanı özel kaynakları (bağlantılar, depolama, yedekleme) tüketir.
- Karmaşıklık: Yüksek. Şema geçişleri her veritabanında gerçekleştirilmelidir.
- Ölçeklenebilirlik: Doğrusal ama pahalı. Her yeni kiracı = yeni altyapı.
- İdeal kullanım durumu: 100'den az büyük kuruluşu olan, FERPA/HIPAA gerekliliklerine sahip kuruluş.
Desen 2: Kiracı Şeması (Köprü Modeli)
Tüm kiracılar aynı veritabanı örneğini paylaşır ancak her birinin kendi şeması (ad alanı) vardır. Örneğin PostgreSQL'de her kiracı, aynı tablolarla ancak yalıtılmış verilerle kendi şemasında çalışır. Bu yaklaşım izolasyon ve operasyonel maliyeti dengeler.
- Yalıtım: Güçlü. Şema düzeyinde mantıksal ayırma. Açık hatalar olmadan çapraz bulaşma imkansızdır.
- Maliyet: Orta. Tek bir veritabanı kümesi tüm kiracılara hizmet eder.
- Karmaşıklık: Ortalama. Geçişler tüm şemalarda yineleme gerektirir ancak otomatikleştirilebilir.
- Ölçeklenebilirlik: PostgreSQL örneği başına ~500-1.000 şemaya kadar iyidir, bu durumda parçalama gerekir.
- İdeal kullanım durumu: 50-1.000 orta ölçekli kiracıya sahip SaaS, şema başına özelleştirme gerektirir.
Desen 3: Kiracı Kimliğiyle Paylaşılan Şema (Havuz Modeli)
Tüm kiracılar aynı tabloları paylaşır. Yalıtım bir sütunla garanti edilir tenant_id
her tabloda mevcut ve Satır Düzeyinde Güvenlik (RLS) veritabanı düzeyinde. Ve desen
En uygun maliyetli ve operasyonel olarak yönetilmesi en basit olanıdır ancak disiplin gerektirir
uygulama kodunda titiz.
- Yalıtım: Mantıksal. RLS'ye ve uygulama doğrulamasına bağlıdır. Koddaki bir hata, kiracılar arası verileri açığa çıkarabilir.
- Maliyet: Bas. Tek bir veritabanı örneği binlerce kiracıya hizmet eder.
- Karmaşıklık: Geçişler için düşük (tek şema), güvenlik için yüksek (her sorgu kiracı filtresi içermelidir).
- Ölçeklenebilirlik: Harika. Tenant_id tabanlı parçalama (PostgreSQL için Citus) ile binlerce kiracıyı destekler.
- İdeal kullanım durumu: Binlerce küçük/orta kiracıyla yüksek hacimli SaaS.
Desenler Arası Karşılaştırma
| Kriter | Kiracılar için Veritabanı | Kiracı Planı | Paylaşılan Şema + RLS |
|---|---|---|---|
| Veri izolasyonu | Fiziksel (maksimum) | Mantıksal (güçlü) | Mantıksal (RLS) |
| Kiracı başına maliyet | Yüksek (50-200$/ay) | Orta (10-50$/ay) | Düşük (1-10$/ay) |
| Şema taşıma işlemleri | N yürütme (DB başına 1) | N yürütme (şema başına 1) | 1 yürütme |
| Maksimum önerilen kiracılar | 50-200 | 500-2.000 | 10.000+ |
| Gürültülü komşu riski | Hiç kimse | Orta (paylaşılan G/Ç) | Yüksek (parçalamayla azaltılabilir) |
| FERPA/GDPR uyumluluğu | Harika | İyi | Ek denetimler gerektirir |
| Operasyonel karmaşıklık | Yüksek | Ortalama | Düşük |
| Desen özelleştirme | Toplam | Program başına | Sınırlı (özel alanlar) |
Kiracı Çözümü: Ara Yazılım ve Stratejiler
Çok kiracılı bir sistemde her HTTP isteğinin doğru kiracıyla ilişkilendirilmesi gerekir Önce herhangi bir uygulama mantığının Bu süreç adı verilir kiracı kararıve ilk mimarinin katmanı ve güvenlik açısından kritik bir noktadır. Çözümleme başarısız olursa veya atlanırsa, tüm izolasyon çöker.
Çözüm Stratejileri
Bir talebin kiracısını belirlemek için dört ana strateji vardır:
- Alt alan adı:
mit.lms-platform.com,stanford.lms-platform.com. En yaygın olanı, joker karakterli DNS yapılandırması gerektirir. - Yol öneki:
lms-platform.com/tenants/mit/courses. Basit ama API yollarını kirletiyor. - HTTP Başlıkları:
X-Tenant-ID: mit-university. Makineden makineye API için esnek. - JWT iddiası: Kiracı, kimlik doğrulama belirtecinde kodlanmıştır. Güvenlidir ancak kiracıları değiştirmek için yeniden kimlik doğrulama gerektirir.
Çözünürlük Ara Yazılımının Uygulanması (TypeScript/Express)
Aşağıdaki Express ara yazılımı birleşik bir strateji uygular: birincil olarak alt alan adı, Kiracı kayıt defterine göre doğrulamayla birlikte bir geri dönüş olarak HTTP üstbilgisi.
import { Request, Response, NextFunction } from 'express';
import { LRUCache } from 'lru-cache';
// Interfaccia tenant immutabile
interface TenantConfig {
readonly id: string;
readonly slug: string;
readonly dbSchema: string;
readonly plan: 'free' | 'pro' | 'enterprise';
readonly region: 'eu-west-1' | 'us-east-1' | 'ap-southeast-1';
readonly features: ReadonlyArray<string>;
readonly maxUsers: number;
readonly isActive: boolean;
}
// Cache per evitare lookup al DB su ogni richiesta
const tenantCache = new LRUCache<string, TenantConfig>({
max: 5000,
ttl: 1000 * 60 * 5, // 5 minuti
});
// Repository per il lookup dei tenant
interface TenantRepository {
findBySlug(slug: string): Promise<TenantConfig | null>;
}
export function createTenantMiddleware(repo: TenantRepository) {
return async (req: Request, res: Response, next: NextFunction) => {
// 1. Estrai slug dal sottodominio
const host = req.hostname;
let tenantSlug = extractSubdomain(host);
// 2. Fallback: header X-Tenant-ID
if (!tenantSlug) {
tenantSlug = req.headers['x-tenant-id'] as string | undefined
?? null;
}
// 3. Validazione: slug obbligatorio
if (!tenantSlug) {
res.status(400).json({
error: 'TENANT_NOT_RESOLVED',
message: 'Unable to determine tenant from request',
});
return;
}
// 4. Lookup con cache
let config = tenantCache.get(tenantSlug) ?? null;
if (!config) {
config = await repo.findBySlug(tenantSlug);
if (config) {
tenantCache.set(tenantSlug, config);
}
}
// 5. Tenant non trovato o disattivato
if (!config || !config.isActive) {
res.status(404).json({
error: 'TENANT_NOT_FOUND',
message: `Tenant '${tenantSlug}' not found or inactive`,
});
return;
}
// 6. Inietta il contesto tenant nella request
(req as any).tenantConfig = config;
(req as any).tenantId = config.id;
next();
};
}
function extractSubdomain(host: string): string | null {
const parts = host.split('.');
// es: "mit.lms-platform.com" -> ["mit", "lms-platform", "com"]
if (parts.length >= 3) {
const subdomain = parts[0];
// Escludi subdomini di sistema
const reserved = ['www', 'api', 'admin', 'status'];
return reserved.includes(subdomain) ? null : subdomain;
}
return null;
}
Python uygulaması (FastAPI)
FastAPI kullananlar için işte eşdeğeri: bağımlılık enjeksiyonu kiracı kararı için:
from fastapi import Request, HTTPException, Depends
from functools import lru_cache
from dataclasses import dataclass, field
from typing import Optional
import asyncio
@dataclass(frozen=True)
class TenantConfig:
"""Configurazione immutabile del tenant."""
id: str
slug: str
db_schema: str
plan: str # 'free' | 'pro' | 'enterprise'
region: str
features: tuple[str, ...] = field(default_factory=tuple)
max_users: int = 100
is_active: bool = True
class TenantResolver:
"""Risolve il tenant dalla richiesta HTTP."""
def __init__(self, repository):
self._repo = repository
self._cache: dict[str, TenantConfig] = {}
async def resolve(self, request: Request) -> TenantConfig:
# 1. Sottodominio
slug = self._extract_subdomain(request.headers.get("host", ""))
# 2. Fallback: header
if not slug:
slug = request.headers.get("x-tenant-id")
if not slug:
raise HTTPException(
status_code=400,
detail="Unable to determine tenant from request"
)
# 3. Cache lookup
if slug in self._cache:
config = self._cache[slug]
else:
config = await self._repo.find_by_slug(slug)
if config:
self._cache[slug] = config
if not config or not config.is_active:
raise HTTPException(
status_code=404,
detail=f"Tenant '{slug}' not found or inactive"
)
return config
@staticmethod
def _extract_subdomain(host: str) -> Optional[str]:
parts = host.split(".")
if len(parts) >= 3:
subdomain = parts[0]
reserved = {"www", "api", "admin", "status"}
return None if subdomain in reserved else subdomain
return None
# Dependency injection in FastAPI
async def get_tenant(request: Request) -> TenantConfig:
resolver = request.app.state.tenant_resolver
return await resolver.resolve(request)
Çok Kiracılı ÖYS/ÖYS için Veri Modeli
Bir ÖYS/LMS'nin veri modeli, karmaşık varlıkları ve bunların ilişkilerini yakalamalıdır: kullanıcılar, kuruluşlar, kurslar, modüller, dersler, değerlendirme, gönderim, kayıt, ilerleme, sertifikalar. Bir bağlamda çok kiracılıysa, her varlık, sahibi olan kiracıyla ilişkilendirilmeli ve sorgular hem tek kiracılı (norm) hem de kiracılar arası (idari raporlama) işlemler için verimlidir.
Veritabanı Çekirdek Şeması
Aşağıdaki SQL şeması, "paylaşılan şema + tenant_id" düzenine sahip çok kiracılı bir ÖYS'nin çekirdeğini temsil eder. PostgreSQL'i şununla kullanın: Satır Düzeyinde Güvenlik (RLS) izolasyonu sağlamak için.
-- ============================================
-- TABELLA TENANT (registro delle organizzazioni)
-- ============================================
CREATE TABLE tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug VARCHAR(63) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
plan VARCHAR(20) NOT NULL DEFAULT 'free',
settings JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
is_active BOOLEAN NOT NULL DEFAULT TRUE
);
-- ============================================
-- TABELLA UTENTI
-- ============================================
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
email VARCHAR(255) NOT NULL,
full_name VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'student',
avatar_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (tenant_id, email)
);
-- ============================================
-- TABELLA CORSI
-- ============================================
CREATE TABLE courses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
instructor_id UUID NOT NULL REFERENCES users(id),
title VARCHAR(500) NOT NULL,
slug VARCHAR(255) NOT NULL,
description TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'draft',
max_enrollment INTEGER,
settings JSONB NOT NULL DEFAULT '{}',
published_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (tenant_id, slug)
);
-- ============================================
-- MODULI E LEZIONI (struttura gerarchica)
-- ============================================
CREATE TABLE modules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
position INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE lessons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
module_id UUID NOT NULL REFERENCES modules(id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
content_type VARCHAR(30) NOT NULL, -- 'video', 'text', 'quiz', 'assignment'
content_ref TEXT, -- URL o ID del contenuto
duration_min INTEGER,
position INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================
-- ISCRIZIONI (enrollments)
-- ============================================
CREATE TABLE enrollments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
user_id UUID NOT NULL REFERENCES users(id),
course_id UUID NOT NULL REFERENCES courses(id),
status VARCHAR(20) NOT NULL DEFAULT 'active',
enrolled_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ,
UNIQUE (tenant_id, user_id, course_id)
);
-- ============================================
-- PROGRESSI (lesson completion tracking)
-- ============================================
CREATE TABLE lesson_progress (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
user_id UUID NOT NULL REFERENCES users(id),
lesson_id UUID NOT NULL REFERENCES lessons(id),
status VARCHAR(20) NOT NULL DEFAULT 'not_started',
progress_pct SMALLINT NOT NULL DEFAULT 0,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
UNIQUE (tenant_id, user_id, lesson_id)
);
-- ============================================
-- ASSESSMENT E SUBMISSION
-- ============================================
CREATE TABLE assessments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
course_id UUID NOT NULL REFERENCES courses(id),
title VARCHAR(500) NOT NULL,
assessment_type VARCHAR(30) NOT NULL, -- 'quiz', 'exam', 'assignment', 'peer_review'
max_score DECIMAL(6,2) NOT NULL DEFAULT 100,
passing_score DECIMAL(6,2) NOT NULL DEFAULT 60,
time_limit_min INTEGER,
max_attempts INTEGER DEFAULT 1,
questions JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE submissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
assessment_id UUID NOT NULL REFERENCES assessments(id),
user_id UUID NOT NULL REFERENCES users(id),
attempt_number SMALLINT NOT NULL DEFAULT 1,
answers JSONB NOT NULL DEFAULT '{}',
score DECIMAL(6,2),
status VARCHAR(20) NOT NULL DEFAULT 'submitted',
submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
graded_at TIMESTAMPTZ
);
-- ============================================
-- ROW-LEVEL SECURITY (RLS)
-- ============================================
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE courses ENABLE ROW LEVEL SECURITY;
ALTER TABLE enrollments ENABLE ROW LEVEL SECURITY;
ALTER TABLE lesson_progress ENABLE ROW LEVEL SECURITY;
ALTER TABLE submissions ENABLE ROW LEVEL SECURITY;
-- Policy: ogni utente vede solo i dati del proprio tenant
CREATE POLICY tenant_isolation_users ON users
USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
CREATE POLICY tenant_isolation_courses ON courses
USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
CREATE POLICY tenant_isolation_enrollments ON enrollments
USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
-- INDICI per performance multi-tenant
CREATE INDEX idx_users_tenant ON users(tenant_id);
CREATE INDEX idx_courses_tenant ON courses(tenant_id);
CREATE INDEX idx_enrollments_tenant_user ON enrollments(tenant_id, user_id);
CREATE INDEX idx_enrollments_tenant_course ON enrollments(tenant_id, course_id);
CREATE INDEX idx_lesson_progress_tenant_user ON lesson_progress(tenant_id, user_id);
CREATE INDEX idx_submissions_tenant_assessment ON submissions(tenant_id, assessment_id);
Dikkat: Kiracı Kimliğine Sahip Çok Sütunlu Dizinler
Paylaşılan bir şema LMS'sinde, her indeks içermelidir tenant_id ilk sütun olarak.
Bir indeks (email) ve kiracı tarafından filtrelenen sorgular için işe yaramaz; yardımcı olur (tenant_id, email).
Dağıtılmış Citus/sharding ile dağıtım sütunu (tenant_id) mevcut olmalı
her bileşik birincil anahtarda ve her UNIQUE kısıtlamasında.
İlişki Şeması
| Varlık | İlişki | Bağlı Varlık | Kardinal |
|---|---|---|---|
| Kiracı | sahip | Kullanıcılar, Kurslar | 1:Hayır |
| Kullanıcı | kayıt oluyorsun | Kurs (Kayıt Yoluyla) | N:M |
| Kurs | içerir | Modüller | 1:Hayır |
| Modül | içerir | Dersler | 1:Hayır |
| Kurs | ha | Değerlendirmeler | 1:Hayır |
| Kullanıcı | gönderir | Gönderimler (Değerlendirme yoluyla) | 1:Hayır |
| Kullanıcı | ilerlemeyi takip etmek | Ders İlerlemesi (Ders aracılığıyla) | 1:Hayır |
Çok Kiracılı Kimlik Doğrulaması ve Yetkilendirme
Çok kiracılı bir ÖYS'de kimlik doğrulamanın çözülmesi gerekir iki sorun aynı anda: kullanıcının kimliğini doğrulayın ve kullanıcının hangi kiracıya ait olduğunu belirleyin. Yetki ekler üçüncü düzey: kullanıcının kiracısı içinde hangi kaynaklara erişebileceği.
Talep Kiracılı JWT
En popüler yaklaşımın kullanımı JSON Web Belirteci (JWT) kiracıya özel bir taleple. Belirteç, kimlik doğrulamadan sonra verilir ve çözüm için gerekli tüm bilgileri içerir. veritabanına daha fazla arama yapmadan bağlamın.
import jwt from 'jsonwebtoken';
// Payload JWT immutabile
interface LmsTenantJwtPayload {
readonly sub: string; // User ID
readonly tenantId: string; // Tenant UUID
readonly tenantSlug: string; // Per display e logging
readonly role: 'student' | 'instructor' | 'admin' | 'super_admin';
readonly permissions: ReadonlyArray<string>;
readonly iat: number;
readonly exp: number;
}
// Generazione token con contesto tenant
function generateTenantToken(
user: { id: string; role: string },
tenant: { id: string; slug: string },
permissions: ReadonlyArray<string>
): string {
const payload: Omit<LmsTenantJwtPayload, 'iat' | 'exp'> = {
sub: user.id,
tenantId: tenant.id,
tenantSlug: tenant.slug,
role: user.role as LmsTenantJwtPayload['role'],
permissions,
};
return jwt.sign(payload, process.env.JWT_SECRET!, {
expiresIn: '8h',
issuer: 'lms-platform',
audience: tenant.slug,
});
}
// Middleware di validazione
function validateTenantToken(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'MISSING_TOKEN' });
}
try {
const token = authHeader.slice(7);
const decoded = jwt.verify(token, process.env.JWT_SECRET!, {
issuer: 'lms-platform',
}) as LmsTenantJwtPayload;
// Verifica coerenza tenant tra URL e token
const urlTenantId = (req as any).tenantId;
if (urlTenantId && decoded.tenantId !== urlTenantId) {
return res.status(403).json({
error: 'TENANT_MISMATCH',
message: 'Token tenant does not match request tenant',
});
}
(req as any).user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: 'INVALID_TOKEN' });
}
}
LMS için RBAC modeli
Bir ÖYS/ÖYS'deki yetkilendirme genellikle bir şablonu takip eder Rol Tabanlı Erişim Kontrolü (RBAC) her biri kümülatif izinlere sahip dört ana rol:
| Rol | Anahtar İzinler | Süpürgeler |
|---|---|---|
| Öğrenci | Kayıtlı dersleri görüntüleyin, ödevleri gönderin, notlarınızı görüntüleyin | Yalnızca kayıtlı olduğunuz dersler |
| Eğitmen | Kurslar oluşturun/düzenleyin, gönderimleri değerlendirin, kurslarınızın analizlerini görüntüleyin | Yalnızca kendi kurslarınız |
| Yönetici | Kullanıcıları yönetin, kiracıyı yapılandırın, tüm kursları görüntüleyin, raporları dışa aktarın | Tüm kiracı |
| Süper Yönetici | Kiracıları, faturalandırmayı, özellik işaretlerini, kiracılar arası desteği yönetme | Tüm kiracılar (platform operatörü) |
Çok Kiracılı ÖYS/ÖYS için API Tasarımı
Çok kiracılı bir ÖYS/LMS'nin API'lerinin dengelenmesi gerekir kullanım kolaylığı geliştiriciler için istemci (ön uç, mobil, entegrasyonlar) ile sıkı güvenlik veri izolasyonunda. REST ve GraphQL arasındaki seçim, baskın erişim modellerine bağlıdır.
REST API: Çoklu Kiracılığa İlişkin Kurallar
LMS için bir REST mimarisinde kiracı, ara yazılım (alt etki alanı veya başlık) tarafından çözümlenir. bu nedenle kaynak yolları kiracı kimliğini içermez. Bu, API'leri basitleştirir ve ortamlar arasında taşınabilir.
# Tenant risolto via sottodominio: mit.lms-api.com
# Il tenant_id e iniettato dal middleware, NON presente nei path
# === CORSI ===
GET /api/v1/courses # Lista corsi del tenant
GET /api/v1/courses/:courseId # Dettaglio corso
POST /api/v1/courses # Crea corso (instructor+)
PUT /api/v1/courses/:courseId # Aggiorna corso
DELETE /api/v1/courses/:courseId # Archivia corso (admin+)
# === ISCRIZIONI ===
POST /api/v1/courses/:courseId/enroll # Iscrivi utente corrente
GET /api/v1/enrollments # Le mie iscrizioni
GET /api/v1/courses/:courseId/students # Studenti iscritti (instructor+)
# === CONTENUTI ===
GET /api/v1/courses/:courseId/modules # Moduli del corso
GET /api/v1/modules/:moduleId/lessons # Lezioni del modulo
PUT /api/v1/lessons/:lessonId/progress # Aggiorna progresso
# === ASSESSMENT ===
GET /api/v1/courses/:courseId/assessments # Assessment del corso
POST /api/v1/assessments/:assessmentId/submissions # Sottometti risposta
GET /api/v1/submissions/:submissionId # Dettaglio submission
# === ANALYTICS (instructor/admin) ===
GET /api/v1/analytics/courses/:courseId/completion # Tassi completamento
GET /api/v1/analytics/tenant/overview # Overview tenant
GraphQL: Ön Uç için Esnek Sorgular
GraphQL özellikle LMS istemcileri için etkilidir çünkü sayfalar sıklıkla bunu gerektirir karmaşık toplamalar: kayıtlı dersler, ilerleme durumu ve yaklaşan dersler için öğrenci kontrol paneli atama ve bildirimler tek bir istekte.
# Il contesto tenant e iniettato automaticamente dal middleware
# Ogni resolver filtra per tenant_id dal contesto
type Query {
# Corsi disponibili per il tenant corrente
courses(
status: CourseStatus
search: String
pagination: PaginationInput
): CourseConnection!
# Dettaglio corso (solo se nel tenant corrente)
course(id: ID!): Course
# Dashboard studente: dati aggregati in una singola query
myDashboard: StudentDashboard!
# Analytics per instructor
courseAnalytics(courseId: ID!): CourseAnalytics!
}
type Course {
id: ID!
title: String!
description: String
instructor: User!
modules: [Module!]!
enrollmentCount: Int!
completionRate: Float
status: CourseStatus!
createdAt: DateTime!
}
type StudentDashboard {
enrolledCourses: [EnrolledCourseProgress!]!
upcomingAssessments: [Assessment!]!
recentActivity: [ActivityEvent!]!
overallProgress: Float!
certificatesEarned: Int!
}
type EnrolledCourseProgress {
course: Course!
progressPercent: Float!
lastAccessedAt: DateTime
nextLesson: Lesson
}
type Mutation {
enrollInCourse(courseId: ID!): Enrollment!
updateLessonProgress(lessonId: ID!, progress: Int!): LessonProgress!
submitAssessment(assessmentId: ID!, answers: JSON!): Submission!
createCourse(input: CreateCourseInput!): Course!
}
type Subscription {
# Notifiche real-time per lo studente
studentNotifications: Notification!
# Aggiornamenti live del progresso (per instructor dashboard)
courseProgressUpdates(courseId: ID!): ProgressUpdate!
}
İçerik Dağıtımı ve Medya Akışı
Bir LMS, heterojen içeriği yönetir: PDF belgeleri, sunumlar, isteğe bağlı videolar, canlı akış, interaktif sınavlar ve sanal atölyeler. İçerik dağıtım mimarisi şunları garanti etmelidir: düşük gecikme, kiracı başına izolasyon e erişim kontrolü granüler.
Çok Kiracılı Depolama Stratejisi
Depolama modelinin seçimi veritabanının seçimini yansıtır: daha fazla izolasyon daha fazla maliyet anlamına gelir. SaaS LMS için en yaygın yaklaşım, tek S3 kovası önek tabanlı yalıtımla:
# Pattern di organizzazione S3 per LMS multi-tenant
# Bucket: lms-content-production
# Contenuti del corso
s3://lms-content/{tenant_id}/courses/{course_id}/videos/lecture-01.mp4
s3://lms-content/{tenant_id}/courses/{course_id}/documents/syllabus.pdf
s3://lms-content/{tenant_id}/courses/{course_id}/images/cover.webp
# Submission degli studenti
s3://lms-content/{tenant_id}/submissions/{assessment_id}/{user_id}/file.pdf
# Asset del tenant (logo, branding)
s3://lms-content/{tenant_id}/branding/logo.svg
s3://lms-content/{tenant_id}/branding/theme.json
# Bucket policy limita l'accesso per prefix = tenant_id
# Signed URLs con expiry di 1 ora per accesso ai contenuti
Eğitici Videolar için CDN Mimarisi
Video, modern bir ÖYS/LMS trafiğinin %70-80'ini temsil eder. Teslimat mimarisi video şunları gerektirir:
- Uyarlanabilir Kod Dönüştürme: Her video, uyarlanabilir bit hızı akışı için HLS/DASH ile birden fazla çözünürlükte (360p, 720p, 1080p) kodlanır.
- Belirteç kimlik doğrulamalı CDN: Ücretli içeriğe yetkisiz erişimi önlemek için imzalı URL'lere sahip CloudFront veya Cloudflare.
- Kiracı başına kenar önbelleğe alma: Daha büyük kiracılardan gelen içerik, TTFB'yi azaltmak için PoP kenarlarına önceden yüklenir.
- Dinamik Filigranlama: İçerik sızıntılarını izlemek için Kullanıcı Kimliği ile görünmez katman.
Video Boru Hattı Mimarisi
Bir eğitmen tarafından yüklenen bir videonun tam akışı:
- Yüklemeler: S3'e doğrudan yükleme için önceden imzalanmış URL (uygulama sunucusu atlaması)
- S3 Etkinliği: Lambda/Bulut İşlevini Tetikle
s3:ObjectCreated - Kod dönüştürme: Çoklu bit hızına sahip HLS oluşturmak için ECS/Cloud Run'da AWS MediaConvert veya FFmpeg
- Meta veriler: LMS veritabanına yazılan süre, küçük resim, boyut
- CDN'nin Geçersiz Kılması: Sahip kiracı için önbellek ısınması
- Bildiri: Eğitmenleri WebSocket aracılığıyla bilgilendirmek için etkinlik yayınlandı
Gerçek Zamanlı İşlevsellik için Olay Odaklı Mimari
Modern bir ÖYS/ÖYS, birçok asenkron ve gerçek zamanlı özellik gerektirir: eğitmen olduğunda anlık bildirimler oylama yayınlama, canlı sıralama güncellemeleri, kontrol panelinde gerçek zamanlı ilerleme takibi eğitmen, yaklaşan son teslim tarihleri için otomatik hatırlatmalar. Bu ihtiyaçlar gerektirir birolay odaklı mimari.
LMS için Etki Alanı Etkinlikleri
İlk adım, önemli platform etki alanı olaylarını tanımlamaktır. Her olay, sistemde olup biteni tanımlayan değişmez bir gerçektir.
// Evento base immutabile
interface LmsDomainEvent {
readonly eventId: string;
readonly eventType: string;
readonly tenantId: string;
readonly userId: string;
readonly timestamp: string; // ISO 8601
readonly version: number; // Schema versioning
}
// === ENROLLMENT EVENTS ===
interface StudentEnrolledEvent extends LmsDomainEvent {
readonly eventType: 'student.enrolled';
readonly payload: {
readonly courseId: string;
readonly courseName: string;
readonly enrollmentId: string;
};
}
// === PROGRESS EVENTS ===
interface LessonCompletedEvent extends LmsDomainEvent {
readonly eventType: 'lesson.completed';
readonly payload: {
readonly courseId: string;
readonly moduleId: string;
readonly lessonId: string;
readonly progressPercent: number; // 0-100 per il corso
};
}
// === ASSESSMENT EVENTS ===
interface AssessmentSubmittedEvent extends LmsDomainEvent {
readonly eventType: 'assessment.submitted';
readonly payload: {
readonly assessmentId: string;
readonly submissionId: string;
readonly courseId: string;
readonly attemptNumber: number;
};
}
interface AssessmentGradedEvent extends LmsDomainEvent {
readonly eventType: 'assessment.graded';
readonly payload: {
readonly submissionId: string;
readonly assessmentId: string;
readonly score: number;
readonly maxScore: number;
readonly passed: boolean;
};
}
// === COURSE LIFECYCLE EVENTS ===
interface CoursePublishedEvent extends LmsDomainEvent {
readonly eventType: 'course.published';
readonly payload: {
readonly courseId: string;
readonly courseName: string;
readonly instructorId: string;
readonly moduleCount: number;
readonly lessonCount: number;
};
}
// Event bus con partizionamento per tenant
class LmsEventBus {
constructor(private readonly kafka: KafkaProducer) {}
async publish(event: LmsDomainEvent): Promise<void> {
await this.kafka.send({
topic: `lms.events.${event.eventType.split('.')[0]}`,
messages: [{
key: event.tenantId, // Partizionamento per tenant
value: JSON.stringify(event),
headers: {
'event-type': event.eventType,
'tenant-id': event.tenantId,
'event-version': String(event.version),
},
}],
});
}
}
Tüketici ve Projeksiyonlar
Olaylar, yan etkileri gerçekleştiren özel işleyiciler tarafından tüketilir: bildirimler, Gerçekleştirilmiş görünüm, analiz ve e-posta planlamasının güncellenmesi. Desen etki alanı başına tüketici bütünlüğü korur.
| Etkinlik | Tüketici | Aksiyon |
|---|---|---|
student.enrolled | Bildirim Hizmeti | Hoş geldiniz e-postası + anlık bildirim |
student.enrolled | Analiz Hizmeti | Ders kayıt sayacını güncelle |
lesson.completed | İlerleme Hizmeti | Genel kurs ilerlemesini yeniden hesaplayın |
lesson.completed | Oyunlaştırma Hizmeti | Bir rozetin kilidini açıp açmadığına bakın |
assessment.graded | Bildirim Hizmeti | Öğrenciye notunu bildir |
assessment.graded | Sertifika Hizmeti | Kurs tamamlanırsa sertifika oluşturun |
course.published | SearchIndexService | Katalog Elasticsearch dizinini güncelle |
course.published | Bildirim Hizmeti | Kayıtlı öğrencileri öğretmene bilgilendirmek |
Mikro Hizmetlere Ayrışma
100.000'den fazla eşzamanlı kullanıcıya hizmet veren kurumsal LMS platformları için monolit, bir darboğaz. Mikro hizmetlere ayrıştırma, bağımsız olarak ölçeklendirmenize olanak tanır en stresli bileşenler. Önemli olan tanımlamaktır sınırlı bağlam doğru Etki Alanına Dayalı Tasarım ilkelerini takip ederek.
Bir ÖYS/ÖYS'nin Sınırlı Bağlamı
| Mikro hizmet | Sorumluluk | Veritabanları | Protokol |
|---|---|---|---|
| Kiracı Hizmeti | Kiracı kaydı, yapılandırmalar, faturalandırma, özellik işaretleri | Özel PostgreSQL | REST + gRPC |
| Kimlik Hizmeti | Kimlik doğrulama, SSO (SAML/OIDC), kullanıcı yönetimi, RBAC | PostgreSQL + Redis (oturumlar) | REST + OAuth 2.0 |
| Kurs Hizmeti | CRUD kursları, modüller, dersler, katalog, arama | PostgreSQL + Elasticsearch | REST + GraphQL |
| Kayıt Hizmeti | Kayıtlar, ilerleme, tamamlamalar, sertifikalar | PostgreSQL + Redis (ilerleme önbelleği) | REST + Kafka etkinlikleri |
| Değerlendirme Hizmeti | Sınav motoru, not verme, değerlendirme listesi, hakem incelemesi | PostgreSQL + S3 (dosya gönderimi) | REST + Kafka etkinlikleri |
| İçerik Hizmeti | Yükleme, kod dönüştürme, depolama, CDN, SCORM paketleme | S3 + DynamoDB (meta veriler) | REST + S3 etkinlikleri |
| Bildirim Hizmeti | E-posta, push, uygulama içi, WebSocket, özet planlama | Redis + SQS | Kafka + WebSocket etkinlikleri |
| Analitik Hizmeti | xAPI/Caliper, kontrol panelleri, raporlar, veri aktarımı | ClickHouse + S3 (veri gölü) | Kafka + REST etkinlikleri |
Hizmetler Arası İletişim
İletişim iki temel modeli izler:
- Eşzamanlı (REST/gRPC): Anında müdahale gerektiren işlemler için. Örnek: Kurs Hizmeti, kurs oluşturmadan önce kullanıcının izinlerini kontrol etmek için Kimlik Hizmetini çağırır.
- Asenkron (Kafka): Anında müdahale gerektirmeyen işlemler için. Örnek: Bir öğrenci bir dersi tamamladığında etkinlik Notification, Analytics ve Gamification'dan bağımsız olarak yayınlanır ve tüketilir.
Altın Kural: API Çağrıları yerine Etkinlikleri Tercih Edin
Bir eylem eşzamanlı yanıt gerektirmiyorsa, her zaman bir etkinlik kullan. Bu, hizmetler arasındaki bağlantıyı azaltır, esnekliği artırır (bir tüketicinin başarısızlığı) yapımcıyı engellemez) ve hata ayıklama ve kurtarma için olayın tekrar oynatılmasına olanak tanır. Bir ÖYS/LMS'de ideal oran yaklaşık olarak %70 etkinlik / %30 eşzamanlı çağrıdır.
Ölçeklenebilirlik: Önbelleğe Alma, Parçalama ve CDN
Bir kurumsal ÖYS/ÖYS'nin dramatik zirvelerle başa çıkması gerekir: bir üniversite döneminin ilk günü, on binlerce öğrenci aynı anda giriş yapıyor. Çevrimiçi sınav platformu Sınav oturumları sırasında 10 kat yüke maruz kalır. İyi tasarlanmış bir ölçeklendirme stratejisi olmadan, sistem tam da en ihtiyaç duyulduğu anda çöküyor.
Çok Düzeyli Önbelleğe Alma Stratejisi
| Seviye | Teknoloji | Cachati verileri | TTL |
|---|---|---|---|
| L1: Tarayıcılar | Hizmet Çalışanı + Önbellek API'si | Statik varlıklar, ders içeriği (çevrimdışı) | 24 saat (versiyonlama yoluyla geçersiz kılma) |
| L2: CDN Kenarı | CloudFront / Cloudflare | HLS videoları, görselleri, CSS/JS, PDF belgeleri | 7 gün (yayınlandığında geçersiz kılınma) |
| L3: API Ağ Geçidi | Redis/Vernik | API yanıtlarını AL (kurs kataloğu, kullanıcı profili) | 5-15 dakika |
| L4: Uygulama | Redis Kümesi | Oturumlar, kiracı yapılandırması, öğrenci ilerlemesi, RBAC izinleri | 5-30 dakika |
| L5: Veritabanları | PostgreSQL Gerçekleştirilmiş Görünümler | Toplu analizler, skor tablosu ve kurs istatistikleri | Her 5-60 dakikada bir yenileyin |
PostgreSQL için Citus ile Parçalama
Milyonlarca kullanıcısı olan ve "paylaşılan şema" kalıbına sahip LMS için, Narenciye (PostgreSQL uzantısı
dağıtılmış bilgi işlem için), SQL uyumluluğunu korurken verileri birden çok düğüme dağıtmanıza olanak tanır.
Çok kiracılı bir ÖYS/LMS için doğal parçalama stratejisi ve tenant_id.
-- Distribuire le tabelle principali per tenant_id
SELECT create_distributed_table('users', 'tenant_id');
SELECT create_distributed_table('courses', 'tenant_id');
SELECT create_distributed_table('enrollments', 'tenant_id');
SELECT create_distributed_table('lesson_progress', 'tenant_id');
SELECT create_distributed_table('submissions', 'tenant_id');
-- Tabelle di riferimento (replicate su ogni nodo)
SELECT create_reference_table('tenants');
-- Query automaticamente distribuite
-- Citus instrada le query al nodo corretto basandosi su tenant_id
SELECT c.title, COUNT(e.id) AS enrolled_students
FROM courses c
JOIN enrollments e ON c.id = e.course_id AND c.tenant_id = e.tenant_id
WHERE c.tenant_id = 'uuid-tenant-mit'
GROUP BY c.title
ORDER BY enrolled_students DESC;
-- Performance: query single-tenant eseguita su un singolo nodo
-- Nessun shuffle di dati tra nodi = latenza minima
Performans Karşılaştırmaları
Aşağıdaki kıyaslamalar 5.000 kiracı ve 2 milyon toplam kullanıcıya sahip bir ÖYS/LMS'yi temel almaktadır:
PostgreSQL 16 + Citus, 3 düğüm ile AWS'de dağıtıldı r6g.2xlarge:
| Operasyon | Citus olmadan (tek düğüm) | Citus ile (3 düğüm) | Gelişim |
|---|---|---|---|
| Kurs listesi (kiracı başına) | 45ms | 12ms | 3,7x |
| Öğrenci kontrol paneli | 180ms | 35ms | 5,1x |
| Kurs analitiği (toplama) | 2.400ms | 320ms | 7,5x |
| Kayıt patlaması (1.000 eş zamanlı) | Zaman aşımı (>5s) | 89ms (p99) | >56x |
| Kiracılar arası raporlama (yönetici) | 12.000ms | 1.800ms | 6,7x |
Mimari Karşılaştırma: Gerçek LMS Platformları
Teorik kalıpların gerçek uygulamalara nasıl dönüştüğünü anlamak için gelin analiz edelim. Piyasadaki ana LMS platformlarının mimarisi.
| platformu | Mimarlık | Çoklu Kiralama | Teknik Yığın | Ölçeklenebilirlik |
|---|---|---|---|---|
| Moodle | Modüler monolit | Kiracı başına (MoodleCloud) veya tek kiracılı (kendi kendine barındırılan) veritabanı | PHP 8, MySQL/PostgreSQL, cron işleri | Dikey, yoğun ayarlama gerekmeden örnek başına ~10.000 kullanıcıyla sınırlıdır |
| Kanvas ÖYS/ÖYS | Uydu hizmetlerine sahip monolit | Kuruma göre parçayla paylaşılan şema (Switchman gem) | Ruby on Rails, PostgreSQL, Redis, S3, Konsolos | Parçalama yoluyla yatay, milyonlarca kullanıcıya hizmet veren |
| Blackboard Learn Ultra | Mikro hizmetler (Java monolitinden geçiş) | Hibrit: Kiracı başına veritabanı (kurumsal), paylaşılan şema (SaaS) | Java/Spring Boot, AWS, mikro hizmetler, Kafka | Bulutta yerel, hizmet başına otomatik ölçeklendirme |
| edX'i aç | Hizmet Odaklı (Django hizmetleri) | Kuruluş başına örnek (Öğretmen) veya çoklu site | Python/Django, MySQL, MongoDB, Kereviz, RabbitMQ | Kubernetes tabanlı, hizmet başına ölçeklendirme |
| Docebo | Bulutta yerel SaaS | tenant_id + RLS ile paylaşılan şema | Tescilli (Node.js/Go olduğu varsayılır), AWS, AI-yerel | Otomatik ölçeklendirme, 3.800'den fazla kurumsal müşteriye hizmet veriyor |
| Coursera | Olay odaklı mikro hizmetler | İş ortağı başına parçalamayla yerel çok kiracılı | Scala, Python, Cassandra, Kafka, Kubernetes | Küresel, 130 milyondan fazla kullanıcı, çoklu bölge |
Gerçek Platformlardan Dersler
- Tuval: Switchman gem aracılığıyla parçalama, iyi tasarlanmış bir monolitin, mikro hizmetleri yeniden yazmaya gerek kalmadan milyonlarca kullanıcıya ölçeklenebileceğini gösteriyor.
- Karatahta: Java monolitinden mikro hizmetlere geçiş 4 yıldan fazla sürdü ve önemli bir yatırımdı. Alınan ders: Erken ayrıştırın ya da hiç ayrıştırmayın.
- Kurs: Kafka'nın omurgası olduğu olay odaklı mimari, mevcut hizmetlerde değişiklik yapmadan yeni özellikler (Yapay Zeka önerileri, analizler) eklemenize olanak tanır.
- edX'i açın: Django çoklu hizmet yaklaşımı, "yekpare" bir çerçeveyle bile sınırlı bağlamların iyi bir şekilde ayrılmasının sağlanabileceğini göstermektedir.
Mimarlık Seçimi Rehberi
Evrensel olarak en iyi model yoktur. Seçim ürün profiline bağlıdır, beklenen kiracı sayısına ve mevcut mühendislik ekibine göre. İşte pratik bir karar ağacı:
Karar Ağacı
- 2 yıl içinde kaç kiracı öngörüyorsunuz?
- 50'den az, özel sözleşmeli tüm işletmeler → Kiracı başına veritabanı
- 50-2.000, farklı boyutlarda → Kiracı başına şema
- Çoğu KOBİ olmak üzere 2.000'den fazla → Paylaşılan şema + RLS
- Uyum ne kadar kritik?
- Titiz denetim takibine sahip FERPA/HIPAA → Fiziksel izolasyonu tercih edin (kiracı başına DB)
- Standart GDPR → Uygun denetime sahip Kiracı veya RLS planı
- Mühendislik ekibi ne kadar büyük?
- 3-5 geliştirici → Paylaşılan şema (daha az operasyonel ek yük)
- 10-20 geliştirici → Kiracı veya hafif mikro hizmet şeması
- 20'den fazla geliştirici → Mikro hizmetleri ekip sahipliğiyle tamamlayın
- Mikro hizmetler mi yoksa monolit mi?
- 50.000'den az eşzamanlı kullanıcı → Modüler monolit (A la Canvas)
- 50.000-500.000 eşzamanlı kullanıcı → Medya ve analiz için Monolith + uydu hizmetleri
- 500.000'den fazla eşzamanlı kullanıcı → Olay veri yolu ile mikro hizmetleri tamamlayın
Sonuçlar ve Sonraki Adımlar
Ölçeklenebilir, çok kiracılı bir ÖYS/LMS tasarlamak, etki yaratacak mimari kararlar gerektirir Yıllardır ürünün büyüme kabiliyeti, veri güvenliği ve işletme maliyetleri konusunda. Tek bir çözüm yok: En başarılı platformlar mimarilerini geliştirmiştir zamanla, genellikle ortak bir desene sahip bir monolitten başlayıp yavaş yavaş ayrışıyor En kritik hizmetler.
Bu makalenin önemli noktaları:
- Müşteri profilinize göre çok kiracılı modeli seçinteknolojik moda değil. Çoğu SaaS LMS için paylaşılan şema + RLS yeterlidir.
- Kiracı çözümü ve güvenliğin temeli. Bunu, önbelleğe alma ve sıkı doğrulama özellikleriyle sağlam bir ara katman yazılımı olarak dağıtın.
- PostgreSQL Satır Düzeyinde Güvenlik en iyi müttefikinizdir aşırı uygulama karmaşıklığı olmadan veritabanı düzeyinde izolasyon sağlamak.
- Olay odaklı mimari vazgeçilmezdir modern bir ÖYS/ÖYS'nin bildirimleri, analizleri ve tüm gerçek zamanlı özellikleri için.
- PostgreSQL için Citus Tek düğüm artık yeterli olmadığında minimum uygulama değişikliğiyle yatay olarak ölçeklendirmenize olanak tanır.
- Modüler bir monolitle başlayın ve yalnızca ölçek veya ekip organizasyonu gerektirdiğinde mikro hizmetlere ayrıştırılır.
Serinin bir sonraki makalesinde, konuyu inceleyeceğiz.eğitici video akışı mimarisi, Uyarlanabilir kod dönüştürmeyi, korumalı içerik için DRM'yi ve canlı dersler için ultra düşük gecikmeyi ele alıyor. Video altyapısı genellikle bir ÖYS/ÖYS'nin en pahalı ve karmaşık bileşenidir ve analize değerdir kapsamlı.
Daha fazla bilgi edinmek için kaynaklar
- Crunchy Veri Blogu: "Postgres Veritabanınızı Çoklu Kiralama için Tasarlama" - PostgreSQL ile çoklu kiracılığa yönelik pratik kılavuz
- Citus Belgeleri: Gerçek dünyadan parçalama örnekleriyle çok kiracılı SaaS eğitimi
- Kanvas LMS (GitHub): Switchman for Rails + PostgreSQL parçalama ile açık kaynak kodu
- EdX Mimarisini açın: Open edX hizmetleri mimarisinin resmi belgeleri
- Microsoft Azure Çok Kiracılı Desenler: Çok kiracılı bulut SaaS için mimarlar kılavuzu







