API-Design: Konkreter vs. abstrakter Ansatz - Best Practices?

25

Bei der Erörterung von APIs zwischen Systemen (auf Unternehmensebene) gibt es in unserem Team häufig zwei unterschiedliche Standpunkte: Einige bevorzugen einen allgemeineren abstrakten Ansatz, andere einen direkten "konkreten" Ansatz.

Beispiel: Entwurf einer einfachen API für die Personensuche. die konkrete version wäre

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

Die Befürworter der konkreten Version sagen:

  • Die API ist selbstdokumentierend
  • es ist leicht zu verstehen
  • es ist einfach zu validieren (Compiler oder als Webservice: Schema-Validierung)
  • KUSS

Die andere Gruppe in unserem Team würde sagen: "Das ist nur eine Liste von Suchkriterien."

searchPerson(List<SearchCriteria> criteria)

mit

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

mit möglicherweise machen "Parameter" von einem Aufzählungstyp.

Befürworter sagen:

  • Ohne Änderung der (Deklaration der) API kann sich die Implementierung ändern, z. B. Hinzufügen weiterer Kriterien oder weiterer Optionen. Auch ohne eine solche Änderung zur Bereitstellungszeit zu synchronisieren.
  • Dokumentation ist auch bei der konkreten Variante notwendig
  • Die Schemaüberprüfung wird überbewertet. Oft müssen Sie weitere Überprüfungen durchführen. Das Schema kann nicht alle Fälle behandeln
  • Wir haben bereits eine ähnliche API mit einem anderen System - Wiederverwendung

Das Gegenargument ist

  • Viel Dokumentation über gültige Parameter und gültige Parameterkombinationen
  • Mehr Kommunikationsaufwand, da es für andere Teams schwieriger zu verstehen ist

Gibt es Best Practices? Literatur?

erik
quelle
3
Das wiederholte "String first / name, boolean soundEx" ist eine eindeutige Verletzung von dry und deutet darauf hin, dass dieses Design die Tatsache nicht berücksichtigt, dass der Name mit soundEx einhergehen soll. Konfrontiert mit einfachen Design - Fehler so fühlt es sich schwer mit anspruchsvollere Analyse zu gehen
gnat
Das Gegenteil von "konkret" ist nicht "generisch", sondern "abstrakt". Abstraktion ist für eine Bibliothek oder API sehr wichtig, und diese Diskussion stellt nicht die wirklich grundlegende Frage, sondern konzentriert sich auf eine ganz offen gesagt triviale Stilfrage. FWIW, die Gegenargumente für Option B klingen wie eine FUD-Ladung. Sie sollten keine zusätzliche Dokumentation oder Kommunikation benötigen, wenn das API-Design nur halbwegs sauber ist und SOLID-Prinzipien folgt.
Aaronaught
@Aaronaught danke für den Hinweis ("abstract"). Es könnte ein Übersetzungsproblem sein, "generisch" klingt für mich immer noch OK. Was ist für Sie die "wirklich grundlegende Frage"?
Erik
4
@Aaronaught: Die Frage ist nicht abstrakt. Die richtige Korrektur wäre, dass das Gegenteil von "generisch" "spezifisch" und nicht "konkret" ist.
Jan Hudec
Ein weiteres Votum dafür ist nicht generisch oder abstrakt, sondern generisch oder spezifisch. Das obige "konkrete" Beispiel ist spezifisch für den Namen, den Vornamen und das Geburtsdatum, das andere Beispiel ist generisch für alle Parameter. Weder sind besonders abstrakt. Ich würde den Titel editieren, aber ich möchte keinen
Editierkrieg

Antworten:

18

Es hängt davon ab, über wie viele Felder Sie sprechen und wie sie verwendet werden. Beton ist für stark strukturierte Abfragen mit nur wenigen Feldern vorzuziehen. Wenn die Abfrage jedoch sehr frei ist, wird der konkrete Ansatz mit mehr als drei oder vier Feldern schnell unhandlich.

Andererseits ist es sehr schwierig, eine generische API rein zu halten. Wenn Sie an vielen Stellen eine einfache Namenssuche durchführen, wird irgendwann jemand müde, dieselben fünf Codezeilen zu wiederholen, und sie werden sie in eine Funktion einschließen. Eine solche API entwickelt sich ausnahmslos zu einer Mischung aus einer generischen Abfrage und konkreten Wrappern für die am häufigsten verwendeten Abfragen. Und daran sehe ich nichts auszusetzen. Es gibt Ihnen das Beste aus beiden Welten.

