10 Integrating Akka.NET

published book

This chapter covers

  • Designing custom protocols using Akka.NET to receive and send data
  • Embracing a fully reactive application stack by integrating with real-time connection mechanisms
  • Adding a web API frontend to allow web applications to communicate with an actor system

All of the examples we’ve seen in the book so far have contained the actors within the actor system and not exposed them to the world. As you build applications based on actors, you’ll probably need to be able to consume the data stored within them. When it comes to the technologies which are able to consume the data stored within the actor systems, you’ve got huge amounts of potential data sinks. The data from within these actor systems can end up being forwarded down to individual Internet of Things devices in order to enable command-and-control-style scenarios: you may be sending data down to mobile apps and games to automatically refresh the information on user’s devices, you may be sending the data down to video game consoles to provide multiscreen experiences to gamers, you may be sending data to other cloud services that are responsible for other utilities, or you may need to make the data available to websites. Ultimately, in all of these cases, you need to present the information stored within your actor system in the most appropriate manner for each of the individual services you have available.

We saw in chapter 8 when looking at the capabilities of Akka.Remote that you can expose your actor systems over the internet by allowing them to listen on a specific port and accept incoming communication from other actor systems. This certainly has some key benefits, most notably in its simplicity, but it leaves you in a difficult position. In chapter 8, we also saw the recommended practice when it comes to security, which is to only allow access to Akka.NET actor systems from behind a firewall, thus preventing you from exposing it to the whole internet. But there is another reason why you are unlikely to want to allow communication through actor systems. In all the cases we’ve seen where you want to integrate with an actor system, you’re unlikely to be in a position where you can run an Akka.NET actor system.

Let’s consider the example of an Internet of Things device. When you consider the usage of such a device, you’re typically looking at low-powered hardware that is designed to be as energy efficient as possible in order to save battery life for as long as possible. Akka.NET is not optimized for these low-powered devices as it needs the likes of a full CLR virtual machine in order to execute the .NET application. This is a lot of overhead needed to execute on something that’s low-powered. You also need to consider other issues as well, notably with the transport and integration with other third-party tooling. In the world of the Internet of Things, MQTT has become a standard for simple communication between devices. By using Akka.NET with its remoting capabilities, you’re potentially excluding your device from communicating with other manufacturers’ devices.

Figure 10.1 - An Akka.NET actor system may need to receive input from a number of different devices all using different technologies.

You also need to consider the supporting libraries available on the client machines. Although some machines may be more powerful and able to support a fully functioning virtual machine, you may be unable to run this sort of codebase due to security permissions on the client’s device. For example, an iOS mobile device won’t allow you to run a just-in-time compiler, and instead you have to run a full ahead-of-time compiler on the application. This means that you’re not able to run a full virtual machine within your application, and this means you need special tools to ensure the abilities of certain tooling.

Another consideration is in the languages used to access the actor system. Akka.NET actor systems are only able to communicate with other Akka.NET actor systems, which forces your client application to be written in C# or F#. There are many cases where this is not possible. One such example is in video game development, which for many involves the use of low-level languages such as C and C++ in order to make the most of the hardware and get the best performance possible. By using C or C++, these game developers would not be able to communicate with the backend systems.

Finally, you should ensure that the tools you’re using to perform interoperation are appropriate given historical and likely future tooling. An example of this is the use of HTTP in web browsers. Although we saw earlier in the book how much the backend of the World Wide Web is changing to move to a more reactive model, communication between the user’s web browser and the backend service has become fairly standardized, with communication being based on HTTP. As more and more people focus on using web applications as a means of delivering content to users, this means that you need to support access to the underlying data in a way that is both consumable and manageable.

All of these examples have left you in a situation where you need to be able to expose your actor systems in such a way that they’re consumable from the widest range of devices using the most appropriate protocols and technologies. As it stands, although you may have systems that are capable of generating responses in milliseconds and creating enjoyable experiences for your end users, they’re not able to access them. As such, another component of an actor system deployment you need to consider is how to design them so that they’re able to communicate with other systems, applications and devices.

In this chapter, we’ll see how to integrate a new Akka.NET application with a number of different protocols and technologies you’re likely to be using in a pre-existing .NET application. This allows you to put incredibly simple integrations in place that are capable of allowing access from as many different devices as possible. We’ll see how to create actor systems that can be accessed through HTTP using your preferred web framework; in this case you’ll be using ASP.NET, but you’ll be able to follow the same guidelines and use any of the alternatives, such as NancyFX, ServiceStack, Suave, or many others. We’ll also see how to create more reactive services that run on the web by integrating with web sockets through the use of SignalR. Finally, we’ll also see how to integrate low-level TCP socket connections into your application so that you can build protocols up yourself on top of the underlying socket connection.

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

10.1         Integrating with ASP.NET

Over the past twenty years, the World Wide Web has changed significantly. Originally intended as a means of sharing research materials between academics, it’s evolved into a tool that many people rely on as a means of communication, entertainment, and many other applications. As a result of that growth, web browsers have changed and grown in functionality to the extent that it’s now possible to create applications with a native feel that are delivered through the internet directly to the user’s web browser, all thanks to the power of HTML, CSS, and JavaScript.

Despite all the changes that users are able to see in the frontend of the application, all of the communication with the backend has essentially followed the same pattern of HTTP-based communication. Although the HTTP specification has seen some minor changes, it has for the most part stayed consistent. This level of consistency and standardization presents some clear benefits for those looking to integrate their application over a network. The majority of devices provide support for either a web browser or, at the very least, a set of tools for performing requests to an HTTP API. This level of availability has led to HTTP becoming the de facto transport of choice for many applications.

Within the .NET ecosystem, there is a wide variety of web servers designed for writing applications designed to communicate over HTTP with clients. In addition to the Microsoft supported ASP.NET web framework, other options have been built by the wider community, including the likes of NancyFX, ServiceStack, and Suave. All of them have their respective merits and all can be used with Akka.NET, but in this section, we’ll be looking at Microsoft’s ASP.NET framework due to its wide usage. The same techniques used in this section are also applicable to the other frameworks, and as such, the examples can be ported across.

Integration with ASP.NET builds upon one of the concepts we saw back in chapter 3, where we saw that you can send a message to an actor and then wait for a response by Ask-ing an actor in your actor system.

Assuming you have an application already set up[1] and configured to get the basic features required to build an ASP.NET based website, you can add all of the components required to use Akka.NET. As before, you need to add the references to the pre-requisite components of Akka.NET, for the most basic Akka.NET application, this simply relies on installing the Akka package from NuGet exactly as we did in chapter 3.

Before you can use Akka.NET, you first need an actor system to be set up and configured to be run within your application. We saw in chapter 3 that you should only have a single actor system deployed per application which contains all of your actors. In order to make an actor system available across your entire application and ensure that the actor system runs for the entire lifetime of your application, you can use one of two different approaches:

  • Global.asax – you can choose to initialize your actor system within the Global.asax file and make it accessible to all parts of the application with a static property. By using the Global.asax file, you can also register to certain events raised by the underlying ASP.NET framework or IIS host. For example, you can listen to events that notify that the application is due to be shut down or restarted. This then allows you to shut down the actor system and save the state of the actors within it in preparation.
  • Startup.cs – in addition to an IIS host for your application, you can also choose to use one of the OWIN compatible hosts, which allows you to change the underlying server quickly and easily. This removes the possibility of using the Global.asax file, which is tied to IIS. But as an alternative, you can store data in the OWIN environment, which is then accessible from anywhere in the application.

In this example, you’ll be using the Startup.cs option due to its cross-platform capabilities and simple usage. OWIN is a simple contract that .NET web servers can implement that then allows applications to be moved between different hosting technologies with minimal effort. For example, an application written using the OWIN middleware can be targeted at a self-hosted version for local development and then deployed onto an IIS instance when in a production deployment.

OWIN works as a pipeline in which each component has a subsequent operation that is executed, until it gets to the end of the chain, upon which a result is passed back along the chain. This means that if you want to use your actor system in later stages, you need to ensure that you’ve initialized it at the very start of the queue. In order to add an element to the OWIN pipeline, you call the Use method on the IAppBuilder provided in the Configuration method. Use takes a function as a parameter that simply takes the environment and the next element in the chain and returns a task. It’s in this function where you’ll create your actor system and store it in the environment. Having done this, you can access it again from any other step in the chain that follows it. In the following example, you store the actor system in the environment dictionary under the key akka.actorsystem. You follow the exact same process for creating an actor system as if you were creating it in a simple console application, which includes the likes of loading configuration in from a file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Startup { public void Configuration(IAppBuilder appBuilder) { var actorSystem = ActorSystem.Create("webapi"); appBuilder.Use((ctx, next) => { ctx.Environment["akka.actorsystem"] = actorSystem; return next(); }); } }

If you now have an element that follows the actor system creation, you can access the actor system stored within the environment. If your pipeline looks as follows, then anything within the web API step is able to access the actor system by using the OWIN environment:

1
2
3
4
5
6
appBuilder.Use((ctx, next) => { ctx.Environment["akka.actorsystem"] = actorSystem; return next(); }).UseWebApi(config);

If you now add a controller to your project, you can retrieve the actor system by using the OWIN extension methods that are available. Now that you have a reference to the actor system, you can interact with it just as you would if it was a console application. Even though it will be accessed by multiple threads, you’re still safe to interact with the actor system due to Akka.NET’s guarantees that each actor will only process one message at a time, but actors can operate concurrently. This makes it ideal for web applications where you might have multiple users with multiple web browsers or applications trying to modify the data stored on the web server concurrently.

You can now start to work with the actor system in your controller. For example, if you’re looking to pull data out of the actor system as part of a get request, you can select an actor path and then send it a message with Ask, awaiting the response. Because Ask returns a task and operates asynchronously, you can use asynchronous controller support alongside it. In the following example, you request data from one of the actors within the actor system by sending it a message and awaiting a response with Ask:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class GreeterController : ApiController { [HttpGet] [Route("hello/{name}")] public async Task<string> GetGreeting(string name) { var owinCtx = Request.GetOwinContext(); var actorSystem = owinCtx.Get<ActorSystem>("akka.actorsystem"); var greeter = actorSystem.ActorSelection("/user/greeter"); var greeting = await greeter.Ask<string>(name); return greeting; } }

As you can see, this integration is incredibly simple, especially in small services, but there are some issues that you might encounter if your web service gets popular. We saw in chapter 7 when looking at scaling that the best option for scaling is to create more instances of something rather than scaling up your existing infrastructure. As it stands, every time you add a new web server, you’ll add a new actor system as well, each of which are independent. Fortunately, we saw in chapter 8 how to combine multiple actor systems together thanks to Akka.Remote. This ensures that you can set up your actor system as a service that other servers are then able to connect to. Each of the web servers in your example then has a lightweight actor system containing no actors that you use to communicate with the shared actor system. Your architecture then looks as follows, where each web server shares a centralized actor system. All of this is possible using the features we saw in chapter 8:

Figure 10.2 - When developing a load balanced web application, you can keep the frontends stateless by having a common actor system that the frontends all communicate with.

By combining your actor system with a web API, you can now access it from the vast majority of devices and systems. Anything that can access your web API or website can communicate with your actor system. You also get the benefit of being able to use the vast array of existing middleware already available for OWIN, ASP.NET, or other web frameworks, thus reducing the possible time spent working on components such as security and using pre-built and thoroughly tested options.

Get Reactive Applications with Akka .NET
buy ebook for  $35.99 $25.19

10.2  Integrating with SignalR

Integrating your actor system into your existing applications through a web service affords you some key benefits, such as the ability to access it from almost any device. As a means of data interchange it is relatively static, allowing clients a single-point-in-time snapshot of their data. But in an age where data flows rapidly and users want the most responsive systems possible, you’re left having to periodically check whether the service has some updated data for you to use. As part of the Reactive Manifesto, you should ensure that the applications you write are responsive for the users who consume your applications and services. When you defined responsive earlier in the book, you saw that it meant that the system was able to react to changes in its environment near instantaneously.

Instead of simply waiting for the user to refresh the webpage, your overall system would be much more responsive if you were able to quickly push changes down to the client without them needing to interact with the application at all. Although this may not have been possible 20 years ago, we’ve seen that the habits of users have greatly changed, which has pushed further changes through the web browser. One example of a change is the addition of web sockets, which provide a bi-directional persistent channel of communication between the user’s web browser and the web server hosting your application. Due to the persistent nature of communication, you can push new messages to the client as soon as they arrive on the server, causing them to potentially update the UI or notify the user of new information becoming available.

One example of this is a situation where you’re rendering charts plotting the data being aggregated by your system in real time. In this situation, you can push new readings to the UI, which is then able to append them to the end of the graph providing you with live visualizations of the current state. In this scenario, your actor system may be receiving data from Internet of Things devices that it is then acting on and performing calculations continuously, which ultimately helps you to predict the future direction of readings that you then present to the user.

Within .NET, SignalR is the library most commonly used for providing WebSocket support to web applications and provides a number of abstractions over the top of the low-level WebSocket protocol. The library also provides a number of fallback mechanisms in the event that the user’s web browser doesn’t support WebSockets. The two abstractions provided by SignalR are persistent connections and hubs, each of which has their own valid use case:

  • Hubs – Hubs are a means of performing remote procedure calls either from a JavaScript client to a .NET server or from a .NET server to a JavaScript client.
  • Persistent connections – Persistent connections are essentially a socket over which you can transfer data down to clients by pushing messages through the connection on the server side.

Throughout this section we’ll look at how to use SignalR within an Akka.NET application to provide a constant stream of information to clients through the use of persistent connections. Although it’s still possible to use hubs, persistent connections more closely match the messaging and usage patterns within an Akka.NET application. In order to use SignalR in your web application, you’ll need to add a reference to the Microsoft.AspNet.SignalR NuGet package in the same way you added a reference to the Akka.NET libraries you’ve added so far.

10.2.1  Communicating through an actor

SignalR provides a fairly advanced level of abstraction over the top of the underlying WebSocket protocol, allowing you to focus on the business logic rather than accepting connections and passing data through these connections. But there’s a number of benefits you’re likely to see as a result of wrapping your SingalR abstraction within an actor. The biggest advantage is the prevention of concurrency bugs that may occur as clients connect and disconnect and send messages. As the SignalR connection is likely to be used in an ASP.NET application hosted on an IIS server or within a console application, there are a number of threads, all of which can be used to handle incoming connections. This leads to the potential for data races or other concurrency bugs that we saw earlier in the book. By wrapping your connection within an actor, you can actively prevent these kinds of bug occurring in your codebase. You also gain an advantage in that you can completely integrate your WebSocket connection into your actor system, allowing you to make use of all of the benefits of Akka.NET, notably remoting, routing, and supervision.

You can first define some basic classes that will represent the messages you can process within your persistent connection actor. The messages fall into one of two categories: commands that tell the actor to do something, in this case, you tell the actor to send a message with the SendMessage command; and you also have events that inform the actor that something has happened, such as the UserConnected, UserDisconnected, and MessageReceived messages you can see in the following example. This is in line with what we’ve seen in other chapters where you’ve created messages to send to actors. You can add a number of properties that reflect the information you’ll be looking to store on a per-connection basis. For example, whenever you receive a message, you track the connection that sent the message; or when a new connection is received by the server, you take the username of the connection and the connection identifier.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class ClientDisconnected { private readonly string _connectionId; public string ConnectionId { get { return _connectionId; } } public ClientDisconnected(string connectionId) { _connectionId = connectionId; } } public class ClientConnected { private readonly string _connectionId; public string ConnectionId { get { return _connectionId; } } public ClientConnected(string connectionId) { _connectionId = connectionId; } } public class MessageReceived { private readonly string _connectionId; private readonly string _data; public string ConnectionId { get { return _connectionId; } } public string Data { get { return _data; } } public MessageReceived(string connectionId, string data) { _connectionId = connectionId; _data = data; } } public class SendMessage { private readonly string _connectionId; private readonly string _data; public string ConnectionId { get { return _connectionId; } } public string Data { get { return _data; } } public SendMessage(string connectionId, string data) { _connectionId = connectionId; _data = data; } }

Having defined your messages, you can create an actor that is able to respond to these messages and react appropriately. Within the actor, you can maintain any state you want, and it will be safe from any potential race condition-based bugs across all of the threads in the thread pool within which it runs. You may want to store a unique user identifier along with the request so that you can address all of the persistent connections for a given user, thus allowing you to push messages to all of their web browser sessions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class WebsocketActor : ReceiveActor { public WebsocketActor() { Receive<MessageReceived>(msg => { //Application specific functionality //for receiving messages }); Receive<ClientConnected>(client => { //Application specific functionality //to handle client connects }); Receive<ClientDisconnected>(client => { //Application specific functionality //to handle client disconnects }); } }

You now have an actor that you can use as the basis for sending messages through a WebSocket connection so that browsers are able to receive direct push-based messages through the web server from the actor system within a web browser.

10.2.2  Connecting to the user’s web browser

You currently have an actor that can handle events raised by the SignalR library, as well as handlers that allow you to push data to clients, but you have no means of sending data to clients. In order to counter this, you need to create a SignalR connection that will allow you to interact with the underlying WebSocket.

In order to create this interaction with the underlying socket, you create a class that inherits from SignalR’s PersistentConnection class. In the following example, you create a simple class that overrides the default behavior for the situations where you receive a message, a client connects, or a client disconnects. Whenever one of these events happens, the method is invoked:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class GraphingConnection : PersistentConnection { protected override Task OnReceived( IRequest request, string connectionId, string data) { return base.OnReceived(request, connectionId, data); } protected override Task OnConnected( IRequest request, string connectionId) { return base.OnConnected(request, connectionId); } protected override Task OnDisconnected( IRequest request, string connectionId, bool stopCalled) { return base.OnDisconnected(request, connectionId, stopCalled); } }

Having now created a persistent connection class, you need to be able to create it and host it within the application. This functionality is provided by OWIN as we saw in the previous section. In order to register your persistent connection, you simply add a MapSignalR call into your OWIN startup class following the actor system initialization. The following example shows what the OWIN startup looks like in a situation where you have an Akka.NET actor system, SignalR, and MVC within the same project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Startup { public void Configuration(IAppBuilder appBuilder) { var actorSystem = ActorSystem.Create("webapi"); appBuilder.Use((ctx, next) => { ctx.Environment["akka.actorsystem"] = actorSystem; return next(); }).MapSignalR<GraphingConnection>("graph"); } }

We briefly mentioned at the start of the previous section that one of the core reasons to wrap your connection within an actor is to ensure that your application is not susceptible to any race conditions it might encounter during its lifetime. SignalR creates a single instance of the persistent connection, but it gets executed as part of a thread pool provided by the application host, which could potentially lead to concurrent invocations of the methods on this class. In order to counter this, you can forward all of the events through to the actor you created earlier. Before you can send a message to the actor, you first need to be able to reference the actor system. By configuring SignalR after you have configured the actor system in the OWIN startup, you can access it from within the OWIN environment, a dictionary of strings and objects relating to the request. This follows the same pattern as we saw in the previous section. You can access the OWIN environment using the Environment property on the incoming request on each method you override. Having retrieved the actor system, you can interact with it and send it messages, as we’ve seen before. In the following example, having received a message, you first wrap the message in an envelope along with the connection identifier and the send it to the actor responsible for the SignalR connection:

1
2
3
4
5
6
7
8
9
10
11
12
13
protected override Task OnReceived( IRequest request, string connectionId, string data) { var actorSystem = (ActorSystem)request.Environment["akka.actorsystem"]; var websocketActor = actorSystem.ActorSelection("/user/messagingConnection"); websocketActor.Tell(new MessageReceived(connectionId, data)); return base.OnReceived(request, connectionId, data); }

In addition to the scenarios where you receive events from the connection, there are also commands that the actor handles that need to be processed and forwarded on through the connection. In the following example, you can see how to retrieve PersistentConnection from within the actor so that you can send messages to a given client identifier in response to a SendMessage command:

1
2
3
4
5
6
7
8
9
10
var connection = GlobalHost .ConnectionManager .GetConnectionContext<GraphingConnection>(); Receive<SendMessage>(msg => { connection.Connection.Send(msg.ConnectionId, msg.Data); });

By wrapping a lot of the behavior you might have seen in the derived PersistentConnection class, you can remove the possibility of race conditions occurring within your applications, thus reducing the potential number of bugs.

10.2.3  Integrating with SignalR summary

WebSockets provide a means of creating end-to-end reactive web applications so that users are able to receive updates in their frontend UI immediately after the event has been triggered in the backend systems powering the applications. By using SignalR, you can rapidly build web applications where users are able to directly visualize and manipulate data within your actor system incredibly easily. By combining SignalR with Akka.NET, users can see their application changing and responding to events in real time, allowing them to act upon the information they’re presented with as soon as it happens and not as soon as they refresh.

Sign in for more free preview time

10.3  Custom integrations with Akka.IO

Although the cases we’ve seen so far have covered a large majority of cases, there are still many other potential systems and devices that are likely to want to connect to your actor systems and send messages. HTTP has clear benefits in its uptake and support across a vast number of devices, but it proves to be a fairly heavyweight protocol due to its reliance on text rather than a simple binary protocol instead. In some environments, this amount of overhead could lead to difficulties. For example, in an Internet of Things scenario where you’re sending data through a cellular modem, you need to minimize the amount of data transferred in an effort to bring down costs and also ensure that you minimize the potential amount of data that could be lost. You shouldn’t just limit yourself to IoT scenarios where you could see potential benefits, though; if you’re interoperating with other custom developed solutions, you may see significantly reduced latencies as a result of using a significantly simpler protocol, which ultimately leads to a more responsive application as per the aims of the Reactive Manifesto.

In these cases, you ideally want a means of providing a low-level connection into your actor system so that you can operate on a socket level rather than having to go through a complex pipeline of operations in an effort to get a request from the user. Instead, you want to receive a message every time the socket receives a message and allow the actor to process the message instead of relying on complex code surrounding sockets, which can be difficult to correctly set up. This allows you to quickly write tooling that relies on more basic or less popular protocols. For example, by using the low-level socket APIs provided within Akka.NET, you can easily create implementations of the likes of DNS servers, monitoring servers, and mail servers, or implement your own protocol tailored to the requirements of your application.

Throughout this section, we’ll look at how to use Akka.IO to create a simple socket server designed to communicate with a common metrics collection protocol known as StatsD. StatsD is designed to be an incredibly simple API that can receive counters, gauges, and timers sent to it over a network from either TCP or UDP. By bringing this kind of low-level protocol directly within the actor system, you can use all of the key benefits that Akka.NET provides, such as message routing, fault tolerance and on demand scaling. These benefits ensure that you can react instantly to any load or failure that you might encounter when receiving metrics, an incredibly valuable tool in any operations team for debugging that requires a certain degree of resiliency, even in the face of failure, in order to provide the information you need to try and understand the causes of the failures.

The StatsD protocol is incredibly simple and consists of two core concepts; buckets and values. A bucket is a means of representing a certain metric which you are looking to collect, for example, you may choose to create a bucket called UserService.Login.Latency as a way of representing all of the latencies that you’ve observed by users logging in through the user service. There are a number of valid metrics types you can send, but in this section, we’ll focus on three core metrics; counters, gauges, and timers. Counters are used for basic counting tasks; some examples of metrics you may want to count include the number of requests for a web service, the number of times you fail to retrieve a value from a cache and need to talk to a database instead, and the amount of times an exception was thrown. Gauges are for already averaged data. This might include things such as system load or average latency; these are things that are likely to be set once every second and don’t fluctuate. The final type we’ll look at in this section is a timer, which is used to specify the amount of time an operation took to complete. An example of such an operation might be the latency involved when executing a query against a database or the time it takes between a request being received from the user and a response being sent back to the user.

The underlying StatsD implementation requires a simple line of text to be sent to the socket as part of a single packet sent over the network. The basic structure of the line you need to send to your socket is shown in the following example. As you can see, it’s as simple as sending the bucket name, a value for it, and the type of metric it is. For each of these components, you provide a string for each component. These can be anything for the first two parts, and then the final part matches a string representation of each of the metric types. This can then be sent over either a TCP or UDP socket depending upon what the server is configured to listen on.

1
2
3
<metricname>:<value>|<type> UserService.RequestCount:50|c

Having created an implementation of the underlying protocol in Akka.NET, you can dispatch the received metrics to anywhere else in your actor system to quickly perform tasks such as data storage, stats aggregation, and notifications. But we’ll focus on the actual task of ingestion and how you can map your incoming packets over to messages that can be used within the context of your actor system.

10.3.1  Creating a listen server

Akka.IO provides support for a number of different scenarios based on factors such as whether you should be listening for incoming packets or sending packets over the socket, as well as the underlying socket protocol you should be listening on. Although TCP and UDP support is provided as a pluggable transport within the Akka.NET distribution, you can also create your own underlying transport mechanism to reflect other networking technologies. For example, you may want to create an Akka.IO transport that can communicate over a pipe rather than a socket, or use a socket but use a different transport protocol than UDP or TCP, such as SPDY. In this example, you’ll use a simple TCP socket as the basis of your incoming communications and you’ll listen for incoming messages rather than sending outgoing messages over the socket.

Before you can receive messages over a socket, you first need to tell the underlying operating system that you want any messages received on a certain port to be forwarded onto your application. This is achieved by using the TcpManager, which handles all of the underlying bind operations to get the operating system to route any TCP packets through to the application. The TcpManager is made available through an extension method on the actor system itself and is accessed through the TCP extension method on the actor system. In the following example, you get the reference to the TcpManager, which then allows you to bind to a specific port:

1
2
3
4
5
6
7
8
public class StatsDServer : ReceiveActor { public StatsDServer() { var tcpManager = Context.System.Tcp(); } }

The typical approach to building socket servers based on Akka.IO is to have an actor that is responsible for the original port binding and handling the incoming connection requests from the underlying socket before passing the network connection onto another actor, which will deal with the specifics of that one connection. The following diagram demonstrates how to design such a system. There is a single actor that acts as the server and deals with incoming messages from the TcpManager relating to the status of the connection. This includes the likes of binding success or failure messages, incoming connection requests, and socket errors. This server actor then has a number of children that are spawned on a per-connection basis. As we saw in chapter 3, actors are a very cheap abstraction, meaning that you can create millions of them within a single application.

Figure 10.3 - Akka.IO creates an actor per network connection that prevents any potential concurrency problems when receiving messages from multiple senders simultaneously.

The first component of this design is the server actor, which is responsible for the communication with the TcpManager. This has the responsibility of telling the TcpManager that you are using this actor as the target for all new incoming connections. In order to do that, you send it a Bind message with a reference to the actor that should be told of new messages, as well as the incoming endpoint on which you should listen for packets. In the following example, you create a new actor that is responsible for any server-related responsibilities and then send a message to the TcpManager telling it that you want it to receive any incoming connection requests:

1
2
tcpManager.Tell(new Tcp.Bind(Self, new IPEndPoint(IPAddress.Any, 8080)));

Having told the TcpManager that you want this actor to receive all incoming connections, you need to then receive the incoming messages containing the incoming connections. You’ll typically receive two messages during the initialization of the actor; Bound and Connected. Bound is sent when the underlying socket listener has been established and is able to start receiving incoming connections. The Bound message contains the underlying socket address that the operating system is now listening on. Whenever a new connection is established with the TCP listener, a new actor is spawned internally that is responsible for handling all of the low-level socket internals, such as message serialization, buffering, and any other tasks. This actor sends a message to your server actor, informing it that a new connection is available. Then you can register an actor that should receive the deserialized messages from this connection. You could choose to register them in your server actor, but in the following example, you create a new actor that is responsible for that single connection. This decision allows you to process more packets in parallel while also getting all of the benefits of single actors, including the likes of using finite state machines, which may be useful for more complex protocols that rely on more involved handshake protocols. In the following example, you spawn a new actor that is responsible for each incoming connection and responding to the incoming messages:

1
2
3
4
5
6
7
8
9
10
11
12
Receive<Tcp.Bound>(bound => { Console.WriteLine("The connection was bound to port 8080"); }); Receive<Tcp.Connected>(connected => { var connectionActor = Context.ActorOf(Props.Create(() => new StatsDServerChannel())); Sender.Tell(new Tcp.Register(connectionActor)); });

Having created your actor hierarchy with an actor per connection, you can now start to process the incoming information from each actor. Whenever a connection receives an incoming packet, Akka.IO first reassembles the complete TCP message, which may have been split across several packets, before sending it to the actor registered to the given connection wrapped in a Received message. You can then access the underlying bytes transferred over the network through the Data property of the Received message. From here, you can convert it into an appropriate format. This may include leaving it in a binary format, converting it into a text format, or deserializing into an object graph with tools such as Google’s Protocol Buffers or other binary serialization tooling. In this case, you convert the message to an ASCII string, because this matches the specification as laid out by StatsD. In the following example, you can convert your received ByteString into a string that you can then parse and take the appropriate action on as required:

1
2
3
4
5
6
Receive<Tcp.Received>(packet => { var statsDData = packet.Data.DecodeString(); HandleStatsDData(statsDData); });

Having now got your metrics data into your actor system, you can use any of the other Akka.NET features at your disposal to better understand the usage. You may want to create an actor for each bucket that will be responsible for aggregating the incoming data and creating alerts based on that aggregated data; you may want to ingest it into other systems or databases for easier analysis or visualization to better understand the metrics you receive.

10.3.2  Sending data through Akka.IO

Having created a socket that is capable of listening, you may want to open a connection to a socket listening at a remote endpoint. You may to create a low-level socket implementation, but because you have Akka.IO, you can bring your client socket connection into the actor system, following many of the same principles we saw when you created a socket capable of listening for incoming data. You can follow many of the same ideas, but as one client is only able to connect to one server, you can simplify it further.

When you created a server, the first step was to bind to a socket so that you’d receive any incoming messages, but when developing a client designed to consume a socket, you need to connect to the remote endpoint before you can communicate with it. In order to connect to a remote socket, you simply need to retrieve a reference to the TcpManager as before, which is responsible for handling all of the low-level socket management, and send it a Connect message. In the following example, you create an actor that is responsible for communicating with the server. It does this by sending a message to the TcpManager actor with the endpoint that the server is listening on. Your endpoint consists of two key pieces of information, the IP address of the remote host and the port on which it’s listening.

1
2
3
4
5
6
var serverEndpoint = new IPEndPoint( IPAddress.Parse("127.0.0.1"), 8080); tcpManager.Tell(new Tcp.Connect(serverEndpoint));

Having sent the message to the TcpManager, a new connection is created and the original actor requesting the connection is informed of whether the connection was successful or not. If the connection was unsuccessful, it receives a CommandFailed message with a string representation of the issue. But if it was successful, then it receives a message with both the remote endpoint you are communicating with and the local endpoint the connection was opened on. The sender of this message can then be used as a means of communicating with the server. Every message the sender receives, it forwards through the underlying socket. In the following example, you can see how to use the switchable behavior functionality of an actor to be able to communicate with the server by having a different behavior for the connected and unconnected states. The actor can then receive a variety of messages relating to either the connection itself or other actors in the system who want to communicate with the socket.

1
2
3
4
5
6
7
8
9
10
11
Receive<Tcp.Connected>(msg => { Sender.Tell(new Tcp.Register(Self)); Become(Connected(Sender)); }); Receive<Tcp.CommandFailed>(msg => { Console.WriteLine("Failed to connect to remote endpoint"); });

Now that you have a client connection, you can start communicating with the remote server. We’ve already seen the StatsD protocol, and we’ll now see how to directly communicate with a server running the protocol. By having a client within your actor system that deals with communicating with the StatsD server, you can directly ingest metrics into your StatsD server with little effort. This could mean that you can persist metrics such as the time taken to process certain message types or the number of messages an actor has processed. The following example shows how to receive messages from inside your actor system and send them through the socket. In order to send data through the socket, you simply send a Tcp.Write message to your coordinating actor, with a ByteString that contains the data you want to send over the network. You can create a ByteString from other ByteStrings, an array, or a string, meaning that you can easily serialize the ASCII strings required by the StatsD protocol. But there may be other situations in which you receive a message from the socket, ranging from an IO error where the network connection was physically cut, a peer reset error where the other party quickly quit the application without first closing the connection, or a situation where the remote party normally disconnects. In these cases, the coordinating actor sends a message relating to the cause of the problem. For example, if a peer reset occurs, a Tcp.PeerReset message is sent, or if a network error occurs, a Tcp.IOError is sent. You can handle these errors and respond appropriately. For example, you may want to cache any incoming messages until you can reconnect and then send them once the connection has been re-established. In this simple case, you simply write a message to the log that the connection was reset.

1
2
3
4
5
6
7
8
9
10
11
12
private Action Connected(IActorRef connection) { return () => { Receive<string>(msg => { var write = Tcp.Write.Create(ByteString.FromString(msg)); connection.Tell(write); }); }; }

Although the example here focuses on connecting to an Akka.NET-based socket server, the implementation that you’ve created can connect to any of the available StatsD servers that use TCP, because you’re no longer simply constrained to connecting to other actor systems. You’re also not limited to using the StatsD protocol, and you can implement clients for any protocol sent over a network. For example, you could just as easily create a client that interoperates with an SMTP server in order to send and receive emails, or a DNS server providing IP addresses for domain names.

10.3.3  Akka.IO Summary

In the example of how to use Akka.IO in this section, you’ve created a socket connection on both a client actor system and server actor system, allowing you to communicate between the two. Although you could have easily used the Akka.Remote features we saw in chapter 8, this would have limited your usage to only allowing Akka.NET-based actor systems to connect and record metrics, but by using Akka.IO, you can receive input on a common protocol, in this instance StatsD, allowing other clients to connect and publish metrics. This means that you can use other technologies where appropriate, for example, simple shell scripts when you want to monitor operating system-level metrics or clients available in other languages such as Java or Ruby, allowing you to receive metrics from any system within your overall system architecture.

By using Akka.IO, you quickly and easily set up a high-performance low-level socket connection that enabled you to receive messages sent from applications outside of the actor system with minimal overhead and low latency and immediately have them available within your actor system. This level of abstraction helps to remove many of the complexities you’re likely to encounter as you develop network-based applications. By having a well-known, easy-to-use API, you can ensure that you can receive messages from the network in a performant manner without deep technical knowledge of the underlying operating system kernel and how it handles packets received from the network.

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

10.4  Case study: IO – integration – IoT applications

As you see an increase in the uptake of Internet of Things devices, you see devices used in potentially more hostile environments where you don’t necessarily have many of the same conveniences that you might expect when deploying a traditional software project. As an example, when designing systems, you typically expect a fast internet connection between the client and the server. As an example, high speed broadband connections are found in the majority of homes, but as you seek data from more remote environments, you’re unlikely to have access to a home broadband connection. Instead, you’re more likely to encounter low speed, low bandwidth, and high latency connections operating over older cellular connections. This puts pressure on you to choose the correct protocol for transferring data over the network. In typical scenarios, you might choose to use HTTP, but in these hostile environments, you’ll use a much lower-level protocol, specialized for the task at hand, focusing on minimizing the overall packet size.

You can therefore use the IO components of Akka.NET to use actors as a means of simplifying the acquisition of data from a network socket and immediately processing it within your actor system. From here, you can decompress or deserialize the contents of the network packet and push it through to other actors in the system. This allows you to then perform more complex logic on actors that process the messages as .NET objects.

As an example, one of the frequent tasks relating to Internet of Things workflows is to receive time series data and perform complex event processing on this time series data, helping to understand historical data and predict future trends. You can see an example architecture in the following diagram, where you have a number of Internet of Things devices deployed in a field on a farm. These devices monitor a number of factors, including the moisture of soil and weather data over time, which they periodically upload to an Akka.NET based system in the cloud. This system then aggregates the data from multiple devices and calculates predictions based on the data and historic weather data to ascertain whether irrigation should be switched on or natural water is used. Given that these devices will be deployed in rural locations outside of major population centers, it’s likely that the only internet connection available will be through a cellular connection, probably 2G or EDGE. As such, you’ll have to use a protocol that prioritizes small packet sizes because of the high cost of cellular data and the low bandwidth available. In the following diagram, you can see how you can create an actor that receives data from a network socket and then passes it onto an actor within the system dedicated to processing the event data from each of the individual devices. Further actors are then able to aggregate data from clusters of devices if necessary.

Figure 10.3 - Internet of Things applications operating in hostile environments may be subject to bandwidth constraints. By opening a direct socket into your actor system, you have the opportunity to create your own custom protocol relevant to the circumstances.

By using the IO components of Akka.NET, you can simplify the ingestion of data from a network source that might not have a pre-existing network client. Given the complexities typically associated with low-level socket programming, the use of IO components within Akka.NET allows you to simply treat a socket as another actor.

Sign in for more free preview time

10.5  Summary

In this chapter, you learned:

  • How Akka.NET can be combined with other tools, such as Web API and SignalR, to build completely reactive applications
  • How to use Akka.IO to treat sockets as first-class components of an actor system

[1] This book focuses on features relating to Akka.NET and as such won’t go in depth into features related to ASP.NET. For more information on ASP.NET including getting started tutorials, documentation and more then please visit http://asp.net or the ASP.NET in Action series available from Manning.

sitemap

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage
Up next...
  • Adding a persistent backing datastore to an actor to save its state
  • The concepts behind event sourcing
  • Creating evolvable applications using Akka.Persistence and event sourcing
meap badge
{{{UNSCRAMBLE_INFO_CONTENT}}}