RESTful services
Introduction
Business Entities of the SmartComponent Library can be exposed as RESTful services. RESTful services provide an alternative HTTP and JSON based interface which is very common for providing interfaces to 3rd party applications or mobile applications (the JSDO generic service is specialized to be used by JSDO based clients).
A few useful articles on the subject:
- REST Resource Naming Guide
- 10 Best Practices for Better RESTful API
- RESTful API Design. Best Practices in a Nutshell.
We support exposing Business Entities to provide resource collections and individual resources through simple HTTP URI's. We support GET (read), POST (create), PUT (update), DELETE and PATCH (partial update) methods.
Setting up the PASOE environment
We support exposing Business Entities as RESTful services through a PASOE Web Handler.
Registering the Web Handler
The Web Handler must be registered in the openedge.properties file:
handler28=Consultingwerk.OERA.RestResource.RestEntitiesWebHandler: /Entities
Note, that OpenEdge does not seem to support gaps in the numbering of Web Handlers. The handler28= ... is only supposed to serve as an example.
The entry registers the RestEntitiesWebHandler for the /web/Entities URI. From OpenEdge 11.7 on, we support using any other URI prefix instead of /Entities. OpenEdge 11.6 does only support the /Entities URI prefix.
Initializing the REST Address Service
While starting the PASOE ABL sessions, we need to initialize the RestResourceService (as an Service Instance of the IRestResourceService). This service is responsible to map the request URI to a specific Business Entities and provides the configuration to build a FetchDataRequest. The Service can be loaded through the provided services.xml file:
Consultingwerk/OERA/RestResource/services.xml
The contents of the services.xml file can also be merged with any other services.xml file if preferred. The Service follows the CCS IService Interface and requires the initialize() method to be executed when launched (the Consultingwerk.Framework.ServiceLoader will call the initialize() method). During the start, the RestAddressService will provide logging output like this.
AS-7 RestResour ### Seeking for ISupportsRestAddress implementations AS-7 RestResour ### Registering REST Resource: Consultingwerk.SmartComponentsDemo.OERA.Sports2000.OrderBusinessEntity AS-7 RestResour ### Number of Addresses: 4 AS-7 RestResour ### Registering REST Address: Record : /Orders/{OrderNum} Consultingwerk.SmartComponentsDemo.OERA.Sports2000.OrderBusinessEntity eOrder,eCustomer,eOrderLine,eItem OrderNum eOrder.*,eCustomer.Name,eCustomer.City,eCustomer.Country,eOrderLine.*,eItem.ItemName,eItem.Price AS-7 RestResour ### Registering REST Address: Record : /Orders/{OrderNum}/OrderLines/{LineNum} Consultingwerk.SmartComponentsDemo.OERA.Sports2000.OrderBusinessEntity eOrderLine,eItem LineNum * AS-7 RestResour ### Registering REST Address: Collection: /Customers/{CustNum}/Orders Consultingwerk.SmartComponentsDemo.OERA.Sports2000.OrderBusinessEntity eOrder OrderNum OrderDate,ShipDate,OrderStatus AS-7 RestResour ### Registering REST Address: Collection: /Orders/{OrderNum}/OrderLines Consultingwerk.SmartComponentsDemo.OERA.Sports2000.OrderBusinessEntity eOrderLine LineNum ItemNum,Qty,Price AS-7 RestResour ### --- AS-7 RestResour ### Registering REST Resource: Consultingwerk.SmartComponentsDemo.OERA.Sports2000.SalesRepBusinessEntity AS-7 RestResour ### Number of Addresses: 2 AS-7 RestResour ### Registering REST Address: Record : /Salesreps/{SalesRep} Consultingwerk.SmartComponentsDemo.OERA.Sports2000.SalesRepBusinessEntity eSalesrep SalesRep eSalesRep.* AS-7 RestResour ### Registering REST Address: Collection: /Salesreps Consultingwerk.SmartComponentsDemo.OERA.Sports2000.SalesRepBusinessEntity eSalesRep SalesRep SalesRep,RepName,Region AS-7 RestResour ### ---
The logging output requires the RestResource custom log entry type to be active.
Configuring Business Entities as RESTful resources
There are two requirements for Business Entities as RESTful resources:
ISupportsRestAddress Interface
Business Entities exposed as RESTful resources must implement the Consultingwerk.OERA.RestResource.ISupportsRestAddress interface. The interface requires the GetRestAddress() method. This method is already implemented by the Consultingwerk.OERA.BusinessEntity base class - so that basically the ISupportsRestAddress is a marker interface.
The GetRestAddress() method is used at runtime to register URI's of RESTful resources with Business Entities. The Business Entity returns the RESTful URI's it supports along with the meta data defining the details of the associated requests.
@RestAddress annotations
@RestAddress (type="record", address="/Customers/~{CustNum}", tables="eCustomer,eSalesrep", id="CustNum", fields="eCustomer.*,eSalesrep.RepName,eSalesrep.Region", canRead="true", canUpdate="true", canDelete="true", childAddresses="eSalesrep:/Salesreps/~{SalesRep}", links="orders:/Customers/~{CustNum}/Orders,salesrep:/Salesreps/~{SalesRep}"). @RestAddress (type="collection", address="/Customers", tables="eCustomer", id="CustNum", fields="Name,City,Country", canCreate="true", links="orders:/Customers/~{CustNum}/Orders,salesrep:/Salesreps/~{SalesRep}").
Business Entities define the details of the RESTful access through annotations (see The Annotation based Type Descriptor).
Overview of Annotation Parameters
The attributes for the RestAddress annotation are listed in the table below.
Parameter Name | Description |
---|---|
type | The type of the request, either "record" for a single record, or "collection" for list of records |
address | The URI template. "/Customers/{CustNum}" means that the resources can be accessed using the resulting URI like http://localhost:8820/web/Entities/Customers/1 - The {CustNum} placeholder represents a field used in the resulting QueryString (FOR EACH eCustomer WHERE eCustomer.CustNum = 1). There can be multiple placeholders in a single URI template (e.g. /Customers/{CustNum}/Orders/{OrderNum}/OrderLines/{OrderLine} Collection or singleton (a single record resource that does not require any further selection criteria) may not require any value placeholder at all. |
tables | The comma delimited list of tabels requested from the Business Entity (FetchDataRequest:Tables property) to serve the request |
id | The comma delimited list of fields used to return the ID of a resource |
fields | The comma delimited list of fields returned to the caller (by default), possible values may be field names, tablename.fieldname's or tablename.* |
canRead | true/false, logical value determing of the URI supports read (GET) requests |
canUpdate | true/false, logical value determing of the URI supports update requests (PUT and PATCH) - supported for individual records only |
canCreate | true/false, logical value determing of the URI supports create requests (POST) - supported for collections only |
canDelete | true/false, logical value determing of the URI supports delete (DELETE) requests - supported for collections and individual records |
childAddresses | links to resource URI's for the child records returned by the request, comma delimited list, entries are in the form of {tablename}:{uri pattern}, e.g. eSalesrep:/Salesreps/~{SalesRep} |
links | HATEOAS style links (to other business entities) in the form of {rel}:{uri pattern}, e.g.orders:/Customers/~{CustNum}/Orders,salesrep:/Salesreps/~{SalesRep} |
tags | Comma-separated list of tags used for grouping endpoints in Swagger documentation. |
methodDescriptionGet | (optional) A description for Swagger documentation for GET operations for this address. |
methodDescriptionPut | (optional) A description for Swagger documentation for PUT operations for this address. |
methodDescriptionPost | (optional) A description for Swagger documentation for POST operations for this address. |
methodDescriptionPatch | (optional) A description for Swagger documentation for PATCH operations for this address. |
methodDescriptionDelete | (optional) A description for Swagger documentation for DELETE operations for this address. |
A Business Entity may also use an ApiDoc annotation to provide additional information for Swagger documentation.
Query String Parameters
RESTful requests for collections can be filtered using query string parameters.
Parameter Name | Description |
---|---|
fields | comma delimited list of field names to be returned |
sort | The sort criteria as a comma delimited list in the form of +fieldname or -fieldname, e.g. sort=+country,-city |
limit | The maximum number of records returned from a collection |
offset | The first record to be returned, allows paging in combination with the limit parameter, e.g. limit=20&offset=20 |
{fieldname} | filter criteria, e.g. salesrep=BBB&creditlimit>=10000 - supported operators are =, >=, <=, >, <>, < |
Supported Operators
The following query operators are supported by the RESTful interface:
- =, [=], [EQ]
- <>, [<>], [NE]
- >, [>], [GT]
- <, [<], [LT]
- >=, [>=], [GE]
- <=, [<=], [LE]
- [BEGINS]
- [CONTAINS]
- [MATCHES]
Invoking Business Entity and Business Tasks Methods through the RESTful interface
See Support for RESTful invocation of Business Task and Business Entity Methods.
Samples
Returns the first three customers by CreditLimit descending. Returns also links to orders of that customer and the salesrep.
Annotation:
@RestAddress (type="collection", address="/Customers", tables="eCustomer", id="CustNum", fields="Name,City,Country", canCreate="true", links="orders:/Customers/~{CustNum}/Orders,salesrep:/Salesreps/~{SalesRep}").
Response:
[ { "id": 242770, "url": "http://localhost:8820/web/Entities/Customers/242770", "Name": "Mikro Designs", "CreditLimit": 83500.0, "links": [ { "rel": "orders", "href": "http://localhost:8820/web/Entities/Customers/242770/Orders" }, { "rel": "salesrep", "href": "http://localhost:8820/web/Entities/Salesreps/GPE" } ] }, { "id": 1242770, "url": "http://localhost:8820/web/Entities/Customers/1242770", "Name": "Mikro Designs", "CreditLimit": 83500.0, "links": [ { "rel": "orders", "href": "http://localhost:8820/web/Entities/Customers/1242770/Orders" }, { "rel": "salesrep", "href": "http://localhost:8820/web/Entities/Salesreps/GPE" } ] }, { "id": 46030, "url": "http://localhost:8820/web/Entities/Customers/46030", "Name": "Lazysize", "CreditLimit": 58000.0, "links": [ { "rel": "orders", "href": "http://localhost:8820/web/Entities/Customers/46030/Orders" }, { "rel": "salesrep", "href": "http://localhost:8820/web/Entities/Salesreps/JAL" } ] } ]
http://localhost:8820/web/Entities/Customers/3
Returns the customer with the CustNum of 3. Returns links to orders and salesrep (links property) as well as a preview of the eSalesrep record returned by the Business Entity (childAddresses property)
Annotation:
@RestAddress (type="record", address="/Customers/~{CustNum}", tables="eCustomer,eSalesrep", id="CustNum", fields="eCustomer.*,eSalesrep.RepName,eSalesrep.Region", canRead="true", canUpdate="true", canDelete="true", childAddresses="eSalesrep:/Salesreps/~{SalesRep}", links="orders:/Customers/~{CustNum}/Orders,salesrep:/Salesreps/~{SalesRep}").
Response:
{ "id": 3, "url": "http://localhost:8820/web/Entities/Customers/3", "CustNum": 3, "Country": "DE", "Name": "Hoops", "Address": "Suite 415", "Address2": "werwe", "City": "", "State": "GA", "PostalCode": "02112", "Contact": "Michael Traitser", "Phone": "(617) 355-1557", "SalesRep": "HXM", "CreditLimit": 75000.0, "Balance": 1199.95, "Terms": "Net30", "Discount": 10, "Comments": "This customer is now OFF credit hold.", "Fax": "", "EmailAddress": "info@hoops.com", "Flags": "C", "eSalesrep": { "url": "http://localhost:8820/web/Entities/Salesreps/HXM", "RepName": "Harry Munvig", "Region": "West" }, "links": [ { "rel": "orders", "href": "http://localhost:8820/web/Entities/Customers/3/Orders" }, { "rel": "salesrep", "href": "http://localhost:8820/web/Entities/Salesreps/HXM" } ] }
http://localhost:8820/web/Entities/Customers/3/Orders?OrderStatus=Partially%20Shipped
Returns the "Partially Shipped" Orders of Customer number 3. The request is served by the Orders Business Entity, the Customer Business Entity is not involved in the request.
Annotation:
@RestAddress (type="collection", address="/Customers/~{CustNum}/Orders", tables="eOrder", id="OrderNum", fields="OrderDate,ShipDate,OrderStatus", canCreate="true").
Response:
[ { "id": 160, "url": "http://localhost:8820/web/Entities/Customers/3/Orders/160", "OrderDate": "2008-12-02", "ShipDate": "2009-05-23", "OrderStatus": "Partially Shipped" } ]