Escrito por

Sales Engineer at InterSystems Iberia
MOD
Artículo Luis Angel Pérez Ramos · 9 hr atrás 6m read

Cómo añadir vuestras APIs al entorno de Producción de Interoperabilidad.

Puede que ya lo haya mencionado antes: creo que las Trazas Visuales, estos diagramas de secuencia con el contenido completo de cada paso, son una característica fantástica de la plataforma de datos IRIS. La información detallada sobre cómo funciona internamente la API, presentada como una traza visual, puede ser muy útil para proyectos en la plataforma IRIS. Por supuesto, esto se aplica cuando no estamos desarrollando una solución de alta carga, ya que en ese caso simplemente no tenemos tiempo para guardar o leer mensajes. Para todos los demás casos, ¡bienvenidos a este tutorial!

 

Voy a utilizar un enfoque specification-first, por lo que el primer paso será crear una especificación. Pedí a Codex que generara una especificación de ejemplo de OpenAPI y obtuve el siguiente JSON:

{
  "swagger": "2.0",
  "info": {
    "version": "1.0.0",
    "title": "Sample Spec API",
    "description": "Example Swagger 2.0 specification"
  },
  "basePath": "/sample-api",
  "schemes": [
    "http"
  ],
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "paths": {
    "/sample/echo": {
      "post": {
        "summary": "Accepts JSON payload and returns another JSON payload",
        "operationId": "postSampleEcho",
        "parameters": [
          {
            "in": "body",
            "name": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/SampleRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "schema": {
              "$ref": "#/definitions/SampleResponse"
            }
          }
        }
      }
    }
  },
  "definitions": {
    "SampleRequest": {
      "type": "object",
      "required": [
        "name",
        "count"
      ],
      "properties": {
        "name": {
          "type": "string",
          "example": "test"
        },
        "count": {
          "type": "integer",
          "format": "int32",
          "example": 1
        }
      }
    },
    "SampleResponse": {
      "type": "object",
      "properties": {
        "status": {
          "type": "string",
          "example": "ok"
        },
        "message": {
          "type": "string",
          "example": "Request processed successfully"
        }
      }
    }
  }
}

A continuación viene la creación de las clases de Caché: spec, disp e impl. Utilizaré la API del sistema de IRIS para ello. Ejecutad la siguiente petición en Postman (o CURL) y exportad los archivos generados al proyecto:

curl --location 'http://localhost:52773/api/mgmnt/v2/dev/Sample.REST.API?IRISUsername=_system&IRISPassword=SYS' \
--header 'Content-Type: application/json' \
--data '/// your spec here ///'

Donde:

  • dev - vuestro namespace
  • Sample.REST.API - paquete para las clases de la API

Después de eso, necesitaremos un Business Service. Os propongo crear un servicio base común y extender nuestro servicio particular a partir de este. Esto es necesario para proporcionar un punto de entrada independiente en la Producción de Interoperabilidad para cada una de nuestras futuras APIs. Aquí tenéis el código completo de este servicio base:

Class Sample.REST.Service.Core Extends (Ens.BusinessService, %REST.Impl)
{

/// Call this method from your .impl class</br> 
/// pTarget - the Business Process that processes the message</br>
/// pPayload - the body of the HTTP request
ClassMethod OnMessage(pTarget As %String, pPayload As %DynamicObject) As %DynamicObject
{
    Do ..%SetContentType("application/json")
    
    Set input = ##class(Ens.StreamContainer).%New()
    Set input.Stream = ##class(%Stream.GlobalCharacter).%New()
    Do input.Attributes.SetAt(pTarget, "Target")
    
    Do pPayload.%ToJSON(input.Stream)
    
    Return:$CLASSNAME()'=$GET($$$ConfigClassName($CLASSNAME())) ..Error($$$ERROR($$$EnsErrBusinessDispatchNameNotRegistered, $CLASSNAME()))
	
	Set tSC = ##class(Ens.Director).CreateBusinessService($CLASSNAME(), .service)
	Return:$$$ISERR(tSC) ..Error(tSC)
	
	Set tSC = service.ProcessInput(input, .output)
	Return:$$$ISERR(tSC) ..Error(tSC)

    Return ..Success(output)
}

ClassMethod Error(pStatus As %Status) As %DynamicObject
{
    Do ..%SetStatusCode(##class(%CSP.REST).#HTTP500INTERNALSERVERERROR)
    Do ##class(%CSP.REST).StatusToJSON(pStatus, .json)
    Return json
}

ClassMethod Success(pOutput As Ens.StreamContainer) As %DynamicObject
{
    Do ..%SetStatusCode(##class(%CSP.REST).#HTTP200OK)

    If $iso(pOutput) {
        // HTTP status can be set during the processing of the call in the Business Process
        Do:pOutput.Attributes.GetAt("Status")'="" ..%SetStatusCode(pOutput.Attributes.GetAt("Status"))
        Set stream = pOutput.StreamGet()
		Return:$iso(stream)&&(stream.Size>0) {}.%FromJSON(stream)
	}

    Return ""
}

Method OnProcessInput(pInput As Ens.StreamContainer, Output pOutput As Ens.StreamContainer) As %Status
{
    Return ..SendRequestSync(pInput.Attributes.GetAt("Target"), pInput, .pOutput)
}
}

Algunos detalles de la implementación. Estoy utilizando Ens.StreamContainer para pasar JSON entre el Business Service y el Business Process. El método OnMessage():

  1. Se llama desde la clase impl.
  2. Recibe el cuerpo de la petición HTTP como %DynamicObject
  3. Crea una instancia del Business Service.
  4. Envía un mensaje desde el Service al Business Process que actúa como manejador (ver el parámetro pTarget) mediante OnProcessInput()
  5. Y devuelve el %DynamicObject recibido del Business Process como respuesta HTTP.

A continuación, vamos a crear un manejador sencillo para nuestros mensajes:

Class Sample.REST.Process.Echo Extends Ens.BusinessProcess
{

Method OnRequest(pRequest As Ens.StreamContainer, Output pResponse As Ens.StreamContainer) As %Status
{
    Set jsonIn = {}.%FromJSON(pRequest.StreamGet())
    Set jsonOut = {"status": "ok", "message": ($$$FormatText("Request processed for %1 with count %2", jsonIn.name, jsonIn.count))}

    Set pResponse = ##class(Ens.StreamContainer).%New()
    Set pResponse.Stream = ##class(%Stream.GlobalCharacter).%New()
    Do jsonOut.%ToJSON(pResponse.Stream)

    Return $$$OK
}
}

Y por último, de las clases de Caché, tenemos una clase hija de  Sample.REST.Service.Core:

Class Sample.REST.Service.API Extends Sample.REST.Service.Core
{

ClassMethod OnGetConnections(Output pArray As %String, pItem As Ens.Config.Item)
{
	Set pArray(##class(Sample.REST.Process.Echo).%ClassName(1)) = ""
}
}

Aquí, solo necesitáis sobrescribir OnGetConnections() para describir las conexiones con los Business Processes en Producción. Esto no afecta a la funcionalidad, pero es necesario para construir enlaces correctos en la interfaz de Producción.

⚠️ Lo importante aquí: debéis usar los nombres de clase de los business hosts en Producción. Esto es fundamental para que nuestro ejemplo funcione y, en mi opinión, es una buena práctica.

Ahora, podemos añadir la implementación a nuestra clase impl:

ClassMethod postSampleEcho(body As %DynamicObject) As %DynamicObject
{
    Return ##class(Sample.REST.Service.API).OnMessage(##class(Sample.REST.Process.Echo).%ClassName(1), body)
}

La parte de codificación ha finalizado. A continuación, necesitamos añadir una aplicación web llamada /sample-api(igual que el basePath en nuestra especificación), establecer la clase Sample.REST.API.disp como Dispatch Class, y crear los siguientes business hosts en Producción:

  1. Sample.REST.Service.API
  2. Sample.REST.Process.Echo

Deberíamos obtener que Producción se vea así:

Production UI

Vamos a intentar ejecutar la petición:

curl --location 'http://localhost:52773/sample-api/sample/echo' \
--header 'Content-Type: application/json' \
--data '{
  "name": "test",
  "count": 1
}'

Obtendremos una respuesta:

{
    "status": "ok",
    "message": "Request processed for test with count 1"
}

Y además, en los mensajes del servicio Sample.REST.Service.API, obtendremos una traza visual que detalla nuestra llamada.

Eso es todo lo que quería contaros hoy sobre las APIs REST en Producciones de Interoperabilidad. Os recomiendo usar iris-web-swagger-ui para proporcionar vuestra especificación OpenAPI a equipos externos de manera cómoda. Y utilizar la autenticación JWT incorporada para la seguridad. Creo que esto es lo que debería ser una API armoniosa en la plataforma de datos IRIS.

No dudéis en hacer cualquier pregunta.