New post

Find

Question
· Feb 4

Process multiple messages by increasing pool size while maintaining order with keys

At the moment we're creating multiple BPLs are using a router (or another BPL) to direct to these based on a unique key modulo the amount of BPLs available, e.g. if we have 3 BPLs created.

Message key = 1 mod 3 + 1 -> BPL02
Message key = 2 mod 3 + 1 -> BPL03
Message key = 3 mod 3 + 1 -> BPL01

FIFO only matters in that each messages for each key is processed in order.
What we were considering doing is increasing the pool size to 3 and programmatically creating a BPL on each thread that processes messages that would be directed to it, rather than having to create multiple BPLs into the production.

Everything I've found so far suggests just keeping the pool size as one to maintain FIFO and I can't really find much about how that may be over-ridden, or if it's even possible or advised. So not really sure if I'm completely barking up the wrong tree here.

Any tips or advice appreciated.

 

7 Comments
Discussion (7)1
Log in or sign up to continue
Article
· Feb 4 2m read

Memoria un Acelerador de API

Estimada comunidad,

Quería comentarles que hoy publique en OpenExchange un acelerador de APIs muy simple de implementar (con algún parecido a Redis, pero más funcional) y con resultados bastante buenos, a continuación está la publicación del README, espero les sea de ayuda!

https://openexchange.intersystems.com/package/memoria

1 Comment
Discussion (1)2
Log in or sign up to continue
Question
· Feb 4

How to find all globals in routines?

The task is to find all globals that are referenced in certain routines. I could search for ^ using  class(%Studio.Project).FindInFiles  but that would also find ^ in comments and function calls. I can distinguish between a global and a function call visually, but it would be lovely to be able to skip function calls programmatically. Is it possible?

The Find and Replace screen in Studio has a capability of searching for ^ with Match Element Type set to Global Variable. Maybe I could use that but what is the ObjectScript function behind this screen if any?

16 Comments
Discussion (16)3
Log in or sign up to continue
Article
· Feb 4 7m read

Generation of OpenAPI Specifications

Introduction

A REST API (Representational State Transfer) is an interface that allows different applications to communicate with each other through the HTTP protocol, using standard operations such as GET, POST, PUT, and DELETE. REST APIs are widely used in software development to expose services accessible by other applications, enabling integration between different systems.

However, to ensure that APIs are easy to understand and use, good documentation is essential. This is where OpenAPI comes in.

OpenAPI is a standard for describing RESTful APIs. It allows for a structured definition of an API's functionality, specifying available endpoints, accepted and returned data types, required parameters, and expected responses. All this information is collected in a specification file (usually with a .yaml or .json extension), which can be interpreted by automated tools to generate code, documentation, and more.

The OpenAPI Specification is designed to be readable by both machines and humans, enabling the description, production, consumption, and visualization of RESTful web services in a standardized way. Therefore, an OpenAPI document represents a formal description of the API, useful both for developers who need to use it and for tools that can leverage it to automate various processes.

Why is it useful to define a specification file?

Adopting OpenAPI to document an API offers several benefits:

  • Clarity: It provides detailed and structured documentation, allowing developers to quickly understand how to interact with the API, what requests to send, and what data to expect in response.
  • Automation: Documentation can be automatically generated from the code, staying up-to-date with any API changes.
  • Interactivity: Tools like Swagger, an open-source suite for API documentation and testing, include Swagger UI. This allows you to explore and test APIs directly from the browser, simplifying development, verification, and understanding of APIs.
  • Standardization: Using OpenAPI ensures that documentation follows a shared, recognized format, facilitating integration with other tools and services.

How can you produce an OpenAPI document?

There are two main approaches to generate an OpenAPI specification file:

  • "Code-first" (automatic) approach: If a REST API has already been developed in InterSystems IRIS, you can automatically generate the OpenAPI documentation without manually writing the specification file. IRIS offers an integrated feature to export the OpenAPI documentation in JSON or YAML format, based on the REST class definitions.
  • "Specification-first" (manual) approach: In this case, the OpenAPI file is manually written in YAML or JSON, describing all the endpoints, parameters, and expected responses. This approach is useful when you want to define the API before implementing it, facilitating design and sharing with other developers or stakeholders.

