Quand je vois ces exemples de DCI, je ne vois qu'une couche d'abstraction rajouté à un code parfaitement clair auparavant.
Il faut se méfier de notre tendance en tant que développeur à "systèmatiser" les solutions, à "overenginerer" les solutions au nom de l'élégance et des principes. Je crois plus au principe du "KISS" et du "YAGNI" : ne pas écrire du code qui n'apporte rien pour plus tard, ou qui complique. Ecrire du code DCI, pourquoi pas, si on a une raison bien argumentée, pas juste au nom d'un principe. Extraire une méthode d'une ligne dans une classe, qui ne sera implémenté qu'à un endroit … je ne vois pas l'intérêt. Le 4 juin 2013 13:48, Guirec Corbel <[email protected]> a écrit : > Dans le fond, si je résume ce que tu dis, tu penses que dans un projet > c'est mieux de ne pas trop perdre de temps à se prendre la tête sur des > problèmes de conception alors que le client, quand il verra le projet, il > verra les détails comme la couleur du titre. > > Je suis d'accord avec toi. Il ne faut quand même pas oublié que > l'architecture est importante. Si tu néglige trop ce point tu vas le payer. > Je penses que, pour le moment, je vais laissé du code un peu moins beau et, > lors de ma prochaine release, je ferais un peu de ménage. Ceci dit, je > penses avoir trouvé une bonne manière de faire. J'ai une recette qui marche > bien. > > > Le 3 juin 2013 17:38, lucas di cioccio <[email protected]> a > écrit : > > Hello, >> >> Le truc que je n'aime pas avec les présentations que j'ai vu sur DCI >> c'est pas tant l'"extend" par objet, c'est plutôt d'avoir une classe par >> "méthode". En fait, pour être précis, ce sont surtout les *présentations* >> qui ne m'ont pas plues car un peu trop dogmatiques. Les "flavors" de DCI >> sont peut-être une solution propre à pas mal de problèmes, mais l'important >> c'est d'identifier les cas où cette approche est pertinente et ce qu'elle >> apporte. Pour moi l'avantage de cette approche c'est de "router" vers les >> bonnes méthodes en fonction du contenu des données. J'estime que les >> modules apportent déjà l'encapsulation et donc DCI est redondant si le but >> n'est que l'encapsulation. >> Ensuite, pour "l'extend", je ne suis pas sûr que ce soit une raison >> suffisante pour rejeter DCI. Après tout, les perfs pourries de extend sont >> surtout un problème d'implémentation dans MRI, ce genre de truc peut varier >> d'une implémentation à l'autre. Du reste, si tes modules sont bien isolés, >> c'est trivial de changer du code qui fait un "extend" en une classe qui >> prend un objet et là où tu avais un "self" tu as l'objet en question: au >> final, foo.bar(machin) (DCI) ou Bar.new(foo).machin (contextes, cf. les >> liens d'Olivier, j'entrevoyais un problème proche ici: >> http://unchoke.me/blog/2012/08/15/on-states-and-behaviours/ ) c'est du >> kiffkiff niveau lisibilité. >> >> A mon avis, les contextes et DCI marchent bien quand il y a plusieurs >> prismes par lesquels regarder un objet, et que le prisme dépend d'un état >> contenu dans l'objet, et, qui n'est pas déterminé au "compile time" (i.e., >> quand tu édites ton code pour du Ruby). En gros si t'as un stream >> d'évènements JSON ou XML qui t'arrive dans une socket, et que tu dois >> tantôt les convertir des fois en pomme, parfois en prunes, et tantôt en >> poire ; si en plus tu as des fruits rouges et des fruits verts, ton >> problème se résume à trouver les bonnes méthodes dans une matrice 3x2. Si >> tu fais du code un peu soigné, tu ne vas pas faire une méthode spécifique à >> "pomme verte" (même si c'est valable dans une première itération) pour >> éviter que ce code path existe quand tu as une "poire". Tu ne vas pas non >> plus écrire une sous classe pour chaque des six combinaisons fruit/couleur >> (surtout que, dans cette analogie, tu pourrais avoir des éléments qui ont >> plusieurs couleurs). En revanche, ce serait raisonnable d'avoir 5 modules >> (3 qui répondent plus ou moins à une interface Fruit et 2 à Couleur) et tu >> vas "étendre" (ou encapsuler) à la volée chaque JSON/XML que tu reçois. >> Enfin, si tu as une méthode qui a besoin d'une couleur+d'un fruit, alors il >> te faudra peut-être une classe/méthode/fonction qui se base sur ces deux >> valeurs. En clair, DCI/Contextes permet de sélectionner quelles méthodes >> sont applicables sur quelle état des données d'entrées. >> >> Dans le cas où tu as un framework web tel que Rails qui, pour ton bien, >> fait le job d'encapsuler les requêtes dans le modèle correspondant au >> contenu des données (l'URL), ça ne semble pas universellement judicieux de >> refaire cette partie du travail. Je te conseille de démarrer "fat model" >> puis de modulariser progressivement en faisant juste des module MyModule >> ... end autour des méthodes qui sont liées (souvent une grosse méthode >> plein de if/then et une panoplie de "one-line-helpers" genre >> is_machin_chose?(bidule)). >> >> In fine, le but est de router les bonnes data aux bonnes méthodes et DCI >> peut servir, fais confiance à ton XP/flair, des fois le truc genre "gros >> sale avec des if et des else et des variables locales comparées à des >> variables d'instances" est pertinent si tu ne trouves pas de moyen immédiat >> de rendre ton code plus propre. Faire du Ruby ça demande de savoir fuir les >> problèmes par la première porte de sortie, ce qui est très difficile quand >> on est curieux (c'est du vécu). L'important c'est de se mettre un TODO: >> refactor XYZ et d'avoir la discipline pour revenir dessus. D'expérience, >> quand on a ce genre de questionnements quand on code c'est que soit le >> cerveau n'est plus très frais (i.e., oui, on devient vraiment >> temporairement plus con) soit on a l'impression d'avoir beaucoup de temps >> devant soi et qu'on peut se permettre de procrastiner-positivement. Dans le >> second cas, mets toi une deadline (en t'autorisant un peu de recherche >> perso pour gagner un peu d'XP). Dans le premier cas, autant passer son >> temps sur des problèmes moins intellectuellement difficiles mais tout aussi >> important/utiles pour le projet, genre demander à un tiers si la couleur du >> titre lui semble laide (ce qui risque d'avoir un impact plus important sur >> ton projet que si tu utilises extend ou machin.new(chose)). >> >> Let me know si ça résonne un peu en toi :). >> >> --Lucas, donneur de coup de pieds au cul à toute heure :P >> >> >> Le 3 juin 2013 21:35, Simon Courtois <[email protected]> a écrit : >> >> Concernant ton gist une recommandation, évite de faire des classes qui >>> héritent de Struct.new. >>> Ça crée déjà une classe donc tu ajoutes une niveau inutile. >>> >>> Tu peux faire ceci à la place: >>> >>> MaClasse = Struct.new(:a, :b) do >>> def plop >>> "toto" >>> end >>> end >>> >>> Pour le reste question de point de vue, je trouve qu'on a plus de fierté >>> à faire notre travail du mieux qu'on peut ;) >>> >>> Simon Courtois >>> >>> On Monday 3 June 2013 at 21:29, Guirec Corbel wrote: >>> >>> J'ai fait un gist avec ma solution : >>> https://gist.github.com/GCorbel/5699834. Je penses que je suis content >>> avec ça. >>> >>> Tout de même, c'est pas facile de vouloir faire son métier comme il >>> faut. Ha que j'étais bien quand je faisais du PHP pourri qui bugait tout le >>> temps et qui n'avait aucune architecture mais que je m'en foutais. C'est >>> une grosse prise de tête de vouloir toujours faire au mieux. Je ne suis >>> jamais sûre d'avoir fait du bon code... >>> >>> >>> Le 3 juin 2013 13:07, Simon Courtois <[email protected]> a écrit : >>> >>> C'est un edge case et selon moi dans le cas du remplacement de DCI par >>> SimpleDelegator il ne s'applique pas plus que ça. >>> Cela dit, si on est est dans une situation de "contexte" ça me >>> choquerait pas que InfravisionPotionModule implémente un "observe" décoré. >>> Au final c'est ce que je fais dans Draper quand j'en ai besoin. >>> >>> Pour le reste, je pense que tu as raison de rester sur des Service >>> objects, c'est une bonne façon de fonctionner. >>> >>> Bonne journée, >>> >>> >>> >>> Simon Courtois >>> >>> On Monday 3 June 2013 at 18:59, Guirec Corbel wrote: >>> >>> Simon, As-tu vu ça : >>> http://devblog.avdi.org/2012/01/31/decoration-is-best-except-when-it-isnt/? >>> Il peut y avoir quelques soucis à utiliser SimpleDelegator. >>> >>> Je pense que je vais continuer à faire comme je fais. Mettre de la >>> logique dans le contrôleur et utiliser des services objects quand il y a un >>> logique un peu plus complexe. À chaque fois qu'il y a besoin de callbacks >>> il est mieux de le remplacer par un service object. Si jamais j'ai besoin >>> d'utiliser un héritage je ferais un rôle à la place. >>> >>> Un exemple de code que j'aime bien : >>> >>> class MessagesController < InheritedResource::Base >>> def create >>> sender = User.find(params[:send_id]) >>> receiver = User.find(params[:receiver_id]) >>> >>> notifier = MessageNotifier.new(sender, receiver) >>> notifier.call >>> >>> MessageGeocoder.new(notifier.message).call >>> >>> create! >>> end >>> end >>> >>> class MessageNotifier < Struct.new(:sender, :receiver, :message) >>> def self.call(sender, receiver) >>> MessageNotifier.new(sender, receiver).call >>> end >>> >>> def call >>> receiver.extend(CollectorRole) >>> #traitement >>> end >>> end >>> >>> class MessageGeocoder < Struct.new(:message) >>> def self.call(message) >>> MessageGeocoder.new(message).call >>> end >>> >>> def call >>> #traitement >>> end >>> end >>> >>> module CollectorRole >>> def paintings >>> #find paintings >>> end >>> end >>> >>> Je crois que c'est propre. Ça serait facile de factoriser la méthode >>> "self.call". La différence entre un service object et un context c'est que >>> j'ai l'impression que le context doit prendre tout le traitement faire dans >>> l'action d'un contrôleur. Un service object fait quelque chose de plus >>> petit et on peut en utiliser plusieurs par action. >>> >>> Ça reste facile à comprendre et a tester. Je ne me sens pas tyranniser >>> par le DCI. Avec le DCI ça m'aurait gêner d' utiliser "@user = User.all" >>> dans un contrôleur. Avec un service object je fais ce que je veux. >>> >>> Vous en pensez quoi? >>> >>> >>> Le 3 juin 2013 12:00, Olivier El Mekki <[email protected]> a >>> écrit : >>> >>> Hello, >>> >>> Personnellement, dans le DCI, l'idée d'avoir des méthodes disponibles >>> sur le model uniquement lorsqu'on en a besoin m'intéresse peu : ça >>> m'intéresse que mon code soit lisible, donc je vais utiliser des modules >>> ou des concerns, mais peu m'importe que des méthodes soient disponibles >>> sur mon instance alors qu'elle n'en a pas besoin. >>> >>> Le concept de context m'a en revanche beaucoup plus séduit, notamment >>> parce qu'il me permet de sortir de l'enfer du STI et permet de résoudre >>> des problèmes que le STI lui-même ne permet pas de résoudre. J'utilise >>> aujourd'hui quotidiennement des contextes via cette implémentation : >>> https://gist.github.com/oelmekki/474dcc99649a82986dc3 >>> >>> L'idée est de faire gérer les validations, filtrages de paramètres et >>> before / after save (qui seraient conditionnels) par une classe dédiée. >>> Ça permet d'éviter d'avoir deux models (ou plus) pour gérer des >>> validations spécifiques de ce qui est finalement une seule resource. >>> Ça permet également de prendre en compte ce principe de rôle (sans >>> toutefois nécessiter que le rôle soit définit dans une classe, dans >>> mon implémentation). Parce qu'après tout, lorsqu'un admin edit un >>> utilisateur, ce n'est pas la même chose que lorsqu'un utilisateur >>> s'edite lui même (pas les mêmes validations, pas les mêmes callbacks, >>> pas les mêmes attributs autorisés, etc). >>> >>> La documentation inclue dans ce fichier en dit plus long. >>> >>> Et effectivement, pas question de forcer son usage dans toutes les >>> actions : on doit pouvoir faire un `Foo.all` si c'est tout ce qui >>> est nécessaire :) >>> >>> Note : pour exécuter ce code, il faut utiliser active_record, >>> strong_parameters et cette lib : >>> https://gist.github.com/oelmekki/4a82dce1d9c2a5d66936 >>> >>> On 10:40 Mon 03 Jun , Guirec Corbel wrote: >>> > Bonjour à tous, >>> > >>> > J'ai lu quelques trucs à propos du DCI (Data Context Integration) et >>> je ne >>> > sais pas quoi en penser. J'aimerais avoir votre avis sur la question. >>> > >>> > Pour ceux qui ne connaissent pas, je vous conseil de lire ceci : >>> > http://mikepackdev.com/blog_posts/24-the-right-way-to-code-dci-in-ruby >>> . >>> > >>> > J'aime bien le concept de rôle. Dans l'application que je fais j'ai des >>> > utilisateurs et un type d’utilisateur spécial, le collectionneur. Si >>> j'ai >>> > bien compris le concept il faut écrire un role comme ceci : >>> > >>> > module CollectorRole >>> > def paintings >>> > Painting.where(user_id: self.id) >>> > end >>> > end >>> > >>> > Et pour avoir toutes les peintures d'un collectionneur je pourrais >>> faire >>> > ceci : >>> > >>> > user = User.new >>> > user.extend CollectorRole >>> > user.paintings >>> > >>> > Je n'aurais donc que deux modèles minimaux, User et Painting, >>> représentant >>> > uniquement les données (présente dans la base donnée). Si je veux >>> ajouter >>> > un comportement à mon utilisateur je l'ajouterai dans un rôle. Je >>> respect >>> > donc plus facilement le SRP (Single Responsability Principle). Ça >>> remplace >>> > un héritage. >>> > >>> > Là ou j'ai du mal c'est au niveau du context et du contrôleur. Un >>> exemple >>> > qui fonctionne pas trop mal serait dans le cas ou je voudrais faire une >>> > fonctionnalité qui permet aux utilisateurs de contacter les >>> > collectionneurs. Je ferais un truc du genre : >>> > >>> > class UserContactCollectorContext >>> > attr_reader :user_from, user_to >>> > >>> > def self.call(user_from, user_to) >>> > UserContactCollectorContext.new(user_from, user_to).call >>> > end >>> > >>> > def initialize(user_from, user_to) >>> > @user_from, @user_to = user_from, user_to >>> > @user_to.extend CollectorRole >>> > end >>> > >>> > def call >>> > @paintings = @user_to.paintings >>> > # traitement et envoi du mail >>> > end >>> > end >>> > >>> > Et dans mon contrôleur j'aurais ceci : >>> > >>> > class MessagesController < ApplicationController >>> > def send_message_to_collector >>> > UserContactCollectorContext.call(User.find(params[:user_from]), >>> > User.find(params[:user_to])) >>> > end >>> > end >>> > >>> > Ce code me convient. Là ou ça ne me convient pas c'est quand je veux >>> tout >>> > simplement la liste des utilisateurs. Il faudrait faire un contrôleur >>> du >>> > genre : >>> > >>> > class UsersController < ApplicationController >>> > def index >>> > @users = UserAllContext.call >>> > end >>> > end >>> > >>> > Il y a un exemple similaire ici : >>> > >>> https://github.com/randx/rails-dci-example/blob/master/app/controllers/documents_controller.rb >>> > >>> > Franchement, créé un context pour chaque action de ce type je trouve >>> que >>> > c'est trop. C'est plus simple de faire "@users = User.all". L'avantage >>> > c'est que ça sépare vraiment la logique. Le contrôleur n'a pas à >>> savoir ce >>> > que fait le context. S'il y a un mail envoyé, un géocodage ou tout le >>> > reste, le contrôleur ne le sait pas. C'est vraie pour l'inverse. Le >>> context >>> > ne sait pas ce que fait le contrôleur. On a donc un séparation franche >>> > entre la logique métier et la partie système. >>> > >>> > J'ai lu le livre CleanRuby. Je penses que Jim Gay va trop loin. Il >>> créé un >>> > context "framework agnostic" qui appel des fonctions du contrôleur. >>> J'ai du >>> > mal à comprendre pourquoi il écrit un livre prônant la valeur du >>> "Skinny >>> > Controller" alors qu'il a du code comme ceci : >>> > https://github.com/radiant/radiant/blob/master/app/models/page.rb. >>> > >>> > Qu'en pensez-vous? Est-ce que quelqu'un d'entre vous à mis en pratique >>> ce >>> > concept? Quels sont vos impressions? >>> > >>> > Merci à tous! >>> > >>> > -- >>> > -- >>> > Vous avez reçu ce message, car vous êtes abonné au groupe >>> "Railsfrance" de Google Groups. >>> > Pour transmettre des messages à ce groupe, envoyez un e-mail à >>> l'adresse [email protected] >>> > Pour résilier votre abonnement envoyez un e-mail à l'adresse >>> [email protected] >>> > --- >>> > Vous recevez ce message, car vous êtes abonné au groupe Google >>> Groupes Railsfrance. >>> > Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le >>> concernant, envoyez un e-mail à l'adresse >>> [email protected]. >>> > Pour plus d'options, visitez le site >>> https://groups.google.com/groups/opt_out . >>> > >>> > >>> >>> >>> -- >>> Olivier El Mekki. >>> >>> -- >>> -- >>> Vous avez reçu ce message, car vous êtes abonné au groupe "Railsfrance" >>> de Google Groups. >>> Pour transmettre des messages à ce groupe, envoyez un e-mail à l'adresse >>> [email protected] >>> Pour résilier votre abonnement envoyez un e-mail à l'adresse >>> [email protected] >>> --- >>> Vous recevez ce message, car vous êtes abonné au groupe Google >>> Groupes Railsfrance. >>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le >>> concernant, envoyez un e-mail à l'adresse >>> [email protected]. >>> Pour plus d'options, visitez le site >>> https://groups.google.com/groups/opt_out . >>> >>> >>> >>> -- >>> -- >>> Vous avez reçu ce message, car vous êtes abonné au groupe "Railsfrance" >>> de Google Groups. >>> Pour transmettre des messages à ce groupe, envoyez un e-mail à l'adresse >>> [email protected] >>> Pour résilier votre abonnement envoyez un e-mail à l'adresse >>> [email protected] >>> --- >>> Vous recevez ce message, car vous êtes abonné au groupe Google >>> Groupes Railsfrance. >>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le >>> concernant, envoyez un e-mail à l'adresse >>> [email protected]. >>> Pour plus d'options, visitez le site >>> https://groups.google.com/groups/opt_out . >>> >>> >>> >>> >>> -- >>> -- >>> Vous avez reçu ce message, car vous êtes abonné au groupe "Railsfrance" >>> de Google Groups. >>> Pour transmettre des messages à ce groupe, envoyez un e-mail à l'adresse >>> [email protected] >>> Pour résilier votre abonnement envoyez un e-mail à l'adresse >>> [email protected] >>> --- >>> Vous recevez ce message, car vous êtes abonné au groupe Google >>> Groupes Railsfrance. >>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le >>> concernant, envoyez un e-mail à l'adresse >>> [email protected]. >>> Pour plus d'options, visitez le site >>> https://groups.google.com/groups/opt_out . >>> >>> >>> >>> >>> -- >>> -- >>> Vous avez reçu ce message, car vous êtes abonné au groupe "Railsfrance" >>> de Google Groups. >>> Pour transmettre des messages à ce groupe, envoyez un e-mail à l'adresse >>> [email protected] >>> Pour résilier votre abonnement envoyez un e-mail à l'adresse >>> [email protected] >>> --- >>> Vous recevez ce message, car vous êtes abonné au groupe Google >>> Groupes Railsfrance. >>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le >>> concernant, envoyez un e-mail à l'adresse >>> [email protected]. >>> Pour plus d'options, visitez le site >>> https://groups.google.com/groups/opt_out . >>> >>> >>> >>> >>> -- >>> -- >>> Vous avez reçu ce message, car vous êtes abonné au groupe "Railsfrance" >>> de Google Groups. >>> Pour transmettre des messages à ce groupe, envoyez un e-mail à l'adresse >>> [email protected] >>> Pour résilier votre abonnement envoyez un e-mail à l'adresse >>> [email protected] >>> --- >>> Vous recevez ce message, car vous êtes abonné au groupe Google >>> Groupes Railsfrance. >>> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le >>> concernant, envoyez un e-mail à l'adresse >>> [email protected]. >>> Pour plus d'options, visitez le site >>> https://groups.google.com/groups/opt_out . >>> >>> >>> >> >> -- >> -- >> Vous avez reçu ce message, car vous êtes abonné au groupe "Railsfrance" >> de Google Groups. >> Pour transmettre des messages à ce groupe, envoyez un e-mail à l'adresse >> [email protected] >> Pour résilier votre abonnement envoyez un e-mail à l'adresse >> [email protected] >> --- >> Vous recevez ce message, car vous êtes abonné au groupe Google >> Groupes Railsfrance. >> Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le >> concernant, envoyez un e-mail à l'adresse >> [email protected]. >> Pour plus d'options, visitez le site >> https://groups.google.com/groups/opt_out . >> >> >> > > -- > -- > Vous avez reçu ce message, car vous êtes abonné au groupe "Railsfrance" de > Google Groups. > Pour transmettre des messages à ce groupe, envoyez un e-mail à l'adresse > [email protected] > Pour résilier votre abonnement envoyez un e-mail à l'adresse > [email protected] > --- > Vous recevez ce message, car vous êtes abonné au groupe Google > Groupes Railsfrance. > Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le > concernant, envoyez un e-mail à l'adresse > [email protected]. > Pour plus d'options, visitez le site > https://groups.google.com/groups/opt_out . > > > -- -- Vous avez reçu ce message, car vous êtes abonné au groupe "Railsfrance" de Google Groups. Pour transmettre des messages à ce groupe, envoyez un e-mail à l'adresse [email protected] Pour résilier votre abonnement envoyez un e-mail à l'adresse [email protected] --- Vous recevez ce message, car vous êtes abonné au groupe Google Groupes Railsfrance. Pour vous désabonner de ce groupe et ne plus recevoir d'e-mails le concernant, envoyez un e-mail à l'adresse [email protected]. Pour plus d'options, visitez le site https://groups.google.com/groups/opt_out .
