Grundlagen des SAP Cloud Connector (SAP CC)

Grundlagen des SAP Cloud Connector (SAP CC)

Inhalt des CloudDNA Expert Talk

Lernen Sie in diesem Expert Talk die Grundlagen des SAP Cloud Connector (SAP CC) kennen und Sie werden sehen, dass er keine Weltraumwissenschaft ist.

Architektur des SAP Cloud Connector

Der SAP Cloud Connector ist ein zentraler Bestandteil hybrider SAP-Systemlandschaften. Er ermöglicht Ihnen eine sichere Integration der Services und Subskriptionen in der SAP Business Technology Platform (SAP BTP) in Ihre On-Premise-Landschaft. Daher ist ein gutes Verständnis der Grundlagen des SAP Cloud Connectors unerlässlich, damit Sie schnell und effizient starten können.

Was bedeutet On-Premise ?

Mit dieser Frage werden wir häufig konfrontiert und sie lässt sich sehr einfach beantworten. Unter On-Premise versteht man die bei Ihnen im Unternehmen bereitgestellten Systeme. Diese laufen entweder in Ihrem eigenen Rechenzentrum, bei Ihrem Hosting-Anbieter oder in der SAP HANA Enterprise Cloud (SAP HEC).  

Wie funktioniert der SAP Cloud Connector ?

Der SAP Cloud Connector ist eine Softwarekomponente, die in Ihrer On-Premise-Landschaft installiert wird und einen TLS (Transport Level Security) Tunnel in die SAP BTP (ehemals SAP Cloud Platform) herstellt. Um konkret zu sein: die Verbindung zum Subaccount hergestellt.

Der SAP Cloud Connector verwendet den Reverse Invoke Proxy Ansatz. D.h. er stellt Ihnen auf Ebene des Subaccount Endpunkte bereit, die von den Services (z.B. SAP Cloud Integration, SAP WebIDE, SAP Mobile Services oder SAP Cloud Portal) aufgerufen werden. Er nimmt Ihre Aufrufe aus der SAP BTP / SAP Cloud Platform entgegen und leitet Sie an die On-Premise-Systeme weiter.

Welche Voraussetzung bestehen für den Cloud Connector ?

Für den Betrieb des SAP CC benötigen Sie lediglich eine JVM in der Java Version 1.8. Eine wesentliche Voraussetzung, die Sie beachten müssen, ist die Netzwerkkonnektivität.

Der SAP CC muss einerseits unter Verwendung des HTTPS-Protokolls die Hosts der SAP Business Technology Platform ansprechen können und andererseits die gewünschten Backendsysteme technisch erreichen. Mit der Version 2.13 sind einige spannende Neuerung gekommen, vor allem mit Blick auf Principal Propagation in der Cloud Foundry Umgebung.

Verbindung mit der SAP Business Technology Platform

Der SAP Cloud Connector verbindet sich mit der Business Technology Platform auf Ebene des Subaccounts. Der SAP CC authentifiziert sich mit einem Benutzer der vom Platform Identity Provider (Platform IdP) verwaltet wird. Sie können entweder den SAP ID Service oder den SAP Identity Authentication Service (SAP IAS) als Platform IdP verwenden. Bei der erstmaligen Anmeldung wird für den Benutzer ein Client Zertifikat erstellt, das in weiterer Folge für die Authentifizierung benötigt wird. Dieses Zertifikat wird am im Cloud Connector gespeichert.

Anbindung der Backend-Systeme

Der SAP Cloud Connector stellt Ihre Backend-Systeme im Subaccount der SAP BTP / SAP Cloud Platform unter einem virtuellen Hostnamen und zugehörigem Port bereit. Diesen virtuellen Hostnamen müssen Sie in den Cloud Services auf Ebene des Subaccounts zwingend verwenden. Für die Verbindung kommt der sogenannte Connectivity Service zur Verwendung. Er stellt über den On-Premise-Proxy technisch die Verbindung zum Cloud Connector her.

