Skip to content
Dennis Chacko edited this page Jul 18, 2024 · 95 revisions

Tool Rental Service

A simple tool rental application

Table of Contents

Overview

The Tool Rental Service is simple tool rental application with the below features:

  • Customers rent a tool for a specified number of days.
  • When a customer checks out a tool, a Rental Agreement is produced.
  • The store charges a daily rental fee, whose amount is different for each tool type.
  • Some tools are free of charge on weekends or holidays.
  • Clerks may give customers a discount that is applied to the total daily charges to reduce the final charge.

High Level Design and Solution

Solution Overview

  • The service exposes an REST API that supports the below resources/operations
    • Operation to read all available tools
    • Operation to add/delete tools available for rent [Future state]
    • Operation to read pricing details per tool
    • Operation to add/update pricing details per tool [Future state]
    • Operation to checkout and create a rental agreement per tool
  • Each operation should validate user entered input and throw HTTP BAD Request for validation errors
  • The service should follow an API First Design approach while adding new functionality. See API First Design approach for more details
  • The service should auto-generate Spring REST code using the supplied Open API file. The OpenAPI file is available here

Class Diagram

The Class Diagram includes some classes auto-generated from the OpenApi specification for this service

  • The OpenAPI specification for the Tool Rental Service is available here.
  • Please use a API first approach to implement this service. Use an OpenAPI tool to generate the API related classes
  • Create an RentalAgreementService service that implements the API functionality
  • Create JPA repository classes to interface with the database
    • ToolRepository to fetch available tools from the database
    • ToolPriceRepository to fetch pricing details about a tool
    • RentalRequestRepository to persist each rental request in the database
    • RentalAgreementRepository to persist rental agreements in the database
---
title: ToolRentalService Class Diagram
---
classDiagram
   
    ToolRentalAPI <-- Tool
    ToolRentalAPI <-- ToolRentalPrice
    ToolRentalAPI <-- RentalRequest
    ToolRentalAPI <-- RentalAgreement
    ToolRentalAPI <|-- AbstractController

    class Tool{
    }
    class ToolRentalPrice{
    }
    class RentalRequest{
    }
    class RentalAgreement{
    }
    class AbstractController{
    }

    IRentalAgreementService <|-- RentalAgreementService
    class RentalAgreementService{
    }
    
    IRentalAgreementService <-- AbstractController 

    ToolRepository <-- RentalAgreementService
    ToolPriceRepository <-- RentalAgreementService
    RentalRequestRepository <-- RentalAgreementService
    RentalAgreementRepository <-- RentalAgreementService

    IHoliday <|-- FourthJuly
    IHoliday <|-- LaborDay
    IHoliday <|-- Weekend
    IHoliday <|-- ObserverableHoliday
    IHoliday: +isHoliday()
    
    class FourthJuly{
        +isHoliday()
    }
    class LaborDay{
        +isHoliday()
    }
    class Weekend{
        +isHoliday()
    }
    class ObserverableHoliday{
        +isHoliday()
    }

    
Loading
Class Diagram

image

Sequence Diagram : Fetch All Tools available for Rental

Sequence diagram for fetching all tools available for rent in the system

image

Sequence Diagram : Fetch Pricing Details for each Tool

Sequence diagram for fetching pricing details per tool

image

Sequence Diagram : Checkout a tool for rental

Sequence Diagram to checkout out a tool for rent

image

OpenAPI Specification

Sample OpenAPI Specification

