Skip to content
academia.sh

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.

Start typing to search.

↑↓ ↵ Esc navigate · open · close