Der Cloud Connector bildet den virtuellen Hostnamen auf den physischen Hostnamen ab. D.h. er führt ein Mapping durch. Zusätzlich muss im Cloud Connector definiert werden, welche Ressourcen im Backend angesprochen werden können. Dies entspricht im Falle eines SAP-ABAP-Systems den ICF-Knoten. Der Cloud Connector unterstützt folgende Protokolle:

  • HTTP / HTTPS
  • RFC / RFC (SNC)
  • TCP / TCPS
  • Mail (SMTP, POP3, IMAP)
  • LDAP / LDAPS
  • SFTP / FTP / FTPS

Sicherheitsaspekte

Es gibt eine Reihe an sicherheitsrelevanten Aspekten, die im Cloud Connector zu beachten sind. Dieser bringen wir Ihnen in einem anderen Artikel näher. Doch eines vorab, die Authentifizierung der Administratoren und anderen Benutzer gegen einen LDAP-Server ist essentiell. Sie sollten auch das selbstsignierte SSL-Zertifikat, mit dem der CC im Standard ausgeliefert wird, so schnell wie möglich ersetzen.

SAP HANA Cloud Connector

Sie werden in vielen Blogs und auf Webseiten noch vom HANA Cloud Connector lesen. Dieser wurde im Jahr 2017 auf den Namen SAP Cloud Connector umgetauft. Sollten Sie noch vom SAP HANA Cloud Connector oder der HANA Cloud Platform lesen, raten wir Ihnen sich aktuellere, zeitgemäße Blogs zu suchen. Die Zeitspanne von mehr als drei Jahren seit der Umbenennung sind im Cloud Zeitalter so, wie wenn Sie noch immer in der Bronzezeit leben und denken würden. 😉

Das Video zum Expert Talk

Im Video zeigen wir Ihnen Schritt für Schritt, wie der SAP Cloud Connector funktioniert. Wir hoffen Ihnen damit weiterhelfen zu können. Für Fragen und auch Unterstützung in Projekten stehen wir Ihnen gerne zur Verfügung.

Folgen Sie unserem Youtube Channel um keine Neuigkeiten zu verpassen. Wir veröffentlich in regelmäßigen Intervallen Videos zur SAP Cloud Platform / SAP Business Technology Platform.

CloudDNA Expert Talks zum SAP Cloud Connector

Sehen Sie sich auch unsere anderen Expert Talks zum SAP Cloud Connector an:

SAP Cloud Connector Location Identifier verstehen und richtig verwenden

Verlieren Sie keine Zeit - kontaktieren Sie uns jetzt

Zögern Sie nicht uns zu kontaktieren. Mit Fragen zum SAP Cloud Connector, zur SAP BTP, zur SAP Cloud Platform, zur SAP CPI, zur SAP Integration Suite und anderen innovativen Themen sind Sie bei uns genau an der richtigen Adresse!

Ihre Nachricht an uns

SAP Cloud Connector Location Identifier richtig nutzen!

SAP Cloud Connector Location Identifier richtig nutzen!

Sie können den SAP Cloud Connector (CC) für unterschiedlichste Szenarien verwenden. Unter anderem auch um sich aus einer geografisch verteilten Systemlandschaft mit dem Subaccounts der SAP Business Technology Platform (SAP BTP). Damit können Sie unter Verwendung der SAP Cloud Platform Integration (CPI) bzw. der SAP Integration Suite Schnittstellen in die On-Premise-Landschaft umsetzen. Wir zeigen Ihnen wie der SAP Cloud Connector Location Identifier richtig verwendet wird. Der Artikel wurden von den Experten der CloudDNA GmbH aus Österreich auf deutsch veröffentlicht. Er soll als Begleittext zum unserem YouTube-Video dienen.

Inhalt des CloudDNA Expert Talks

In diesem CloudDNA Expert Talk geht es darum, wie Sie den SAP Cloud Connector (SAP CC) mit Location Identifier (Location ID) verwenden können. Wir zeigen Ihnen wie sich unterschiedliche SAP Cloud Connector Instanzen mit demselben Subaccount verbinden lassen kann.

SAP Cloud Integration Kommunikation über den SAP Cloud Connector

Gehen wir von folgender Annahme aus. Sie haben eine SAP CPI (Cloud Platform Integration) oder die SAP Integration Suite mit aktiver Cloud Integration Capability. Sie möchten aus der Cloud Integration heraus über den SAP Cloud Connector (CC) eine Verbindung zu einem System in der On-Premise-Landschaft herstellen. Der SAP Cloud Connector öffnet dazu einen TLS Tunnel (=Transport Level Security) zum Subaccount. Über diesen Tunnel läuft der gesamte Traffic aus der Cloud in die On-Premise-Systemlandschaft. Dies funktioniert sowohl bei SAP- als auch bei Non-SAP-Systemen.

Die SAP Cloud Connector LocationID

Wir gehen in unserer Annahme ein Schritt weiter. Sie müssen Gesellschaften in unterschiedlichen Ländern oder in unterschiedlichen geografischen Regionen über die SAP CPI integrieren. Sie können für dieses Szenario in jeder Region in der Sie ein Backendsystem integrieren möchten einen Cloud Connector installieren. Der Subaccount in der SAP BTP bzw. der SAP Cloud Platform könnte nun nicht mehr unterscheiden über welchen Cloud Connector die Nachrichten in die On-Premise-Landschaft gesendet werden. Auch dafür bietet der SAP CC eine Lösung. Es ist lediglich erforderlich, dass jede Cloud Connector Instanz beim Aufbau der Verbindung mit dem Subaccount eine Location-ID (LOC) verwendet.

Funktioniert in der Neo- und Cloud-Foundry-Umgebung

Die SAP Cloud Connector LocationID wird sowohl für die Neo- als auch für die Cloud-Foundry-Umgebung unterstützt. Die Kommunikation aus den Services und Subskriptionen auf Ebene des Subaccount erfolgt immer über den Connectivity Service. Dieser bedient sich der über den Destination Service angelegten Destinationen. Diese beinhalten die technische Konfiguration. In der Destination muss die Location-ID ebenfalls gepflegt werden. Sie haben jedoch auch die Möglichkeit, einen Cloud Connector als Default zu verwenden. Dazu dürfen Sie keine Location-ID anführen.

Unsere Empfehlung

Wir verwenden in unseren Projekten SAP Cloud Connector Location Identifier die an die IATA 3-Letter-Codes in der Luftfahrt angelehnt sind. Die Begründung dafür ist recht einfach. Ich bin ausgebildeter Linienpilot und Fluglotse. Ich habe erkannt hat dass die Standardisierung und Normierung in der Luftfahrt absolut gewinnbringen in der IT verwendet werden kann. Folgende Beispiele für Location-IDs können wir Ihnen als Inspiration geben:

  • Wien (VIE)
  • Berlin (BER) – mittlerweile ja fertiggestellt…
  • Frankfurt (FRA)
  • New York (NYC)
  • Sydney (SYD)
  • Dubai (DUB)

Das Video zum Expert Talk

Im Video bauen wir diese Szenario Schritt für Schritt auf. Wir hoffen Ihnen damit weiterhelfen zu können. Für Fragen und auch Unterstützung in Projekten stehen wir Ihnen gerne zur Verfügung.

Folgen Sie unserem Youtube Channel um keine Neuigkeiten zu verpassen. Wir veröffentlich in regelmässigen Intervallen Videos zur SAP Cloud Platform.

CloudDNA Expert Talks zum SAP Cloud Connector

Sehen Sie sich auch unsere anderen Expert Talks zum SAP Cloud Connector an:

Grundlagen des SAP Cloud Connector

Verlieren Sie keine Zeit - kontaktieren Sie uns jetzt

Zögern Sie nicht uns zu kontaktieren. Mit Fragen zum SAP Cloud Connector, zur SAP BTP, zur SAP Cloud Platform, zur SAP CPI, zur SAP Integration Suite und anderen innovativen Themen sind Sie bei uns genau an der richtigen Adresse!

Ihre Nachricht an uns

SAP Cloud Platform Java Entwicklung – Teil 2

