Skip to main content

Classes

Une classe se définit comme un document YAML, dans un fichier de modèle.

Une classe doit au minimum avoir un nom (name), un commentaire (comment) et une liste de propriétés (properties).

Définition d'une classe

  • name : Nom de la classe, qui identifie la classe de manière unique (au moins dans le fichier en cours). Toute référence vers une classe utilisera son nom.
  • pluralName : Nom de la classe au pluriel, qui pourra être utilisé par certains générateurs pour représenter une collection de cette classe. Si non renseigné, TopModel prendra le nom de la classe et le suffixera par un "s".
  • sqlName : Surcharge du nom de la classe en SQL, à la place de la transformation en CONSTANT_CASE par défaut.
  • comment : Description de la classe, qui est un champ libre. Comme tous les commentaires dans TopModel, il est obligatoire pour encourager la documentation du modèle. En plus de son utilisation dans les divers générateurs pour des commentaires dans le code, il est aussi utilisé dans l'IDE au survol d'une référence de classe.
  • label : Libellé de la classe, largement inutilisé.
  • trigram : Code, à priori de 3 lettres, qui préfixera si renseigné tous les noms de propriétés de la classe en SQL (hors associations, qui utiliseront le trigramme de la classe ciblée).
  • extends : Référence vers une autre classe pour indiquer que la classe en cours hérite de celle-ci. Il s'agit d'un héritage "classique" qui n'a pas d'impact particulier dans la modélisation.
  • reference : Indique que la classe peut être considérée comme une classe de référence, pour laquelle on suppose que l'ensemble des valeurs ne change pas souvent (voir jamais), et donc qu'il est souhaitable de mettre en cache. Cet attribut est généralement associé à l'ajout d'informations pour la mise en cache et la récupération des éléments depuis ce cache dans les divers générateurs. Pour être définie comme une classe de référence, une classe doit avoir une clé primaire simple (ou à défaut, au moins une propriété avec une clé d'unicité simple).

Propriétés d'une classe

  • properties : Liste des propriétés de la classe.
  • defaultProperty : Propriété "par défaut" de la classe, parfois utilisée comme libellé ou pour le tri. Doit référencer une propriété existante de la classe. Si non renseignée et qu'il existe une propriété nommée Label ou Libelle, elle sera automatiquement ajoutée comme defaultProperty.
  • orderProperty : Propriété de tri de la classe, remplace defaultProperty pour cet usage. Si non renseignée et qu'il existe une propriété nommée Order ou Ordre, elle sera automatiquement ajoutée comme orderProperty.
  • flagProperty : Propriété de la classe à utiliser comme flag binaire (ayant des valeurs comme 1, 10, 100, 1000...). Si non renseignée et qu'il existe une propriété nommée Flag, elle sera automatiquement ajoutée comme flagProperty.
  • localeProperty : Propriété de la classe à utiliser pour indiquer la locale. Si non renseignée et qu'il existe une propriété nommée Locale, elle sera automatiquement ajoutée comme localeProperty.
  • preservePropertyCasing : Par défaut, TopModel converti les noms de propriétés dans la casse du langage cible. Cela veut dire par exemple qu'en C#, tous les noms de propriétés vont être convertis en PascalCase, même si la propriété a été déclarée en camelCase dans TopModel, et inversement en Java (ce qui était déjà le cas en revanche). De même, si vous avez des noms avec des _ dans votre modèle (une classe Profil_utilisateur ou une propriété utilisateur_id), ils seront également convertis de la même façon (en Java par exemple, ça donnerait ProfilUtilisateur et utilisateurId). Cette propriété, si renseignée à true, permet de désactiver ce comportement s'il est important de garder la casse telle qu'elle a été définie dans le modèle (par exemple pour s'interfacer avec une API externe qui n'a pas les mêmes conventions de nommage).

Indexes et clés d'unicité

TopModel permet de définir des indexes sur une classe via les propriétés indexes et/ou unique :

indexes:
- properties: [MyProperty] # Définit un index sur la propriété `MyProperty`.

- [MyProperty2, MyProperty3] # Définit un index sur le couple `MyProperty2` / `MyProperty3`, force raccourcie équivalente à la définition précédente.

- properties: [MyProperty4]
unique: true # Définit un index unique sur la propriété `MyProperty4`.

unique:
- [MyProperty5, MyProperty6] # Définit un index unique sur le couple `MyProperty5` / `MyProperty6`, force raccourcie équivalente à la définition précédente.

Toutes ces façons différentes de créer des indexes et des contraintes d'unicité sont équivalentes, les mêmes objets seront crées dans dans le modèle et le même code sera généré derrière.

Si les indexes simples ne sont en général qu'un détail d'implémentation, les indexes uniques quand à eux seront utilisés dans le modèle pour déterminer certains comportements et certaines vérifications.

Valeurs d'une classe

TopModel permet de définir des valeurs (initiales, ou exhaustives) d'une classe via la propriété values. Cela prendra la forme suivante par exemple :

values:
Valeur1: { Code: "1", Libelle: Valeur 1 }
Valeur2: { Code: "2", Libelle: Valeur 2 }

Les différents générateurs utiliseront ces valeurs pour des scripts d'initialisation de base de données, par exemple. Ils utiliseront aussi les templates de valeurs associés à leur implémentation pour la génération.

Classe enum

Si la classe définit au moins une value, alors cette classe pourra être considérée comme une enum, qui est une classe dont l'ensemble des valeurs possibles est défini dans les values

Une classe enum peut être représentée de 2 façons :

  • Soit comme une classe (en renseigant enum: class sur la classe). Par rapport à une classe classique, ses propriétés uniques peuvent êtres typées comme des enums (en particulier la clé primaire). De plus, en indiquant readonly: true sur la classe (ce qui dans le cas général permet de rendre toutes les propriétés readonly), l'ensemble des valeurs pourra être généré de manière statique sous forme d'instances de la classe dans le code généré.

  • Soit comme une enum (en renseignant enum: true sur la classe). Dans ce cas, la classe deviendra une "vraie" enum et non une classe, et sera générée comme telle, selon les capacités du langage cible. La plupart des langages ne supportant que des enums simples, cela pourrait impliquer que toutes les propriétés autre que la clé primaire ne soient pas disponibles. Quelques remarques :

    • Pour définir une classe enum: true, une clé primaire simple (une seule propriété marquée comme primaryKey, ou à défaut au moins une propriété avec une clé d'unicité simple) est nécessaire sur la classe, et il faut que tous ses identifiants soient valides dans tous les langages cibles (même si vous ne générez du code que dans des langages qui n'ont pas cette restriction)
    • Une association vers une classe enum: true aura toujours useClass: true (la clé primaire ciblée et la classe étant la même chose).
    • Une classe enum: true ne peut avoir que des associations ou compositions vers d'autres classes enum: true (les deux types de propriétés seront d'ailleurs tout à fait équivalents ici).

