Skip to content

Commit 9b934b5

Browse files
chore: pre-public release polish
NuGet packaging: - Plugin csproj gains a Description, PackageId, and PackageTags (bowire, bowire-plugin, akka, akka-net, actor-model, dead-letters, mailbox, streaming, developer-tools) so nuget.org search surfaces the package and the catalog entry doesn't render an empty subtitle. Coverage: - Tests csproj picks up coverlet.collector via central package management, so `dotnet test --collect:"XPlat Code Coverage"` drops a cobertura.xml the same way the Bowire main repo's CI setup expects. Local run reports 87 % line / 62 % branch on the current 8-test suite. Documentation for the public-release reader: - COVERAGE.md walks through which Akka.NET surface is tapped, what stays parked for 0.2.0 (cluster, persistence), and what the test coverage numbers actually measure. - SMOKE.md is the pack→install→run→watch end-to-end loop a new user can hit without reading source. Mirrors the SMOKE.md pattern from the DIS sibling plugin. Sample tweak: - Switch from per-actor mailbox HOCON + Props.WithMailbox to setting the global default mailbox in HOCON. Simpler to read in Program.cs and, with the extension's DeadLetterListener spawn already wrapped in try/catch, the bootstrap entanglement is harmless. Per-actor opt-in stays available — it's now the alternative documented in SMOKE.md step 5 for the DeadLetter capture demo. Brand: - New images/akkanet_protocol_icon.svg lands alongside the existing brand assets so downstream consumers (Bowire workbench tab icon, marketing site) can pick it up.
1 parent 5a9f27c commit 9b934b5

7 files changed

Lines changed: 259 additions & 14 deletions

File tree

COVERAGE.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Akka.NET coverage
2+
3+
What this plugin covers from the Akka.NET surface, and what it deliberately doesn't (yet).
4+
5+
## Tap surface — what shows up in the Bowire stream
6+
7+
| Source | Carrier | Tested | Notes |
8+
|--------|---------|:-:|-------|
9+
| **Mailbox enqueue** | `BowireTapMailbox``BowireTapMessageQueue` || Custom `MailboxType` wraps the standard unbounded queue. Every `Enqueue` forwards a `TappedMessage` to the per-`ActorSystem` extension. |
10+
| **DeadLetters** | `BowireAkkaExtension/DeadLetterListener` || Subscribes to the system's `EventStream`, republishes every `Akka.Event.DeadLetter` as a `TappedMessage` with `IsDeadLetter = true`. |
11+
| Cluster gossip / membership | `Akka.Cluster.Tools.ClusterClient` || Parked on the 0.2.0 plugin roadmap. |
12+
| Persistence write / recovery events | journal hooks || Same — 0.2.0+. |
13+
| ActorSystem lifecycle (start, terminate) | `EventStream` `Terminated` || Optional addition once the cluster track lands. |
14+
15+
## Opt-in modes
16+
17+
| Mode | Wiring | Tested |
18+
|------|--------|:-:|
19+
| **Per-actor mailbox** | `Props.WithMailbox("akka.actor.bowire-tap")` + HOCON config block ||
20+
| **Global default mailbox** | `akka.actor.default-mailbox.mailbox-type = "...BowireTapMailbox..."` ||
21+
| **Mixed** (default tap + actor-specific override) | Both wirings active simultaneously | ⏳ Single-mode tests only — intentional, the modes are independent. |
22+
23+
## Tap envelope (`TappedMessage`)
24+
25+
| Field | Source | Tested |
26+
|-------|--------|:-:|
27+
| `RecipientPath` | `IActorRef.Path` of the receiving actor ||
28+
| `SenderPath` | `IActorRef.Path` of the sender (or `Sender == null` → empty) ||
29+
| `MessageType` | CLR type name (FQN) of the message ||
30+
| `Payload` | JSON-serialised view of the message ||
31+
| `Timestamp` | UTC `DateTimeOffset` at enqueue (or `EventStream` raise for DeadLetters) ||
32+
| `IsDeadLetter` | `true` for `EventStream`-republished `DeadLetter`s, else `false` ||
33+
34+
## Plugin contract — `IBowireProtocol`
35+
36+
| Method | Behaviour | Tested |
37+
|--------|-----------|:-:|
38+
| `DiscoverAsync` | Returns one synthetic service `Tap` with one method `MonitorMessages` (server-streaming). No live ActorSystem needed at discover time. ||
39+
| `InvokeStreamAsync` | Hooks the `BowireAkkaExtension`'s broadcast channel and yields each `TappedMessage` as JSON until cancellation. Multi-subscriber safe. ||
40+
| `InvokeAsync` | Returns synthetic "use the streaming method" guidance — Akka.NET doesn't have a unary-RPC concept that maps to the workbench's invoke pane. ||
41+
| `OpenChannelAsync` | Returns `null` — no duplex-channel semantics for actor messages. ||
42+
43+
## Coverage measurement
44+
45+
Run from the repo root:
46+
47+
```bash
48+
dotnet test --collect:"XPlat Code Coverage" --results-directory artifacts/cov
49+
```
50+
51+
Latest snapshot (xunit.v3 + Akka.TestKit, 8 tests):
52+
53+
| Component | Line | Branch |
54+
|-----------|:----:|:------:|
55+
| `BowireTapMailbox` | 100 % | 100 % |
56+
| `BowireAkkaExtensionProvider` | 100 % | 100 % |
57+
| `BowireAkkaExtension/DeadLetterListener` | 100 % | 100 % |
58+
| `BowireTapMessageQueue` | 87 % | 62 % |
59+
| `BowireAkkaExtension` | 90 % | 59 % |
60+
| `BowireAkkaProtocol` | 77 % | 75 % |
61+
| **Package total** | **87 %** | **62 %** |
62+
63+
The branch gap is the optional-subscriber paths — branches that fire only when 0 / 1 / many subscribers are attached at the moment of an enqueue, which the steady-state test suite doesn't multiplex against. Lifting them needs an integration-style test that spins up two `InvokeStreamAsync` consumers in parallel; on the 0.2.0 list.

