Chapter 9 · Part VI · Capstone

Capstone: Nex Radar

Nothing in this chapter is new. Every piece was already built, tested, and proven in an earlier chapter. What this project adds is composition — and composition, it turns out, tests something no individual project's own check suite ever could.

"Nex Radar" watches a directory for duplicate files, live: Chapter 5's Dup_Finder does the scanning, an HTTP API in Chapter 7's style reports the results, a background task counts elapsed ticks the way Chapter 6's hub task ran continuously, and a Chapter 8-style Swing dashboard polls it all from a separate window, refreshing live. Every one of those pieces already has its own chapter, its own design, and its own passing check suite. This chapter is about what happens when they meet.

Problem

The brief is reuse without coupling: wire five independently-built pieces together through intern, with none of them changing to accommodate any of the others, and none of them aware the others exist. dup_finder.nex is copied into this project unmodified — the same file, the same class, doing exactly what Chapter 5 already proved it does. The service adds a background counter and an HTTP surface around it; the dashboard adds a window around that. If the pieces were actually as independent as this book has argued they should be, plugging them together should be the easy part.

It was not, quite — plugging five independently-correct pieces together over a real process boundary turned out to be its own test, one none of their individual check suites had run. The reason why is the actual lesson of this chapter, saved for the takeaways below rather than smoothed over here.

Design as Contract

Radar_Metrics is the smallest possible piece of new state this project needed — nothing Dup_Finder or Http_Server already provided, just the three numbers a status dashboard actually wants to show:

class Radar_Metrics
create
  make() do
    ticks := 0
    duplicate_groups := 0
    scan_count := 0
  end
feature
  ticks: Integer
  duplicate_groups: Integer
  scan_count: Integer

  tick() do ticks := ticks + 1 end
  record_scan(groups: Integer) do
    duplicate_groups := groups
    scan_count := scan_count + 1
  end
end

Radar_Service's constructor is where composition actually happens — three interned pieces, wired together in a dozen lines, each one exactly as capable as it was in its own chapter and no more:

class Radar_Service
create
  make(port: Integer, scan_root: String) do
    this.server := create Http_Server.make(port)
    this.metrics := create Radar_Metrics.make()
    this.finder := create Dup_Finder.make()
    ...
    run_scan()

    server.get("/status", fn(req: Http_Request): Http_Server_Response do
      result := this.status_payload()
      end)
    server.post("/rescan", fn(req: Http_Request): Http_Server_Response do
      this.run_scan()
      result := this.status_payload()
      end)
  end

Dup_Finder has no idea it is running inside a service; Http_Server has no idea what it is serving status for. The only new contract this project adds is the shape of what /status reports, and it is the same design as Chapter 7's json_response, reused rather than reinvented: a status code, a Map, one method that turns both into an HTTP response with the right content type.

Build

Source

The complete, current code for this project is at examples/contracts_at_work/09_radar on GitHub.

The background tick counter runs the same way Chapter 6's hub task did — spawned once, at startup, running until the process itself ends, calling a method on this directly from inside the spawned closure:

start() do
  server.start()
  let ticker: Task := spawn do
    this.tick_loop()
  end
end

tick_loop() do
  let running: Boolean := true
  from running := true until not running do
    sleep(1000)
    metrics.tick()
  end
end

The dashboard side is Chapter 8's pattern, applied without a single structural change: Radar_Dashboard_State holds only plain Nex fields, three listener classes each set exactly one flag on it, and the poll loop is the only code that ever touches a widget:

from
until
  state.should_quit
do
  Thread.sleep(150)
  if state.should_rescan then
    state.set_status(fetch_rescan(client, base, ticks_rx, groups_rx, scans_rx))
    state.clear_rescan()
  elseif state.should_refresh then
    state.set_status(fetch_status(client, base, ticks_rx, groups_rx, scans_rx))
    state.clear_refresh()
  end
  label.setText(state.status_text)
end

The one addition Chapter 8's pattern did not need to make: the poll loop now performs a real network call, not just a local state read. fetch_status and fetch_rescan use Http_Client and a fixed-lookbehind text/Regex to pull three numbers out of the response body — the identical technique Chapter 7's calc_client.nex used, for the identical reason: both are functions, and interning Http_Client alongside data/Json still breaks a parsed Map's method calls specifically inside a function body.

Test

The check suite builds a small directory with two duplicate files, starts the service against it on an ephemeral port, and drives it with a real Http_Client — confirming not just that the pieces run together, but that a change on disk actually reaches the HTTP response through the whole composed chain:

let r1: Http_Response := client.get(base + "/status")
c.check("initial duplicate groups", "1", "" + groups_rx.find(r1.body()))
c.check("initial scan count", "1", "" + scans_rx.find(r1.body()))

-- add another duplicate group, then rescan
root.child_path("d.txt").write_text("second duplicate")
root.child_path("e.txt").write_text("second duplicate")

let r2: Http_Response := client.post(base + "/rescan", "")
c.check("rescan finds two groups", "2", "" + groups_rx.find(r2.body()))
c.check("rescan count incremented", "2", "" + scans_rx.find(r2.body()))

Beyond the automated suite, this project was smoke-tested the way its own design demands — live, across two real processes, over a real network boundary. radar_service_main.nex run in one terminal, radar_dashboard_main.nex in another, its background tick counter visibly climbing as it polls, its refresh and rescan buttons round-tripping real HTTP requests, scan_count confirmed incrementing from 1 to 2 by hitting /rescan directly with curl in parallel with the dashboard's own view of the same number. A capstone's real test is watching the whole thing run, not just its check suite passing.

Takeaways

Keep a monitor and the thing it watches in separate processes, talking over a real boundary, and you get something closer to how monitoring actually works in practice than a monitor sharing a process with its subject — worth choosing on its own architectural merits, not only when something forces your hand into it. Reusing a pattern that already proved itself elsewhere in this book, rather than reaching for a fresh solution to a problem already solved once, is its own kind of design discipline — consistency a reader (or a future maintainer) can rely on.

Composing independently-correct pieces is not automatic just because each piece's own check suite passes. Proving something correct in isolation and proving it correct as part of a whole system are two different tests, and only integration testing — wiring everything together, over a real process boundary, and watching what actually happens — runs the second one. Unit-level work, however thorough, structurally cannot.