Files
gh-secondsky-sap-skills-ski…/references/development.md
2025-11-30 08:54:56 +08:00

9.5 KiB

Development Reference

Development patterns and best practices for SAP BTP applications.

Source: https://github.com/SAP-docs/sap-btp-cloud-platform/tree/main/docs/30-development


Table of Contents

  1. Multi-Target Applications
  2. Application Router
  3. CAP Development
  4. Service Bindings
  5. CI/CD Pipelines
  6. Deployment Strategies

Multi-Target Applications

MTA Structure

my-app/
├── mta.yaml              # MTA descriptor
├── srv/                  # Backend service
│   ├── package.json
│   └── src/
├── app/                  # Frontend
│   └── webapp/
├── db/                   # Database artifacts
│   └── src/
└── xs-security.json      # Security config

mta.yaml Template

_schema-version: "3.1"
ID: my-app
version: 1.0.0
description: My SAP BTP Application

parameters:
  enable-parallel-deployments: true

build-parameters:
  before-all:
    - builder: custom
      commands:
        - npm install --production

modules:
  # Backend service
  - name: my-app-srv
    type: nodejs
    path: srv
    parameters:
      buildpack: nodejs_buildpack
      memory: 256M
    build-parameters:
      builder: npm
    requires:
      - name: my-app-db
      - name: my-app-auth
    provides:
      - name: srv-api
        properties:
          srv-url: ${default-url}

  # Database deployer
  - name: my-app-db-deployer
    type: hdb
    path: db
    parameters:
      buildpack: nodejs_buildpack
    requires:
      - name: my-app-db

  # UI module
  - name: my-app-ui
    type: html5
    path: app
    build-parameters:
      builder: custom
      commands:
        - npm run build
      supported-platforms: []

  # App Router
  - name: my-app-approuter
    type: approuter.nodejs
    path: approuter
    parameters:
      disk-quota: 256M
      memory: 256M
    requires:
      - name: my-app-auth
      - name: srv-api
        group: destinations
        properties:
          name: srv-api
          url: ~{srv-url}
          forwardAuthToken: true

resources:
  # HDI Container
  - name: my-app-db
    type: com.sap.xs.hdi-container
    parameters:
      service: hana
      service-plan: hdi-shared

  # XSUAA
  - name: my-app-auth
    type: org.cloudfoundry.managed-service
    parameters:
      service: xsuaa
      service-plan: application
      path: ./xs-security.json

Build and Deploy

# Build MTA archive
mbt build

# Deploy
cf deploy mta_archives/my-app_1.0.0.mtar

# Deploy with options
cf deploy my-app.mtar --strategy blue-green

Application Router

Purpose

  • Single entry point for applications
  • User authentication
  • Static content serving
  • URL routing to microservices
  • Session management

xs-app.json

{
  "welcomeFile": "/index.html",
  "authenticationMethod": "route",
  "sessionTimeout": 30,
  "routes": [
    {
      "source": "^/api/(.*)$",
      "target": "$1",
      "destination": "srv-api",
      "authenticationType": "xsuaa",
      "csrfProtection": true
    },
    {
      "source": "^/(.*)$",
      "target": "$1",
      "localDir": "webapp",
      "authenticationType": "xsuaa"
    }
  ]
}

Authentication Types

Type Description
xsuaa Require authentication
none No authentication
basic Basic auth (dev only)

Route Properties

Property Description
source Regex pattern for incoming URL
target Rewritten path
destination Destination name
localDir Serve from local directory
csrfProtection Enable CSRF tokens
scope Required authorization scope

Environment Variables

{
  "destinations": [
    {
      "name": "srv-api",
      "url": "[https://my-srv.cfapps.eu10.hana.ondemand.com",](https://my-srv.cfapps.eu10.hana.ondemand.com",)
      "forwardAuthToken": true
    }
  ]
}

CAP Development

Project Setup

# Create new project
cds init my-project

# Add features
cds add hana
cds add xsuaa
cds add mta

Service Definition (CDS)

// srv/catalog-service.cds
using { my.bookshop as my } from '../db/schema';

service CatalogService {
  @readonly entity Books as projection on my.Books;
  entity Orders as projection on my.Orders;
}

Data Model

// db/schema.cds
namespace my.bookshop;

entity Books {
  key ID : Integer;
  title  : String;
  author : Association to Authors;
  stock  : Integer;
}

