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:
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.

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):
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
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:
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
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.