Lesson 17 / 19
Distributed Configuration
A central configuration value changes outside the application, but who sees it depends on the scope of the component — a singleton component carries the value at the moment it was constructed forever, a component rebuilt on every request sees the change at its next construction. The limiting measurement measures the course's quietest defect — no error occurs at all, two components that exist at the same moment answer the same key with two different values, and the difference is visible only when they are placed side by side.
Published
In this lesson
- Shows, by running it, that a singleton component never sees a configuration value that changes outside it, while a component rebuilt on every request does see it
- Measures that a refresh never happens on its own and requires an explicit call
- Shows that two components existing at the same moment can give two different, both error-free, answers to the same key
Contents
The previous lesson measured how a component’s internal state leaks to the outside — which field was marked, which was not. This lesson asks the reverse question: how does a value from the outside get in, and once it gets in, which component sees it when that value changes. Central configuration is modeled — values are kept outside the application, not in any single component’s code — and what is measured is which component sees an outside change, and when. The concept of central configuration and an external configuration store were built in the Infrastructure as Code and the Platform Engineering courses and are not repeated here; this lesson measures only when a given value reaches which component. The limiting measurement carries the course’s quietest defect: no error occurs at all, two components at the same moment each mistakenly believe two different values are correct.
CentralSettings is modeled in this lesson as a single Java class — in a real distributed
system this store lives in a separate process, even on a separate machine, and components reach
it over the network. There is no real network call here; what is modeled is not the network
itself but the timing problem the network creates — a value changes in one place, and that
change reaching another place never takes zero time. A static Java field does not imitate this
delay, but it carries, exactly, the structure of “a single value kept outside is read by
multiple components at different moments” — and that structure is exactly what is measured.
A Value That Changes Outside
CentralSettings is a key-value store kept outside, belonging to no component of the
application at all. Whenever a component reads this value, it sees whatever is currently in the
store — but whether this reading happens only once or every single time depends on the
component’s scope. The concept of scope was built in the Core Concepts topic: a singleton
component is constructed once and that same instance is reused continually, while a per-request
component is rebuilt for every request.
// CentralSettings.java — key-value store kept outside the application, changeable from the outside (helper source, no main)
import java.util.*;
class CentralSettings {
static final Map<String, String> values = new HashMap<>();
static { values.put("mode", "normal"); }
static String get(String key) { return values.get(key); }
static void update(String key, String newValue) { values.put(key, newValue); }
}
// SingletonComponent.java — a singleton component that reads the value only at the moment it is constructed and keeps it in a field (helper source, no main)
class SingletonComponent {
final String modeValue;
SingletonComponent() { modeValue = CentralSettings.get("mode"); }
String mode() { return modeValue; }
}
// PerRequestComponent.java — rebuilt on every request, so it reads the value again every single time (helper source, no main)
class PerRequestComponent {
final String modeValue;
PerRequestComponent() { modeValue = CentralSettings.get("mode"); }
String mode() { return modeValue; }
}
// Measurement.java — result-printing helper (helper source, no main)
class Measurement {
static void show(String label, Object value) {
System.out.printf("%-52s: %s%n", label, value);
}
}
SingletonComponent and PerRequestComponent’s constructors carry the exact same line:
CentralSettings.get("mode"). The difference between them is not in the code, it is in how
many times each is constructed — SingletonComponent‘s constructor runs only once over the
application’s lifetime, PerRequestComponent’s runs again every time a new request arrives.
// ChangingOutside.java — the same read line runs once in the singleton component, and once on every construction in the per-request component
public class ChangingOutside {
public static void main(String[] args) {
SingletonComponent singleton = new SingletonComponent();
Measurement.show("singleton component, before the change", singleton.mode());
CentralSettings.update("mode", "maintenance");
Measurement.show("did the outside value change", CentralSettings.get("mode"));
Measurement.show("the same singleton component, after the change", singleton.mode());
PerRequestComponent newRequest = new PerRequestComponent();
Measurement.show("component rebuilt after the change", newRequest.mode());
}
}
singleton component, before the change : normal did the outside value change : maintenance the same singleton component, after the change : normal component rebuilt after the change : maintenance
ST22. Central configuration is kept outside, in no component’s own field; when the value is
read depends on the scope distinction built in the Core Concepts topic — not repeated here, only
measured here in how it determines the value’s timing. ST23. A singleton component
reads the value only at the moment it is constructed and keeps it in its field; an outside
change never reaches that instance at all — singleton.mode() still says normal on the third
line, even though the store had already turned to maintenance. ST24. A component rebuilt
on every request reads the value again every single time; the newRequest built after the
change runs the same line and sees the current value.
This difference does not mean the component rebuilt on every request is “more correct” — only more current. A singleton component is cheap, because it is constructed once and reused continually; a per-request component is more costly, because it re-runs its constructor every single time, but in exchange it sees every outside change at its next construction. Whichever scope is chosen, the choice is written once in the source and stays the same for every instance of that class; a component behaving sometimes as a singleton and sometimes as per-request does not exist in this model.
Refresh Is a Call, Not Automatic
A singleton component never seeing an outside change is not always desirable. Some components add an explicit refresh method of their own — but nothing changes at all unless this method is called.
// RefreshableComponent.java — singleton, but able to re-read its value through a method called from outside (helper source, no main)
class RefreshableComponent {
String modeValue;
RefreshableComponent() { modeValue = CentralSettings.get("mode"); }
String mode() { return modeValue; }
void refresh() { modeValue = CentralSettings.get("mode"); }
}
// RefreshIsACall.java — the outside value changes, but the component never sees it until refresh() is called
public class RefreshIsACall {
public static void main(String[] args) {
CentralSettings.update("mode", "normal");
RefreshableComponent component = new RefreshableComponent();
Measurement.show("refreshable component, at construction", component.mode());
CentralSettings.update("mode", "maintenance");
Measurement.show("outside value changed, refresh() never called", component.mode());
component.refresh();
Measurement.show("after refresh() is called", component.mode());
}
}
refreshable component, at construction : normal outside value changed, refresh() never called : normal after refresh() is called : maintenance
ST25. A refresh never happens on its own, it is an explicit call; until refresh() is
called, a singleton component keeps holding the old value, and the gap between the outside
change and what the component sees closes only once that call arrives. This line adds something
SingletonComponent never had at all: the possibility of seeing an outside change. But
possibility does not mean automatic application — RefreshableComponent, too, still says
normal on the second line, exactly like SingletonComponent, because refresh() has not been
called yet. Refresh is not a default, it is a decision.
These three component kinds — singleton, per-request, refreshable singleton — hold three
separate positions toward an outside change: the first never sees it at all, the second sees it
automatically but at a cost, the third stays cheap but needs someone to call refresh() for it
to see the change. Who will call it lies outside this lesson’s boundaries — in a real system
this call might be triggered by a scheduler, an outside signal, or the next request; what matters
here is that nothing changes on its own until the call arrives.
A Silent Defect: Two Different Truths at the Same Moment
The difference the previous two sections showed was about ordering — before and after. This section asks the same question with concurrency: while an application has several components at once, are all of them looking at the same central value at the same moment?
// SilentDifference.java — three components existing at the same moment give two different answers to a single outside value
import java.util.*;
public class SilentDifference {
public static void main(String[] args) {
CentralSettings.update("mode", "normal");
SingletonComponent oldSingleton = new SingletonComponent();
CentralSettings.update("mode", "maintenance");
PerRequestComponent newRequest1 = new PerRequestComponent();
PerRequestComponent newRequest2 = new PerRequestComponent();
Measurement.show("oldSingleton.mode()", oldSingleton.mode());
Measurement.show("newRequest1.mode()", newRequest1.mode());
Measurement.show("newRequest2.mode()", newRequest2.mode());
long distinctViewCount = new HashSet<>(List.of(oldSingleton.mode(), newRequest1.mode(), newRequest2.mode())).size();
Measurement.show("did any of these three calls raise an error", "no");
Measurement.show("number of distinct values observed at the same time", distinctViewCount);
}
}
oldSingleton.mode() : normal newRequest1.mode() : maintenance newRequest2.mode() : maintenance did any of these three calls raise an error : no number of distinct values observed at the same time : 2
oldSingleton is deliberately constructed before the change, newRequest1 and
newRequest2 after — the order is written explicitly into the run, because in a real
application when a given component was constructed usually is not this visible; requests arrive
seconds, even milliseconds apart, and a change squeezed in between them may never be noticed at
all. All three calls return successfully, all three give a value, none throws an exception.
ST26. Three components existing at the same moment can give two different, both error-free,
answers to the same outside key: oldSingleton says the application is in normal mode, while
newRequest1 and newRequest2 say it is in maintenance mode — all three are looking at the same
mode key, all three are certain their answer is “correct.” ST27. This difference never
appears as an error anywhere: no warning is logged, no exception is thrown, because each
component behaves consistently with the value it read. The difference becomes visible only when
oldSingleton.mode() and newRequest1.mode() are placed side by side, when someone looking
from outside compares the two — no one inside the code performs this comparison at all, because
oldSingleton and newRequest1 have no knowledge of each other’s existence. The course’s
earlier silent defects (a mistyped setting name, a mistakenly marked metrics field) were at
least confined within a single component; this defect is between components and cannot be
seen by looking at any single component’s source at all.
The response of the three component kinds to an outside change can be summarized in a single table:
| Component kind | What it sees after the change | When it sees the change |
|---|---|---|
SingletonComponent |
the old value, forever | never |
PerRequestComponent |
the current value | at the next construction |
RefreshableComponent |
the old value, until refresh() is called |
only when called |
No row of the table is “wrong” — all three work consistently by their own rule. The problem is
not in the rule itself, it is in the fact that different components of the same application can
be constructed with different rules: oldSingleton and newRequest1 are looking at the same
CentralSettings, both work correctly, and yet they tell a different truth from one another.
Summary
- Central configuration is kept outside; when a component sees this value depends on its own scope.
- A singleton component reads the value only at the moment it is constructed; an outside change never reaches that instance.
- A component rebuilt on every request reads the value again every single time; the change becomes visible at its next construction.
- A refresh never happens on its own, it requires an explicit call; until it is called, a singleton component keeps holding the old value.
- Two components existing at the same moment can give two different, both error-free, answers to the same outside value; the difference becomes visible only when they are compared side by side.
Next Step
This lesson measured which component a value reaches. The next lesson asks which target a call reaches. When one component wants to call another, where does it find that target — from a fixed reference written in the source, from a registry, or from the container’s own resolution? There is no real network call, but how the target gets determined can still be measured.
To keep your progress and take notes, Log in
My notes
Log in to take notes.