Zugriff auf SAP HANA in der NEO Umgebung

Im ersten Teil der Blogserie habe ich Sie in die Java Entwicklung auf Basis des Spring Frameworks eingeführt. Anhand eines einfachen RestControllers wurden die Grundlagen gezeigt.

Was wäre eine Applikation ohne Zugriff auf eine Datenbank. Im SAP Cloud Kontext ist es natürlich naheliegend dass SAP HANA als Datenbank verwendet wird. In diesem Teil der Blogserie zeige ich Ihnen wie auf die Datenbank in der NEO Umgebung zugegriffen werden kann.

JNDI – Die gelben Seiten

Der Zugriff auf die HANA Datenbank erfolgt über JNDI, aus meiner Sicht eine der spannendsten Technologien im Java Umfeld. Mit einem JNDI Lookup können von der Laufzeitumgebung verwaltete Resourcen geladen werden. Die Idee dahinter ist sehr einfach – die Laufzeitumgebung kümmert sich um das Instanzieren der benötigten Klassen und stellt diese der Applikation bereit.

Damit die hier vorgestellte Applikation JNDI unterstützt müssen einige Tätigkeiten durchgeführt werden. Im ersten Schritt ist es erforderlich dass im Verzeichnis src > main eine Unterverzeichnis namens webapp und darin in Unterverzeichnis namens WEB-INF angelegt wird. Im Verzeichnis src > main > webapp > WEB-INF ist es nur erforderlich eine Datei namens web.xml anzulegen.

Verzeichnisstruktur

Verzeichnisstruktur für web.xml

Jene Resourcen die mittels JNDI verwaltet werden, müssen in der Datei web.xml als Resource Reference (resource-ref) angeführt werden. Darin muss der Name (res-ref-name) definiert werden unter dem die Resource angesprochen wird sowie die dahinterliegende Java Klasse (res-type) auf die typisiert wird.

 

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<resource-ref>
<res-ref-name>jdbc/DefaultDB</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
</resource-ref>
</web-app>

Spring Data Dependency deklarieren

Damit ist der erste Schritt bereits erledigt. Nachdem wir den Zugriff auf die Datenbank nicht mittels SQL Kommandos selbst implementieren, sondern auf Spring Data zurückgreifen, muss im pom.xml noch eine entsprechende Dependency definiert werden.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Nachdem Spring Data eine Vielzahl an Datenbanken unterstützt ist es erforderlich, der Laufzeitumgebung mitzuteilen welche Datenbank zu verwenden ist. Das passiert über die Datei application.properties. Falls diese noch nicht existiert muss sie im Verzechnis src > main > resources angelegt werden. Der vollqualifizierte Klassenname des HANA Treibers lautet com.sap.db.jdbc.Driver

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.HANAColumnStoreDialect
spring.jpa.properties.hibernate.connection.pool_size = 10
spring.datasource.driverClassName=com.sap.db.jdbc.Driver

@Configuration

Spring @Configuration-Annotation ist Teil des Spring Core Frameworks. Die Spring-Annotation weist darauf hin, dass die Klasse über eine @Bean-Definitionsmethoden verfügt. Der Spring-Container kann dadurch die Klasse verarbeiten und Spring Beans zur Laufzeit generieren, die in der Anwendung verwendet werden können.

Für die Verwendung im Neo Stack muss die Datasource mittels JNDI geladen werden. Dazu muss eine entsprechende Klasse erstellt werden. Ich nachfolgenden Code-Snippet wird die vollständige Klasse dargestellt.

Neo Datasource Configuration
package at.clouddna.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;

import javax.sql.DataSource;
import java.sql.SQLException;

@Configuration
@Profile({"neo"})
public class NeoConfig 
{	
	@Bean(destroyMethod="")
	public DataSource jndiDataSource() throws IllegalArgumentException, SQLException
	{
		JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
		DataSource ds = dataSourceLookup.getDataSource("java:comp/env/jdbc/DefaultDB");
		return ds;
	}
}

Der JNDI-Lookup zielt auf den Namen java:comp/env/jdbc/DefaultDB ab. Der Prefix java:comp/env/ ist in der SAP Cloud Platform immer derselbe. Der dahinter definierte Name jdbc/DefaultDB entspricht dem res-ref-name in der web.xml

Entwicklung einer Entity-Klasse

Die Verwendung von Spring-Data ermöglicht eine sehr effiziente Entwicklung der Persistenzschicht. Spring-Data basiert auf dem Hibernate Framework. Sobald wir eine Klasse anlegen und diese mit der Annotation @Entity versehen, wird auf der darunterliegenden Datenbank eine zugehörige Tabelle angelegt. Im nachfolgende Code-Snippet zeige ich Ihnen eine einfach User Klasse.

User.java
package at.clouddna.demo.model;

import javax.persistence.*;

@Entity
public class User {

    @Column(nullable = false)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Long id;

    private String firstname;
    private String lastname;

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    public String getFirstname() {
        return this.firstname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    public String getLastname() {
        return this.lastname;
    }
}

CRUD Methoden

Das geniale an Spring-Data ist die Out-of-the-box Verfügbarkeit von CRUD Methoden. Dazu muss lediglich ein Interface erstellt werden das vom JpaRespository erbt. Dies wird für die User Entity im nachfolgenden Code-Snippet dargestellt.

package at.clouddna.demo.repository;

import at.clouddna.demo.model.User;

public interface IUserRepository extends JpaRepository<User, Long> {

}

Das Respository kann nun direkt im Controller verwendet werden indem über Autowiring darauf zugegriffen wird. Mein Unternehmen sieht davon jedoch ab. Wir erstellen für jede Entity-Klasse immer ein zugehöriges DTO (Data Transfer Object) und erstellen zusätzlich eine Serivce-Klasse, die mit der @Service Annotation versehen wird, welche die Verwendung des Repository kapselt. Die Service-Klasse kann im Controller über die @Autowired Annotation injiziert werden.

Selbstverständlich zeige ich Ihnen wie das funktioniert.

ServiceBase Klasse und Model Mapper

Das Mapping der Entity-Klasse auf das DTO und umgekehrt führen wir nicht manuell sondern mittels ModelMapper durch. Der Modelmapper muss ins pom.xml als Dependency aufgenommen werden.

<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.5</version>
</dependency>
ServiceBase.java
package at.clouddna.codegenerator.service.da;

import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public abstract class ServiceBase {

    private ModelMapper modelMapper;

    public ServiceBase(){
        this.modelMapper = new ModelMapper();
        this.modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STANDARD);
    }

    public <D, T> D map(T entity, Class<D> outClass) {
        return modelMapper.map(entity, outClass);
    }

    public <D, T> List<D> mapAll(Collection<T> entityList, Class<D> outCLass) {
        return entityList.stream()
                .map(entity -> map(entity, outCLass))
                .collect(Collectors.toList());
    }

    protected void writeToFile(String fileName, String content) throws IOException {
        FileOutputStream outputStream = new FileOutputStream(fileName);
        byte[] strToBytes = content.getBytes();
        outputStream.write(strToBytes);
        outputStream.close();
    }

    protected void writeToFile(String fileName, byte[] content) throws IOException {
        FileOutputStream outputStream = new FileOutputStream(fileName);
        outputStream.write(content);
        outputStream.close();
    }
}

Service Klasse

Die Service Klasse erbt von der ServiceBase Klasse und kapselt den Zugriff auf die Datenbank. Nachfolgendes Code-Snippet zeigt die UserService Klasse. Wichtig ist dabei dass die Klasse mit der Annotation @Service versehen wird. Dadurch kann sie im Controller mittels Autowiring verwendet werden.

UserService.java
package at.clouddna.demo.service;

import at.clouddna.demo.dto.UserDto;
import at.clouddna.demo.model.User;
import at.clouddna.demo.respository.IUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class UserService extends ServiceBase {

    @Autowired
    private IUserRepository repository;

    public UserDto create(UserDto userDto) {
        return map(repository.save(map(userDto, User.class)), UserDto.class);
    }

    public UserDto update(UserDto userDto) {
        return map(repository.save(map(userDto, User.class)), UserDto.class);
    }

    public boolean delete(Long id) {
        repository.deleteById(id);
        return true;
    }

    public UserDto findById(Long id) {
        Optional<User> userOptional = repository.findById(id);
        if(!userOptional.isPresent()) {
            return null;
        }
        return map(userOptional.get(), UserDto.class);
    }

    public List<UserDto> findAll() {
        return mapAll(repository.findAll(), UserDto.class);
    }
}

RestController

Abschließend zeige ich Ihnen wie der zuvor entwickelte Service im RestController vollumfänglich für alle CRUD-Methoden verwendet werden kann. Sie werden überrascht sein wie einfach das möglich ist!

UserController.java
package at.clouddna.demo.controller;

import at.clouddna.demo.dto.UserDto;
import at.clouddna.demo.model.User;
import at.clouddna.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping
    public ResponseEntity getAll() {
        return ResponseEntity.ok(userService.findAll());
    }

    @GetMapping("/{id}")
    public ResponseEntity getById(@PathVariable("id") Long id) {
        return ResponseEntity.ok(userService.findById(id));
    }

    @PostMapping
    public ResponseEntity create(@RequestBody UserDto body) {
        return ResponseEntity.ok(userService.create(body));
    }

    @PutMapping("/{id}")
    public ResponseEntity update(@PathVariable("id") Long id,
                                 @RequestBody UserDto body) {
        body.setId(id);
        return ResponseEntity.ok(userService.update(body));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity delete(@PathVariable("id") Long id) {
        return ResponseEntity.ok(userService.delete(id));
    }
}

Fazit

Ich hoffe dass ich Ihnen in diesem Teil die Java Entwicklung in der SAP Cloud Platform schmackhaft gemacht habe. Wie Sie hoffentlich erkannt haben handelt es sich um kein Hexenwerk und keine Weltraumwissenschaft. Einen Tipp möchte ich Ihnen noch mitgeben – achten Sie bereits in der Planung des Projekts darauf dass alles sauber strukturiert ist und Sie jeweils ein eigenes Paket für Entity, DTO, Repository, Service und Controller definieren.

SAP Cloud Platform Java Entwicklung – Teil 1

SAP Cloud Platform Java Entwicklung – Teil 1

In dieser Blogreihe zeige ich Ihnen wie die Java Entwicklung für die SAP Cloud Plaform auf Basis von Spring Boot durchgeführt wird. Spring Boot ist das defacto Standard Framework wenn es um die Entwicklung von Java Applikationen in der Cloud / bei Software-as-a-Service geht. Zur Entwicklung mit Spring Boot gibt es eine Vielzahl an Resourcen im Internet, jedoch blieben die SAP Cloud Platform Spezifika dabei meist auf der Strecke. SAP bietet im Neo Stack der SAP Cloud Platform ein SDK dass die Verwendung von SAP-eigenen Funktionalitäten in Java ermöglicht. Dieser Blog fokusiert sich auf die allgemeinen Entwickleraufgaben in der Entwicklung von Java Applikationen. Dabei wird der Aufbau der pom.xml gezeigt und ein einfacher RestController implementiert. Folgende Inhalte werde ich in zukünftigen Blogs zeigen:
  • Zugriff auf HANA DB mittels JNDI
  • Zugriff auf Destinations mittels JNDI
  • Zugriff auf Tenant Informationen mittels JNDI
  • Zugriff auf User Informationen

Aufbau der pom.xml

Die pom.xml beinhaltet alle relevanten Informationen die für den Build der Applikation erforderlich sind.

Parent

Nachdem wir auf Spring Boot setzen muss dies als parent definiert werden.
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.3.1.RELEASE</version>
  <relativePath />
</parent>

Properties

In den properties werden die Java Version sowie die Version des SAP Cloud SDK definiert.
<properties>
  <java.version>1.8</java.version>
  <sap.cloud.sdk.version>3.107.18</sap.cloud.sdk.version>
</properties>

Dependencies

