This API is a case study for T.O.M. company.
I have used combinations of different design patterns in my project: modified MVC with focus on controller and model layers, Repository Pattern, Service Pattern, Exception Handling Pattern. Since my API doesn't need any user interface, I didn't need to write any views.
S.O.L.I.D. principles were always top priority at development stage, no layer does the job of any other layer, no function deals with anything more than it needs to deal.
Following are the technologies I have used in this project: Java 17, Maven, Spring, SpringBoot, Spring Initializr, Spring Boot Actuator, Docker, Postman, MongoDB, JUnit, Mockito, TomCat, MockMVC, AWS, EC2, JMeter, Prometheus, Grafana.
Both code comments and API explanation are ready as a documentation.
After creating a shopping cart please store id of shopping cart, because you will need it for nearly every request.
This is the base url where you can access it directly from clearnet, http://16.171.2.137:8080/v1/shopping-carts
And this is the dockerized version of it with Amazon Linux 2, http://13.51.177.101:8080/v1/shopping-carts I have stopped it because it exceeded the limit of my subscription
If you locally downloaded the project, please do clean install with maven first, then do docker-compose up
The project is written as it can be tested. ShoppingCartServiceTest and ShoppingCartControllerIT are test classes. Mockito is used in this project. Unit tests are written with JUnit and Mockito, Integration tests are written with MockMVC and Mockito.
51 Unit tests are passed.
- getItemsOfShoppingCartById()
- getItemsOfShoppingCartByIdThrowsShoppingCartNotFoundException()
- getAllShoppingCarts()
- getShoppingCartById()
- getShoppingCartByIdThrowsException()
- validateItem()
- validateItemUrlThrowsException()
- validateItemNameThrowsException()
- validateItemPriceThrowsException()
- validateItemPriceThrowsException2()
- validateItemQuantityThrowsException()
- validateItemQuantityThrowsException2()
- validateItemCategoryThrowsException()
- changeCouponToHardCoded()
- changeCouponToHardCodedCoupon1()
- changeCouponToHardCodedCoupon2()
- changeCouponToHardCodedCoupon3()
- validateCoupon()
- validateCoupon2()
- validateCoupon3()
- validateCouponTypeThrowsException()
- validateCouponTypeThrowsException2()
- validateCouponTypeIsAmountButRateIsNotZeroThrowsException()
- validateCouponTypeIsRateButAmountIsNotZeroThrowsException()
- validateCouponBothRateAndAmountAreZeroThrowsException()
- validateCouponTypeIsRateButRateIsNegativeThrowsException()
- validateCouponTypeIsRateButRateIsBiggerThanOneThrowsException()
- validateCouponTypeIsAmountButAmountIsNegativeThrowsException()
- validateCouponTypeIsAmountButAmountIsZeroThrowsException()
- validateWholeShoppingCart()
- validateWholeShoppingCartTotalPriceThrowsException()
- validateWholeShoppingCartDiscountedPriceThrowsException()
- validateWholeShoppingCartItemListHasRepetitionThrowsException()
- validateWholeShoppingCartCouponListHasRepetitionThrowsException()
- validateWholeShoppingCartCouponListIsEmpty()
- calculatePrices()
- calculatePricesButTotalAmountOfCouponsBiggerThanCartSum()
- calculatePricesButCouponTypeIsWrongThrowsException()
- calculatePricesButCouponListIsNull()
- calculatePricesButSumIsEqualsToUpperLimit()
- createNewShoppingCart()
- createNewShoppingCartButShoppingCartAlreadyExistsThrowException()
- createNewShoppingCartButShoppingCartAlreadyExists2ThrowException()
- createNewShoppingCartButCouponListIsNull()
- createNewShoppingCartButItemListIsNull()
- deleteItem()
- deleteItemButItemNotFoundThrowsException()
- applyCoupon()
- applyCouponButCouponListIsNull()
- applyCouponButCouponListIsNotNull()
- addNewItemToShoppingCart()
7 Integration tests are passed.
- getAllShoppingCarts()
- getShoppingCartById()
- getAllItemsInShoppingCarts()
- addNewShoppingCart()
- addNewItemToShoppingCart()
- applyCouponToShoppingCart()
- deleteItemInShoppingCart()
Package | File | Code Coverage |
---|---|---|
com.tom.shoppingcartapi.service | ShoppingCartService | 100% |
com.tom.shoppingcartapi.controller | ShoppingCartController | 100% |
- I exclude exception handlers in controller while calculating code coverage because they are one line methods that has no user implementation in it.
- Because my business logic only resides at ShoppingCartService, I wrote my unit tests only for service layer, and I calculate code coverage according to that.
This endpoint returns list of every shopping carts.
List<ShoppingCart>.
GET
No parameters.
No body.
Code | Description |
---|---|
200 | Success |
This endpoint returns a ShoppingCart object that id={id}.
ShoppingCart object.
GET
PathVariable: String id
No body.
Code | Description | Text |
---|---|---|
200 | Success | |
404 | ShoppingCartNotFoundException | There is no ShoppingCart with that id. |
This endpoint returns a list of Items in a shopping cart that id={id}.
List<Item>.
GET
PathVariable: String id
No body.
Code | Description | Text |
---|---|---|
200 | Success | |
404 | ShoppingCartNotFoundException | There is no ShoppingCart with that id. |
This endpoint creates the given shopping card and returns it back to the client.
ShoppingCart object that is newly created.
POST
No parameters.
ShoppingCart object.
{
"id": "STRING_ID"
"customerId": "STRING_ID",
"items": [ @Optional
{ @Optional
"id": "STRING_ID_ITEM",
"name": "STRING_NAME",
"url": "STRING_URL",
"price": DOUBLE_PRICE,
"quantity": INTEGER_QUANTITY,
"category": "STRING_CATEGORY",
"createDate": "DATE", @Optional(auto-assigned)
"changeDate": "DATE" @Optional(auto-assigned)
}
],
"coupons": [ @Optional
{@Optional
"id": "coupon1",
},
],
"totalPrice": DOUBLE_TOTALPRICE, @Optional(will be calculated and updated anyways)
"discountedPrice": DOUBLE_DISCOUNTEDPRICE @Optional(will be calculated and updated anyways)
}
- Total price and discounted price are going to be calculated by backend any time it needs to change(like at create, new item, delete item, apply coupon).
- You can use following coupons by changing id: "coupon1", "coupon2", "coupon2"
- You can add custom coupons by filling out every attributes of a coupon like so:
{
"id": "STRING_ID",
"type": "STRING_TYPE",
"rate": DOUBLE_RATE,
"amount": DOUBLE_AMOUNT
}
Type can be either 'rate' or 'amount'. If you pick 'rate' as type then amount should be 0, if you pick 'amount' as type then rate should be 0. Rates should be between 0 and 1, and cannot be 0. Amounts should be positive.
Code | Description | Text |
---|---|---|
201 | Created | |
400 | BadShoppingCartException | Price must be a positive number. Incorrect shopping cart. |
400 | BadCouponException | Type of a coupon cannot be neither empty nor null. Type of a coupon must be either 'rate' or 'amount'. Rate of a coupon of type rate, must be a positive value between 0 and 1. Amount of a coupon of type amount, must be a positive value. Amount of, a coupon of type rate must be zero. Rate of, a coupon of type amount must be zero. |
400 | BadItemException | Item url cannot be empty. Item name cannot be empty. Category name cannot be empty. Price must be a positive number. Quantity must be a positive number. |
409 | ItemAlreadyPresent | There are multiple items with the same id. Incorrect shopping cart. |
409 | CouponAlreadyPresentException | There are multiple coupons with the same id. Incorrect shopping cart. |
409 | ShoppingCartAlreadyPresentException | Customer already has a shopping cart. You may want to update it or delete then recreate it. ShoppingCart with that id already present. You may want to update it or delete then recreate it. |
This endpoint returns a list of items after adding the given item to the items list of a shopping cart that id={id}.
List<Item> that is updated recently.
POST
PathVariable: String id
Item object.
{
"id": "STRING_ID_ITEM",
"name": "STRING_NAME",
"url": "STRING_URL",
"price": DOUBLE_PRICE,
"quantity": INTEGER_QUANTITY,
"category": "STRING_CATEGORY",
"createDate": "DATE", @Optional(auto-assigned)
"changeDate": "DATE" @Optional(auto-assigned)
}
Code | Description | Text |
---|---|---|
200 | Success | |
400 | BadItemException | Item url cannot be empty. Item name cannot be empty. Category name cannot be empty. Price must be a positive number. Quantity must be a positive number. |
400 | BadShoppingCartException | Price must be a positive number. Incorrect shopping cart. |
404 | ShoppingCartNotFoundException | There is no ShoppingCart with that id. |
409 | ItemAlreadyPresent | There are multiple items with the same id. Incorrect shopping cart. |
Apply a coupon to a specific shopping cart that id={id}. Total price and discounted price will be automatically updated if coupon is correct.
Coupon object that is newly created.
POST
PathVariable: String id
Coupon object. If you are going to use the first structure then id must be either 'coupon1', 'coupon2' or 'coupon3'.
{
"id": "STRING_ID"
}
If you are going to use the second stucture then coupon id must be different than first one's and other coupons at the shopping cart.
{
"id": "STRING_ID",
"type": "STRING_TYPE",
"rate": DOUBLE_RATE,
"amount": DOUBLE_AMOUNT
}
Type must be either 'rate' or 'amount'. If you picked 'rate' then amount should be 0 and rate should be between 0 and 1. If you picked 'amount' then rate should be 0 and amount should be positive. Id must be different than other coupons' id at the specific shopping cart. I could have done this better by not having rate or amount but by having only 'value' and 'type'.
Code | Description | Text |
---|---|---|
201 | Created | |
400 | BadCouponException | Type of a coupon cannot be neither empty nor null. Type of a coupon must be either 'rate' or 'amount'. Rate of a coupon of type rate, must be a positive value between 0 and 1. Amount of a coupon of type amount, must be a positive value. Amount of, a coupon of type rate must be zero. Rate of, a coupon of type amount must be zero. |
404 | ShoppingCartNotFoundException | There is no ShoppingCart with that id. |
409 | CouponAlreadyPresentException | There are multiple coupons with the same id. Incorrect shopping cart. |
This endpoint deletes an item from a shopping cart. And returns nothing.
Void.
DELETE
PathVariable: String id
PathVariable: String itemId
No body.
Code | Description | Text |
---|---|---|
201 | Created | |
404 | ShoppingCartNotFoundException | There is no ShoppingCart with that id. |
404 | ItemNotFoundException | There is no Item with that id. |
You can use this endpoint to see different informations and metrics about the API like logs, status etc.
Information about the API.
No parameters.
No body.
Code | Description | Text |
---|---|---|
200 | Success |