Chapter 2. Reactive application design

published book

This chapter covers

  • Designing reactive systems with Akka.NET design patterns
  • Exploring application design concepts
  • Reactive design trade-offs

In chapter 1, you saw many reasons why you might want to design an application using the principles laid out in the Reactive Manifesto—reasons primarily driven by the changing face of technology over the past several decades. Whereas computers were once rarities, used primarily by researchers or organizations with sufficient funds, they have since been transformed into the commonplace, with the vast majority of households now having at least one computer, smartphone, or tablet. This number is set to grow with the introduction of the Internet of Things, which is transforming many of the mundane tasks we perform on a daily basis by harnessing the power of an interconnected network of smart devices. This transformation is likely to replicate many of the changes we’ve already seen in industry over the past few decades, as companies adapt to provide their services in the internet age.

For example, in the world of e-commerce, more and more retailers are providing products and services through online stores. Online shopping has grown, with more consumers opting to use the internet for the majority of their shopping. This has led to a situation in which online retailers are in direct competition with each other. Although this level of competition benefits consumers who can access readily available products with competitive pricing, it puts a huge amount of pressure on retailers to ensure that their online user experience (UX) is near-perfect; otherwise, customers can easily transfer their business to competitors. Research indicates that when consumers encounter errors and excessive page-loading times, they move to competitors with friendlier websites.

join today to enjoy all our content. all the time.
 

2.1. Basic reactive system design

Given that the overall aim of the Reactive Manifesto is to provide a responsive experience to the end user, it’s apparent that the principles of reactive application design could have significant benefits for the world of e-commerce. You saw the four tenets of a reactive application in chapter 1: it’s responsive, fault tolerant, elastically scalable, and message-driven. Of these four, three are directly relevant to an e-commerce website’s UX.

If you want to increase the likelihood that customers remain on the website, then you need to ensure that pages load quickly and other actions are performed promptly. If a customer wants to spend money on an e-commerce website, that website should provide a fluid UX; otherwise, the website runs the risk of alienating the user and sending them to a competitor.

An e-commerce website should also be elastically scalable, especially during sharp spikes in numbers of visitors during peak periods of traffic. When you analyze common shopping habits, you see many users accessing websites during a given period, driven by gift-giving holidays such as Christmas, key shopping-discount events such as Black Friday, and so on. In these cases, you want your service to handle the most requests possible. If you have a user spike of an order of magnitude more than normal, you need to accommodate it; otherwise, you run the risk of customers flocking en masse to competing websites.

Similarly, when designing for failure, you want to ensure that even if a non-essential component of an e-commerce website fails, it can still accept the user’s payment. For example, if a customer navigates to the checkout page, they shouldn’t be faced with errors caused by non-essential features, such as recommendation services or advertising features intended to sell additional products or services. If such components fail, the customer should still be able to complete the purchase.

This combination of requirements suggests that designing an e-commerce application using the principles specified in the Reactive Manifesto may provide substantial benefits. But effectively designing an application using the concepts of the Manifesto can mean significant changes, in terms of both the developer’s thought process and the application architecture. In order to better understand reactive application design, in this chapter we’ll look at how to design a traditionally CRUD-based application using actors with Akka.NET and the principles from the Manifesto. We’ll consider some of the challenges and design decisions you’re likely to encounter as you design such applications, as well as how you can effectively design an application using features made available by Akka.NET.

Get Reactive Applications with Akka .NET
add to cart

2.2. Reactive e-commerce application with actors

As we’ve already considered, the way we use computers has rapidly changed over the past several decades, and they’re now seen as a commodity that exists in the majority of households, along with internet connection. But users have also become more demanding, requiring more features to enhance their shopping experience. These features include recommendation engines that suggest alternative products, trend calculations to predict which products are due to be the most popular, and integrations with external third-party services that provide additional features and benefits. And e-commerce businesses are interested in gaining insights into how customers are shopping and better positioning themselves to respond to customer demands. This produces high demands on the scalability of both the traffic-handling and application architectures.

Let’s consider how you can effectively design a reactive system with an actor-based approach, representing the system entities with actors, in the familiar context of an e-commerce application. If you haven’t had the experience of writing e-commerce websites, you’ve likely used one to make purchases. You saw in the previous section why the world of e-commerce is a strong candidate for reactive application design, where the goal is to create applications that are responsive.

Given that an average e-commerce website is quite large, we won’t examine every component within the system; instead, we’ll focus on one key aspect of the application: the final purchasing experience. This is the part of the website the user will navigate to once they’ve finished browsing and are ready to purchase their selected items. This component will have a number of requirements such as providing a shopping cart where users can store items as they browse the site, a checkout where users enter their shipping address, and a payment gateway where users enter payment card details.

Chapter 1 addressed what an actor is and how actors allow you to design applications with concurrency handled transparently for you. It also addressed the requirements of a reactive application and the principles you should adhere to for success. In the rest of this chapter, we’ll consider how to incorporate these ways of thinking into a real-life application. We’ll also consider how these components would fit into the context of an application designed using Akka.NET, by linking these design ideas to the functionality and features provided as core components of the Akka.NET distribution.

2.2.1. A reactive shopping cart

The first component that a potential customer is likely to encounter is the shopping cart, analogous to the shopping cart in a physical store, which they can use when browsing to collect the items they intend to purchase. Thousands of users may browse the site at once, so your application must support the simultaneous use of thousands of shopping carts. To design this, you’ll create a shopping cart, which is nothing more than a list of items and the quantity of items. Each shopping cart is accessed through a unique identifier, which is stored in the user’s session. One of the core attributes of actors is the ability to store state, which varies per type of actor. In the case of the shopping cart, you could store a dictionary of the user’s session identifier, along with a list of items and quantities. This is how you might model the component if you were to use a database. But actors serialize all incoming messages; only one message is processed at any one time, in order. This means that if lots of users are trying to access their shopping carts at the same time, then they’ll have to sit through long queues. This defeats the aim of providing a responsive UX.

But because actors can perform work in parallel and are an incredibly cheap abstraction in terms of memory and computation, you can create many actors to work concurrently. For the shopping cart example, you can create a single actor per shopping cart, responsible for storing a list of items and associated quantities, and the actors are addressed by the shopping cart identifier. You can see an example of this in figure 2.1; each actor is effectively a shopping cart, which is similar to how this might be modeled in the physical world, where you have one physical entity per customer.

Figure 2.1. Creating one actor per shopping cart allows each shopping cart to be accessed independently of the others. This means there’s no contention for resources between User 1 and User 2.

This leads us to the first design pattern you’re likely to encounter in an actor-based reactive system. Where possible, you should partition work into actors based on concurrency boundaries; in the case of a shopping cart, a concurrency boundary is an individual shopping cart. Designing systems like this allows for the greatest throughput, ensuring your application remains responsive. It also allows for easier scalability, because you can fine-tune the deployment of the actor to better handle any increased throughput it might experience. The next chapter will look at how to effectively design actors in the context of Akka.NET; you’ll also see how to handle an increased throughput by scaling actors out across multiple servers in chapter 12.

2.2.2. Changing states of actors

While a customer is browsing the store, they’ll hopefully add items to their cart. There are a number of states a shopping cart can exist in. For example, the two key states are browsing, where the customer continues to browse the store, and purchase-completion, where the user is completing a purchase. When a user is browsing, they should be able to add more items to their cart; but once they start the process of completing a purchase, you typically don’t allow the cart to be modified. This is for a number of reasons, such as the need to reserve items while the purchase is being completed, and computing the cost of the shopping cart contents. You therefore want to prevent new items from being added to the cart while it’s in the purchase-completion state.

You saw in the previous section how the shopping cart can be represented as an actor and as part of the actor model. Actors can change their state on demand to respond differently to subsequent messages. Given how this is such a fundamental component of designing actors within actor systems, Akka.NET provides the functionality of switching between states to invoke different behavior and simplify the process of responding to messages. In figure 2.2, you can see how the shopping cart actor can switch between multiple different states.

Figure 2.2. The use of state machines with actors in Akka.NET allows you to simplify the process of developing actors with complex internal logic.

This shows another pattern you’ll typically encounter with applications written using Akka.NET, whereby you switch between multiple different states as required. Specifying multiple different states allows you to focus on one individual state and the messages it’s able to respond to, without needing to consider alternative scenarios simultaneously. This lets you easily understand the logic embedded within actors at a later stage. You’ll see how this can be applied to actors in chapter 4, when we consider the importance of state machines in Akka.NET applications and how you can represent them.

2.2.3. Making the purchase

Once a customer has decided on everything they want to purchase, they proceed to the payment stage where you process their choice of payment, typically a credit or debit card. To simplify the development of complex systems involving credit card details, a solution is to integrate with an external payment gateway. You send a request to a third-party service with a token representing the user’s credit card details and the total value of the purchase. Given that you’re integrating with external services, there’s a high probability of failures happening, so you need the system to allow for failure. As you saw in chapter 1, for a system to be truly responsive, it needs to continue to work even in situations where individual components might fail. Due to the high potential for failure when designing complex systems, the Akka.NET mindset is to embrace failure and provide the ability to recover from it quickly.

