Hi Evgeny,
In ISOS, the "quit" command is inherited from MUMPS ("Q[UIT]") and has two forms (see the documentation for details) : 

  • with arguments : terminates a method, function or routine, returning it's arguments as value
  • without arguments : exits the current context (e.g. for, while, ...)

The "return" command was introduced by InterSystems, I think in Caché 2016.2  (@all please, correct me if I'm wrong).  It was added to avoid confusion, making ISOS clearer and also more aligned with 'modern' programming conventions.
"return" is equivalent to "quit" with argument, thus is has only one form and one, clear meaning : returning a value.
When the code intent is to return a value, nowadays I favor using "return".

p.s. IMHO, generative AIs, while useful in some contexts, are nor artificial, because they are trained with human sourced corpus, nor intelligent, because they are not able of any real creativity and are not doing any reasoning (even if they can mimic both rather well) 😇

Hi Evgeny,
Thank you for sharing the details, now I see why you have so much lines in a single class 😊
As an example of what Scott and Robert are explaining in their excellent answers, when dealing with a REST API spec defining (mostly) CRUD operation on many resources, I think one way to divide and, hopefully, conquer is to delegate the implementation of operations related to each resource to a separate class.
Taking as example petstore, for which, of course, this is an overkill, this would result in five classes :

  1. Class dc.petstore.impl Extends %REST.Impl : (generated), all endpoints class methods, with most bodies being single line call to another class method
  2. Class dc.petstore.Commons : logic common to all the classes below
  3. Class dc.petstore.PetOperations Extends Commons : class methods dealing with 'pet' resource (CRUD + other)
  4. Class dc.petstore.StoreOperations Extends Commons : same for 'store' resource
  5. Class dc.petstore.UserOperation Extends Commons : same for 'user' resource

dc.petstore.impl class methods simply delegate the call to resource class, e.g.

ClassMethod findPetsByStatus(status As%String) As%DynamicArray
{
    return##class(PetOperations).FindByStatus(status)
}

Hope this helps

Why is there so many lines in this class ? 

From code quality point of view, a class exceeding 500 lines is usually considered a candidate for refactoring.

Is the code generated ?

Following these two insightful days, I wanted to thank you for the incredibly warm welcome and kind feedback. 
Here is my Bingo card 😁


🖼 Musée du Quai Branly

  • Photo of something at Musée du Quai Branly that sparked your curiosity

Le Jardin d’O outside the Museum

  • Describe one Musée du Quai Branly exhibit in three words

Much common ancestry!

The Diola mask from Senegal with horns, woven leafs canvas and seeds, share similarities with Celt masks of Cernunos made of plants, fruits and deer horns

  • An exhibit you’d like to revisit and why

I'm particularly interested in the musical instrument collection, we did not have the time to visit

  • Your favorite song sung by Laure Poissonnier at the Gala dinner

« Take Me to the Moon » 🌙

Shoot for the moon, even if you miss, you’ll land among the stars — Oscar Wilde

🎤 Sessions & Ideas

  • A project you discovered at READY and one question you have about it

CHU Toulouse FHIR + interop, a very inspiring design. I wonder what is the strategy for change/release management, such as FHIR release migration.

  • The most unexpected analogy used by a speaker

Zweder Bergman talking about how difficult it is to pick a bakery in Paris and comparing this to the many choices facing us with FHIR interoperabiliy

  • One key takeaway from a session

From « Care Coordination: From Patient Journey to Sustainable Population Health » : public health agencies often do not measure the value of prevention and how quality data exchange networks contribute to cost reduction.

  • A session outside your usual role and what surprised you

Retailer SPAR putting InterSytems technologies to effective and global use on such a large scale is quite surprising to me, I wonder how it initially sparked.

☕️ Informal Moments

  • A READY conversation that made you laugh

Chatting about where we are now, in a world obsessed by AI, with MUMPS developers about my age 😁

  • Your favorite non-session moment of the day

Meeting Egveny Shvarov, Guillaume Rongier, Irène Mykhailova and Anastasia Dyubaylo in real life : we are now more than avatars to each other 😊

  • One insight gained outside a session

The vast collection of python libraries is now available to IRIS, and it’s especially useful when dealing with AI. Thanks @Guillaume Rongier 
 for pointing this out !

  • Your favorite informal discussion (coffee, lunch, evening)

During the evening, chatting about using a FHIR repository a central storage for an hospital, and how it empowers the institution vs data being captive in supplier software. Also outlining how it is difficult to convince hospital boards and software suppliers to invest in the technology, the ROI being on the long term.

🤝 Reflection & Connections

  • Photo of your favorite session

  • Your READY moment of the day (one emoji + one sentence)

Learning more about EHDS initiative 😎

  • Exchange business cards or LinkedIn with two attendees

I offered some cards during informal moments, but cards seems to be somewhat deprecated, and attendees did not have a card to exchange.

  • Meet someone new at READY and tag them in your post

It was a pleasure to meet @Lorenzo Scalese  in real life, and sharing about developping with ObjectScript.

My rules, motivated by readability :

  • use #dim with no inline assignment
  • group dims at the beginning of the method/routine
  • use set to assign variables
Robert Barbiaux · Oct 30, 2025 go to post

By the look of the stack trace, I think the adapter is trying to write request but it fails. Check the SSLConfiguration setting of the operation, it should be the name of an existing, valid, TLS configuration.

Robert Barbiaux · Sep 25, 2025 go to post

It is also possible to specify an ODBC connection string instead of system DSN name :

Driver={ODBC Driver 13 for SQL Server};server=localhost;database=WideWorldImporters;trusted_connection=Yes;

Robert Barbiaux · Aug 27, 2025 go to post

I would check out EnsLib.REST.DynamicObjVDoc, looks like it can parse something like JSONPath.

Robert Barbiaux · Jun 26, 2025 go to post

In interop Ens.MessageHeader, we have TimeCreated and TimeProcessed. I would along the same lines and have TimeCreated and TimeUpdated. I find Time… more appropriate as those are time stamps, not dates.

Robert Barbiaux · Jun 25, 2025 go to post

Use the concatenation operator, _ (underscore) in the value of the assign action. Something like : source.{EVN:5.2}_source.{EVN:5.3}

Robert Barbiaux · Apr 30, 2025 go to post

Absolutely. The new nomenclature does not reflect the conceptual differences between services and operations : the origin of the initial event that triggers processing. This could cause confusion for beginners.

I like ‘Services’ and ‘Operations’ better. The words also keep the link with the actual class names and wording in the documentation.

Robert Barbiaux · Mar 22, 2025 go to post

Error message 

ERROR #5001: element 'sql' is not allowed for content model '(annotation?,((true,false)|(false,true)|(true)|(false)))'
Indicates the <sql> element in the DTL XData block is in an unexpected location in the xml document, and that expected elements in that location are <annotation>, <true> or <false>. Maybe it is directly under <if> ?

Robert Barbiaux · Mar 10, 2025 go to post

Congratulations everyone, and thank you so much for making such an excellent community of developers possible !💐

Robert Barbiaux · Feb 22, 2025 go to post

You can query the message body class table with a left join to Ens.MessageHeader to get the orphaned message body identifiers :

select 
  %NOLOCK bod.Id
from
  MySample.BodyTable bod
  leftjoin Ens.MessageHeader hdr on bod.Id=hdr.MessageBodyId and hdr.MessageBodyClassName='MySample.BodyTable'where
 hdr.MessageBodyId isNULL
Robert Barbiaux · Feb 6, 2025 go to post

An approach using an operation that, on message : 

  • enqueue incoming message in a queue named after patient identifier
  • dequeue and process message from all existing patient queues

If PoolSize = 1, it will process 1 message at a time.

If PoolSize > 1, multiple jobs will process incoming messages from other operations in sequence for each patient

Class dc.interop.PatientMessageOperation Extends Ens.BusinessOperation
{

Parameter SETTINGS = "DeQueueTimeout,MinProcessTime,MaxProcessTime";Property DeQueueTimeout As%Integer [ InitialExpression = 1 ];Property MinProcessTime As%Integer [ InitialExpression = 1 ];Property MaxProcessTime As%Integer [ InitialExpression = 5 ];
Method HandlePatientMessage(request As test.src.ut.ks.lib.interop.PatientMessage, Output response As%Persistent) As%Status
{
    #Dim sc as%Status#Dim ex as%Exception.AbstractExceptions sc = $$$OKtry {
        $$$TOE(sc,..EnqueueRequest(request))
        $$$TOE(sc,..ProcessPatientMessages())
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}

Method EnqueueRequest(request As PatientMessage) As%Status
{
    #Dim sc as%Status#Dim ex as%Exception.AbstractException#Dim queue as Ens.Queue
    #Dim queueName as%String#Dimsas EnsLib.File.InboundAdapter
    
    s sc = $$$OKtry {
        s queueName = ..%ConfigName_".patient."_request.PatientId
        $$$TOE(sc,##class(Ens.Queue).Create(queueName))
        s ..%RequestHeader.TargetQueueName = queueName        
        $$$TOE(sc,##class(Ens.Queue).EnQueue(..%RequestHeader))   
        s$$$EnsRuntimeAppData(..%ConfigName,"queues",queueName) = 1
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}

Method ProcessPatientMessages() As%Status
{
    #Dim sc as%Status#Dim ex as%Exception.AbstractExceptions sc = $$$OKtry {
      s queueName = ""s queueName = $order($$$EnsRuntimeAppData(..%ConfigName,"queues",queueName))
      while queueName '= "" {
        $$$TOE(sc,..ProcessQueuedMessages(queueName))
        s queueName = $order($$$EnsRuntimeAppData(..%ConfigName,"queues",queueName))
      }
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}

Method ProcessQueuedMessages(queueName As%String) As%Status
{
    #Dim sc as%Status#Dim ex as%Exception.AbstractException#Dim header as Ens.MessageHeader
    #Dim timedOut as%Booleans sc = $$$OKtry {
        $$$TOE(sc,##class(Ens.Queue).DeQueue(queueName,.header,..DeQueueTimeout,.timedOut,1))
        while 'timedOut && $isobject(header) {          
          $$$TOE(sc,..ProcessHeader(header))
          $$$TOE(sc,##class(Ens.Queue).DeQueue(queueName,.header,..DeQueueTimeout,.timedOut,1))
        }
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}

Method ProcessHeader(header As Ens.MessageHeader) As%Status
{
    #Dim sc as%Status#Dim ex as%Exception.AbstractException#Dim msg as PatientMessage
    
    s sc = $$$OKtry {
        s msg = $classmethod(header.MessageBodyClassName,"%OpenId",header.MessageBodyId)
        $$$TOE(sc,..ProcessMessage(msg))
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}

Method ProcessMessage(msg As PatientMessage) As%Status
{
    #Dim sc as%Status#Dim ex as%Exception.AbstractException#Dim processTime as%Integers sc = $$$OKtry {
        $$$TRACE("job "_$job_" is processing message for patient "_msg.PatientId_" seq "_msg.Seq)
        s processTime = $random(..MaxProcessTime-..MinProcessTime)+..MinProcessTimehang processTime
        $$$TRACE("job "_$job_" processed message for patient "_msg.PatientId_" seq "_msg.Seq_" in "_processTime_" seconds")
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}

Method OnTearDown() As%Status
{
    #Dim sc as%Status#Dim ex as%Exception.AbstractExceptions sc = $$$OKtry {
      s queueName = ""s queueName = $order($$$EnsRuntimeAppData(..%ConfigName,"queues",queueName))
      while queueName '= "" {
        $$$TOE(sc,##class(Ens.Queue).Delete(queueName,"*"))
        s queueName = $order($$$EnsRuntimeAppData(..%ConfigName,"queues",queueName))
      }
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}

XData MessageMap
{
<MessageMap>
     <MapItem MessageType="ut.ks.lib.interop.PatientMessage">
      <Method>HandlePatientMessage</Method>
     </MapItem>
    </MessageMap>
}

}

The message class

Class ut.ks.lib.interop.PatientMessage Extends Ens.MessageBody
{

Property PatientId As%Integer;Property Seq As%Integer;Property Payload As%String;
Storage Default
{
<Data name="PatientMessageDefaultData">
<Subscript>"PatientMessage"</Subscript>
<Value name="1">
<Value>PatientId</Value>
</Value>
<Value name="2">
<Value>Seq</Value>
</Value>
<Value name="3">
<Value>Payload</Value>
</Value>
</Data>
<DefaultData>PatientMessageDefaultData</DefaultData>
<Type>%Storage.Persistent</Type>
}

}

Sample test method

ClassMethod SendMessages(patientCount As%Integer = 100, messageCount = 1000) As%Status
{
    #Dim sc as%Status#Dim ex as%Exception.AbstractExceptions sc = $$$OKtry {
        
        for i = 1:1:messageCount {
            s msg = ##class(PatientMessage).%New()
            s msg.PatientId = $random(patientCount)+1s msg.Seq = $increment(seq(msg.PatientId))
            s msg.Payload = "message "_msg.Seq_" for "_msg.PatientId
            $$$TOE(sc,##class(EnsLib.Testing.Service).SendTestRequest("PatientOperation",msg))
        }        
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}
Robert Barbiaux · Feb 5, 2025 go to post

Another approach would be to write a custom router class extending EnsLib.MsgRouter.RoutingEngine, overriding OnRequest() method.

In OnRequest(), before calling ##super(), assign the JSON %DynamicAbstractObject build from the input request stream to a property.

Use the property in the router rules.

Robert Barbiaux · Jan 24, 2025 go to post

You can override the Page() method of %CSP.Page and redirect output to a stream, process the resulting stream and write it to the original device the page is using to send data back to client.
Here is a quick and dirty example, using IO-Redirect package available on OpenExchange.
The redirecting page : 

Class test.src.RedirectedPage Extends%CSP.Page
{

ClassMethod Page(skipHeader As%Boolean = 1) As%Status [ ServerOnly = 1 ]
{
    #dim sc as%Status#dim ex as%Exception.AbstractException#dim pageStream,processedPageStream As%Stream.Object#dim len as%Integer#dim buffer as%Strings sc = $$$OKtry {
    
    Set pageStream = ##class(%Stream.GlobalCharacter).%New()
    Do##class(IORedirect.Redirect).ToStream(pageStream)
    $$$TOE(sc,##super(skipHeader))
    Do##class(IORedirect.Redirect).RestoreIO()
    Set pageStream = ##class(IORedirect.Redirect).Get()
    $$$TOE(sc,..ProcessPageStream(pageStream,.processedPageStream))
    while 'processedPageStream.AtEnd {
        s len = 32768s buffer = processedPageStream.Read(.len,.sc)
        $$$TOE(sc,sc)
        write buffer
    }
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}

ClassMethod ProcessPageStream(pageStream As%Stream.Object, Output processedPageStream As%Stream.Object) As%Status
{
    #dim sc as%Status#dim ex as%Exception.AbstractExceptions sc = $$$OKtry {
        s processedPageStream = ##class(%Stream.TmpCharacter).%New()
        $$$TOE(sc,processedPageStream.CopyFrom(pageStream))
        d processedPageStream.Write("<div><span>original page had "_pageStream.Size_" bytes </span></div>")
    } catch (ex) {
      s sc = ex.AsStatus()
    }
    return sc
}

}

The original page :

Class test.src.OriginalPage Extends test.src.RedirectedPage
{

ClassMethod OnPage() As%Status [ ServerOnly = 1 ]
{
    &html<
        <div><span>Hello, world, again</span></div>
    >return$$$OK
}
Robert Barbiaux · Dec 12, 2024 go to post

You can use the auxiliary property (aux attribute) of the <subtransform> DTL element to pass the index of the group to process to the subtransform, passing the entire source message as source object.

Robert Barbiaux · Nov 10, 2024 go to post

EnsLib.EDI.XML.Document implements Ens.VDoc.Interface, and exposes GetValueAt(propertyPath) method.
This method accepts XPath-like property paths such as "/foo/bar" or "/foo/bar/@attr".
This also means you can use the curly brace syntax in business rules (classes extending Ens.Rule.Definition).
For example, if the context class has a "xmlDocument" property, this expression
xmlDocument.{/foo/bar}
will be compiled into 
xmlDocument.GetValueAt("/foo/bar")

Robert Barbiaux · Sep 25, 2024 go to post

For example (in open exchange package ks-iris-lib) :

/// list of data types exposing %GetSerial and %SetSerialClass ks.lib.collections.ListOfDataTypes Extends%Library.ListOfDataTypes
{

/// serialize object
Method %GetSerial(force As%Integer = 0) As%String
{
    return##super(force)
}

/// deserialize object
Method %SetSerial(serialized As%String) As%Status
{
    return##super(.serialized)
}

Storage Custom
{
<Type>%Library.CompleteCustomStorage</Type>
}

}
Robert Barbiaux · Sep 23, 2024 go to post

There is a limit to string length, as explained in the documentation.
To convert a binary stream to a base64 encoded character stream, you have to call the function in a loop using a buffer. Due the way the function works, the buffer length must be a  multiple of 57.
Here a sample implementation, from ks.lib.stream.Utils class in the ks-iris-lib package available on Open Exchange.
 

/// encode stream data into encoded using bufferSize (due the implementation of $system.Encryption.Base64Encode, this must be a multiple of 57)ClassMethod Base64Encode(stream As%Stream.Object, Output encoded As%Stream.Object, bufferSize As%Integer = 5700) As%Status
{
  #Dim sc as%Status#Dim ex as%Exception.AbstractException#Dim len As%Integers sc = $$$OKtry {
    throw:((bufferSize#57)'=0) ##class(%Exception.General).%New("buffer size must be a multiple of 57")
    $$$TOE(sc,stream.Rewind())
    s:'$d(encoded) encoded = ##class(%Stream.TmpCharacter).%New()
    s len=bufferSize
    while 'stream.AtEnd {
      $$$TOE(sc,encoded.Write($system.Encryption.Base64Encode(stream.Read(.len),1)))
      s len = bufferSize
    }
  }
  catch (ex) {
    s sc = ex.AsStatus()
  }
  return sc
}
Robert Barbiaux · Aug 7, 2024 go to post

 Using the second approach  : edited down to 39 characters 

ClassMethodascii()
{
 f i=32:1:126w:$t(ascii+1)'[$c(i) *i
}
Robert Barbiaux · Aug 7, 2024 go to post

Yes Enrico, it is my understanding exactly : if your program code (i.e. the lines in the routine generated by the compiler) contains all printable ASCII characters, it should output an empty string. Of course it sounds silly to devise a very clever way to output an empty string, but after all, golf - from a practical point of view - is a silly sport isn't it ? 🤣

Robert Barbiaux · Aug 7, 2024 go to post

Alas, I'm well behind : 54 characters (according to %Dictionary.MethodDefinition Implementation.Size).
Please, enlighten me 😅

ClassMethodascii() [ CodeMode = objectgenerator ]
{
 f i=0,83,0,33,29,2,3:1:94,2d%code.Write($c(i+32))
}