---
title: 'Component and Deployment Diagrams'
source: 'https://academia.sh/en/courses/modeling-and-representation/component-and-deployment-diagrams'
course: 'Modeling and Representation'
language: en
updated: '2026-08-17T18:08:16+00:00'
license: 'CC BY-SA 4.0'
---

# Component and Deployment Diagrams

The same six classes fall into 203 distinct runtime partitions: a five-symbol component representation reduces 540 class distributions to 20, never states the internal-association load, and fixes the remote-link count only once deployment is written.

The previous two lessons measured the system as a static set of associations and
showed that the ambiguity drops to zero once thirty-one symbols are reached. A
reversible class representation was obtained — but that representation never says
**into how many separate parts** the six classes sit while running. All of them
could run in a single unit, or the six could run separately.

This lesson measures that gap. Classes are split into runtime units, the units are
placed onto nodes, and at each of the two steps, how many systems collapse onto the
same representation is counted. Which partition or which deployment should be
chosen is not this lesson's question; the architectural decision is the subject of
the Software Architecture curriculum and the System Design and Distributed Systems
curriculum. Only what the representations do and do not distinguish is counted
here.

## The Class Representation Does Not State the Split

A **component** is a runtime unit made up of classes that are deployed together and
move together. For the shared definition's six classes, a component arrangement is
a **partition** of the classes: every class sits in exactly one component.

- **SD14** — The class set follows from the shared definition's associations:
  `WorkOrder`, `Technician`, `LineItem`, `Part`, `Machine`, `Document`. The
  associations are the five between a1 and a5.
- **SD15** — In a partition, if an association's two ends are in different
  components, that association **crosses out** and shows up in the component
  representation; if they are in the same component, it never shows up.
- **SD16** — A single partition is followed throughout the lesson: `Intake` = work
  order and document, `Workshop` = technician and machine, `Stock` = line item and
  part.

```python
"""M01/K06: the shared definition's association set is split into runtime units. The
class representation never writes this split; every partition of the six classes is
produced by exhaustive count."""
CLASSES = ["WorkOrder", "Technician", "LineItem", "Part", "Machine", "Document"]
PAIRS = [("WorkOrder", "Technician"), ("WorkOrder", "LineItem"), ("LineItem", "Part"),
         ("Technician", "Machine"), ("WorkOrder", "Document")]      # shared definition's a1..a5
SELECTED = [["WorkOrder", "Document"], ["Technician", "Machine"], ["LineItem", "Part"]]


def partitions(items):
    """Every partition of a set: each class in exactly one component."""
    if not items:
        yield []
        return
    first, rest = items[0], items[1:]
    for p in partitions(rest):
        for i in range(len(p)):
            yield p[:i] + [[first] + p[i]] + p[i + 1:]
        yield [[first]] + p


def external_count(partition):
    location = {c: i for i, group in enumerate(partition) for c in group}
    return sum(1 for source, target in PAIRS if location[source] != location[target])


all_partitions = list(partitions(CLASSES))
print("classes", len(CLASSES), "| associations", len(PAIRS), "| distinct component partitions", len(all_partitions))
print("selected partition:", SELECTED, "-> external associations", external_count(SELECTED))
print()
distribution = {}
for p in all_partitions:
    key = (len(p), external_count(p))
    distribution[key] = distribution.get(key, 0) + 1
print("components  external  partitions in the same rough drawing")
for key in sorted(distribution):
    marker = "  <- selected" if key == (len(SELECTED), external_count(SELECTED)) else ""
    print(f"{key[0]:8d}{key[1]:12d}{distribution[key]:22d}{marker}")
print("total:", sum(distribution.values()))
print()
EXTENDED = CLASSES + ["Supplier"]
print("class count  distinct partitions")
for n in range(4, len(EXTENDED) + 1):
    print(f"{n:12d}{len(list(partitions(EXTENDED[:n]))):18d}")
```

