Archives par étiquette : oop

Interroger un cluster MongoDB Atlas avec Java

image tirée du site medium.com

Dans le cadre de ma formation de trois jours « MongoDB – exploiter vos données efficacement », destinée à de futurs ingénieurs en informatique, je propose aux étudiants de réaliser les travaux dirigés dans le langage de programmation de leur choix.

De mon côté, j’en profite chaque année pour m’essayer à un nouveau langage ou pour me remettre à un langage que je n’ai pas pratiqué depuis longtemps. Cette année, je reviens à Java, que je n’ai plus pratiqué depuis 2020.

Nous avons d’abord exécuté toutes les requêtes de l’exercice sur la collection salles (PDF) à partir de l’interpréteur JavaScript du shell MongoDB accessible dans Compass, en nous connectant à des clusters créés sur Atlas. Ensuite, nous avons traduit ces requêtes dans le langage de programmation préféré de chaque étudiant.

Dépendances du projet

Le code repose sur un nombre très limité de dépendances afin de rester simple et facilement compréhensible par les étudiants. La première est le driver officiel MongoDB Java Sync, qui permet d’interagir avec la base de données en mode synchrone et d’exécuter les requêtes MongoDB depuis le code Java. J’ai également ajouté la bibliothèque dotenv-java, qui facilite la gestion des paramètres de configuration en permettant de charger les variables d’environnement à partir d’un fichier .env, pratique pour stocker les informations de connexion ou les identifiants sans les intégrer directement dans le code source. Enfin, la dépendance slf4j-simple fournit une implémentation minimale du framework de logging SLF4J, utilisée par le driver MongoDB pour afficher les logs.

<dependencies>
    <dependency>
      <groupid>org.mongodb</groupid>
      <artifactid>mongodb-driver-sync</artifactid>
      <version>5.2.0</version>
    </dependency>

    <dependency>
      <groupid>io.github.cdimascio</groupid>
      <artifactid>dotenv-java</artifactid>
      <version>3.0.0</version>
    </dependency>

    <dependency>
      <groupid>org.slf4j</groupid>
      <artifactid>slf4j-simple</artifactid>
      <version>2.0.13</version>
    </dependency>
  </dependencies>

Le fichier d’environnement

On y isole les credentials qui nous permettent de nous connecter à notre cluster fraîchement créé en ligne :

MONGO_USER=monutilisateur
MONGO_PASSWORD=monmotdepasse
MONGO_CLUSTER=url.de.mon.cluster.mongodb.net
MONGO_DB=mabasededonnees
MONGO_COLLECTION=salles

La classe principale

Notre main n’est pas bien compliqué : on y execute tous nos exercices contenus dans un repository dédié à notre collection salles. Ce repository est récupéré via l’appel à une fabrique dédiée.

package com.cesi.mongodb;

import com.mongodb.MongoException;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoDatabase;

public class Main {

    public static void main(String[] args) {
        int exitCode = 0;

        try (MongoClient client = MongoConnection.createClient()) {

            MongoDatabase db = MongoConnection.getDatabase(client);

            System.out.println("Connexion réussie à MongoDB !");
            System.out.println("Database : " + db.getName());

            runSallesExercices(db);

        } catch (MongoException e) {
            exitCode = 2;
            System.err.println("Erreur MongoDB (connexion/requête) : " + e.getMessage());
            e.printStackTrace(System.err);

        } catch (RuntimeException e) {
            exitCode = 1;
            System.err.println("Erreur inattendue : " + e.getMessage());
            e.printStackTrace(System.err);

        } finally {
            System.exit(exitCode);
        }
    }

    private static void runSallesExercices(MongoDatabase db) {
        RepositoryFactory factory = new RepositoryFactory(db);
        SalleRepository repository = factory.salles();

        runExercise(1, repository::exercice1);
        runExercise(2, repository::exercice2);
        runExercise(3, repository::exercice3);
        runExercise(4, repository::exercice4);
        runExercise(5, repository::exercice5);
        runExercise(6, repository::exercice6);
        runExercise(7, repository::exercice7);
        runExercise(8, repository::exercice8);
        runExercise(9, repository::exercice9);
        runExercise(10, repository::exercice10);
    }

