Conception d'API: approche concrète vs abstraite - meilleures pratiques?


25

Lorsque nous discutons des API entre les systèmes (au niveau de l'entreprise), il y a souvent deux points de vue différents dans notre équipe: certaines personnes préfèrent une approche abstraite plus générique, disons, une approche directe "concrète".

Exemple: la conception d'une API de "recherche de personne" simple. la version concrète serait

 searchPerson(String name, boolean soundEx,
              String firstName, boolean soundEx,
              String dateOfBirth)

Les partisans de la version concrète disent:

  • l'API est auto-documentée
  • C'est facile à comprendre
  • il est facile à valider (compilateur ou comme webservice: validation de schéma)
  • BAISER

L'autre groupe de personnes de notre équipe dirait "Ce n'est qu'une liste de critères de recherche"

searchPerson(List<SearchCriteria> criteria)

avec

SearchCritera {
  String parameter,
  String value,
  Map<String, String> options
}

avec éventuellement la création de "paramètre" d'un certain type d'énumération.

Les partisans disent:

  • sans changer la (déclaration de) l'API, l'implémentation peut changer, par exemple en ajoutant plus de critères ou plus d'options. Même sans synchroniser un tel changement au moment du déploiement.
  • la documentation est nécessaire même avec la variante concrète
  • la validation du schéma est surfaite, souvent vous devez valider davantage, le schéma ne peut pas gérer tous les cas
  • nous avons déjà une API similaire avec un autre système - réutilisation

Le contre-argument est

  • beaucoup de documentation sur les paramètres valides et les combinaisons de paramètres valides
  • plus d'effort de communication car il est plus difficile à comprendre pour les autres équipes

Existe-t-il des meilleures pratiques? Littérature?


3
"String first / name, booléen soundEx" répété est une violation claire de dry et suggère que cette conception n'a pas réussi à répondre au fait que le nom devrait aller de pair avec soundEx. Face à de simples erreurs de conception comme celle-là, il est difficile de procéder à une analyse plus sophistiquée
gnat

Le contraire de "concret" n'est pas "générique", c'est "abstrait". L'abstraction est très importante pour une bibliothèque ou une API, et cette discussion ne parvient pas à poser la question vraiment fondamentale, se concentrant plutôt sur ce qui est franchement une question de style plutôt triviale. FWIW, les contre-arguments pour l'option B sonnent comme une charge de FUD, vous ne devriez pas avoir besoin de documentation ou de communication supplémentaire si la conception de l'API est même à moitié propre et suit les principes SOLIDES.
Aaronaught

@Aaronaught merci d'avoir souligné cela ("résumé"). Ce pourrait être un problème de traduction, "generisch" en allemand me semble toujours OK. Quelle est pour vous la "question vraiment fondamentale"?
erik

4
@Aaronaught: La question n'est pas abstraite. La bonne correction serait que l'opposé de «générique» est «spécifique», et non «concret».
Jan Hudec

Un autre vote pour que ce ne soit pas générique contre abstrait, mais générique contre spécifique. L'exemple "concret" ci-dessus est en fait spécifique au nom, firstName et dateOfBirth, l'autre exemple est générique pour tous les paramètres. Ni l'un ni l'autre ne sont particulièrement abstraits. Je voudrais éditer le titre mais je ne veux pas commencer une guerre d'édition :-)
matt freake

Réponses:


18

Cela dépend du nombre de champs dont vous parlez et de la façon dont ils sont utilisés. Le béton est préférable pour les requêtes hautement structurées avec seulement quelques champs, mais si l'interrogation a tendance à être de forme très libre, alors l'approche concrète devient rapidement compliquée avec plus de trois ou quatre champs.

En revanche, il est très difficile de conserver une API générique pure. Si vous effectuez une recherche de nom simple dans de nombreux endroits, quelqu'un finira par se lasser de répéter les mêmes cinq lignes de code, et ils l'envelopperont dans une fonction. Une telle API évolue invariablement en un hybride d'une requête générique avec des wrappers concrets pour les requêtes les plus couramment utilisées. Et je ne vois rien de mal à cela. Il vous offre le meilleur des deux mondes.


7

Concevoir une bonne API est un art. Une bonne API est appréciée même après le temps. À mon avis, il ne devrait pas y avoir de parti pris général sur la ligne abstraite-concrète. Certains paramètres peuvent être aussi concrets que des jours de la semaine, d'autres doivent être conçus pour être extensibles (et il est assez stupide de les rendre concrets, par exemple, une partie des noms de fonctions), un autre peut aller encore plus loin et afin d'avoir une élégance L'API dont on a besoin pour fournir des rappels ou même un langage spécifique au domaine aidera à lutter contre la complexité.

Il se passe rarement de nouvelles choses sous la Lune. Jetez un œil à l'art antérieur, en particulier aux normes et formats établis (par exemple, beaucoup de choses peuvent être modélisées après les flux, les descriptions des événements ont été élaborées en ical / vcal). Rendez votre API facilement additive, où les entités fréquentes et omniprésentes sont concrètes et les extensions envisagées sont des dictionnaires. Il existe également des schémas bien établis pour faire face à des situations spécifiques. Par exemple, la gestion des requêtes HTTP (et similaires) peut être modélisée dans l'API avec des objets Request et Response.

Avant de concevoir l'API, réfléchissez sur les aspects, y compris ceux, qui ne seront pas inclus, mais vous devez en être conscient. Des exemples de tels sont la langue, la direction d'écriture, l'encodage, les paramètres régionaux, les informations de fuseau horaire et similaires. Faites attention aux endroits où des multiples peuvent apparaître: utilisez une liste, pas une valeur unique pour eux. Par exemple, si vous concevez une API pour un système de chat vidéo, votre API sera beaucoup plus utile, si vous supposez N participants, pas seulement deux (même si vos spécifications en ce moment sont telles).

Parfois, être abstrait aide à réduire considérablement la complexité: même si vous concevez une calculatrice pour ajouter seulement 3 + 4, 2 + 2 et 7 + 6, il peut être beaucoup plus simple d'implémenter X + Y (avec des limites techniquement réalisables sur X et Y, et ajoutez ADD (X, Y) à votre API au lieu de ADD_3_4 (), ADD_2_2 (), ...

Dans l'ensemble, le choix d'une manière ou d'une autre n'est qu'un détail technique. Votre documentation doit décrire les cas d'utilisation fréquents de manière concrète.

Quoi que vous fassiez du côté de la structure des données, fournissez un champ pour une version d'API.

Pour résumer, l'API doit minimiser la complexité lors de l'utilisation de votre logiciel. Pour apprécier l'API, le niveau de complexité exposé doit être adéquat. Le choix de la forme de l'API dépend beaucoup de la stabilité du domaine problématique. Ainsi, il devrait y avoir une estimation dans quelle direction le logiciel et son API vont croître, car ces informations peuvent affecter l'équation de la complexité. De plus, la conception d'API est là pour que les gens comprennent. S'il existe de bonnes traditions dans le domaine de la technologie logicielle dans laquelle vous vous trouvez, essayez de ne pas trop vous en écarter, car cela facilitera la compréhension. Tenez compte de qui vous écrivez. Les utilisateurs plus avancés apprécieront la généralité et la flexibilité, tandis que ceux qui ont moins d'expérience peuvent être plus à l'aise avec les concrets. Cependant, prenez soin de la majorité des utilisateurs d'API là-bas,

Du côté de la littérature, je peux recommander les programmeurs principaux de "Beautiful Code" expliquent comment ils pensent par Andy Oram, Greg Wilson, car je pense que la beauté consiste à percevoir l'optimalité cachée (et l'adéquation à un usage).


