Course 4: Implement API Operations
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.
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.
- Open your Workspace in the Solution Designer.
- 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.
- Ensure that you have setup the necessary prerequisites for local development
- 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.
If you have cloned the project before already, it is enough to perform the command k5 pull
to get the latest changes.
-
Open the section "Implementation" in the "Solution CLI" and follow the instructions of the section in your terminal.
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â
- TypeScript
- Java
-
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.
-
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. -
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 namespaceord
.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. -
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 schemaOrder
. -
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. -
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 errorCustomerNotFound
, which is thrown by the domain serviceFindOpenOrdersForCustomer
that is used inside the API operation.
Therefore, we will extend the functiongetOrdersForCustomerErrorHandler()
. As a first step, check if the function's parametererror
is of typeBusinessError
and if so, then check if it is aCustomerNotFound
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. -
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;
-
Open the file
/src/main/java/<package-name>/api/ordapi/CustomerApiOrdapiProvider.java
. (Replace the placeholders with your values)You will see that the file contains an auto-generated stub with a more or less empty implementation of the class
CustomerApiOrdapiProvider
with the functiongetOrdersForCustomer
that represents our API Operation.CustomerApiOrdapiProvider.java/**
* A stub that provides implementation for the CustomerApiOrdapiDelegate
*/
@Service
@ComponentScan(basePackages = "com.knowis.orderj.sdk.api.ordapi.api")
public class CustomerApiOrdapiProvider implements CustomerApiOrdapiDelegate {
@Override
public ResponseEntity<List<Order>> getOrdersForCustomer(String customerId) {
//TODO Auto-generated method stub
return getOrdersForCustomerExampleResponse();
}
}In the next steps, we will extend the implementation of the class step by step with the code to get all orders for a customer.
-
Extend the implementation class. Therefore, inject an instance of the Domain Service
FindOpenOrdersForCustomer
by defining a new constructor for the API-Service.CustomerApiOrdapiProvider.javapublic class CustomerApiOrdapiProvider implements CustomerApiOrdapiDelegate {
// Define domain service dependency
private FindOpenOrdersForCustomer findOpenOrdersForCustomer;
// Create a constructor with the new dependency
public CustomerApiOrdapiProvider (FindOpenOrdersForCustomer findOpenOrdersForCustomer) {
this.findOpenOrdersForCustomer = findOpenOrdersForCustomer;
}
...
}Please use the auto-import function of your IDE (IntelliJ is recommended) to import the missing classes.
SpringBoot will provide an instance of the domain service
FindOpenOrdersForCustomer
, that is injected here in the Constructor. -
In the next step, you need to remove the auto-generated lines in the method stub (see highlighted lines) from the
getOrdersForCustomer
method.@Override
public ResponseEntity<List<Order>> getOrdersForCustomer(String customerId) {
//TODO Auto-generated method stub
return getOrdersForCustomerExampleResponse();
} -
As mentioned above, we want to use the domain service
FindOpenOrdersForCustomer
, which throws a business errorCustomerNotFound
. This exception has to be handled inside the API operation. Therefore, add a "try-catch" block inside methodgetOrdersForCustomer
as preparation for handling the exception.@Override
public ResponseEntity<List<Order>> getOrdersForCustomer(String customerId) {
try {
} catch(CustomerNotFound e) {
}
} -
Extend the try-block with the following code to construct an input entity for the domain service, fill it with the customerId and call the domain service:
try {
// construct input for the call to the domain service
FindOpenOrdersForCustomerInput serviceInput = new FindOpenOrdersForCustomerInputEntity();
// set customerId from path parameter for the service input
serviceInput.setCustomerId(customerId);
// call the domain service to receive all the orders if the customer
List<com.knowis.ordlcj.sdk.domain.ord.entity.Order> orders = findOpenOrdersForCustomer.execute(serviceInput);
}Replace
com.knowis.ordlcj
with your package name -
Loop over orders which have been returned by domain service to transform them to API Schema "Order" from our API namespace:
try {
...
// create a new empty list of API schema "Order" to return them as HTTP response
List<Order> apiOrders = new ArrayList<>();
// loop over all order entities loaded by the domain service
for (com.knowis.ordlcj.sdk.domain.ord.entity.Order order : orders) {
// create new API schema "Order"
Order apiOrder = new Order();
// map properties from order entity to API schema "Order"
apiOrder.setCustomerId(order.getCustomerId());
// add API schema to orders list
apiOrders.add(apiOrder);
}
} -
Then return the list of API schema "Order" as a HTTP response with status code 200 via the use of a
ResponseEntity
. This indicates to users of the API operation that it was executed successfully.try {
...
// return a ResponseEntity with the orders list as body and Http status code 200
return ResponseEntity.status(HttpStatus.OK).body(apiOrders);
} -
Now, we switch to the catch-block to handle the business exception. Therefore, throw another exception of class
OrdapiBusinessException
.catch(CustomerNotFound e) {
throw new OrdapiBusinessException(e, HttpStatus.NOT_FOUND, ErrorCode.E0001);
}âšī¸notePlease be aware that
OrdapiBusinessException
is a manually added exception class. For more information, you can checkout this HowTo and the classes inside directory/src/main/java/<package-name>/api/ExceptionHandling
.
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.
- Go to your terminal and run command
k5 compile
to ensure that you do not have any issues in your code. - Run the command
k5 push -m "Implementation of API operation getOrdersForCustomer for my Orders Project"
.
You have successfully implemented your Orders Project. You also pushed your changes to your remote Git repository in Gitlab.
Related Linksâ
Please find more information about Implementing API operations: