BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Articles Jakarta Data : Meilleure Intégration Entre Java Et La Base De Données

Jakarta Data : Meilleure Intégration Entre Java Et La Base De Données

Points Clés

  • Les défis lors de l’utilisation d’une base de données en Java

  • Les patterns d’intégration entre Java et les bases de données

  • La proposition de la nouvelle spécification Jakarta Data

La gestion d'une base de données est l'un des plus grands défis au sein d'une architecture logicielle, car en plus de choisir une option parmi plusieurs sur le marché, il est nécessaire de considérer l'intégration de la persistance dans une application. Le but de cet article est de montrer certains de ces patterns et de découvrir une nouvelle proposition de spécification, Jakarta Data, qui vise à faciliter la vie des développeurs avec Java.

Comprendre les couches qui peuvent constituer un logiciel

Chaque fois que nous parlons de complexité dans un système d'entreprise, nous nous concentrons sur l'ancienne stratégie militaire romaine : diviser pour régner ou divide et impera, dans laquelle pour simplifier l'ensemble, nous le décomposons en petites unités.

Dans le monde du logiciel, l'un des moyens de simplifier et de décomposer la complexité consiste à utiliser des couches, qu'elles soient physiques ou logiques. Ces couches, principalement logiques, peuvent être fragmentées entre elles, devenant des composants et/ou des fonctionnalités.

Ce mouvement de découpage en couches est l'un des thèmes du livre Clean Architecture lorsqu'il évoque par exemple la stratégie de séparation du code métier du code d’infrastructure.

Les 3 types de couches dans une application

Quel que soit le style architectural avec lequel vous travaillez, comme le monolithe ou les microservices, il existe un nombre minimum de 3 types de couches dans une application, selon le livre Learning Domain-Driven Design, qui sont :

  1. La couche de présentation : c'est la couche d'interaction avec l'utilisateur, qu'il soit un utilisateur final ou quelqu'un qui travaille dans le génie logiciel

  2. La couche de logique métier : comme son nom l'indique, c'est là que se trouvera la logique métier, ou selon les mots d'Eric Evans, « c'est la couche qui contient le cœur du métier »

  3. La couche d'accès aux données : il s'agit de la couche qui contient l'ensemble des mécanismes de persistance. Dans le cadre d'une architecture Stateless, on peut penser que c'est l'endroit avec une certaine garantie d'état, notant que le mécanisme de stockage est transparent, que ce soit une base de données NoSQL, SQL, une API Rest, etc

Ces trois types de couches ont tendance à être présents dans la grande variété de styles d'architecture logicielle, des plus simples, tels que MVC, aux plus complexes, tels que CQRS, architecture hexagonal, architecture oignon, entre autres.

Outre les trois types de couches, les modèles architecturaux sont très soucieux de la communication entre les composants pour assurer une forte cohésion et un faible couplage, en plus de suivre le principe d'injection de dépendances, le DIP.

Cet isolement de la couche d'accès aux données garantit principalement que le paradigme de la base de données « s'infiltre » dans le modèle métier. Nous savons qu'il existe une grande différence - également appelée impédance - en particulier entre les bases de données relationnelles et la programmation orientée objet

La Couche de persistance

Après l'introduction appropriée sur les couches et leur importance, nous nous concentrerons sur la couche de persistance ou la base de données. Un point de critique et d'admiration au sein du paradigme orienté objet est qu'il existe un large éventail d'options et de normes de communication.

Pour cet article, nous parlerons de quelques patterns prenant en compte le principe de responsabilité unique et également le couplage avec la base de données de la couche métier.

Il convient de noter qu'il n'y a pas de solution miracle, il est donc important de faire attention au contexte dans lequel ce pattern sera utilisé.

Les types de patterns en matière de base de données

Bien sûr, nous avons plusieurs patterns et patterns d'intégration entre la base de données et la POO. Cet article se concentre sur les points suivants :

  • Pattern 1 : programmation orientée données

  • Pattern 2 : enregistrement actif (Active Record)

  • Pattern 3 : mappeur (Mapper)

Maintenant, regardons ces patterns en détail.

Le pattern 1 : programmation orientée données

Dans son livre Data-Oriented, l'auteur Yehonathan Sharvit propose une réduction de la complexité en valorisant et en traitant les données comme un "citoyen de première classe".

Ce pattern peut être résumé en trois principes :

  1. Le code est séparé des données.

  2. Les données sont immuables.

  3. Les données ont un accès flexible.

Cette proposition élimine la couche d'entité, se concentrant explicitement sur les dictionnaires de données à travers, par exemple, d'une implémentation de Map.

Le pattern 2 : enregistrement actif (Active Record)

Ce pattern est devenu très populaire après le livre de Martin Follower "Patterns of Enterprise Application Architecture".

En résumé, le pattern a un seul objet, qui est responsable à la fois des données et du comportement. Cela permet de simplifier les règles métier, puisque l'idée est qu'il y a un accès direct aux données.

Cependant, dans le cas d'architectures plus complexes, Active Record a tendance à devenir assez difficile à gérer, car on ajoute un fort couplage entre la solution de base de données sans parler du non respect du principe de responsabilité unique. Autrement dit : en plus d'être responsable des règles métier, la même classe est également responsable des opérations de base de données.

Dans le monde Java, la solution la plus récente et la plus largement utilisée est Panache de Quarkus. De cette façon, il est possible de faire une application avec des microservices avec seulement deux classes.

@Entity
public class Person extends PanacheEntity {
    public String name;
    public LocalDate birth;
    public Status status;
}

Person person =...;