Automatic Approach

There are two ways to automatically generate the OpenAPI specification file in InterSystems IRIS.

Method 1: Using the GetWebRESTApplication Function

One approach is to use the GetWebRESTApplication function provided by the %REST.API class.
A practical example of how to use it is by adding the following function in the dispatch class:

ClassMethod GenerateOpenAPI() As %Status
{
    // The name of the REST application
    Set webApplication = "MyAPP"  // Replace with the name of your web app
    // Retrieve the OpenAPI 2.0 documentation
    Set sc = ##class(%REST.API).GetWebRESTApplication("", webApplication, .swagger)
    
    If $$$ISERR(sc) {
        Quit sc  // If an error occurred, exit the method
    }
    
    // Return the documentation in JSON format
    Set %response.ContentType = "application/json"
    Do ##class(OMRREST.impl).%WriteResponse(swagger.%ToJSON()) 

    Quit $$$OK
}

Additionally, add the following route to the UrlMap:

  <Route Url="/openapi" Method="GET" Call="GenerateOpenAPI"/>

At this point, you'll have everything you need to generate the specification file from your dispatch class. To view the documentation, connect to the URL shown (where MyWebapp is the name of your web application, as defined in the management portal):

<host>:<port>/MyWebapp/openapi

The JSON generated this way represents the OpenAPI specification of your API. After exploring the second method, we will see how to view and test it in Swagger.

Method 2: Using the Management API

Another way to generate the OpenAPI specification file is by using the InterSystems IRIS Management API.

To call this service, you can use tools like Postman, which is a developer tool that allows you to test, document, and automate APIs.
Postman provides a simple and intuitive interface for sending HTTP requests (GET, POST, PUT, DELETE, etc.), viewing responses, managing authentication, and creating automated tests.

To make the request using Postman, follow these steps:

  1. Click on the New button and create an HTTP request.
  2. Configure the request as follows and send it:
    • Select GET as the HTTP method.
    • Specify the URL in the following format, using the <baseURL> of your instance:
      https://<baseURL>/api/mgmnt/v1/namespace/myapp
      Here, namespace is the name of the namespace where you have created the REST service, and myapp is the name of your web application.
    • Set the Authorization method to Basic Auth and provide the username and password of a user with read access to the specified namespace.

Once the JSON is generated, it can be viewed and tested using tools like Swagger Editor.

{
   "info":{
      "title":"",
      "description":"",
      "version":"",
      "x-ISC_Namespace":"MyNamespace"
   },
   "basePath":"/MyWebapp",
   "paths":{
      "/loginForm":{
         "post":{
            "parameters":[
               {
                  "name":"payloadBody",
                  "in":"body",
                  "description":"Request body contents",
                  "required":false,
                  "schema":{
                     "type":"string"
                  }
               }
            ],
            "operationId":"loginForm",
            "x-ISC_ServiceMethod":"loginForm",
            "responses":{
               "default":{
                  "description":"(Unexpected Error)"
               },
               "200":{
                  "description":"(Expected Result)"
               }
            }
         }
      },
      "/refresh":{
         "post":{
            "parameters":[
               {
                  "name":"payloadBody",
                  "in":"body",
                  "description":"Request body contents",
                  "required":false,
                  "schema":{
                     "type":"string"
                  }
               }
            ],
            "operationId":"refresh",
            "x-ISC_ServiceMethod":"refresh",
            "responses":{
               "default":{
                  "description":"(Unexpected Error)"
               },
               "200":{
                  "description":"(Expected Result)"
               }
            }
         }
      },
      "/logout":{
         "post":{
            "parameters":[
               {
                  "name":"payloadBody",
                  "in":"body",
                  "description":"Request body contents",
                  "required":false,
                  "schema":{
                     "type":"string"
                  }
               }
            ],
            "operationId":"logout",
            "x-ISC_ServiceMethod":"logout",
            "responses":{
               "default":{
                  "description":"(Unexpected Error)"
               },
               "200":{
                  "description":"(Expected Result)"
               }
            }
         }
      },
      "/openapi":{
         "get":{
            "operationId":"GenerateOpenAPI",
            "x-ISC_ServiceMethod":"GenerateOpenAPI",
            "responses":{
               "default":{
                  "description":"(Unexpected Error)"
               },
               "200":{
                  "description":"(Expected Result)"
               }
            }
         }
      }
   },
   "swagger":"2.0"
}

Manual Approach

The manual approach for generating an OpenAPI document involves writing the specification file by hand in YAML or JSON format. This approach is particularly useful when you want complete control over the API design before its implementation, or when documenting an already existing API without relying on automated tools.

To write the OpenAPI specification file, you can refer to the official documentation for version 2.0 of the OpenAPI Specification, where you'll find information on required fields and how to describe endpoints, parameters, responses, and more. This detailed guide will help you understand how to properly structure the YAML or JSON file to meet OpenAPI standards.

A good example of using this approach is when creating a REST service using the methods outlined in the official InterSystems IRIS documentation.
You can find an introduction to how to develop and configure a REST application in IRIS by following this page of the documentation, which describes step-by-step the methods needed to expose a RESTful application with IRIS.

6 Comments
Discussion (6)5
Log in or sign up to continue
Article
· Feb 4 6m read

REST Service in IRIS Production: When You Crave More Control Over Raw Data

 

  “At the request of the survivors, the names have been changed. Out of respect for the dead, the REST has been told exactly as it occurred.”

Warning: There’s an important poll at the end of this article. I’m eagerly awaiting your responses!
 

So, we hit a snag. A big one. There’s no way to search messages in our production.

Sure, a message comes into the REST service, and everything in the production runs smoothly as it should.



 

Here’s a sample request:

{

"case":"2-12-85-06",

"to":"management of ZOO",

"subject":"Children report that the horse has grown a fifth leg"
}

Everything looks great in production!

BUT! Then either we or the client wants more:

"Hey, can we filter messages by specific criteria? For example, did we ever get anything related to case number 2-12-85-06?"

And that's where things took a sinister turn.

The standard message type, EnsLib.HTTP.GenericMessage, stores JSON data in streams. A great idea, right? Until it’s not.


Because searching through messages in streams works about as well as a flyscreen door on a submarine. Spoiler: It doesn’t. Like, at all.

You’ve probably seen this error before:


This adorable little red “NOPE” is the vigilant gatekeeper of your data. Always on duty, always blocking your sweaty fingers from touching anything sensitive. A true protector! But also a huge headache.

 

The Fight Back

  "The red tide, Lester. Life. We’re being fed crap, day after day - bosses, wives, whatever. It’s killing us. And if you don’t stand up, if you don’t show them you’re still an animal deep down, the tide will sweep you away."

 

We don’t retreat, though. Never.

There are a few possible solutions. I personally went for a storage-limited one because I don’t want to keep extra stored data. I just want to stay within the realm of production messages.

So, here’s the plan:

We create a custom message class, inheriting from EnsLib.HTTP.GenericMessage. Something like this:

Class Production.Message.ZooMessage Extends EnsLib.HTTP.GenericMessage
{

Property RequestJSON As %String(MAXLEN = "");
Property CaseId As %String;
Index ZooMessageCaseIdIndex On CaseId;
}

Next, we need to pass this custom message to the operation and include additional search-ready data (message body and case number).

 

Solution 1: The “Not-So-Great Start”

 

 

“Don’t trust what comes out of the ocean.

- But... we came from the ocean.”

 

We create a service by inheriting from EnsLib.REST.GenericService, then add it to the production. Simple, right?

Class Production.Service.ZooREST Extends EnsLib.REST.GenericService
{

Parameter DOCCLASS = "Production.Message.ZooMessage";
}

Production:

  <Item Name="Service.RESTver1" Category="" ClassName="Production.Service.ZooREST">
    <Setting Target="Host" Name="TargetConfigName">Operation.RESTver1</Setting>
    <Setting Target="Adapter" Name="Port">1984</Setting>
  </Item>

Except it’s not.


The input message type stays the same.

Parameter DOCCLASS = "Production.Message.ZooMessage" didn’t work.

 


Solution 2: “Too Many Cooks”

 

  “This is quite unusual…
- Unusual? Once I found a human foot in the oven. That’s unusual. This is merely weird.”

 

Turns out, the issue boils down to this single line of code in the OnProcessInput method:


Replacing it with:
Set tRequest=$classmethod(..#DOCCLASS,"%New",pRequestBody,,pRequestBody)


...would fix the issue. But this change would render the vendor’s code in subsequent IRIS updates useless. A dead-end option that leaves a bad taste in the mouth.

 

 

Solution 3: The “Dragon-Guarded Path”

 

 

“You could just get in your car and drive away.

- And why’s that?

- Because some roads you just shouldn’t take. Used to be maps would mark them, saying, ‘Here Be Dragons.’ Now they don’t, but the dragons are still there.”

 

The idea? Pass the message to a router, transform it, and send it to the process.

Components needed:

  • A router
  • A transformation
  • A pinch of something mysterious and arcane
Class Production.Rule.Router Extends Ens.Rule.Definition
{

XData RuleDefinition [ XMLNamespace = "http://www.intersystems.com/rule" ]
{
<ruleDefinition alias="" context="EnsLib.MsgRouter.RoutingEngine" production="Production.Test">
<ruleSet name="ZOO Router" effectiveBegin="" effectiveEnd="">
<rule name="ZOO Process Inbound Http" disabled="false">
<constraint name="source" value="Service.RESTver3"></constraint>
<when condition="1">
<send transform="Production.DataTransformation.ZOO" target="Operation.RESTver3"></send>
<return></return>
</when>
</rule>
</ruleSet>
</ruleDefinition>
}

}

 

Class Production.DataTransformation.ZOO Extends Ens.DataTransformDTL [ DependsOn = (EnsLib.HTTP.GenericMessage, Production.Message.ZooMessage) ]
{

Parameter IGNOREMISSINGSOURCE = 1;
Parameter REPORTERRORS = 1;
Parameter TREATEMPTYREPEATINGFIELDASNULL = 0;
XData DTL [ XMLNamespace = "http://www.intersystems.com/dtl" ]
{
<transform sourceClass='EnsLib.HTTP.GenericMessage' targetClass='Production.Message.ZooMessage' create='new' language='objectscript' >
<assign value='source.HTTPHeaders' property='target.HTTPHeaders' action='set' />
<assign value='source.Stream.Read(1000000)' property='target.RequestJSON' action='set' />
<assign value='{}.%FromJSON(source.Stream).case' property='target.CaseId' action='set' />
</transform>
}

}

A bit bulkier, yes, but it works. No ritual dances required.

 

The Verdict?

  “- Nobody killed a judge. We own all the judges. What'd be the point of killing one?”

 

Honestly, I can’t decide. That’s why I’m writing this.

So, here’s a question for you:

  1. Should we grovel before Intersystems and ask them to fix the code?
  2. Fix it ourselves with rusty tools and questionable ethics?
  3. Accept the current status quo and build something clunky but functional?
  4. ?

Your call.

 

 

PS: Code from the examples above is available in this GitHub repository: https://github.com/banksiaglobal/http-rest-production

 

Aleksandr Kolesov is a seasoned software developer from Banksia Global with over 30 years of experience in the tech industry, specializing in the InterSystems IRIS platform. With a deep understanding of high-performance data management and data intensive application architectures, he has played a pivotal role in developing and optimizing applications for enterprises across various sectors. Over the years, Alex has earned a reputation for his expertise in building scalable, efficient systems, as well as his passion for mentoring up-and-coming developers. When he's not coding or solving complex technical challenges, Alex enjoys exploring new technologies and contributing to the growing community of InterSystems developers.
 
   
Banksia Global is a leading technology consultancy that specializes in helping organizations harness the full potential of modern data management solutions. With a strong focus on the InterSystems IRIS platform, Banksia Global provides strategic guidance, system integration, and custom software development services to clients across various industries. Their expertise lies in delivering high-performance, scalable solutions that enable businesses to drive innovation, streamline operations, and unlock new opportunities. Known for its collaborative agile approach, Banksia Global works closely with clients to ensure that each solution is tailored to meet their specific needs, fostering long-term partnerships and sustainable growth.
 
10 Comments
Discussion (10)4
Log in or sign up to continue