diff --git a/tutorials/odata-01-intro-origins/association-definition.png b/tutorials/odata-01-intro-origins/association-definition.png deleted file mode 100644 index 47d2db8e0e..0000000000 Binary files a/tutorials/odata-01-intro-origins/association-definition.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/associationset-definition.png b/tutorials/odata-01-intro-origins/associationset-definition.png deleted file mode 100644 index 73322a02e4..0000000000 Binary files a/tutorials/odata-01-intro-origins/associationset-definition.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/entitycontainer.png b/tutorials/odata-01-intro-origins/entitycontainer.png deleted file mode 100644 index 6fa6fe2ca6..0000000000 Binary files a/tutorials/odata-01-intro-origins/entitycontainer.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/entry-links.png b/tutorials/odata-01-intro-origins/entry-links.png deleted file mode 100644 index 9e39a4e283..0000000000 Binary files a/tutorials/odata-01-intro-origins/entry-links.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/northwind-metadata.png b/tutorials/odata-01-intro-origins/northwind-metadata.png deleted file mode 100644 index f5013df709..0000000000 Binary files a/tutorials/odata-01-intro-origins/northwind-metadata.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/northwind-v3-service-document.png b/tutorials/odata-01-intro-origins/northwind-v3-service-document.png deleted file mode 100644 index e223163668..0000000000 Binary files a/tutorials/odata-01-intro-origins/northwind-v3-service-document.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/oasis-odata-services.png b/tutorials/odata-01-intro-origins/oasis-odata-services.png deleted file mode 100644 index 53d67a198c..0000000000 Binary files a/tutorials/odata-01-intro-origins/oasis-odata-services.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/odata-01-intro-origins.md b/tutorials/odata-01-intro-origins/odata-01-intro-origins.md deleted file mode 100644 index ba36a22458..0000000000 --- a/tutorials/odata-01-intro-origins/odata-01-intro-origins.md +++ /dev/null @@ -1,267 +0,0 @@ ---- -parser: v2 -author_name: DJ Adams -author_profile: https://github.com/qmacro -auto_validation: false -primary_tag: software-product>sap-business-technology-platform -tags: [ software-product>sap-business-technology-platform, topic>cloud, programming-tool>odata, tutorial>beginner ] -time: 15 ---- - -# Learn about OData Fundamentals - Discover OData's origins and learn about the fundamentals of OData by exploring a public OData service. - -## You will learn - - Where OData came from and why it's designed the way it is - - What the standard OData operations are and how they relate to HTTP - - What the public Northwind OData service has to offer - - What OData service documents and metadata documents describe - - The basics of OData entity types, sets and relationships - -## Intro -OData is an open standard that is both a data format and a protocol for consuming and manipulating data in a uniform way. It's ISO/IEC approved and managed by the [OASIS organization](https://www.oasis-open.org/). - -OData has its origins in the world of weblogs and syndication, but now serves to power a great deal of the API and integration activities in typical SAP enterprise environments. This tutorial will help you understand OData from the ground up. By looking briefly at RSS and Atom, precursors of OData in some ways, you'll understand and feel more comfortable with OData and its mechanisms. - -> This tutorial is based upon OData versions 2 and 3. With the advent of OData version 4, there are some differences, but none significant enough to distract from the purpose of this particular tutorial which is to give a simple overview of OData and its origins. - ---- - -### Examine RSS, an ancestor of OData - - -You can understand OData as being the combination of two essential parts. The first is the format, the second is the protocol. The format defines how data is described, how it is serialized. The protocol defines how that data is manipulated. - -The origin of OData's format comes from the world of weblogs, blogging and syndication. The Rich Site Summary (RSS) format was defined to describe a blog and the posts available in it, typically with the newest posts first, but in XML format for machine consumption. It can also describe a set of posts collected in another context, for example all posts tagged with a certain value. - -> RSS is also known as "RDF Site Summary" or "Really Simple Syndication". - -Let's look at an example of RSS. The National Aeronautics and Space Administration (NASA) maintains many RSS feeds, and you can see a list of them on the [NASA RSS Feeds](https://www.nasa.gov/content/nasa-rss-feeds) page. Go there now and select the [Breaking News](https://www.nasa.gov/rss/dyn/breaking_news.rss) feed which is at this URL: - - - -The resulting RSS content of this resource should look something like this (reduced here for brevity): - -```xml - - - - NASA Breaking News - A RSS news feed containing the latest NASA news articles and press releases. - http://www.nasa.gov/ - - en-us - - NASA Administrator to Visit Florida Students, Industry - http://www.nasa.gov/press-release/nasa-administrator-to-visit-florida-students-industry - NASA Administrator Bill Nelson will speak to elementary school students about the future of space exploration Monday, May 9, and tour a lab working on robotic construction technologies Tuesday, May 10, during a trip to Florida. - - http://www.nasa.gov/press-release/nasa-administrator-to-visit-florida-students-industry - Fri, 06 May 2022 11:13 EDT - NASA Breaking News - 479411 - - - NASA, ESA Astronauts Safely Return to Earth - http://www.nasa.gov/press-release/nasa-esa-astronauts-safely-return-to-earth - NASA's SpaceX Crew-3 astronauts aboard the Dragon Endurance spacecraft safely splashed down Friday in the Gulf of Mexico off the coast of Florida, completing the agency's third long-duration commercial crew mission to the International Space Station. - - http://www.nasa.gov/press-release/nasa-esa-astronauts-safely-return-to-earth - Fri, 06 May 2022 01:04 EDT - NASA Breaking News - 479399 - - - -``` - -Observe the structure of the XML document. Within the outermost `` element, it describes a `` that has some metadata such as title and description. That `` contains one or more `` elements, each of them representing a breaking news item. - -``` -rss - | - +-- channel - | - +-- item - | - +-- item - | - +-- ... -``` - -Think of this overall structure like a document, with a header and items. - - - -### Examine Atom and the Atom Publishing Protocol - - -Atom is a format very similar to RSS, serving the same purpose, and is properly known as the [Atom Syndication Format](https://tools.ietf.org/html/rfc4287). Some may call Atom a successor to RSS. Unlike RSS, which is just a format specification, Atom also has a related protocol called the [Atom Publishing Protocol](https://tools.ietf.org/html/rfc5023) that enables the manipulation of data stored in Atom-formatted resources. This was useful for weblog authors, who could use tools that spoke the Atom Publishing Protocol to edit and publish posts to remote blogging systems. - -Look at an example of the Atom format in the corresponding Wikipedia entry: - follow the link in the "Contents" box to section 5 "Example of an Atom 1.0 feed". Notice that the general structure of the elements is the same as RSS, consisting of a root `feed` element containing `entry` child elements. - -The Atom Publishing Protocol Request For Comments (RFC) document ([RFC5023](https://tools.ietf.org/html/rfc5023)) describes a series of standard operations that can be performed on entries in an Atom feed - in other words, operations on XML representations of blog posts that are in the form of XML `entry` elements. These operations are for listing multiple entries and creating, editing, retrieving & deleting individual entries, and they correspond to the standard HTTP methods (GET, POST, PUT and DELETE). - -The Atom Publishing Protocol specification also details the concept of a service document that describes what collections of entries are available for a given resource. Here's an example of a service document: - -``` - - - - Main Site - - My Blog Entries - - - - Pictures - image/png - image/jpeg - image/gif - - - -``` - -You will see that these fundamental building blocks of Atom are alive and well in the OData protocol today. - - - -### Look at the basics of OData - - -The ideas in Atom formed the foundation of OData. OData is described in full at but at a simple level, OData has: - - - a service document describing the data available in a given OData service - - the concept of entity sets and entities, which are direct parallels of feeds and entries, respectively, in RSS and Atom - - a basic set of operations: Create, Read, Update, Delete and Query (commonly referred to as CRUD+Q) - -There is a publicly available set of OData services maintained by the OASIS organisation, which are known as the **Northwind** services because they offer a data set based on a business scenario that revolves around a company called **Northwind Traders**. This data set contains entities such as customers, products and suppliers. - -Go to the OASIS OData sample service root URL . You should see something like this: - -![OASIS OData services page](oasis-odata-services.png) - -Select the link **Browse the Read-Only Northwind Service** and you will see the XML contents of this resource: . This is the service document for the OData service at this location, and the start of it should look like this: - -![Northwind service document](northwind-v3-service-document.png) - -Notice how similar it is to the Atom service document, with a "service" root element and "workspace" elements containing "collection" elements that outline the types of data available. In this case you see that there are `Categories`, `CustomerDemographics`, `Customers`, `Employees` and more available in this service. - - -### Look at an OData metadata document - - -In addition to the service document, an OData service also has a metadata document, a resource which describes the data in the OData service. The metadata document itself is available at a "well-known" URL, which is the service document URL with the value `$metadata` appended. For this Northwind OData service, this means that the metadata document should be available at: - - - -Go to this URL and examine the first part of the metadata, which should look something like this: - -![Northwind metadata document](northwind-metadata.png) - -Notice that the basic structure at the start of the metadata document describes the entity types and their properties. You should see entity type definitions for the `Category` entity type, the `CustomerDemographics` entity type, and more. - -You should also see that the properties within these entities are described, that some are defined as key properties, and also some are defined as navigation properties, that describe a link from one entity type to another. For example, there is a relationship between the `Category` entity type and the `Products` entity type by means of the "Products" navigation property in the definition of the `Category` entity type. - -If you're interested, you can scroll through the metadata document to the `Association` definitions to find more details about this relationship identified by the ID `FK_Products_Categories`. You will find the definition of an association that looks like this: - -![Definition of an association](association-definition.png) - -and the definition of an association set that looks like this: - -![Definition of an association set](associationset-definition.png) - - - -### View the products data in the OData service - - -In the previous step you examined entity types. These are detailed descriptions of entities available in the OData service. The entities themselves are available in so-called entity sets. The relationship between entities and entity sets with OData is the direct equivalent of the relationship between entries and feeds in RSS and Atom. In fact, you'll see that `entry` and `feed` elements live on in the OData format. - -Entity sets have their own definitions in the metadata document, described by `EntitySet` elements within the `EntityContainer` element. Scroll down to find, or search for `EntityContainer`, and you will see within it a definition of all the entity sets available in this OData service. It will look something like this: - -![entity container definition](entitycontainer.png) - -Notice there is an entity set "Products", that is a set of entities of type "Product". Search higher up in the metadata document for the entity type "Product", and examine the definition, which should look something like this: - -![product entity definition](product-entity.png) - -You can navigate directly to an entity set by appending its name onto the end of the service document URL. Do that now for the Products entity set, like this: - - - -You will see the XML representation of the Products entity set. Unless you already have a browser feature to display XML prettily, it will look something like this: - -![raw products entity set XML](products-entityset-raw.png) - -It's not easy to read like this, but you should be still able to discern, even in this rendering, features with which you're now familiar. Notice the XML `feed` element is the root element, representing a collection of things. Notice also the first `entry` element, representing the start of the first product record in this collection. - - - -### Install a Chrome extension for XML rendering - - -The Chrome browser is recommended here, as it has a good choice of extensions that can make life easier. There are extensions for Chrome to render XML in a more human-friendly way. One of these extensions is [XML Tree](https://chrome.google.com/webstore/detail/xml-tree/gbammbheopgpmaagmckhpjbfgdfkpadb?hl=en). There are others, but this one will do. Install this in your Chrome browser by following the instructions on the extension page and then reload the [Products entity set resource](https://services.odata.org/V3/Northwind/Northwind.svc/Products). It should now look something like this: - -![rendered products entity set XML](products-entityset-rendered.png) - -Much easier to read, and clearly visible is the structure and relationship described by the `feed` and `entry` elements. It's now also easier to see the actual product data - in this screenshot there is the `Chai` product, with 39 units in stock. - - -### Explore the navigation properties from a product - - -In the screenshot in the previous step, notice the `link` XML elements, in particular the ones with the title attribute values `Category`, `Order_Details` and `Supplier`. Notice also the corresponding values of their type attributes: `entry`, `feed` and `entry` respectively: - -![links from a product entry](entry-links.png) - -Re-examine the [metadata document](https://services.odata.org/V3/Northwind/Northwind.svc/$metadata) to work out what these might be. Look for the Product entity type definition, which (with the new XML Tree extension) will look something like this: - -![product entity type definition](product-entity-type.png) - -Look at the three navigation properties defined. They describe relationships between the `Product` entity type and the `Category`, `Order_Details` and `Supplier` entity types. - -![relationship diagram](relationship-diagram.png) - -The relationship to the `Category` entity type is described with the ID `NorthwindModel.FK_Products_Categories`, with the `To_Role` attribute value being `Categories`. Search elsewhere in the metadata document for `FK_Products_Categories` to find the `Association` definition: - -![products to categories association](products-categories-association.png) - -Notice that the value of the `Multiplicity` attribute for the `Categories` role is defined as "0..1". This means that there can be either zero or one categories for a product. This is why when we follow the navigation property from a `Product` entity type to a `Category` entity type (see the screenshot at the start of this step) the type of the `link` element is `entry`, not `feed`. - -Follow the same path for the relationship to the `OrderDetails` navigation property described with the `To_Role` attribute value of `Order_Details`, and you will find, via the relationship `FK_Order_Details_Products`, that the `Association` definition looks like this: - -![product to order details association](products-orderdetails-association.png) - -In this case, the value of the Multiplicity attribute described for this relationship is `*`. This means that there can be zero, one or more order details for a product. This is why when we follow this navigation property the type of the `link` element is `feed`, rather than `entity`. - - -### Retrieve a specific product - - -The URL shows the `Products` entity set, a feed of individual entries, each one representing a product. In each product `entry` element there is a child `id` element with the unique URL for that particular product, like in this example: - -![id of product entry](product-entry-id.png) - -Specify that ID in the browser address bar, by adding `(1)` to the end of the existing URL: - - - -Note that the resource returned is the entry for that specific product. - - - -### Retrieve order details for a specific product - - -To see how the navigation properties work, go from the individual property entry in the previous step to a list of the related order details. Remembering the navigation property concerned, `Order_Details`, add it to the end of the existing URL in the address bar to navigate to this URL: . - -You should see that the resulting resource is a feed, a collection of entries representing the orders relating to the product specified. - -Finally, use the OData system query option $count to retrieve the number of order details, rather than the order details themselves. Append `$count` onto the end of the existing URL like this: - - - - diff --git a/tutorials/odata-01-intro-origins/product-entity-type.png b/tutorials/odata-01-intro-origins/product-entity-type.png deleted file mode 100644 index 12d987fa15..0000000000 Binary files a/tutorials/odata-01-intro-origins/product-entity-type.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/product-entity.png b/tutorials/odata-01-intro-origins/product-entity.png deleted file mode 100644 index 1a5fbf1805..0000000000 Binary files a/tutorials/odata-01-intro-origins/product-entity.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/product-entry-id.png b/tutorials/odata-01-intro-origins/product-entry-id.png deleted file mode 100644 index d1f7ab9659..0000000000 Binary files a/tutorials/odata-01-intro-origins/product-entry-id.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/products-categories-association.png b/tutorials/odata-01-intro-origins/products-categories-association.png deleted file mode 100644 index a1eaf9ae3c..0000000000 Binary files a/tutorials/odata-01-intro-origins/products-categories-association.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/products-entityset-raw.png b/tutorials/odata-01-intro-origins/products-entityset-raw.png deleted file mode 100644 index caa0bbe5cc..0000000000 Binary files a/tutorials/odata-01-intro-origins/products-entityset-raw.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/products-entityset-rendered.png b/tutorials/odata-01-intro-origins/products-entityset-rendered.png deleted file mode 100644 index aa7fe67c50..0000000000 Binary files a/tutorials/odata-01-intro-origins/products-entityset-rendered.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/products-orderdetails-association.png b/tutorials/odata-01-intro-origins/products-orderdetails-association.png deleted file mode 100644 index cb01c2fbd5..0000000000 Binary files a/tutorials/odata-01-intro-origins/products-orderdetails-association.png and /dev/null differ diff --git a/tutorials/odata-01-intro-origins/relationship-diagram.png b/tutorials/odata-01-intro-origins/relationship-diagram.png deleted file mode 100644 index e67b8b636c..0000000000 Binary files a/tutorials/odata-01-intro-origins/relationship-diagram.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/basic-credentials.png b/tutorials/odata-02-exploration-epm/basic-credentials.png deleted file mode 100644 index 62632bcd7e..0000000000 Binary files a/tutorials/odata-02-exploration-epm/basic-credentials.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/categories-in-json.png b/tutorials/odata-02-exploration-epm/categories-in-json.png deleted file mode 100644 index a4115b13e1..0000000000 Binary files a/tutorials/odata-02-exploration-epm/categories-in-json.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/entity-relationships.png b/tutorials/odata-02-exploration-epm/entity-relationships.png deleted file mode 100644 index 2e2476371b..0000000000 Binary files a/tutorials/odata-02-exploration-epm/entity-relationships.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/execute-sicf.png b/tutorials/odata-02-exploration-epm/execute-sicf.png deleted file mode 100644 index 4908db4970..0000000000 Binary files a/tutorials/odata-02-exploration-epm/execute-sicf.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/main-sub-categories.png b/tutorials/odata-02-exploration-epm/main-sub-categories.png deleted file mode 100644 index e900a67303..0000000000 Binary files a/tutorials/odata-02-exploration-epm/main-sub-categories.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/nested-feed.png b/tutorials/odata-02-exploration-epm/nested-feed.png deleted file mode 100644 index 02d0f9f717..0000000000 Binary files a/tutorials/odata-02-exploration-epm/nested-feed.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/node-hierarchy.png b/tutorials/odata-02-exploration-epm/node-hierarchy.png deleted file mode 100644 index 54a6823295..0000000000 Binary files a/tutorials/odata-02-exploration-epm/node-hierarchy.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/odata-02-exploration-epm.md b/tutorials/odata-02-exploration-epm/odata-02-exploration-epm.md deleted file mode 100644 index 46b00b5db1..0000000000 --- a/tutorials/odata-02-exploration-epm/odata-02-exploration-epm.md +++ /dev/null @@ -1,299 +0,0 @@ ---- -parser: v2 -author_name: DJ Adams -author_profile: https://github.com/qmacro -auto_validation: true -primary_tag: products>sap-cloud-platform -tags: [ products>sap-cloud-platform, topic>cloud, topic>odata, tutorial>beginner ] -time: 15 ---- - -# Continue Your OData Exploration with EPM - Continue your exploration of OData with the Enterprise Procurement Model (EPM) data set in the Gateway demo system. - -## You will learn - - How to explore an OData service in your browser - - How to use navigation paths - - What the common query options are and how to use them - - How to switch to a JSON output format - -## Intro -The Enterprise Procurement Model (EPM) represents a typical business scenario that is complex enough to have meaning in an enterprise context but still simple enough to use for exploring technologies and techniques at a beginner level. - -EPM exists as data in a set of related tables and views, and there are also various OData services that marshal that data and provide business functionality. The EPM and the related OData services are available in the SAP Gateway demo system, and there is a specific EPM OData service, intended for use in a reference app called "Shop", that will be used in this tutorial. - ---- - -### Find the EPM OData service - - -In this step you'll find the EPM OData service by looking for it via the maintenance transaction for the Internet Communication Framework (ICF), to understand how web-based resources in general and OData services in particular are managed within an ABAP system. - -Log on to the SAP Gateway Demo system via the [Web GUI](https://sapes5.sapdevcenter.com/). If necessary, use the arrow button to make the OK Code field appear, so you can enter transaction codes. - -![menu option to show the OK Code field](show-okcode-field.png) - -Enter transaction code **`SICF`** into the OK Code field to start the "Define Services" transaction. In the Service Path field enter **`/sap/opu/odata`** and then select the Execute function. - -![executing SICF with /sap/opu/odata](execute-sicf.png) - -You will be presented with display of the ICF node hierarchy filtered to display only those nodes starting with the path `/sap/opu/odata`, which represents the root of the OData services. Feel free to explore the hierarchy of nodes available within the `odata` branch, and in particular the `sap` node, which is where you'll see a number of sub nodes representing OData services. - -![the ICF node hierarchy for /sap/opu/odata](node-hierarchy.png) - -Now scroll down to find this OData service node: - -`EPM_REF_APPS_SHOP_SRV` - -This is the OData service you will explore. It is an EPM based service for a reference app called "Shop", which explains most of the node's name. The last part, `SRV`, short for "service", is common for OData services served from ABAP systems. This is similar to the convention you may have noticed with the Northwind service in the tutorial [Learn about OData fundamentals](https://developers.sap.com/tutorials/odata-01-intro-origins.html) where the end part of the OData service name was `svc`. - -Use the information in the node hierarchy that leads down to the `EPM_REF_APPS_SHOP_SRV` node to form the part of the OData service URL that will be relative to the SAP Gateway demo system base URL: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV` - -Enter your credentials for the SAP Gateway demo system if prompted. - -![basic authentication](basic-credentials.png) - -The resource returned is the OData service document, showing the collections available, such as `Suppliers`, `MainCategories` and so on. - -![OData service document](service-document.png) - - - - -### Explore entity relationships - - -At a very high level, the entity types and their relationships in this OData service look like this: - -![entity types and their relationships](entity-relationships.png) - -There are corresponding entity sets for each of the entity types. If you want to confirm this for yourself, look at the service's metadata document at this URL to see those relationships (look particularly at the `NavigationProperty`, `Association` and `EntitySet` elements for details): - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/$metadata` - -You should now explore the `Products` entity set and see how a specific product relates to its supplier. - -First, look at all of the products, using this URL for the `Products` entity set: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Products` - -Now, find the product entry with the ID `HT-1001` and the name "Notebook Basic 17". - -> If there isn't a product entry with this specific ID, you can choose another one - the IDs follow a similar pattern. - -Use the entry's ID to navigate directly to that product, as an entity: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Products('HT-1001')` - -Now you're looking at an individual product, in the form of a single entity (`Products('HT-1001')`) rather than all the products via the entire entity set (`Products`). - -> Note the difference between the `Products` entity set resource and the resource for this specific `Product` entity - the former is represented by the root XML element `feed`, and the latter by the root XML element `entry`. - -Next, follow the link from this product to its supplier, using the information in the relevant `link` element: - -![link from product to supplier](product-to-supplier-link.png) - -In other words, specify this URL: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Products('HT-1001')/Supplier` - -You should see data for a single supplier, Becker Berlin, described inside a root XML `entry` element, denoting a single entity: - -![single supplier Becker Berlin](single-supplier.png) - -It is also possible to navigate to individual properties within an entity. Try this now. Select the supplier's web address, by specifying this URL: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Products('HT-1001')/Supplier/WebAddress` - - - - -### Page through the products with $top and $skip - - -OData has system query options `$top` and `$skip` that facilitate paging through large entity sets. - -First, find out how many `Suppliers` there are, using the `$count` system query option: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Suppliers/$count` - -At the time of writing, the number of suppliers in the `Suppliers` entity set for this service is 45. You may find there is a different number, but it doesn't matter. - -Request the first 5 suppliers, using `$top`, like this: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Suppliers?$top=5` - -> The system query options like `$top` and `$skip` are part of the query string of the URL which itself is introduced with the `?` symbol. - -Now get the next 5 suppliers, by using `$skip` in conjunction with `$top`: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Suppliers?$top=5&$skip=5` - -> Certain frameworks that process OData, like the [SAP UI5 toolkit](https://ui5.sap.com), use these system query options internally to allow comfortable paging through large data sets in list or table situations. - - -### Have related data included in an entity set request - - -Instead of navigating from an entity to a related entity or entity set using two requests (one for the original entity and then another for the related data), the `$expand` system query option allows for related data to be returned in-line with resources retrieved, in a single request. - -Try this out, by looking at another couple of EPM entities exposed in this OData service - the product categories. There's a `MainCategory` entity type, with a navigation property to a list of entities of type `SubCategory`. Have a quick look at the metadata document to confirm this: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/$metadata` - -![main and sub category entity types](main-sub-categories.png) - -Request a list of all the main categories, and ask for their sub categories to be returned in-line in the response, using the `$expand` system query option: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/MainCategories?$expand=SubCategories` - -If you look closely at the response, you'll see that there is a `feed` XML element at the root, containing `entry` elements. A typical entity set response. However, inside each `entry` element there is a nested `feed` element, itself containing further `entry` elements. - -![nested feeds with $expand](nested-feed.png) - -The `SubCategory` entities related to each `MainCategory` entity are returned in-line, in the response to the request for the `MainCategories` entity set. - - - -### Request responses in a JSON format - - -You may have found looking through the nested XML structures in the previous step quite tedious. XML is human-readable, but not necessarily human-friendly. The OData specification describes an alternative format in JavaScript Object Notation (JSON). This is a more lightweight format and somewhat easier to read, if you have a browser extension that will format JSON for you. - -First, install a JSON formatter extension for your Chrome browser. The `JSONView` extension is a good choice. Go to the [JSONView extension page](https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc?hl=en) and add it to your Chrome browser. - -Now, reload the main and sub category structure from the previous step with this URL: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/MainCategories?$expand=SubCategories` - -Next, append the OData system query option `$format=json` to the query string, like this: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/MainCategories?$expand=SubCategories&$format=json` - -> Adding more system query options to the query string of an OData URL is just like adding query parameters to any other URL query string - they are concatenated with the '&' symbol. Don't forget the '$' prefix on each of the OData system query options, though! - -The response is returned in JSON, and formatted by the `JSONView` is considerably easier to read: - -![categories response in JSON](categories-in-json.png) - -> While the entity set and entity responses can be returned in JSON format, the service document and metadata document of an OData service, at least a V2 OData service, cannot - they only exist in XML format. - - -### Reduce the number of properties returned - - -An OData service may contain a definition of an entity type that has a large number of properties. If the consumer of the service really only needs a couple of them, transferring the rest is an unnecessary load on network traffic and can increase response times. The OData system query option `$select` allows you to specify a smaller list of properties that should be returned. - -Looking at the metadata document of the OData service, you will see that the `Product` entity type is one that has many properties. - -First, take a look at all the properties and some sample values by requesting the first entity in the `Products` entity set with this URL: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Products?$top=1&$format=json` - -Even in the more lightweight JSON format it's still a lot of data, especially if an entire entity set is requested: - -![a single product entity in JSON format](single-product-entity-in-json.png) - -Now reduce the number of properties down to just a few: `AverageRating`, `Name` and `StockQuantity` using the `$select` system query option: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Products?$top=1&$format=json&$select=AverageRating,Name,StockQuantity` - -You'll get a response that looks something like this - a whole lot smaller! - -![product with a reduced number of properties](product-with-few-properties.png) - -The `$select` system query option can also be used to specify properties that are part of entities that are returned in-line with `$expand`. The `Product` entity type has a `Supplier` navigation property as well as a `Reviews` navigation property - these can be returned in-line with `$expand`, and a restricted set of their properties can be specified in `$select` as well. - -Specify this URL, to request the first product, along with the name of its supplier, and also the names of the users who have reviewed that product: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Products?$top=1&$format=json&$expand=Supplier,Reviews&$select=AverageRating,Name,StockQuantity,Supplier/Name,Supplier/FormattedAddress,Reviews/UserDisplayName` - -> Depending on how the data has been modified in this demo system, you may find that the first product sometimes has no reviews. In that case, search for one using combinations of the `$top` and `$skip` that you learned about in a previous step. - -The query string portion of this URL is quite long and getting difficult to read. Broken down into its parts, we have the following: - - - Get the first entity: -`$top=1` - - - Return the response in JSON format -`$format=json` - - - Include in-line the related Supplier and Reviews data as well -`$expand=Supplier,Reviews` - - - Only return these specific properties -`$select=AverageRating,Name,StockQuantity,Supplier/Name,Supplier/FormattedAddress,Reviews/UserDisplayName` - -> Notice the format for specifying properties in related entity types, such as `Supplier/Name` and `Reviews/UserDisplayName`. - -This request brings back exactly what we asked for: - -![review and supplier detail for a product](review-supplier-detail-for-product.png) - - - - -### Reduce the number of entities returned by filtering - - -The `$filter` system query option can be used to filter the entities according to criteria that can be expressed by a broad set of operators. - -Refer back to the properties of the first product in the `Products` entity set, as shown in the screenshot in the previous step. Any of the properties here can be used with the `$filter` system query option. - -First, count how many products there are, using `$count`: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Products/$count` - -This may well vary, but at the time of writing, this shows 125. - -Now, use the `$filter` system query option in conjunction with `$count` to find out how many products are in the "Computer Systems" main category: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Products/$count?$filter=MainCategoryId%20eq%20%27Computer%20Systems%27` - -This should return a count value less than the total, earlier. At the time of writing, the value is 34. - - -> If you're wondering about the strange characters in this URL, they're just [URL encoded](https://en.wikipedia.org/wiki/Percent-encoding) versions of the space and single-quote characters, in other words %20 and %27 respectively. You can actually type the original space and single-quote values into the browser address bar like this: `$filter=MainCategoryId eq 'Computer Systems'` and the characters will be encoded automatically. - -You can double check the results by removing the `$count` part from the URL to see that each of the product entities returned really do belong to the 'Computer Systems' main category: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Products/?$filter=MainCategoryId%20eq%20%27Computer%20Systems%27` - -![products in the Computer Systems main category](products-in-computer-systems-main-category.png) - -Operators can be combined. Try this, by finding out whether there are any products in the "Software" main category where the stock is low (10 units or fewer), restricting the results to just show the product name and stock information, in JSON format: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Products/?$filter=MainCategoryId%20eq%20%27Software%27%20and%20StockQuantity%20le%2010&$select=StockQuantity,Name&$format=json` - -Again, to break this down, we have the following (before the special characters are URL encoded): - - - Restrict entries to those where the `MainCategoryId` value is "Software" and where the `StockQuantity` value is less than or equal to 10: -`$filter=MainCategoryId eq 'Software' and StockQuantity le 10` - - - Return only the values for the `StockQuantity` and `Name` properties: -`$select=StockQuantity,Name` - - - Return the response in JSON format: -`$format=json` - -You can learn more about the different operators available in the [Filter System Query Option ($filter)](https://www.odata.org/documentation/odata-version-2-0/uri-conventions/#FilterSystemQueryOption) documentation on the OASIS OData website. You'll see that there are functions available for use with `$filter` too, functions such as `substringof` and `startswith`. - - - -### Have entities returned in a certain order - - -The final system query option to examine in this tutorial is `$orderby`, which takes the specification of a list of one or more properties, and optional indicators to specify whether ascending order (the default) or descending order is desired. - -Use the `$orderby` system query option to list the products sorted by average rating, with the most highly rated appearing first, showing the product name, price and the average rating score: - -`https://sapes5.sapdevcenter.com/sap/opu/odata/sap/EPM_REF_APPS_SHOP_SRV/Products?$format=json&$orderby=AverageRating%20desc&$select=Name,Price,CurrencyCode,AverageRating` - -This is the first part of what's returned - looks good: - -![products sorted by average rating](products-sorted-by-average-rating.png) - -There are more system query options, but what you've seen in this tutorial are the main ones. You've been using them primarily in the context of the OData "query" operation, which makes a lot of sense. Some of the system query options are used implicitly in frameworks such as UI5 as stated earlier, but all of them are useful to know to explore, "by hand", an OData service, especially when you intend to use it in building an app. - diff --git a/tutorials/odata-02-exploration-epm/product-properties.png b/tutorials/odata-02-exploration-epm/product-properties.png deleted file mode 100644 index 97036e58a9..0000000000 Binary files a/tutorials/odata-02-exploration-epm/product-properties.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/product-to-supplier-link.png b/tutorials/odata-02-exploration-epm/product-to-supplier-link.png deleted file mode 100644 index 9c1c0e43a4..0000000000 Binary files a/tutorials/odata-02-exploration-epm/product-to-supplier-link.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/product-with-few-properties.png b/tutorials/odata-02-exploration-epm/product-with-few-properties.png deleted file mode 100644 index e2f72e6557..0000000000 Binary files a/tutorials/odata-02-exploration-epm/product-with-few-properties.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/products-in-computer-systems-main-category.png b/tutorials/odata-02-exploration-epm/products-in-computer-systems-main-category.png deleted file mode 100644 index 992be45954..0000000000 Binary files a/tutorials/odata-02-exploration-epm/products-in-computer-systems-main-category.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/products-sorted-by-average-rating.png b/tutorials/odata-02-exploration-epm/products-sorted-by-average-rating.png deleted file mode 100644 index f0302b5482..0000000000 Binary files a/tutorials/odata-02-exploration-epm/products-sorted-by-average-rating.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/review-supplier-detail-for-product.png b/tutorials/odata-02-exploration-epm/review-supplier-detail-for-product.png deleted file mode 100644 index f27db03010..0000000000 Binary files a/tutorials/odata-02-exploration-epm/review-supplier-detail-for-product.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/service-document.png b/tutorials/odata-02-exploration-epm/service-document.png deleted file mode 100644 index a132d636c8..0000000000 Binary files a/tutorials/odata-02-exploration-epm/service-document.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/show-okcode-field.png b/tutorials/odata-02-exploration-epm/show-okcode-field.png deleted file mode 100644 index 277a355dc3..0000000000 Binary files a/tutorials/odata-02-exploration-epm/show-okcode-field.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/single-product-entity-in-json.png b/tutorials/odata-02-exploration-epm/single-product-entity-in-json.png deleted file mode 100644 index 9aad895bef..0000000000 Binary files a/tutorials/odata-02-exploration-epm/single-product-entity-in-json.png and /dev/null differ diff --git a/tutorials/odata-02-exploration-epm/single-supplier.png b/tutorials/odata-02-exploration-epm/single-supplier.png deleted file mode 100644 index 7500b6d8e8..0000000000 Binary files a/tutorials/odata-02-exploration-epm/single-supplier.png and /dev/null differ diff --git a/tutorials/odata-05-data-model-service/odata-05-data-model-service.md b/tutorials/odata-05-data-model-service/odata-05-data-model-service.md deleted file mode 100644 index b40eae7b9b..0000000000 --- a/tutorials/odata-05-data-model-service/odata-05-data-model-service.md +++ /dev/null @@ -1,424 +0,0 @@ ---- -parser: v2 -author_name: DJ Adams -author_profile: https://github.com/qmacro -auto_validation: true -primary_tag: software-product-function>sap-cloud-application-programming-model -tags: [products>sap-business-application-studio, programming-tool>odata, tutorial>beginner ] -time: 20 ---- - -# Define a Simple Data Model and OData Service with CDS - Use Core Data Services (CDS) in the context of the SAP Cloud Application Programming Model (CAP) to quickly set up your own simple OData service. - -## Prerequisites - - **Tutorials:** [Create a Dev Space for Business Applications](appstudio-devspace-create) - -## You will learn -- How to use CDS to model entities and services -- How to seed your OData service with test data -- What CAP can do for you in terms of generating and servicing an OData service - -## Intro -[CDS](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/855e00bd559742a3b8276fbed4af1008.html) powers a significant part of [CAP](https://cap.cloud.sap). CDS has many features, and in this tutorial you'll encounter a couple of fundamental ones - the ability to declaratively define your data model, concentrating on the domain at hand, and to then be able to expose parts (or all) of that model in a service. You'll also learn how much CAP can do for you with respect to creating full CRUD+Q\* OData services almost from nothing. It's hard to remember how difficult it was to do that before the advent of CAP. - -\*CRUD+Q is a common shorthand for referring to a fully formed OData service that sports Create, Read, Update, Delete, and Query operations. - -You'll use the SAP Business Application Studio (App Studio), with a dev space for business applications, that you should already have ready and set up from the prerequisite tutorial. - -The model and service you'll create is deliberately a very simple one, based on a small subset of something you have seen before if you have followed previous OData tutorials (in particular the [Learn about OData Fundamentals](odata-01-intro-origins) tutorial) - the product information from the Northwind service. - ---- - - -### Remind yourself of the Northwind product data - - -In the tutorial [Learn about OData Fundamentals](odata-01-intro-origins), you familiarized yourself with some of the structure and content of the [Northwind OData service](https://services.odata.org/V4/Northwind/Northwind.svc/). In this tutorial, you'll create your own simple OData service based on information in the Products entity set, so now's a good time look at that product data. - -Jump to the Products entity set in the V4 version of the OData service, with this URL . - -In a [previous tutorial](odata-01-intro-origins), we used the V3 version at . This resource has a default resource representation of XML; more specifically, the value of the `Content-Type` header returned with this resource is `application/atom+xml;type=feed;charset=utf-8` (you can check this by using your browser's developer tools to inspect the HTTP response headers). - -In this tutorial, we're using the V4 version. After all, OData version 4 has been around as an OASIS standard [since 2014](https://raw.githubusercontent.com/qmacro/odata-specs/master/overview.md). Notice that the default representation of most OData V4 resources here is JSON; more specifically, the value of the `Content-Type` header in the response is `application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8`. This JSON representation is also used for OData service document resources in V4 too, whereas in earlier versions it was XML. - -The representation of the `Products` entity set should look something like this: - -```JSON -{ - "@odata.context": "https://services.odata.org/V4/Northwind/Northwind.svc/$metadata#Products", - "value": [ - { - "@odata.etag": "W/\"1,1\"", - "ProductID": 1, - "ProductName": "Chai", - "SupplierID": 1, - "CategoryID": 1, - "QuantityPerUnit": "10 boxes x 20 bags", - "UnitPrice": 18.0000, - "UnitsInStock": 39, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": false - }, - { - "@odata.etag": "W/\"1,1\"", - "ProductID": 2, - "ProductName": "Chang", - "SupplierID": 1, - "CategoryID": 1, - "QuantityPerUnit": "24 - 12 oz bottles", - "UnitPrice": 19.0000, - "UnitsInStock": 17, - "UnitsOnOrder": 40, - "ReorderLevel": 25, - "Discontinued": false - } - ] -} -``` - -This of course is just the data; to understand what you're looking at, look now at the heart of the definition of this entity set, in the OData service's metadata document at . - -Ignoring the navigation properties of the `Product` entity type for now, we see this set of property definitions: - -```XML - - - - - - - - - - - - - - - -``` - -So, we know that the `ProductID` property is the only key field, and the types of other properties make sense to us too. - -To find the right balance between realism and efficiency (no-one wants to type in a large amount of definition or data), the first entity definition in the OData service you'll create will be a cut down version of this `Product` entity type, encompassing the following properties: - -- `ProductID` -- `ProductName` -- `UnitsInStock` - -Further entities will be cut down versions of entities in the Northwind OData service too; this suggests that a cut down name for your OData service is appropriate too, so we'll go from `Northwind` to `Northbreeze` (see what we did there?). - - - -### Start a new CAP project - - -To start creating your `Northbreeze` OData service, start by creating a new CAP project in your App Studio dev space using the "New project from Template" wizard available on the Get Started page (if you don't have the Get Started page open, you can recall it with menu path **Help** **→** **Get Started**). - -In the "Select Template and Target Location" step, select the **CAP Project** template and then use the **Start** button to continue. - -![Select the CAP Project template](select-cap-project-template.png) - -In the "CAP Project Details" step, enter `northbreeze` for the project name, ensure that "Node.js" is selected for the runtime, and leave all the other options as they are. Then select the **Finish** button to complete, and wait for the generated project to appear. - -> It's better if you use the all-lowercase version of the name (`northbreeze`) as the name is used as the name of the NPM package that you're (indirectly) creating, and convention there dictates lowercase only. - -Make yourself acquainted with the content of the generated project, by looking through some key files and directories in the App Studio's Explorer. Among these, you should see three directories named `app/`, `db/`, and `srv/`. To understand what these are, and how they relate to what you're going to do in the rest of this tutorial, think of them in a vertical structure like this: - -``` -+------+ -| app/ | -+------+ -| srv/ | -+------+ -| db/ | -+------+ -``` - -At a high level this represents a typical full stack application, with the frontend represented by `app/`, the business logic and services represented by `srv/`, and the persistence layer represented by `db/`. CAP supports work in all of these layers. - -In building your OData service, however, you won't need to make use of the `app/` layer. This is because an OData service is just that - a *service*. You'll be focusing your efforts at the persistence layer (in the `db/` directory) and the business logic layer (in the `srv/` directory). - -While ultimately you'll have created an OData service, which is "flat", providing access to entity data through a uniform and well understood interface, it's best if you think about that service as being the combination of two things -- schema and service -- at two different levels, thus: - -``` -+------+ -| app/ | -+------+ -| srv/ | <-- service: combination(s) of entities focused on consumption -+------+ -| db/ | <-- schema: basic level entity definitions -+------+ -``` - -The OData service you'll be creating is simple and has a one-to-one mapping between schema and service; however, note that CAP's focus on and strong support for [domain modeling](https://cap.cloud.sap/docs/about/#domain-modeling) allows for flexible relationships to be constructed between these two layers, to fit your service consumption needs precisely. - - - -### Define the schema layer - - -The `db/` directory is where entities are defined, and relationships made. Think of it as the overall schema, independent of any intended consumption. - -To keep things as simple as possible, you're going to define a single entity, with only a few properties, and (at least in this tutorial) no relationships to further entities. - -Use the context menu on the `db/` node in the Explorer view to create a new file; give it the name `schema.cds`. - -It's time to define your entity, reflecting a simplified version of the `Product` entity type in the Northwind service definition. Here's the entire content that should go into `schema.cds`. - -> Try to resist the temptation to copy/paste this content; instead, type it in and get to know the rich support for CAP that the App Studio sports, via the SAP CDS Language Support extension. When entering it, you don't have to worry about formatting either - the extension will do that for you too (just use the context menu or the Command Palette to invoke the "Format Document" facility). - -```CDS -namespace northbreeze; - -entity Products { - key ProductID : Integer; - ProductName : String; - UnitsInStock : Integer; -} -``` - -> Note that while in the Northwind service definition the entity type followed the "singular" naming approach ("Product"), the convention in CAP is to use the "plural" naming approach for entity definitions (i.e. "Products"). - -Is this all that's needed for an OData service? Let's find out. - -Open a terminal (menu path **Terminal** **→** **New Terminal**) and that should give you a Bash shell and put you automatically in the root directory of the project you have open in your workspace, that is, `northbreeze`. You'll see a prompt, which consists of your generic username in your App Studio's dev space, the most significant part of the name of the directory you're in (enter the command `pwd` to see the full name, if you're curious) and the traditional shell prompt character `$`. - -```Shell/Bash -user: northbreeze $ -``` - -App Studio dev spaces that have been created using the "SAP Cloud Business Application" type (as you'll have done in the prerequisite tutorial) automatically have the CAP development kit installed (also known as the CDS DK, or "Development Kit", from the name of the NPM package `@sap/cds-dki), including the main command line tool `cds`. One of the features in `cds`'s arsenal is the `watch` command, which will start the CAP server (the runtime) which will start serving services, and restart the CAP server when changes are detected. It will also automatically use an in-memory persistence layer provided by SQLite, which is enough for what we need here in our explorations. - -At the prompt, enter `cds watch`, and observe the output, which should look something like this: - -```Shell/Bash -user: northbreeze $ cds watch - -cds serve all --with-mocks --in-memory? -live reload enabled for browsers - - ___________________________ - -[cds] - loaded model from 1 file(s): - - db/schema.cds - -[cds] - connect using bindings from: { registry: '~/.cds-services.json' } -[cds] - connect to db > sqlite { url: ':memory:' } -/> successfully deployed to in-memory database. - - -[cds] - server listening on { url: 'http://localhost:4004' } -[cds] - launched at 10/23/2024, 3:31:06 PM, version: 8.3.1, in: 363.072ms -[cds] - [ terminate with ^C ] - - - No service definitions found in loaded models. - Waiting for some to arrive... -``` - -This tells us an awful lot already; most importantly for our question, however, is the line "No service definitions found in loaded models - Waiting for some to arrive...". - -You have defined an entity, in a namespace, but not exposed it yet in a service definition. Moreover, if you navigate to the port 4004 that App Studio will have prompted you to connect to, you'll see a welcome page describing what is being served, and the list of service endpoints is currently empty. - -So, creating a service definition is next. You can leave the `cds watch` process running, and it will notice and react to anything you subsequently add or change. - - -### Define the service layer - - -In this step, you'll create the simplest service definition exposing the entire `Products` entity (all three elements) in a service called `Main`. - -Create a new file in the `srv/` directory, calling it `service.cds`. In the same fashion as in the previous step, type (rather than copy/paste) the following into it, exploring what features such as completion help are offered by the language support for CDS in the editor: - -```CDS -using northbreeze from '../db/schema'; - -service Main { - entity Products as projection on northbreeze.Products; -} -``` - -You should see some new output from the `cds watch` process in the terminal, that looks like this: - -``` -[cds] - loaded model from 2 file(s): - - srv/service.cds - db/schema.cds - -[cds] - connect using bindings from: { registry: '~/.cds-services.json' } -[cds] - connect to db > sqlite { url: ':memory:' } -/> successfully deployed to in-memory database. - -[cds] - using auth strategy { - kind: 'mocked', - impl: '../../../../managed-content/globals/pnpm/5/.pnpm/@sap+cds@8.3.1_express@4.21.1/node_modules/@sap/cds/lib/auth/basic-auth' -} - -[cds] - using new OData adapter -[cds] - serving Main { path: '/odata/v4/main' } - -[cds] - server listening on { url: 'http://localhost:4004' } -``` - -This looks promising, in particular the message about the Main service being served. - -If you have still got a browser tab open and looking at the service (or lack thereof), jump to that tab and hit refresh. If you haven't got such a browser tab open, use the Command Palette (call it up with menu path **View** **→** **Find Command...**) to invoke the "Ports: Preview" command, which should give you a link to connections to ports that are currently being exposed. It should look something like this: - -![ports preview](ports-preview.png) - -Make the selection, and you should see a welcome page, this time listing a service endpoint, similar to this: - -![service endpoint](service-endpoint.png) - -This tells us that you have your very own OData service, being served by the CAP runtime. Congratulations! - -Let's pause for a moment to understand what we're seeing here. First, there are the two well-known URLs that are standard for any OData service - the service document, represented by the `/odata/v4/main` hyperlink, and the metadata document, represented by the `$metadata` hyperlink. Note also that these two components are joined with slashes like this: - -``` -/odata/v4/main/$metadata -``` - -This denotes the relative path info for the URL of your OData service. In other words, independent of what host is to serve this service, `/odata/v4/main/` is the actual relative path for the service document. - -Explore the service document and the metadata document now, by following the hyperlinks. There are some high-level observations that are worth making here: - -- The service document faithfully reflects the fact that there is a single entity set `Products` available. -- The metadata document reflects exactly the details that you defined for the entity at the schema layer; this is because the service exposure (in `srv/service.cds`) was the simplest thing that could possibly work, that is, a "pass through" (aka "naked") service, where no properties were filtered out, or added from elsewhere. -- The types in the entity definition (`Integer`, `String`) have been translated into OData types (`Edm.Int32`, `Edm.String`) in the `Property` elements within the `EntityType` element in the metadata document. -- The `ProductID` property has been correctly marked as being a key property. -- An entity set has been defined automatically for the `Products` entity definition, as can be seen within the `EntityContainer` element. - -Note also that: - -- In the root element (`Edmx`) there's a `Version` attribute that declares that the OData version is 4.0. - - -Don't forget to leave the `cds watch` running, ready for the next step! - -### Add data - - -You have got a fully functioning OData service, but it's not as exciting as it could be - there's no data in it yet! If you had selected the `Products` hyperlink on the welcome page in the previous step, you'd have seen something like this: - -```JSON -{ - "@odata.context": "$metadata#Products", - "value": [] -} -``` - -In this penultimate step, you're going to seed your fledgling OData service with data. This will allow you to better kick the tires and discover that yes, this really is a fully functional CRUD+Q OData service that you have created. - -Add a new directory below the `db/` directory, called `data/`, and in there, create a comma-separated value (CSV) file. Given the right names, CSV files in this directory are automatically read, and the data within imported, into the corresponding entities, and that data can then be served in the OData service. - -In order for this to work, the names of the CSV files are important, and are based on a combination of namespace and entity name, separated by a dash. - -So, create a file in the new `db/data/` directory called `northbreeze-Products.csv` and add the following records to it: - -```CSV -ProductID,ProductName,UnitsInStock -1,Chai,39 -2,Chang,17 -3,Aniseed Syrup,13 -``` - -As soon as the contents of this file are saved, you should notice the `cds watch` restart the CAP server, but there's also a new line in the output, that should look something like this: - -```Shell/Bash -> init from db/data/northbreeze-Products.csv -``` - -Great, your seed data is now part of your OData service. - -Jump back to the service (via the welcome page in the previous step) and reselect the `Products` entity set resource. Rather than an empty array for the `value` property, you should now see something like this: - -```JSON -{ - "@odata.context": "$metadata#Products", - "value": [ - { - "ProductID": 1, - "ProductName": "Chai", - "UnitsInStock": 39 - }, - { - "ProductID": 2, - "ProductName": "Chang", - "UnitsInStock": 17 - }, - { - "ProductID": 3, - "ProductName": "Aniseed Syrup", - "UnitsInStock": 13 - } - ] -} -``` - -It's now time to finish this tutorial with a few OData operations. - - -### Try some OData operations - - -There's plenty to explore now you have some data in your simple OData service. Try your own queries, or experiment with some of these. Each time, manipulate the path info and query string as appropriate, based on the URL in your browser. Remember that for the purposes of this tutorial, the URL can be thought of as being made up of three parts. If we take an example OData URL from App Studio, it might look something like this: - -``` -https://port4004-workspaces-ws-czcx7.us10.trial.applicationstudio.cloud.sap/odata/v4/main/Products?$top=11 -``` - -- The first part is the fully qualified hostname, all the way up to the first single slash. -- The second part is the path info, all the way up to the question mark. -- The third part is the query string, introduced by the question mark and made up of one or more `key=value` pairs, with URL-encoded values where appropriate, and joined together with & characters. - -(There is another common part that we see in some URLs, and that's the document fragment identifier, also known as the hash path, introduced with the # character, but this part is not relevant for OData URL construction). - -**Return just the first product** -`/odata/v4/main/Products?$top=1` - -**Return a count of how many products are available** -`/odata/v4/main/Products/$count` - -**Return a single product** -`/odata/v4/main/Products(2)` - -**Return only those "highly stocked" products** -`/odata/v4/main/Products?$filter=UnitsInStock%20gt%2015` - -Your OData service isn't read-only either - it supports all operations (Create, Read, Update, Delete, and Query) out of the box, with no effort on your part at all. - -Try out some write operations now, by opening up a second terminal and using the command line user agent `curl` that's available automatically in all App Studio dev spaces. Here are a few for you to try; in each example, you'll see the prompt (`user: northbreeze $`), the actual invocation (with `curl`) and an indication of the expected output. - -**Add a further product** - -```Shell/Bash -user: northbreeze $ curl -H "Content-Type: application/json" -d '{"ProductID":77,"ProductName":"Original Frankfurter grüne Soße","UnitsInStock":32}' http://localhost:4004/odata/v4/main/Products -{"@odata.context":"$metadata#Products/$entity","ProductID":77,"ProductName":"Original Frankfurter grüne Soße","UnitsInStock":32} -``` - -Once you have added this new product, you can check its existence by going back to the query of the entire entity set: - -`/odata/v4/main/Products` - -**Reduce the number of units in stock for the Chang product** - -```Shell/Bash -user: northbreeze $ curl -H "Content-Type: application/json" -d '{"UnitsInStock":1}' -X PATCH "http://localhost:4004/odata/v4/main/Products(1)" -{"@odata.context":"$metadata#Products/$entity","ProductID":1,"ProductName":"Chai","UnitsInStock":1} -``` - -**Remove the recently added product** - -```Shell/Bash -user: northbreeze $ curl -X DELETE "http://localhost:4004/odata/v4/main/Products(77)" -``` - -At this point, you have exercised your OData service and tried out all five OData operation types. - -Well done! - diff --git a/tutorials/odata-05-data-model-service/ports-preview.png b/tutorials/odata-05-data-model-service/ports-preview.png deleted file mode 100644 index 49ae201915..0000000000 Binary files a/tutorials/odata-05-data-model-service/ports-preview.png and /dev/null differ diff --git a/tutorials/odata-05-data-model-service/select-cap-project-template.png b/tutorials/odata-05-data-model-service/select-cap-project-template.png deleted file mode 100644 index 46541878c0..0000000000 Binary files a/tutorials/odata-05-data-model-service/select-cap-project-template.png and /dev/null differ diff --git a/tutorials/odata-05-data-model-service/service-endpoint.png b/tutorials/odata-05-data-model-service/service-endpoint.png deleted file mode 100644 index c089ce16a1..0000000000 Binary files a/tutorials/odata-05-data-model-service/service-endpoint.png and /dev/null differ diff --git a/tutorials/odata-06-extend-odata-service/main-hyperlink.png b/tutorials/odata-06-extend-odata-service/main-hyperlink.png deleted file mode 100644 index e955c47813..0000000000 Binary files a/tutorials/odata-06-extend-odata-service/main-hyperlink.png and /dev/null differ diff --git a/tutorials/odata-06-extend-odata-service/northbreeze-Products.csv b/tutorials/odata-06-extend-odata-service/northbreeze-Products.csv deleted file mode 100644 index ea876efbc7..0000000000 --- a/tutorials/odata-06-extend-odata-service/northbreeze-Products.csv +++ /dev/null @@ -1,78 +0,0 @@ -ProductID,Category_CategoryID,ProductName,UnitsInStock -1,1,"Chai",39 -2,1,"Chang",17 -3,2,"Aniseed Syrup",13 -4,2,"Chef Anton's Cajun Seasoning",53 -5,2,"Chef Anton's Gumbo Mix",0 -6,2,"Grandma's Boysenberry Spread",120 -7,7,"Uncle Bob's Organic Dried Pears",15 -8,2,"Northwoods Cranberry Sauce",6 -9,6,"Mishi Kobe Niku",29 -10,8,"Ikura",31 -11,4,"Queso Cabrales",22 -12,4,"Queso Manchego La Pastora",86 -13,8,"Konbu",24 -14,7,"Tofu",35 -15,2,"Genen Shouyu",39 -16,3,"Pavlova",29 -17,6,"Alice Mutton",0 -18,8,"Carnarvon Tigers",42 -19,3,"Teatime Chocolate Biscuits",25 -20,3,"Sir Rodney's Marmalade",40 -21,3,"Sir Rodney's Scones",3 -22,5,"Gustaf's Knäckebröd",104 -23,5,"Tunnbröd",61 -24,1,"Guaraná Fantástica",20 -25,3,"NuNuCa Nuß-Nougat-Creme",76 -26,3,"Gumbär Gummibärchen",15 -27,3,"Schoggi Schokolade",49 -28,7,"Rössle Sauerkraut",26 -29,6,"Thüringer Rostbratwurst",0 -30,8,"Nord-Ost Matjeshering",10 -31,4,"Gorgonzola Telino",0 -32,4,"Mascarpone Fabioli",9 -33,4,"Geitost",112 -34,1,"Sasquatch Ale",111 -35,1,"Steeleye Stout",20 -36,8,"Inlagd Sill",112 -37,8,"Gravad lax",11 -38,1,"Côte de Blaye",17 -39,1,"Chartreuse verte",69 -40,8,"Boston Crab Meat",123 -41,8,"Jack's New England Clam Chowder",85 -42,5,"Singaporean Hokkien Fried Mee",26 -43,1,"Ipoh Coffee",17 -44,2,"Gula Malacca",27 -45,8,"Rogede sild",5 -46,8,"Spegesild",95 -47,3,"Zaanse koeken",36 -48,3,"Chocolade",15 -49,3,"Maxilaku",10 -50,3,"Valkoinen suklaa",65 -51,7,"Manjimup Dried Apples",20 -52,5,"Filo Mix",38 -53,6,"Perth Pasties",0 -54,6,"Tourtière",21 -55,6,"Pâté chinois",115 -56,5,"Gnocchi di nonna Alice",21 -57,5,"Ravioli Angelo",36 -58,8,"Escargots de Bourgogne",62 -59,4,"Raclette Courdavault",79 -60,4,"Camembert Pierrot",19 -61,2,"Sirop d'érable",113 -62,3,"Tarte au sucre",17 -63,2,"Vegie-spread",24 -64,5,"Wimmers gute Semmelknödel",22 -65,2,"Louisiana Fiery Hot Pepper Sauce",76 -66,2,"Louisiana Hot Spiced Okra",4 -67,1,"Laughing Lumberjack Lager",52 -68,3,"Scottish Longbreads",6 -69,4,"Gudbrandsdalsost",26 -70,1,"Outback Lager",15 -71,4,"Flotemysost",26 -72,4,"Mozzarella di Giovanni",14 -73,8,"Röd Kaviar",101 -74,7,"Longlife Tofu",4 -75,1,"Rhönbräu Klosterbier",125 -76,1,"Lakkalikööri",57 -77,2,"Original Frankfurter grüne Soße",32 diff --git a/tutorials/odata-06-extend-odata-service/odata-06-extend-odata-service.md b/tutorials/odata-06-extend-odata-service/odata-06-extend-odata-service.md deleted file mode 100644 index 0dd2940351..0000000000 --- a/tutorials/odata-06-extend-odata-service/odata-06-extend-odata-service.md +++ /dev/null @@ -1,642 +0,0 @@ ---- -parser: v2 -author_name: DJ Adams -author_profile: https://github.com/qmacro -auto_validation: true -primary_tag: software-product-function>sap-cloud-application-programming-model -tags: [ software-product-function>sap-business-application-studio, programming-tool>odata, tutorial>beginner ] -time: 20 ---- - -# Extend your Simple Data Model with a Second Entity - Explore entity relationships and navigation properties by extending your simple OData service with further Core Data Services (CDS) definitions. - -## You will learn -- How OData metadata navigation properties work -- How to define relationships between entities in CDS -- What those relationships look like in an OData context - -## Intro -This tutorial assumes you've completed the tutorial [Define a Simple Data Model and OData Service with CDS](odata-05-data-model-service). If you have done, you'll have a brand new OData service `Northbreeze` of your own to use. However, it's still rather simple, with just a single entity. - -In this tutorial, you'll first study the relationship between products and categories in the Northwind OData V4 service. Then, in your own service, you'll add a second entity at the `db/` layer and define a relation between it and the first entity. You'll then expose this second entity at the `srv/` layer and examine what this looks like from an OData metadata and operations perspective. Finally, you'll build a relationship between those two entities, add some data, and check that everything works as intended. - -Before you start, open up the workspace in the SAP Business Application Studio (App Studio) dev space you were using in that previous tutorial, ready to extend the CDS definitions you have so far. - ---- - -### Take a look at the Northwind Product / Category relationship - -In the [previous tutorial in this group](odata-05-data-model-service) you added a cut down version of the `Product` entity type. If you examine [Northwind's metadata document](https://services.odata.org/V4/Northwind/Northwind.svc/$metadata), you should see that this entity type actually has relationships with three other entity types - look for the `NavigationProperty` elements in this extract from the metadata document (some of the `Property` elements have been omitted for brevity): - -```XML - - - - - - - - - - - - - - - - - - -``` - -Let's focus on the relationship to the `Category` entity type, defined in the corresponding `NavigationProperty` element. Here's what that looks like, with some added whitespace for readability: - -```xml - -``` - -Digging into the [Navigation Property section of the OData V4 standards document](http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_Toc453752536) we can better understand what this declaration is telling us. Here's what we can work out together, at a high level: - -The navigation property - -- is on the `Product` entity type -- is itself called `Category` -- leads to the `Category` entity type -- has a corresponding path back from a `Category` entity to entities of this `Product` entity type via a `Products` path (this is defined in the optional `Partner` attribute here) - -That's great to know from a theory perspective, but what does this mean in practical terms? - -Well, for starters, it means that we can use the relationship defined to find out the details of the category for a product. For example, taking the first Northwind product, we can navigate to the corresponding category. We can even request the product _and_ category information together. - -Here are three relative URLs, and the default JSON representations of those URLs, that demonstrate this: - -A read of the first Northwind product, via /Products(1): - -```JSON -{ - "@odata.context": "https://services.odata.org/V4/Northwind/Northwind.svc/$metadata#Products/$entity", - "@odata.etag": "W/\"1,1\"", - "ProductID": 1, - "ProductName": "Chai", - "SupplierID": 1, - "CategoryID": 1, - "QuantityPerUnit": "10 boxes x 20 bags", - "UnitPrice": 18, - "UnitsInStock": 39, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": false -} -``` - -Here, we only see the value of the `CategoryID` property (`1`), but none of the details of the category itself. - -We can ask to see all the information on the category for that product, with /Products(1)/Category: - -```JSON -{ - "@odata.context": "https://services.odata.org/V4/Northwind/Northwind.svc/$metadata#Categories/$entity", - "CategoryID": 1, - "CategoryName": "Beverages", - "Description": "Soft drinks, coffees, teas, beers, and ales", - "Picture": "FRwvAAIA..." -} -``` - -> From a path info perspective, this (`/Products(1)/Category`) is nothing special; it's just the same as specifying a normal (non-navigation) property, such as `ProductName`, like this: `/Products(1)/ProductName` - -And this is an example of a request for product _and_ category information to be returned in the same response, using the OData system query option `$expand`: /Products(1)?$expand=Category: - -```JSON -{ - "@odata.context": "https://services.odata.org/V4/Northwind/Northwind.svc/$metadata#Products/$entity", - "@odata.etag": "W/\"1,1\"", - "ProductID": 1, - "ProductName": "Chai", - "SupplierID": 1, - "CategoryID": 1, - "QuantityPerUnit": "10 boxes x 20 bags", - "UnitPrice": 18, - "UnitsInStock": 39, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": false, - "Category": { - "CategoryID": 1, - "CategoryName": "Beverages", - "Description": "Soft drinks, coffees, teas, beers, and ales", - "Picture": "FRwvAAIA..." - } -} -``` - -### Take a look at the Northwind Category -> Product relationship - -To balance things out, let's take a brief look at the other entity type in this relationship - and that's the `Category` entity type. Here's what the definition looks like in the metadata document: - -```XML - - - - - - - - - - -``` - -Of course, our gaze falls immediately upon the single `NavigationProperty` here which is `Products`, the "other end of the connection" to what we looked at in the previous step. - -Notice here that in contrast to the type defined for the `NavigationProperty` in the previous step, the type defined for this one is a [built-in abstract type](http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_Toc453752518) - a collection of zero or more entities of the type `NorthwindModel.Product`. - -Thinking about what this data model is about, this makes sense, of course. There are categories, and there are products that belong to categories. There may be many products belonging to a single category (say, Beverages), and there may theoretically also be categories for which there are no products. Note also that a product can only belong to a single category here. - -This is a relationship that might be commonly expressed like this: - -``` -+------------+ +------------+ -| Categories | 1 ------- 0..N | Products | -+------------+ +------------+ -``` - -Before moving on to start creating a relationship like this in our own `Northbreeze` OData service, let's just try out an OData query operation on Northwind that uses this `NavigationProperty` that we've been looking at, and that is a count of the products in the seventh category ("Produce"): /Categories(7)/Products/$count. This should return a simple numeric value (which is 5, at the time of writing). - -Staying with Northwind, let's look at the details for that seventh category next, including a list of products in that category /Categories(7)?$expand=Products (only a couple of the products are shown in this sample output, for brevity): - -```JSON -{ - "@odata.context": "https://services.odata.org/V4/Northwind/Northwind.svc/$metadata#Categories/$entity", - "CategoryID": 7, - "CategoryName": "Produce", - "Description": "Dried fruit and bean curd", - "Picture": "FRwvAAIA...", - "Products": [ - { - "@odata.etag": "W/\"3,7\"", - "ProductID": 7, - "ProductName": "Uncle Bob's Organic Dried Pears", - "SupplierID": 3, - "CategoryID": 7, - "QuantityPerUnit": "12 - 1 lb pkgs.", - "UnitPrice": 30, - "UnitsInStock": 15, - "UnitsOnOrder": 0, - "ReorderLevel": 10, - "Discontinued": false - }, - { - "@odata.etag": "W/\"6,7\"", - "ProductID": 14, - "ProductName": "Tofu", - "SupplierID": 6, - "CategoryID": 7, - "QuantityPerUnit": "40 - 100 g pkgs.", - "UnitPrice": 23.25, - "UnitsInStock": 35, - "UnitsOnOrder": 0, - "ReorderLevel": 0, - "Discontinued": false - } - ] -} -``` - -### Add a new Categories entity to your Northbreeze schema - -Now it's time to build out your own OData service to reflect a similar relationship. As it's a cut down version of Northwind, and we want to concentrate here on the relationships rather than anything else, you'll only include the category ID, name, and description properties. - -Assuming you have your App Studio dev space already open at the workspace you were using in the previous tutorial (see the instructions at the top of this tutorial), open up a terminal (menu path **Terminal** > **New Terminal**) and start `cds watch`, whereupon you should see some familiar output like this: - -```Shell/Bash -user: northbreeze $ cds watch - -cds serve all --with-mocks --in-memory? -live reload enabled for browsers - - ___________________________ - -[cds] - loaded model from 2 file(s): - - srv/service.cds - db/schema.cds - -[cds] - connect using bindings from: { registry: '~/.cds-services.json' } -[cds] - connect to db > sqlite { url: ':memory:' } - > init from db/data/northbreeze-Products.csv -/> successfully deployed to in-memory database. - -[cds] - using auth strategy { - kind: 'mocked', - impl: '../../../../managed-content/globals/pnpm/5/.pnpm/@sap+cds@8.3.1_express@4.21.1/node_modules/@sap/cds/lib/auth/basic-auth' -} - -[cds] - using new OData adapter -[cds] - serving Main { path: '/odata/v4/main' } - -[cds] - server listening on { url: 'http://localhost:4004' } -[cds] - launched at 10/24/2024, 10:22:57 AM, version: 8.3.1, in: 371.49ms -[cds] - [ terminate with ^C ] -``` - -Using `cds watch` gives you a lovely tight [feedback loop](https://martinfowler.com/articles/developer-effectiveness.html#FeedbackLoops) where you can make changes, observe their effects, make further changes, and experience the joy of learning-by-doing. - -Now your feedback loop is active, add a new `Categories` entity definition to your `db/schema.cds` file, so that the resulting entire contents looks like this: - -```CDS -namespace northbreeze; - -entity Products { - key ProductID : Integer; - ProductName : String; - UnitsInStock : Integer; -} - -entity Categories { - key CategoryID : Integer; - CategoryName : String; - Description : String; -} -``` - -> Again, try to resist the temptation just to copy/paste this new definition, and type it in manually instead to learn more about the support for CDS that App Studio has (via the [SAP CDS Language Support extension](https://www.youtube.com/watch?v=eY7BTzch8w0)). - -As soon as the file is saved, you'll see the `cds watch` process restart things. Take a look at the metadata definition to check if you can now see the `Categories` entity type. If you've previously closed the tab for this, remember that you can get to the list of ports that the App Studio is exposing for you in this dev space with the **Ports: Preview** command (use the Command Palette). - -Following the link to the preview of port 4004 (which is where we find the CAP Welcome page), and from there the link to the `/main/$metadata` resource, you may be unsurprised to see that your new entity type isn't there: - -```XML - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -You know why, don't you? Because you haven't yet exposed that entity definition at the **service layer**. - -Do that now, by adding a second line to the definition details for the `Main` service in your `srv/service.cds` file, adding a reference to the `Categories` entity, so the entire file then looks like this: - -```CDS -using northbreeze from '../db/schema'; - -service Main { - entity Products as projection on northbreeze.Products; - entity Categories as projection on northbreeze.Categories; -} -``` - -You should notice that `cds watch` restarts things - when that happens, re-request the metadata document in the other tab, and you should then see the new entity type: - -```XML - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -So far so good, right? Well, let's see. Let's add some category data in the next step. - -### Add data for the Categories entity - -In the same way as in the [tutorial that precedes this](odata-05-data-model-service), you should now seed this entity with some data, using the same Comma Separated Value (CSV) file based technique as before. - -In the `db/data/` directory, next to the existing `northbreeze-Products.csv` file, create a new file `northbreeze-Categories.csv` and save the following content into it (including the header line): - -```CSV -CategoryID,CategoryName,Description -1,"Beverages","Soft drinks, coffees, teas, beers, and ales" -2,"Condiments","Sweet and savory sauces, relishes, spreads, and seasonings" -3,"Confections","Desserts, candies, and sweet breads" -4,"Dairy Products","Cheeses" -5,"Grains/Cereals","Breads, crackers, pasta, and cereal" -6,"Meat/Poultry","Prepared meats" -7,"Produce","Dried fruit and bean curd" -8,"Seafood","Seaweed and fish" -``` - -Back at the CAP Welcome page (the root resource on the exposed port 4004), you can now follow links to both the `Categories` and `Products` entity sets, and see some data for both. - -But there's something missing - there's no way to get from a product to a category, or from a category to a list of products. - -### Define relationships in your schema - -While we can retrieve data for both entities, there's no connection yet between them. Let's address that in this step. - -We understand what a (two-way) relationship looks like when expressed in OData metadata XML. Now you're going to define such a relationship at the schema layer in your simple OData service, drawing a link between the `Products` and `Categories` entities. You're going to do this declaratively in CDS, using [Associations](https://cap.cloud.sap/docs/cds/cdl#associations). - -Open up the `db/schema.cds` file and add a new element to each of the entities, so that the resulting contents look like this: - -```CDS -namespace northbreeze; - -entity Products { - key ProductID : Integer; - ProductName : String; - UnitsInStock : Integer; - Category : Association to Categories; -} - -entity Categories { - key CategoryID : Integer; - CategoryName : String; - Description : String; - Products : Association to many Products - on Products.Category = $self; -} -``` - -> Note the terminology difference for what we might think of as "fields"; OData uses the term "property", and in CAP, or more specifically in CDS, the term "element" is used. - -You've added a pair of pointers, effectively, but note that they're different: - -- the `Category` property is a pointer to a single instance of the `Categories` entity -- the `Products` property is a pointer to zero or more instances of the `Products` entity, and has a qualifying expression to ensure the right relationships - -These types of association you've added are "managed" associations, and allow you to remain at the "what you want" level rather than have to descend to the "how you need to achieve it" level. That said, we need to understand what these declarations mean in terms of OData metadata. Let's find out. - -On saving this `db/schema.cds` file, you should notice that the `cds watch` process (that you should still have running in your terminal session) should restart things. At this point, go to the CAP Welcome page (served as usual on the exposed port 4004) and retrieve the metadata document again. It should now look something like this: - -```XML - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -Excellent! We now not only have the definitions of the entity types `Products` and `Categories`, but these definitions also contain appropriate `NavigationProperty` elements describing the two-way relationship between them. - -Our extended simple OData service's metadata is now what we want it to be, but there's one more thing to do. While we have an indication of a relationship in the metadata, there's no indication or evidence of that relationship in our (CSV-based) test data. - -What do we need to do here? Well, take a closer look at the XML above. The relationship is qualified by a referential constraint, that refers to a new property (which now also appears as a `Property` element within the `EntityType` definition). That property is `Category_CategoryID`. This has been generated by the managed association and is a clue for us as to what we need to do in the data to bring the actual relationships to life. - -### Modify and extend the data for the Products entity - -In fact, if you look at the `Products` entity set (follow the `Products` hyperlink from the CAP Welcome page), you will see that this `Category_CategoryID` is indeed a new property, and there are currently no values for it in any of the entities: - -```JSON -{ - "@odata.context": "$metadata#Products", - "value": [ - { - "ProductID": 1, - "ProductName": "Chai", - "UnitsInStock": 39, - "Category_CategoryID": null - }, - { - "ProductID": 2, - "ProductName": "Chang", - "UnitsInStock": 17, - "Category_CategoryID": null - }, - { - "ProductID": 3, - "ProductName": "Aniseed Syrup", - "UnitsInStock": 13, - "Category_CategoryID": null - } - ] -} -``` - -Let's rectify that now, and also add more products. - -We know that each entity in the [Products entity set in the original Northwind service](https://services.odata.org/V4/Northwind/Northwind.svc/Products) contains a value for the property `CategoryID`; so it's just a case of taking all that data and putting it into our `db/data/northbreeze-Products.csv` file. There's a file associated with this tutorial that's been prepared for you: - - - -Open it and copy the entire contents - then use that to replace what you have in `db/data/northbreeze-Products.csv`. - -After doing this, the contents of that file should look like this: - -```CSV -ProductID,Category_CategoryID,ProductName,UnitsInStock -1,1,"Chai",39 -2,1,"Chang",17 -3,2,"Aniseed Syrup",13 -4,2,"Chef Anton's Cajun Seasoning",53 -5,2,"Chef Anton's Gumbo Mix",0 -6,2,"Grandma's Boysenberry Spread",120 -7,7,"Uncle Bob's Organic Dried Pears",15 -8,2,"Northwoods Cranberry Sauce",6 -9,6,"Mishi Kobe Niku",29 -... -``` - -Note that in the header line, we have the property names - they should match the names of the elements in the entity definition; this includes the link to the `Categories` entity, represented by `Category_CategoryID`. - -On saving the file, you should again see the `cds watch` process restart things, and also (as before) a successful loading of data, indicated by these two lines in the log output: - -```Shell/Bash -> init from db/data/northbreeze-Products.csv -> init from db/data/northbreeze-Categories.csv -``` - -Go back to the `Products` entity set in your service that you were looking at in the previous step, and re-request it. You should now see not only more products, but also that there's a value for the `Category_CategoryID` properties in each of the entities. Here's a cut down version of what you should see: - -```JSON -{ - "@odata.context": "$metadata#Products", - "value": [ - { - "ProductID": 1, - "ProductName": "Chai", - "UnitsInStock": 39, - "Category_CategoryID": 1 - }, - { - "ProductID": 2, - "ProductName": "Chang", - "UnitsInStock": 17, - "Category_CategoryID": 1 - }, - { - "ProductID": 3, - "ProductName": "Aniseed Syrup", - "UnitsInStock": 13, - "Category_CategoryID": 2 - } - ] -} -``` - -That looks good! - -### Exercise the new relationships in your OData service - -Let's use this final step to check if we can now explore the relationship between the two entities in our simple `Northbreeze` OData service. Why don't we use the same approach and set of requests from the first step in this tutorial? - -All of these request URLs will be based on and relative to your `Northbreeze` OData service document - use the CAP Welcome page to get to that service document, and then append each of the relative path info examples onto that. - -In other words, in the CAP Welcome page, select the `/odata/v4/main` hyperlink from the `/odata/v4/main / $metadata` line directly following the "Service Endpoints" heading: - -![main hyperlink](main-hyperlink.png) - -This should take you to the service document, with a URL that looks similar to this: - -`https://port4004-workspaces-ws-czcx7.us10.trial.applicationstudio.cloud.sap/odata/v4/main` - -All paths you use in the rest of this step should be relative to (that is, appended to) this service document URL. - -Let's start by requesting just the first `Northbreeze` product, via `/Products(1)` - -```JSON -{ - "@odata.context": "$metadata#Products/$entity", - "ProductID": 1, - "ProductName": "Chai", - "UnitsInStock": 39, - "Category_CategoryID": 1 -} -``` - -Here, we only see the value of the `Category_CategoryID` property (`1`), but none of the details of the category itself. - -We can ask to see all the information on the category for that product, with `/Products(1)/Category`: - -```JSON -{ - "@odata.context": "../$metadata#Categories/$entity", - "CategoryID": 1, - "CategoryName": "Beverages", - "Description": "Soft drinks, coffees, teas, beers, and ales" -} -``` - -And this is the product _and_ category example request again, a request where we expect information from both entities to be returned in the same response, using the `$expand` OData system query option `/Products(1)?$expand=Category`: - -```JSON -{ - "@odata.context": "$metadata#Products/$entity", - "ProductID": 1, - "ProductName": "Chai", - "UnitsInStock": 39, - "Category_CategoryID": 1, - "Category": { - "CategoryID": 1, - "CategoryName": "Beverages", - "Description": "Soft drinks, coffees, teas, beers, and ales" - } -} -``` - -As you can see, your simple OData service is looking great. With just a few lines of declarative schema definitions, plus pass-through exposure of the entities in a minimal service definition, and some CSV based data, you have yourself a fully functioning OData service. - -Congratulations! - diff --git a/tutorials/odata-07-extend-custom-code/odata-07-extend-custom-code.md b/tutorials/odata-07-extend-custom-code/odata-07-extend-custom-code.md deleted file mode 100644 index ace2f4f64d..0000000000 --- a/tutorials/odata-07-extend-custom-code/odata-07-extend-custom-code.md +++ /dev/null @@ -1,326 +0,0 @@ ---- -parser: v2 -author_name: DJ Adams -author_profile: https://github.com/qmacro -auto_validation: true -primary_tag: software-product-function>sap-cloud-application-programming-model -tags: [ software-product-function>sap-business-application-studio, programming-tool>odata, tutorial>beginner ] -time: 20 ---- - -# Extend the Built-In OData Features with Custom Code - Learn how to customize your OData service with event handlers. - -## You will learn -- What custom event handlers are -- Where and how to define a simple event handler -- How to use a custom event handler to define an OData function import - -## Intro -This tutorial assumes you've completed the tutorial [Extend your Simple Data Model with a Second Entity](odata-06-extend-odata-service). If you have done, you'll have an OData service `Northbreeze` with two related entities. All OData operations - create, read, update, delete and query - are supported out of the box. - -In this tutorial, you'll learn how to add custom behaviour, in the form of handlers, to make your OData service do what you want it to do, beyond the standard operation handling. - -Before you start, open up the workspace in the SAP Business Application Studio (App Studio) dev space you were using in that previous tutorial, ready to add code. - ---- - -### Review the product data - -Let's take the `Products` entity as the target for our explorations of custom functions. Remind yourself of what the data looks like by starting up the service with `cds watch` in a terminal, just like you've done in the previous tutorial. - -Open up the service in a new browser tab or window, and navigate to the `Products` entity set. You should see the familiar list of products, with values for the properties in each case, and it should look like this (only the first two products are shown here): - -```JSON -{ - "@odata.context": "$metadata#Products", - "value": [ - { - "ProductID": 1, - "ProductName": "Chai", - "UnitsInStock": 39, - "Category_CategoryID": 1 - }, - { - "ProductID": 2, - "ProductName": "Chang", - "UnitsInStock": 17, - "Category_CategoryID": 1 - } - ] -} -``` - -Remember that at this stage your fully functioning OData service is a result of purely declarative definitions. Now it's time to add some simple business logic. - -### Create a service implementation - -Business logic in OData services belongs in a [service implementation](https://cap.cloud.sap/docs/node.js/core-services#implementing-services). The simplest way to do this is to create a `service.js` file in the same directory as your `service.cds` file, i.e. in the `srv/` directory. The framework will automatically recognize and use this "sibling" file. - -In a new `srv/service.js` file, add the following JavaScript: - -```JavaScript -module.exports = srv => { - srv.after('READ', 'Products', items => { - return items.map(item => { - if (item.UnitsInStock > 100) { - item.ProductName += ' SALE NOW ON!' - } - return item - }) - }) -} -``` - -Let's stare at this for a few moments. You won't be far wrong if you guess that it's something to do with adding an indication of a product sale for items where there's a high number of units in stock. But how does it work, and in what context? - -First, in order to be used by CAP's runtime framework, a service implementation file such as this needs to offer a function definition for the framework to call on startup. This "offer" is via Node.js's module export mechanism, and what's exported here is the anonymous function which (apart from the `module.exports =` part itself) is the entire file contents. - -When the framework finds and invokes this anonymous function, it passes a server object, which we can use to define event handlers via the [Handler Registration API](https://cap.cloud.sap/docs/node.js/core-services#srv-on-before-after). That's why we have a single `srv` parameter defined, and that's what we use to access the `srv.after` API to declare a function to be run under specific circumstances (more on that shortly). - -Examining that API call, we see this pattern: - -```JavaScript -srv.after('READ', 'Products', items => { ... }) -``` - -This is how we can add custom business logic to extend the standard handling that is provided for us out of the box. Specifically, this call defines a function (`items => { ... }`) that should be executed whenever there's an OData READ (or QUERY) operation on the `Products` entity data. - -The use of the specific `after` API call is quite common, and allows us to jump onto the request processing flow towards the end, when the heavy lifting of data retrieval from the persistence layer has been done for us. As well as `after`, the Handler Registration API supports `before` and `on` events, but right now, `after` is what we want here. - -What does the function specified in this API call do? As you'd correctly guessed, it just adds a string on to the end of the value for each of the product names, specifically for the cases where the number of units in stock is high. - -In its simplest form, the function provided is given the data retrieved, and whatever the function does ends up in the response to the original request. Note, however, that in the context of the `after` API call, the handler function cannot change the "shape" of the data, such as omit specific items. We'll look at how to do that later on in this tutorial. - -So with the simple `map` invocation, we are modifying the values for the `ProductName` properties of those items where the `UnitsInStock` value is more than 100. - -Once you've added this code and saved the file, check that the `cds watch` process has restarted the service successfully, and have another look at the `Products` entity set. - -Here's an example of what you should see; this data was retrieved using the system query options `$skip=4` and `$top=2` to narrow in on just two of the products, with "Grandma's Boysenberry Spread" having 120 units in stock and the extra "SALE NOW ON!" text: - -```JSON -{ - "@odata.context": "$metadata#Products", - "value": [ - { - "ProductID": 5, - "ProductName": "Chef Anton's Gumbo Mix", - "UnitsInStock": 0, - "Category_CategoryID": 2 - }, - { - "ProductID": 6, - "ProductName": "Grandma's Boysenberry Spread SALE NOW ON!", - "UnitsInStock": 120, - "Category_CategoryID": 2 - } - ] -} -``` - -### Modify the custom code - -That's great, but let's look now at a simple example of where we might want to change the shape of the data, or, as the documentation describes it, to make "asynchronous modifications". - -If we wanted to reduce the list of products returned - to omit those products that had a low stock count - we would not use the `after` API call, but the `on` API call, and provide a function that effectively replaces the standard processing. - -The prospect of doing this isn't as daunting as it first seems, as we're given everything that we need to be able to do this. - -Remove the entire call to `srv.after` and replace it with a call to `srv.on`, so that the resulting `service.js` content looks like this: - -```JavaScript -module.exports = srv => { - srv.on('READ', 'Products', async (req, next) => { - const items = await next() - return items.filter(item => item.UnitsInStock > 100) - }) -} -``` - -This differs from the previous step thus in a number of ways. - -First, we're using the `on` API call to provide a function that should be run _instead of_ standard processing when product data is requested. - -Next, the function we provide doesn't expect the data (like we did in the previous function, with the `items` parameter), as the data will not be provided to it. Instead, it's expecting to be given the original request object (`req`), and a reference to the subsequent standard handler (`next`). We can use this `next` handler to actually do the work of retrieving the data for us, and are then free to do what we want with it. - -Finally, because we're wanting to call that `next` function synchronously (with `await`), we must declare our function with the `async` keyword. - -Once we have the data, in `items`, we return a filtered subset that only includes those products where the value of the `UnitsInStock` property is greater than 100. - -Once you have this new implementation saved, and your service has restarted, check the `Products` entity set once more, and you should see only a small number of entries; if you're still using the data provided in the tutorials prior to this, there should be 10. - -### Define a function import - -That's great, but there's more that can be done in such a service implementation file. - -The two JavaScript functions you've provided so far have been to affect the processing of standard OData operations on the `Products` entity. But OData V4 defines [actions and functions](http://docs.oasis-open.org/odata/odata/v4.0/os/part1-protocol/odata-v4.0-os-part1-protocol.html#_Toc372793604), in addition to entities. Actions and functions can be bound, or unbound. Think of such things as the next generation of function imports that you might know from OData V2. - -So to round off this tutorial, let's define a simple [unbound function](https://cap.cloud.sap/docs/cds/cdl#actions) on our OData service. - -> Bear in mind the distinction between "function" in the JavaScript sense, and "function" in the OData sense. - -While the custom logic that we've written so far has been implicit in our OData service's definition, as they work as handlers for existing operations, an OData function needs to be explicitly declared and described in the service's metadata. - -To do this, extend the CDS definition in `srv/service.cds`, where you should add a line to define a function `TotalStockCount` in the `Main` service. The resulting content of `srv/service.cds` should look like this: - -```CDS -using northbreeze from '../db/schema'; - -service Main { - entity Products as projection on northbreeze.Products; - entity Categories as projection on northbreeze.Categories; - function TotalStockCount() returns Integer; -} -``` - -At this point, it's worth checking to see if this has any effect on your OData service. Once the CDS file is saved, and your service has restarted, navigate to the metadata document (that's the relative path `/odata/v4/main/$metadata`, but you knew that already, right?). It should look something like this: - -```XML - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -We can see that this simple declarative definition has already had an effect - there is evidence of this new function import: - -* in the `EntityContainer` element where it's listed alongside the two entity sets -* defined near the bottom, after the definitions of the `Categories` and `Products` entity types - -The function import definition here in the metadata document reflects what we intended; in particular, the function is called `TotalStockCount`, is unbound, and has an integer return type: - -```XML - - - -``` - -Great. Now we can get to writing the implementation of this function import. - -### Implement the function import - -The implementation of this function import might as well go in the same `srv/service.js` file as before, to keep things simple. Here's what the entire contents of the file should look like with all the additions: - -```JavaScript -const { Products } = cds.entities('northbreeze') - -module.exports = srv => { - srv.on('READ', 'Products', async (req, next) => { - const items = await next() - return items.filter(item => item.UnitsInStock > 100) - }) - - srv.on('TotalStockCount', async (req) => { - const items = await cds.tx(req).run(SELECT.from(Products)) - return items.reduce((a, item) => a + item.UnitsInStock, 0) - }) -} -``` - -Let's look at what's new. - -First, at the top of the file, there is this new line: - -```JavaScript -const { Products } = cds.entities('northbreeze') -``` - -Here, we're using destructuring to pull out the `Products` entity definition from the `northbreeze` service, via the `cds` module. - -Next, directly below the existing `srv.on('READ', 'Products', async (req, next) => { ... })` call that you already had, there is now a second call to the Handler Registration API to define a handler for the `TotalStockCount` function import. - -This handler is an anonymous function just like the other, except that it only expects and needs the request (in `req`). It uses this as a context for the transaction that it creates, within which it then retrieves the product data. - -> Note that `Products` is a constant, not a literal string, and refers to the entity set object that we retrieved via `cds.entities` earlier. - -The product data retrieved is stored in the `items` constant, and looks like this: - -```JavaScript -[ { ProductID: 1, - ProductName: 'Chai', - UnitsInStock: 39, - Category_CategoryID: 1 }, - { ProductID: 2, - ProductName: 'Chang', - UnitsInStock: 17, - Category_CategoryID: 1 }, - ... -] -``` - -It's then just a simple case of summing the values of the `UnitsInStock` property for each of the items, which we do cleanly with a simple [reduce](https://www.google.com/search?q=site%3Aqmacro.org+reduce) function, and return the result. Being a numeric value, the result type corresponds to what we defined as what the function import returns, back in the CDS file: - -```CDS -function TotalStockCount() returns Integer; -``` - -Once you've saved the service implementation file and the `cds watch` process has restarted the service, you should try this function import out. Switch to the other tab and navigate to the relative path: - -``` -/odata/v4/main/TotalStockCount() -``` - -The response should look something like this: - -```JSON -{ - "@odata.context": "$metadata#Edm.Int32", - "value": 3119 -} -``` - -That is, there are a total of 3119 stock units across all products. - -Well done! You've now successfully implemented an OData V4 unbound function, and hopefully feel comfortable enough to implement your own custom business logic for your CAP-powered OData services. - - ----