Understanding the initial example app
Imagine you woke up one morning and decided that you were going to take down the mighty Amazon.com. They've spread themselves too thin in trying to sell anything and everything the world has to offer. You see an opportunity back in their original space of online book selling and have started a company to challenge them in that area.
Over the past few months, you and your team of engineers have built out a simple Minimum Viable Product (MVP) reactive bookstore application build on top of the Akka 2.3.x series. As this is an MVP, it's pretty basic, but it has served its purpose of getting something to the market quickly to establish a user base and get good feedback to iterate on. The current application covers the following subdomains within the overall domain of a bookstore application:
- User management
- Book (inventory) management
- Credit card processing
- Sales order processing
The code that represents the initial application can be found in the initial-example-app
folder within the code distribution for this book. The application is an sbt multi-project build with the following individual projects:
- common: Common utilities and a shared domain model
- user-services: User management related services
- book-services: Book management related services
- credit-services: Credit card processing services
- sales-services: Sales order processing services
- server: A single project that aggregates the individual service projects and contains a main method to launch the server
If you were to look at the different projects from a dependency view, they would look like this:
The individual services subprojects were set up to avoid any direct code dependencies to each other. If services in different modules need to communicate with each other, then they use the shared domain model (entities and messages) from the common project as the protocol. The initial intention was to allow each module to eventually be built and deployed independent of each other, even though currently it's built together and deployed as a monolith.
Each service module is made up of HTTP endpoint classes, services, and, in some cases, Data Access Objects (DAOs). The endpoint classes are built on top of the Unfiltered library and allow inbound, REST-oriented HTTP requests to be serviced asynchronously, using Netty under the hood. The service business logic is modeled using the actor model and implemented with Akka actors that are called from the endpoints. Service actors that need to talk to the relational Postgres db do so via DAOs that reside within the same .scala
files as the services that use them. The DAOs use Lightbend's Slick library with the Plain SQL approach to talk to Postgres.
The way the application is currently structured, an inbound request that talks to the db would be handled as follows:
- The HTTP request comes in and is handled by Netty's NIO channel handling code on top of its own thread pool.
- Netty passes the code off to the Unfiltered framework for handling, still using Netty's thread pool.
- Unfiltered looks up a service actor via actor selection and uses the ask pattern to send it a message, returning a Future that will hold the result of the service call.
- The service actor that receives the message is running on the actor system's main Fork/Join thread pool.
- The actor talks to the Postgres db via the Slick DAO. The SQL itself runs within Slick's
AsycExecutor
system, on top of another separate thread pool. - The actor sends a response back to the sender (the Future from the endpoint) using the pipe pattern.
- The Future in the endpoint, which runs on the actor system's dispatcher, is completed, which results in a response being communicated through Unfiltered and Netty and then back into the wire.
As you can see from the preceding steps, there are a few different thread pools involved in the servicing of the request, but it's done completely asynchronously. The only real blocking done in this flow is the JDBC calls done via Slick (sadly, JDBC has yet to incorporate async calls into the API). Thankfully though, those blocking calls are isolated behind Slick's own thread pool.
If you've played with Akka long enough, you know it's taboo to block in the actor system's main Fork/Join pool. Akka is built around the concept of using very few threads to do a lot of work. If you start blocking the dispatcher threads themselves, then your actors can suffer from thread starvation and fall behind in the processing of their mailboxes. This will lead to higher latency in call times, upsetting end users, and nobody wins when that happens. We avoided such problems with this app, so we can pat ourselves on the back for that.
You should take a little time in going through the example code to understand how everything is wired together. Understanding this example app is critical as this serves as the foundation for our progressive refactoring. Spending a little time upfront getting familiar with things will help as different sections are discussed in the upcoming chapters.
The app itself is not perfect. It has intentional shortcomings to give us something to refactor. It was certainly beyond the scope of this book to have me build out a fully functioning, coherent, and production-ready storefront application. The code is just a medium in which to communicate some of the flawed ways in which an Akka reactive application could be put together. It was purposely built as a lead-in to discover some of the newer features in the Akka toolkit as a way to solve some common shortcomings. View it as such, with an open mind, and you will have already taken the first step in our refactoring journey.
Tip
Detailed steps to download the code bundle are mentioned in the Preface of this book. Please have a look. The code bundle for the book is also hosted on GitHub at https://github.com/PacktPublishing/Mastering-Akka. We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!