Diseño API: enfoque concreto frente a abstracto: ¿mejores prácticas?


25

Cuando se discuten las API entre sistemas (a nivel empresarial), a menudo hay dos puntos de vista diferentes en nuestro equipo: algunas personas prefieren un enfoque abstracto más, digamos, genérico , otro un enfoque "concreto" directo.

Ejemplo: el diseño de una API simple de "búsqueda de personas". la versión concreta sería

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

Las personas a favor de la versión concreta dicen:

  • la API es autodocumentada
  • es facil de entender
  • es fácil de validar (compilador o como servicio web: validación de esquema)
  • BESO

El otro grupo de personas en nuestro equipo diría "Eso es solo una lista de criterios de búsqueda"

searchPerson(List<SearchCriteria> criteria)

con

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

posiblemente haciendo "parámetro" de algún tipo de enumeración.

Los defensores dicen:

  • sin cambiar la (declaración de) API, la implementación puede cambiar, por ejemplo, agregar más criterios o más opciones. Incluso sin sincronizar dicho cambio en el momento del despliegue.
  • la documentación es necesaria incluso con la variante concreta
  • la validación del esquema está sobrevalorada, a menudo hay que validar más, el esquema no puede manejar todos los casos
  • ya tenemos una API similar con otro sistema - reutilice

El contraargumento es

  • mucha documentación sobre parámetros válidos y combinaciones válidas de parámetros
  • mayor esfuerzo de comunicación porque es más difícil de entender para otros equipos

¿Hay alguna mejor práctica? ¿Literatura?


3
La repetición "Nombre de cadena / nombre, boolean soundEx" es una clara violación de dry y sugiere que este diseño no solucionó el hecho de que se espera que el nombre coincida con soundEx. Frente a errores de diseño simples como ese, se siente difícil proceder con análisis más sofisticados
mosquito

Lo contrario de "concreto" no es "genérico", es "abstracto". La abstracción es muy importante para una biblioteca o API, y esta discusión no logra hacer la pregunta verdaderamente fundamental, sino que se fija en lo que es francamente una cuestión de estilo bastante trivial. FWIW, los contraargumentos para la opción B suenan como una carga de FUD, no debería necesitar ninguna documentación o comunicación adicional si el diseño de la API es incluso medio limpio y sigue principios SÓLIDOS.
Aaronaught

@Aaronaught, gracias por señalar eso ("resumen"). Puede ser un problema de traducción, "generisch" en alemán todavía me parece correcto. ¿Cuál es la "pregunta verdaderamente fundamental" para usted?
erik

44
@Aaronaught: La pregunta no es sobre el resumen. La corrección correcta sería que lo opuesto a "genérico" es "específico", no "concreto".
Jan Hudec

Otro voto para esto no es sobre genérico versus abstracto, sino genérico versus específico. El ejemplo "concreto" anterior es realmente específico para el nombre, firstName y dateOfBirth, el otro ejemplo es genérico para cualquier parámetro. Tampoco son particularmente abstractos. Editaría el título pero no quiero comenzar una guerra de edición :-)
matt freake

Respuestas:


18

Depende de cuántos campos esté hablando y cómo se usen. Es preferible el concreto para consultas altamente estructuradas con solo unos pocos campos, pero si la consulta tiende a ser de forma muy libre, entonces el enfoque concreto rápidamente se vuelve difícil de manejar con más de tres o cuatro campos.

Por otro lado, es muy difícil mantener una API genérica pura. Si realiza una búsqueda simple de nombres en muchos lugares, eventualmente alguien se cansará de repetir las mismas cinco líneas de código y lo envolverá en una función. Dicha API evoluciona invariablemente en un híbrido de una consulta genérica junto con contenedores concretos para las consultas más utilizadas. Y no veo nada malo en eso. Te da lo mejor de ambos mundos.


7

Diseñar una buena API es un arte. Se aprecia una buena API incluso después de que pase el tiempo. En mi opinión, no debería haber un sesgo general en la línea abstracta-concreta. Algunos parámetros pueden ser tan concretos como los días de la semana, algunos requieren estar diseñados para la extensibilidad (y es bastante estúpido hacerlos concretos, por ejemplo, parte de los nombres de funciones), y otros pueden ir aún más lejos y tener un estilo elegante. API one necesita proporcionar devoluciones de llamada o incluso un lenguaje específico de dominio ayudará a combatir la complejidad.

Raramente ocurren cosas nuevas bajo la Luna. Eche un vistazo a la técnica anterior, especialmente a los estándares y formatos establecidos (por ejemplo, muchas cosas se pueden modelar después de los feeds, las descripciones de los eventos se elaboraron en ical / vcal). Haga su API fácilmente aditiva, donde las entidades frecuentes y omnipresentes son concretas y las extensiones previstas son diccionarios. También hay algunos patrones bien establecidos para tratar situaciones específicas. Por ejemplo, el manejo de solicitudes HTTP (y similares) puede modelarse en la API con objetos de solicitud y respuesta.