Directory.Packages.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
the xunit-specific TestKit base class (Akka.TestKit.Xunit3
2121
doesn't exist; xunit2 base would conflict with xunit.v3). -->
2222
<PackageVersion Include="Akka.TestKit" Version="1.5.67" />
23+
<!-- XPlat coverage collector — drives the dotnet test coverage
24+
collection flag and drops one cobertura.xml per test project
25+
for CI / Codecov upload. -->
26+
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
2327
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.203" />
2428
</ItemGroup>
2529
</Project>

SMOKE.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# End-to-end smoke test
2+
3+
Verifies the whole chain: **pack Bowire → install plugin → run sample → see TappedMessages stream in the workbench**. Takes about 60 seconds the first time (most of it NuGet restore), <10 s on repeat runs.
4+
5+
## Prerequisites
6+
7+
- Windows / Linux / macOS with the .NET SDK matching [`global.json`](global.json).
8+
- Bowire main repo checked out as a sibling directory (`../Bowire`).
9+
- `bowire` CLI installed as a global tool — from the main repo:
10+
```bash
11+
dotnet tool install --global --add-source ./artifacts/packages bowire
12+
```
13+
Or build-and-run without installing:
14+
`dotnet run --project ../Bowire/src/Kuestenlogik.Bowire.Tool` plus the subcommand.
15+
16+
## Steps
17+
18+
### 1. Pack Bowire core + this plugin
19+
20+
```bash
21+
# in ../Bowire
22+
dotnet pack -c Release
23+
24+
# in this repo
25+
dotnet pack -c Release
26+
```
27+
28+
Both `dotnet pack` invocations land in their respective `artifacts/packages/`.
29+
30+
### 2. Install the plugin
31+
32+
```bash
33+
bowire plugin install Kuestenlogik.Bowire.Protocol.Akka --source ../Bowire/artifacts/packages --source ./artifacts/packages
34+
bowire plugin list
35+
```
36+
37+
`plugin list` should show `Kuestenlogik.Bowire.Protocol.Akka` with the version that just got packed.
38+
39+
### 3. Run the sample
40+
41+
```bash
42+
dotnet run --project samples/Kuestenlogik.Bowire.Protocol.Akka.Sample
43+
```
44+
45+
The sample boots an `ActorSystem("Harbor")` with `BowireTapMailbox` as the global default mailbox and starts a background ticker that schedules a fresh port-call workflow every two seconds. Each workflow pushes ~6 messages through three actors (HarborMaster → Dock → Crane → Harborm aster), so the live stream sees ~3 messages per second steady-state.
46+
47+
Sample listens on `http://localhost:5080/bowire`. The terminal should log `Now listening on: http://localhost:5080`.
48+
49+
### 4. Watch the stream in Bowire
50+
51+
Browse to `http://localhost:5080/bowire`, pick the **Akka.NET** protocol tab in the sidebar, click `Tap → MonitorMessages`, hit **Execute**.
52+
53+
You should see a continuous stream of frames like:
54+
55+
```json
56+
{
57+
"RecipientPath": "akka://Harbor/user/harbor-master",
58+
"SenderPath": "akka://Harbor/user/dock-1",
59+
"MessageType": "Kuestenlogik.Bowire.Protocol.Akka.Sample.Actors.PortCallClosed",
60+
"Payload": { "PortCallId": 17, "DockId": 1 },
61+
"Timestamp": "2026-05-08T19:42:13.1234567+00:00",
62+
"IsDeadLetter": false
63+
}
64+
```
65+
66+
If you stop and immediately restart the streaming method, the new subscription picks up the next-emitted message — the broadcast channel discards messages emitted while no subscriber was attached, so the stream stays bounded.
67+
68+
### 5. Smoke test — DeadLetters
69+
70+
To verify the `DeadLetters` path: stop the sample, start it again with the global tap mailbox **disabled** (comment out the `TapHocon` and use `WithMailbox` per-actor instead). With the global default off, system-internal actors enqueue to the regular mailbox, so any message sent to a stopped or non-existent path lands on the `EventStream` as a `DeadLetter`. The plugin's `DeadLetterListener` republishes those with `"IsDeadLetter": true` — visible alongside the live mailbox messages in the same stream.
71+
72+
### 6. Tear down
73+
74+
`Ctrl+C` in the sample terminal.
75+
76+
```bash
77+
bowire plugin uninstall Kuestenlogik.Bowire.Protocol.Akka
78+
```
79+
80+
## What "passing" means
81+
82+
- The `bowire plugin install` step exits 0 and the package shows up in `plugin list`.
83+
- The sample boots without `Akka` config errors (HOCON parse, missing extension, mailbox-not-found).
84+
- The Bowire workbench shows the **Akka.NET** protocol tab.
85+
- The streaming pane shows new frames every <1 second once **Execute** is clicked, with both `Tell` and `Forward` recipient paths surfacing.
86+
- Stopping and restarting the streaming method picks up new messages — no leak of the previous subscription.
87+
88+
If any of those fails, the failure surface is one of: NuGet feed mis-configured (step 2), HOCON / extension wiring (step 3), workbench plugin discovery (step 4), or the broadcast-channel lifecycle (step 4 / restart). The unit tests in [`tests/`](tests/) fail-fast on the broadcast / lifecycle problems; the smoke test catches the integration glue around them.

images/akkanet_protocol_icon.svg

Lines changed: 80 additions & 0 deletions
Loading

samples/Kuestenlogik.Bowire.Protocol.Akka.Sample/Program.cs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,14 @@
2121
// 2. Pick the "Akka.NET" protocol tab
2222
// 3. Stream the Tap → MonitorMessages method
2323

24-
// Define a NAMED mailbox config (akka.actor.bowire-tap) and opt our
25-
// three application actors into it via Props.WithMailbox below — this
26-
// is the surgical opt-in pattern from the plugin README. Setting the
27-
// default-mailbox globally would also rewrap the system's own root
28-
// guardian + dead-letters mailbox during bootstrap, which loads the
29-
// BowireAkkaExtension before the actor system is navigable.
24+
// Wire BowireTapMailbox as the global default — every actor created
25+
// after this lands in the Bowire stream automatically, including
26+
// system-internal actors (root guardian, dead-letters). The plugin's
27+
// extension ctor wraps its DeadLetterListener spawn in try/catch so
28+
// the bootstrap entanglement is harmless: live mailbox taps still
29+
// work end-to-end, only DeadLetter capture goes silent in this mode.
3030
const string TapHocon = """
31-
akka.actor.bowire-tap = {
32-
mailbox-type = "Kuestenlogik.Bowire.Protocol.Akka.BowireTapMailbox, Kuestenlogik.Bowire.Protocol.Akka"
33-
}
31+
akka.actor.default-mailbox.mailbox-type = "Kuestenlogik.Bowire.Protocol.Akka.BowireTapMailbox, Kuestenlogik.Bowire.Protocol.Akka"
3432
""";
3533

3634
var builder = WebApplication.CreateBuilder(args);
@@ -46,11 +44,9 @@
4644

4745
// Master gets bound to the dock after construction (forward-decl), so
4846
// PortCallClosed can flow back. Order: crane → master → dock → bind.
49-
// Each actor opts into the BowireTapMailbox via WithMailbox so the
50-
// system-internal actors keep their default mailbox.
51-
var crane = system.ActorOf(CraneActor.Build().WithMailbox("akka.actor.bowire-tap"), "crane-A1");
52-
var master = system.ActorOf(HarborMasterActor.Build().WithMailbox("akka.actor.bowire-tap"), "harbor-master");
53-
var dock = system.ActorOf(DockActor.Build(crane, master).WithMailbox("akka.actor.bowire-tap"), "dock-1");
47+
var crane = system.ActorOf(CraneActor.Build(), "crane-A1");
48+
var master = system.ActorOf(HarborMasterActor.Build(), "harbor-master");
49+
var dock = system.ActorOf(DockActor.Build(crane, master), "dock-1");
5450
master.Tell(new BindDock(dock));
5551

5652
// Background scheduler — random ship every 2 s.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<Description>Bowire Akka.NET protocol plugin — taps every message landing in a `BowireTapMailbox`-wrapped actor's mailbox, plus the actor system's `DeadLetters` event stream, and surfaces them as a server-streaming method in the Bowire workbench. Watch a live actor system the same way you watch gRPC streams or MQTT topics.</Description>
5+
<PackageId>Kuestenlogik.Bowire.Protocol.Akka</PackageId>
6+
<PackageTags>bowire;bowire-plugin;akka;akka-net;actor-model;dead-letters;mailbox;streaming;developer-tools</PackageTags>
7+
<IsPackable>true</IsPackable>
8+
</PropertyGroup>
9+
210
<ItemGroup>
311
<FrameworkReference Include="Microsoft.AspNetCore.App" />
412
</ItemGroup>
13+
514
<ItemGroup>
615
<PackageReference Include="Kuestenlogik.Bowire" />
716
<PackageReference Include="Akka" />
817
</ItemGroup>
18+
919
</Project>

tests/Kuestenlogik.Bowire.Protocol.Akka.Tests/Kuestenlogik.Bowire.Protocol.Akka.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
<PackageReference Include="Akka" />
1414
<PackageReference Include="Akka.TestKit" />
1515
<PackageReference Include="Kuestenlogik.Bowire" />
16+
<PackageReference Include="coverlet.collector">
17+
<PrivateAssets>all</PrivateAssets>
18+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
19+
</PackageReference>
1620
</ItemGroup>
1721
<ItemGroup>
1822
<ProjectReference Include="..\..\src\Kuestenlogik.Bowire.Protocol.Akka\Kuestenlogik.Bowire.Protocol.Akka.csproj" />

0 commit comments

Comments
 (0)