Skip to main content

Course 4: Implement API Operations

đŸŽ¯Overview

In this course you will learn about the implementation of API Operation in Domain Service projects, both for the TypeScript and Java Spring Boot stack.

Exercise​

Estimated time: 30 minutes
Supported languages: TypeScript and Java
Precondition:

In this exercise, you will implement an API Operation within your Orders project.

ℹī¸note

Please note that the links to the workbench tools in this tutorial only apply to the IBM Education Environment we provide. If you are using a different environment, e.g. your own installation, you will need to navigate directly to the required tools.

Introduction​

In this use case we want to retrieve all orders for a customer. To do this, a GET Operation has to be implemented which can return the list of all orders for a customer.

Open your imported Orders project​

The following steps will take your Orders project which you have imported in Course Domain Service Development as a base.

  1. Open your Workspace in the Solution Designer.
  2. Find your imported Project and open it by clicking on it.

Discover the API Operation modelled in the -solutionDesigner​

We will now discover the API Namespace "ordapi" which is already designed in the Solution Designer. The API namespace consists of the following elements:

  • A Path and an Operation "getOrdersForCustomer"

  • A Parameter that is used to read the customerId

  • A Response that returns the list of orders

Discover the Domain Namespace in the -solutionDesigner​

Now, we will discover the Domain Namespace "ord" which already contains items around managing orders:

  • A RootEntity "Order" that holds all details of the order

  • A Domain Service "FindOpenOrdersForCustomer" that returns all open orders of a specific customer

Implement the API Operation​

After discovering the API, the operation needs to be implemented so that it returns the expected response. This part is not done within the Solution Designer. Instead we will use the native development tooling on your local machine.

❗ī¸Before doing this
  • Ensure that you have setup the necessary prerequisites for local development
    • For TypeScript please install at least Node.js and TypeScript see here
    • For Java please install Java SDK and Maven see here
  • Ensure that you have installed the Solution CLI.
    • Therefore follow the "Solution CLI Setup" instructions in your Project in the Solution Designer (see here)

Clone the project​

To implement your designed operation in your IDE (e.g. Visual Studio Code, IntelliJ) you have to clone your project to your local machine.

ℹī¸note

If you have cloned the project before already, it is enough to perform the command k5 pull to get the latest changes.

  1. Open the section "Implementation" in the "Solution CLI" and follow the instructions of the section in your terminal.

❗ī¸info

If you choose TypeScript, please ensure that within your IDE you have opened the project directory instead of your workspace directory to ensure that all of the provided features work smoothly.

Implement API Operation​

  1. Open the file /src-impl/api/ordapi/operations/GetOrdersForCustomerApi.ts

    You will see that the file contains an auto-generated stub with two more or less empty functions. The only content inside the functions is commented out sample code for some often-used functionality aimed at serving as a template for what you might want to do.

    /**
    * @param request Request.GetOrdersForCustomerRequest
    * getOrdersForCustomer logic
    */
    getOrdersForCustomer = async (request: Request.GetOrdersForCustomerRequest) => {
    // TODO: add your implementation logic
    this.util.log.info('start getOrdersForCustomer execution');
    // this.response;
    }

    /**
    * @param request Request.GetOrdersForCustomerRequest
    * @param error TypeError
    * getOrdersForCustomerErrorHandler logic
    */
    getOrdersForCustomerErrorHandler = async (error: TypeError, request: Request.GetOrdersForCustomerRequest) => {
    // TODO: error handling should go here
    this.util.log.info('start getOrdersForCustomerErrorHandler execution');
    // this.response;
    };

    getOrdersForCustomer(): When fully implemented, this function provides the actual functionality of the API operation.

    getOrdersForCustomerErrorHandler(): When fully implemented, this function provides the error handling.

    In the next steps, we will extend the functions step by step with the code to get all orders for a customer and to handle exceptions that might occur.

  2. Now, we will add content to function getOrdersForCustomer(). As a first step, extract the customerId from the request path and store it in a variable:

    getOrdersForCustomer = async (request: Request.GetOrdersForCustomerRequest) => {
    this.util.log.info('start getOrdersForCustomer execution');

    // read customerId from path
    const customerId = request.path.customerId;
    }

    request.path: provides type-safe access to the request path that is modeled for the current operation.

  3. As a next step, create an input entity, fill it with the customerId from the request and trigger the domain service:

    // construct input entity for the call to the domain service
    const serviceInput = this.factory.entity.ord.FindOpenOrdersForCustomer_Input();

    // set customerId from path parameter for the service input
    serviceInput.customerId = customerId;

    // trigger the domain service to get all open orders for a customer
    const orders = await this.services.ord.FindOpenOrdersForCustomer(serviceInput);

    this.factory.entity.ord: provides the creation of instances of each entity, input, output and payload entity in domain namespace ord.

    this.services: allows to call any service that is modeled within a domain or integration namespace. Returns an entity or a list of entities which can be then mapped to the response of the API operation.

  4. Loop over orders which have been returned by domain service in order to transform them to Schema "Order" from our API namespace:

    // initialize the response body as an empty array of api schema orders
    const apiOrders = [];

    for (const order of orders) {
    // create an empty order schema in "api format"
    const apiOrder: Schema.Order = {};

    // map properties from domain entity Order to the api schema "Order"
    apiOrder.customer_id = order.customerId;

    // add api schema "Order" to the apiOrders array
    apiOrders.push(apiOrder);
    }

    Schema.Order: provides access to the type for the modeled schema Order.

  5. Set the response status code to 200 and the response body to the list of API schema "Order". This indicates to users of the API operation that it was executed successfully.

    // set response status to 200
    this.response.statusCode = 200;

    // set response body to apiOrders
    this.response.body = apiOrders;

    this.response.body: provides type-safe access to the response body of the operation. Only schemas that are modelled as response body are allowed to be set here.

    this.response.statusCode: Response status has to be set in each operation. The implementation is restricted to the status codes that are modeled for this operation.

  6. Now, we are complete with function getOrdersForCustomer() and we will continue with the error handling of the API operation. Here, we have to handle one business error CustomerNotFound, which is thrown by the domain service FindOpenOrdersForCustomer that is used inside the API operation.
    Therefore, we will extend the function getOrdersForCustomerErrorHandler(). As a first step, check if the function's parameter error is of type BusinessError and if so, then check if it is a CustomerNotFound business error.

    /**
    * @param request Request.GetOrdersForCustomerRequest
    * @param error TypeError
    * getOrdersForCustomerErrorHandler logic
    */
    getOrdersForCustomerErrorHandler = async (error: TypeError, request: Request.GetOrdersForCustomerRequest) => {
    this.util.log.info('start getOrdersForCustomerErrorHandler execution');

    if(this.isInstanceOf.error.BusinessError(error)) {
    // Set the error response if a BusinessError occurred
    if(this.isInstanceOf.businessError.ord.CustomerNotFound(error)) {
    // Set the error response if the CustomerNotFound error occurred
    }
    }
    };

    this.instanceOf: allows to check the type of errors in general, business errors specifically and entities.

  7. Inside the inner if-block, set the response status code to 404 and the response body to the API schema "ErrorResponse". This indicates to users of the API operation that it's execution failed.

    this.response.statusCode = 404;

    const errorSchema: Schema.ErrorResponse = {
    errorCode: 'ERR001',
    message: error.errorMessage
    };

    this.response.body = errorSchema;

Push the changes to Git​

To make the changes of your implementation visible to others you have to push them to the remote Git repository.

  1. Go to your terminal and run command k5 compile to ensure that you do not have any issues in your code.
  2. Run the command k5 push -m "Implementation of API operation getOrdersForCustomer for my Orders Project".
🌟GOOD JOB!

You have successfully implemented your Orders Project. You also pushed your changes to your remote Git repository in Gitlab.

Please find more information about Implementing API operations: