Leveraging Object Store BTP Service with SAP CAP: A Guide to Storing Static Files

In today's digital landscape, businesses are constantly seeking efficient ways to manage their data, including static files such as images, documents, and multimedia content. With the advent of cloud computing, storing and accessing these files has become easier and more scalable than ever before. One such solution is leveraging the Object Store service within the SAP Business Technology Platform (BTP) in conjunction with SAP Cloud Application Programming Model (CAP).

In this blog post, we'll explore how you can seamlessly integrate Object Store BTP Service with SAP CAP to store static files, enabling efficient data management within your applications.

Introducing Object Store BTP Service

Object Store is a cloud-based storage solution offered as part of the SAP Business Technology Platform. It provides scalable and durable object storage that can be seamlessly integrated into your applications. With Object Store, you can securely store and retrieve files of any type, making it an ideal solution for managing static assets in your applications.

Benefits of Using Object Store with SAP CAP

Integrating Object Store BTP Service with SAP CAP offers several benefits:

  1. Scalability: Object Store provides scalable storage that can accommodate large volumes of static files, ensuring your application can handle growth in data demand.
  2. Durability: Files stored in Object Store are redundantly stored across multiple data centers, ensuring high durability and data resilience.
  3. Integration: Seamlessly integrate static file storage capabilities into your CAP applications without the need for complex configuration or custom development.
  4. Security: Leverage built-in security features of SAP BTP to ensure that access to Object Store resources is securely managed and controlled.
Integrating Object Store with SAP CAP

To integrate Object Store BTP Service with SAP CAP, follow these steps:

  1. Set Up Object Store Service: Begin by provisioning the Object Store service within your SAP BTP account. You can do this through the SAP BTP cockpit or using the Cloud Foundry command-line interface (CF CLI).

  2. Install Required Dependencies: In your CAP project, install the necessary dependencies to interact with Object Store. This may include SDKs or libraries provided by SAP for accessing Object Store APIs.

  3. Configure Object Store Service Binding: Create a service binding between your CAP application and the Object Store service instance. This will allow your application to securely access the Object Store resources.

  4. Implement File Upload and Download: Within your CAP application, implement functionality to upload and download static files to and from Object Store. This may involve creating RESTful endpoints or service functions to handle file operations.

  5. Secure Access Control: Ensure that access to Object Store resources is securely managed within your application. Utilize authentication and authorization mechanisms provided by SAP BTP to control access to sensitive data.

What are we building?

We will create a simple CAP application with a single endpoint to upload and download files. To simplify this tutorial, we will limit the type of files to PNG images, however we can easily change this to fit any kind of file.

Object Store service exposes the access to the static storage service of the infrastructure provider. This depends on your BTP account configuration. In my BTP Account I have AWS as IaaS provider, so our storage will be an S3 bucket.

BTP Object Store Architecture

Prerequisites

To build this you need a subscription to the Object Store service in BTP with the standard plan. More information about this service here.

It is important to understand the general concepts of Object Store service, and also to assign the corresponding quotas and entitlements for your subaccount.

Furthermore we need Node.js and a basic knowledge of SAP CAP, the Cloud Foundry CLI, and the CAP CLI.

Step 1: Create a basic CAP application

Follow the basic steps to create a CAP app in the folder of your choice.

In my case:

cds init CAP_ObjectStore

Step 2: Define the db schema and the service to expose the endpoint

In /db folder create a schema.cds file with the following code:

namespace media.db;

entity Pictures {
  key ID : UUID;
  @Core.MediaType: 'image/png'
  content : LargeBinary;
}

Note that we are forcing image/png as Mime Type. As we said before you can easily change this to fit any other type of file

In /srv folder create a media-service.cds file with the following code:

using media.db as db from '../db/schema';

service MediaService {
  entity Pictures as projection on db.Pictures;
}

Step 3: Create an instance of the Object Store service and a service key to create a binding with the application

To do that I use the CF CLI with the following commands:

cf create-service objectstore standard cap-objectstore-instance
cf create-service-key cap-objectstore-instance cap-objectstore-instance-key
cds bind --to cap-objectstore-instance:cap-objectstore-instance-key

Those commands create an instance of the object store and creates a binding to the local CAP application. The cds bind command creates a file in your project folder with the credentials needed so the CAP app binds to the service instance when running it in hybrid mode.

Step 4: Create the custom handler for our service endpoint

Here is where the magic happens. We will rewrite the /Pictures endpoint implementation so we intercept the data stream of the uploaded file and send it to the AWS S3 bucket using the native AWS SDK

First we need to install the dependency

npm i --save aws-sdk

Now in the /srv folder, create a media-service.js file with the following code:

const AWS = require('aws-sdk');

/* Initialize S3 bucket */
function _initializeS3Bucket(vcap_services) {
  const credentials = new AWS.Credentials(
    vcap_services.objectstore[0].credentials.access_key_id,
    vcap_services.objectstore[0].credentials.secret_access_key
  );
  AWS.config.update({
    region: vcap_services.objectstore[0].credentials.region,
    credentials: credentials
  });
  return new AWS.S3({
    apiVersion: '2006-03-01'
  });
}

