Table of Contents

IoT

The IoT use case is an example of the way DMediatR is opinionated: Encourage the distribution of the same monolith across all nodes of a microservice architecture - it is the responsibility of the RPC caller to use only a node that actually provides the desired service. In this case, querying the CPU temperature on a Windows machine throws the exception "Iot.Device.CpuTemperature is not available on this device".

The Iot sample node

The Iot sample host can be published from VS with the RuntimeIdentifier linux-arm641. The included limux-arm64.pubxml points to an SMB share Z: connected via USB to the target Raspberry PI. After deployment, it can be started there locally with ./Iot.

The developer certificate

Important

Although it is in fact not used at all, the dynamic late certificate assignment causes the server to require a developer certificate installed during startup. To avoid having to install the dotnet SDK2 despite publishing it as self-contained app3, you can copy a developer certificate manually to its directory created with

mkdir -p ~/.dotnet/corefx/cryptography/x509stores/my

The smoke test

The appsettings.Iot.json in DMediatR.Tests points to the Raspberry PI host named rpi. It is written with a trailing dot to force Windows to actually query the Linux DNS server assigned by DHCP:

"Remotes": {
  "CpuTemp": {
    "Host": "rpi.",
    "Port": 18001,
    "OldPort": 18002
  }
},

The certificate path in the DMediatR:Certificate:FilePath points to the cert directory in the Iot application. If you create the initial certificate chain on the Raspberry Pi with ./Iot init, manually copy it over to that directory.

The first of the two test methods in IotTest.cs simply sends a GET request like a web browser would and asserts having received the same response:

[Test]
public async Task CpuTempReachable()
{
    using var httpClient = await TestSetUp.GetHttpClientAsync();
    var response = await httpClient.GetStringAsync(Remote.Remotes["CpuTemp"].Address);
    Assert.That(response, Is.EqualTo("DMediatR on rpi:18001"));
}

[Test]
public async Task GetRemoteTemp()
{
    var temperature = await this.SendRemote(new TempRequest(), CancellationToken.None);
    TestContext.WriteLine($"CpuTemp of {Remote.Remotes["CpuTemp"].Address} is {temperature,0:f1} ℃.");
}

The second one actually uses the DMediatR infrastructure and sends a serialized MediatR IRequest to remotely query the CPU temperature of the Raspberry PI using the Iot.Device.Bindings NuGet package4. On the PI, the request is handled locally in the TempHandler.cs and the result is sent back. Then, for plausibility, this temperature can manually be compared to what e.g. vcgencmd measure_temp in an SSH shell reports:

 CpuTemp of https://rpi.:18001/ is 46.7 ℃.

Monolithic /remotes.svg Graph

In above configuration, the /remotes.svg graph produced by the Iot node contains just the node itself:

remotes.Iiot.svg

Splitting up the monolith

By simply amending a Remotes section to the appsettings.json, the monolith can be split up into two microservices: The former monolith handles the TempRequest as before, but transparently forwards it to the designated second node called rpi2 (this time without the trailing dot, as the DNS is queried on Linux):

    "Remotes": {
      "CpuTemp": {
        "Host": "rpi2",
        "Port": 18001,
        "OldPort": 18002
      }
    }

When the GetRemoteTemp test is run again, it suddenly fails the plausibility test (the first one is a PI 4, the second one a PI 5):

 CpuTemp of https://rpi.:18001/ is 56.2 ℃.

Two nodes /remotes.svg Graph

The graph produced by the original rpi node now displays the second node:

remotes.Iot2.svg


  1. It's been a long way from the days when the special Windows distribution designed to run .NET on a Raspberry PI 3 refused to boot on a 3+ because of the "+"…

  2. ./dotnet-install.sh --channel 8.0 with a scripted install

  3. Deploying a self-contained app

  4. Iot.Device.Bindings