    private static void runExercise(int number, Runnable exercise) {
        System.out.println("\n===== Exercice " + number + " =====");
        exercise.run();
    }
}

La fabrique de repositories

Elle ne contient qu’un seul type de repository à ce stade : celui dédié à la gestion de la collection salles, sur laquelle est basée l’exercice.

package com.cesi.mongodb;

import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import io.github.cdimascio.dotenv.Dotenv;
import org.bson.Document;

public final class RepositoryFactory {

    private final MongoDatabase db;
    private final Dotenv env;

    public RepositoryFactory(MongoDatabase db) {
        this(db, Dotenv.load());
    }

    public RepositoryFactory(MongoDatabase db, Dotenv env) {
        this.db = db;
        this.env = env;
    }

    public SalleRepository salles() {
        String collectionName = getRequired("MONGO_COLLECTION", "salles");
        MongoCollection<Document> collection = db.getCollection(collectionName);
        return new SalleRepository(collection);
    }

    private String getRequired(String key, String defaultValue) {
        String value = env.get(key);
        if (value == null || value.isBlank()) {
            return defaultValue;
        }
        return value;
    }
}

Le repository dédié à la collection salles

Il contient la totalité de nos exercices, chacun isolé dans sa méthode de classe.

package com.cesi.mongodb;

import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Sorts;
import com.mongodb.client.model.Updates;
import static com.mongodb.client.model.Filters.*;
import static com.mongodb.client.model.Projections.*;

import org.bson.BsonType;
import org.bson.Document;
import org.bson.conversions.Bson;

import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;

public class SalleRepository {

    private final MongoCollection<Document> collection;

    public SalleRepository(MongoCollection<Document> collection) {
        this.collection = collection;
    }

    public void exercice1() {
        collection.find(eq("smac", true))
            .projection(fields(include("_id", "nom")))
            .forEach(doc -> System.out.println(doc.toJson()));
    }

    public void exercice2() {
        collection.find(gt("capacite", 1000))
            .projection(fields(excludeId(), include("nom")))
            .forEach(doc -> System.out.println(doc.toJson()));
    }

    public void exercice3() {
        collection.find(exists("adresse.numero", false))
            .projection(fields(include("_id")))
            .forEach(doc -> System.out.println(doc.toJson()));
    }

    public void exercice4() {
        collection.find(size("avis", 1))
            .projection(fields(include("_id", "nom")))
            .forEach(doc -> System.out.println(doc.toJson()));
    }

    public void exercice5() {
        collection.find(eq("styles", "blues"))
            .projection(fields(excludeId(), include("styles")))
            .forEach(doc -> System.out.println(doc.toJson()));
    }

    public void exercice6() {
        collection.find(eq("styles.0", "blues"))
            .projection(fields(excludeId(), include("styles")))
            .forEach(doc -> System.out.println(doc.toJson()));
    }

    public void exercice7() {
        Pattern regex = Pattern.compile("^84");
        collection.find(and(regex("adresse.codePostal", regex), lt("capacite", 500)))
            .projection(fields(excludeId(), include("adresse.ville")))
            .forEach(doc -> System.out.println(doc.toJson()));
    }

    public void exercice8() {
        collection.find(or(mod("_id", 2, 0), exists("avis", false)))
            .projection(fields(include("_id")))
            .forEach(doc -> System.out.println(doc.toJson()));
    }

    public void exercice9() {
        collection.find(and(gte("avis.note", 8), lte("avis.note", 10)))
            .projection(fields(excludeId(), include("nom")))
            .forEach(doc -> System.out.println(doc.toJson()));
    }

