Service/Adapter : mes adapters sont trop gros mon capitaine !
Si vous implémentez le pattern Adapter, ou bien si vous suivez mes posts, vous avez entendu parler de la notion d'Adapter. Mon point de vue sur la question est qu'ils doivent être minimals (1 à 2 lignes par méthodes, aucune logique autre que l'appel à une méthode native php / extension). On arrive alors de temps en temps à une problématique : les adapters ont tendance à vouloir "grossir", quand on veux "généraliser" la logique dans le service (et qu'on relègue l'implémentation dans l'adapter). La contrainte des 1 à 2 lignes est alors difficile à respecter... ou presque !
Quand vous développez ou refactorez un morceau de code (Service, Adapter, ...) posez vous la question suivante :
"Si je devais tester unitairement ce morceau de code quels seraient les impacts extérieurs (appels de méthodes externes à la classe, utilisation de singleton ou d'objet externes non passés en arguments et par exemple instancié dans le code...) ?"
Si la réponse est "aucun", alors vous êtes en face d'un service. Si la réponse n'est pas "aucun" alors vous êtes AUSSI en face d'un service si vous utilisez if, while, for, try, foreach, ... (i.e. si vous avez de la logique qui "créé, modifie, contrôle l'information)
Ensuite, imaginez que vous êtes un "parser" technique bête et méchant (je sais que ce n'est pas le cas), parcourez alors votre morceau de code, instruction par instruction, ligne par ligne de haut en bas. A chaque fois que vous tombez sur une utilisation d'une fonction native php qui a un impact sur le système (réseau, système de fichiers, mémoire vive / partagée, base de données, extension c...) alors vous êtes en face d'une méthode d'un adapter à fournir potentiellement.
Souvent quand on me demande de regarder des adapters déjà développés, ils ont beau s'appeller ..../Adapter/....php, il s'agit de service ! car ils ont une succession de méthodes qui sont volumineuses (beaucoup plus qu'une ligne chacune) avec de la logique de if, while, for, try, foreach...
Par contre, je suis d'accord que certaines au moins de ces méthodes doivent être dans l'adapter et pas dans le service (peut être pas toutes cependant).
Quand vous êtes en face d'un problème comme cela, vous devez au moins vous poser 2 questions :
1/ Est-il possible de déplacer cette méthode de cet adapter dans son service (au dessus), en fournissant éventuellement de nouvelle méthodes dans l'adapter qui sont plus unitaires et qui sont orchestrées par cette logique qui arrive dans le service ? 2/ Est-il plus pertinent de garder cette méthode dans l'adapter (bien placée) et de créer un nouveau service, dit "technique", qui sera utilisé par l'adapter en une seule ligne, ce nouveau service proposant une ou plusieurs méthodes, et ayant lui même un adapter ?
Si S = Service, A = Adapter, le schéma habituel / commun est le suivant :
S1 => A1.1 (i.e. Le service 1 utilise son adapter 1)
Mais on peut aussi chainer les utilisations de services :
S1 => A1.1 => S2 => A2.1 (i.e. Le service 1 utilise son adapter 1 qui lui même devant réaliser des opérations complexes, utilise un second service (2) qui lui même utilise son adapter 1)
Par exemple,dans le cas simple (avec beaucoup de lignes dans l'adapter), l'implémentation est :
Service(ex: AccuseReception) => Adapter(ex: Feed)
dans le but de simplifier l'adapter Feed, de rendre le code plus testable et plus module on peut avoir :
Service(ex: AccuseReception) => Adapter(ex: Feed) => Service(ex: XMLParser) => Adapter(ex: Native ou SimpleXMLElement)
Ce qui nécessite la création d'un service technique 'XMLParser' avec sa batterie d'adapter (Default/Native, Mock, ...)
Le service XMLParser devient utilisable à son tour directement en tant que service depuis un contrôleur (MVC), ou un adapter.
L'intérêt est alors que l'on peut facilement tester unitairement (PHPUnit, SimpleTest) les services (i.e AccuseReception et XMLParser dans notre exemple), alors qu'il est difficile (personnellement je le déconseille) de tester les adapter (et donc l'implémentation initiale Feed qui faisait plusieurs centaines de lignes).
Attention cependant à respecter la règle suivante dans la mesure du possible :
"Un service a le droit uniquement d'appeller ses propres méthodes ou celle de son adapter, mais pas celle d'un autre service".
Idéalement il faut donc créer une méthode dans l'adapter et passer par cette méthode pour appeller le XMLParser, et non utiliser le XMLParser en direct.
Il peut être envisageable d'avoir "plusieurs" adapter en même temps pour un même service (adapter read, adapter write...), cela est potentiellement intéressant car permet une réutilisabilité accrue, attention par contre à la complexité de gestion. Je recommande de n'avoir qu'un seul adapter par service, cela est plus facile à gérer, et permet de garantir qu'un adapter est 100% adapté au service qui l'utilise (on ne se retrouve pas dans des situations d'interblocage dans lesquels l'adapter générique de lecture dans un fichier qu'on utilise n'a pas la méthode toto qui est utile par un des services qui l'utilise (mais uniquement que par celui la !))
Tout cela est un peut théorique, j'en conviens, dans la forme.
Je peux tenter d'illustrer mon propos sur des exemples précis / ciblés si vous me le demandez.
Et vous quelles sont vos pratiques concernant le découpage Service/Adapter ?
Commentaires récents