1

Ma préférence personnelle est d'être abstraite, mais les politiques de mon entreprise m'incitent à être concret. C'est la fin du débat pour moi :)

Vous avez bien répertorié les avantages et les inconvénients des deux approches, et si vous continuez à creuser, vous trouverez de nombreux arguments en faveur des deux côtés. Tant que l'architecture de votre API est correctement développée - ce qui signifie que vous avez réfléchi à la façon dont elle sera utilisée aujourd'hui et à la façon dont elle pourra évoluer et se développer à l'avenir - alors tout devrait bien se passer.

Voici deux signets que j'avais avec des points de vue opposés:

Favoriser les classes abstraites

Favoriser les interfaces

Demandez-vous: "L'API répond-elle à mes besoins commerciaux? Est-ce que j'ai des critères de réussite bien définis? Peut-elle évoluer?". Celles-ci semblent être des pratiques exemplaires très simples à suivre, mais honnêtement, elles sont beaucoup plus importantes que concrètes ou génériques.


1

Je ne dirais pas qu'une API abstraite est nécessairement plus difficile à valider. Si les paramètres des critères sont assez simples et ont peu de dépendances entre eux, cela ne fait pas beaucoup de différence que vous passiez les paramètres séparément ou dans un tableau. Vous devez toujours les valider tous. Mais cela dépend de la conception des paramètres des critères et des objets eux-mêmes.

