diff --git a/talk.md b/talk.md new file mode 100644 index 0000000000000000000000000000000000000000..8271ff13b03d3a31aa6c06271d9a59e0654c2c34 --- /dev/null +++ b/talk.md @@ -0,0 +1,173 @@ +Bonsoir, bonsoir, +je me présente, je suis Grégoire Paris, Software Engineer chez ManoMano.com +depuis 3 jours, car je viens de quitter Universciné, un fournisseur de vidéo à +la demande. +C'est mon premier talk alors il va falloir être très indulgents avec moi, +heureusement je ne suis pas seul, je suis venu avec Maxime Veber, qui va se +présenter à son tour. + +… + +Nous sommes venu vous parler d'un outil que vous connaissez bien, Doctrine, +conçu par Benjamin Eberlei, maintenu au jour le jour par Marco Pivetta et +Ghuilherme Blanco, et plus récemment par Michael Moravec et Luis Cobucci, +particulièrement en ce qui concerne la prochaine version, Doctrine 3. + +Ce soir, nous allons essayer de vous montrer que Doctrine est un outil qui est +très peu intrusif pour peu qu'on sache comment faire pour s'en découpler. + +Rentrons tout de suite dans le vif du sujet avec une bonne vieille entité telle +que vous la connaissez, telle que n'importe programmeur lisant la documentation +Doctrine ou Symfony la produira. + +Alors, une entité, pour le programmeur lambda, c'est avant tout une suite de +propriétés typées. Chaque propriété vient avec un setter et un getter, à +l'exception notable de l'identifiant, qui est initialisé par Doctrine, qui +demande le prochain identifiant à la base de données. Ces setters et getters +sont dépourvus de toute logique, et si vous les testez, vous aurez +probablement l'impression de perdre votre temps et de faire un travail +répétitif, à tel point que certains vont jusqu'à créer des paquets Composer +pour automatiser ce genre de tests, et que jusqu'à très récemment, il y avait +une commande qui permettait de les générer. +On peut remarquer que Doctrine ne fait pas d'active record et ne vous oblige +pas à étendre quoi que ce soit, ce qui est une très bonne chose, car ce serait +une forme de couplage très forte. +Ce modèle porte un nom ou des noms, il est connu sous le nom de modèle +anémique, car il plein de vide. + +Mais si vous avez écouté le talk de Romain Kuzniak tout à l'heure, vous savez +que ce n'est pas la seule architecture possible. + +Maxime et moi, nous aimons et utilsons le Domain Driven Design dans nos application. + +C'est une architecture dont voici certains points clés, le principal point-clé +étant dans le nom, DDD. Une des choses importantes en DDD, c'est qu'on commence +par concevoir son domaine, et qu'on repousse le choix du framework, de la +méthode de communication (http ou console), de la base de données et au final, +de la plupart des outils tiers au plus tard possible. +On commence par concevoir son modèle, c'est à dire principalement ses entités +et les règles métier qui vont avec. Comme il n'y a pas de dépendances tierces +impliquées à ce moment là, les tests sont faciles à écrire car on maitrise +totalement le code. +Concevoir une entité c'est s'assurer qu'elle est conforme à tout moment, dès sa +construction, aux règles demandées par le client. +Faire du DDD, c'est faire de la bonne POO, et faire de la bonne POO, c'est +respecter l'encapsulation. Et l'entité qu'on a vu précédemment ne le fait pas +du tout: elle vous laisse lire et écrire librement ce que vous voulez dans +chacune des propriétés, sans aucun contrôle. +Une fois qu'on a fait la partie domaine, alors et seulement alors, on +s'intéresse à l'infrastructure, et dans ce talk on va vous parler de la partie +persistence avec Doctrine, et comment l'utiliser sans corrompre notre entité. + +Par exemple, saviez vous que Doctrine n'a pas du tout besoin de setters? +Lorsque je me suis mis au DDD, j'avais ce préjugé, et je me suis rendu compte +que Doctrine n'appelle aucune de vos méthodes. Pour être exact, quasiment +aucune de vos méthodes, et en Doctrine 3, ce sera vraiment aucune. Même pas le +constructeur. Ce que fait Doctrine lors de l'hydratation, c'est qu'il utilise +des techniques de magie noire comme de la réflection, ou de la déserialisation +de string pour initialiser les propriétés. + +Sachant cela, ce que vous pouvez faire, c'est utiliser le constructeur pour +valider les règles métier, des sortes de règles de validation aussi appelées +invariants car ce sont des choses qui doivent être vraies tout le temps, qui +sont données par le client. +De cette façon, il devient impossible d'avoir un objet invalide. Si ce que vous +passez au constructeur est invalide, alors une exception métier est jetée, avec +un message explicite. Pour rendre le debug de votre application facile, il faut +crasher aussitôt et de la façon la plus visible possible. Plutôt que de cacher +la poussière sous le tapis, on jette une exception et on se débarasse de tout +une classe de bug venant du fait que vous pensez quelque chose être vrai alors +qu'il ne l'est pas. Par exemple ici, après l'instanciation, si on fait appel à +getId(). C'est un des problèmes avec les identifiants. L'autre problème, c'est +le couplage qu'on a avec la base de données qui est censée rester un outil +externe et ne devrait pas dicter les ids. + +Comme solution, vous pouvez utiliser les UUIDs, si vous n'avez pas de clé +naturelle. Une clé naturelle, c'est par exemple l'ISBN d'un livre. Si vous avez +la chance d'en avoir une dans votre domaine, utilisez la, sinon utilisez les +UUIDs. + +UUID, ça veut dire universally unique identifier, ce qui signifie que +l'identifiant est unique non seulement au sein de votre application, mais de +l'univers tout entier. Si vous faites la refonte d'une application en Symfony +2, ça peut s'avérer très utile si vous avez besoin de donner la possibilité à +votre client de créer ses propres entités, mais aussi d'en importer depuis +l'ancienne base, car vous ne risquez pas d'avoir les collisions que vous auriez +en cas d'auto-increment. Autre chose, un UUID, c'est dur à deviner, alors que +si vous avez un entier, il est fort probable qu'une entité avec un entier +voisin existe également, information toujours bonne à prendre quand on est +malveillant. Autre information que ça peut laisser transparaitre: le nombre +d'entité. + +Maintenant qu'on a résolu le problème des ids, revenons à nos moutons. On a un +constructeur, avec n propriétés, et chaque propriété a x règles de validation… +ça peut vite devenir très lourd pour le constructeur, surtout que certaines +règles de validation devront aussi être vérifier si vous avez des mutateurs +(des méthodes qui changent vos propriétés). Pour résoudre ce problème, on peut +utiliser des value objects. Voilà à quoi ça peut ressembler. + +On a une classe, sans identités, avec seulement une ou deux propriétés qui vont +ensemble, et des règles métier pour les valider. Le nom de value object vient +du fait que ce genre d'objet est caractérisé par sa valeur, et que deux value +object doivent être considérés comme égaux si et seulement si toutes leurs +propriétés sont égales. Ce qui va se passer si vous partez là dessus, c'est que +vous allez passer au constructeur des value objects. + +Le problème avec cette approche, c'est que l'instanciation peut vite devenir +très pénible, car il faut ajouter un use statement par value object, plus celui +de la classe principale. + +Une bonne solution, c'est d'utiliser des constructeurs nommés, qui sont des +factory statiques. Je dis des parce que vous pouvez en avoir plusieurs. Ces +constructeurs nommés, vous allez leur passer des types primitifs et vous allez +pouvoir les charger de mettre des valeurs par défaut ou non suivant le +constructeur. Vous pouvez même mettre le constructeur en privé si vous voulez +forcer les gens à créer de nouveaux constructeurs nommés si ceux que vous avez +faits ne sont pas assez complets. + +Alors maintenant, on a un obtenu des entités qui ne sont plus des tableaux +améliorés. On n'est pas en train de modéliser une base de données, on a fait +les chose à l'endroit et on a modélisé le problème du client, code first, ou +même model first pour ceux qui commencent par faire de l'UML et génèrent +ensuite du code. Comment persister une structure arborescente de value objects +avec Doctrine? + +Deux solutions: les custom types, et les value objects. Commençons par la plus +simple, les custom types. + +Un custom type, c'est un type à vous, qui va devenir utilisable dans vos +mappings Doctrine au même type que string, json_array ou integer. +Pour en créer un, vous devez pour le moment étendre une classe de base, et +surcharger quelques méthodes. Dans le futur, il y aura probablement un gros +refactoring de cette partie pour que vous n'ayez plus qu'à implémenter une +interface. Il y a également gros à parier que `getName` disparaitra, de même +qu'il avait disparu dans le composant form de Symfony. Pour le moment, il vous +faut utiliser le même nom dans cette méthode et lors de l'enregistrement dans +la registre des types, qui se comme ceci. +Les 2 méthodes qui nous intéressent, `convertToPHPValue` et +`convertToDatabaseValue` doivent permettre de convertir une valeur provenant +d'une colonne de la base de données en value object et vice versa. Avec cette +méthode, le constructeur de vos value object va être appelé lors de +l'hydratation, et vous pourrez voir si vous avez oublié de migrer vos données +dans le cas où une nouvelle règle métier n'est pas respectée. +Le problème, c'est que ça ne gère qu'une seule colonne. Si vous désirez gérer +plusieurs colonnes, tournez vous plutôt vers les embeddables. + +Les embeddables sont des values objects marqué comme tels par du mapping +Doctrine, et embarqués dans votre classe de base. Dans la classe embarquée, +vous pouvez mapper chaque champ comme vous le feriez pour n'importe quelle +autre classe, et Doctrine va simplement stocker ces nouveaux champs dans la +même table que l'entité qui embarque l'embeddable. +L'annotation `Embedded` vous permet de spécifier un préfixe pour les colonnes +correspondant à la classe embarquée, ce qui vous permet d'éviter les +collisions. Notez que le constructeur de la classe embarquée n'est pas appelée +lors de l'hydratation. + +Le seul problème avec cette implémentation, c'est la nullabilité. Si votre +embeddable est optionnel, plutôt que de mettre null, doctrine va l'hydrater +avec un value object dont les propriétés seront null. + +Pour pallier ce problème un paquet est disponible, ce paquet utilise un +listener qui va agir en postLoad pour vérifier si l'embeddable est considéré +null. Si oui, il utilisera le composant property access pour mettre ce champ à +`null`. La conséquence, c'est qu'il vous faut un setter.