Antes de diseñar API, haga una lluvia de ideas sobre aspectos, incluidos aquellos que no se incluirán, pero que debe tener en cuenta. Ejemplos de tales son lenguaje, dirección de escritura, codificación, localización, información de zona horaria y similares. Preste atención a los lugares donde pueden aparecer múltiples: use la lista, no un valor único para ellos. Por ejemplo, si está diseñando API para el sistema de videochat, su API será mucho más útil, si asume N participantes, no solo dos (aunque sus especificaciones en este momento son tales).

A veces, ser abstracto ayuda a reducir drásticamente la complejidad: incluso si diseña una calculadora para agregar solo 3 + 4, 2 + 2 y 7 + 6, puede ser mucho más sencillo implementar X + Y (con límites técnicamente posibles en X y Y, e incluye ADD (X, Y) a tu API en lugar de ADD_3_4 (), ADD_2_2 (), ...

Con todo, elegir una forma u otra es solo un detalle técnico. Su documentación debe describir casos de uso frecuente de manera concreta.

Independientemente de lo que haga en el lado de la estructura de datos, proporcione un campo para una versión de API.

En resumen, la API debe minimizar la complejidad cuando se trata con su software. Para apreciar la API, el nivel de complejidad expuesta debe ser adecuado. Decidir sobre la forma de la API depende mucho de la estabilidad del dominio del problema. Por lo tanto, debería haber alguna estimación en qué dirección crecerá el software y su API, porque esta información puede afectar la ecuación de complejidad. Además, el diseño de API está ahí para que la gente lo entienda. Si hay buenas tradiciones en el área de tecnología de software en la que se encuentra, trate de no desviarse mucho de ellas, ya que esto ayudará a comprender. Ten en cuenta para quién escribes. Los usuarios más avanzados apreciarán la generalidad y la flexibilidad, mientras que aquellos con menos experiencia pueden sentirse más cómodos con la concreción. Sin embargo, cuide a la mayoría de los usuarios de API allí,

Por el lado de la literatura, puedo recomendar a los principales programadores de "Beautiful Code" que expliquen cómo piensan. Por Andy Oram, Greg Wilson, ya que creo que la belleza se trata de percibir la optimización oculta (y la idoneidad para algún propósito).


1

Mi preferencia personal es ser abstracto, sin embargo, las políticas de mi empresa me impiden ser concreto. Ese es el final del debate para mí :)

Has hecho un buen trabajo enumerando los pros y los contras de ambos enfoques, y si sigues cavando encontrarás muchos argumentos a favor de ambos lados. Siempre que la arquitectura de su API se desarrolle correctamente, lo que significa que ha pensado en cómo se usará hoy y cómo puede evolucionar y crecer en el futuro, entonces debería estar bien de cualquier manera.

Aquí hay dos marcadores que tenía con puntos de vista opuestos:

Favoreciendo clases abstractas

Interfaces favorecedoras

Pregúntese: "¿La API cumple con los requisitos de mi negocio? ¿Tengo criterios bien definidos para el éxito? ¿Se puede escalar?". Parecen las mejores prácticas realmente simples a seguir, pero honestamente son mucho más importantes que las concretas frente a las genéricas.


1

No diría que una API abstracta es necesariamente más difícil de validar. Si los parámetros de criterios son lo suficientemente simples y tienen pocas dependencias entre sí, no hay mucha diferencia si pasa los parámetros por separado o en una matriz. Aún necesita validarlos a todos. Pero eso depende del diseño de los parámetros de criterios y de los propios objetos.

Si la API es lo suficientemente compleja, tener métodos concretos no es una opción. En algún momento, probablemente terminará con métodos con muchos parámetros o con métodos demasiado simples que no cubrirán todos los casos de uso requeridos. En cuanto a mi experiencia personal en el diseño de una API de consumo, es mejor tener métodos más genéricos en el nivel de API e implementar envoltorios necesarios específicos en el nivel de la aplicación.


1

El argumento de cambio debe descartarse con YAGNI. Básicamente, a menos que realmente tenga al menos 3 casos de uso diferentes que usen la API genérica de manera diferente, es muy probable que la diseñe para que no tenga que cambiar cuando aparezca el próximo caso de uso (y cuando tenga el uso- casos, obviamente necesita la interfaz genérica, punto). Así que no intentes y prepárate para el cambio.