```
classes 6 | associations 5 | distinct component partitions 203
selected partition: [['WorkOrder', 'Document'], ['Technician', 'Machine'], ['LineItem', 'Part']] -> external associations 2

components  external  partitions in the same rough drawing
       1           0                     1
       2           1                     5
       2           2                    10
       2           3                    10
       2           4                     5
       2           5                     1
       3           2                    10  <- selected
       3           3                    30
       3           4                    35
       3           5                    15
       4           3                    10
       4           4                    30
       4           5                    25
       5           4                     5
       5           5                    10
       6           5                     1
total: 203

class count  distinct partitions
           4                15
           5                52
           6               203
           7               877
```

The six classes fall into **203 distinct partitions**, and the reversible class
representation excludes none of them. In the previous lesson, the ambiguity dropped
to zero with 31 symbols; that zero held only for the **association fields**. The
split is a different axis, and on that axis, the class representation carries no
information at all.

The table shows that even the roughest component drawing carries a number. A
representation that draws three boxes and puts two links between them is
consistent with **10** of the 203 partitions: once the box count and the count of
associations crossing out are written, 203 systems drop to 10. The same table's
most crowded row is three components and four external associations: 35
partitions. The ends of the distribution are singular — there is one partition
with a single component and it sends no association out, and one partition with
six components, sending all five associations out.

The final table gives the scale of this axis. There are 15 partitions at four
classes, 52 at five, 203 at six, 877 at seven; adding a single class to the system
increases the partition count by more than fourfold. The class representation sees
none of this growth, because the new class only adds it a few symbols. In the
previous lesson, the representation grew linearly while the association ambiguity
grew exponentially; here, the split ambiguity grows even faster than
exponentially. As a system grows, **the share of what is not said about it**
grows, and this comes from the nature of the number, not from the quality of the
representation.

## What the Component Representation Takes

The rough drawing does not name the components. In a named representation, the
identity of the components is fixed, and what is to be measured is **which class
is in which component**.

- **SD17** — Three named components are fixed and none can be empty; so the count
  is over the **surjective** assignments of six classes onto three components.

```python
"""Named component representation: three fixed components, variable class distribution.
Internal associations do not show up in the representation; external ones do."""
from itertools import product
from math import log2

CLASSES = ["WorkOrder", "Technician", "LineItem", "Part", "Machine", "Document"]
PAIRS = [("WorkOrder", "Technician"), ("WorkOrder", "LineItem"), ("LineItem", "Part"),
         ("Technician", "Machine"), ("WorkOrder", "Document")]
COMPONENTS = ("Intake", "Workshop", "Stock")
SELECTED = {"WorkOrder": "Intake", "Document": "Intake", "Technician": "Workshop", "Machine": "Workshop",
            "LineItem": "Stock", "Part": "Stock"}


def external_structure(location):
    """The structure that shows up in the representation: how many associations between which pair of components."""
    counts = {}
    for source, target in PAIRS:
        if location[source] != location[target]:
            pair = tuple(sorted((location[source], location[target])))
            counts[pair] = counts.get(pair, 0) + 1
    return tuple(sorted(counts.items()))


def internal_load(location):
    counts = dict.fromkeys(COMPONENTS, 0)
    for source, target in PAIRS:
        if location[source] == location[target]:
            counts[location[source]] += 1
    return tuple(counts[c] for c in COMPONENTS)


surjective = [dict(zip(CLASSES, d)) for d in product(COMPONENTS, repeat=len(CLASSES))
              if len(set(d)) == len(COMPONENTS)]
goal = external_structure(SELECTED)
matching = [y for y in surjective if external_structure(y) == goal]
print("surjective assignments:", len(surjective), "| external associations of the selected distribution:",
      [(f"{a}-{b}", n) for (a, b), n in goal])
print()
print("representation level                  symbols  assignments  bits")
for label, symbols, count in (("0  boxes and their names", 3, len(surjective)),
                               ("1  + inter-component links", 5, len(matching)),
                               ("2  + each box's content", 11, 1)):
    print(f"  {label:36s}{symbols:3d}{count:12d}{log2(count):8.2f}")
print()
load = {}
for y in matching:
    load[internal_load(y)] = load.get(internal_load(y), 0) + 1
print("internal-association distribution of the", len(matching), "assignments collapsing onto the same component view")
print("  (Intake , Workshop , Stock)  assignments")
for key in sorted(load):
    print(f"  {str(key):26s}{load[key]:5d}")
```

```
surjective assignments: 540 | external associations of the selected distribution: [('Intake-Stock', 1), ('Intake-Workshop', 1)]

representation level                  symbols  assignments  bits
  0  boxes and their names              3         540    9.08
  1  + inter-component links            5          20    4.32
  2  + each box's content              11           1    0.00

internal-association distribution of the 20 assignments collapsing onto the same component view
  (Intake , Workshop , Stock)  assignments
  (0, 0, 3)                     2
  (0, 3, 0)                     2
  (1, 1, 1)                     2
  (2, 0, 1)                     4
  (2, 1, 0)                     4
  (3, 0, 0)                     6
```

Three numbers side by side. **System:** six classes, five associations, 203
partitions, and 540 surjective assignments for three named components.
**Representation:** five symbols — three boxes and two links. **Cost:** 20
assignments collapsing onto the same representation.

The steps of the ladder behave differently from the previous lesson's. The two
symbols that draw the links bring the bit count from 9.08 down to 4.32: 2.37 bits
per symbol. The six symbols that write the content take the remaining 4.32 bits:
0.72 bits per symbol. In the class representation, every symbol took exactly one
bit, because the hidden fields were independent binary choices. Here, the hidden
facts are not independent — placing one class in a component also constrains
which component the classes linked to it can be in. **Symbols that write
dependent facts do not carry equal bits.**

The lower table shows the component representation's most expensive silence. All
twenty assignments produce the same representation, but the three internal
associations that do not show up in the representation are distributed in **six
distinct ways** across these twenty assignments. At one end, all internal
associations pile up in a single component — `Intake` gets three, the others get
zero — and this happens in six assignments. At the other end, the load is split
evenly: one association per component, in two assignments. Someone looking at the
diagram sees three boxes and does not know how the load inside the boxes is
distributed; the difference between two assignments is as large as one component
carrying all the internal work versus carrying none of it. The weights of the six
distributions are not equal either: six of the twenty assignments — the table's
most crowded row — give all three internal associations to the `Intake`
component.

The reading that follows from this is: the component representation exists **to
hide internal structure**, and what it hides is not just detail, but **the
distribution of the load**. Hiding itself is not the flaw; the flaw is that it is
unmeasured.

## Deployment and Remote Links

When components are placed onto nodes, some associations cross the node boundary.
An association that crosses the boundary is counted in the shared definition as a
**remote link**; one that does not is local.

- **SD18** — Deployment is one symbol per component: which component is on which
  node. The two inter-component links read from the component representation,
  plus the three internal associations that do not show up in it, give a total of
  five associations; internal associations are, by definition, always local.

```python
"""Deployment: components are distributed onto nodes. An association becomes REMOTE
if its two ends are on different nodes. The component representation never states
this; the deployment representation states it with one symbol per component."""
from itertools import product

COMPONENTS = ("Intake", "Workshop", "Stock")
COMPONENT_LINKS = [("Intake", "Workshop"), ("Intake", "Stock")]   # read from the component representation
INTERNAL_LINKS = 3                                                 # never shows up, always local


def remote(deployment):
    return sum(1 for a, b in COMPONENT_LINKS if deployment[a] != deployment[b])


for nodes in (2, 3):
    distribution = {}
    for combo in product(range(nodes), repeat=len(COMPONENTS)):
        distribution.setdefault(remote(dict(zip(COMPONENTS, combo))), []).append(combo)
    print(f"{nodes} nodes -> {nodes ** len(COMPONENTS)} deployments, deployment representation"
          f" {len(COMPONENTS)} symbols")
    for r in sorted(distribution):
        print(f"   remote links {r}: {len(distribution[r]):2d} deployments"
              f" | local links {INTERNAL_LINKS + len(COMPONENT_LINKS) - r}")
```

```
2 nodes -> 8 deployments, deployment representation 3 symbols
   remote links 0:  2 deployments | local links 5
   remote links 1:  4 deployments | local links 4
   remote links 2:  2 deployments | local links 3
3 nodes -> 27 deployments, deployment representation 3 symbols
   remote links 0:  3 deployments | local links 5
   remote links 1: 12 deployments | local links 4
   remote links 2: 12 deployments | local links 3
```

The component representation alone does not state the remote-link count. In a
two-node setup there are eight deployments, and these eight deployments produce
three distinct remote-link values: 0, 1, or 2. Once the three-symbol deployment
representation is added, the value drops to a single number. This is why reading a
component representation and concluding "there are two links, so there must be two
remote calls" is wrong: both links can end up on the same node, in which case the
remote-link count is zero.

The second configuration raises the node count to three. The deployment
representation is still three symbols, but the number of possible deployments
rises from 8 to 27, and the distribution shifts: the share of deployments with
zero remote links drops from two-of-eight to three-of-twenty-seven, and the share
with two remote links rises from two-of-eight to twelve-of-twenty-seven. **The
same number of symbols says less with more nodes** — because the symbol count
depends on the component count, while the ambiguity depends on the node count.

The limit of what the deployment representation distinguishes is also read from
this. The representation states whether an association is remote; it does not
state **at what speed** or **with what reliability** it operates, because the
representation has no such field. The cost of a remote link is not counted in this
course; the only thing counted here is how many associations cross the boundary,
and how much the representation determines this.

One more field sits on the representation, and it was not counted in this lesson:
the **artifact**. The deployment representation does not place components onto
nodes, it places the packaged forms of components; a component can split into
more than one artifact, and an artifact can be copied onto more than one node.
This two-way flexibility says that the three-symbol deployment representation
used here rests on the assumption of **one node per component**. Once the
assumption is lifted, the deployment count grows and the representation stays the
same — so the measured numbers 8 and 27 are lower bounds under this assumption.

## Summary

- Six classes fall into 203 distinct component partitions, and the reversible
  class representation eliminates none of them: the split is an axis separate
  from the association fields.
- The roughest component drawing, made of three boxes and two links, is
  consistent with 10 of the 203 partitions; the same table's most crowded row is
  three components and four external associations, carrying 35 partitions.
- There are 540 surjective assignments for three named components. Three symbols
  (the boxes and their names) eliminate none of them, two link symbols bring it
  down to 20, six content symbols bring it down to a single assignment.
- Link symbols take 2.37 bits each, content symbols take 0.72 bits each; the
  result of exactly one bit per symbol from the class representation does not
  hold here, because the hidden facts are not independent of one another.
- The 20 assignments collapsing onto the same component representation distribute
  the three internal associations, invisible to the representation, in six
  distinct ways; at one end a single component carries all three, at the other
  end the load splits evenly across three.
- In a two-node setup, eight deployments give three distinct remote-link values
  (0, 1, 2); the three-symbol deployment representation fixes the value. When
  nodes rise to three, the symbol count does not change, but the deployment count
  rises to 27.

## Next Step

All three lessons in this topic measured the system's **static** side: which type
is linked to what, how many, in which unit, on which node. All three shared the
same limit — none of them states what the system **does over time**. The
thirty-one-symbol class representation was reversible for the association fields,
but in what order a work order is processed, which states it passes through, and
which sequences never happen at all are not written on that representation.

The next topic takes up behavioral representations and builds the measure the
same way. The first lesson takes up the use case representation: it measures how
a representation that shows three of the system's external facts and
**deliberately** drops seven internal facts cannot be distinguished from one that
drops them **inadvertently**, unless that deliberate omission is put into a
number.
