Skip to content

mohllal/vending-machine-rest

Repository files navigation

Vending Machine RESTFul APIs

Design a RESTFul API system for a vending machine, allowing users with a “seller” role to add, update or remove products, while users with a “buyer” role can deposit coins into the machine and make purchases.

The vending machine should only accept 5, 10, 20, 50 and 100 cent coins.

Commands

npm run dev # run the API in development mode
npm run test # test using Jest
npm run coverage # test and open the coverage report in the browser
npm run lint # lint using ESLint
npm run docs # generate API docs

Playing locally

First, you will need to install and run MongoDB in another terminal instance. You need to run the MongoDB server as a ReplicaSet in order to be able to use the Transactions feature.

Please, check this tutorial for more information on how to convert a standalone MongoDB server to a single-node replica set.

mongod --replSet rs0 --dbpath /data/db --port 27017
$ rs.initiate()

Then, run the server in development mode.

npm install
npm run dev
Express server listening on http://0.0.0.0:9000, in development mode

Playing with Docker Compose

To run the application locally, you can use the Docker Compose tool.

docker-compose up -f docker-compose.local.yaml
Express server listening on http://0.0.0.0:9000, in compose mode

And to run the tests locally using Docker Compose.

docker-compose up -f docker-compose.test.yaml

API Docs

You can find the API documentation in the DOCS.md file. This file is autogenerated by the apiDoc inline APIs documentation tool.

Data Consistency and Transactions

How we can guarantee data consistency in the /buy endpoint?

  • Pessimistic Locking: In MongoDB, a write operation is atomic on the level of a single document/record, but not on the level of a collection. Read more about Atomicity and Transactions.

  • Optimistic Locking: Ensuring that the document/record wasn't updated by some other write operation by checking the locking key before updating/writing the document back using the findAndUpdateOne() method. Read more about Atomic Updates using findOneAndUpdate.

  • Transactions: MongoDB supports multi-document transactions (in a single or multiple collections). So decreasing the product's stock, decreasing the user's balance, and creating the order document/record itself are all part of the same transaction. Read more about Transactions.

const product = await Product.findOneAndUpdate({ _id: productId, amount: { $gte: amount } }, ... }, { session })

...

const buyer = await User.findOneAndUpdate({ _id: user.id, deposit: { $gte: cost } }, ... }, { session })

...

const order = new Order({ buyerId: buyer, productId: product, amount, cost })
await order.save({ session })
...

Languages