Cómo manejar dos APIs diferentes con la misma Mule App

Por defecto, Mulesoft propone un esquema en el que la implementación de cada API que queremos exponer es desplegada como una instancia separada. Esto nos ofrece bastantes ventajas, siendo seguramente el aislamiento de estas implementaciones unas de las más destacadas, ya que conseguiremos asegurar que los fallos en una, no nos creen problemas en otras.

Sin embargo, especialmente cuando estamos desplegando nuestras API en Anypoint Cloudhub, la nube que Mulesoft nos ofrece como iPaaS, donde estas instancias o workers consumen un porcentaje de nuestra subscripción, seguir este esquema puede no ser siempre el deseado.

Asumiendo que perderemos el deseado aislamiento anteriormente comentado, vamos a ver cómo podemos hacer, con el fin de disminuir el número de cores a consumir, para tener distintas APIs, con distintas especificaciones, desplegando una única Mule App en una única instancia.

Para este artículo, usaremos RAML como lenguaje de especificación de APIs, y desplegaremos en Cloudhub.

Vamos a partir de dos especificaciones de API sencillas, que nos servirán de ejemplo. Serán sencillas para que nos permitan ilustrar lo que queremos hacer fácilmente.

People API

#%RAML 1.0
title: People

/people:
  get:
    description: This is the People API
    responses:
      200:
        body:
          application/json:
            example:
              [
                {
                  "name": "Luke Skywalker",
                  "height": "172",
                  "mass": "77",
                  "hair_color": "blond",
                  "skin_color": "fair",
                  "eye_color": "blue",
                  "birth_year": "19BBY",
                  "gender": "male"
                },
                { 
                  "name": "Darth Vader",
                  "height": "202",
                  "mass": "136",
                  "hair_color": "none",
                  "skin_color": "white",
                  "eye_color": "yellow",
                  "birth_year": "41.9BBY",
                  "gender": "male"
                }
              ]

Starships API

#%RAML 1.0
title: Starships

/starships:
  get:
    description: This is the Starships API
    responses:
      200:
        body:
          application/json:
            example:
              [
                {
                  "name": "Star Destroyer",
                  "model": "Imperial I-class Star Destroyer",
                  "manufacturer": "Kuat Drive Yards",
                  "cost_in_credits": "150000000",
                  "length": "1,600",
                  "max_atmosphering_speed": "975",
                  "crew": "47,060",
                  "passengers": "n/a",
                  "cargo_capacity": "36000000",
                  "consumables": "2 years",
                  "hyperdrive_rating": "2.0",
                  "MGLT": "60",
                  "starship_class": "Star Destroyer"
                },
                {
                  "name": "Millennium Falcon",
                  "model": "YT-1300 light freighter",
                  "manufacturer": "Corellian Engineering Corporation",
                  "cost_in_credits": "100000",
                  "length": "34.37",
                  "max_atmosphering_speed": "1050",
                  "crew": "4",
                  "passengers": "6",
                  "cargo_capacity": "100000",
                  "consumables": "2 months",
                  "hyperdrive_rating": "0.5",
                  "MGLT": "75",
                  "starship_class": "Light freighter"
                }
              ]

Y crearemos las dos correspondientes APIs en nuestro API Manager:

Desde aquí, lo que haremos será crear un único proyecto mule que llamaremos people-starships API que será quien contendrá la implementación de nuestras APIs. Para este ejemplo, no modificaremos ningún código, de manera que usaremos el scafolding que Mulesoft nos da una vez importamos una especificación de API.

Crearemos el proyecto a través del wizard, importando una de las especificaciones de nuestras APIs como base:

Simplemente modificaremos la configuración habitual, incorporando el Autodiscovery Id correspondiente a este API:

Podemos desplegar y comprobar que todo funciona correctamente lanzando alguna petición.

Hasta aquí todo ha sido bastante normal, simplemente hemos creado un proyecto en base a una especificación y hemos configurado el Autodiscovery Id. Lo que haremos ahora será añadir la nueva especificación a nuestro proyecto, y para ello lo que haremos será importarla como dependencia, como una nueva API.

Esto hará que se nos cree un nuevo archivo de flujo, haciendo Mule por nosotros de nuevo todo el scafolding necesario.

Ahora procederemos a hacer el mismo paso que antes, configurando el Autodiscovery Id. Este es el paso importante, ya que lo que tendremos que hacer es crear una configuración NUEVA, apuntando al flujo del API Starships en este caso, con el valor del Autodiscovery ID que le corresponde.

Lo que vamos a hacer, ya que tenemos un único proyecto, es crear un único archivo global de configuraciones, donde tendremos ambos discovery Id y donde los HTTP Listener de ambas API compartirán la misma configuración. En caso de no hacerlo así, mejor revisar que los puertos configurados son correctos.

También debemos modificar los listener de los flujos de cada API para establecer path distintos, y que de esta manera representen claramente las dos distintas APIs.

De nuevo, podemos redesplegar y comprobar que todo funciona como debe:

Algo que debemos de tener en cuenta, es que evidentemente tendremos los logs de ambas APIs juntos:

A partir de aquí podremos manejar las APIs de manera independiente, tanto a nivel de monitorización como a nivel de las políticas que apliquemos. Por ejemplo, vamos a securizar una de las APIs y veremos como la política no se aplica en la otra.

Con esto, ya tendríamos el objetivo buscado:

  • 2 especificaciones de API independientes

  • 2 API en nuestro Manager independientes, que podemos governar y monitorizar de manera independiente.

  • 1 única Mule App consumiendo, en este caso, un único worker.

Y la solución tendría un aspecto de este tipo.

Aunque podamos hacer esto, debemos analizar el caso concreto y tener en cuenta varias consideraciones:

  • Estamos perdiendo aislamiento entre las implementaciones, lo que puede hacer que un error en la implementación de una haga que la otra entre también en estado de error.

  • Ya que ambas implementaciones compartirán log, hacer el seguimiento de trazas puede ser más complicado.

  • Estamos acoplando APIs a nivel de implementación, lo que nos obligará a hacer un despliegue de ambas cuando sea necesario un cambio en una de ellas. Esto potencialmente nos obligará a lanzar test como pueden ser de integración y/o regresión de ambas APIs.

  • A la hora de agrupar estas APIs, podemos pensar en hacerlo de manera que tengan cierto sentido desde el punto de vista de negocio; ya que estamos acoplándolas, mejor intentar que tengan la mayor cohesión posible.

  • Como beneficio principal, estamos consiguiendo un consumo menor de recursos, pero manteniendo la versatilidad de una granularidad de APIs en nuestro ecosistema mayor.

 

Guia introduccion MuleSoft AnyPoint