Si l'API est suffisamment complexe, avoir des méthodes concrètes n'est pas une option. À un certain point, vous vous retrouverez probablement avec des méthodes avec trop de paramètres ou trop de méthodes simples qui ne couvriront pas tous les cas d'utilisation requis. En ce qui concerne mon expérience personnelle dans la conception d'une API consommatrice, il est préférable d'avoir des méthodes plus génériques au niveau de l'API et de mettre en œuvre des wrappers spécifiques requis au niveau de l'application.


1

L'argument du changement doit être rejeté avec YAGNI. Fondamentalement, à moins que vous n'ayez en fait au moins 3 cas d'utilisation différents qui utilisent l'API générique différemment, les chances sont assez faibles que vous le conceviez de sorte qu'il ne doive pas changer lorsque le prochain cas d'utilisation apparaîtra (et lorsque vous aurez l'utilisation- cas, vous avez évidemment besoin de l'interface générique, point). Alors n'essayez pas et soyez prêt pour le changement.

La modification n'a pas besoin d'être synchronisée pour le déploiement dans les deux cas. Lorsque vous généralisez l'interface ultérieurement, vous pouvez toujours fournir l'interface plus spécifique pour la compatibilité descendante. Mais en pratique, tout déploiement comportera tellement de modifications que vous le synchroniserez de toute façon afin de ne pas avoir à tester les états intermédiaires. Je ne verrais pas cela comme un argument non plus.

Quant à la documentation, l'une ou l'autre solution peut être facile à utiliser et évidente. Mais c'est un argument important. Implémentez l'interface afin qu'elle soit facile à utiliser dans vos cas réels. Parfois spécifique peut être meilleur et parfois générique peut être.


1

Je préférerais l'approche d'interface abstraite. Poser une requête sur ce type de service (de recherche) est un problème courant et se reproduira probablement. De plus, vous trouverez de manière appropriée plus de candidats au service qui sont appropriés pour réutiliser une interface plus générale. Pour pouvoir fournir une interface commune cohérente pour ces services, je n'énumérerais pas les paramètres de requête actuellement identifiés dans la définition de l'interface.

Comme cela a été souligné précédemment - j'aime l'opportunité de changer ou d'étendre l'implémentation sans modifier l'interface. L'ajout d'un autre critère de recherche ne doit pas se refléter dans la définition du service.

Bien qu'il ne soit pas question de concevoir des interfaces bien définies, concises et expresses, vous devrez toujours fournir une documentation supplémentaire. L'ajout de la portée de définition pour des critères de recherche valides n'est pas un tel fardeau.


