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.
  • The service uses an API first approach to implement this service. The Java and Spring annotated REST classes were auto generated from The Online Swagger Editor
  • The AbstractController clas implements the API interface
  • The below JPA repository classes are used 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
    
   namespace ControllerClasses{
    
        class ToolRentalAPI{
        <<interface>>
        }
        class Tool{
        }
        class ToolRentalPrice{
        }
        class RentalRequest{
        }
        class RentalAgreement{
        }
        class AbstractController{
        }
    }

    Tool <-- ToolRentalAPI
    ToolRentalPrice <-- ToolRentalAPI
    RentalRequest <-- ToolRentalAPI
    RentalAgreement <-- ToolRentalAPI
    ToolRentalAPI <|-- AbstractController

    namespace ServiceClasses {
        class IRentalAgreementService {
        <<interface>>
        }
        class IHoliday {
            <<interface>>
        }
        class FourthJuly{
            +isHoliday()
        }
        class LaborDay{
            +isHoliday()
        }
        class Weekend{
            +isHoliday()
        }
        class ObserverableHoliday{
            +isHoliday()
        }
        class RentalAgreementService{
        }
        class RentalDurationService{
        }
        class DateRangeDetails {
        }
    }

    IRentalAgreementService <|-- RentalAgreementService
    RentalDurationService <-- RentalAgreementService
    Weekend <-- RentalDurationService
    ObserverableHoliday <-- RentalDurationService
    DateRangeDetails <-- RentalDurationService
    DateRangeDetails <-- RentalAgreementService

    
    IRentalAgreementService <-- AbstractController 
    
    namespace PersistenceClasses {
    
        class ToolRepository { 
        <<interface>>
        }
        class ToolPriceRepository { 
        <<interface>>
        }
        class RentalRequestRepository { 
        <<interface>>
        }
        class RentalAgreementRepository { 
        <<interface>>
        }   
        class ToolEntity{
        }
        
        class ToolPriceEntity{
        }
        
        class RentalRequestEntity{
        }
        
        class RentalAgreementEntity{
        }
    
    }

    ToolRepository <-- RentalAgreementService
    ToolPriceRepository <-- RentalAgreementService
    RentalRequestRepository <-- RentalAgreementService
    RentalAgreementRepository <-- RentalAgreementService
    RentalAgreementEntity <-- RentalAgreementRepository
    RentalRequestEntity <-- RentalRequestRepository
    ToolPriceEntity <-- ToolPriceRepository
    ToolEntity <-- ToolRepository

    IHoliday <|-- FourthJuly
    IHoliday <|-- LaborDay
    IHoliday <|-- Weekend
    IHoliday <|-- ObserverableHoliday
    LaborDay <-- ObserverableHoliday
    FourthJuly <-- ObserverableHoliday
    IHoliday: +isHoliday()
    
Loading

Sequence Diagram : Fetch All Tools available for Rental

Please see Fetch all tools available for rent

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