Lesson 03 / 10
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.
Contents
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.
"""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.
"""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.
"""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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.