// rendre l’entité persistante
person.persist();
List<Person> people = Person.listAll();

// obtenir une personne à partir de son ID
person = Person.findById(personId);

Le pattern 3 : mappeur (Mapper)

Une autre solution de communication entre les bases de données qui répond au principe précédent est d’utiliser un Mapper. D'une manière générale, cela laisse les opérations d'entité et de base de données séparées, ce qui, tout au plus, a tendance à apparaître dans les classes, ce sont les métadonnées ou les annotations du monde Java.

Cette norme assure une plus grande séparation entre la couche métier et la base de données, en plus de garantir le principe de responsabilité unique. Avec cela, nous simplifions la maintenance et la lecture dans les applications, car nous avons une classe pour l'entité et une autre pour les opérations de base de données.

Cependant, pour les projets simples, Mapper a tendance à être irréalisable. C'est le cas, par exemple, d'applications extrêmement simples avec CRUD qui, selon le style, auront besoin de trois ou quatre classes pour atteindre le même objectif, contrairement à des standards comme Active Record, qui rempliraient cette fonction avec deux fois moins d’objets.

L'exemple le plus courant, bien sûr, est l'ORM Hibernate. Cependant, ce type de solution n'est pas exclusif aux bases de données relationnelles, car il existe des solutions pour NoSQL telles que Spring Data et Jakarta NoSQL.

@Entity
public class Person {
    @Id
    String name;
    @Column
    LocalDate birth;
    @Column
    Status status;
}

Person person =...;
template.insert(person);

List<Person> people = template.query("select * from Person");

// Obtenir une personne à partir de son ID
person = template.find(Person.class, personId);

DAO versus Repository 

À partir d'un mappeur, il est possible de penser à deux patterns supplémentaires qui sont :

  1. DAO

  2. Repository

Ces patterns prennent en compte le découplage entre le modèle et la couche de persistance, mais la similitude s'arrête là.

Les différences résident dans le couplage car il est lié à la couche d'accès aux données, et non au modèle, en plus de l'accent mis sur les couches de service, la sémantique et la nomenclature. Comprenons mieux ces patterns séparément.

Data Access Object (DAO)

La norme DAO (Data Access Object) isole les couches application et métier de la persistance via une API d'abstraction. Cette API, à son tour, ressemble beaucoup au moteur de base de données.

Person person =...;
personDAO.insert(person);

// obtenir une personne à partir de son ID
person = personDAO.read(id);

Repository

Le pattern repository est un mécanisme qui vous permet d'encapsuler le comportement de stockage, de récupération et de recherche en utilisant une collection d'objets. De cette façon, le pattern masque davantage la relation du mécanisme de persistance.

Ce pattern se concentre sur la proximité la plus proche des entités et masque la provenance des données, ce qui permet à un repository d'utiliser lui-même un pattern DAO.

DAO x Repository

Un point important est la sémantique. Prenons un exemple : en considérant qu'une entité Car aurait Garage comme classe qui représente la collection d'entités. Le résultat serait l'extrait de code suivant:

Car car =...;
garage.save(car);

// obtenir une voiture par son ID
car = garage.get(id);

En résumé : si nous comparons les deux types de pattern  :

  • DAO se concentre sur une abstraction pour accéder à certains types de données.

  • Le Repository se concentre sur une collection, quelle que soit l'origine de l'objet. Pour cette raison, il est possible pour un Repository d'utiliser un DAO pour atteindre son objectif.

 

Une nouvelle API d'accès aux données est née : Jakarta Data

Après avoir pris connaissance de tous les patterns, ainsi que leur importance et leurs compromis respectifs, il est important de franchir une étape supplémentaire : connaître une API qui explore mieux ces patterns et, ainsi, facilite l'adoption de bonnes pratiques avec Java et les ressources de données.

C'est là qu'est née la proposition d'une nouvelle spécification appelée Jakarta Data. Pour en savoir plus, elle est ici (Google Documents).

Jakarta Data repose sur trois principes :

  1. Une API simple et déclarative

  2. Une cohésion dans cette couche

  3. Un découplage avec les autres couches

Toujours dans sa première version, l'objectif de cette nouvelle proposition est de fournir le pattern Repository afin qu'avec une seule interface et quelques annotations, il soit possible d'implémenter Jakarta Data, dans un premier temps, dans trois projets : Relationnel avec JPA, NoSQL avec Jakarta NoSQL et Rest avec Rest Client pour MicroProfile.

@Entity
public class Person {
    @Id
    String name;
    @Column
    LocalDate birth;
    @Column
    Status status;
}

public interface PersonRepository extends Repository<Person, String> {
    
}


Person person =...;
repository.save(person);

Optional<Person> person = repository.findById(id);
repository.deleteById(id);

En résumé : avec Jakarta Data, il est plus simple de traiter la couche métier tant au niveau de la persistance polyglotte que de son découplage.

Jakarta Data : plus de facilité et d'intégration

Travailler avec la Programmation Orientée Objet et la persistance  apporte, en soi, beaucoup de complexité du fait de la richesse d'options, quelle que soit son origine.

Globalement, nous prenons toujours en compte le degré d'isolation des autres couches, ainsi que leur facilité d'utilisation et de maintenance. De ce scénario est né Jakarta Data, dont l'objectif est d'aider l'utilisation de ces patterns de manière simple et intégrée au monde des spécifications Java. Et, par défaut, l'attente est un soutien de la part de la communauté, en aidant et en donnant des commentaires, en particulier dans la liste de diffusion de Jakarta.

 

Au sujet de l’Auteur

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT