microservices, consuming LANSA created restful

This forum allows developers to post programming tips and coding techniques that may be useful to other Visual LANSA developers. The information contained in this forum has not been validated by LANSA and, as such, LANSA cannot guarantee the accuracy of the information.
Post Reply
User avatar
Dino
Posts: 402
Joined: Fri Jul 19, 2019 7:49 am
Location: Robbinsville, NC
Contact:

microservices, consuming LANSA created restful

Post by Dino » Wed Jun 01, 2022 8:32 am

Hi

Some presumptions before to begin this post:

1) You know you can use the assistants available in Visual LANSA, under Home, "Build a Mobile Web Application" to generate for example a basic sample application with session handling that between other things can do the maintenance to your tables and it will create the web page, views, dialogs and server modules that will access the tables. The server modules created here access the tables directly and perform the select, fetch, update, insert, etc.

2) You know you can Publish a server module as Restful service, or create a new one, using for example the assistant available in Visual LANSA, under File, New, Server Module, Web API, and that will expose some operations, like GetDepartment, UpdateDepartment, DeleteDepartment and on the other hand, CreateDepartment, GetDepartments, which internally perform the equivalent to a fetch, update, delete, insert and select to the table and return that information as a json.

3) Microservices..., long subject and discussion, but in brief, you want to expose your data publishing restful services and your presentation layer should be consuming those restful, not accessing the table directly. While this is not for everybody and not for every case, it allows to centralize and standardized the operations over the data in one place.

Now, let's begin.

Let's say I create a new restful server module to publish DEPTAB, and my server module is called TEST0516PD, the object is Department, the table used is DEPTAB:
restful531_01.png
restful531_01.png (20.29 KiB) Viewed 16898 times
you compile it, notice a few things.

CreateDepartment and GetDepartments operations, are under /Departments and use that Path. GetDepartments is using the Verb Get, CreateDeparments (if you expand it) is using the verb Post.

In the same way, GetDepartment is using the verb Get, UpdateDepartment is using the verb Put, DeleteDepartment is using the verb Delete, and all those three are under /Department/{Deparment ID} path.

Image

Now you create a sample application, which includes a maintenance over the Deptab table, the code of the server module created will look something like this (a few lines have been removed just for clarity):
restful531_02.png
restful531_02.png (59.35 KiB) Viewed 16898 times
full code of the original server module created by the assistant here:

Code: Select all

Begin_Com Role(*EXTENDS #PRIM_SRVM) SessionIdentifier('O16SESSION')
Define Field(#O16ListCount) Type(*Int)
Def_List Name(#List) Fields(#DEPTMENT #DEPTDESC) Counter(#O16ListCount) Type(*Working) Entrys(*Max)

Group_By Name(#Fields) Fields(#DEPTMENT #DEPTDESC)

Srvroutine Name(Find) Session(*REQUIRED)
Field_Map For(*Input) Field(#STD_STRNG) Parameter_Name(Filter)
List_Map For(*Output) List(#List)

#STD_STRNG := #STD_STRNG.UpperCase.Trim
Select Fields(#List) From_File(DEPTAB) Io_Error(*Next) Val_Error(*Next)
If ((#STD_STRNG = *BLANKS) *OrIf (#DEPTDESC.UpperCase.Contains( #STD_STRNG )))
Add_Entry To_List(#List)
Endif
Endselect
Endroutine

Srvroutine Name(Read) Session(*REQUIRED)
Field_Map For(*Input) Field(#DEPTMENT)
Group_Map For(*Output) Group(#Fields)

Fetch Fields(#Fields) From_File(DEPTAB) With_Key(#DEPTMENT) Io_Error(*Next) Val_Error(*Next)
Endroutine

Srvroutine Name(Save) Session(*REQUIRED)
Group_Map For(*Input) Group(#Fields)
Field_Map For(*Output) Field(#STD_CODE) Parameter_Name(ReturnCode)

Update Fields(#Fields) In_File(DEPTAB) With_Key(#DEPTMENT) Io_Error(*Next) Val_Error(*Next)
If_Status Is(*Norecord)
Insert Fields(#Fields) To_File(DEPTAB) Io_Error(*Next) Val_Error(*Next)
Endif
If_Status Is(*Okay)
#STD_CODE := "OK"
Else
#STD_CODE := "ER"
Endif
Endroutine

Srvroutine Name(Delete) Session(*REQUIRED)
Field_Map For(*Input) Field(#DEPTMENT)
Delete From_File(DEPTAB) With_Key(#DEPTMENT) Io_Error(*Next) Val_Error(*Next)
Endroutine
End_Com
basically it have a Find routine that retrieves a list of records from deptab and apply a filter, then a Read routine that perform a Fetch, a Save routine that performs an update and if fails, performs an insert, plus a delete routine.

I have modified that code, to remove all access to the tables and instead to consume the service, let's call this version one, there are many different ways to code as you know:
restful531_03.png
restful531_03.png (66.13 KiB) Viewed 16898 times
full code of the server module here:

Code: Select all

Begin_Com Role(*EXTENDS #PRIM_SRVM) SessionIdentifier('O16SESSION')
Define_Com Class(#XPRIM_HttpRequest) Name(#Request)
Define_Com Class(#XPRIM_UriBuilder) Name(#Url)
Define_Com Class(#XPRIM_JsonObject) Name(#JsonObject)
Define_Com Class(#XPRIM_RandomAccessJsonReader) Name(#Reader)
Define_Com Class(#XPRIM_ErrorInfo) Name(#ErrorInfo)
Define_Com Class(#PRIM_BOLN) Name(#Found)

Define Field(#O16ListCount) Type(*Int)
Def_List Name(#List) Fields(#DEPTMENT #DEPTDESC #xAttachmentHasAttachments #xNoteHasNotes) Counter(#O16ListCount) Type(*Working) Entrys(*Max)
Group_By Name(#Fields) Fields(#DEPTMENT #DEPTDESC)

Srvroutine Name(Find) Session(*REQUIRED)
Field_Map For(*Input) Field(#STD_STRNG) Parameter_Name(Filter)
List_Map For(*Output) List(#List)
Field_Map For(*Output) Field(#STD_CODE) Parameter_Name(ReturnCode)

#STD_STRNG := #STD_STRNG.UpperCase.Trim

#Url.SetScheme( 'http' )
#Url.SetHost( 'localhost:8080' )
#Url.SetPath( ("/" + #partition + "/TEST0516PD/Departments") )

#Request.Content.ContentInfo.MediaType := 'application/json'
#Request.DoGet Url(#Url)

If (#Request.Response.IsSuccessfulRequest)
#STD_CODE := #Request.Response.IsSuccessfulRequest

* Successful Request
#VF_ELTXTX := #Request.Response.AsString.AsNativeString
#Reader.SetSourceString String(#VF_ELTXTX) ErrorInfo(#ErrorInfo)
#Reader.BeginArrayWithPath Path('/') Found(#Found)
#STD_COUNT := #Reader.GetChildCount
Begin_Loop Using(#STD_NUM) To(#STD_COUNT)
#Reader.BeginObjectAtIndex Index(#STD_NUM) Found(#Found)

If (#Found)
#DEPTMENT := #Reader.ReadStringWithName( 'DEPTMENT' ).AsNativeString
#DEPTDESC := #Reader.ReadStringWithName( 'DEPTDESC' ).AsNativeString

If ((#STD_STRNG = *BLANKS) *OrIf (#DEPTDESC.UpperCase.Contains( #STD_STRNG )))
Add_Entry To_List(#List)
Endif
#Reader.EndObject
Endif
End_Loop
#Reader.EndArray
Else
#STD_CODE := #Request.Response.ErrorCode.AsNativeString + ' ' + #Request.Response.ErrorMessage.AsNativeString
Endif
Endroutine

Srvroutine Name(Read) Session(*REQUIRED)
Field_Map For(*Input) Field(#DEPTMENT)
Group_Map For(*Output) Group(#Fields)
Field_Map For(*Output) Field(#STD_CODE) Parameter_Name(ReturnCode)

#Url.SetScheme( 'http' )
#Url.SetHost( 'localhost:8080' )
#Url.SetPath( ("/" + #partition + "/TEST0516PD/Departments/" + #DEPTMENT) )

#Request.DoGet Url(#Url)
* Check if the server returned a response
If (#Request.Response.IsSuccessfulRequest)
If (#Request.Response.IsSuccessHttpStatusCode)
#Reader.SetSourceHttpResponse HttpResponse(#Request.Response) ErrorInfo(#ErrorInfo)
#DEPTMENT := #Reader.ReadStringWithName( 'DEPTMENT' ).AsNativeString
#DEPTDESC := #Reader.ReadStringWithName( 'DEPTDESC' ).AsNativeString
#STD_CODE := "OK"
Else
* Failed HTTP Status Code
#STD_CODE := #Request.Response.ErrorCode.AsNativeString
Endif
Else
* Failed HTTP Request
#STD_CODE := #Request.Response.HttpStatusCode.AsString
Endif
Endroutine

Srvroutine Name(Save) Session(*REQUIRED)
Group_Map For(*Input) Group(#Fields)
Field_Map For(*Output) Field(#STD_CODE) Parameter_Name(ReturnCode)

#Url.SetScheme( 'http' )
#Url.SetHost( 'localhost:8080' )
#Url.SetPath( ("/" + #partition + "/TEST0516PD/Departments/" + #DEPTMENT) )

#JsonObject.InsertString Key("DEPTMENT") String(#DEPTMENT)
#JsonObject.InsertString Key("DEPTDESC") String(#DEPTDESC)

#Request.Content.AddJsonObject( #JsonObject )

#Request.DoPut Url(#Url)
*
If (#Request.Response.IsSuccessfulRequest)
If (#Request.Response.IsSuccessHttpStatusCode)
#STD_CODE := "OK"
Else
* Original logic been replaced is to update, if fails, then insert, so lets do that.
#Url.Clear
#Url.SetScheme( 'http' )
#Url.SetHost( 'localhost:8080' )
#Url.SetPath( ("/" + #partition + "/TEST0516PD/Departments") )

#Request.Clear
#JsonObject.InsertString( 'DEPTMENT' #DEPTMENT )
#JsonObject.InsertString( 'DEPTDESC' #DEPTDESC )
#Request.Content.AddJsonObject( #JsonObject )

#Request.DoPost Url(#Url)

If (#Request.Response.IsSuccessfulRequest)
If (#Request.Response.IsSuccessHttpStatusCode)
#STD_CODE := "OK"
Else
* Failed HTTP Status Code
#STD_CODE := #Request.Response.ErrorCode.AsNativeString
Endif
Else
* Failed HTTP Request
#STD_CODE := #Request.Response.HttpStatusCode.AsString
Endif
Endif
Endif
Endroutine

Srvroutine Name(Delete) Session(*REQUIRED)
Field_Map For(*Input) Field(#DEPTMENT)

#Url.SetScheme( 'http' )
#Url.SetHost( 'localhost:8080' )
#Url.SetPath( ("/" + #partition + "/TEST0516PD/Departments/" + #DEPTMENT) )

#JsonObject.InsertString Key("DEPTMENT") String(#DEPTMENT)
#Request.Content.AddJsonObject( #JsonObject )

#Request.DoDelete Url(#Url)

If (#Request.Response.IsSuccessfulRequest)
If (#Request.Response.IsSuccessHttpStatusCode)
#STD_CODE := "OK"
Else
* Failed HTTP Status Code
#STD_CODE := #Request.Response.ErrorCode.AsNativeString
Endif
Else
* Failed HTTP Request
#STD_CODE := #Request.Response.HttpStatusCode.AsString
Endif
Endroutine
End_Com
Notice a few things in this code:

1. My computer is using port 8080, I am working in localhost. For configuration with the IBM there is another post in the forum that deals with that.
2. My LANSA installation requires the partition as part of the call to the published server module. Some installations do not need the partition and you can remove it.
3. for simplicity, I used SetSourceString and moved the result to a large string of 64k, so in this code I added that limit. there are other ways to handle this if needed, other posts in the forum deal with that.
4. DEPTAB is an RDML table, when you create the server module, the "tags" used in the json text, are the short name of the field. if you use a table with long file names, rdmlx table, most likely your tag will be different to the field name, adjust accordingly.
5. You can capture more information on the errors, I just restricted my example to what the current server module does, so its an easy one to one replacement.
6. my fields are both strings. There are more json intrinsics to handle numbers, booleans, etc.

atostaine
Posts: 657
Joined: Wed Jan 20, 2016 7:38 am

Re: microservices, consuming LANSA created restful

Post by atostaine » Thu Jun 09, 2022 2:44 am

Just saw this. Cool idea. Thanks
Art Tostaine

kno_dk
Posts: 171
Joined: Tue Feb 23, 2016 12:00 am

Re: microservices, consuming LANSA created restful

Post by kno_dk » Fri Jun 10, 2022 8:33 pm

Hi

I have tried your exsample - first part, but i get an 404 error when i try the url in a browser. A normal vl-web is working correct. What am i missing?

User avatar
Dino
Posts: 402
Joined: Fri Jul 19, 2019 7:49 am
Location: Robbinsville, NC
Contact:

Re: microservices, consuming LANSA created restful

Post by Dino » Sun Jun 12, 2022 11:04 pm

Hi,

Once you compile the server module Web API to publish your service, a few entries are also automatically created in a file called webmodules.conf in the folder c:\Program Files (x86)\lansa\run\conf

For example, I created a new server module in this way, called it TEST612RA and the Object was Department, then I went to that file and I have a few entries like this:
test01.png
test01.png (26.27 KiB) Viewed 16802 times
where you can see the method (Get, Put, etc) , the module it will call (the server module), the partition(rho) and the aliases or the route that it will use. You will notice in my case that the aliases include the partition and also that the "template" added an "S" to Department, Departments it is then, so I can test this now calling:

http://localhost:8080/rho/TEST612RA/Departments/ADM
test02.png
test02.png (7.13 KiB) Viewed 16802 times
if you have any error or missing character in the url, you will typically get that 404.

kno_dk
Posts: 171
Joined: Tue Feb 23, 2016 12:00 am

Re: microservices, consuming LANSA created restful

Post by kno_dk » Mon Jun 13, 2022 10:58 pm

Hi

It works on my PC, it was the url with the partition that was wrong.

I have now tried it on my IBM I - but with 404 again. Is there anything in the Http conf I should be aware of?
How should the url be?

/klaus

User avatar
Dino
Posts: 402
Joined: Fri Jul 19, 2019 7:49 am
Location: Robbinsville, NC
Contact:

Re: microservices, consuming LANSA created restful

Post by Dino » Mon Jun 13, 2022 11:30 pm

Hi Klaus

For IBM and restful configuration, you can use this other post guidance:
viewtopic.php?f=3&t=2356&p=7049

kno_dk
Posts: 171
Joined: Tue Feb 23, 2016 12:00 am

Re: microservices, consuming LANSA created restful

Post by kno_dk » Tue Jun 14, 2022 11:50 pm

hi

Had hoped there was a nativ IBM i solution.

Will it be possible to run it nativ on the IBM I so we not have to setup an MS IIS server?

/klaus

User avatar
Dino
Posts: 402
Joined: Fri Jul 19, 2019 7:49 am
Location: Robbinsville, NC
Contact:

Re: microservices, consuming LANSA created restful

Post by Dino » Wed Jun 15, 2022 12:26 am

so far I know at epc150040, not, I'll suggest to create a case with support for a enhancement request to have your voice heard as that is part of what is needed for an enhancement request to see the light.

User avatar
Dino
Posts: 402
Joined: Fri Jul 19, 2019 7:49 am
Location: Robbinsville, NC
Contact:

Re: microservices, consuming LANSA created restful

Post by Dino » Mon Jun 27, 2022 11:59 pm

Finding that I need to create server modules like this, not once, but several times, it makes sense to make a TEMPLATE (take the program you create, change the variable things for variables, make questions to fill those variables and voala) from it.

Attached here.
QuickExport20220627094457_RORESTSMC.zip
(7.44 KiB) Downloaded 658 times
Basically the template ask for the URL details to call the JSON service, then, as a way to find the attributes used by it, ask for a reference table. The
final code itself don't access the table as you saw at the beginning of this post, it just makes easier to create a template with less questions. AS-IS, etc.

To use this template, just create a new server module, or empty the code of the one you want to replace and call it by clicking the template icon in the LANSA editor toolbar, select the template (RORESTSMC), answer the questions and the code will be created for you.
templateuse1.png
templateuse1.png (55.35 KiB) Viewed 16669 times
notice that this version of the template, assumes just one key in the file and that the tags used in the service, were the field identifiers. you need to adjust as needed or continue to enhance the template.

Templates are one great things in LANSA that you can use to avoid repetitive manual tasks like when you have to copy a program to create a variation of it with different tables, fields, conditions, and reducing errors or mistakes. you just identify once the variable things, make questions to get the answers you need to make a variation of it, and use those answers in the template code. You can have templates that create a full program like the attached here or just sections of code. More details here:

https://docs.lansa.com/15/en/lansa015/i ... ef94377512

Post Reply