entity Authors {
  key ID : Integer;
  name   : String;
  books  : Association to many Books on books.author = $self;
}

entity Orders {
  key ID : UUID;
  book   : Association to Books;
  amount : Integer;
}

Service Implementation

// srv/catalog-service.js
module.exports = cds.service.impl(async function() {
  const { Books, Orders } = this.entities;

  this.before('CREATE', 'Orders', async (req) => {
    const { book_ID, amount } = req.data;
    const book = await SELECT.one.from(Books).where({ ID: book_ID });
    if (book.stock < amount) {
      req.error(409, 'Not enough stock');
    }
  });

  this.after('CREATE', 'Orders', async (order, req) => {
    await UPDATE(Books)
      .set({ stock: { '-=': order.amount } })
      .where({ ID: order.book_ID });
  });
});

Running Locally

# Start with watch (SQLite in-memory)
cds watch

# With hybrid profile (remote services, local app)
cds watch --profile hybrid

# Deploy to database
cds deploy --to hana

Profile Options:

Profile Description Use Case
default SQLite in-memory, mock auth Initial development, quick testing
hybrid Connect to remote BTP services while running locally Test with real HANA, XSUAA, destinations
production Full BTP services Deployed application

Hybrid Profile Setup (.cdsrc.json):

{
  "[hybrid]": {
    "requires": {
      "db": {
        "kind": "hana",
        "credentials": { "from": "env:VCAP_SERVICES" }
      },
      "auth": {
        "kind": "xsuaa",
        "credentials": { "from": "env:VCAP_SERVICES" }
      }
    }
  }
}

Run cds bind to fetch service credentials, then cds watch --profile hybrid.


Service Bindings

Accessing Bound Services

Environment Variable (VCAP_SERVICES):

const vcap = JSON.parse(process.env.VCAP_SERVICES);
const hanaCredentials = vcap.hana[0].credentials;

Using @sap/xsenv:

const xsenv = require('@sap/xsenv');
xsenv.loadEnv();

const hanaCredentials = xsenv.serviceCredentials({ tag: 'hana' });

Using CAP:

// Automatic binding via cds.requires in package.json
const db = await cds.connect.to('db');

package.json (CAP)

{
  "cds": {
    "requires": {
      "db": {
        "kind": "hana",
        "credentials": {
          "binding": "db"
        }
      },
      "auth": {
        "kind": "xsuaa"
      }
    }
  }
}

CI/CD Pipelines

SAP Continuous Integration and Delivery

Pipeline types:

  1. Cloud Foundry - Fiori, CAP
  2. SAP Fiori for ABAP Platform
  3. SAP Integration Suite Artifacts

Pipeline Configuration

# .pipeline/config.yml
general:
  buildTool: mta
  mtaBuildTool: cloudMbt

stages:
  Build:
    npmExecuteBefore:
      dockerImage: 'node:18'

  Integration:
    credentials:
      cfCredentialsId: cf-credentials

  Release:
    cfSpace: prod
    cfCredentialsId: cf-credentials

GitHub Actions Example

name: Deploy to BTP

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Build MTA
        run: npx mbt build

      - name: Deploy to CF
        env:
          CF_API: ${{ secrets.CF_API }}
          CF_USER: ${{ secrets.CF_USER }}
          CF_PASSWORD: ${{ secrets.CF_PASSWORD }}
        run: |
          cf login -a $CF_API -u $CF_USER -p $CF_PASSWORD -o $CF_ORG -s $CF_SPACE
          cf deploy mta_archives/*.mtar -f

Deployment Strategies

Rolling Deployment (Default)

Replace instances one by one:

cf push my-app

Blue-Green Deployment

Zero-downtime with instant rollback:

# Deploy new version
cf push my-app-new -f manifest.yml

# Map production route
cf map-route my-app-new cfapps.eu10.hana.ondemand.com -n my-app

# Unmap from old
cf unmap-route my-app cfapps.eu10.hana.ondemand.com -n my-app

# Delete old version
cf delete my-app -f

# Rename
cf rename my-app-new my-app

With MTA:

cf deploy my-app.mtar --strategy blue-green

Canary Deployment

Gradual traffic shift:

# Deploy canary with different route
cf push my-app-canary -f manifest-canary.yml

# Gradually shift traffic (manual or with load balancer)