Par défaut, si une classe peut être une enum, possède une clé primaire, et si son domaine définit ses valeurs comme n'étant pas auto-générées (via autoGeneratedValue: false, ce qui est la valeur par défaut, ce qui est une façon de dire que sa clé primaire n'est pas un ID auto-incrémenté), alors la valeur par défaut de enum sera class. Renseigner enum: true sera toujours explicite, et vous pouvez toujours forcer enum: class si la clé primaire est auto-générée, ou enum: false si vous ne voulez pas d'enum.

Interfaces et classes abstraites

Une classe peut être définie comme une interface ou une classe abstraite via la propriété type de la classe.

Interface

Une interface (type: interface) dans TopModel est une classe sans implémentation, qui se traduira en général comme une interface dans le code généré. Une interface a les caractéristiques suivantes :

  • Elle ne peut pas hériter que d'une autre interface.
  • Aucun setter ne sera généré pour les propriétés marquées readonly: true (si readonly: true est indiqué sur la classe, tous les propriétés seront readonly: true, et donc aucun setter ne sera généré tout court).
  • Elle ne sera pas générée par les générateurs SQL.

De plus, les autres classes du modèle pourront implémenter cette interface, en la listant dans implements. Cela aura pour effet de recopier les propriétés de cette interface dans la classe cible (à l'image d'un décorateur). Cette interface sera aussi implémentée dans le code généré.

Vous pouvez contrôler l'ordre d'insertions des propriétés dans la classe via sourcePropertyOrder, entre properties, implements et decorators (qui sont dans cet ordre-là par défaut).

Classe abstraite

Via type: abstract, la classe peut être marquée comme abstraite. Cela permettra d'indiquer que cette classe ne peut pas être instanciée, et devra être étendue par d'autres classes du modèle. La section héritage plus bas détaille les possibilités et les impacts de rendre une classe abstraite.

Classe persistée

TopModel considère une classe comme étant persistée si elle définit au moins une propriété de clé primaire (primaryKey: true). Cette catégorisation est largement utilisée par les divers générateurs pour déterminer si une classe fait partie du modèle de base de données ou s'il d'agit d'un DTO. Cette distinction, bien qu'importante dans l'architecture générale du modèle d'une application, n'a (presque) aucun impact dans la façon de modéliser des classes dans TopModel. De ce fait, il n'est pas possible de surcharger cette classification.