    public void exercice10() {
        Date after = Date.from(Instant.parse("2025-11-15T00:00:00Z"));

        collection.find(gt("avis.date", after))
            .projection(fields(excludeId(), include("nom")))
            .forEach(doc -> System.out.println(doc.toJson()));
    }
}

Pour exécuter tout ça, il ne reste plus qu’à compiler avec Maven avant. On met les logs MongoDB en mode warn pour éviter la pollution due au mode par défaut, INFO…

mvn compile
mvn exec:java -Dexec.mainClass="com.cesi.mongodb.Main" \
  -Dorg.slf4j.simpleLogger.defaultLogLevel=info \
  -Dorg.slf4j.simpleLogger.log.org.mongodb.driver=warn \
  -Dorg.slf4j.simpleLogger.log.com.mongodb=warn

PHP 7 : les types de retour

Un futur vers les retours

On continue dans la série « Ces nouveautés qui rendent heureux » avec l’annonce de la possibilité de forcer des types de retour de fonctions dans PHP 7 ! Cette avancée majeure a fait là encore l’objet de débats assez vifs au sein de la communauté des développeurs PHP, avec des RFC critiquées, d’autres écartées et d’autres qui reviendront un jour ou l’autre d’outre-tombe !

Terminator, un type de retour

Le type de retour le plus connu au monde !

C’est l’histoire d’un type…

Scalaire ou pas, nous pouvons maintenant signifier un type de retour dans les déclarations de nos fonctions, sous la forme suivante :

<MODE D'ACCES> function <NOM> (liste de paramètres) : <TYPE DE RETOUR>

La position de ce type de retour a fait débat d’entrée de jeu, certains développeurs ne souhaitant pas le voir devant le nom de la fonction, comme c’est le cas en C ou Java, pour des raisons de commodité lors des recherches dans le code (chercher « function mafonc » n’aurait plus fonctionné si elle avait du s’appeler « function int mafonc »).

Commençons avec des types non scalaires :

function eleves(): array {
    return ["jean", "eric", null];
}

var_dump(eleves());

/*
array(3) {
  [0]=>
  string(4) "jean"
  [1]=>
  string(4) "eric"
  [2]=>
  NULL
}
*/

ou bien encore :

class Eleve {
    private $_nom;
    private $_prenom;

    public function __construct ($nom, $prenom) {
        $this->_nom = $nom;
        $this->_prenom = $prenom;
    }
}

function eleve($n, $p): Eleve {
    return new Eleve ($n, $p);
}

var_dump(eleve("Rack", "Eric"));

/*
object(Eleve)#1 (2) {
  ["_nom":"Eleve":private]=>
  string(4) "Rack"
  ["_prenom":"Eleve":private]=>
  string(4) "Eric"
}
*/

Nous avons utilisé dans l’exemple précédent des types non scalaires, respectivement tableau (array) et objet (de la classe Eleve).

Nous pouvons aussi renvoyer des types scalaires, tels que int, float, bool ou encore string :

function qi(): int {
    return 150;
}

var_dump(qi());

Si je modifie le type de retour pour essayer les quatre types susnommés, voici ce que j’obtiens :

int(150)
bool(true)
string(3) "150" 
float(150)

Pensez à activer le typage strict en faisant figurer en première ligne de votre fichier de test (ou plus proprement, dans l’autoloader de votre application) la directive maintenant bien connue de ceux qui veulent bénéficier des plus récentes fonctionnalités en matière de typage strict, à savoir:

declare(strict_types=1);

Comme toujours, il faut respecter le contrat, sinon gare ! Vous ne pouvez pas écrire :

function quotientintellectuel(): int {
    return null;
}

ou bien encore:

function quotientintellectuel(): int {
    return "150";
}

Sous peine de courroucer PHP, qui, pour le premier exemple, vous jettera à la figure un joli :

Fatal error: Uncaught TypeError: Return value of quotientintellectuel()
 must be of the type integer, null returned 

Invariance

