Deux figures poussant des formes cubiques et cylindriques, symbolisant la comparaison des processus

Ne vous demandez pas ce que les syndiqués peuvent faire pour vous ; demandez-vous ce que vous pouvez faire pour les syndiqués.

3 Lecture en une minute

Les types union sont utiles lorsque vous avez une collection de classes qui n'agissent pas, mais qui doivent toutes être traitées de la même manière.

En tant que développeur logiciel chez Swimlane, j'ai parfois l'occasion d'apprendre de nouvelles choses qui profitent non seulement à moi et à toute l'équipe, mais aussi à l'efficacité de nos produits. Dans un projet récent, j'ai été chargé d'ajouter un journal d'audit. Il s'agissait d'enregistrer chaque modification (création, mise à jour, suppression, etc.) apportée à l'une de nos collections de base de données dans une nouvelle collection, « AuditLog », et d'étiqueter chaque enregistrement avec l'action effectuée. Plutôt simple.

Cependant, les actions de mise à jour posaient problème : différents objets du moteur de workflow peuvent être mis à jour de différentes manières (par exemple, les utilisateurs peuvent être activés, désactivés ou connectés ; les plugins peuvent être mis à niveau), et je ne voulais pas surcharger les fonctions de mise à jour elles-mêmes avec une logique de journalisation. Il me fallait donc écrire une fonction prenant comme arguments l’état du document avant et après modification. Ensuite, en fonction des modifications apportées au document, il fallait déterminer l’action qui les avait effectuées. Mais quel type devaient être les arguments « avant » et « après » ?

class AuditLogsService { async log(avant: ???, après: ???): Promise { //... } }

Une solution aurait consisté à utiliser une interface. Nos documents n'implémentant ni classe de base ni interface, cette solution paraissait simple. Il suffisait de créer une interface `BaseDocument` et de la faire implémenter par les huit classes de documents. Les propriétés `before` et `after` pourraient alors être typées selon cette interface.

interface AuditLogable {}

Le problème avec cette solution, c'est que l'interface serait inutile car aucune des classes de documents ne partage de propriétés ni de méthodes. Enfin, pas tout à fait : elles partagent un champ “ id ”. Je devrais donc modifier huit classes différentes juste pour implémenter une interface avec une seule propriété et aucune méthode.

interface AuditLogable { id: chaîne; }

Et si j'ajoutais la méthode `log` à l'interface et que je permettais à chaque type de document de définir son propre comportement de journalisation ? C'est une approche orientée objet classique, non ? Sauf que maintenant, je dois implémenter huit méthodes de journalisation, chacune avec un comportement assez similaire. Pourquoi ne pas utiliser une classe de base plutôt qu'une interface pour éviter de me répéter ? Parce que le comportement de chaque méthode de journalisation est uniquement… surtout De même, les plugins, par exemple, sont le seul type de document qui ne possède pas d'action “ créer ”. On parle plutôt d'“ installer ”.

Je me suis rendu compte que j'abordais ce problème à l'envers. Ce ne sont pas les documents qui devraient effectuer l'audit, mais bien l'inverse. Ainsi, je pourrais tout écrire dans une seule fonction, sans avoir à implémenter d'interfaces inutiles ni d'abstractions complexes.

Heureusement, TypeScript offre une méthode simple pour construire ce modèle : les types union. Avec un type union, je n’ai pas besoin d’abstraire les propriétés ou le comportement. Je peux en effet dire :,

“ L'argument 'before' est de l'un des types pouvant être consignés dans un journal d'audit. ‘ type AuditLogable = User | Plugin | Playbook; // … class AuditLogsService { async log(before: AuditLogable, after: AuditLogable): Promise { //... } }

Étant donné que les informations de type sont perdues lors de l'exécution, je dois également passer un paramètre supplémentaire indiquant quel type, au sein de l'union, je passe comme arguments before et after.

enum SwimType = { user, plugin, playbook, // ... } class AuditLogsService { async log(before: AuditLogable, after: AuditLogable, type: SwimType): Promise { //... } }

J'ai implémenté la journalisation d'audit avec une seule fonction et un nouveau type union. Aucun document journalisé n'a besoin d'être modifié ; ajouter une nouvelle classe au système de journalisation est donc aussi simple que de l'ajouter à AuditLogable et d'ajouter le comportement supplémentaire requis à la fonction de journalisation. Tout est facile à tester et à comprendre.

Demander une démo en direct