1

Le meilleur résumé que j'ai jamais vu est l'échelle de Rusty, maintenant appelée manifeste de conception d'API de Rusty . Je ne peux que recommander fortement celui-là. Par souci d'exhaustivité, je cite le résumé de l'échelle du premier lien (le meilleur en haut, le pire en dessous):

Bonnes API

  • Il est impossible de se tromper.
  • Le compilateur / éditeur de liens ne vous laissera pas vous tromper.
  • Le compilateur vous avertira si vous vous trompez.
  • L'utilisation évidente est (probablement) la bonne.
  • Le nom vous indique comment l'utiliser.
  • Faites-le correctement ou il se cassera toujours lors de l'exécution.
  • Suivez la convention commune et vous y arriverez.
  • Lisez la documentation et vous aurez raison.
  • Lisez l'implémentation et vous y arriverez.
  • Lisez le fil de la liste de diffusion correcte et vous aurez raison.

Mauvaises API

  • Lisez le fil de la liste de diffusion et vous vous tromperez.
  • Lisez l'implémentation et vous vous tromperez.
  • Lisez la documentation et vous vous tromperez.
  • Suivez la convention commune et vous vous tromperez.
  • Faites-le correctement et il se cassera parfois lors de l'exécution.
  • Le nom vous indique comment ne pas l'utiliser.
  • L'utilisation évidente est fausse.
  • Le compilateur vous avertira si vous le faites correctement.
  • Le compilateur / éditeur de liens ne vous permettra pas de faire les choses correctement.
  • Il est impossible de bien faire les choses.

Les deux pages de détails ici et ici viennent avec une discussion approfondie de chaque point. C'est vraiment une lecture incontournable pour les concepteurs d'API. Merci Rusty, si jamais tu lis ça.


0

Dans les mots du profane:

  • L'approche abstraite a l'avantage de permettre de construire des méthodes concrètes autour d'elle.
  • L'inverse n'est pas vrai

UDP a l'avantage de vous permettre de créer vos propres flux fiables. Alors pourquoi presque tout le monde utilise TCP?
svick

Il est également tenu compte de la majorité des cas d'utilisation. Certains cas peuvent être nécessaires si fréquemment qu'il est possible de les rendre spéciaux.
Roman Susi

0

Si vous étendez SearchCriteriaun peu l' idée, cela peut vous donner une flexibilité telle que la création AND, ORetc. de critères. Si vous avez besoin d'une telle fonctionnalité, ce serait la meilleure approche.

Sinon, concevez-le pour l'utilisabilité. Rendez l'API facile pour les personnes qui l'utilisent. Si vous avez des fonctions de base qui sont souvent nécessaires (comme rechercher une personne par son nom), fournissez-les directement. Si les utilisateurs avancés ont besoin de recherches avancées, ils peuvent toujours utiliser le SearchCriteria.


0

Que fait le code derrière l'API? Si c'est quelque chose de flexible, une API flexible est bonne. Si le code derrière l'API est très spécifique, y mettre un visage flexible signifie simplement que les utilisateurs de l'API seront frustrés et ennuyés par tout ce que l'API prétend être possible mais ne peut pas être accompli.

Pour votre exemple de recherche de personne, les trois champs sont-ils obligatoires? Si c'est le cas, la liste des critères est mauvaise car elle permet une multitude d'utilisations qui ne fonctionnent tout simplement pas. Sinon, demander à l'utilisateur de spécifier les entrées non requises est mauvais. Quelle est la probabilité que la recherche par adresse soit ajoutée dans la V2? L'interface flexible rend cela plus facile à ajouter que l'inflexible.

Tous les systèmes n'ont pas besoin d'être ultra flexibles, essayant de tout faire, il en va de même pour l'architecture astronautique. Un arc flexible tire des flèches. Une épée flexible est aussi utile qu'un poulet en caoutchouc.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.