Module 1 - Introduction à WikiJS et concepts de base
Découvrez WikiJS, la plateforme wiki moderne avec authentification avancée et SSO
Module 1Introduction
Bienvenue dans votre formation complète sur WikiJS et l'authentification moderne ! Dans ce premier module, nous allons découvrir WikiJS, une plateforme wiki nouvelle génération qui révolutionne la gestion documentaire avec des fonctionnalités avancées d'authentification et de sécurité.
Pourquoi WikiJS ?
- Moderne : Interface intuitive et responsive, basée sur Node.js
- Sécurisé : Authentification multi-providers (SSO, OAuth, LDAP)
- Flexible : Déploiement cloud ou on-premise selon vos besoins
- Évolutif : Architecture modulaire et extensible
Ce que vous allez apprendre :
- Qu'est-ce que WikiJS et ses avantages
- Comparaison avec autres solutions wiki
- Architecture et composants de WikiJS
- Cas d'usage professionnels
- Introduction aux systèmes d'authentification
- Concepts de base du SSO
- Stratégies de monétisation
- Préparation à l'installation
Qu'est-ce que WikiJS ?
Définition
WikiJS est une plateforme wiki moderne et puissante construite avec Node.js. Elle permet de créer, organiser et partager des connaissances de manière collaborative avec une interface utilisateur intuitive et des fonctionnalités avancées d'authentification.
Analogie : Les styles architecturaux
Tout comme l'architecture a différents styles (gothique, moderne, art déco), la programmation a différents paradigmes. Chaque style a ses propres règles, avantages et contextes d'utilisation appropriés.
// IMPéRATIF : Comment faire étape par étape
var somme = 0
for (i <- 0 until liste.length) {
somme += liste(i)
}
// FONCTIONNEL : Quoi faire, pas comment
val somme = liste.sum
// ou
val somme = liste.reduce(_ + _)
Comment faire quelque chose, étape par étape
Organiser le code autour d'objets et leurs interactions
Quoi faire, en utilisant des fonctions comme briques de base
Paradigme Impératif
La programmation impérative décrit comment un programme doit accomplir une tâche en décrivant une séquence d'instructions à exécuter dans un ordre précis.
Caractéristiques principales :
- Séquence d'instructions : Le programme est une liste d'ordres à suivre
- état mutable : Les variables peuvent changer de valeur
- Contrôle de flux : if/else, boucles, sauts conditionnels
- Effets de bord : Les fonctions peuvent modifier l'état global
// Style impératif - étape par étape
def findMax(numbers: Array[Int]): Int = {
var max = numbers(0) // 1. Initialiser le maximum
var i = 1 // 2. Initialiser l'index
while (i < numbers.length) { // 3. Boucler tant qu'il y a des éléments
if (numbers(i) > max) { // 4. Comparer l'élément actuel
max = numbers(i) // 5. Mettre à jour si nécessaire
}
i += 1 // 6. Passer à l'élément suivant
}
max // 7. Retourner le résultat
}
Avantages
- Proche du fonctionnement de la machine
- Contrôle précis de l'exécution
- Facile à comprendre pour les débutants
- Performance optimisable
Inconvénients
- Code verbeux et répétitif
- Difficile à maintenir et déboguer
- Problèmes de concurrence
- Effets de bord imprévisibles
Exemple de problème : Course condition
En programmation impérative, plusieurs threads peuvent modifier la même variable, causant des résultats imprévisibles. C'est pourquoi les paradigmes plus modernes favorisent l'immutabilité.
Programmation Orientée Objet (POO)
La programmation orientée objet organise le code autour d'objets qui encapsulent des données (attributs) et des comportements (méthodes).
Les 4 piliers de la POO :
- Encapsulation : Regrouper données et méthodes
- Héritage : Réutiliser et étendre du code existant
- Polymorphisme : Un même interface, plusieurs implémentations
- Abstraction : Cacher la complexité interne
// Classe abstraite : définit le contrat
abstract class Forme {
def aire(): Double
def perimetre(): Double
// Méthode concrète commune
def description(): String = s"Aire: ${aire()}, Périmètre: ${perimetre()}"
}
// Implémentation spécifique : Rectangle
class Rectangle(val largeur: Double, val hauteur: Double) extends Forme {
def aire(): Double = largeur * hauteur
def perimetre(): Double = 2 * (largeur + hauteur)
}
// Implémentation spécifique : Cercle
class Cercle(val rayon: Double) extends Forme {
def aire(): Double = math.Pi * rayon * rayon
def perimetre(): Double = 2 * math.Pi * rayon
}
// Utilisation polymorphe
val formes: List[Forme] = List(
new Rectangle(5, 3),
new Cercle(2)
)
formes.foreach(forme => println(forme.description()))
Exemple concret : Analogie avec le monde réel
Imaginez une Voiture : vous n'avez pas besoin de connaître le fonctionnement interne du moteur pour l'utiliser. Vous interagissez avec des interfaces simples (volant, pédales) qui cachent la complexité.
Avantages
- Code modulaire et réutilisable
- Maintien de la logique métier
- Facilite la collaboration en équipe
- Parallèle avec le monde réel
Inconvénients
- Peut être sur-complexe pour des problèmes simples
- Hiérarchies profondes difficiles à maintenir
- état mutable pose des problèmes de concurrence
- Peut encourager un couplage fort
Paradigme Fonctionnel
La programmation fonctionnelle traite le calcul comme l'évaluation de fonctions mathématiques et évite les états changeants et les données mutables.
Principes fondamentaux :
- Immutabilité : Les données ne changent jamais après création
- Fonctions pures : Même entrée = même sortie, sans effets de bord
- Fonctions de première classe : Les fonctions sont des valeurs
- Composition : Combiner des petites fonctions pour créer des solutions complexes
// Données immutables
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// Fonctions pures
def estPair(n: Int): Boolean = n % 2 == 0
def doubler(n: Int): Int = n * 2
def additionner(a: Int, b: Int): Int = a + b
// Composition de fonctions
val resultat = numbers
.filter(estPair) // Garder seulement les pairs: [2, 4, 6, 8, 10]
.map(doubler) // Doubler chaque nombre: [4, 8, 12, 16, 20]
.reduce(additionner) // Additionner tous: 60
println(s"Résultat: $resultat") // Résultat: 60
// Version encore plus concise
val resultatConcis = numbers
.filter(_ % 2 == 0)
.map(_ * 2)
.sum
Parallèle avec les mathématiques
En mathématiques, f(x) = x²
donne toujours le même résultat pour le même x
.
La programmation fonctionnelle applique ce principe : une fonction avec les mêmes paramètres
retourne toujours le même résultat.
// ⌠FONCTION IMPURE : résultat dépend d'un état externe
var compteur = 0
def incrementerImpure(): Int = {
compteur += 1 // Modifie l'état global
compteur // Résultat différent à chaque appel
}
// ✅ FONCTION PURE : résultat déterministe
def incrementerPure(valeur: Int): Int = {
valeur + 1 // Ne modifie rien, retourne simplement valeur + 1
}
// Utilisation
println(incrementerImpure()) // 1
println(incrementerImpure()) // 2 - Résultat différent !
println(incrementerPure(5)) // 6
println(incrementerPure(5)) // 6 - Toujours le même résultat
Avantages
- Code prévisible et testable
- Excellente pour la parallélisation
- Moins de bugs liés aux états
- Code concis et expressif
- Idéal pour le Big Data
Inconvénients
- Courbe d'apprentissage plus raide
- Peut être moins intuitif au début
- Performance parfois moins optimale
- Certains problèmes difficiles à modéliser
Comparaison des paradigmes
Aspect | Impératif | Orienté Objet | Fonctionnel |
---|---|---|---|
Focus principal | Comment faire | Organisation en objets | Quoi faire |
état des données | Mutable | Généralement mutable | Immutable |
Structure | Séquence d'instructions | Classes et objets | Fonctions et composition |
Réutilisabilité | Fonctions et procédures | Héritage et polymorphisme | Composition de fonctions |
Parallélisation | Difficile | Complexe | Naturelle |
Debugging | Moyen | Moyen à difficile | Plus facile |
Big Data | Inadapté | Possible mais complexe | Excellent |
Exemple concret : Calculer le total des ventes par région
// Données exemple
case class Vente(region: String, montant: Double)
val ventes = List(
Vente("Nord", 100), Vente("Sud", 200), Vente("Nord", 150),
Vente("Est", 80), Vente("Sud", 120), Vente("Nord", 90)
)
// ===== APPROCHE IMPéRATIVE =====
def totalParRegionImperatif(ventes: List[Vente]): Map[String, Double] = {
val resultat = scala.collection.mutable.Map[String, Double]()
for (vente <- ventes) {
if (resultat.contains(vente.region)) {
resultat(vente.region) = resultat(vente.region) + vente.montant
} else {
resultat(vente.region) = vente.montant
}
}
resultat.toMap
}
// ===== APPROCHE ORIENTéE OBJET =====
class AnalyseurVentes(val ventes: List[Vente]) {
private var cache: Map[String, Double] = Map()
def totalParRegion(): Map[String, Double] = {
if (cache.isEmpty) {
cache = ventes.groupBy(_.region).map { case (region, ventesRegion) =>
region -> ventesRegion.map(_.montant).sum
}
}
cache
}
}
// ===== APPROCHE FONCTIONNELLE =====
def totalParRegionFonctionnel(ventes: List[Vente]): Map[String, Double] = {
ventes
.groupBy(_.region)
.mapValues(_.map(_.montant).sum)
}
// Utilisation
val resultats = totalParRegionFonctionnel(ventes)
// Map("Nord" -> 340.0, "Sud" -> 320.0, "Est" -> 80.0)
Scala : Un langage multi-paradigme
Scala combine harmonieusement les paradigmes impératif, orienté objet et fonctionnel, vous permettant de choisir la meilleure approche pour chaque situation.
Pourquoi cette approche multi-paradigme est puissante :
- Flexibilité : Utilisez le paradigme le plus adapté au problème
- Migration progressive : Passez graduellement de OOP vers fonctionnel
- Interopérabilité Java : Réutilisez l'écosystème Java existant
- Expressivité : Code plus concis et lisible
// ===== ORIENTé OBJET : Définition des structures =====
case class Personne(nom: String, age: Int, salaire: Double)
class Entreprise(val nom: String) {
private var employes: List[Personne] = List()
// Méthode OOP pour ajouter un employé
def ajouterEmploye(personne: Personne): Unit = {
employes = personne :: employes // Style hybride : immutable + mutable
}
// ===== FONCTIONNEL : Traitement des données =====
def calculerMoyenneSalaire(): Double = {
employes
.map(_.salaire) // Transformation fonctionnelle
.sum / employes.length // Réduction fonctionnelle
}
def employesEligiblesBonus(seuilAge: Int): List[Personne] = {
employes
.filter(_.age >= seuilAge) // Filtrage fonctionnel
.sortBy(_.salaire) // Tri fonctionnel
}
// ===== IMPéRATIF : Quand c'est plus clair =====
def afficherStatistiques(): Unit = {
println(s"Entreprise: $nom")
println(s"Nombre d'employés: ${employes.length}")
if (employes.nonEmpty) {
val moyenne = calculerMoyenneSalaire()
println(f"Salaire moyen: $moyenne%.2f€")
// Boucle impérative pour l'affichage détaillé
for (employe <- employes.sortBy(_.nom)) {
println(s" - ${employe.nom}, ${employe.age} ans: ${employe.salaire}€")
}
} else {
println("Aucun employé")
}
}
}
// ===== UTILISATION =====
val entreprise = new Entreprise("TechCorp")
// Style OOP
entreprise.ajouterEmploye(Personne("Alice", 30, 50000))
entreprise.ajouterEmploye(Personne("Bob", 25, 45000))
entreprise.ajouterEmploye(Personne("Charlie", 35, 60000))
// Style fonctionnel
val eligibles = entreprise.employesEligiblesBonus(30)
println(s"Employés éligibles au bonus: ${eligibles.map(_.nom).mkString(", ")}")
// Style impératif pour l'affichage
entreprise.afficherStatistiques()
Pourquoi Scala est parfait pour Apache Spark
- Immutabilité : Les RDD sont immutables par nature
- Fonctions de première classe : Parfait pour map, filter, reduce
- Concision : Moins de code boilerplate qu'en Java
- Performance : Compilation vers bytecode Java optimisé
- écosystème : Interopérabilité avec les bibliothèques Java
Questions pratiques
Quel paradigme illustre ce code ?
val nombres = List(1, 2, 3, 4, 5)
val pairs = nombres.filter(_ % 2 == 0)
val doubles = pairs.map(_ * 2)
val somme = doubles.reduce(_ + _)
Réponse : Paradigme Fonctionnel
Ce code illustre parfaitement le paradigme fonctionnel pour plusieurs raisons :
- Immutabilité : Toutes les variables sont déclarées avec
val
- Fonctions pures :
filter
,map
,reduce
ne modifient pas les données originales - Composition : Chaque étape transforme les données sans effet de bord
- Déclaratif : On décrit quoi faire, pas comment
Version impérative équivalente :
val nombres = Array(1, 2, 3, 4, 5)
var somme = 0
for (i <- nombres.indices) {
if (nombres(i) % 2 == 0) {
somme += nombres(i) * 2
}
}
Pourquoi l'immutabilité est-elle importante en Big Data ?
Réponses multiples :
1. 🔒 Sécurité des threads
En Big Data, les calculs sont distribués sur plusieurs machines/threads. Avec des données immutables, impossible d'avoir des "race conditions" o๠deux threads modifient la même donnée simultanément.
2. 🚀 Parallélisation naturelle
Apache Spark peut distribuer les tâches sans se soucier des conflits, car chaque partition de données est indépendante et ne change jamais.
3. 🧹 Simplification du debugging
Une fois créée, une structure de données ne change jamais. Vous savez qu'un bug ne peut pas venir d'une modification inattendue.
4. 📊 Optimisations du compilateur
Le compilateur peut faire des optimisations agressives sachant que les données ne changeront pas.
// RDD immutable - peut être distribué en toute sécurité
val donnees = spark.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
// Chaque transformation crée un nouveau RDD
val pairs = donnees.filter(_ % 2 == 0) // Nouveau RDD
val doubles = pairs.map(_ * 2) // Encore un nouveau RDD
val somme = doubles.reduce(_ + _) // Résultat final
// Les RDD originaux existent toujours et sont inchangés
println(s"Données originales: ${donnees.collect().mkString(", ")}")
println(s"Données pairs: ${pairs.collect().mkString(", ")}")
println(s"Données doublées: ${doubles.collect().mkString(", ")}")
println(s"Somme finale: $somme")
Pour chaque scénario, quel paradigme choisiriez-vous ?
Guide de choix par scénario :
🚀 Choisir FONCTIONNEL pour :
- Traitement de grandes quantités de données
- Calculs mathématiques complexes
- Transformations de collections
- Applications concurrentes
- ETL (Extract, Transform, Load)
ðŸ—ï¸ Choisir ORIENTé OBJET pour :
- Modélisation de domaines métier
- Interfaces utilisateur
- Systèmes avec beaucoup d'état
- APIs et frameworks
- Code nécessitant de l'encapsulation
// Modélisation OOP du domaine
case class LogEntry(ip: String, timestamp: Long, url: String, statusCode: Int, size: Long)
class LogAnalyzer {
// Traitement fonctionnel des données
def analyzerLogs(logs: List[LogEntry]): Map[String, Int] = {
logs
.filter(_.statusCode >= 400) // Erreurs seulement
.groupBy(_.ip) // Grouper par IP
.mapValues(_.length) // Compter par IP
.filter(_._2 >= 10) // IPs avec 10+ erreurs
}
// Style impératif pour l'affichage
def afficherTopErrorIPs(logs: List[LogEntry]): Unit = {
val erreurs = analyzerLogs(logs)
println("=== TOP IPs avec erreurs ===")
for ((ip, count) <- erreurs.toSeq.sortBy(-_._2).take(10)) {
println(f"$ip: $count%d erreurs")
}
}
}
🎯 Stratégie Scala : Combiner intelligemment
- OOP : Pour structurer et modéliser votre domaine
- Fonctionnel : Pour transformer et analyser vos données
- Impératif : Pour les tâches séquentielles spécifiques (I/O, affichage)
Exercice : Transformer du code impératif en fonctionnel
Voici un code impératif qui calcule la moyenne des nombres positifs. Réécrivez-le en style fonctionnel :
def moyennePositifs(nombres: Array[Int]): Double = {
var somme = 0
var compteur = 0
for (i <- nombres.indices) {
if (nombres(i) > 0) {
somme += nombres(i)
compteur += 1
}
}
if (compteur > 0) somme.toDouble / compteur else 0.0
}
Solution fonctionnelle :
def moyennePositifs(nombres: Array[Int]): Double = {
val positifs = nombres.filter(_ > 0)
if (positifs.nonEmpty) positifs.sum.toDouble / positifs.length
else 0.0
}
// Ou encore plus concis avec Option :
def moyennePositifsOption(nombres: Array[Int]): Option[Double] = {
val positifs = nombres.filter(_ > 0)
if (positifs.nonEmpty) Some(positifs.sum.toDouble / positifs.length)
else None
}
// Version ultra-concise :
def moyennePositifsConcise(nombres: Array[Int]): Double = {
nombres.filter(_ > 0) match {
case Array() => 0.0
case positifs => positifs.sum.toDouble / positifs.length
}
}
🔠Analyse des améliorations :
Avantages de la version fonctionnelle :
- Lisibilité : Intent plus claire
- Concision : Moins de code
- Sécurité : Pas de variables mutables
- Testabilité : Plus facile à tester
- Réutilisabilité : Composable
Points d'attention :
- Peut être moins efficace en mémoire (deux passes)
- Courbe d'apprentissage pour les débutants
- Syntaxe moins familière au début
Récapitulatif du Module 1
Vous avez découvert les trois paradigmes fondamentaux de la programmation et compris pourquoi Scala, en les combinant intelligemment, est le choix idéal pour le Big Data et Apache Spark.
🎯 Points clés retenus :
- Paradigmes = styles de pensée
- Fonctionnel = puissant pour données
- Scala = multi-paradigme
🚀 Prêt pour la suite :
- Installer l'environnement Scala
- Syntaxe et types de base
- Collections immutables
💡 Vers Apache Spark :
- RDD immutables
- Transformations fonctionnelles
- Parallélisation naturelle