Elle reste toujours de mise, à savoir que les signatures des méthodes héritées ou implémentées doivent rester EXACTEMENT les mêmes dans les sous-types. Ainsi, si vous écrivez ceci, vous vous provoquerez naturellement l’ire du compilateur :

class Eleve {}
 
interface EleveDao {
    function trouverParNom($nom): Eleve; 
}
 
class EleveSqlDao implements EleveDao {

    function trouverParNom($nom) {
        // mes plus belles requêtes SQL 
        return new Eleve();
    }
}
Fatal error: Declaration of EleveSqlDao::trouverParNom($nom) must be compatible with EleveDao::trouverParNom($nom): Eleve

Notez que ce comportement pourrait être modifié dans les versions futures du langage.

Peut-on renvoyer « rien » ?

Il y a eu une tentative d’introduire un type void comme en C/C++ ou Java mais cette proposition a été rejetée. Vous savez que par défaut une fonction PHP renvoie null :

function rien() {}

var_dump(rien());
# NULL

Quand on écrit void, on notifie que la fonction ne renvoie rien. Or null par défaut est retourné par PHP, ce sont deux choses différentes.

Avec void on ne renvoie rien, avec return null on renvoie « absence de valeur » !

Quid des fonctions spéciales ?

Certaines fonctions ne sont pas autorisées à faire usage des types de retour :

  • les constructeurs
  • les destructeurs
  • les fonctions de clonage

En tentant de le faire, vous provoqueriez des erreurs fatales.

En résumé

Coluche disait « Voilà une nouvelle qu’elle est bonne ! »; une délicieuse formule qui s’applique à cette nouvelle fonctionnalité qui, avec le typage strict et de nombreuses autres features, constitue une avancée majeure dans le cycle de vie de notre langage préféré. L’introduction de ces possibilités est l’occasion une fois de plus de renforcer la bonne hygiène de vos développements ! Il reste encore des points à régler (void notamment, qu’on espère voir arriver un jour !) mais tout va dans la bonne direction !

PHP : La loi de Déméter en quelques exemples

La loi de Déméter (LoD en anglais, pour Law of Demeter) est un principe de design de systèmes dits « orientés objet ». Son principe élémentaire tient en une phrase :

« Ne parle qu’à tes amis »

Ce principe de conception orientée objet à été évoqué pour la première fois en 1987 à la Northeastern University de Boston (Massachussets) par Ian Holland qui travaillait alors sur un projet du nom de Demeter. Le but de cette « loi » est de maintenir un couplage laĉhe entre les classes.

Ce que permet LoD

LoD nous donne pour principe la chose suivante :

Une méthode dans un objet donné doit seulement invoquer les méthodes des types d’objets suivants : l’objet lui-même, ses objets paramètres, les objets qu’il crée, les objets qu’il a en composition

L’objet lui-même


class A {
    public function __construct() {
        $this->_faireUnTruc();
    }

    protected function _faireUnTruc() {}
}

L’objet instance de A sera autorisé à appeler ses propres fonctions (tout de même…)

Ses paramètres


class B {
    public function faireQuelqueChose() {}
}

class A {
    public function __construct(B $b) {
        $b->faireQuelqueChose();
    }

    protected function _faireUnTruc() {}
}

N’importe quel objet créé dans la classe

class C {
    public function faireDesMiracles() {}
}

class A {
    public function __construct() {
        		$c = new C;
        		$c->faireDesMiracles();
    }
}

Les objets en composition

class D {
    public function faireAutreChose() {}
}

class A {
    private $_d;

    public function __construct() {
        $c = new D;
    }

    public function faireDesChoses() {
        $this->_d->faireAutreChose();
    }
}

Ce qui remet en cause LoD

class C {
    public function faireBeaucoupDeChoses() {}
}

class B {
    private $_c;

    public function __construct(C $c) {
        $this->_c = $c;
    }

    public function getC() {
        return $this->_c;
    }
}

class A {
    private $_d;

    public function __construct() {
        $c = new C;
        $b = new B($c);
        $b->getC()->faireBeaucoupDeChoses();
    }
}

Dans cet exemple, ce n’est pas le chaînage des méthodes en lui-même qui pose un problème mais le fait que A fasse appel à C à travers B. Idéalement, A fait appel à B qui lui, délègue le service demandé à C de sorte que nous n’aurions plus que :

$b->faireBeaucoupDeChoses();

et


class B {
    private $_c;

    public function __construct(C $c) {
        $this->_c = $c;
    }

    public function faireBeaucoupDeChoses() {
        return $this->_c->faireBeaucoupDeChoses();
    }
}

Ici, chaque classe parle à ses amis les plus proches (A à B, B à C) et Déméter n’en sera pas offusquée !

Pour en savoir plus…

Un billet du blog d’Avdi Grimm (anglais)

La page du projet Demeter (anglais)

Les principes SOLID expliquées à ma fille

Ma fille ne comprend rien à l’informatique. Il faut dire qu’à 6 ans, on a autre chose à faire que de se passionner pour ces choses bizarres ! Et puis l’informatique ça a l’air compliqué ! On voit souvent Papa se prendre la tête à deux mains en se demandant pourquoi c’est fait de manière si compliquée alors que le problème adressé est on ne peut plus simple. J’ai tout de même essayé de m’imaginer en train d’expliquer à ma petite tête brune à couettes les principes SOLID, énoncés par « Uncle Bob » il y a maintenant plus d’une décennie.

Commençons par le commencement : le S de SOLID, c’est pour Single Responsibility (responsabilité unique) : une classe n’est pas comme ces équipiers qu’on voit au comptoir des fast-foods; elle ne doit pas faire le drive-in, servir les boissons, nettoyer les tables, faire chauffer les frites, non : UNE SEULE chose à la fois.

Le O c’est pour Open/Closed (ouvert/fermé) : une classe doit être fermée à la modification (de son code source) mais ouverte à l’extension : on n’essaie pas de faire rentrer un carré dans un rond, on crée une « forme », qu’on spécialisera ensuite en rond, carré, losange, triangle, etc.

Le L rend hommage à Madame Barbara Liskov et son principe de substitution: mon objet chimpanzé de la classe Singe a une méthode manger(Fruit) : la banane, la mangue et la noix de coco sont des fruits; ma méthode manger doit avoir le même comportement que je fasse manger(Banane), manger(Mangue) ou manger(Coco), c’est à dire si je substitue le sous-type Banane, Mangue ou Coco au super-type Fruit.

Le I évoque la ségrégation des interfaces (Interface Segregation) : certains animaux savent voler, le singe est un animal mais ne sait pas voler. Pourquoi le faire dépendre d’une classe Animal qui implémenterait une interface AnimalVolant si on sait qu’il ne volera jamais ? Ce principe est très simple : une classe ne doit pas dépendre d’interfaces dont elle n’a pas l’utilité ! L’objectif est ici, une fois de plus, de réduire le couplage inutile entre les classes.

Le dernier de ces 5 principes est le principe d’inversion des dépendances (D pour Dependency Inversion). A est client de B, lui-même client de C; on peut dire que A dépend de B qui dépend de C. Prenons A et B: ce n’est plus A qui va dépendre de B, mais A qui va dire à B ce dont il a besoin. B ne dit plus à A « voilà comment je fonctionne, débrouille toi ! », c’est A qui dit à B « voilà ce dont j’ai besoin, donne le moi ! »…Nous avons donc bien inversé le rapport de force entre fournisseur et client !

Allez, je vous laisse, j’ai un match entre les Littlest Pet Shop et les My little Pony à gérer…

IMG_3521

Sources :

Pour ceux que l’anglais ne rebute pas, je vous conseille l’excellent site d’Oncle Bob et ses hilarants (autant qu’instructifs) codecastsCleanCoders

Toujours dans la langue de Shakespeare…

http://aspiringcraftsman.com/2008/12/28/examining-dependency-inversion/