Now that you have an understanding of the initial code, we can build it and then get it up and running. It's assumed that you already have Scala and sbt installed. Assuming you have those two initial requirements installed, we can get started on getting the example app functional.
Throughout this book, we will be using Docker to handle setting up any additional applications (such as Postgres) and for running the bookstore application itself (within a container). For those unfamiliar with Docker, it is a containerization platform that will let you package and run your applications, ensuring that they run and behave the same no matter what the environment is that they are running on. This means that when you are testing things locally, on your Mac or Windows computer, the components will run and behave the same as when they eventually get deployed to whatever production environment you run (say some Linux distribution on Amazon's Elastic Compute Cloud). You package up all of the application components and their dependencies into a neat little container that can then be run anywhere Docker itself is running.
The decision to use Docker here should make set up simpler (as Docker will handle the majority of it). Also, you won't clutter up your computer with these applications as they will only run as Docker containers instead of being directly installed. When it comes to your Docker installation, you have two possible options:
- Install Docker Toolbox, which will install the docker engine, docker-machine and docker-compose, which are necessary for running the bookstore application.
- Install one of the native Docker apps (Docker for Windows or Docker for Mac), both of which will also work for running the bookstore application.
The biggest difference between these two options will be what local host address Docker uses when binding applications to ports. When using Docker Toolbox, docker-machine is used, which will by default bind applications to the local address of 192.168.99.100
. When using one of the native Docker apps, the loopback address of 127.0.0.1
(localhost) will be used instead.
If you already have Docker installed, then you can use that pre-existing installation. If you don't have Docker installed, and you are on a Mac, then please read through the link from below to help you decide between Docker for Mac and Docker Toolbox: https://docs.docker.com/docker-for-mac/docker-toolbox/.
For Windows users, you should check out the following link, reading through the section titled What to know before you install, to see if your computer can support the requirements of Docker for Windows. If so, then go ahead and install that flavor. If not, then callback to using Docker Toolbox: https://docs.docker.com/docker-for-windows/.
Adding the boot2docker hosts entry
Because we gave you a choice in which Docker flavor to run, and because each different flavor will bind to different local addresses, we need a consistent way to refer to the host address that is being used by Docker. The easiest way to do this is to add an entry to your hosts file, setting up an alias for a host called boot2docker
. We can then use that alias going forward to when referring to the local Docker bind address, both in the scripts provided in the code content for this book and in any examples in the book content.
The entry we need to add to this file will be the same regardless of if you are on Windows or a Mac. This is the format of the entry that you will need to add to that file:
<docker_ip> boot2docker
You will need to replace the <docker_ip>
portion of that line with whatever local host your Docker install is using. So for example, if you installed the native Docker app, then the line would look like this:
127.0.0.1 boot2docker
And if you installed Docker Toolkit and are thus using docker-machine, then the line would look like this:
192.168.99.100 boot2docker
The location of that file will be different depending on if you are running Windows or are on a Mac. If you are on Windows, then the file an be found at the following location: C:\Windows\System32\Drivers\etc\hosts
.
If you are running in a Mac, then the file can be found here: /etc/hosts
.
Starting up the example application
Now that the database is up and running, we can get the Scala code built and then packaged into a Docker container (along with Java8 and Postgres, via docker-compose) so we can run and play with it locally. First, make sure that you have Docker up and running locally. If you are running one of the native Docker apps, then look for the whale in your system tray. If it's not there, then go and start it up and make sure it shows there before continuing. If you are running Docker Toolbox, then fire up the Docker Quickstart Terminal, which will start up a local docker-machine session within a terminal window with a whale as ASCII art at the top of it. Stay in that window for the remainder of the rest of the following commands as that's the only window where you can run Docker-related commands.
From a terminal window within the root of the initial-example-app
folder run the following command to get the app all packaged up into a Docker container:
docker-build.sh
This script will instruct sbt to build and package the application. The script will then build a docker image, tag it and store it in the local docker repository. This script could take a while to run initially, as it will need to download a bunch of Docker-related dependencies, so be patient. Once that completes, you can then run the following command in that same terminal window:
launch.sh
This command will also take a while initially as it pulls down all of the components of our container, including Postgres. Once this command completes, you will have the bookstore initial example application container up and running locally, which you can verify by running the following command:
docker ps
That will print out a process list for the containers running under Docker. You should see two rows in that list, one for the bookstore and one for Postgres. If you want to log into Postgres via the psql client, to maybe look at the db before and after interacting with the app, then you can do so by executing the following command:
docker run -it --rm --network initialexampleapp_default postgres psql -h postgres -U docker
When prompted for the password, enter docker
. Once in the database, you can switch to the schema used by the example app by running the following command from within psql:
\c akkaexampleapp
From there, you can interact with any of the tables described in the ERD diagram shown earlier.
Tip
If you want to stop a Docker container, use the docker stop
command, supplying the name of the container you want to stop. Then, use the docker rm
command to remove the stopped container or docker restart
if you want to start it up again.
Interacting with the example application endpoints
Once the app is up and running, we can start interacting with its REST-like API in an effort to see what it can do. The interactions will be broken down by subdomain within the app (represented by the -services projects), detailing the capabilities of the endpoint(s) within that subdomain. We will use the httpie
utility to execute our HTTP requests. Here are the installation instructions for each platform.
Installing httpie on Mac OS X
You can install httpie
on your Mac via homebrew. The command to install is as follows:
$ brew install httpie
Interacting with the user endpoint
The first thing we can do when playing with the app's endpoints is to create a new BookstoreUser
entity that will be stored in the StoreUser
Postgres table. If you cd
into the json
folder under the initial-example-app
root, there will be a user.json
file that contains the following json object:
{
"firstName": "Chris",
"lastName": "Baxter",
"email": "[email protected]"
}
In order to create a user with these fields, you can execute the following httpie
command when in the json
folder:
http -v POST boot2docker:8080/api/user < user.json
Here, you can see that we are making use of the hosts file alias we created in section Adding the boot2docker hosts entry. This let us make HTTP calls to the bookstore app container that is running in Docker regardless of what local address it is bound to.
The -v
option supplied in that command will allow you to see the entire request that was sent (headers, body, path, and params), which can be helpful if it becomes necessary to debug issues. We won't supply this param on the remainder of the example requests, but you can if you feel you want to see the full request and response. The <
symbol implies that we want to send the contents of the user.json
file as the POST
body. The resulting user.json
will look like the following:
{
"meta": {
"statusCode": 200
},
"response": {
"createTs": "2016-04-13T00:00:00.000Z",
"deleted:":false,
"email": "[email protected]",
"firstName": "Chris",
"id": 1,
"lastName": "Baxter",
"modifyTs": "2016-04-13T00:00:00.000Z"
}
}
This response structure is going to be the standard for endpoint responses. The "meta"
section mirrors the HTTP status code and can optionally contain error information if the request was not successful. The "response"
section will be there if the request was successful and can contain either a single object as JSON or an array of objects. Notice that the ID of the new user is also returned in case you want to look that user up later.
You should add a few more JSON files of your own to that directory representing more users to create and run the same command referenced earlier (albeit with a different file name) to create the additional users. If you happen to try and create a user with the same e-mail as an existing user, you will get an error.
If you want to view a user that you have created, as long as you know the ID, you can run the following command to do so, using user ID 1
as the example:
http boot2docker:8080/api/user/1
Notice on this request that we don't include an explicit HTTP request verb. That's because httpie
assumes a GET
request if you do not include a verb.
You can also look up a user by e-mail address with the following request:
http boot2docker:8080/api/user [email protected]
The httpie
client uses the param==value convention
to supply query params for requests. In this example, the query string would be: ?email=chris%40masteringakka.com
.
You can make changes to a user's basic info (firstName
, lastName
, email
) by executing the following command:
http PUT boot2docker:8080/api/user/1 < user-edit.json
The included user-edit.json
file contains a set of request json to change the initially created user's e-mail address. As with the creation, if you pick an e-mail here that is already in use by another user, you will get an error.
If at any time you decide you want to delete a user, you can do so with the following request, using user ID 1
as the example:
http DELETE boot2docker:8080/api/user/1
This will perform a soft delete against the database, so the record will still exist but it won't come back on lookups anymore.
Interacting with the Book endpoint
The Book endpoint is for taking actions against the Book
entity. Books are what are added to sales orders, so in order to test sales orders, we will need to create a few books first. To create a Book, you can run the following command:
http POST boot2docker:8080/api/book < book.json
As with the BookstoreUser
entity, you should create a few more book.json
files of your own and run the command to create those books too. Once you are satisfied, you can view a book that you have created by running the following command (using book ID 1
as the example):
http boot2docker:8080/api/book/1
Books support the concept of tags. These tags represent categories that the book is associated with. For example, the 20000 Leagues Under the Sea book
that is represented in the book.json
file is initially tagged as fiction
and sci-fi
. The Book endpoint allows you to add additional tags to the book, and it also allows you to remove a tag. Adding the tag ocean
to book ID 1
can be done with the following command:
http PUT boot2docker:8080/api/book/1/tag/ocean
If you decide that you would like to remove that tag, then you can execute the following command to do so:
http DELETE boot2docker:8080/api/book/1/tag/ocean
If you want to look up books that match a set of input tags, you can run the following command:
http boot2docker:8080/api/book tag==fiction
This endpoint request supports supplying the tag param multiple times. The query on the backend uses an AND condition across all of the tags supplied, so if you supply multiple tags, then the books that match must have each of the tags supplied. An example of supplying multiple tags would be as follows:
http boot2docker:8080/api/book tag==fiction tag==scifi
You can also look up a book by author by executing a request like this:
http boot2docker:8080/api/book author==Verne
This request supports partial matching on the author, so you don't have to supply the complete author name to get matches.
The last concept that we can test out related to book management is allocating inventory to the book once it's been created. Books get created initially with a 0
inventory amount. If a book does not have any available inventory, it can not be included on any sale orders. Since not being able to sell books would be bad for business, we need the ability to allocate available inventory for a book in the system.
To indicate that we have five copies of book ID 1
in stock, the request would be as follows:
http PUT boot2docker:8080/api/book/1/inventory/5
Like the BookstoreUser
entity, you can also perform a soft delete for a book. You can do so by executing the following request, using book ID 1
as the example:
http DELETE boot2docker:8080/api/book/1
Now that we have users and books created and we have a book with inventory, we can move on to pushing sales orders through the system.
Interacting with the Order endpoint
If you want to run a profitable business, at some point, you need to start taking in money. For our bookstore app, this is done by accepting sales orders for the books that we are keeping in inventory. All of the playing with the user and book-related endpoints was done so that we could create SalesOrder
entities in the system. A SalesOrder
is tied to a BookstoreUser
(by user ID) and has 1-n line items, each for a book (by book ID).
The request to create a SalesOrder
also contains the credit card info so that we can first charge that card, which is where our money will come from. In the OrderManager
code, before moving forward with creating the order, we first call over to the CreditCardTransactionHandler
service to charge the card and keep a persistent record of the transaction. As we don't actually own the logic for charging the card ourselves, we call out over HTTP to a fake third-party service (implemented in PretentCreditCardService
in the server project) to simulate this interaction.
Within the JSON directory, there is an order.json
file that has the valid JSON in it for creating a new SalesOrder
within the system. This file assumes that we have already created a BookstoreUser
with ID of 1
and a book with an ID of 1
, and we have added inventory to that book, which we did in the previous two sections. To create the new order, execute the following command:
http POST :8080/api/order < order.json
As long as the userId
supplied and bookId
supplied exist and that Book
has inventory available, then the order should be successfully created. Each SalesOrderLineItem
will draw down inventory (atomically) for the book that item is for in the amount tied to the quantity input for that line item. If you run that same command enough times, you should eventually exhaust all of the available inventory on that book, and you should start getting errors on the creation. This can be fixed by adding more inventory back on the book.
If you want to view a previously created SalesOrder
, as long as you know the ID (which is returned in the create response JSON), then you can make the following request (using order ID 1
as the example):
http boot2docker:8080/api/order/1
If you want to lookup all of the orders for a particular user ID, then you can execute the following request:
http boot2docker:8080/api/order userId==1
The Order
endpoint also supports looking up SalesOrders
that contains a line item for a particular book by its ID value. Using book ID 1
as the example, that request looks like this:
http boot2docker:8080/api/order bookId==1
Lastly, you can also look up SalesOrders
that have line items for books with a particular tag. That kind of request, using fiction
as the tag, would be as follows:
http boot2docker:8080/api/order bookTag==fiction
Unlike the search books by tag functionality, this request only supports supplying a single bookTag
param.