Karl Bielefeldt
quelle
7

Gute API zu entwerfen ist eine Kunst. Gute API wird auch nach einiger Zeit geschätzt. Meiner Meinung nach sollte die abstrakt-konkrete Linie nicht generell voreingenommen sein. Einige Parameter können so konkret wie Wochentage sein, andere müssen auf Erweiterbarkeit ausgelegt sein (und es ist ziemlich dumm, sie konkret zu machen, z. B. einen Teil von Funktionsnamen), andere gehen möglicherweise sogar noch weiter und müssen elegant sein Eine API, die Sie für Rückrufe benötigen, oder sogar eine domänenspezifische Sprache, hilft bei der Bekämpfung der Komplexität.

Unter dem Mond passieren selten neue Dinge. Werfen Sie einen Blick auf den Stand der Technik, insbesondere auf etablierte Standards und Formate (z. B. können viele Dinge nach Feeds modelliert werden, Ereignisbeschreibungen wurden in ical / vcal erstellt). Machen Sie Ihre API einfach additiv, wenn häufige und allgegenwärtige Entitäten konkret sind und geplante Erweiterungen Wörterbücher sind. Es gibt auch einige gut etablierte Muster für den Umgang mit bestimmten Situationen. Beispielsweise kann die Verarbeitung von HTTP-Anforderungen (und ähnlichen) in der API mit Request- und Response-Objekten modelliert werden.

Informieren Sie sich vor dem Entwerfen der API über Aspekte, einschließlich derer, die nicht enthalten sind, die Sie jedoch kennen müssen. Beispiele hierfür sind Sprache, Schreibrichtung, Codierung, Gebietsschema, Zeitzoneninformation und dergleichen. Achten Sie auf Stellen, an denen Mehrfachnennungen auftreten können: Verwenden Sie für diese Listen und keine Einzelwerte. Wenn Sie beispielsweise eine API für das Videochat-System entwickeln, ist Ihre API viel nützlicher, wenn Sie von N Teilnehmern ausgehen, nicht nur von zwei (auch wenn Ihre Spezifikationen im Moment so sind).

Manchmal hilft es, die Komplexität drastisch zu reduzieren, wenn Sie abstrakt sind: Selbst wenn Sie einen Taschenrechner für das Hinzufügen von nur 3 + 4, 2 + 2 und 7 + 6 entwerfen, ist es möglicherweise viel einfacher, X + Y zu implementieren (mit technisch realisierbaren Grenzen für X und Y, und fügen Sie ADD (X, Y) anstelle von ADD_3_4 (), ADD_2_2 (), ... zu Ihrer API hinzu.

Alles in allem ist die Wahl der einen oder anderen Art nur ein technisches Detail. In Ihrer Dokumentation sollten häufige Anwendungsfälle konkret beschrieben werden.

Was auch immer Sie auf der Datenstrukturseite tun, stellen Sie ein Feld für eine API-Version bereit.

Zusammenfassend sollte die API die Komplexität beim Umgang mit Ihrer Software minimieren. Um die API zu schätzen, sollte der Grad der exponierten Komplexität angemessen sein. Die Entscheidung für die Form der API hängt stark von der Stabilität der Problemdomäne ab. Es sollte also eine Einschätzung darüber geben, in welche Richtung die Software und ihre API wachsen werden, da diese Informationen die Komplexitätsgleichung beeinflussen können. API-Design ist auch da, damit die Leute es verstehen. Wenn es im Bereich der Softwaretechnologie, in dem Sie tätig sind, gute Traditionen gibt, versuchen Sie, nicht viel von diesen abzuweichen, da dies das Verständnis erleichtert. Berücksichtigen Sie, für wen Sie schreiben. Fortgeschrittene Benutzer werden die Allgemeinheit und Flexibilität zu schätzen wissen, während diejenigen mit weniger Erfahrung mit der Konkretisierung besser umgehen können. Allerdings kümmern sich die meisten API-Nutzer dort um

Auf der Literaturseite kann ich "Beautiful Code" empfehlen. Führende Programmierer erklären, wie sie denken Von Andy Oram, Greg Wilson, denn ich denke, bei Schönheit geht es darum, verborgene Optimalität (und Eignung für einen bestimmten Zweck) wahrzunehmen.

Roman Susi
quelle
1

Ich persönlich bevorzuge es, abstrakt zu sein, aber die Richtlinien meines Unternehmens beschränken mich darauf, konkret zu sein. Das ist das Ende der Debatte für mich :)