El cambio no necesita estar sincronizado para la implementación en ninguno de los casos. Cuando generaliza la interfaz más adelante, siempre puede proporcionar la interfaz más específica para la compatibilidad con versiones anteriores. Pero en la práctica, cualquier implementación tendrá tantos cambios que la sincronizará de todos modos para que no tenga que probar los estados intermedios. Yo tampoco lo vería como argumento.

En cuanto a la documentación, cualquiera de las soluciones puede ser fácil de usar y obvia. Pero es un argumento importante. Implemente la interfaz para que sea fácil de usar en sus casos reales. A veces específico puede ser mejor y a veces genérico puede ser.


1

Yo favorecería el enfoque abstracto de la interfaz. Hacer una consulta a ese tipo de servicio (de búsqueda) es un problema común y probablemente ocurrirá nuevamente. Además, es probable que encuentre más candidatos para el servicio que sean adecuados para reutilizar una interfaz más general. Para poder proporcionar una interfaz común coherente para esos servicios, no enumeraría los parámetros de consulta actualmente identificados en la definición de interfaz.

Como se señaló anteriormente, me gusta la oportunidad de cambiar o ampliar la implementación sin modificar la interfaz. Agregar otros criterios de búsqueda no debe reflejarse en la definición del servicio.

Aunque no se trata de diseñar interfaces bien definidas, concisas y expresas, siempre tendrá que proporcionar documentación adicional. Agregar el alcance de definición para criterios de búsqueda válidos no es una carga tan pesada.


1

El mejor resumen que he visto es la escala de Rusty, ahora llamada manifiesto de diseño API de Rusty . Solo puedo recomendar fuertemente eso. En aras de la exhaustividad, cito el resumen de la escala desde el primer enlace (el mejor en la parte superior, el peor a continuación):

Buenas API

  • Es imposible equivocarse.
  • El compilador / enlazador no te permitirá equivocarte.
  • El compilador le avisará si se equivoca.
  • El uso obvio es (probablemente) el correcto.
  • El nombre te dice cómo usarlo.
  • Hazlo bien o siempre se romperá en tiempo de ejecución.
  • Siga la convención común y lo hará bien.
  • Lea la documentación y lo hará bien.
  • Lea la implementación y lo hará bien.
  • Lea el hilo correcto de la lista de correo y lo hará bien.

API malas

  • Lea el hilo de la lista de correo y se equivocará.
  • Lea la implementación y se equivocará.
  • Lea la documentación y se equivocará.
  • Siga la convención común y se equivocará.
  • Hazlo bien y a veces se romperá en tiempo de ejecución.
  • El nombre te dice cómo no usarlo.
  • El uso obvio está mal.
  • El compilador le avisará si lo hace bien.
  • El compilador / enlazador no le permitirá hacerlo bien.
  • Es imposible acertar.

Ambas páginas de detalles aquí y aquí vienen con una discusión en profundidad de cada punto. Es realmente una lectura obligada para los diseñadores de API. Gracias Rusty, si alguna vez lees esto.


0

En palabras simples:

  • El enfoque abstracto tiene la ventaja de permitir construir métodos concretos a su alrededor.
  • Al revés no es cierto

UDP tiene la ventaja de permitirle construir sus propias transmisiones confiables. Entonces, ¿por qué casi todos usan TCP?
svick

También se considera la mayoría de los casos de uso. Algunos casos pueden ser necesarios con tanta frecuencia, que es factible hacer que esos casos sean especiales.
Roman Susi

0

Si amplía SearchCriteriaun poco la idea, puede darle flexibilidad, como crear AND, ORetc. criterios. Si necesita dicha funcionalidad, este sería el mejor enfoque.

De lo contrario, diseñarlo para la usabilidad. Haga que la API sea fácil para las personas que la usan. Si tiene algunas funciones básicas que se necesitan con frecuencia (como buscar a una persona por su nombre), proporciónelas directamente. Si los usuarios avanzados necesitan búsquedas avanzadas, aún pueden usar el SearchCriteria.


0

¿Qué está haciendo el código detrás de la API? Si es algo flexible, entonces una API flexible es buena. Si el código detrás de la API es muy específico, ponerle una cara flexible significa que los usuarios de la API se sentirán frustrados y molestos por todo lo que la API pretende que es posible, pero que en realidad no se puede lograr.

Para su ejemplo de búsqueda de persona, ¿se requieren los tres campos? Si es así, la lista de criterios es mala porque permite una multitud de usos que simplemente no funcionan. Si no, entonces requerir que el usuario especifique entradas no requeridas es malo. ¿Qué posibilidades hay de que se agregue la búsqueda por dirección en V2? La interfaz flexible hace que sea más fácil de agregar que la inflexible.

No todos los sistemas deben ser ultra flexibles, tratando de hacer que todo lo sea, así es como Arquitectura Astronauta. Un arco flexible dispara flechas. Una espada flexible es tan útil como un pollo de goma.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.