In den dependencies werden die Abhängigkeiten definiert. Dabei wird u.a. auf SpringBoot Starter Web verwiesen und auch auf das SAP Cloud SDK. Beim SAP Cloud SDK ist es wichtig, dass auf die in den properties definierte Version verlinkt wird, und dass der scope auf den Wert provided gesetzt wird. Damit wird diese Abhängigkeit nicht in das WAR file gepackt und auf als dem Server bereitgestellt betrachtet.
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId> 
</dependency>

<dependency> 
  <groupId>com.sap.cloud</groupId>
  <artifactId>neo-java-web-api</artifactId>
  <version>${sap.cloud.sdk.version}</version>
  <scope>provided</scope>
</dependency>

<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.8.6</version>
</dependency>

Profile

In diesem Beispiel wird für das Deployment auf den Neo Stack ein eigenen Profil angelegt. Darin wird definiert, dass bestimmte Abhängigkeiten vom Server bereitgestellt werden und diese nicht in das WAR file eingebettet werden. Andernfalls würde der Start der Applikation fehlschlagen.
<profile>
  <id>neo</id>
  <activation>
    <activeByDefault>true</activeByDefault>
  </activation>
  <properties>
    <packaging.type>war</packaging.type>
  </properties> 
  <dependencies> 
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <scope>provided</scope>
    </dependency>  
  </dependencies>
</profile>

Build Plugins

Abschließend muss im Build noch das Spring Boot Maven Plugin definiert werden.
<plugins>
  <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
  </plugin>
</plugins>

Build der Applikation für die SAP Cloud Platform

Der Build der Applikation kann über die CommandLine mit folgenden Befehl durchgeführt werden mvn clean package -Pneo Damit die Applikation innerhalb vom Tomcat erfolgreich gestartet werden kann, muss die configure Methode aus dem SpringBootServletInitializer redefiniert werden. Dies kann entweder direkt in der Hauptklasse der Applikation erfolgen oder in einer eigenen Klasse. Ich persönlich bevorzuge an dieser Stelle die Verwendung einer eigenen Klasse.
package at.clouddna.demo;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

public class ServletInitializer extends SpringBootServletInitializer {
  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(DocmgmtApplication.class);
  }
}

Implementierung eines einfachen Rest Controllers

Damit ein Funktionstest durchgeführt werden kann, implementiere ich immer eine Ping Methode in einem eigenen Controller.
package at.clouddna.docmgmt.controller; 

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 
import org.springframework.http.ResponseEntity; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RestController; 

@RestController 
@RequestMapping({ "/ping" }) 
public class PingController { 
  private static final Logger logger = LoggerFactory.getLogger(PingController.class); 

  @GetMapping
  public ResponseEntity<?> ping() { 
    logger.debug("ping called"); 
    return ResponseEntity.ok("Hello World"); 
  }
}
Damit sind wir mit einer einfachen Applikation bereits fertig. Beim Deployment über das SAP Cloud Platform Cockpit ist darauf zu achten, dass die Runtime Java Web Tomcat 8 ausgewählt wird und dass das Profile als JVM Parameter wie folgt übergeben wird.
-Dspring.profiles.active=neo
Der Ping Controller kann direkt aus dem Browser heraus getestet werden, da die entsprechende Methode über einen HTTP GET Request aufgerufen wird.

OData ABAP Backend Exception

State-of-the-Art SAPUI5 / Fiori Apps erfordern ein sauber implementiertes Error Handling. Sind OData Services in einem SAP NetWeaver ABAP Stack implementiert, gibt es zwei ABAP Klassen die für Exceptions verwendet werden:

  • /IWBEP/CX_MGW_BUSI_EXCEPTION für Business / Logische Fehler
  • /IWBEP/CX_MGW_TECH_EXCEPTION für Technische Fehler

Fehler können auf verschieden Arten geworfen werden. Nachfolgend zeigen wir Ihnen unterschiedliche Optionen.

Ein Möglichkeit ist es, über den MessageContainer den Fehlertext direkt zurückzuliefern. Diese Methode hat den Nachteil, dass die Fehlertexte hartkodiert sind und somit eine Übersetzung in unterschiedliche Sprachen nicht möglich ist.

