Skip to content

grupoboticario/nestjs-sap-rfc

Repository files navigation

Nest Logo

NestJS SAP RFC Client

GitHub CodeQL Sonar CI Publish GitHub package.json version GitHub package.json dependency version (prod) semantic-release Commitizen friendly code style: prettier Conventional Commits Quality Gate Status Coverage Code Smells Vulnerabilities Security Rating GitHub repo size npm npm type definitions npm

📚 Description

NestJS SAP RFC Client, providing convenient ABAP business logic consumption from NestJS

🛠️ Installation

SAP NWRFC SDK installation

npm install nestjs-sap-rfc --save

🏃 Getting Started

Register SapModule module in app.module.ts

Connection Pool

import { SapModule } from 'nestjs-sap-rfc';
import { Module } from '@nestjs/common';

@Module({
  imports: [
    SapModule.createPool({
      isGlobal: true, // for global module
      name: 'service_name', // for multiple modules (OPTIONAL)
      connectionParameters: {
        /* see RfcConnectionParameters */
      },
      clientOptions: {
        /* see RfcClientOptions */
      },
      poolOptions: {
        /* see RfcPoolOptions */
      },
    }),
  ],
})
export class AppModule {}

Connection Pool (Async Module)

import { SapModule } from 'nestjs-sap-rfc';
import { Module } from '@nestjs/common';

@Module({
  imports: [
    SapModule.createPoolAsync({
      isGlobal: true, // for global module
      name: 'service_name', // for multiple modules (OPTIONAL)
      useFactory: () => {
        return {
          connectionParameters: {
            /* see RfcConnectionParameters */
          },
          clientOptions: {
            /* see RfcClientOptions */
          },
          poolOptions: {
            /* see RfcPoolOptions */
          },
        };
      },
    }),
  ],
})
export class AppModule {}

Connection Pool (Async Module + ConfigService)