Remarque : en particulier, une classe enum, une classe de référence, une classe abstraite, ou une classe avec des valeurs peuvent tout à fait être persistées comme non persistées. Naturellement, cela impactera ce qui sera généré.

Si vous voulez utiliser un ID auto-incrémenté pour votre clé primaire simple, il faudra configurer son domaine avec autoGeneratedValue.

Classe de traductions

Si vous souhaitez persister vos traductions (de libellés et/ou de listes de référence) en base de données, vous pouvez renseigner sur l'une de vos classes dans le modèle la propriété translation: true. Cela indiquera à TopModel, dans les générateurs qui le supportent, de générer des inserts en base de données avec les traductions, un peu sur le même principe que les valeurs de classes.

Le format d'une classe de traductions est assez rigide, elle doit avoir :

  • Une clé primaire simple pour la clé de traduction si l'appli est mono-langue, ou une clé primaire multiple avec la clé + une localeProperty si l'appli est multi-langue.
  • Une defaultProperty pour y mettre la traduction.
  • Aucune autre propriété obligatoire.

Puisqu'il s'agit d'une classe normale du modèle, vous pouvez la taguer pour qu'elle ne soit prise en compte que par les générateurs que vous voulez (par exemple, si elle ne doit exister qu'en SQL, alors mettez lui un tag qui ne cible que les générateurs SQL).

Décorateurs et mappers

Une classe peut implémenter des annotations, des décorateurs et définir des mappers.

Tags d'une classe

Une classe peut également définir ses propres tags, qui s'ajouteront aux tags du fichier, via la propriété tags, pour plus de flexibilité dans l'organisation des classes en fichiers (par exemple, s'il n'y a qu'une seule classe dans un fichier qui a besoin prise en compte par un autre générateur, alors on peut ajouter un tag directement sur cette classe au lieu de la mettre dans un fichier différent).

Héritage

Il est possible de définir une classe héritée avec le mot clé extends. La classe enfant héritera des différentes propriétés, qui pourront être utilisées dans les mappers, mais aussi en tant que defaultProperty, orderProperty ou flagProperty (ou même joinColumn dans les dataFlows). Ces propriétés héritées pourront également être utilisées dans des values, et être référencées comme propriété cible d'une association.

Stratégies d'héritage sur les classes persistées

Il existe 3 manières de traduire l'héritage en base de données, qui sont globalement utilisées et implémentées par la grande majorité des ORMs du marché pour la plupart des langages. Elle se renseigne via la propriété inheritanceStrategy, dont les valeurs possibles sont :

  • joined-tables (par défaut)

    Une table sera créee en base de données pour chaque classe du modèle, avec les propriétés qui sont définies dans chacune sur chacune. Une association vers la classe parente sera ajoutée sur chaque classe enfant, pour créer une clé étrangère et la possibilité de faire une jointure. Ce mode n'est pas disponible pour les classes abstraites.

  • single-table,

    Une unique table sera créee pour insérer toutes les instances de la hiérarchie de classe, avec les colonnes supplémentaires de chaque implémentation. Cette stratégie nécessite une colonne de discriminateur avec des valeurs pour chaque type non abstrait de la hiérarchie, pour distinguer quelle ligne appartient à quelle classe. Vous avez le choix de :

    • Renseigner, ou pas, discriminatorProperty sur la classe parente avec une propriété de la classe. Si elle n'est pas renseignée, une colonne technique {trigram}_discriminator sera ajoutée en base de données.
    • Renseigner, ou pas, discriminatorValue sur chaque classe non abstraite de la hiérarchie. Si elle n'est pas renseignée, c'est le nom SQL de la classe qui sera utilisé.
  • distinct-tables

    Une table par classe non abstraite de la hiérarchie sera créée en base de données, qui contiendra l'ensemble des colonnes de chaque classe (les colonnes de la classe parente seront donc dupliquées sur chaque table). Cette stratégie utilise cependant une seule séquence pour gérer les clés primaires de chaque table.

La stratégie adaptée pourra donc être choisie au cas par cas, selon le cas métier à traiter. De manière générale :

  • Privilégiez joined-tables si la classe parente n'est pas abstraite.
  • single-table est un bon choix si les différentes sous-classes représentent une variation du même objet métier.
  • N'utilisez distinct-tables que si vous voulez garder les différentes classes séparées et que vous n'avez pas besoin d'agréger leurs données.

Values

Les values ajoutées dans la classe enfant viendront implicitement compléter la liste des valeurs des champs de la classe parent. Les enum de valeurs seront impactées de la manière suivante :

  • Pas d'enum générée pour la clé primaire de la classe enfant. En effet le type de la propriété est le même que celui de la classe parent
  • Les valeurs des champs ajoutées par la classe enfant sont ajoutées à l'enum de la classe parent