In Akka.NET, an actor can supervise other actors that are spawned as its children. This design allows for a number of different operations when an actor is discovered to have failed; for example, you may want to restart the actor and attempt to retry whatever work it had been performing, or you might want to stop the actor’s operations altogether. By pushing as much work as possible down to child actors, you can isolate failures and allow for a broader approach than retrying the operation, which might be sufficient to resolve the issue. In figure 2.3, an actor has to perform two tasks: upload some data to a web API responsible for processing the payment on a user’s credit card, and then send a message to another actor upon completion, informing it that the purchase has been completed. The actor pushes the potentially dangerous work, in this case the web API call, down to a child actor, which is responsible for performing the operation.

Figure 2.3. Creating child actors allows you to perform dangerous work by isolating it from the rest of the application.

This is another common design pattern in Akka.NET. Because actors are completely isolated from other actors, should one actor encounter difficulties, it won’t directly endanger other actors executing as part of an application. You’ll see the concept of passing work down to children in chapter 3, when we look at spawning new actors to perform work. You’ll then learn how to use actor-based supervision to create fault--tolerant applications in chapter 6.

2.2.4. Data transfer between services

Underpinning the methodology behind designing applications with Akka.NET is the actor model, a concurrency model that solves the problems of coordination and synchronization of operations across multiple threads by preferring isolation. In the actor model, every actor is independent of every other actor, and they communicate by sending messages to each other, in much the same way as humans communicate with each other. This means that when you design applications using Akka.NET, you need to think about how actors talk to each other. Let’s consider the example of the payment-gateway actor you saw in the previous section. The payment-gateway actor is solely responsible for interacting with the external payment service, but many components make up the payment flow of an e-commerce website. For example, after a customer has made a purchase, what should the next step be? The logical next step is to fulfill the order speedily. If your e-commerce application is selling digital media, then you may have another actor responsible for assigning privileges to the user’s account, which allows them to view the content they’ve just purchased. Alternatively, if you’re selling physical products, then you need to retrieve the products from the warehouse and prepare printed invoices and shipping labels, which will be sent with the purchase.

Because you want to prioritize fine-grained operations within actors, for reasons of scalability and fault tolerance (which I’ll address in depth later), you’ll have multiple actors responsible for selected operations within the checkout flow. These independent actors need to be able to share results. Each actor has an address associated with it, by means of which other actors can communicate with it; this allows them to pass messages between fixed addresses for actors, rather than having direct references to actors themselves. For example, after the customer has completed their payment and you’ve verified its authenticity, you pass the message on to the component responsible for fulfilling the order. The payment-gateway actor automatically sends a message to the addresses for multiple other actors in response to payment completion, as illustrated in figure 2.4.

Figure 2.4. Sending messages between actors is a fundamental part of reactive application design.

Messaging between actors is a fundamental aspect of reactive applications designed with Akka.NET; it plays a crucial role in the Reactive Manifesto by providing the foundation on which you create fault tolerance and elastic scalability. As such, you’ll see messaging appear in every chapter throughout the rest of the book; but I’ll introduce messages in more detail in chapter 3, when you create your first actors.

2.2.5. Scaling work with routers

In an e-commerce application, often a single actor is responsible for a certain operation: for example, an actor that performs a search operation for products. But due to the concurrency guarantee offered by Akka.NET, which specifies that only one message should be processed at once, there may be a large queue of messages waiting to be processed by that actor. For the task of product search, you maintain an index of words commonly used so an actor can look up search terms related to certain products. A search actor stores the word index as its internal state and then receives a message to search it for products containing the search term. But if several hundred users are searching for products simultaneously, they may encounter queues as the actor performs each search individually. Although this actor is stateful, in that it stores the product index internally, it is stateless between requests, which ensures that you can easily parallelize the search operation. Figure 2.5 shows that you no longer have a single actor processing each message, but instead have multiple independent actors, which are treated as a single target.

Figure 2.5. You can parallelize stateless operations by using multiple actors behind a routing proxy.

The introduction of a router allows you to abstract away multiple individual actor instances and treat them all as a single actor, given that you direct messages to the router rather than to each actor sitting behind the router. The use of routers as a means of parallelizing trivial workloads is a common pattern, and you’ll see how to use one in chapter 6, when we look at using routers to build scalable applications that respond to an increased number of messages by forwarding messages to other targets.

2.2.6. Wrapping up

These are just some of the most basic design patterns you’re likely to encounter when designing applications using the fundamentals of the Reactive Manifesto. Figure 2.6 shows a broader picture of the checkout system as a whole, which features the individual components and how they communicate with each other. You can see the customer’s shopping cart, where they add items as they browse through the store. After a while, the user is finished shopping and wants to complete their purchase. The shopping cart then transfers into the purchase-completion state, which passes customer payment details to an external payment gateway. When a response comes back from the payment gateway, the application can complete the purchase.

Figure 2.6. A simple e-commerce checkout flow has a number of interconnected components that can be built from the design patterns you’ve seen so far.

