Skip to main content

Unit 15: Implement API operation

๐ŸŽฏOverview

In this course you will learn how to implement API operations in the IBM DevOps Solution Workbench using your local machine. You will also practice how to apply error handling to the API operations.

Outlineโ€‹

For the last course of the implementation phase, we will conclude with coding within the API Namespace. As explained in Unit 8: Design API Path and Operation, API paths together with their operations expose functionality of your service to the outside world. API operations have one or more responses, each of them consisting of a status code and a response body. Additionally they can contain a request body and parameters from different sources, either path, query or header. All the things mentioned above have an influence on the generated boilerplate code and implementation stubs for the API operations, so we can start to implement their actual logic.

In general the implementation of an API operation may contain the following pieces:

  • Execute commands or services from the domain namespace
  • Execute services from the integration namespace
  • Map the request body, defined as an API schema, to the input of commands or services
  • Map the output of commands and services to API schemas
  • Return a response - depending on success or error - with the respective status code and body as an API schema.

Prerequisitesโ€‹

โ—๏ธAlternative starting point for your training

If you rather want to use this course as a starting point of your training, you can use a different asset where all courses for designing and the courses for implementing the domain and integration namespace are already completed.
In this case - depending on the chosen implementation language - use either asset "Order_Java_Code_0.3" or "Order_TypeScript_Code_0.3" to create a new project and to continue your training.
You can look up how to create a new project from the Order assets in the Preparation section in the Course Introduction.

Exerciseโ€‹

Estimated time: 20 minutes
Exercise goal: After completing this course you are able to apply all pieces of implementing an API operation including their error handling.
Supported languages: Java and TypeScript

In this exercise we will implement API operation CreateOrder of the /orders path that we designed in the Solution Designer. This operation will be triggered when a POST request is made to the /orders path. It will create a new order based on the provided data in the request body and return a response with the created order as its body.

Step 1: Implement the API operationโ€‹

The API operation to create a new order has the following parts:

  • Map the input of the API operation to the input for the command CreateOrder
  • Execute the command and extract the created entity
  • If no order entity was created, return a response with status code 400 and empty body
  • Map the order entity to the order schema
  • If the new order is a CustomerOrder, send the confirmation email via the PostMail integration service
  • Return a response with status code 201 and the order schema as its body
  1. Open the file /src/main/java/<package-name>/api/v1/OrdersApiV1Provider.java.
    You will see that the file contains an auto-generated stub with a class OrdersApiV1Provider and an empty method createOrder. The implementation of the command needs to be added inside the method block.

    <package-name>: The package name of the Java project (e.g. com.knowis.orderjfinal)

    @Service
    public class OrdersApiV1Provider implements OrdersApiV1Delegate {

    @Override
    public ResponseEntity<Order> createOrder(OrderCreateInput orderCreateInput) {
    //TODO Auto-generated method stub
    return createOrderExampleResponse();
    }
    }
  2. To reuse the implemented command CreateOrder as well as the integration service PostMail, we have to create a private field for class OrderCommand and for the integration service class. Additionally we have to create a constructor with two parameters that are then assigned to the respective private fields.
    Furthermore we will create private field that enables logging since it is not part of the pre-generated code in API operations.

    private static final Logger log = LoggerFactory.getLogger(OrdersApiV1Provider.class);

    private final OrderCommand orderCommand;
    private final PostMail postMail;

    public OrdersApiV1Provider(OrderCommand orderCommand, PostMail postMail) {
    this.orderCommand = orderCommand;
    this.postMail = postMail;
    }

    Please use the auto-import function of your IDE to import the missing classes (and repeat this for the following steps). If you are not very familiar with imports, go back to Course 11 - Step 3: Import missing Classes for detailed information on class imports. Furthermore, you are free to compare your current imports with the recommended list of imports at the end of this section. Therefore, click here.

    โ—๏ธinfo

    If you use a project which is created from one of the assets that are ready to use for the implementation courses (they have the phrase "Code" in their name), the private fields for logging and the OrderCommand class already exist. Then you just have to add the missing code for the PostMail class.

  3. After injecting the fields, we will extend the createOrder method in the following steps.
    First, we have to remove the auto-generated lines in the method stub (see highlighted lines) from the createOrder method.

    @Override
    public ResponseEntity<Order> createOrder(OrderCreateInput orderCreateInput) {
    //TODO Auto-generated method stub
    return createOrderExampleResponse();
    }
  4. Then we will map the operation's input to the input entity of command CreateOrder.

    // Step 1: Map the input of the api operation to the input for the command CreateOrder
    CreateOrderInput createOrderInputEntity = new CreateOrderInputEntity();
    createOrderInputEntity.setReferenceId(orderCreateInput.getReferenceId());
    createOrderInputEntity.setOrderType(OrderType.valueOf(orderCreateInput.getOrderType().name()));

    // Map the order items in the schema to the entity
    List<com.knowis.orderjfinal.sdk.domain.ord.entity.OrderItem> orderItemsEntity = orderCreateInput.getOrderItems()
    .stream()
    .map(MappingUtils::mapOrderItemSchemaToEntity)
    .toList();
    createOrderInputEntity.setOrderItems(orderItemsEntity);

    Replace the phrase com.knowis.orderjfinal with the package name of your project (and repeat this for the following steps).

    MappingUtils: is the class handling the mapping between entities and API schemas. We added this class as preparation for the implementation phase in Course 10: Set up Project for Implementation

  5. Next, we will execute command CreateOrder and extract the returned order entity.

    // Step 2: Execute the domain command and extract the created entity
    com.knowis.orderjfinal.sdk.domain.ord.entity.Order orderEntity
    = this.orderCommand.createOrder(createOrderInputEntity);
  6. In case the extracted entity is null, we have to return a response with status code 400 and empty body. This indicates users that something went wrong when executing the API operation.

    // Step 3: If no order entity was created, return a response with BAD_REQUEST status and empty body
    if (orderEntity == null) {
    log.error("Order could not be created!");
    return ResponseEntity.badRequest().build();
    }

    ResponseEntity.badRequest.build(): creates a response with status 400 and empty body

  7. Then we will map the order entity to the Order schema using the MappingUtils class.

    // Step 4: Map the order entity to the Order schema
    Order orderSchema = MappingUtils.mapOrderEntityToSchema(orderEntity);
  8. Now we will use the PostMail integration service to send confirmation emails, if the new order is a customer order.

    // Step 5: If the new order is a CustomerOrder, send the confirmation email via the PostMail integration service
    // Check the order type from the input
    if (orderCreateInput.getOrderType().equals(OrderCreateInput.OrderTypeEnum.CUSTOMER)) {
    // Create the input for the integration service
    PostMailInput postMailInput = new PostMailInputEntity();
    postMailInput.setMessageFormat(MessageFormat.HTML);
    postMailInput.setSender("roboflow@knowis.email");
    postMailInput.setTo(orderSchema.getReferenceId());
    // Adding a "cc" is not required for the order confirmation
    postMailInput.setSubject("RoboFlow Order Confirmation Email");
    postMailInput.setMessage(
    TextFormatter.format(
    "The Order with id {} and reference {} was confirmed and is ready to be processed!",
    orderSchema.getId(),
    orderSchema.getReferenceId()
    )
    );

    // Call the integration service
    this.postMail.execute(postMailInput);
    }
  9. At last, we will return a response with status code 201 and the order schema as its body. This indicates users that the API operation was executed successfully.

    // Step 6: Return the order schema as a ResponseEntity with status code 201
    return ResponseEntity.status(HttpStatus.CREATED).body(orderSchema);
โ„น๏ธRecommended List of Imports

The provided list supports you to compare your current imports with the recommended list, especially if your IDE complains about errors in the code at this stage of the course.

List of Imports - File OrdersApiV1Provider.java
// Fundamental 1: Generated classes based on design
import com.knowis.orderjfinal.sdk.api.v1.api.OrdersApiV1Delegate;
import com.knowis.orderjfinal.sdk.api.v1.model.Order;
import com.knowis.orderjfinal.sdk.api.v1.model.OrderCreateInput;
import com.knowis.orderjfinal.sdk.api.v1.model.OrderItem;
import com.knowis.orderjfinal.sdk.domain.facade.Repository;
import com.knowis.orderjfinal.sdk.domain.ord.entity.CreateOrderInput;
import com.knowis.orderjfinal.sdk.domain.ord.entity.CreateOrderInputEntity;
import com.knowis.orderjfinal.sdk.domain.ord.type.OrderType;
import com.knowis.orderjfinal.sdk.integration.email.entity.PostMailInput;
import com.knowis.orderjfinal.sdk.integration.email.entity.PostMailInputEntity;

// Fundamental 2: Generated classes reflecting the capabilities of the Workbench
import k5.sdk.springboot.util.text.TextFormatter;

// Fundamental 3: Built-in Java classes
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

// Fundamental 4: Classes from other implementation files
import com.knowis.orderjfinal.api.v1.utils.MappingUtils;
import com.knowis.orderjfinal.domain.ord.command.OrderCommand;
import com.knowis.orderjfinal.integration.email.service.PostMail;

The package name of the provided code is com.knowis.orderjfinal. All occurrences of this phrase have to be replaced with the package name of your project.

Step 2: Push 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 "Added implementation code".
๐ŸŒŸCongratulations!

You have successfully implemented an API operation! You also pushed your changes to your remote Git repository!
Now you are able to provide your own API operations with the actual logic. You understand how elements from the domain and integration namespace can be used and how errors are handled in API operations.

What's Next?โ€‹

As this course concludes the implementation phase of your training, you are now ready to continue with the deployment of your service. In the next course you will learn how to build and deploy your implemented service.