0:008:32

Recursos

Resumen

La extensibilidad en el desarrollo de software es la capacidad de agregar nuevas funcionalidades sin modificar de forma importante lo que ya existe. Si escribes código a diario y te preguntas cómo crecer sin romper lo construido, aquí verás cómo lograrlo con contratos e interfaces. Es relevante para cualquier persona que programe en equipo o quiera mejorar su diseño.

El punto de partida es una frase que resume todo: el cambio es la única constante, y en el desarrollo de software lo vives todos los días. Cada jornada agregas cosas nuevas, así que la meta es hacerlo modificando lo menos posible. Un buen diseño ofrece puntos de extensión o cambios sencillos y comprensibles [00:34].

¿Qué son los contratos e interfaces en programación?

Aquí está el corazón del tema. Un contrato y una interfaz, que para fines prácticos funcionan como sinónimos, son una serie de reglas que todo aquel que quiera implementarlas debe cumplir a rajatabla [01:12]. Firmas el contrato y, por tanto, tienes que implementar todo lo que estipula. Sin excepciones.

¿Qué es una interfaz en programación? Es un contrato que define las reglas y métodos que toda clase debe implementar si quiere pertenecer a ese tipo. No contiene la lógica, solo las obligaciones que cada implementación debe cumplir.

Esta idea conecta con una frase muy conocida: si tu objeto hace como un pato, camina como un pato, entonces es un pato [04:00]. Si tu gateway tiene los métodos definidos, entonces es un gateway.

¿Cuáles son los retos de la extensibilidad?

Antes de saltar al código, conviene frenar un segundo. ¿Cuántas veces programamos sin preguntarnos si esta será una forma adecuada de hacerlo? Olvida si es bueno o malo: busca que tenga sentido y que sea sencillo.

Los retos principales son dos:

  • Tener una buena comprensión del problema, dedicando tiempo a entender qué intentas hacer y por qué [01:52].
  • Encontrar consenso en los estándares o reglas que guíen el desarrollo, algo propio del trabajo en equipo [02:25].

Y aquí viene lo interesante: si tu equipo no tiene documentación, deberías empezar a crearla. Haz un tracking de todas las convenciones a las que llegaron para que el futuro sea más sencillo.

¿Cómo aplicar contratos en un ejemplo real de gateway?

Retomemos el ejemplo de la clase de reusabilidad. Tenemos un servicio de usuarios que depende de un HTTP gateway, que a su vez depende de un módulo HTTP para hacer peticiones al backend. Es código muy común en aplicaciones de front end [02:55].

Entonces llega un requerimiento nuevo: ya no usarán REST por defecto, sino una aproximación con GraphQL. Y hay un detalle extra: algunos servicios seguirán usando la versión antigua y otros la nueva [03:20]. ¿Cómo aseguras que ambas sigan el mismo orden y las mismas reglas?

¿Cómo defino el contrato del gateway?

Lo primero es definir la interfaz. Toda clase que quiera llamarse gateway tendrá que implementar dos métodos obligatorios:

  • fetchMany, con sus parámetros estipulados.
  • fetchOne, con sus parámetros estipulados.

Estos métodos son las cláusulas del contrato. A nivel de diagrama se representa como una interfaz gateway con esos métodos definidos [03:45].

¿Cómo implemento varias clases con el mismo contrato?

Ahora creas clases que implementen ese contrato. Lo que antes era HTTP usaba una aproximación basada en REST, así que lo renombras a RESTGateway, que implementa la interfaz gateway.

typescript class RESTGateway implements Gateway { fetchMany() { // implementación propia de REST } fetchOne() { // implementación propia de REST } }

class GraphQLGateway implements Gateway { fetchMany() { // implementación propia de GraphQL } fetchOne() { // implementación propia de GraphQL } }

Fíjate que la implementación de fetchMany y fetchOne es propia de cada clase, porque las consideraciones de REST y GraphQL son diferentes [04:25]. Ahí está el poder del contrato: solo estipula las reglas, no cómo las cumples.

¿Por qué depender de una interfaz facilita el cambio?

El servicio de usuarios tendrá un atributo llamado serviceGateway cuyo tipo de dato es gateway, la interfaz abstracta. En el constructor creas un nuevo GraphQLGateway [04:55].

typescript class UserService { private serviceGateway: Gateway;

constructor() { this.serviceGateway = new GraphQLGateway(); } }

Si mañana quieres cambiar GraphQLGateway por RESTGateway, el cambio es sencillo. ¿Por qué? Porque el contrato es el mismo: fetchMany y fetchOne siempre están ahí.

¿Por qué es mejor depender de una interfaz y no de una clase concreta? Porque puedes intercambiar implementaciones sin reescribir el código que las usa. Al depender de la abstracción gateway, cambiar de GraphQL a REST solo requiere una línea.

En el diagrama, el servicio de usuario está compuesto por un gateway, que es una interfaz que clases como GraphQLGateway y RESTGateway implementan [05:25]. Esa composición es la clave de la extensibilidad.

Si mañana el servicio necesita otro gateway, lo cambias sin problema. Si debes implementar uno nuevo, solo creas una clase e implementas cada método. No dependes de implementaciones concretas, sino de una abstracción con la que intercambias piezas.

¿Ya habías trabajado con interfaces o contratos antes? Cuéntame tus experiencias en los comentarios para que todos podamos aprender.

Extensibilidad con contratos e interfaces