-
Notifications
You must be signed in to change notification settings - Fork 2
API Walkthrough
In order to successfully follow along with this tutorial, please be sure to read running the application.
This walkthrough covers consuming our fictitious order entry / inventory management web api by creating a customer, category, product, and placing an order on behalf of the new customer. Further, it covers submitting and shipping an order to see how it affects inventory.
Along the way, you will see the peasy-js rules engine in action and how you can use concurrency techniques within your middle tier using peasy-js.
This tutorial uses Postman to interact with the web api, however, you can choose to use whatever http communication tool that you are comfortable with.
To begin, let's query our current customers:
You should notice that one customer is returned with a status code of 200.
Let's create a new customer who we will eventually place an order on behalf of:
Notice that a POST request was made with the content type set as application/json. Further, notice that the request resulted with a status code of 201. Make note of the customer id that was assigned.
Now let's query for existing categories:
You should notice that two categories are returned with a status code of 200.
Let's create a new category:
Notice that a POST request was made with the content type set as application/json. Further, notice that the request resulted with a status code of 201. Make note of the category id that was assigned.
Now let's query for existing products:
You should notice that two products are returned with a status code of 200.
Let's create a new product and intentionally POST an empty request body:
Notice that the response code is 400 and that we receive an array of validation errors. The validation rules responsible for the errors are wired up in the CreateProductCommand exposed via the ProductService.
You can learn more about business services here and more about commands here.
Also notice that the returned error objects contain association
and message
values. The association field is particularly helpful for UI consumption scenarios.
Now let's attempt to create a new product, supplying all of the required information:
Notice that a POST request was made with the content type set as application/json. Further, notice that the request resulted with a status code of 201. Make note of the product id that was assigned.
Finally, let's delete a product that is no longer needed:
Notice that a DELETE request was made and resulted with a status code of 204.
Now let's query for existing inventory items:
You should notice that two inventory items are returned with a status code of 200.
Make special note of the inventory item with the product id of 3. This inventory item was created on our behalf when we created the "Javascript: the good parts" product. The logic for this can be viewed in the CreateProductCommand.
Also notice that there is no inventory item for product id 2. This is because the logic in the DeleteProductCommand removes the associated inventory item.
Both of these commands are exposed via the ProductService.
Now let's create a new order on behalf of our new customer:
Notice that a POST request was made with the content type set as application/json. Further, notice that the request resulted with a status code of 201. Make note of the order id that was assigned.
Now let's add an order item specifying our new product ("Javascript: the good parts") to the order:
Notice that a POST request was made with the content type set as application/json. The OrderItemService has been wired up with business rules associated with the insert that ensure that the order item's amount == the product price * quantity as well as a price validation that matches the current product price in our data store.
Therefore, because we specified an amount of 15 and a price of 10, we received a 400 status code along with two validation errors in our response payload.
Let's correct our mistakes and attempt to create the order item again:
Notice that the request resulted with a status code of 201. Make note of the order item id that was assigned.
Now let's submit the order item, which indicates that it is ready to be shipped:
Here a POST request was made with the content type set as application/json. This may be confusing, as what we are really doing is transitioning the order item from a PENDING state to a SUBMITTED state. This change-state operation is really more of an RPC invocation, which often conflicts with RESTFul design.
While there are many ways to design around these types of design dilemmas, in this case it made the most sense to expose the submission action as a POST against an order item. You can view the SubmitCommand exposed via the OrderItemService here.
Now that we've posted the order item, let's go ahead and ship it:
Notice that a POST request was made with the content type set as application/json. Further, notice that the request resulted with a status code of 201. However, this time the status is set to BACKORDERED. This is because we never updated the inventory quantity for the "Javascript: the good parts" product.
Let's do that now:
In this update, we set the inventory quantity of the "Javascript: the good parts" product to 10. Notice that a PUT request was made with the content type set as application/json and that the request responded with a status code of 200.
Now that the inventory has been updated, let's attempt to ship the item again:
Notice that a POST request was made with the content type set as application/json responding with a status code of 201. In the response payload, notice that the status is now SHIPPED.
Now that the item has shipped, that should have reduced our inventory count for the "Javascript: the good parts" product to 8, as the order item specified a quantity of 2.
Let's verify this:
You should notice that the inventory item is returned with a status code of 200, and that the quantityOnHand amount is set to 8 (10 - 2). Also take note of the version number, which was updated from 2 to 3.
This application was developed with concurrency in mind, and follows the last-write-wins approach on a field-level. That stated, this api allows updates to entities on the individual field-level for the majority of the entities.
Let's take a look by querying by product id:
Now let's update the name field for the product:
In this example, having specified only the name value results in only updating the name, leaving the other values in tact without clobbering them.
The single field update approach allows users to modify the same entity at the same time without clobbering each others changes by performing patch updates. Arguably, full-entity replacement could have been implemented via PUT and single-field updates via PATCH, however, PUT was chosen in favor of simplicity for the sample application.
Sometimes a full-entity replacement is needed. This is particularly true of inventory items in the application, as the only field that matters for updating purposes is the quantityOnHand field. To prevent users and the application from mistakenly modifying the quantityOnHand field based on stale data, we introduce a version.
This version is required to update inventory, and is used in the update statement. The only way that the inventory can be updated is if the supplied version matches that found in the data store. If the supplied version does not match that found in the data store, a concurrency exception is thrown, and the user/application is forced to refresh and recalculate the quantityOnHand value based on the current value in the data store.
Let's simulate this scenario:
You should notice that the inventory item with a version number of 1 is returned with a status code of 200.
Let's update the quantity for this item to 20:
Notice that a PUT request was made with the content type set as application/json and resulted with a status code of 200. Also notice that the version was incremented from 1 to 2.
Without changing the version number, we'll now change the quantity to 13 and attempt to update the inventory item again:
Notice that a PUT request was made with the content type set as application/json. However, this time we received a 409 status code and a concurrency error, stating that the item has been changed.
To remedy this situation, let's update the version number to 2 and try again:
Here, we were successfully able to update the inventory item. Take notice of the version number that has been updated from 2 to 3.
In a high volume order entry system, using a version to prevent concurrency errors is probably not very realistic as you might find that the inventory for a product might need to be changed many times per second on every minute of every hour. In this case, you'd probably want to rely on a queue for concurrency or other common concurrent system design patterns.
However, the version strategy was employed to serve as one example of many business problems handled in a middle-tier application layer consuming peasy-js.