Sie haben gute Arbeit geleistet, indem Sie Vor- und Nachteile für beide Ansätze aufgelistet haben, und wenn Sie weiter graben, werden Sie viele Argumente für beide Seiten finden. Solange die Architektur Ihrer API richtig entwickelt ist - das heißt, Sie haben darüber nachgedacht, wie sie heute verwendet wird und wie sie sich möglicherweise weiterentwickelt und in Zukunft wächst -, sollten Sie in beiden Fällen in Ordnung sein.

Hier sind zwei Lesezeichen, die ich mit entgegengesetzten Gesichtspunkten hatte:

Begünstigung abstrakter Klassen

Schnittstellen bevorzugen

Fragen Sie sich: "Erfüllt die API meine Geschäftsanforderungen? Habe ich gut definierte Erfolgskriterien? Kann sie skaliert werden?" Dies scheinen wirklich einfache Best Practices zu sein, aber ehrlich gesagt sind sie weitaus wichtiger als konkret oder generisch.

gws2
quelle
1

Ich würde nicht sagen, dass eine abstrakte API notwendigerweise schwieriger zu validieren ist. Wenn die Kriterienparameter einfach genug sind und nur geringe Abhängigkeiten voneinander aufweisen, spielt es keine große Rolle, ob Sie die Parameter einzeln oder in einem Array übergeben. Sie müssen sie noch alle validieren. Dies hängt jedoch vom Design der Kriterienparameter und den Objekten selbst ab.

Wenn die API komplex genug ist, kommen konkrete Methoden nicht in Frage. Irgendwann werden Sie wahrscheinlich entweder Methoden mit zu vielen Parametern oder zu einfache Methoden haben, die nicht alle erforderlichen Anwendungsfälle abdecken. Nach meiner persönlichen Erfahrung beim Entwerfen einer konsumierenden API ist es besser, allgemeinere Methoden auf API-Ebene zu haben und bestimmte erforderliche Wrapper auf Anwendungsebene zu implementieren.

Pavels
quelle
1

Das Änderungsargument sollte mit YAGNI abgewiesen werden. Grundsätzlich ist es sehr unwahrscheinlich, dass Sie die generische API so gestalten, dass sie sich nicht ändern muss, wenn der nächste Anwendungsfall auftaucht (und wenn Sie die In diesen Fällen benötigen Sie offensichtlich die generische Schnittstelle (Punkt). Versuchen Sie also nicht, sich auf die Veränderung vorzubereiten.

In beiden Fällen muss die Änderung für die Bereitstellung nicht synchronisiert werden. Wenn Sie die Schnittstelle später verallgemeinern, können Sie aus Gründen der Abwärtskompatibilität immer die spezifischere Schnittstelle bereitstellen. In der Praxis hat jede Bereitstellung jedoch so viele Änderungen, dass Sie sie trotzdem synchronisieren, sodass Sie die Zwischenzustände nicht testen müssen. Ich würde das auch nicht als Argument ansehen.

In Bezug auf die Dokumentation kann jede Lösung einfach zu bedienen und offensichtlich sein. Aber es ist ein wichtiges Argument. Implementieren Sie die Schnittstelle so, dass sie in Ihren tatsächlichen Fällen einfach zu verwenden ist. Manchmal kann spezifisch besser sein und manchmal kann generisch sein.

Jan Hudec
quelle
1

Ich würde den abstrakten Schnittstellenansatz bevorzugen. Die Abfrage dieser Art von (Such-) Diensten ist ein häufiges Problem und wird wahrscheinlich erneut auftreten. Außerdem werden Sie wahrscheinlich mehr Servicekandidaten finden, die geeignet sind, eine allgemeinere Schnittstelle wiederzuverwenden. Um eine kohärente gemeinsame Schnittstelle für diese Dienste bereitzustellen, würde ich die aktuell identifizierten Abfrageparameter in der Schnittstellendefinition nicht auflisten.

Wie bereits erwähnt, gefällt mir die Möglichkeit, die Implementierung zu ändern oder zu erweitern, ohne die Benutzeroberfläche zu ändern. Das Hinzufügen weiterer Suchkriterien muss nicht in der Service-Definition berücksichtigt werden.

Obwohl es keine Frage ist, klar definierte, präzise und ausdrucksstarke Benutzeroberflächen zu entwerfen, müssen Sie zusätzlich immer eine Dokumentation bereitstellen. Das Hinzufügen des Definitionsbereichs für gültige Suchkriterien ist keine solche Belastung.

0x0me
quelle
1

Die beste Zusammenfassung, die ich je gesehen habe, ist Rustys Skala, die jetzt Rustys API-Design-Manifest heißt . Ich kann das nur wärmstens empfehlen. Der Vollständigkeit halber zitiere ich die Zusammenfassung der Skala aus dem ersten Link (je besser oben, desto schlechter unten):

Gute APIs

  • Es ist unmöglich, sich zu irren.
  • Der Compiler / Linker lässt Sie nichts falsch machen.
  • Der Compiler warnt, wenn Sie etwas falsch machen.
  • Die offensichtliche Verwendung ist (wahrscheinlich) die richtige.
  • Der Name sagt Ihnen, wie man es benutzt.
  • Tun Sie es richtig oder es wird immer zur Laufzeit brechen.
  • Befolgen Sie die üblichen Konventionen und Sie werden es richtig machen.
  • Lesen Sie die Dokumentation und Sie werden es richtig machen.
  • Lesen Sie die Implementierung und Sie werden es richtig machen.
  • Lesen Sie den richtigen Mailinglisten-Thread und Sie werden es richtig machen.

Schlechte APIs

  • Lesen Sie den Mailinglisten-Thread und Sie werden es falsch verstehen.
  • Lesen Sie die Implementierung und Sie werden es falsch verstehen.
  • Lesen Sie die Dokumentation und Sie werden es falsch verstehen.
  • Befolgen Sie die üblichen Konventionen und Sie werden es falsch verstehen.
  • Tun Sie es richtig und es wird manchmal zur Laufzeit brechen.
  • Der Name sagt Ihnen, wie Sie ihn nicht verwenden sollen.
  • Die offensichtliche Verwendung ist falsch.
  • Der Compiler warnt, wenn Sie es richtig machen.
  • Mit dem Compiler / Linker können Sie es nicht richtig machen.
  • Es ist unmöglich, richtig zu machen.

Auf beiden Detailseiten hier und hier wird jeder Punkt ausführlich besprochen. Es ist wirklich ein Muss für API-Designer. Danke Rusty, falls du das jemals gelesen hast.

JensG
quelle
0

Mit den Worten des Laien:

  • Der abstrakte Ansatz hat den Vorteil, dass um ihn herum konkrete Methoden aufgebaut werden können.
  • Der umgekehrte Weg ist nicht wahr
Tulains Córdova
quelle
UDP bietet den Vorteil, dass Sie Ihre eigenen zuverlässigen Streams erstellen können. Warum verwendet fast jeder TCP?
Svick
Es wird auch die Mehrzahl der Anwendungsfälle berücksichtigt. Einige Fälle können so häufig benötigt werden, dass es machbar ist, diese Fälle speziell zu gestalten.
Roman Susi
0

Wenn Sie die erweitern SearchCriteriaIdee ein wenig, kann es Ihnen Flexibilität geben , wie das Erstellen AND, ORusw. Kriterien. Wenn Sie eine solche Funktionalität benötigen, ist dies der bessere Ansatz.

Ansonsten gestalten Sie es für Benutzerfreundlichkeit. Machen Sie die API für die Benutzer einfach. Wenn Sie einige grundlegende Funktionen haben, die häufig benötigt werden (z. B. die Suche nach einer Person anhand ihres Namens), geben Sie diese direkt an. Wenn erfahrene Benutzer eine erweiterte Suche benötigen, können sie trotzdem die verwenden SearchCriteria.

Uooo
quelle
0

Was macht der Code hinter der API? Wenn es etwas Flexibles ist, ist eine flexible API gut. Wenn der Code hinter der API sehr spezifisch ist, bedeutet dies, dass Benutzer der API frustriert und verärgert sind, was die API vorgibt, aber nicht erreicht werden kann.

Sind für Ihr Personensuchbeispiel alle drei Felder erforderlich? Wenn ja, dann ist die Kriterienliste schlecht, weil sie eine Vielzahl von Verwendungen zulässt, die einfach nicht funktionieren. Wenn dies nicht der Fall ist, muss der Benutzer keine erforderlichen Eingaben vornehmen. Wie wahrscheinlich ist es, dass die Suche nach Adresse in V2 hinzugefügt wird? Die flexible Schnittstelle macht das Hinzufügen einfacher als die unflexible.

Nicht jedes System muss extrem flexibel sein, und so ist Architecture Astronauting bemüht, alles zu machen. Ein flexibler Bogen schießt Pfeile. Ein flexibles Schwert ist so nützlich wie ein Gummihuhn.

Steinmetalle
quelle