---
title: Peer-to-Peer
source: 'https://academia.sh/en/courses/architectural-styles/peer-to-peer'
course: 'Architectural Styles'
language: en
updated: '2026-08-23T07:01:10+00:00'
license: 'CC BY-SA 4.0'
---

# Peer-to-Peer

Measuring the decentralized arrangement where the initiator and waiter roles merge into a single unit: the neighbor count six transfer hubs must know across three neighborhood arrangements, the round and message count required for a tariff update to reach everyone, the learner count when a unit is withdrawn, and the count of neighbor lists edited when a new unit is added.

In the previous arrangement, asymmetry was deliberate: one side always initiated, the other
always waited, and the waiting side was singular. This singularity has a counterpart in the
delivery operations context — all fee questions going to a single place means that place is known
by everyone. In an arrangement where multiple transfer hubs carry out the same work, the question
reverses. If every hub is both asker and answerer, the obligation to know spreads out instead of
piling up in one direction.

**Peer-to-peer** is the software-architecture-level signature of this style: role separation
falls away, and the two roles merge into the same module. The Client–Server Model lesson in the
Computer Networks curriculum said that this arrangement gains capacity as demand grows, but that
the client–server model was chosen for the web because it requires a stable address and
centralized control. Address, connection, and the network layer belong there. The question here
is three counts: the number of neighbors a unit must know, the number of messages required for a
tariff update to reach every unit, the number of units that keep working when one unit is
withdrawn.

The quality attribute it connects to is reliability, and the quality question is: when one
transfer hub goes out of service, how many hubs fail to learn the new tariff.

## The Module That Carries Two Roles

There are six transfer hubs, and the same file runs for all of them. Two things sit inside the
module at once: `handleRequest`, which serves incoming requests, and `broadcast`, which sends
requests to neighbors. The previous lesson's two files — client and server — are merged into a
single file here.

```sh
mkdir -p es
```

```js
// es/es.mjs — the single module running for each transfer hub: both receiving and initiating
export function peer(name) {
  const neighbors = [];
  let version = "V1", received = 0, sent = 0;
  return {
    name,
    get version() { return version; },
    introduce: (neighbor) => neighbors.push(neighbor),
    counters: () => ({ received, sent, neighbor: neighbors.length }),
    handleRequest(request) {
      received += 1;
      if (request.operation !== "tariff-announce") return { state: "unknown-operation" };
      if (request.version === version) return { state: "known" };
      version = request.version;
      return { state: "new" };
    },
    broadcast() {
      for (const neighbor of neighbors) {
        sent += 1;
        neighbor.handleRequest({ operation: "tariff-announce", version });
      }
    },
  };
}
```

A peer's only knowledge is its neighbors. How many neighbors it knows is supplied from the
outside; the measurable face of the style is right there.

## Three Neighborhood Arrangements

The same six units are connected in three separate ways. In the star arrangement, one hub knows
everyone and everyone knows only the hub; this is the previous lesson's arrangement written out
as a neighborhood. In the ring arrangement, each unit knows two neighbors. In the full mesh, each
unit knows the other five.

The update enters from the same place in all three arrangements: the new tariff version is
announced to the ANK hub. Then it propagates round by round — in each round, every unit that
knows the version announces it to its neighbors.

```js
// es/ag.mjs — the same units in three neighborhood arrangements, and one update's propagation run
import { peer } from "./es.mjs";

export const NAMES = ["IST", "ANK", "IZM", "BUR", "ADA", "TRA"];
export const ENTRY = "ANK";

export const star = (names) => new Map(names.map((a, i) => [a, i === 0 ? names.slice(1) : [names[0]]]));
export const ring = (names) =>
  new Map(names.map((a, i) => [a, [names[(i + 1) % names.length], names[(i - 1 + names.length) % names.length]]]));
export const fullMesh = (names) => new Map(names.map((a) => [a, names.filter((b) => b !== a)]));
export const ARRANGEMENTS = [["star", star], ["ring", ring], ["full mesh", fullMesh]];

export function propagate(map) {
  const peers = new Map([...map.keys()].map((a) => [a, peer(a)]));
  for (const [a, neighbors] of map) for (const n of neighbors) peers.get(a).introduce(peers.get(n));
  peers.get(ENTRY).handleRequest({ operation: "tariff-announce", version: "V2" });
  const all = [...peers.values()];
  let round = 0;
  while (all.some((e) => e.version !== "V2") && round < 20) {
    round += 1;
    for (const e of all.filter((e) => e.version === "V2")) e.broadcast();
  }
  return {
    round,
    message: all.reduce((t, e) => t + e.counters().sent, 0),
    neighbor: all.reduce((t, e) => t + e.counters().neighbor, 0),
    learner: all.filter((e) => e.version === "V2").length,
    unit: all.length,
  };
}
```

```js
// propagation.mjs — known neighbor count, propagation round, and message count in three arrangements
import { NAMES, ARRANGEMENTS, propagate } from "./es/ag.mjs";

console.log("arrangement  units  known neighbor  round  message  learner");
for (const [label, build] of ARRANGEMENTS) {
  const s = propagate(build(NAMES));
  console.log(
    `${label.padEnd(10)}${String(s.unit).padStart(4)}${String(s.neighbor).padStart(13)}` +
      `${String(s.round).padStart(6)}${String(s.message).padStart(7)}${`${s.learner}/${s.unit}`.padStart(9)}`,
  );
}
```

```sh
node propagation.mjs
```

