Book Image

Mastering Akka

By : Christian Baxter
Book Image

Mastering Akka

By: Christian Baxter

Overview of this book

For a programmer, writing multi-threaded applications is critical as it is important to break large tasks into smaller ones and run them simultaneously. Akka is a distributed computing toolkit that uses the abstraction of the Actor model, enabling developers to build correct, concurrent, and distributed applications using Java and Scala with ease. The book begins with a quick introduction that simplifies concurrent programming with actors. We then proceed to master all aspects of domain-driven design. We’ll teach you how to scale out with Akka Remoting/Clustering. Finally, we introduce Conductr as a means to deploy to and manage microservices across a cluster.
Table of Contents (17 chapters)
Mastering Akka
Credits
About the Author
Acknowledgments
About the Reviewer
www.PacktPub.com
Preface

Working with the example application


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.

Setting up Docker

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.

Understanding the bookstore Postgres schema

The initial example app uses Postgres for its persistence needs. It's a good choice for a relational database as it's lightweight and fast (for a relational database at least). It also has fantastic support for JSON fields, so much so that people have been using it for a document store too.

We will use Docker to handle setting up Postgres locally for us, so no need to go out and install it yourself. There are also setup scripts provided as part of the code for this chapter that will handle setting up the schema and database tables that the bookstore application needs. I've included an ERD diagram of that schema below for reference as I feel it's important in understanding the table relationships between the entities for the initial version of the bookstore app.

If you are interested in the script that was used to create the tables from this diagram, then you can find it in the sql directory under the intial-example-app root folder from the code distribution, in a file called example-app.sql.

Running bash scripts on Windows

If you are using a Mac, you can skip reading this section. It only pertains to running the .sh scripts used to start up the app on Windows.

As part of the code content for each chapter, there are some bash scripts that handle building and running the bookstore application. As bash is not native to the Windows operating system, you will have to decide how you want to build and start the bookstore application, choosing from one of the the following possibilities:

  1. If you are using Git for Windows, then you have Git BASH installed locally and you should be able to use that tool to run these fairly simple scripts.

  2. If you are on Windows 10, then you can use the new Windows Subsystem for Linux and install a bash shell. Check out this link for instructions: http://www.howtogeek.com/249966/how-to-install-and-use-the-linux-bash-shell-on-windows-10.

  3. You can install cygwin.

  4. As a last resort, if none of the above options work then you can look at the .sh files referenced and just run the commands (which are a mix of sbt and Docker commands) individually yourself. There's not a lot of them per file, so this is not a bad last resort.

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

Installing httpie on Windows

The installation on Windows is going to be a bit more complicated as you will need Python, curl, and pip. The full instructions are too long to include directly in this book and can be found at: http://jaspreetchahal.org/setting-up-httpie-and-curl-on-windows-environment/.

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.