INFO: This blog is part of a tutorial series for the standalone SAP Graph service on SAP BTP. Recently, SAP released a version of Graph in SAP Integration Suite with better user experience and functional enhancements (see this blog for details:
https://blogs.sap.com/2023/02/22/announcing-graph-in-sap-integration-suite).
What you will learn: use SAP Graph with OData to fetch data and explore the structure of the unified data model exposed by SAP Graph.
Hello!
In this fourth part of the tutorial on SAP Graph - the unified API for SAP’s Integrated Intelligent Suite - we will focus on the data protocol that SAP Graph uses, OData, to explore the structure of the unified data model exposed by SAP Graph. Please refer to the
information map to see all parts of this tutorial series.
OData - the protocol used by SAP Graph
SAP Graph is built on open standards and technologies. Clients communicate with SAP Graph using the
Open Data Protocol (OData). This is a
standardized RESTful HTTP protocol with defined semantics that help promote interoperability between services. SAP Graph follows the
OData V4 protocol.
OData is centered around resources (data entities), which are identified by URLs. In SAP Graph, there are for example the
Customer, Product or SalesQuote entities. The structure of these entities is defined in an
Entity Data Model (EDM) which can be inspected by clients. OData provides many operations to filter or search in entity collections, to navigate between associated entities and to adapt the response format.
With OData, SAP Graph exposes a single unified API with defined semantics and a unified data model, for all SAP-managed data.
We will now look at examples on how to formulate OData requests for SAP Graph.
Tutorial setup
To keep things simple, we will be using the SAP Graph sandbox endpoint. All you need to use this endpoint is your favorite HTTP tool, for example Postman, and your API key from SAP API Hub. To retrieve your API key, log into
SAP API Business Hub, go to
settings and copy your API key from the
Show API Key button.
To make requests against the SAP Graph sandbox, add your API key as an HTTP header in your requests:
apiKey: <your API key>
. Then you can use the SAP Graph sandbox through the following endpoint:
Note: The API key is just used for the purposes of this sandbox endpoint and not relevant in the context of OData requests or productive SAP Graph instances.
Also note, that the URL of the SAP Graph sandbox differs from the productive URL of SAP Graph: https://eu30.graph.sap/api
.
Exploring the OData data model
OData defines an
Entity Data Model that describes the structure of all known entities. This helps developers and clients alike to reason about the available entities and their structure. The data model and further metadata can be inspected through a special
$metadata endpoint. SAP Graph makes metadata available for each namespace, such as
sap.graph for the unified SAP Graph data model.
To retrieve the metadata of the unified data model in SAP Graph, make the following request, which will return an EDMX specification (an XML dialect for describing OData
Entity Data Models😞
In the response, we can inspect the structure of all the entities (and sub-entities) that are described as
EntityType entries as well as other metadata. Let's have a closer look at the definition of the
SalesQuote entity type in this extract from the sales domain metadata:
...
<EntityType Name="SalesQuote">
<Key>
<PropertyRef Name="id"/>
</Key>
<Property Name="id" Type="Edm.String" MaxLength="82" Nullable="false"/>
<Property Name="displayId" Type="Edm.String"/>
<Property Name="netAmount" Type="Edm.Decimal" Scale="6" Precision="22"/>
<Property Name="pricingDate" Type="Edm.DateTimeOffset"/>
<Property Name="netAmountCurrency" Type="Edm.String" MaxLength="5"/>
<NavigationProperty Name="_netAmountCurrency" Type="sap.graph.Currency"/>
<NavigationProperty Name="items" Type="Collection(sap.graph.SalesQuote_items)" Partner="up_" ContainsTarget="true"/>
<Property Name="soldToParty" Type="Edm.String" MaxLength="10"/>
<NavigationProperty Name="_soldToParty" Type="sap.graph.Customer"/>
<NavigationProperty Name="_cxsales" Type="sap.cxsales.SalesQuoteCollection"/>
...
</EntityType>
...
We can see that a
SalesQuote has several properties of different types, such as
String or
Decimal, but also more complex structured types like the
items property, which is a collection of several
sap.graph.SalesQuote_items, that are also defined in the same data model. Furthermore, we see that the
id property is referenced as key for this entity type.
Navigation Properties allow for navigation to a related entity. The
items property is a navigation property because the
sap.graph.SalesQuote_items is modeled as a separate entity type in the metadata.
Let's look at an example. Add the following query path to the sandbox endpoint to make the request. This will retrieve one
SalesQuote entity (here we add
$top=1 to limit the result set to one entity):
/sap.graph/SalesQuote?$top=1
From inspecting the metadata, we have learned that
SalesQuotes have items, however when comparing with the response from the example, no
items are included in the response.
The reason for this is that OData has a mechanism for the client to control which navigation properties are included as part of the response. This is called expanding a property. By default, SAP Graph returns responses non-expanded if not specified by the client. In addition to expanding the response, OData also allows for restricting it to requested values only. We will have a look at these mechanisms next.
Expanding and restricting the response
OData allows clients to adapt the response format in two ways. Clients can restrict the response to a set of specified properties via
$select. And they can expand the response by including referenced entities inline as part of the response via
$expand.
If we only require the
netAmount of a
SalesQuote, we can restrict the response by adding it to the
$select query parameter:
/sap.graph/SalesQuote?$top=1&$select=netAmount
This will return only the
netAmount property along with the
id, as it is the key and therefore always included.
If we also require the
items of a
SalesQuote, we need to expand the response (as it is a navigation property) by adding it to the
$expand query parameter:
/sap.graph/SalesQuote?$top=1&$select=netAmount&$expand=items
This will result in the
items array being returned as part of the
SalesQuote response as illustrated here:
If we have a look at a response snippet, this is what is being returned:
{
"@odata.context": "$metadata#SalesQuote(netAmount,id,items())",
"value": [
{
"id": "cxsales~1",
"netAmount": 890,
"items": [
{
"itemId": "10",
"parentItemId": "",
"alternativeToItemId": "",
"itemCategory": "AGN",
"itemText": "Green Emission Calculator",
"product": "P300100",
"soldToPartyProductId": "",
"quantity": 1,
"quantityUnit": "EA",
"grossWeight": 0,
"grossWeightUnit": "",
"netWeight": 0,
"netWeightUnit": "",
"volume": 0,
"volumeUnit": "",
"plant": "",
"netAmount": 890,
"netAmountCurrency": "USD",
"pricingProduct": "",
"incotermsClassification": "",
"incotermsLocation": "",
"processingStatus": "1",
"cancellationReason": ""
}
]
}
]
}
Note that we do not have to specify expanded properties explicitly as part of $select.
We see that the
item in the example above has a property
product that references an
id. When we inspect the metadata of the items by searching for the
EntityType SalesQuote_items. We see that the
product property represents the referenced product with a string value and the
_product navigation property references the connected
sap.graph.Product entity.
...
<EntityType Name="SalesQuote_items">
<Key>
<PropertyRef Name="itemId"/>
</Key>
<Property Name="itemId" Type="Edm.String" MaxLength="10" Nullable="false"/>
<Property Name="itemText" Type="Edm.String"/>
<Property Name="product" Type="Edm.String"/>
<NavigationProperty Name="_product" Type="sap.graph.Product"/>
...
</EntityType>
...
To also include this
_product as part of the response, we can expand it inside the already expanded
items: a
nested expand.
/sap.graph/SalesQuote?$top=1&$select=netAmount&$expand=items($expand=_product)
Which will include the
_product response inline in the
item of the
SalesQuote response.
If we also want to adapt the format of the expanded product we can nest a
$select query parameter in parentheses after the product. To restrict the nested product to the
displayId, for example:
/sap.graph/SalesQuote?$top=1&$select=netAmount&$expand=items($expand=_product($select=displayId))
If we
also want to
select more properties, such as
netAmountCurrency, we can just add them to the
$select query parameter targeting the
SalesQuote, separating by commas:
/sap.graph/SalesQuote?$top=1&$select=netAmount,netAmountCurrency&$expand=items($expand=_product($select=displayId))
The response for the
SalesQuote with included
netAmountCurrency, items and
_product now looks like this:
{
"@odata.context": "$metadata#SalesQuote(netAmount,netAmountCurrency,id,items(_product(displayId,id)))",
"value": [
{
"id": "cxsales~1",
"netAmount": 890,
"netAmountCurrency": "USD",
"items": [
{
"itemId": "10",
"parentItemId": "",
"alternativeToItemId": "",
"itemCategory": "AGN",
"itemText": "Green Emission Calculator",
"product": "P300100",
"_product": {
"id": "cxsales~P300100",
"displayId": null
},
"soldToPartyProductId": "",
"quantity": 1,
"quantityUnit": "EA",
"grossWeight": 0,
"grossWeightUnit": "",
"netWeight": 0,
"netWeightUnit": "",
"volume": 0,
"volumeUnit": "",
"plant": "",
"netAmount": 890,
"netAmountCurrency": "USD",
"pricingProduct": "",
"incotermsClassification": "",
"incotermsLocation": "",
"processingStatus": "1",
"cancellationReason": ""
}
]
}
]
}
Working with collections: filtering, ordering and pagination
When working with collections of entities, we typically want to filter the entities by some criteria or arrange them in a specific order. In OData this is supported via the
$filter and
$order query parameters. With
$top and
$skip we can additionally define a sliding window over the result to implement pagination.
To continue with our example above, let's remove the
$top=1 we added initially to retrieve a collection of
SalesQuotes. Say we want to retrieve all
SalesQuotes with a value greater than 100 U.S. Dollars.
The value condition can be formulated with the greater equal operator:
netAmount ge 100. For the currency condition we need to compare it with a string (enclosed in single quotes) for equality:
netAmountCurrency eq 'USD'. We then combine these conditions with a logical
and expression and add it to the example query below. We also define an ordering on the
netAmount property with the
$orderby query parameter, which is ascending by default.
Note: OData operators are lower-case and space-separated. This requires URL encoding. If you are using Postman for this tutorial you don't need to worry as Postman automatically applies URL encoding.
/sap.graph/SalesQuote?$select=netAmount,netAmountCurrency&$expand=items($expand=_product($select=displayId))&$filter=netAmount ge 100 and netAmountCurrency eq 'USD'&$orderby=netAmount
Note: OData only supports writing filter expressions using a small vocabulary of filter operators. See here for a full list.
As a last step, we will now define a paging-window over all results. This can be achieved with the query parameters
$top, which specifies how many result entities should be returned: the page size, and
$skip, which defines how many result entities should be skipped from the beginning of the ordering. Hence, we describe the page size with
$top and index the single pages with multiples of the page size in
$skip. For the second page of five entities each, this would translate to our example as follows:
/sap.graph/SalesQuote?$select=netAmount,netAmountCurrency&$expand=items($expand=_product($select=displayId))&$filter=netAmount ge 100 and netAmountCurrency eq 'USD'&$orderby=netAmount&$top=5&$skip=5
Summary
In this tutorial we had a look at OData, the data protocol used by SAP Graph. We covered all the features that you as a developer working with SAP Graph need to know, like how to:
- formulate complex queries
- inspect the metadata and structure of the unified data model
- expand and restrict responses
- work with collections
OData itself offers much more than what we showed in this tutorial. You can read more about it in the
standard.
With SAP Graph, you as a developer now have one data protocol you can use together with one API to retrieve data in one unified format, no matter the source system.
Florian Moritz, User Experience Developer – SAP Graph
Visit the SAP Graph website at
https://navigator.graph.sap
Contact us at
sap.graph@sap.com