METHOD customerset_get_entity.
  SELECT SINGLE * FROM zcustomer where ...
  IF sy-subrc NE 0.
    mo_context->get_message_container( )->add_message_text_only( 
iv_msg_type = 'E'                                                       
iv_msg_text = |Der angeforderte Datensatz wurde nicht gefunden.| ).
  
    RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      EXPORTING
        message_container = mo_context->get_message_container( ).
  ENDIF.
ENDMETHOD.

Eine weitere Möglichkeit besteht in der Verwendung einer Nachrichtenklasse. Damit können alle bekannten ABAP Tools wie z.B. die SE91 verwendet werden.

METHOD customerset_get_entity.
  SELECT SINGLE * FROM zcustomer where ...
  IF sy-subrc <> 0.
    mo_context->get_message_container( )->add_message( iv_msg_type   = 'E'
                                                       iv_msg_id     = 'SY'
                                                       iv_msg_number = '100'
                                                       iv_msg_text   = |Der angeforderte Datensatz wurde nicht gefunden.| ).
  
    RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      EXPORTING
        message_container = mo_context->get_message_container( ).
  ENDIF.
ENDMETHOD.

Eine weitere Möglichkeit in Kombination mit dem Message Container bietet sich, wenn Fehler in Form eine BABIRET2 Struktur vorliegen.

METHOD xyz_get_entity.
  DATA: lt_return TYPE bapirettab.
   
  CALL FUNCTION 'BAPI_CALL_XYZ'
    ...
    TABLES
      return = lt_return.
   
  IF lt_return IS NOT INITIAL.
    mo_context->get_message_container( )->add_messages_from_bapi( it_bapi_messages = lt_return ).
 
    RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      EXPORTING
        message_container = mo_context->get_message_container( ).
  ENDIF.
ENDMETHOD.

Es gibt in diesem Szenario auch die Option die Leading Message zu bestimmen. In diesem Fall muss das Coding leicht abgeändert werden.

METHOD xyz_get_entity.
  DATA: lt_return TYPE bapirettab.
   
  CALL FUNCTION 'BAPI_CALL_XYZ'
    ...
    TABLES
      return = lt_return.
   
  IF lt_return IS NOT INITIAL.
    mo_context->get_message_container( )->add_messages_from_bapi( it_bapi_messages = lt_return
                                                                  iv_determine_leading_msg = /iwbep/if_message_container=>gcs_leading_msg_search_option-first ).
 
    RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      EXPORTING
        message_container = mo_context->get_message_container( ).
  ENDIF.
ENDMETHOD.

Exceptions können auch direkt ohne Verwendung des Message Containers geworfen werden.

METHOD customerset_get_entity.
  SELECT SINGLE * FROM zcustomer where ...
  IF sy-subrc NE 0.
    RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      EXPORTING
        textid  = /iwbep/cx_mgw_busi_exception=>business_error
        message = |Der angeforderte Datensatz wurde nicht gefunden.|.
  ENDIF.
ENDMETHOD.

Eine weitere Option besteht darin, der Exception eine Struktur vom Typ scx_t100key mitzugeben.

METHOD customerset_get_entity.
  SELECT SINGLE * FROM zcustomer where ...
  IF sy-subrc NE 0.
    DATA(lv_message) = VALUE scx_t100key( msgid = 'SY'
                                          msgno = '001'
                                          attr1 = |Der angeforderte Datensatz wurde nicht gefunden.| ).
 
    RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      EXPORTING
        textid  = lv_message.
  ENDIF.
ENDMETHOD.

Die letzte in diesem Blog dargestellte Option ist es, eine Exception abzufangen, und den Exceptiontext durchzureichen.

METHOD customerset_get_entity.
  TRY.
      ...
    CATCH cx_root INTO DATA(lr_ex).
      RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
        EXPORTING
          textid  = /iwbep/cx_mgw_busi_exception=>business_error
          message = |{ lr_ex->get_text( ) }|.
  ENDTRY.
ENDMETHOD.