Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions docs/articles/intro/getting-started/tutorial-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ Creating a non-top-level actor is possible from any actor, by invoking
`Context.ActorOf()` which has the exact same signature as its top-level
counterpart. This is how it looks like in practice:

[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial1/ActorHierarchyExperiments.cs?name=print-refs)]
[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial1/ActorHierarchyExperiments.cs?name=print-refs2)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial1/ActorHierarchyExperiments.cs?name=print-refs)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial1/ActorHierarchyExperiments.cs?name=print-refs2)]

We see that the following two lines are printed

Expand Down Expand Up @@ -129,8 +129,8 @@ override. The most commonly used are `PreStart()` and `PostStop()`.

Again, we can try out all this with a simple experiment:

[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial1/ActorHierarchyExperiments.cs?name=start-stop)]
[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial1/ActorHierarchyExperiments.cs?name=start-stop2)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial1/ActorHierarchyExperiments.cs?name=start-stop)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial1/ActorHierarchyExperiments.cs?name=start-stop2)]

After running it, we get the output

Expand Down Expand Up @@ -160,8 +160,8 @@ The default _supervisor strategy_ is to stop and restart the child. If you don't
change the default strategy all failures result in a restart. We won't change
the default strategy in this simple experiment:

[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial1/ActorHierarchyExperiments.cs?name=supervise)]
[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial1/ActorHierarchyExperiments.cs?name=supervise2)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial1/ActorHierarchyExperiments.cs?name=supervise)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial1/ActorHierarchyExperiments.cs?name=supervise2)]

After running the snippet, we see the following output on the console:

Expand Down Expand Up @@ -225,11 +225,11 @@ the logging facility built into Akka.NET directly. Furthermore, we are using a
recommended pattern for creating actors; define a static `Props()` method in
the the actor:

[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial1/IotSupervisor.cs?name=iot-supervisor)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial1/IotSupervisor.cs?name=iot-supervisor)]

All we need now is to tie this up with a class with the `main` entry point:

[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial1/IotApp.cs?name=iot-app)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial1/IotApp.cs?name=iot-app)]

This application does very little for now, but we have the first actor in place
and we are ready to extend it further.
Expand Down
14 changes: 7 additions & 7 deletions docs/articles/intro/getting-started/tutorial-2.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The protocol for obtaining the current temperature from the device actor is rath

We need two messages, one for the request, and one for the reply. A first attempt could look like this:

[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial2/DeviceInProgress.cs?name=read-protocol-1)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial2/DeviceInProgress.cs?name=read-protocol-1)]

This is a fine approach, but it limits the flexibility of the protocol. To understand why we need to talk
about message ordering and message delivery guarantees in general.
Expand Down Expand Up @@ -144,20 +144,20 @@ can be helpful to put an additional query ID field in the message which helps us

Hence, we add one more field to our messages, so that an ID can be provided by the requester:

[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial2/DeviceInProgress.cs?name=read-protocol-2)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial2/DeviceInProgress.cs?name=read-protocol-2)]

Our device actor has the responsibility to use the same ID for the response of a given query. Now we can sketch
our device actor:

[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial2/DeviceInProgress.cs?name=device-with-read)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial2/DeviceInProgress.cs?name=device-with-read)]

We maintain the current temperature, initially set to `null`, and we simply report it back if queried. We also
added fields for the ID of the device and the group it belongs to, which we will use later.

We can already write a simple test for this functionality (we are using xUnit but any other test framework can be
used with the Akka.NET Testkit):

[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial2/DeviceSpec.cs?name=device-read-test)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial2/DeviceSpec.cs?name=device-read-test)]

## The Write Protocol

Expand All @@ -167,7 +167,7 @@ As a first attempt, we could model recording the current temperature in the devi

Such a message could possibly look like this:

[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial2/DeviceInProgress.cs?name=write-protocol-1)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial2/DeviceInProgress.cs?name=write-protocol-1)]

The problem with this approach is that the sender of the record temperature message can never be sure if the message
was processed or not. We have seen that Akka.NET does not guarantee delivery of these messages and leaves it to the
Expand All @@ -177,12 +177,12 @@ Just like in the case of temperature queries and responses, it is a good idea to

Putting read and write protocol together, the device actor will look like this:

[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial2/Device.cs?name=full-device)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial2/Device.cs?name=full-device)]

We are also responsible for writing a new test case now, exercising both the read/query and write/record functionality
together:

[!code-csharp[Main](../../../src/core/Akka.Docs.Tutorials/Tutorial2/DeviceSpec.cs?name=device-write-read-test)]
[!code-csharp[Main](../../../../src/core/Akka.Docs.Tutorials/Tutorial2/DeviceSpec.cs?name=device-write-read-test)]

## What Is Next?

Expand Down
20 changes: 10 additions & 10 deletions docs/articles/intro/getting-started/tutorial-3.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ is known up front: device groups and device actors are created on-demand. The st
Now that the steps are defined, we only need to define the messages that we will use to communicate requests and
their acknowledgment:

[!code-csharp[DeviceManager.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceManager.cs?name=device-manager-msgs)]
[!code-csharp[DeviceManager.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceManager.cs?name=device-manager-msgs)]

As you see, in this case, we have not included a request ID field in the messages. Since registration is usually happening
once, at the component that connects the system to some network protocol, we will usually have no use for the ID.
Expand All @@ -104,7 +104,7 @@ message is preserved in the upper layers.* We will show you in the next section
We also add a safeguard against requests that come with a mismatched group or device ID. This is how the resulting
the code looks like:

[!code-csharp[Device.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial3/Device.cs?name=device-with-register)]
[!code-csharp[Device.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial3/Device.cs?name=device-with-register)]

We should not leave features untested, so we immediately write two new test cases, one exercising successful
registration, the other testing the case when IDs don't match:
Expand All @@ -115,7 +115,7 @@ and fails if it receives any messages during this period. If no messages are rec
assertion passes. It is usually a good idea to keep these timeouts low (but not too low) because they add significant
test execution time otherwise.

[!code-csharp[DeviceSpec.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceSpec.cs?name=device-registration-tests)]
[!code-csharp[DeviceSpec.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceSpec.cs?name=device-registration-tests)]

## Device Group

Expand All @@ -128,18 +128,18 @@ by using `Forward` instead of the `Tell` operator. The only difference between t
sender while `Tell` always sets the sender to be the current actor. Just like with our device actor, we ensure that we don't
respond to wrong group IDs:

[!code-csharp[DeviceGroup.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceGroupInProgress.cs?name=device-group-register)]
[!code-csharp[DeviceGroup.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceGroupInProgress.cs?name=device-group-register)]

Just as we did with the device, we test this new functionality. We also test that the actors returned for the two
different IDs are actually different, and we also attempt to record a temperature reading for each of the devices
to see if the actors are responding.

[!code-csharp[DeviceGroupSpec.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceGroupSpec.cs?name=device-group-test-registration)]
[!code-csharp[DeviceGroupSpec.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceGroupSpec.cs?name=device-group-test-registration)]

It might be, that a device actor already exists for the registration request. In this case, we would like to use
the existing actor instead of a new one. We have not tested this yet, so we need to fix this:

[!code-csharp[DeviceGroupSpec.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceGroupSpec.cs?name=device-group-test3)]
[!code-csharp[DeviceGroupSpec.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceGroupSpec.cs?name=device-group-test3)]

So far, we have implemented everything for registering device actors in the group. Devices come and go, however, so
we will need a way to remove those from the `Dictionary<string, IActorRef>`. We will assume that when a device is removed, its corresponding device actor
Expand All @@ -164,13 +164,13 @@ its ID, which we need to remove it from the map of existing device to device act
need to introduce another placeholder, `Dictionary<IActorRef, string>`, that allow us to find out the device ID corresponding to a given `IActorRef`. Putting
this together the result is:

[!code-csharp[DeviceGroup.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceGroupInProgress.cs?name=device-group-remove)]
[!code-csharp[DeviceGroup.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceGroupInProgress.cs?name=device-group-remove)]

So far we have no means to get what devices the group device actor keeps track of and, therefore, we cannot test our
new functionality yet. To make it testable, we add a new query capability `RequestDeviceList` that simply lists the currently active
device IDs:

[!code-csharp[DeviceGroup.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceGroup.cs?name=device-group-full)]
[!code-csharp[DeviceGroup.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceGroup.cs?name=device-group-full)]

We almost have everything to test the removal of devices. What is missing is:

Expand All @@ -184,14 +184,14 @@ We add two more test cases now. In the first, we just test that we get back the
a few devices. The second test case makes sure that the device ID is properly removed after the device actor has
been stopped:

[!code-csharp[DeviceGroupSpec.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceGroupSpec.cs?name=device-group-list-terminate-test)]
[!code-csharp[DeviceGroupSpec.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceGroupSpec.cs?name=device-group-list-terminate-test)]

## Device Manager

The only part that remains now is the entry point for our device manager component. This actor is very similar to
the device group actor, with the only difference that it creates device group actors instead of device actors:

[!code-csharp[DeviceManager.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceManager.cs?name=device-manager-full)]
[!code-csharp[DeviceManager.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial3/DeviceManager.cs?name=device-manager-full)]

We leave tests of the device manager as an exercise as it is very similar to the tests we have written for the group
actor.
Expand Down
24 changes: 12 additions & 12 deletions docs/articles/intro/getting-started/tutorial-4.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ that each device can be in, according to the query:

Summarizing these in message types we can add the following to `DeviceGroup`:

[!code-csharp[DeviceGroup.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroup.cs?name=query-protocol)]
[!code-csharp[DeviceGroup.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroup.cs?name=query-protocol)]

## Implementing the Query

Expand Down Expand Up @@ -90,7 +90,7 @@ until the timeout to mark these as not available.

Putting together all these, the outline of our actor looks like this:

[!code-csharp[DeviceGroupQueryInProgress.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQueryInProgress.cs?name=query-outline)]
[!code-csharp[DeviceGroupQueryInProgress.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQueryInProgress.cs?name=query-outline)]

The query actor, apart from the pending timer, has one stateful aspect about it: the actors that did not answer so far or,
from the other way around, the set of actors that have replied or stopped. One way to track this state is
Expand All @@ -110,7 +110,7 @@ we will discuss later. In the case of timeout, we need to simply take all the ac
(the members of the set `stillWaiting`) and put a `DeviceTimedOut` as the status in the final reply. Then we
reply to the submitter of the query with the collected results and stop the query actor:

[!code-csharp[DeviceGroupQuery.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuery.cs?name=query-state)]
[!code-csharp[DeviceGroupQuery.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuery.cs?name=query-state)]

What is not yet clear is how we will "mutate" the `answersSoFar` and `stillWaiting` data structures. One important
thing to note is that the function `WaitingForReplies` **does not handle the messages directly. It returns an `UntypedReceive`
Expand Down Expand Up @@ -139,7 +139,7 @@ only the first call will have any effect, the rest is simply ignored.

With all this knowledge, we can create the `ReceivedResponse` method:

[!code-csharp[DeviceGroupQuery.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuery.cs?name=query-collect-reply)]
[!code-csharp[DeviceGroupQuery.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuery.cs?name=query-collect-reply)]

It is quite natural to ask at this point, what have we gained by using the `Context.Become()` trick instead of
just making the `repliesSoFar` and `stillWaiting` structures mutable fields of the actor? In this
Expand All @@ -153,7 +153,7 @@ with the solution we have used here as it helps structuring more complex actor c

Our query actor is now done:

[!code-csharp[DeviceGroupQuery.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuery.cs?name=query-full)]
[!code-csharp[DeviceGroupQuery.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuery.cs?name=query-full)]

## Testing

Expand All @@ -163,27 +163,27 @@ various normal or failure scenarios. Thankfully we took the list of collaborator
to the query actor, so we can easily pass in `TestProbe` references. In our first test, we try out the case when
there are two devices and both report a temperature:

[!code-csharp[DeviceGroupQuerySpec.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuerySpec.cs?name=query-test-normal)]
[!code-csharp[DeviceGroupQuerySpec.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuerySpec.cs?name=query-test-normal)]

That was the happy case, but we know that sometimes devices cannot provide a temperature measurement. This
scenario is just slightly different from the previous:

[!code-csharp[DeviceGroupQuerySpec.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuerySpec.cs?name=query-test-no-reading)]
[!code-csharp[DeviceGroupQuerySpec.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuerySpec.cs?name=query-test-no-reading)]

We also know, that sometimes device actors stop before answering:

[!code-csharp[DeviceGroupQuerySpec.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuerySpec.cs?name=query-test-stopped)]
[!code-csharp[DeviceGroupQuerySpec.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuerySpec.cs?name=query-test-stopped)]

If you remember, there is another case related to device actors stopping. It is possible that we get a normal reply
from a device actor, but then receive a `Terminated` for the same actor later. In this case, we would like to keep
the first reply and not mark the device as `DeviceNotAvailable`. We should test this, too:

[!code-csharp[DeviceGroupQuerySpec.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuerySpec.cs?name=query-test-stopped-later)]
[!code-csharp[DeviceGroupQuerySpec.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuerySpec.cs?name=query-test-stopped-later)]

The final case is when not all devices respond in time. To keep our test relatively fast, we will construct the
`DeviceGroupQuery` actor with a smaller timeout:

[!code-csharp[DeviceGroupQuerySpec.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuerySpec.cs?name=query-test-timeout)]
[!code-csharp[DeviceGroupQuerySpec.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQuerySpec.cs?name=query-test-timeout)]

Our query works as expected now, it is time to include this new functionality in the `DeviceGroup` actor now.

Expand All @@ -192,7 +192,7 @@ Our query works as expected now, it is time to include this new functionality in
Including the query feature in the group actor is fairly simple now. We did all the heavy lifting in the query actor
itself, the group actor only needs to create it with the right initial parameters and nothing else.

[!code-csharp[DeviceGroupQueryInProgress.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQueryInProgress.cs?name=query-added)]
[!code-csharp[DeviceGroupQueryInProgress.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupQueryInProgress.cs?name=query-added)]

It is probably worth reiterating what we said at the beginning of the chapter: By keeping the temporary state
that is only relevant to the query itself in a separate actor we keep the group actor implementation very simple. It delegates
Expand All @@ -204,4 +204,4 @@ would significantly improve throughput.
We close this chapter by testing that everything works together. This test is just a variant of the previous ones,
now exercising the group query feature:

[!code-csharp[DeviceGroupSpec.cs](../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupSpec.cs?name=group-query-integration-test)]
[!code-csharp[DeviceGroupSpec.cs](../../../../src/core/Akka.Docs.Tutorials/Tutorial4/DeviceGroupSpec.cs?name=group-query-integration-test)]