Expand to view code
{
  "openapi": "3.0.1",
  "info": {
    "title": "ToolRentalApi",
    "description": "An API to facilitate the rentai of tools",
    "termsOfService": "",
    "contact": {
      "email": ""
    },
    "license": {
      "name": "",
      "url": "http://unlicense.org"
    },
    "version": "1.0"
  },
  "servers": [
    {
      "url": "http://localhost:3000",
      "description": "Generated server url"
    }
  ],
  "paths": {
    "/api/v1/tool/{code}/checkout": {
      "post": {
        "tags": [
          "Tool"
        ],
        "summary": "Create a rental agreement",
        "operationId": "postApiV1ToolCodeCheckout",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RentalRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RentalAgreement"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool/{code}/inventory": {
      "get": {
        "tags": [
          "Tool"
        ],
        "summary": "Get Inventory details",
        "operationId": "getApiV1ToolInventory",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Inventory"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool/{code}": {
      "get": {
        "tags": [
          "Tool"
        ],
        "summary": "Get pricing details",
        "operationId": "getApiV1ToolCode",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolPricingDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool/rentalAgreement": {
      "get": {
        "tags": [
          "RentalAgreement"
        ],
        "summary": "Get all rental agreements",
        "operationId": "getAllRentalAgreements",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RentalAgreement"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "tags": [
          "Tool"
        ],
        "summary": "Get tools available for rent",
        "operationId": "getApiV1Tool",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Tool"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "RentalRequest": {
        "required": [
          "checkout_date",
          "rental_days_count",
          "tool_code"
        ],
        "type": "object",
        "properties": {
          "tool_code": {
            "type": "string",
            "readOnly": true
          },
          "rental_days_count": {
            "type": "integer",
            "format": "int32"
          },
          "discount_percent": {
            "maximum": 100,
            "minimum": 0,
            "type": "integer",
            "format": "int32"
          },
          "checkout_date": {
            "type": "string"
          }
        }
      },
      "RentalAgreement": {
        "required": [
          "checkout_date",
          "tool_code"
        ],
        "type": "object",
        "properties": {
          "tool_code": {
            "type": "string"
          },
          "tool_type": {
            "type": "string"
          },
          "tool_brand": {
            "type": "string"
          },
          "rental_days": {
            "type": "string"
          },
          "checkout_date": {
            "type": "string"
          },
          "due_date": {
            "type": "string"
          },
          "daily_charge": {
            "type": "number"
          },
          "charge_days": {
            "type": "number"
          },
          "pre_discount_charge": {
            "type": "number"
          },
          "discount_percent": {
            "type": "string"
          },
          "discount_amount": {
            "type": "number"
          },
          "final_charge": {
            "type": "number"
          }
        }
      },
      "Inventory": {
        "required": [
          "current_count",
          "max_available",
          "tool_code"
        ],
        "type": "object",
        "properties": {
          "tool_code": {
            "type": "string"
          },
          "max_available": {
            "type": "number"
          },
          "current_count": {
            "type": "number"
          }
        }
      },
      "ToolPricingDetails": {
        "required": [
          "code",
          "daily_charge",
          "holiday_charge",
          "weekend_charge"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Unique identifier for a tool instance"
          },
          "daily_charge": {
            "type": "number",
            "description": "Daily Charge of the tool"
          },
          "weekend_charge": {
            "type": "boolean",
            "description": "Is the tool chargable on weekends?"
          },
          "holiday_charge": {
            "type": "boolean",
            "description": "Is the tool chargeable on observed holidays?"
          }
        },
        "description": "Rental price details about each tool available for rent"
      },
      "Tool": {
        "required": [
          "brand",
          "code",
          "type"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Unique identifier for a tool instance"
          },
          "type": {
            "type": "string",
            "description": "The type of tool."
          },
          "brand": {
            "type": "string"
          }
        },
        "description": "Tools available for rent"
      }
    }
  }
}

Sample Request/Response to fetch all available tools

Request to fetch all tools available for rent

The below curl command can be used to fetch all available tools

curl --location 'http://localhost:3000/api/v1/tool' \ --header 'Accept: application/json'

Fetch All Available tools Response

The service should respond back with the below json:

[
    {
        "code": "CHNS",
        "type": "Chainsaw",
        "brand": "Stihl"
    },
    {
        "code": "LADW",
        "type": "Ladder",
        "brand": "Werner"
    },
    {
        "code": "JAKD",
        "type": "Jackhammer",
        "brand": "DeWalt"
    },
    {
        "code": "JAKR",
        "type": "Jackhammer",
        "brand": "Ridgid"
    }
]

Sample Request/Response to fetch pricing details for a tool

Request to fetch pricing details for a tool

The below curl command can be used to fetch all available tools

curl --location --globoff '{{baseUrl}}/api/v1/tool/:CHNS/rentalprice' \ --header 'Accept: application/json'

Response to fetch pricing details for a tool
{
  "code": "CHNS",
  "daily_charge": "2.99",
  "holiday_charge": "true",
  "weekend_charge": "false"
}

Sample Request/Response to checkout and create a rental agreement

Request to checkout and create a rental agreement

The below curl command can be used to checkout and create a rental agreement

curl --location --globoff '{{baseUrl}}/api/v1/tool/CHNS/checkout' \ --header 'Content-Type: application/json' \ --header 'Accept: application/json' \ --data '{ "checkout_date": "01/12/2024", "rental_days_count": "10", "tool_code": "CHNS", "discount_percent": "10" }'

{
  "checkout_date": "01/12/2024",
   "rental_days_count": "10",
   "tool_code": "CHNS",
   "discount_percent": "10"
}
Sample Response to checkout and create a rental agreement

The service should respond back with the below json

{
    "tool_code": "CHNS",
    "tool_type": "Chainsaw",
    "tool_brand": "Stihl",
    "rental_days": "10",
    "checkout_date": "01/12/2024",
    "due_date": "01/22/2024",
    "daily_charge": 2.99,
    "charge_days": 7,
    "pre_discount_charge": 20.93,
    "discount_percent": "10%",
    "discount_amount": 2.09,
    "final_charge": 18.84
}

The service responds with the below error codes for invalid inputs

Error Condition Response Code and Desciption
Unknown Tool Code Http 400 Bad Request Invalid tool code
Invalid or missing checkout date Http 400 Bad Request Invalid or missing checkout date
Invalid discount percent Http 400 Bad Request Invalid discount %. Please enter a value between 0-100

Error Handling

  • All business errors should be wrapped in an ValidationException class
  • Communicate business exception as HTTP 400 Bad Request

Sample error handling Spring-based RestService . Reference Error Handling for Spring Rest Services

@ControllerAdvice
public class RestResponseEntityExceptionHandler 
  extends ResponseEntityExceptionHandler {

    @ExceptionHandler(value 
      = { ValidationException.class })
    protected ResponseEntity<Object> handleConflict(
      RuntimeException ex, WebRequest request) {
        String bodyOfResponse = "This should be application specific";
        return handleExceptionInternal(ex, bodyOfResponse, 
          new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
    }
}
Clone this wiki locally