/* Get object stream from S3 */
function _getObjectStream(vcap_services, s3, objectKey) {
  const params = {
    Bucket: vcap_services.objectstore[0].credentials.bucket,
    Key: objectKey
  };
  return s3.getObject(params).createReadStream();
}

module.exports = (srv) => {
  const vcap_services = JSON.parse(process.env.VCAP_SERVICES);
  const s3 = _initializeS3Bucket(vcap_services);

  srv.on('UPDATE', 'Pictures', async (req) => {
    const params = {
      Bucket: vcap_services.objectstore[0].credentials.bucket,
      Key: req.data.ID,
      Body: req.data.content,
      ContentType: 'image/png'
    };
    s3.upload(params, function (err, data) {
      console.log(err, data);
    });
  });

  srv.on('READ', 'Pictures', (req, next) => {
    if (!req.data.ID) {
      return next();
    }

    return {
      value: _getObjectStream(vcap_services, s3, req.data.ID)
    };
  });
};

With this code we are reimplementing the UPDATE and READ handlers of the endpoint. In the UPDATE handler we create an upload stream to the S3 bucket to store the file. In the READ handler, we do the opposite, create a read stream to download the file from S3.

Step 5: Test the application

Let's run the CAP app in hybrid mode.

cds watch --profile hybrid

This executes the app in localhost and at the same time gets the binding credentials for the object store service we bound in Step 3, and bulk them into the VCAP_SERVICE environment variable.

Now create a /test folder and inside create a requests.http file and include a PNG file of your choice in the same folder. In my case test.png

Fill the requests.http file with the following code

### Create an entry in the DB. Generates an UUID
POST http://localhost:4004/odata/v4/media/Pictures
Content-Type: application/json

{}


### Upload the file with the entry ID is key name
PUT http://localhost:4004/odata/v4/media/Pictures(368e5f5e-4744-4387-a54a-d474f82ab232)/content
Content-Type: image/png

< ./test.png

### Downloads the file by file ID
GET http://localhost:4004/odata/v4/media/Pictures(f0df9601-d362-466c-931c-f2b0087d8b01)/content

Now you can click on send request after each comment to fire the corresponding request and test the service.

The way it works is the following:

  1. You have to send a POST request to create an entry in the DB for that file. This will generate a unique ID for this file, which will be used to store the file in S3 avoiding overwritting due to unexpected upload of files with the same name.
  2. Next, send a PUT request against the same endpoing. This PUT request include the ID we just generated and the file to upload
  3. Last, if we want to download the file, we just need to send a GET request agains the endpoint providing the specific ID.

Step 6: Deploy app as MTA

To deploy the CAP app to our Cloud Foundry account, we just need to define the MTA file as follows:

_schema-version: '3.1'
ID: CAP_ObjectStore
version: 1.0.0
description: 'A simple CAP project.'
parameters:
  enable-parallel-deployments: true
build-parameters:
  before-all:
    - builder: custom
      commands:
        - npx cds build --production
modules:
  - name: CAP_ObjectStore-srv
    type: nodejs
    path: gen/srv
    parameters:
      buildpack: nodejs_buildpack
    build-parameters:
      builder: npm
    provides:
      - name: srv-api # required by consumers of CAP services (e.g. approuter)
        properties:
          srv-url: ${default-url}
    requires:
      - name: cap-objectstore-instance
      - name: CAP_ObjectStore-db
      - name: CAP_ObjectStore-auth

  - name: CAP_ObjectStore-db-deployer
    type: hdb
    path: gen/db
    parameters:
      buildpack: nodejs_buildpack
    requires:
      - name: CAP_ObjectStore-db

resources:
  - name: cap-objectstore-instance
    type: objectstore
    parameters:
      service: objectstore
      service-plan: standard
      service-name: cap-objectstore-instance
  - name: CAP_ObjectStore-db
    type: com.sap.xs.hdi-container
    parameters:
      service: hana
      service-plan: hdi-shared
  - name: CAP_ObjectStore-auth
    type: org.cloudfoundry.managed-service
    parameters:
      service: xsuaa
      service-plan: application
      path: ./xs-security.json
      config:
        xsappname: CAP_ObjectStore-${org}-${space}
        tenant-mode: dedicated

Note that I have included an XSUAA instance and a HANA HDI container, which are bound the CAP server.

Now just build and deploy:

mbt build mta.yaml
cf deploy mta_archives/CAP_ObjectStore_1.0.0.mtar

Recap

With this project we have created a CAP application exposing one endpoint to manage static BLOB files and store them in an AWS S3 bucket using the SAP BTP Object Store Service.

Published on
  • February 21, 2024
Category
Tags
About Me
Me

Hi! I'm Rafa, a fullstack software developer living in Madrid (Spain).

I love to code and the freedom it gives me to create things out of ideas. When I'm not coding, I'm usualy practicing sports (kitesurfing, team handball, padel tenis, golf, ...), reading or getting some beers while enjoying the spanish sun with some friends.

Wanna get some? Check out the About page to know how to reach out to me.