Skip to Content
Technical Articles
Author's profile photo Daniel Dragolea

CDS-TS-Dispatcher: Simplifying SAP CAP TypeScript Development

Hi all,

Introduction

Many of us are leveraging SAP CAP (Node.js with TypeScript) for our projects.

While CAP provides an abstract mechanism to derive RESTful oData services given a CDS data model there’s always room for more efficiency and improvement in the implementing communication between business logic, service, and persistence layer.

It shall be noted at this point, that improvements have been attempted with regard to the service layer communication using cds-routing-handler However, this framework does not appear to be maintained, especially for newer versions of CAP.

We introduce two powerful npm packages developed by the ABS & DxFrontier team:  

In this blog, we will concentrate on the CDS-TS-Dispatcher.

Prerequisites

Before we dive in, you should have a basic understanding of SAP CAP NodeJS and TypeScript. If you need a refresher, here are some helpful resources Official SAP CAP TypeScript

Section 1: Introduction to CDS-TS-Dispatcher

The goal of CDS-TS-Dispatcher is to significantly reduce the boilerplate code required to implement the SAP CAP TypeScript handlers by using TypeScript decorators and Dependency Injection

Section 2: Benefits of CDS-TS-Dispatcher

  • Dependency Injection
  • Common team project architecture: Promotes a common team architecture by separating persistence, business logic, and REST interface (Controller – Service – Repository).
  • Support for Draft: Discover its built-in support for draft handling.
  • Controller – Service – Repository design pattern: by using this pattern you can break code based on domain.
    • Controller – Handling the REST interface to business logic.
      • On, Before, and After events
    • Service – Business logic implementations.
    • Repository – Interface for CDS-QL
      • INSERT, SELECT, UPDATE, DELETE

Section 3: Getting Started with CDS-TS-Dispatcher

Now, let’s get started with CDS-TS-Dispatcher. We’ll walk you through the steps to integrate it into your SAP CAP TypeScript project, providing code snippets and configuration examples for a smooth onboarding.

Use the following steps if you want to create a new SAP CAP typescript project.

1: We’ll start by installing @sap/cds-dk globally 

npm i -g @sap/cds-dk

2: Create a new folder

mkdir new-sap-cap-project
cd new-sap-cap-project

3: Initialize the CDS folder structure :

cds init

4: Add packages

npm install @sap/cds express @dxfrontier/cds-ts-dispatcher
npm install --save-dev @types/node @cap-js/sqlite typescript

5: Add a tsconfig.json

tsc --init

6: It is recommended to use the following tsconfig.json properties

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strictPropertyInitialization": false,
    "target": "ES2020",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

7: Run the CDS-TS server and happy TS coding

cds-ts watch

Section 4: Getting Started with CDS-TS-Dispatcher on an existing project

If you want to add the @dxfrontier/cds-ts-dispatcher to an existing project, follow these steps:

1: Install the CDS-TS-Dispatcher package

npm install @dxfrontier/cds-ts-dispatcher

2: Modify your tsconfig.json

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strictPropertyInitialization": false,
    "target": "ES2020",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

Section 5: CDS-TS-Dispatcher examples

Example 1 : 

Handling AfterRead event using the @AfterRead decorator

import { AfterRead, TypedRequest } from "@dxfrontier/cds-ts-dispatcher";
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterRead()
public async afterReadMethod(results: MyEntity[], req: TypedRequest<MyEntity>) {
  // ...
}

Example 2: 

Handling after read, before read, and singe instance capable using the @AfterRead, @SingleInstanceCapable & @BeforeRead decorators.

@AfterRead() //  Will handle single instance and entity set
@SingleInstanceCapable() // All methods above '@SingleInstanceCapable()' will be triggered when single instance is requested and entity set
@BeforeRead() // Will handle only entity set
public async singeInstanceMethodAndEntitySet(results : MyEntity[], req: TypedRequest<MyEntity>, isSingleInstance: boolean) {
  if(isSingleInstance) {
    return this.customerService.handleSingleInstance(req)
  }

  return this.customerService.handleEntitySet(req)
}

Example 3:

Handling Dependency injection by using the @Inject decorator

@EntityHandler(MyEntity)
class CustomerHandler {
  ...
  @Inject(CustomerService) private customerService: CustomerService
  @Inject(SRV) private srv: Service
  ...
  constructor() {}
  ...

Example 4:

Below we have an @EntityHandler decorator used for handling the Book entity

We inject 2 dependencies the CDS Service (SRV) and BookService 

  • @Inject(SRV) private readonly srv: Service;
  • @Inject(BookService) private readonly bookService: BookService;

We handle @AfterCreate @AfterRead + @SingleInstancCapable, @AfterUpdate and AfterDelete

@EntityHandler(Book)
class BookHandler {
  @Inject(SRV) private readonly srv: Service;
  @Inject(BookService) private readonly bookService: BookService;

  @AfterCreate()
  private async validateCurrencyCodes(result: Book, req: Request) {
    this.bookService.validateData(result, req);
  }

  @AfterRead()
  @SingleInstanceCapable()
  private async addDiscount(results: Book[], req: Request, isSingleInstance: boolean) {
    if (isSingleInstance) {
      req.notify('Single instance');
    } else {
      req.notify('Entity set');
    }

    this.bookService.enrichTitle(results);
  }

  @AfterUpdate()
  private async addDefaultDescription(result: Book, req: TypedRequest<Book>) {
    await this.bookService.addDefaultTitleText(result, req);
  }

  @AfterDelete()
  private async deleteItem(deleted: boolean, req: Request) {
    req.notify(`Item deleted : ${deleted}`);
  }
}

export default BookHandler;

Example 5:

Bootstrap is very simple :

  • Just add the Book entity in the CDS Service implementation from Example 4
import { CDSDispatcher } from '@dxfrontier/cds-ts-dispatcher';

module.exports = new CDSDispatcher([
  // Entities
  BookHandler,
  ... 
]).initialize();

 

OPTIONAL 

Simplify Entity Manipulation with CDS-QL: BaseRepository

The goal of CDS-QL BaseRepository is to significantly reduce the boilerplate code required to implement data access layers for persistence entities by providing out-of-the-box actions on the database. It offers a simplified interface for common database actions such as :

create(), createMany(), getAll(), find(), update(),
updateLocaleTexts(), getLocaleTexts(), count(), exists(), delete(),
deleteMany() ...

and many other actions.

You can find additional documentation for BaseRepository at CDS-TS-Repository GitHub

Conclusion

In conclusion, CDS-TS-Dispatcher combined with CDS-TS-Repository is a powerful tool that can speed up your SAP CAP TypeScript projects by eliminating repetitive code and being a better fit for common team architecture setups.

Additional Resources

Find an example of usage of the CDS-TS-Samples GitHub

Assigned Tags

      2 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Tomas Rimkus
      Tomas Rimkus

      Love all your new libraries. They seem to remove a lot of boilerplate functional code and are really awesome for anyone who love TypeScript, CAP and well structured, maintainable code.
      Thank you very much for sharing the libraries and the demos.

      Author's profile photo Daniel Dragolea
      Daniel Dragolea
      Blog Post Author

      Thank you, really appreciate it.