```
arrangement  units  known neighbor  round  message  learner
star         6           10     2      7      6/6
ring         6           12     3     18      6/6
full mesh    6           30     1      5      6/6
```

Propagation here is a model, not a network measurement: what is counted is the message and round
count, no duration is measured.

## Reading the Numbers

All three arrangements reached six out of six units, so they do the same job. The differences
collect in three columns.

The known-neighbor count is the total of the obligation to know. In the star it is 10: the hub
knows five, and five units know one hub each. In the ring it is 12, in the full mesh 30. The full
mesh is the most expensive arrangement to know, and this grows with the square of the unit count.

The message and round counts move in opposite directions. The full mesh finished with the fewest
messages (5) and in a single round, because the entry unit knew everyone. The ring spent the most
messages (18) and took three rounds, because the news has to hop from neighbor to neighbor; in
every round, every unit that knows the news announces it again, and most of those announcements
carry an already-known version. The star landed between the two: 7 messages, 2 rounds — the news
had to reach the hub in the first round and spread out from the hub in the second.

These three columns form a trade-off table: reducing the obligation to know increases the message
count.

## When a Unit Is Withdrawn

The measurement for the reliability question is this experiment: every unit except the entry unit
is removed from the network in turn, propagation is run from the start, and the count of how many
of the remaining units learned the new version is taken. The same script also calculates how many
neighbor lists must be edited when a seventh hub is added to the network.

```js
// resilience.mjs — learner count when a unit is withdrawn, the cost of adding a unit, and the two roles in one file
import { readFileSync } from "node:fs";
import { NAMES, ENTRY, ARRANGEMENTS, propagate } from "./es/ag.mjs";

const withdraw = (map, name) =>
  new Map([...map].filter(([a]) => a !== name).map(([a, n]) => [a, n.filter((x) => x !== name)]));

console.log("arrangement  worst learner  critical unit  lists edited for new unit");
for (const [label, build] of ARRANGEMENTS) {
  const full = build(NAMES);
  let critical = 0, worst = [Infinity, 0];
  for (const name of NAMES.filter((a) => a !== ENTRY)) {
    const s = propagate(withdraw(full, name));
    if (s.learner < s.unit) critical += 1;
    if (s.learner < worst[0]) worst = [s.learner, s.unit];
  }
  const bigger = build([...NAMES, "OLD"]);
  const changed = NAMES.filter((a) => full.get(a).join(",") !== bigger.get(a).join(",")).length;
  console.log(
    `${label.padEnd(9)}${`${worst[0]}/${worst[1]}`.padStart(16)}${String(critical).padStart(14)}` +
      `${String(changed).padStart(34)}`,
  );
}

const source = readFileSync("es/es.mjs", "utf8");
const receiver = (source.match(/handleRequest\(request\)/g) ?? []).length;
const initiator = (source.match(/\.handleRequest\(\{/g) ?? []).length;
console.log(`es/es.mjs: receiving definitions = ${receiver}, initiating calls = ${initiator}`);
```

```sh
node resilience.mjs
```

```
arrangement  worst learner  critical unit  lists edited for new unit
star                  1/5             1                                 1
ring                  5/5             0                                 2
full mesh             5/5             0                                 6
es/es.mjs: receiving definitions = 1, initiating calls = 1
```

In the star, the critical-unit count is 1, and that unit is the hub: when it is withdrawn, only
one of the remaining five units — the one the news enters through — knows the version, and four
fail to learn it. In the ring and the full mesh, the critical-unit count is 0; whichever unit is
withdrawn, all five of the remaining five learn it. This is the count of decentralization. The
word is not a description of structure but a measurement result: the number of units that stop
others' work when withdrawn is zero.

The last column is the other half of the cost. When the seventh hub is added, one list — the
hub's list — is edited in the star. In the ring, two lists are edited; in the full mesh, six. The
full mesh's gain of finishing in a single round is paid for by updating every existing unit's
knowledge with each new unit.

The last line gives the style's code signature. There is one receiving definition and one
initiating call in a single file. The previous lesson's two separate files collapsed into one
file here, and this is the reason for the measurement: because the direction of initiation does
not pile up in a single direction, removing one unit from the network does not stop the rest from
talking to each other.

## Summary

- The code signature of the peer-to-peer style is the removal of role separation: the receiving
  definition and the initiating call sit in the same module, and a single file runs for six
  units.
- The known-neighbor count came out to 10 in the star, 12 in the ring, and 30 in the full mesh;
  the obligation to know grows with the square of the unit count in the full mesh.
- The message and round counts moved in opposite directions: the full mesh spent 5 messages and
  1 round, the star 7 messages and 2 rounds, the ring 18 messages and 3 rounds; all three reached
  six out of six units.
- The critical-unit count is 1 in the star, 0 in the ring and the full mesh; when the hub was
  withdrawn, four of the remaining five units failed to learn the new tariff.
- The cost of decentralization is the price of adding a new unit: for the seventh hub, 1 neighbor
  list was edited in the star, 2 in the ring, and 6 in the full mesh.

## Next Step

The two styles covered so far both addressed interaction between units of the same kind: both
sides of the conversation were units that calculated a fee or announced a tariff, and the only
difference was who initiated. The library also has a boundary of a different kind. The clerk at
the operations desk does not see how the fee was calculated, only what appears on the screen: the
zone name, the tier, the discount rationale, and the amount due must be arranged in a specific
form. The two sides of this boundary are not equal — one carries the rule and the data, the other
displays it. The next lesson compares three separate distributions of this responsibility: how
many of the field names the displaying file recognizes, how many separate places formatting is
done in, and how many files a display change touches.