import { SapModule } from 'nestjs-sap-rfc';
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Module({
  imports: [
    SapModule.createPoolAsync({
      isGlobal: true, // for global module
      name: 'service_name', // for multiple modules (OPTIONAL)
      useFactory: (config: ConfigService) => {
        return {
          connectionParameters: {
            /* see RfcConnectionParameters */
            /* config.get(...) */
          },
          clientOptions: {
            /* see RfcClientOptions */
            /* config.get(...) */
          },
          poolOptions: {
            /* see RfcPoolOptions */
            /* config.get(...) */
          },
        };
      },
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

Direct Client

import { SapModule } from 'nestjs-sap-rfc';
import { Module } from '@nestjs/common';

@Module({
  imports: [
    SapModule.createClient({
      isGlobal: true, // for global module
      name: 'service_name', // for multiple modules (OPTIONAL)
      connectionParameters: {
        /* see RfcConnectionParameters */
      },
      clientOptions: {
        /* see RfcClientOptions */
      },
    }),
  ],
})
export class AppModule {}

Direct Client (Async Module)

import { SapModule } from 'nestjs-sap-rfc';
import { Module } from '@nestjs/common';

@Module({
  imports: [
    SapModule.createClientAsync({
      isGlobal: true, // for global module
      name: 'service_name', // for multiple modules (OPTIONAL)
      useFactory: () => {
        return {
          connectionParameters: {
            /* see RfcConnectionParameters */
          },
          clientOptions: {
            /* see RfcClientOptions */
          },
        };
      },
    }),
  ],
})
export class AppModule {}

Direct Client (Async Module + ConfigService)

import { SapModule } from 'nestjs-sap-rfc';
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Module({
  imports: [
    SapModule.createClientAsync({
      isGlobal: true, // for global module
      name: 'service_name', // for multiple modules (OPTIONAL)
      useFactory: (config: ConfigService) => {
        return {
          connectionParameters: {
            /* see RfcConnectionParameters */
          },
          clientOptions: {
            /* see RfcClientOptions */
          },
        };
      },
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

Inject SapService

import { InjectSapService, SapService, SapRfcObject, SapRfcStructure } from 'nestjs-sap-rfc';
import { Injectable } from '@nestjs/common';

// RfcStructure
type PositionData = SapRfcStructure; // SAP structure

// RfcStructure
interface NestedData extends SapRfcStructure {
  readonly E_NESTED?: string; // SAP field name
}

// MySapInterface
interface MySapInterface extends SapRfcObject {
  readonly E_NAME?: string; // SAP field name
  readonly E_DATA?: PositionData; // SAP field name
  readonly E_DATA2?: NestedData; // SAP field name
  readonly E_ERROR?: string; // SAP field name
  readonly I_OBJID?: string; // SAP field name
}

@Injectable()
export class MyService {
  /**
   * @param {SapService} sapService
   */
  constructor(
    @InjectSapService()
    private readonly sapService: SapService,
  ) {}

  public async test(): MySapInterface {
    return this.sapService.execute<MySapInterface>('rfcName', {
      ...rfcParams,
    });
  }
}

Inject SapService by name

import { InjectSapService, SapService, SapRfcObject, SapRfcStructure } from 'nestjs-sap-rfc';
import { Injectable } from '@nestjs/common';

// RfcStructure
type PositionData = SapRfcStructure; // SAP structure

// RfcStructure
interface NestedData extends SapRfcStructure {
  readonly E_NESTED?: string; // SAP field name
}

// MySapInterface
interface MySapInterface extends SapRfcObject {
  readonly E_NAME?: string; // SAP field name
  readonly E_DATA?: PositionData; // SAP field name
  readonly E_DATA2?: NestedData; // SAP field name
  readonly E_ERROR?: string; // SAP field name
  readonly I_OBJID?: string; // SAP field name
}

@Injectable()
export class MyService {
  /**
   * @param {SapService} sapService
   */
  constructor(
    @InjectSapService('service_name')
    private readonly sapService: SapService,
  ) {}

  public async test(): MySapInterface {
    return this.sapService.execute<MySapInterface>('rfcName', {
      ...rfcParams,
    });
  }
}

Creating and using transactions

Transactions are created using SapService. Example:

import { InjectSapService, SapService, SapRfcObject, SapRfcStructure } from 'nestjs-sap-rfc';
import { Injectable } from '@nestjs/common';

// RfcStructure
type PositionData = SapRfcStructure; // SAP structure

// RfcStructure
interface NestedData extends SapRfcStructure {
  readonly E_NESTED?: string; // SAP field name
}

// MySapInterface
interface MySapInterface extends SapRfcObject {
  readonly E_NAME?: string; // SAP field name
  readonly E_DATA?: PositionData; // SAP field name
  readonly E_DATA2?: NestedData; // SAP field name
  readonly E_ERROR?: string; // SAP field name
  readonly I_OBJID?: string; // SAP field name
}

@Injectable()
export class MyService {
  /**
   * @param {SapService} sapService
   */
  constructor(
    @InjectSapService()
    private readonly sapService: SapService,
  ) {}

  public async runTransaction(): MySapInterface {
    await this.sapService.transaction(async (sapClient: SapClient) => {
      // call rfcs using sapClient
    });
  }
}

Everything you want to run in a transaction must be executed in a callback:

@Injectable()
export class MyService {
  /**
   * @param {SapService} sapService
   */
  constructor(
    @InjectSapService()
    private readonly sapService: SapService,
  ) {}

  public async runTransaction(): MySapInterface {
    await this.sapService.transaction(async (sapClient: SapClient) => {
      await sapClient.call('rfcName_1', {
        ...rfcParams,
      });
      await sapClient.call('rfcName_2', {
        ...rfcParams,
      });
    });
  }
}

The most important restriction when working in a transaction is to ALWAYS use the provided instance of SapClient. All operations MUST be executed using the provided SapClient.

✅ Test

# unit tests
$ npm run test

# test coverage
$ npm run test:cov

💡 Generate Docs

The docs can be generated on-demand. This will produce a documentation folder with the required front-end files.

# generate docs for code
$ npm run doc

# generate docs for code and serve on http://localhost:8080
$ npm run doc:serve

⬆️ Commitizen

commitizen is a command line utility that makes it easier to create commit messages following the conventional commit format specification.

Use npm run commit instead of git commit to use commitizen.

🔨 Built With

✔️ Roadmap

The following improvements are currently in progress:

  • Dynamic Configuration
  • Transaction with auto commit and rollback
  • Resource injection by name
  • Update to node-rfc 3.x