Como adicionar suas APIs à Produção de Interoperabilidade
Eu já devo ter mencionado isso antes: acredito que os Visual Traces, esses diagramas de sequência com o conteúdo completo de cada etapa, são um recurso fantástico da plataforma IRIS Data! Informações detalhadas sobre como a API funciona internamente, como um rastreamento visual, podem ser muito úteis para projetos na plataforma IRIS. É claro que isso se aplica quando não estamos desenvolvendo uma solução de alta carga, caso em que simplesmente não temos tempo para salvar/ler mensagens. Para todos os outros casos, bem-vindos a este tutorial!
Estarei usando uma abordagem baseada em especificação (specification-first), então o primeiro passo será criar uma especificação. Pedi ao Codex para criar uma especificação OpenAPI de exemplo e obtive o seguinte 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"
}
}
}
}
}Em seguida, vem a criação das classes Cache: spec, disp e impl. Usarei a API de sistema do IRIS para isso. Vamos executar a seguinte requisição no Postman (ou CURL) e exportar os arquivos gerados para o projeto:
curl --location 'http://localhost:52773/api/mgmnt/v2/dev/Sample.REST.API?IRISUsername=_system&IRISPassword=SYS' \
--header 'Content-Type: application/json' \
--data '/// sua especificação aqui ///'Onde:
dev- meu namespaceSample.REST.API- pacote para as classes da API
Depois disso, precisaremos de um Business Service. Proponho criar um serviço base comum e estender nosso serviço particular a partir dele. Isso é necessário para fornecer um ponto de entrada separado na Produção de Interoperabilidade para cada uma de nossas futuras APIs. Aqui está o código completo deste serviço base:
Class Sample.REST.Service.Core Extends (Ens.BusinessService, %REST.Impl)
{
/// Chame este método a partir da sua classe .impl</br>
/// pTarget - o Business Process que processa a mensagem</br>
/// pPayload - o corpo da requisição HTTP
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) {
// O status HTTP pode ser definido durante o processamento da chamada no 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)
}
}Alguns detalhes de implementação. Estou usando Ens.StreamContainer para passar JSONs entre o Business Service e o Business Process. O método OnMessage() é:
- Chamado a partir da classe
impl - Recebe o corpo da requisição HTTP como um
%DynamicObject - Cria uma instância do Business Service
- Envia uma mensagem do Serviço para o Business Process manipulador (veja o parâmetro
pTarget) viaOnProcessInput() - E retorna o
%DynamicObjectrecebido do processo de negócio como uma resposta HTTP
Em seguida, vamos criar um manipulador simples para nossas mensagens:
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
}
}E a última das classes Cache é uma classe filha 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)) = ""
}
}Aqui, você só precisa sobrescrever OnGetConnections() para descrever as conexões com os Business Processes na Produção. Isso não afeta a funcionalidade, mas é necessário para construir os links corretos na UI da Produção.
⚠️ O que é importante aqui: você deve usar os nomes das classes para os business hosts na Produção. Isso é fundamental para que nosso exemplo funcione e é, na minha opinião, uma boa prática.
Agora, podemos adicionar a implementação à nossa classe impl:
ClassMethod postSampleEcho(body As %DynamicObject) As %DynamicObject
{
Return ##class(Sample.REST.Service.API).OnMessage(##class(Sample.REST.Process.Echo).%ClassName(1), body)
}A parte de codificação está finalizada. Em seguida, precisamos adicionar uma aplicação web chamada /sample-api (mesma que o basePath em nossa especificação), definir a classe Sample.REST.API.disp como Dispatch Class, e criar os seguintes business hosts na Produção:
Sample.REST.Service.APISample.REST.Process.Echo
Nossa Produção deve ficar assim:
Vamos tentar executar a requisição:
curl --location 'http://localhost:52773/sample-api/sample/echo' \
--header 'Content-Type: application/json' \
--data '{
"name": "test",
"count": 1
}'Receberemos uma resposta:
{
"status": "ok",
"message": "Request processed for test with count 1"
}E também, nas mensagens do serviço Sample.REST.Service.API, obteremos um rastreamento visual (visual trace) detalhando nossa chamada.
Isso é tudo o que eu queria contar hoje sobre APIs REST em Produções de Interoperabilidade. Recomendo usar o iris-web-swagger-ui para fornecer sua especificação OpenAPI para equipes externas de forma conveniente. E use a autenticação JWT integrada para segurança. Acredito que é assim que uma API harmoniosa na plataforma IRIS Data deve ser.
Sinta-se à vontade para fazer qualquer pergunta.