Here, we’ve considered only some of the basic design patterns; but even simple Akka.NET functionality forces you to consider many of the principles that make up a reactive application. The use of message passing directs you down a systems path that simplifies the process of scalability and fault tolerance; actors force you to think about concurrency boundaries and which tasks can be performed simultaneously; and supervision makes you think about what will happen in the event of the failure of other systems, whether internal or external. Throughout the rest of the book, we’ll progressively consider deeper design patterns that help simplify the process of developing larger and more complex systems.

Sign in for more free preview time

2.3. Building on reactive foundations

Although these few components allow you to build an e-commerce system with a wide array of features, many potential enhancements are available for you to take it further. On top of the basic components provided by Akka.NET, there are a number of additional components for building more-advanced functionality. In this section, we’ll consider how you can extend your application using these features, and easily build larger and more complex systems that continue to follow the principles of the Manifesto.

2.3.1. Publishing the e-commerce application to the world

As it currently stands, the e-commerce application you designed exists solely as an Akka.NET application. A typical system is consumed by a number of different clients including web browsers and mobile apps; this means that you need to add some degree of integration with existing systems. With Akka.NET, you can expose an actor system onto a network using the Akka.Remote functionality, which lets other clients consume your e-commerce application.

Let’s consider one of the most typical ways of consuming an e-commerce application: using a website in a web browser. In this case, you need to serve the contents of the application through a secure HTTP interface. Figure 2.7 shows the scenario: a web API that communicates with the Akka.NET application. By using an HTTP-based web API, clients and web browsers can consume your application.

Figure 2.7. Akka.NET applications need to be accessible to a number of clients. This can be achieved through the use of a web proxy in front of the application.

To achieve this level of integration, Akka.NET provides the Akka.Remote functionality, which lets you expose an Akka.NET application over a network connection to other clients. This allows a web proxy to communicate with the actors defined in your web application, such as the shopping cart or payment-gateway actors. You’ll see in chapter 8 how to use Akka.Remote to work with existing web API projects.

2.3.2. Storing state within actors

The application you’ve created has no persistence of state and, instead, stores everything in memory. You’ll usually want to persist changing data so that users can modify it. Consider the shopping cart. In an e-commerce store, you want to ensure that there’s minimal friction in the buying process. Losing the user’s shopping cart is a potential loss of revenue for the business. To avoid data loss, it’s important to persist data to a persistent data store such as a database or a filesystem. In chapter 11, you’ll see how to use a database to back up the data stored in your actors to create resilient applications with Akka.Persistence.

2.3.3. Scaling out across a cluster of machines

As e-commerce sites continue to grow in popularity, it’s likely that your website and e-commerce application will see an increase in traffic volume. This may become too much for a single server to handle, so you’ll need to use multiple servers. For this purpose, Akka.NET provides the Akka.Cluster extension, which allows you to treat a number of machines as a cluster. You can run your e-commerce application across all machines in the cluster and scale up the application beyond the limits of a single server. In chapter 12, you’ll see how Akka.Cluster lets you build elastically scalable services and systems that scale on demand across multiple machines, as dictated by load.

2.3.4. Continuing to react to environmental changes

The Reactive Manifesto states that an application should react to changes in its environment. In an Akka.NET application, this often means responding to a message from another actor, such as the way multiple actors communicated in the checkout flow. But sometimes changes need to trigger an immediate response; for example, in your e-commerce application, peripheral components may rely on knowing when a customer completes a purchase. Such components might include systems that autosuggest new products based on historical purchases, or internal systems that adjust pricing automatically based on the number of purchases within a given time period. In these cases, responding to events as soon as they occur lets you build more-reactive applications. Akka.NET provides publish-subscribe functionality, allowing decoupled components to register to receive any messages that are published onto an event bus. In chapter 12, you’ll see how the distributed publish/subscribe functionality can respond to changes in the cluster as soon as they happen.

2.3.5. Wrapping up

Akka.NET provides a rich ecosystem of additional functionality, much of which is beyond the scope of a simple checkout in an e-commerce application. Given increasingly demanding customers and a more competitive marketplace, it’s important to consider how some of this additional functionality can be applied to a simple shopping cart to create a richer experience for users, leading to increased spending on your e-commerce website. These more advanced features will be addressed later in the book, when we look at clustering across machines, persisting actor state to external data stores, and integrating an Akka.NET application with other applications, whether these are new or legacy applications.

Summary

In this chapter, you learned

  • Applications that follow the principles of the Reactive Manifesto are responsive, fault tolerant, elastically scalable, and message-driven.
  • Several core design patterns can be used to simplify reactive application development: the actor model, using state machines, and using routers.
  • When designing a system to benefit from Akka.NET, consider using Akka.Remote to expose your application to other clients, persisting data, scaling services, and incorporating publish/subscribe functionality.
sitemap
×

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage