---
title: 'The Enterprise Application Framework'
source: 'https://academia.sh/en/courses/enterprise-application-framework'
language: en
updated: '2026-09-13T07:32:18+00:00'
license: 'CC BY-SA 4.0'
lessons: 19
outcomes:
    - 'Explains the dependency injection and automatic configuration model'
    - 'Builds a layered web application with data access and security'
    - 'Adds configuration, resilience and observability components for a distributed deployment'
    - 'Separates by measurement whether a behavior comes from a call, an annotation or a default'
---

# The Enterprise Application Framework

The strangest property of a program built on an application framework is that nobody calls most of its code: there is no expression constructing the object, no assignment supplying the dependency, no line invoking the setup — only annotations stand in their place. The course measures every mechanism from the object container to the web layer, from security to distributed deployment, with a single question: did a call in the source produce this behavior, did an annotation, or did neither?

## Core Concepts

The trace left in the source by handing an object's lifecycle over to a container, a dependency being supplied through the constructor and its effect on testability, component scopes splitting between a default and an annotation, the precedence order of configuration sources, automatic configuration that kicks in when there is no annotation at all, and cross-cutting concerns being wrapped without touching the call site.

### Inversion of Control

Source: https://academia.sh/en/courses/enterprise-application-framework/inversion-of-control

An IoC container derives an object's construction, the return of the same instance, and a manually new-built instance receiving none of the annotations at all from three separate sources: of twelve behaviors, three come from a call in the source, six from an annotation alone, three from the container's own default.

The closing lesson of the Build, Testing and Ecosystem course measured that the source text does
not say where a class enters the runtime class path from: the calling side only saw an
interface, it never mattered whether the class behind it had passed through a Java compiler.
This lesson carries the same gap one step further. There, what was missing was *where* a type
came from; here, what is missing is *who* constructs an object. In a program that uses an
application framework, someone searching for the `new` keyword most often finds nothing at
all — the objects exist, they work, but the line that constructs them is not in the source.

The mechanism that fills this gap is **inversion of control**: not the program itself, but an
**IoC container** running the program decides when an object is constructed, with which
dependencies, and how many times. Normally, the code that writes a program's flow determines it —
the writing side decides which class is instantiated and in what order it is called. When the
container takes over this decision, control changes direction: the party deciding when a class
is constructed is no longer the one who wrote that class, it is the container itself.

This lesson **models** that container — no real framework's name, annotation syntax, or
behavior is used here; what is built is only a small class written with the standard library's
own reflection (`java.lang.reflect`) and annotation type (`java.lang.annotation`) tools. The
question asked stays the same throughout the course: did a **call** in the source produce a
behavior, an **annotation**, or a **default** that leaves no trace in the source at all?

#### What the Container Models

The `Container` class built below recognizes four annotation types: **`Component`**, which
marks that a class falls under the container's management and carries an optional `scope`;
**`Init`**, which requests that a method be called once the object is ready; **`Wrap`**, which
requests that a class be placed behind a call-counting proxy; **`Value`**, which requests that a
field be filled with a value supplied from outside. All four are the container's own
definitions — this lesson's vocabulary is contributed by this course, not by
`docs/TERMINOLOGY.md`.

The object the container manages is referred to in this course as a **managed component**. A
managed component is nothing more than an instance of a class carrying the `Component` annotation; the
container constructs it, resolves the dependencies it needs through the constructor, calls a
method marked `Init` if one exists, and keeps the same instance if `scope` is singleton. A
managed component's construction follows these steps: find the constructor, resolve its
parameters in order (since each parameter can itself be a managed component, this step calls
itself), create the instance, fill the fields marked `Value`, call the method marked `Init`, and
if marked `Wrap`, place it behind a proxy. This lesson measures only the first two steps in
depth — construction and scope; `Init`, `Value`, and `Wrap` are the subjects of this topic's
third, fourth, and sixth lessons respectively, and appear here only as part of the model.

- **CC1.** The IoC container is built here as a model; no real framework's name, annotation
  syntax, or API is used. `Component`, `Init`, `Wrap`, `Value` are this lesson's own annotation
  types.
- **CC2.** Identity is compared only with `==`; no object's identity hash is ever printed.
- **CC3.** Singleton scope is the container's default scope; every managed component is a
  singleton unless a different one is explicitly requested with an annotation.
- **CC4.** Constructor dependencies are resolved recursively by the container; supplying a
  dependency through a field is not built in this topic.
- **CC5.** The measurement contains no real network, database, or file system call; the store is
  a field held in memory.

#### Construction Invisible in the Source

The program below builds a small two-component system: `Store` is an interface, `InMemoryStore`
is the only class implementing it, and `Service` is a second managed component depending on
`Store`. Nowhere is `new InMemoryStore()` or `new Service(...)` written — these objects appear on
their own when the container's `get` method is called. The second third of the program compares
this same `InMemoryStore`, built by hand, against the one built by the container; the third
third records where a broken annotation lands.

```java
// Container.java — did a call in the source produce this behavior, an annotation, or the container's own default
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Container {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Component { String scope() default "singleton"; }

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
    @interface Init {}

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Wrap {}

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface Value { String name(); String defaultValue(); }

    static int call = 0, annotation = 0, byDefault = 0;
    static int error = 0, silent = 0, ineffective = 0;

    static void observe(String behavior, String source) {
        switch (source) {
            case "call" -> call++;
            case "annotation" -> annotation++;
            default -> byDefault++;
        }
        System.out.printf("%-38s %s%n", behavior, source);
    }

    static void rule(String name, String result) {
        switch (result) {
            case "error" -> error++;
            case "silent" -> silent++;
            default -> ineffective++;
        }
        System.out.printf("%-38s %s%n", name, result);
    }

    static void summary() {
        System.out.printf("behavior: %d call, %d annotation, %d default%n",
                call, annotation, byDefault);
        System.out.printf("defect  : %d error, %d silent, %d ineffective%n", error, silent, ineffective);
    }

    final Map<Class<?>, Object> singletons = new HashMap<>();
    final Map<String, String> settings = new LinkedHashMap<>();
    final List<String> log = new ArrayList<>();
    int wrapCalls = 0;

    final Map<Class<?>, Class<?>> bindings = new HashMap<>();

    Container configure(String name, String value) { settings.put(name, value); return this; }

    Container bind(Class<?> iface, Class<?> implementation) { bindings.put(iface, implementation); return this; }

    Object get(Class<?> type) throws Exception {
        if (type.isInterface()) {
            Class<?> implementation = bindings.get(type);
            if (implementation == null)
                throw new IllegalStateException("no implementation bound to interface: " + type.getSimpleName());
            return get(implementation);
        }
        Component c = type.getAnnotation(Component.class);
        if (c == null) throw new IllegalStateException("no component annotation: " + type.getSimpleName());
        boolean singleton = c.scope().equals("singleton");
        if (singleton && singletons.containsKey(type)) return singletons.get(type);

        Constructor<?> constructor = type.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        Object[] arg = new Object[constructor.getParameterCount()];
        for (int i = 0; i < arg.length; i++) arg[i] = get(constructor.getParameterTypes()[i]);
        Object instance = constructor.newInstance(arg);

        for (Field f : type.getDeclaredFields()) {
            Value v = f.getAnnotation(Value.class);
            if (v == null) continue;
            f.setAccessible(true);
            f.set(instance, settings.getOrDefault(v.name(), v.defaultValue()));
        }
        for (Method m : type.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Init.class)) { m.setAccessible(true); m.invoke(instance); log.add("init:" + type.getSimpleName()); }
        }
        if (type.isAnnotationPresent(Wrap.class) && type.getInterfaces().length == 1) {
            Object target = instance;
            instance = Proxy.newProxyInstance(type.getClassLoader(), type.getInterfaces(),
                    (p, m, a) -> { wrapCalls++; return m.invoke(target, a); });
        }
        if (singleton) singletons.put(type, instance);
        return instance;
    }

    String tryGet(Class<?> type) {
        try { get(type); return "ok"; }
        catch (Exception e) { return e.getClass().getSimpleName(); }
    }

    public static void main(String[] args) throws Exception {
        Container container = new Container().bind(Store.class, InMemoryStore.class).configure("store.startup", "full");

        System.out.println("-- what produced the behavior --");

        Service service = (Service) container.get(Service.class);
        Container.observe("object construction", "annotation");
        Container.observe("dependency supplied", service.store != null ? "annotation" : "call");
        Container.observe("same instance returned", container.get(Service.class) == service ? "default" : "call");
        Container.observe("new instance every call", container.get(Request.class) != container.get(Request.class) ? "annotation" : "default");
        Container.observe("init method called", container.log.contains("init:InMemoryStore") ? "annotation" : "call");

        Container empty = new Container().bind(Store.class, InMemoryStore.class);
        Service defaulted = (Service) empty.get(Service.class);
        Container.observe("setting's default value", defaulted.report().equals("ready:empty") ? "default" : "call");
        Container.observe("setting given from outside", service.report().equals("ready:full") ? "annotation" : "default");

        int previousWraps = container.wrapCalls;
        service.report();
        Container.observe("call wrapped", container.wrapCalls > previousWraps ? "annotation" : "default");

        InMemoryStore manual = new InMemoryStore();
        Container.observe("wrapping on a manually built object", Proxy.isProxyClass(manual.getClass()) ? "annotation" : "default");
        Container.observe("direct method call", manual.read().equals("not initialized") ? "call" : "annotation");
        Container.observe("manual construction with new", "call");
        Container.observe("dependency given by hand", new Service(manual).report().equals("not initialized") ? "call" : "annotation");

        System.out.println("-- where annotation defects land --");

        Container.rule("unannotated type requested", container.tryGet(Unannotated.class).equals("ok") ? "silent" : "error");

        Container k2 = new Container().bind(Store.class, InMemoryStore.class).configure("store.startup", "full");
        WrongSetting ws = (WrongSetting) k2.get(WrongSetting.class);
        Container.rule("setting name misspelled", ws.value.equals("full") ? "ineffective" : "silent");
        System.out.println("expected value full, seen           : " + ws.value);

        Object y1 = k2.get(WrongScope.class), y2 = k2.get(WrongScope.class);
        Container.rule("scope name not recognized", y1 == y2 ? "ineffective" : "silent");

        Object noIface = k2.get(InterfacelessComponent.class);
        Container.rule("wrap put on an interfaceless class", noIface instanceof Proxy ? "error" : "ineffective");

        Container.summary();
    }
}

interface Store { String read(); }

@Container.Component @Container.Wrap
class InMemoryStore implements Store {
    @Container.Value(name = "store.startup", defaultValue = "empty") String startup;
    String status = "not initialized";
    @Container.Init void prepare() { status = "ready:" + startup; }
    public String read() { return status; }
}

@Container.Component
class Service {
    final Store store;
    Service(Store store) { this.store = store; }
    String report() { return store.read(); }
}

@Container.Component(scope = "prototype")
class Request { }

class Unannotated { }

@Container.Component @Container.Wrap
class InterfacelessComponent { String hello() { return "hello"; } }

@Container.Component(scope = "undefined-scope")
class WrongScope { }

@Container.Component
class WrongSetting {
    @Container.Value(name = "store-startup", defaultValue = "empty") String value;
}
```

```
-- what produced the behavior --
object construction                    annotation
dependency supplied                    annotation
same instance returned                 default
new instance every call                annotation
init method called                     annotation
setting's default value                default
setting given from outside             annotation
call wrapped                           annotation
wrapping on a manually built object    default
direct method call                     call
manual construction with new           call
dependency given by hand               call
-- where annotation defects land --
unannotated type requested             error
setting name misspelled                silent
expected value full, seen           : empty
scope name not recognized              silent
wrap put on an interfaceless class     ineffective
behavior: 3 call, 6 annotation, 3 default
defect  : 1 error, 2 silent, 1 ineffective
```

#### Three Sources, Twelve Behaviors

The output's first line was never printed by any code — the call `Container.observe("object
construction", "annotation")` just gives a fixed string, because the `service` variable had
already returned from the `container.get(Service.class)` call, and what produced that call was
not a line in the source at all, it was the `@Container.Component` annotation on the `Service`
class. This topic's opening claim sits exactly here: constructing an object is not an **action**,
it is an **effect**. Whoever reads the program sees the line
`Service service = (Service) container.get(Service.class);`, but this line does not say how
`Service` is to be constructed — it only asks the container for an instance. The construction
decision itself sits in the annotation on `Service.class`.

The second line — `container.get(Service.class) == service` — makes the same request twice and
gets the same object both times. This is the container's **default** decision: when the
`Component` annotation does not specify a `scope`, the container chooses singleton scope, and
this choice is not written anywhere in the source. We can see the value `scope = "singleton"` on
the line `@interface Component { String scope() default "singleton"; }`, but the `Service` class
itself never mentions this value at all — the annotation is bare `@Container.Component`, with no
parameter. Knowing where the default comes from requires looking at the container's own source
code; looking at `Service.java` is not enough.

The final third of the program builds the same `InMemoryStore` class by hand:
`InMemoryStore manual = new InMemoryStore();`. This line really does stand in the source as a
**call** — and it produces an object that differs from the one the container builds in three
ways. The output's final call line traces back to the same root: `new Service(manual)` is also a
manually written dependency handoff; the container never steps in at all. The three `call` lines
standing in the source — a direct method call, a manual `new`, a manual dependency handoff — all
confirm the same observation: every line that bypasses the container is a line that really
appears in the source.

The remaining behaviors are the subject of this topic's later lessons and are only pointed to
here: "dependency supplied" and "dependency given by hand" are the second lesson's (Dependency
Injection), "new instance every call" and "init method called" are the third lesson's (Component
Scopes and Lifecycle), "setting's default value" and "setting given from outside" are the fourth
lesson's (Configuration Sources), "call wrapped" is the sixth lesson's (Aspect-Oriented
Programming). Of the twelve behaviors, three come from a call in the source, six from an
annotation, three from the container's own default — someone reading the source from top to
bottom cannot see three-quarters of these behaviors.

#### The Object Left Outside the Container

The manually built `InMemoryStore` never received three things: the `prepare` lifecycle hook was
never called (the `status` field still reads `"not initialized"`), no `Value` was placed into the
`startup` field (the field stayed `null`; the `read()` method never uses it so it did not show up
in the output, but this line does not observe that at all), and the object was not placed behind
a proxy — `Proxy.isProxyClass(manual.getClass())` returned `false`. All three of these are the
container's **default**, but a strange kind of default: the container never getting involved at
all, the class staying in its own bare form. The annotation keeps standing on the class —
`InMemoryStore` is still `@Container.Component`, still `@Container.Wrap` — but there is no one
reading it.

This topic's rule is: **an annotation is a request, not a feature**; when no container exists
to fulfill the request, nothing happens at all — no error, no warning. The `manual` object is a
completely valid Java object, produced without throwing any exception, carrying no defect flag —
it only did not receive the three things the container was supposed to give. This is not a
separate defect from the behaviors measured through this lesson; it is the container's own
boundary. Everything an IoC container gives exists only to the extent that container gave it.

The lesson's second rule block shows a different defect: the `Unannotated` class carries no
`@Container.Component` at all, and the container throws an exception immediately when an
instance is requested from it — this is the only **noisy** one of this course's four defects.
The defect falls not at the line where the container is built, but at the moment
`container.get(Unannotated.class)` is called — that is, at the **first request**: the
`new Container()` line itself performs no check at all, it does not scan in advance which
classes are annotated. The other three defects — a misspelled setting name, an unrecognized
scope name, a wrap marker placed on an interfaceless class — are measured in depth in this
topic's later lessons; here they only appear in the summary line.

#### Summary

- Inversion of control is the decision of when and how an object is constructed passing from the
  writing side to an IoC container; the container reads this decision from annotations on the
  class.
- Making the same request twice gives the same instance — this is the container's singleton
  scope default, and it is written nowhere in the source.
- An object built by hand with `new` arises from a call that really stands in the source, and
  never enters the container's management at all.
- An object left outside the container receives none of the annotations on it, even though
  those annotations keep standing there — an annotation is a request, and if there is no one to
  read it, it goes unanswered.
- When a type with no annotation is requested, the container throws an exception immediately and
  by name; this defect falls not at the moment the container is built, but at the moment of the
  first request.
- Of twelve measured behaviors, three come from a call in the source, six from an annotation,
  three from the container's own default.

#### Next Step

The manual `new Service(manual)` line was actually hand-imitating one of the most important
mechanisms inside the container: giving a dependency to a constructor. The container does this
by looking at `Service`'s single constructor parameter, requesting that parameter's type, and
passing in the returned object — none of these lines are in the source, but the result is the
same. The next lesson measures this mechanism itself: why the difference between a dependency
being supplied and being given by hand is not just a readability preference, but a testable
design decision.

### Dependency Injection

Source: https://academia.sh/en/courses/enterprise-application-framework/dependency-injection

When an IoC container supplies a dependency through the constructor, it is born from an annotation; the same dependency given by hand is born from a call standing in the source — and the same class works with both. When no implementation is bound to an interface, the container throws an exception immediately, but not while the container is being built — at the first request.

The previous lesson closed by saying that the `new Service(manual)` line was hand-imitating a
mechanism inside the object container: giving a dependency to a constructor. The `Service`
class's single constructor takes a `Store` parameter, and the container resolves this parameter
on its own while constructing `Service` — without ever writing `new InMemoryStore()` in the
source. This lesson's question is: is this resolution really an **injection**, or is it just
another name for construction? And more importantly: does the same `Service` class also work
when its dependency is given by hand?

**Dependency injection** itself is not a new idea in this course — the Design Patterns course
established it as a pattern that provides the separation between principle and detail, the
cornerstone of testable design. This lesson does not re-grind that pattern; it measures **which
mechanism** the container applies it with and what it leaves in the source. The question asked
is the same: did this behavior come from a call in the source, an annotation, or neither?

#### The Container Fills the Constructor's Inside

The container's `get` method, when a type is requested, first finds that type's constructor,
then walks through the constructor's parameter types one by one and, for each one, **calls
itself again**: `arg[i] = get(constructor.getParameterTypes()[i]);`. Since `Service`'s only
parameter is `Store`, this loop runs once and calls `get(Store.class)`; since `Store` is an
interface, the container looks it up in the `bindings` map, constructs the `InMemoryStore` it
finds, and returns it. In the end, when `constructor.newInstance(arg)` is called, `arg[0]` is
already a ready `InMemoryStore` instance — there is no line in the source that fills this value
at all, filling it happens entirely inside this recursive loop.

This is the definition of constructor-based injection: a dependency is resolved **before** the
object is constructed and passed to the constructor as an argument. Injection through a field
(the object first being constructed and then a value being written to its field through
reflection) is not built at all in this topic — the container only fills constructor parameters,
because `Service`'s `store` field is `final` and is assigned in the constructor. This constraint
is deliberate: a `final` field cannot be changed after it is constructed, so it is impossible for
`Service` to exist without its dependency — the compiler itself enforces this, not the
container's courtesy.

The `constructor.setAccessible(true)` line is worth a separate observation. `Service`'s
constructor carries no access modifier at all, meaning it has package visibility; an ordinary
`new Service(store)` call would work with this visibility anyway, since it would come from the
same package too. But the container itself constructs `Service` through a general-purpose
`Class<?>` object, without knowing in advance which package it came from; reflection's normal
access check steps in in this case, and without the `setAccessible(true)` call it would throw an
`IllegalAccessException`. This line is the source of the container's guarantee that it can
"construct every managed component regardless of its visibility" — and this guarantee, too, is a
decision that never shows up in the source, it only stands in the container's own code.

The container's constructor selection also carries an assumption of its own: the `get` method
makes no selection at all, it says directly `type.getDeclaredConstructors()[0]` — it assumes from
the start that the class has **exactly one** constructor, and that even if there is more than
one, the first is correct. Since both `Service` and `InMemoryStore` have a single constructor,
this measurement never stresses this assumption, but the container itself never checks it
anywhere. If a second constructor had been added — an overload like
`Service(Store store, String label)` — the order of the array returned by
`getDeclaredConstructors()` is **not guaranteed** by the Java language specification; the
container could not know in advance which constructor it would choose, and if the chosen
constructor's parameter carried a type that could not be resolved as a managed component (like
`String`), the recursive `get` call would throw an exception. This is a fragility this lesson
does not measure but the model carries: constructor-based injection rests on the assumption that
a class has a **single** way to be constructed, and this assumption is not written explicitly in
the source at all — it can only be understood by looking at how many constructors the class
declares.

The injection measured by this topic is also, at the same time, a narrow injection: the
constructor can only request another managed component or an interface, not a bare value like
`String` or `int`. Filling a value-typed dependency requires a separate mechanism — the `Value`
annotation — and this measurement does not build that at all; this is exactly why `InMemoryStore`
returns its own data as a fixed value.

- **CC6.** Constructor parameter resolution is depth-first: if a parameter has its own
  dependency, that too is resolved first, through the same `get` call.
- **CC7.** `Service`'s `store` field is `final`; this guarantees at compile time that the
  dependency cannot be assigned through any path in the source other than the constructor.
- **CC8.** The same `InMemoryStore` instance is constructed both through the container and by
  hand, and called through the same interface; the measured difference is the construction
  style, not the call contract.
- **CC9.** The `bindings` map holds only interface–implementation pairs; the decision of which
  implementation is selected sits not in the source, but in the `bind` call itself.
- **CC10.** When an unbound interface is requested, the container throws an exception naming
  which type is missing; this exception is not caught, it surfaces to the party making the
  first request.

```java
// Container.java — did a call in the source produce this dependency, or an annotation
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Container {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Component {}

    static int call = 0, annotation = 0;

    static void observe(String behavior, String source) {
        if (source.equals("call")) call++; else annotation++;
        System.out.printf("%-38s %s%n", behavior, source);
    }

    static void summary() {
        System.out.printf("behavior: %d call, %d annotation%n", call, annotation);
    }

    final Map<Class<?>, Class<?>> bindings = new HashMap<>();

    Container bind(Class<?> iface, Class<?> implementation) { bindings.put(iface, implementation); return this; }

    Object get(Class<?> type) throws Exception {
        if (type.isInterface()) {
            Class<?> implementation = bindings.get(type);
            if (implementation == null)
                throw new IllegalStateException("no implementation bound to interface: " + type.getSimpleName());
            return get(implementation);
        }
        Component c = type.getAnnotation(Component.class);
        if (c == null) throw new IllegalStateException("no component annotation: " + type.getSimpleName());

        Constructor<?> constructor = type.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        Object[] arg = new Object[constructor.getParameterCount()];
        for (int i = 0; i < arg.length; i++) arg[i] = get(constructor.getParameterTypes()[i]);
        return constructor.newInstance(arg);
    }

    String tryGet(Class<?> type) {
        try { get(type); return "ok"; }
        catch (Exception e) { return e.getClass().getSimpleName(); }
    }

    public static void main(String[] args) throws Exception {
        Container container = new Container().bind(Store.class, InMemoryStore.class);

        Service service = (Service) container.get(Service.class);
        Container.observe("dependency supplied", service.store != null ? "annotation" : "call");

        InMemoryStore manual = new InMemoryStore();
        Container.observe("direct method call", "call");
        Container.observe("dependency given by hand", "call");

        Service manualService = new Service(manual);
        System.out.println("value returned through the container : " + service.report());
        System.out.println("value returned through manual wiring  : " + manualService.report());

        Container.summary();
    }
}

interface Store { String read(); }

@Container.Component
class InMemoryStore implements Store {
    public String read() { return "data"; }
}

@Container.Component
class Service {
    final Store store;
    Service(Store store) { this.store = store; }
    String report() { return store.read(); }
}
```

```
dependency supplied                    annotation
direct method call                     call
dependency given by hand               call
value returned through the container : data
value returned through manual wiring  : data
behavior: 2 call, 1 annotation
```

This topic's first lesson measured this same question across twelve behaviors and four defects,
and arrived at a total of `3 call, 6 annotation, 3 default` and `1 error, 2 silent, 1
ineffective`; this lesson stays only in that core's dependency-supplying slice.

#### Two Paths, the Same Contract

The `dependency supplied` line comes out `annotation` because the `service.store` field fills
with no source line at all — the container gave it to the constructor. The `dependency given by
hand` line comes out `call` because the same field fills through an argument that really stands
in the source, in the `new Service(manual)` call. The only difference is *where* it came from;
`Service` itself does not know which path constructed it, and it does not need to — the `store`
field looks at the same `Store` interface in both cases.

This connects directly to testability: `service.report()` and `manualService.report()` both
work, both return the same `"data"` string, and the `direct method call` line proves this
separately by calling `manual.read()` directly, with the container never stepping in at all —
`Service` can be built by hand and tested. The principle the Design Patterns course established
becomes concrete right here: `Service` depends on an interface, not on a specific
implementation, which is why passing a fake `Store` implementation in place of the real
`InMemoryStore` during a test is possible without changing a single line of `Service`. The
container does not **apply** this principle — `Service` itself was already written according to
this principle; the container only records which implementation corresponds to which interface,
through the `bind` call, and hands it back at the right moment.

The `bindings` map itself is also a measurement point: the method
`Container bind(Class<?> iface, Class<?> implementation)` is explicitly called in the source —
`new Container().bind(Store.class, InMemoryStore.class)` is a **call**, not an annotation. The
decision of which implementation is chosen sits not on the classes, but inside the code that
builds the container. This is the lesson's second observation: even though injection itself is
born from an annotation, the decision of **which** implementation gets injected comes from a
line that really stands in the source, one that writes it.

The importance of this distinction becomes visible once a second `Store` implementation is put
in `InMemoryStore`'s place. The `Service` class only mentions the `Store` interface — in the
constructor signature, in the `report()` body, `InMemoryStore`'s name appears nowhere at all. If
the line `bind(Store.class, AnotherStore.class)` replaced `bind(Store.class,
InMemoryStore.class)`, not a single character in `Service`'s source file would change; the only
thing that changes is the line that builds the container. The **principle** and the **detail**
the Design Patterns course separated stand exactly at this boundary: the principle is the `Store`
interface, the detail is the `InMemoryStore` class, and the decision that connects them is
written not inside `Service` but outside the container. What this topic measures is that this
decision, too, leaves a trace in the source — just not inside `Service`.

A small side note: since `bindings` is a map, calling `bind` twice for the same interface
silently overwrites the first with the second — no exception, no warning. This lesson does not
measure this separately, but it is an observation from the same family as the third lesson's
scope measurement: writing to a map carries a different rule than adding to a list.

#### Limiting Measurement: An Unbound Interface

In the setup above, `Store` was always bound to `InMemoryStore`. What happens if this binding is
skipped?

```java
// InterfaceError.java — the container throws an error if no implementation is bound to the interface, and exactly at the first request
public class InterfaceError {
    public static void main(String[] args) throws Exception {
        Container unbound = new Container();
        System.out.println("container built, no request made yet");
        System.out.println("number of bound implementations       : " + unbound.bindings.size());
        String result = unbound.tryGet(Service.class);
        System.out.println("exception thrown when Service requested : " + result);
    }
}
```

```
container built, no request made yet
number of bound implementations       : 0
exception thrown when Service requested : IllegalStateException
```

The `new Container()` line performs no check at all — even if `bind` was never called, the
container is constructed without a problem, the `bindings` map stays empty, and no warning
comes out. The defect falls when `Service` is really requested: `get(Service.class)`, while
trying to resolve its constructor parameter, enters `get(Store.class)`, sees that `Store` is an
interface, `bindings.get(Store.class)` returns `null`, and the container throws an
`IllegalStateException` explicitly naming the type. This is the only **noisy** one of this
course's four defects — the remaining three (a misspelled setting name, an unrecognized scope
name, a wrap marker placed on an interfaceless class) stay silent or ineffective, but an unbound
interface does not leave the container silent.

Timing matters here: the error falls **not while the container is built, but at the first
request**. A program can execute `new Container().bind(...)` lines and move on without getting
any error, then crash minutes later the first time `Service` is requested. The moment the
container is built and the moment the dependency graph is actually validated are not the same
moment — this distinction determines when you will find out if there is a missing link between
managed components.

The `Container` in this measurement was deliberately left in its simplest form: the `get`
method notices a missing binding only when that binding is actually requested, it does not scan
and validate the entire dependency graph in advance the moment the container is built. As a
result, a missing binding can go unnoticed for as long as the program never runs that path —
unlike a type mismatch the compiler could catch, this defect only surfaces once the relevant
code path is actually run.

This choice carries a trade-off: early validation (scanning the whole graph while the container
is built and finding missing bindings in advance) risks crashing right at the program's start
but catches the error before it reaches production; late validation (the path this measurement
chose) keeps the container's construction fast and simple, but a missing link can go unnoticed
for as long as that code path is never run. This lesson models the second path, because the
container itself is nothing more than a decision that does not show up in the source either —
when the `get` method validates is, just like where a dependency comes from, a choice that can
only be understood by reading the container's own code.

#### Summary

- Constructor-based injection is the mechanism that resolves a dependency before the object is
  constructed and gives it to the constructor as an argument; there is no assignment anywhere in
  the source that fills this value.
- The same class can be constructed both through the container and by hand, and both work — as
  long as the dependency is bound to an interface, the container steps in without breaking
  testability.
- The decision of which implementation is injected into which interface comes from a `bind` call
  that really stands in the source; this is a behavior born from a call, not an annotation.
- When an unbound interface is requested, the container throws an exception immediately and by
  name — this is the only noisy one of the four defects.
- This error falls not on the line where the container is built, but at the first moment the
  dependency is really requested; construction and validation do not happen at the same time.

#### Next Step

This lesson measured where `Service`'s dependency came from, but it never asked when `Service`
itself is reconstructed by the container — does `container.get(Service.class)` return the same
instance every time it is called, or does it construct a new one each time? The `Component`
annotation has a `scope` field, and this lesson never used it at all; the first lesson's `Service`
staying a singleton, and `Request`'s reconstruction on every call, both came from exactly this
field's value. The next lesson opens this field: when does a managed component stay the same,
when is it reconstructed, and at exactly what moment is the init method called?

### Component Scopes and Lifecycle

Source: https://academia.sh/en/courses/enterprise-application-framework/component-scopes-and-lifecycle

A managed component staying a singleton is the IoC container's default decision; being reconstructed on every call comes from an explicitly written scope annotation. An unrecognized scope name throws no error at all — the component silently stops being a singleton, and this is only visible by comparing two requests' identity with ==

The previous lesson closed by noting that the `Component` annotation carries a `scope` field
that it never used: does `container.get(Service.class)` return the same instance every time it
is called, or does it construct a new one each time? This lesson opens exactly that question — it
measures what the `scope` field does and when a managed component is reconstructed; its second
half also examines a single method that is called on a component after it is constructed but
before it is used — the **lifecycle hook**.

#### Where the Scope Decision Lives

The `Component` annotation carries a `scope` field, and its default value is `"singleton"`. The
`get` method reads this value for a type and only checks the `singletons` map if
`scope.equals("singleton")` is true; if the scope is a different value (like `"prototype"`), this
check is never done at all, and the code falls to the `constructor.newInstance(arg)` line every
single time, constructing a new instance. The `Service` class writes bare `@Container.Component`,
with no parameter — this means the default value of `scope()` applies. The `Request` class writes
`@Container.Component(scope = "prototype")` — an explicit value.

The way this distinction is confirmed is by **identity**, not value equality: the expression
`container.get(Service.class) == service` asks whether two references point to the same object
at the same memory location. There is no other shortcut to confirm this in Java — even if two
`Service` objects had identical fields, `==` returns `true` only when they really are the same
object. This lesson's measurement never prints an identity hash (`hashCode()` output) anywhere,
because that value can vary from run to run and is not itself proof; the proof is only the result
of the `==` comparison.

- **CC11.** The scope check looks in the `singletons` map only for the `singleton` value; every
  other value means "reconstruct every time," no separate list or counter is kept.
- **CC12.** Identity is read only with `==`; no object's identity hash or memory address is ever
  printed.
- **CC13.** The init method (the `Init` annotation) is called **after** the constructor has run
  and the value fields have been filled; the object is already completely constructed.
- **CC14.** A managed component has at most one init method; this measurement does not try a
  class with more than one method marked `Init`.
- **CC15.** An unrecognized scope name (like `"undefined-scope"`) is not caught at compile
  time — `scope()` returns a string, and the container only compares it to `"singleton"` at run
  time.

```java
// Container.java — what a managed component's scope and init order derive from the source: an annotation or the container's default
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Container {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Component { String scope() default "singleton"; }

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
    @interface Init {}

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface Value { String name(); String defaultValue(); }

    static int annotation = 0, byDefault = 0;
    static int silent = 0;

    static void observe(String behavior, String source) {
        if (source.equals("default")) byDefault++; else annotation++;
        System.out.printf("%-38s %s%n", behavior, source);
    }

    static void rule(String name, String result) {
        if (result.equals("silent")) silent++;
        System.out.printf("%-38s %s%n", name, result);
    }

    static void summary() {
        System.out.printf("behavior: %d annotation, %d default%n", annotation, byDefault);
        System.out.printf("defect  : %d silent%n", silent);
    }

    final Map<Class<?>, Object> singletons = new HashMap<>();
    final Map<String, String> settings = new LinkedHashMap<>();
    final List<String> log = new ArrayList<>();

    final Map<Class<?>, Class<?>> bindings = new HashMap<>();

    Container configure(String name, String value) { settings.put(name, value); return this; }

    Container bind(Class<?> iface, Class<?> implementation) { bindings.put(iface, implementation); return this; }

    Object get(Class<?> type) throws Exception {
        if (type.isInterface()) {
            Class<?> implementation = bindings.get(type);
            if (implementation == null)
                throw new IllegalStateException("no implementation bound to interface: " + type.getSimpleName());
            return get(implementation);
        }
        Component c = type.getAnnotation(Component.class);
        if (c == null) throw new IllegalStateException("no component annotation: " + type.getSimpleName());
        boolean singleton = c.scope().equals("singleton");
        if (singleton && singletons.containsKey(type)) return singletons.get(type);

        Constructor<?> constructor = type.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        Object[] arg = new Object[constructor.getParameterCount()];
        for (int i = 0; i < arg.length; i++) arg[i] = get(constructor.getParameterTypes()[i]);
        Object instance = constructor.newInstance(arg);

        for (Field f : type.getDeclaredFields()) {
            Value v = f.getAnnotation(Value.class);
            if (v == null) continue;
            f.setAccessible(true);
            f.set(instance, settings.getOrDefault(v.name(), v.defaultValue()));
        }
        for (Method m : type.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Init.class)) { m.setAccessible(true); m.invoke(instance); log.add("init:" + type.getSimpleName()); }
        }
        if (singleton) singletons.put(type, instance);
        return instance;
    }

    public static void main(String[] args) throws Exception {
        Container container = new Container().bind(Store.class, InMemoryStore.class).configure("store.startup", "full");

        Service service = (Service) container.get(Service.class);
        Container.observe("same instance returned", container.get(Service.class) == service ? "default" : "annotation");
        Container.observe("new instance every call", container.get(Request.class) != container.get(Request.class) ? "annotation" : "default");
        Container.observe("init method called", container.log.contains("init:InMemoryStore") ? "annotation" : "default");

        InMemoryStore manual = new InMemoryStore();
        System.out.println("status of the container-built one : " + ((InMemoryStore) service.store).status);
        System.out.println("status of the manually built one   : " + manual.status);

        Container k2 = new Container();
        Object y1 = k2.get(WrongScope.class), y2 = k2.get(WrongScope.class);
        Container.rule("scope name not recognized", y1 == y2 ? "ineffective" : "silent");

        Container.summary();
    }
}

interface Store { String read(); }

@Container.Component
class InMemoryStore implements Store {
    @Container.Value(name = "store.startup", defaultValue = "empty") String startup;
    String status = "not initialized";
    @Container.Init void prepare() { status = "ready:" + startup; }
    public String read() { return status; }
}

@Container.Component
class Service {
    final Store store;
    Service(Store store) { this.store = store; }
    String report() { return store.read(); }
}

@Container.Component(scope = "prototype")
class Request { }

@Container.Component(scope = "undefined-scope")
class WrongScope { }
```

```
same instance returned                 default
new instance every call                annotation
init method called                     annotation
status of the container-built one : ready:full
status of the manually built one   : not initialized
scope name not recognized              silent
behavior: 2 annotation, 1 default
defect  : 1 silent
```

This topic's first lesson measured this same question across twelve behaviors and four
defects, arriving at a total of `3 call, 6 annotation, 3 default` and `1 error, 2 silent, 1
ineffective`; this lesson stays only in that core's scope and lifecycle slice — the dependency
injected with `bind` and the unbound interface `tryGet` measures are not re-measured here.

#### Singleton, the Default Itself

The `same instance returned` line comes out `default`, because there is no word in `Service`'s
source file at all stating its scope. `@Container.Component` is written bare, with no
parentheses — the `default "singleton"` value of `scope()` kicks in, and the line that gives
this value is not `Service.java`, it is the annotation definition in `Container.java`. A pattern
already seen in this topic's first lesson sits exactly at its center here: whoever reads the
source **cannot know** that `Service` is a singleton, because the source never says so at all;
only someone who already knows the container's rule (that the default scope is singleton) can
guess this result.

The `new instance every call` line is the exact opposite: it comes out `annotation`, because the
`Request` class **explicitly overrides** the default value by writing `scope = "prototype"`.
This is not a Java method override, it is a default value being overwritten by an annotation
argument — `container.get(Request.class) != container.get(Request.class)` confirms that two
separate calls return two separate objects, and this difference comes from a word that really
stands in the source: `"prototype"`.

#### The Init Method: After the Object Is Ready

The `InMemoryStore` class's `prepare()` method carries `@Container.Init` and its body is a single
line: `status = "ready:" + startup;`. When this method is called is determined by the `get`
method's own order: first the constructor runs (the `instance` is created), then the fields
marked `Value` are filled (a value is written into the `startup` field), **then** the methods
marked `Init` are called. This order is not random — if `startup` were not already filled by the
time `prepare()` is called, the `status` value would carry something meaningless like
`"ready:null"`. The container forces this order, and there is no number or annotation parameter
in the source stating this order at all — the order is only the order of the `get` method's own
code lines.

That the init method really was called is confirmed with the `container.log` list:
the line `log.add("init:" + type.getSimpleName())` leaves a record after every call, and the
`init method called` line looks for the existence of this record. No real clock or sequence
number is kept here — only a list of strings answering the question "was this method called or
not." The manually built `InMemoryStore` never gets this record at all: the `status` field stays
`"not initialized"` forever, because the only party that would call `prepare()` is the container,
and a manually built object is one the container never sees at all.

Scope and the init method look like two independent mechanisms, but the relationship between
them comes from the `get` method's own order: the init block runs **before** writing to the
singleton cache, and it is unaware of scope — it runs once for every **newly constructed**
instance, regardless of scope. Since the `Request` class has no method marked `Init` at all, this
measurement does not show this directly, but the mechanism implies it: if a component scoped
`prototype` had an init method, that method would be called again on every request — because
`prototype` scope turns every request into a new construction, and every construction triggers
its own init call. In a singleton component, the init method runs **exactly once** over its
lifetime; in a prototype component, it would (hypothetically) run again on every request. Scope
and the init method stand as two separate annotations in the source, but this relationship
between them is written nowhere at all — it can only be extracted by reading the `get` method's
own code order.

#### Why a Separate Step

Why would putting the init method into the constructor itself not have worked? `InMemoryStore` has
no constructor — Java uses the compiler-generated parameterless default constructor when no
constructor is written at all. Moving `prepare()` into this constructor is not possible, because
in Java a constructor's body is only the code its own class writes; in the mechanism where the
container fills the `Value` fields **after** the constructor, the `startup` field is still
`null` the moment the constructor runs. If we had put the init logic into the constructor, that
logic would always see an empty value.

This is the reason for splitting a managed component's lifecycle into two separate phases:
**construction** (the constructor running, the object existing in memory) and **preparation**
(settings being placed, dependent resources being opened, preparation logic running). The
constructor only guarantees the first phase; the second phase needs a separate hook, and that
hook is exactly the `Init` annotation's job. In a manually built object, this second phase never
happens at all — the `manual` object passed through the first phase (the constructor ran, the
object exists), but it never entered the second phase.

The `Request` class has no fields and no init method at all; when a new instance is constructed
on every request, the container never writes it into the `singletons` map, so it holds no
reference at all. This measurement does not separately count the number of `Request` instances,
because what is measured is not duration or memory — only whether two requests get the same
object or separate ones.

#### Limiting Measurement: An Unrecognized Scope Name

The `WrongScope` class carries `@Container.Component(scope = "undefined-scope")` — the only
value the container recognizes is `"singleton"`, but the annotation argument is a string, and
the compiler does not check whether this string is a value the container understands. The result
shows up on the `get` method's line `boolean singleton = c.scope().equals("singleton");`:
`"undefined-scope".equals("singleton")` returns `false`, the `singleton` variable becomes
`false`, and the code constructs a new instance every time, without ever performing the singleton
check.

```
Object y1 = k2.get(WrongScope.class), y2 = k2.get(WrongScope.class);
Container.rule("scope name not recognized", y1 == y2 ? "ineffective" : "silent");
```

`y1 == y2` comes out `false` — two requests are two separate objects. This is why the rule line
falls into the `"silent"` class: no exception is thrown, no log line says "unknown scope," the
program keeps running without a problem. Only the expected behavior (staying a singleton)
silently disappears. The only way to notice this defect is to compare two requests' identity
with `==` — a check looking at value equality (are two objects' fields the same) catches nothing
at all, because `WrongScope` has no fields, so both look "equal." The defect is only visible to
an eye looking at identity, and throughout this lesson identity was always read with `==`, an
identity hash was never printed even once.

This is a design choice from the same family as the previous lesson's unbound interface
observation: there too, the container did not scan the dependency graph while being built, it
only checked when requested. Here too, the scope name is read not at the moment it is written on
the `Component` annotation, but at the moment `get` is called, and the only recognized value is
compared with a string equality — a small spelling difference (`"Singleton"` instead of
`"singleton"`, or `"undefined-scope"`) passes the compiler without it ever seeing it. Both
lessons arrive at the same conclusion: the container not giving an early warning is not a gap,
it is this model's deliberate limit — validation is possible not by reading the source, but only
by actually running the covered behavior.

#### Summary

- Singleton scope is the container's default; even if there is no word in the source stating
  this, `Component`'s `scope()` value takes effect.
- Making a managed component be reconstructed on every request requires an explicit
  `scope = "prototype"` argument — this is a behavior born from a word that really stands in the
  source.
- The init method (the lifecycle hook) is called exactly once, after the constructor and the
  value fields are complete; the order is determined by the `get` method's own code order.
- A manually built object never receives the init method at all; its field always stays at its
  starting value.
- An unrecognized scope name throws no error at all — the component silently stops being a
  singleton, and this is only visible through an identity comparison done with `==`.

#### Next Step

The `prepare()` method fills the `status` field by reading the value written into the `startup`
field, and that `startup` field is itself fed by its own annotation:
`@Container.Value(name = "store.startup", defaultValue = "empty")`. This lesson never asked
where that value comes from — it only assumed it was filled by the time of initialization. The
next lesson opens exactly this question: which source does a managed component's setting value
win from, out of a precedence order that never appears anywhere in the source, and why is that
order invisible to someone looking from outside.

### Configuration Sources

Source: https://academia.sh/en/courses/enterprise-application-framework/configuration-sources

A setting value comes from one of three sources, and the precedence order is the IoC container's own rule — it never appears anywhere in the source: a value given from outside overrides a file source, and a file source overrides an embedded default. A misspelled setting name never disturbs the container at all — the program silently falls to the embedded default and reads empty instead of full.

The previous lesson showed that `prepare()` filled the `status` value by reading the `startup`
field, but it never asked where that field itself came from — it only assumed it was already
filled by the time of initialization. The line
`@Container.Value(name = "store.startup", defaultValue = "empty")` says two things: this field
may have a value from outside, and if it is not given, `"empty"` is used. But "a value given from
outside" does not come from a single place. This lesson splits that source into three and
measures the precedence order among them.

The third lesson had split a managed component's construction into three steps: constructor,
filling the value fields, init method — and the order of these three steps was the container's
own line order, not a number written in the source. This lesson takes the same pattern one level
deeper: filling the value fields is not a single step in itself, it is querying **three separate
sources** with their own order. The previous lesson's "which step first" question turns here into
"which source first," and the answer is in the same place again: in the container's own code, not
in the source.

#### Three Sources, One Value

A setting value can come from one of three places: **a value given from outside** (supplied
directly by the party running the program, for example a command-line argument or a runtime
flag), **a file source** (a configuration set read while the container is built, carrying more
than one setting at once), and an **embedded default** (the annotation's own `defaultValue()`
value, the last resort placed by the class's author). All three can exist at the same time, and
the container must choose a single winner for every setting.

Choosing the winner is a chain: the external source is checked first, if it is not there the
file is checked, if it is not there either it falls to the embedded default. This chain's order
is the **container's own rule**; no setting declaration says "check the external source first" —
this behavior comes from the order of the `fill` method's code lines:
`if (externalSource.containsKey(...)) ... else if (fileSource.containsKey(...)) ... else ...`.
If this order were reversed (file checked first, then the external source), the same three
sources would produce a different winner — no setting definition in the source can change this
order, because the order lives inside the container's own code, not the setting definitions.

This ordering has its own logic, but that logic, too, is nowhere in the source: the external
source gets the highest priority because the party running the program may want to give a
different value every single time — so the same class, unchanged, can work with two different
values across two separate runs. The file source is one step below it, because it belongs to
**the whole** of a setup, not specific to a single run. The embedded default is at the bottom,
because it is the last resort saying "if nothing is given, use this," from the person who wrote
the class. All three steps rest on a reasonable justification, but the justification that orders
the three does not stand in the source even as a comment — it is embedded only in the `fill`
method's if-else order.

The `containsKey` check itself also carries a detail: even if a source gave an **empty string**
as a value for a key, that source is still considered the winner, because the check asks not for
a `null` check but for the key's **existence**. There is no such case in this measurement — none
of the four settings is defined with an empty string — but the mechanism itself makes this
distinction: "value is empty" and "value was never given" are two separate states, and `fill`
only moves to the next source in the second case.

- **CC16.** The precedence chain is a fixed three steps: external source, file, embedded
  default. This measurement does not add a fourth source (an environment variable, for example).
- **CC17.** The external source and the file are not read from a real file system or command
  line; both are `Map<String, String>` objects held in memory.
- **CC18.** Which source a setting comes from is determined only by the `containsKey` check; even
  if the key's value is an empty string, that source is still considered a "winner."
- **CC19.** Four settings represent three separate scenarios: one that is in both the external
  source and the file, one that is only in the file (two settings), one that is in neither.
- **CC20.** The winning source count is kept in a map; the counter starts at zero and only
  counts the four settings this measurement covers.
- **CC21.** There is no duration, file path, or real command-line argument in this measurement;
  the only thing carried is a setting name and a string value.

```java
// Configuration.java — precedence order among three configuration sources: external source, file source, embedded default
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Configuration {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface Value { String name(); String defaultValue(); }

    static final Map<String, Integer> winnerCount = new LinkedHashMap<>();
    static void won(String source) { winnerCount.merge(source, 1, Integer::sum); }

    final Map<String, String> externalSource;
    final Map<String, String> fileSource;

    Configuration(Map<String, String> externalSource, Map<String, String> fileSource) {
        this.externalSource = externalSource;
        this.fileSource = fileSource;
    }

    void fill(Object instance) throws Exception {
        for (Field f : instance.getClass().getDeclaredFields()) {
            Value v = f.getAnnotation(Value.class);
            if (v == null) continue;
            f.setAccessible(true);
            String value; String source;
            if (externalSource.containsKey(v.name())) { value = externalSource.get(v.name()); source = "external-source"; }
            else if (fileSource.containsKey(v.name())) { value = fileSource.get(v.name()); source = "file"; }
            else { value = v.defaultValue(); source = "embedded-default"; }
            f.set(instance, value);
            won(source);
            System.out.printf("%-24s %-6s winner: %s%n", v.name(), value, source);
        }
    }

    public static void main(String[] args) throws Exception {
        Map<String, String> external = new LinkedHashMap<>();
        external.put("store.connection-pool", "50");

        Map<String, String> file = new LinkedHashMap<>();
        file.put("store.connection-pool", "10");
        file.put("store.timeout", "30");
        file.put("store.startup.status", "full");

        Configuration c = new Configuration(external, file);
        ConfiguredComponent b = new ConfiguredComponent();

        System.out.println("-- which source each setting came from --");
        c.fill(b);

        System.out.println();
        System.out.println("-- winning source count --");
        winnerCount.forEach((k, v) -> System.out.printf("%-20s %d%n", k, v));

        System.out.println();
        System.out.println("values read:");
        System.out.println("pool size        : " + b.poolSize);
        System.out.println("timeout          : " + b.timeout);
        System.out.println("startup status   : " + b.startupStatus);
        System.out.println("cache size       : " + b.cacheSize);
    }
}

class ConfiguredComponent {
    @Configuration.Value(name = "store.connection-pool", defaultValue = "5") String poolSize;
    @Configuration.Value(name = "store.timeout", defaultValue = "10") String timeout;
    @Configuration.Value(name = "store.startup.status", defaultValue = "empty") String startupStatus;
    @Configuration.Value(name = "store.cache.size", defaultValue = "100") String cacheSize;
}
```

```
-- which source each setting came from --
store.connection-pool    50     winner: external-source
store.timeout            30     winner: file
store.startup.status     full   winner: file
store.cache.size         100    winner: embedded-default

-- winning source count --
external-source      1
file                 2
embedded-default     1

values read:
pool size        : 50
timeout          : 30
startup status   : full
cache size       : 100
```

#### Four Settings, Four Different Winners

`store.connection-pool` is defined both in the external source (`50`) and in the file (`10`), and
the winner is the external source — the chain's first link. This shows that how many places a
setting is defined in does not matter: the only thing that determines the winner is which source
is queried earlier in the chain. `store.timeout` and `store.startup.status` exist only in the
file, not in the external source; both win from the file because the first check
(`externalSource.containsKey`) returns `false` and moves to the second check.
`store.cache.size` is in neither source — it falls to the chain's third link, the embedded
default.

`winnerCount` is kept on a `LinkedHashMap`, not a `HashMap` — even though correct counting is the
only thing this lesson needs, `LinkedHashMap` preserves insertion order, so the output prints in
the same order on every run too. Even this small choice is a reminder: the container's own
internal data structure choices affect the readability of the output, even when they do not
affect the measured behavior — and this choice, too, appears in none of the setting declarations
in the source.

The count line summarizes this: one of four settings came from the external source, two from the
file, one from the embedded default. This number is **the measure of the precedence order's
effect** — it shows how many settings depend on which source "winning." The Infrastructure as
Code course measured whether a value given from outside is recorded or not; this lesson takes the
same "value given from outside" idea but asks a different question — not recording, but which
component actually **sees** that value.

The `f.setAccessible(true)` call appears here again too, because `ConfiguredComponent`'s fields
are visible at package level and the `fill` method reaches these fields through a general
`Object` — the same access check bypass seen in the second lesson's `Service` constructor, this
time for fields. The difference: here only a value is written, no method is called.

The setting name itself is also not a type, only a string — `String name()`. This is a choice
from the same class as the third lesson's scope name observation: the compiler cannot check
whether a setting name really corresponds to an existing source key, because all it has is a
character sequence. `"store.connection-pool"` and `"store.connection_pool"` (underscore instead
of hyphen) are two strings equally valid as far as the compiler is concerned, but two completely
separate keys as far as `containsKey` is concerned. This lesson uses correctly written names in
all four settings; the next section deliberately breaks this assumption.

#### When the Order Changes, the Winner Changes

The previous section's claim — that the order is embedded in the container's own code — remains
an unproven assertion if left in plain prose. Running the same two sources, the same two values,
with only the query order reversed, confirms this claim:

```java
// ReversedOrder.java — same two sources, only the query order changed, and the winner changes too
import java.util.*;

public class ReversedOrder {
    static String filePriority(Map<String, String> externalSource, Map<String, String> file, String name, String defaultValue) {
        if (file.containsKey(name)) return file.get(name);
        if (externalSource.containsKey(name)) return externalSource.get(name);
        return defaultValue;
    }

    public static void main(String[] args) {
        Map<String, String> external = Map.of("store.connection-pool", "50");
        Map<String, String> file = Map.of("store.connection-pool", "10");

        System.out.println("winner when external source queried first : " + external.get("store.connection-pool"));
        System.out.println("winner when file queried first             : "
                + filePriority(external, file, "store.connection-pool", "5"));
    }
}
```

```
winner when external source queried first : 50
winner when file queried first             : 10
```

The content of the `external` and `file` maps never changed between these two lines — the key
`store.connection-pool` stands with the same values in both. The only thing that changed is which
map the `filePriority` method looks at **first**. `Configuration.fill` queries the external
source first, and `50` wins; the `filePriority` method written here queries the file first, and
`10` wins. The data in the sources is the same, the setting name is the same, the component
itself does not even exist in this measurement — only the order of two `if` lines inside a method
reversed, and the winner changed. This is direct proof that the precedence order depends not on
any setting declaration in the source, but only on the order of the code that writes the
container.

#### Limiting Measurement: A Spelling Difference

The key `store.startup.status` sits in the file with the value `"full"`. A component wants to
read this value, but writes the key wrong in its annotation — a hyphen where a dot should be:

```java
// MisspelledSettingName.java — the value sitting in the file never wins at all, because of a typo in the setting name
public class MisspelledSettingName {
    public static void main(String[] args) throws Exception {
        java.util.Map<String, String> external = java.util.Map.of();
        java.util.Map<String, String> file = java.util.Map.of("store.startup.status", "full");

        Configuration c = new Configuration(external, file);
        Misspelled b = new Misspelled();
        c.fill(b);

        System.out.println("value sitting in the file : full");
        System.out.println("value the component read  : " + b.status);
    }
}

class Misspelled {
    @Configuration.Value(name = "store-startup.status", defaultValue = "empty") String status;
}
```

```
store-startup.status     empty  winner: embedded-default
value sitting in the file : full
value the component read  : empty
```

`Misspelled`'s `name` value is `"store-startup.status"` (a hyphen after "startup"), while the key
in the file is `"store.startup.status"` (a dot) — the two strings are completely different as far
as Java's `equals` comparison is concerned, `containsKey` returns `false` for both, and `fill`
falls to the chain's third link. There is no exception, no warning, no log line saying "key not
found" — the program silently reads `"empty"`; the `"full"` value sitting in the file is never
seen at all.

This is the exact opposite of the second lesson's unbound interface error: there, the container
threw an exception **immediately and loudly**; here, the container **makes no sound at all**.
Just as the precedence order stands nowhere in the source, a source "losing" leaves no trace in
the source either — the losing source is treated as if it never existed at all. The only way to
notice this defect is to compare, by hand, the value sitting in the file against the value the
component reads; the code itself never points out this difference.

It is worth looking at why this behavior does not change: the `fill` method carries no mechanism
that could notice a setting name "almost matching" in the file, because such a mechanism would
itself become a new source of defects — who decides which spelling difference is "acceptable"?
The container never asks this question at all, it only looks for an exact match. This is the
same design choice as the third lesson's unrecognized scope name: instead of making validation
early and automatic, leaving validation out entirely and showing the result only through
behavior. In both cases the defect is not an exception, it is an output that looks **different
from expected but valid** — in `WrongScope` it was two separate objects, here it is `"empty"`
instead of `"full"`.

These two observations — the winner changing when the order changes, no source winning at all
because of a spelling difference — are two faces of the same fact. In the first, all three
sources were defined with the correct name, and the order change determined which one showed up.
In the second, the order never changed at all, but because the setting name was misspelled, even
the chain's first two links could never be queried at all — the `containsKey` question was asked
of the wrong key. Both defects come from the same root: which value a setting will carry is a
result that cannot be read directly from any line in the source, it can only be extracted by
knowing the container's rule and what the sources contain at that moment.

#### Summary

- A setting value can come from three sources: a value given from outside, a file source, and
  the annotation's embedded default; the chain that determines the winner is the container's own
  code order.
- A setting being defined in more than one source does not matter — only the source queried
  earlier in the chain wins.
- One of four settings came from the external source, two from the file, one from the embedded
  default; this distribution is the direct measure of the precedence order's effect.
- The precedence order itself stands nowhere in the source; knowing which source wins requires
  knowing the container's code.
- A misspelled setting name throws no error at all — the container silently falls to the
  embedded default, and the real value sitting in the file is never seen.

#### Next Step

The fourth setting (`store.cache.size`) was not defined in any source and fell to the embedded
default — but this did not mean the container "did nothing," it only meant it reached the
chain's last link. In some cases, the container itself notices that a component was never
declared at all and brings in **its own** default — not a setting value, but the component
itself. The next lesson measures this different kind of default: what the container does when a
component is never written at all, and why this decision never shows up anywhere in the source
with a "conditional" word.

### Automatic Configuration

Source: https://academia.sh/en/courses/enterprise-application-framework/automatic-configuration

If no component is declared for an interface, the IoC container puts its own default implementation in place; when the user declares their own component, the container silently steps back and never installs its own default at all. What is automatic is conditional, and that condition appears nowhere in the source with any word at all.

The previous lesson measured that a setting falls to the embedded default when it is found in no
source at all — but what fell was a **value**, the content of a field that already existed. This
lesson carries the same idea one level up: sometimes what falls is not a value, it is **the
component itself**. When no one at all declares an implementation for an interface, the container
fills that interface on its own — by bringing in a class of its own choosing. This behavior's
name is **automatic configuration**, and this lesson's question is: how conditional is the thing
that is actually "automatic"?

The fourth lesson's precedence chain queried three sources **in order** — external source, file,
embedded default — and for every setting, only one of the three won. This lesson's question is
shaped differently: there is no ordered chain here, only a **binary** decision — did the user
declare something or not. This binary is what separates automatic configuration from the
previous lesson: what determines the winner is not a precedence order, it is an **existence
test**. If a class satisfying the requested interface exists in the set the user declared, the
container never steps in at all; if not, it brings in the only candidate it has.

#### An Undeclared Interface, the Container's Own Decision

In the measurement below there are three interfaces — `Notifier`, `Cache`, `Metric` — and for
each one the container has an **automatic default** class ready on its own: `AutoNotifier`,
`AutoCache`, `AutoMetric`. This measurement does not use the previous lessons' `@Container.
Component` marker — here, "declaring" only means a class being added to a small list given by
the user, but its role is the same: this list carries the candidates that will take the place of
the managed components the container would otherwise construct on its own. Before making a
decision for an interface, the container looks at whether the user has declared a component
implementing that interface. Did they declare one? Then the container **steps back** — it never
installs its own automatic default at all. Did they not? The container puts in its own default,
and the program never explicitly asked for this decision anywhere.

This "looking" happens in the `resolve` method: the method walks through the list of components
the user declared, and if there is a class in the list implementing the requested interface, it
returns it; if not, it increments its own counter and constructs the automatic default. In this
measurement, "the list of components the user declared" is not a real class path scan, it is a
small list given by hand — but the mechanism itself is the same: a real application framework
asks the same question too, just over a far larger set of candidates.

The check is done with `iface.isAssignableFrom(candidate)`, not `instanceof` — because there is
no **object** in hand yet, only a `Class<?>` reference. `isAssignableFrom` asks whether one type
can be assigned to another, without constructing any instance at all, only by comparing two
`Class` objects. This distinction matters: to decide which class implements which interface, the
container does not have to **construct** that class first — it answers the question entirely at
the type level, and only constructs the actual object with
`getDeclaredConstructor().newInstance()` once the winning candidate is known. The automatic
default candidates are constructed the same way; the only difference between them is that the
automatic default is called only when **no candidate is found at all**.

- **CC22.** "The components the user declared" here is a small list given by hand; there is no
  real class path scan or package discovery in this measurement. All three interfaces have
  exactly one automatic default; choosing among more than one automatic candidate is not within
  this measurement's scope.
- **CC23.** The container runs two setups one after another within the same run; each setup
  carries its own component list, there is no shared state between them.
- **CC24.** The "behavior born with no annotation" counter only counts the cases where the
  automatic default was constructed; a component the user declared themselves never enters this
  counter at all.
- **CC25.** How many times a class was constructed is tracked with a counter added to the
  class's own constructor; no real timestamp or memory measurement is used.

This measurement is deliberately narrower than the previous three lessons' `Container`: the
`resolve` method calls `getDeclaredConstructor().newInstance()`, looking for a parameterless
constructor — unlike the second lesson's `get` method, it does not recursively resolve
constructor parameters. None of the four classes in this measurement (`AutoNotifier`,
`AutoCache`, `AutoMetric`, `CustomNotifier`) has a dependency, so this simplification does not
change the result; but in a real application framework, an automatically configured component can
also have its own dependencies, and then the construction process would need a combination of
this lesson's `resolve` and the second lesson's `get`.

```java
// AutomaticConfiguration.java — if a component is never declared the container puts its own default in place, if declared it steps back
import java.util.*;

public class AutomaticConfiguration {

    static int noAnnotationCount = 0;

    static Object resolve(Class<?> iface, List<Class<?>> userComponents, Class<?> autoDefault) throws Exception {
        for (Class<?> candidate : userComponents) {
            if (iface.isAssignableFrom(candidate)) {
                System.out.printf("%-12s user declared %-18s container stepped back%n", iface.getSimpleName(), candidate.getSimpleName());
                return candidate.getDeclaredConstructor().newInstance();
            }
        }
        noAnnotationCount++;
        System.out.printf("%-12s no declaration at all, container put its default: %s%n", iface.getSimpleName(), autoDefault.getSimpleName());
        return autoDefault.getDeclaredConstructor().newInstance();
    }

    static void setup(String name, List<Class<?>> userComponents) throws Exception {
        System.out.println("-- setup: " + name + " --");
        noAnnotationCount = 0;
        Notifier n = (Notifier) resolve(Notifier.class, userComponents, AutoNotifier.class);
        Cache c = (Cache) resolve(Cache.class, userComponents, AutoCache.class);
        Metric m = (Metric) resolve(Metric.class, userComponents, AutoMetric.class);
        System.out.println("notifier result   : " + n.send());
        System.out.println("cache result      : " + c.get("x"));
        System.out.println("metric result     : " + m.count());
        System.out.println("behavior count born with no annotation at all: " + noAnnotationCount);
        System.out.println();
    }

    public static void main(String[] args) throws Exception {
        setup("no user component at all", List.of());
        setup("user wrote their own notifier", List.of(CustomNotifier.class));

        System.out.println("how many times was AutoNotifier constructed across two setups: " + AutoNotifier.constructionCount);
    }
}

interface Notifier { String send(); }
interface Cache { String get(String key); }
interface Metric { int count(); }

class AutoNotifier implements Notifier {
    static int constructionCount = 0;
    AutoNotifier() { constructionCount++; }
    public String send() { return "auto-notification"; }
}
class AutoCache implements Cache {
    public String get(String key) { return "auto-cache:" + key; }
}
class AutoMetric implements Metric {
    public int count() { return 0; }
}

class CustomNotifier implements Notifier {
    public String send() { return "custom-notification"; }
}
```

```
-- setup: no user component at all --
Notifier     no declaration at all, container put its default: AutoNotifier
Cache        no declaration at all, container put its default: AutoCache
Metric       no declaration at all, container put its default: AutoMetric
notifier result   : auto-notification
cache result      : auto-cache:x
metric result     : 0
behavior count born with no annotation at all: 3

-- setup: user wrote their own notifier --
Notifier     user declared CustomNotifier     container stepped back
Cache        no declaration at all, container put its default: AutoCache
Metric       no declaration at all, container put its default: AutoMetric
notifier result   : custom-notification
cache result      : auto-cache:x
metric result     : 0
behavior count born with no annotation at all: 2

how many times was AutoNotifier constructed across two setups: 1
```

#### The Same Container, Two Setups, Two Separate Numbers

In the first setup, the user declares no component at all —
`setup("no user component at all", List.of())` gives an empty list. All three interfaces receive
the container's automatic defaults: `AutoNotifier`, `AutoCache`, `AutoMetric`. The counter is
`3` — all three behaviors were born with no annotation at all. There is not a single line in the
source naming `AutoNotifier`; the container made this decision entirely on its own, because the
question it asked ("did the user declare something") got the answer `no`.

In the second setup, the user only declares their own component for `Notifier` —
`CustomNotifier`. Now one of the three results changes: the `Notifier` request is now met with
`CustomNotifier`, the output becomes `"custom-notification"`, and the container explicitly says
that it stepped back ("container stepped back"). But `Cache` and `Metric` still received no
declaration at all, so they still get their automatic defaults. The counter this time is `2` —
two of three behaviors are still born with no annotation at all, one now comes from a declaration
that really stands in the source.

These two numbers — `3` and `2` — are two separate results the same `resolve` method produces,
with the same three interfaces, with only one input difference. The container itself never
changed; the only thing that changed is the list given by the user. The word "automatic" does
not mean "always the same" here — it means **conditional**: if the condition is not met (no one
declared anything), the automatic default kicks in; if the condition is met (someone declared
their own component), the automatic default is never installed at all.

The setup itself also carries an observation: `noAnnotationCount` is reset to zero before the
`setup` method is called, because the counter is `static` — the two setups share the same static
field, and without the reset, the second setup's count would pile up on top of the first's. This
is the measurement's own internal detail, but it carries a familiar warning: shared state can mix
up two scenarios run one after another; here this mixing is deliberately prevented with the line
`noAnnotationCount = 0;`, but the same precaution is not taken for
`AutoNotifier.constructionCount` — that counter deliberately accumulates across the two setups,
because what is meant to be measured is exactly this: how many times it was constructed in total.

This conditionality carries automatic configuration's real promise and its real risk at the same
time. The promise: getting a reasonable implementation for a commonly used interface without
writing anything at all — three working components come out of a three-line setup, and no one
has to know the name `AutoNotifier`. The risk comes from the same place: the container decides on
its own under which condition it steps back, and this condition (whether the
`isAssignableFrom` check returns true or false) appears explicitly in no line of the code at all.
Adding a component is not just "installing something new," it can also mean "taking the previous
one's place," and this second effect does not show up in the source.

#### Limiting Measurement: Silent Deactivation

The last line shows how complete this stepping-back is: the `AutoNotifier` class's constructor
keeps its own construction count, and across two setups that number is only `1` — `AutoNotifier`
was constructed only in the first setup, in the second setup it was **never constructed at
all**. The container did not even attempt to build it; the `resolve` method left the loop the
moment it found `CustomNotifier`, and never touched the automatic default at all.

This is the lesson's limiting claim: **adding a component can silently disable another
component that is never mentioned in the source at all.** The person adding `CustomNotifier`
probably only wanted their own notification logic; they do not need to know that
`AutoNotifier` exists, or that it is no longer constructed, because both stand behind the same
`Notifier` interface and the calling side can call `send()` without knowing which one is
running. Deactivation produces no warning, no log line, no exception at all — only a class that
is never constructed. `Cache` and `Metric` are still automatic in this second setup too, but
this too is written nowhere in the source explicitly; it is only the natural result of the
silence of "no one declared anything."

The difference between this defect and the fourth lesson's spelling difference is that nothing
here needs to be **misspelled**. `CustomNotifier` was written correctly, implemented the correct
interface, worked as expected — the defect is not in its own code, it is in its **existence**
causing another class to never be constructed at all. The defects seen in the second and third
lessons (an unbound interface, an unrecognized scope name) were both a shortage; this "defect" is
the exact opposite — something being **added**. None of the three sources measured throughout the
course (call, annotation, default) is "good" or "bad" on its own; this lesson shows that the
comfort of an automatic default carries an invisibility cost at the same time.

#### Three Independent Decisions, One Method

The `resolve` method is called separately for each interface, and each call makes its own
decision entirely on its own — the decision made for `Notifier` has no effect at all on the
decision made for `Cache` or `Metric`. This independence is deliberate: in a real application
framework too, dozens of automatic configuration candidates are evaluated at the same time, and
each one answers its own "did the user declare it" question independently for its own interface.
This measurement's three interfaces were chosen to make this independence visible: only
`Notifier` changed in the second setup, the others stayed the same — if these three were tied to
each other, changing `Notifier` could have affected whether `Cache` stayed automatic too. In this
measurement it does not, because the `resolve` calls never see each other at all.

This independence also draws a limit: the `resolve` method never asks what to do if it finds
**more than one** candidate in the user's declared list — the loop returns the moment it finds
the first matching candidate, it never looks at the rest of the list at all. In this measurement
there is at most one user candidate per interface, so this situation never comes up; but in a
real system, two separate components can implement the same interface and both can be declared,
and then the winner would no longer be determined by "did the user declare it" but by the list's
**order** — a defect very similar to the fourth lesson's ordering dependency in the chain, inside
a different mechanism.

#### Summary

- When no component is declared for an interface, the container installs its own automatic
  default; this decision appears in no line of the source at all.
- When the user declares their own component, the container steps back and never installs the
  automatic default at all — two behaviors come from the same `resolve` method, only with an
  input difference.
- When the same container is run in two setups, the count of behavior born with no annotation
  drops from `3` to `2`; this difference is explained only by the one component the user
  declared.
- What is "automatic" is actually conditional: the condition (no one having declared anything)
  appears with no word in the source at all.
- Adding a component can silently disable another component never mentioned in the source at
  all — the disabled class is never constructed, and it produces no warning at all.

This topic's five lessons brought up the same three sources over and over: a behavior was born
either from a call standing in the source, or from an annotation in the source, or from the
container's own rule — a default. This lesson showed that this third source has its own grade
within itself: sometimes the default is a fixed value (the fourth lesson), sometimes a
conditional component choice (this lesson). Both share the same point: someone reading the
source from top to bottom, without knowing the container's rule in advance, cannot predict which
result will be reached.

#### Next Step

In both setups, `Cache` and `Metric` worked through the same interface, without the call site
ever being touched — the calling side never had to know which implementation stood behind it at
all. This topic's last lesson carries this same observation one step further: is it possible to
add something from outside to a component's behavior, without ever touching the call site at
all? The next lesson measures this — how a cross-cutting concern (a behavior belonging to no
single class, like logging, counting, or auditing) is added without changing the call site.

### Aspect-Oriented Programming

Source: https://academia.sh/en/courses/enterprise-application-framework/aspect-oriented-programming

A wrap marker placed on a managed component adds behavior without ever touching the call site: a cross-cutting concern (a counter, in this example) steps in on every call, and the mechanism is a proxy. But the marker existing does not mean it is applicable — the same marker placed on a class with no interface stays ineffective without throwing any error at all.

The previous five lessons measured how a managed component is constructed, which dependency it
is equipped with, how long it lives, where it gets its settings from, and which implementation is
chosen automatically — all of it concerned the component's **own** internal state. The previous
lesson's `Cache` and `Metric` examples worked without the call site ever being touched, but the
calling side still always talked to the object that **actually does the work**. This lesson asks
a different question: is it possible to add something to a component's **behavior**, from
outside, without ever changing its source code at all? The Server Fundamentals course had named
this kind of behavior — one scattered across more than one class, belonging to no single class —
a **cross-cutting concern**: behaviors like logging, keeping a counter, checking authorization,
which accompany the business logic itself without being related to it. This lesson measures how
we add that cross-cutting concern to a managed component, without ever touching the call site.

#### Wrapping: Stepping In Without Touching the Call Site

The `InMemoryStore` class carries `@Container.Wrap`. If the container sees this annotation while
constructing a component and the class implements exactly one interface, it does not return the
constructed object directly — instead it returns a **proxy**. A proxy is an intermediate object
that implements the `Store` interface but does not do the real work; it routes every call through
itself first: the `Proxy.newProxyInstance` call wraps the `InMemoryStore` instance (`target`) and
builds a handler that does `wrapCalls++` on every method call and then forwards the call to the
real object. `Service` itself never knows about this proxy at all — when it calls `store.read()`,
it cannot tell whether the reference it holds is a real `InMemoryStore` or a proxy wrapping one,
because both carry the same `Store` interface.

This is the concrete form of a cross-cutting concern being added without ever touching the call
site: `Service.report()` still calls `store.read()` — not a single character
changed, but now every call passes through a counter increment. What adds the counter is neither
`Service`'s source code nor `InMemoryStore`'s `read()` body — what adds it is the container itself
seeing the `Wrap` marker. This is a mechanism from the same family as the previous lesson's
automatic configuration: there, the container decided which class would be constructed for an
interface; here, the container decides what gets wrapped **around** the constructed object. Both
are container decisions that leave no trace in the source.

The third argument given to `Proxy.newProxyInstance` is a handler —
`(p, m, a) -> { wrapCalls++; return m.invoke(target, a); }`. This three-letter parameter
abbreviation comes from a standard contract: `p` is the proxy object the call was made on itself,
`m` is a `Method` object representing the called method, `a` is the array of arguments given to
that call. **Every** call made to the proxy — whether it is `read()` or some other method added
later — passes through this single handler, because `Proxy.newProxyInstance` does not produce a
separate method per call, it builds a single interceptor and tells which method was called using
the `m` parameter. In this measurement the `Store` interface has a single method (`read()`), so
`m`'s value is always the same; but the mechanism stays the same no matter how many methods are
added to the interface.

- **CC26.** The proxy only increases the call count, it forwards the real call to the target with
  `m.invoke(target, a)`; no call is blocked or changed by the proxy.
- **CC27.** Wrapping is applied only to classes implementing exactly one interface
  (`type.getInterfaces().length == 1`); this is not `Proxy.newProxyInstance`'s own constraint, it
  is this measurement's own simplification. A class implementing more than one interface is not
  tried in this measurement.
- **CC28.** The counter only tracks the call **count**; which thread called it, how long it took,
  or with which arguments it was called are not in this measurement.
- **CC29.** A manually built object never enters the container's `get` method at all, so it never
  reaches the proxy logic either — wrapping applies only to objects the container constructs.
- **CC30.** Identity is read only with `Proxy.isProxyClass`; no proxy instance's identity hash is
  ever printed.

```java
// Container.java — how a wrap marker's producing behavior via a proxy, without the source ever changing, comes from a call, an annotation, or the container's own default
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Container {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Component {}

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Wrap {}

    static int annotation = 0, byDefault = 0;
    static int ineffective = 0;

    static void observe(String behavior, String source) {
        if (source.equals("default")) byDefault++; else annotation++;
        System.out.printf("%-38s %s%n", behavior, source);
    }

    static void rule(String name, String result) {
        if (result.equals("ineffective")) ineffective++;
        System.out.printf("%-38s %s%n", name, result);
    }

    static void summary() {
        System.out.printf("behavior: %d annotation, %d default%n", annotation, byDefault);
        System.out.printf("defect  : %d ineffective%n", ineffective);
    }

    int wrapCalls = 0;
    final Map<Class<?>, Class<?>> bindings = new HashMap<>();

    Container bind(Class<?> iface, Class<?> implementation) { bindings.put(iface, implementation); return this; }

    Object get(Class<?> type) throws Exception {
        if (type.isInterface()) {
            Class<?> implementation = bindings.get(type);
            if (implementation == null)
                throw new IllegalStateException("no implementation bound to interface: " + type.getSimpleName());
            return get(implementation);
        }
        Component c = type.getAnnotation(Component.class);
        if (c == null) throw new IllegalStateException("no component annotation: " + type.getSimpleName());

        Constructor<?> constructor = type.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        Object[] arg = new Object[constructor.getParameterCount()];
        for (int i = 0; i < arg.length; i++) arg[i] = get(constructor.getParameterTypes()[i]);
        Object instance = constructor.newInstance(arg);

        if (type.isAnnotationPresent(Wrap.class) && type.getInterfaces().length == 1) {
            Object target = instance;
            instance = Proxy.newProxyInstance(type.getClassLoader(), type.getInterfaces(),
                    (p, m, a) -> { wrapCalls++; return m.invoke(target, a); });
        }
        return instance;
    }

    public static void main(String[] args) throws Exception {
        Container container = new Container().bind(Store.class, InMemoryStore.class);

        Service service = (Service) container.get(Service.class);
        int previousWraps = container.wrapCalls;
        service.report();
        Container.observe("call wrapped", container.wrapCalls > previousWraps ? "annotation" : "default");

        InMemoryStore manual = new InMemoryStore();
        Container.observe("wrapping on a manually built object", Proxy.isProxyClass(manual.getClass()) ? "annotation" : "default");

        Object noIface = container.get(InterfacelessComponent.class);
        Container.rule("wrap put on an interfaceless class", noIface instanceof Proxy ? "error" : "ineffective");

        Container.summary();
    }
}

interface Store { String read(); }

@Container.Component @Container.Wrap
class InMemoryStore implements Store {
    public String read() { return "data"; }
}

@Container.Component
class Service {
    final Store store;
    Service(Store store) { this.store = store; }
    String report() { return store.read(); }
}

@Container.Component @Container.Wrap
class InterfacelessComponent { String hello() { return "hello"; } }
```

```
call wrapped                           annotation
wrapping on a manually built object    default
wrap put on an interfaceless class     ineffective
behavior: 1 annotation, 1 default
defect  : 1 ineffective
```

This topic's first lesson measured this same question across twelve behaviors and four defects,
arriving at a total of `3 call, 6 annotation, 3 default` and `1 error, 2 silent, 1 ineffective`;
this lesson stays only in that core's wrapping slice.

#### The Counter Rose Without the Call Site Ever Being Touched

The `call wrapped` line comes out `annotation`: the `previousWraps` variable holds the counter's
value right before `service.report()` is called, then `service.report()` is called, and
afterward `container.wrapCalls > previousWraps` comes out true — the counter really did rise. It
is worth noting that this line calls `service.report()`, not `store.read()` directly: `Service.
report()` internally goes to `store.read()`, and since the `store` field is actually a proxy,
this internal call passes through the proxy's `invoke` handler. There is not a single word in
`Service`'s source showing the proxy — the `Store store` field only carries the interface type,
it does not say which concrete class (a real `InMemoryStore` or a proxy wrapping one) stands
behind it.

`wrapCalls` is an instance field, not the `Container` class's own — since this measurement uses a
single `container` object, we see a single counter, but if two separate `Container` instances had
each wrapped the same `InMemoryStore` class separately, their two counters would stay independent
of each other too. The counter is part of the container's own state, not the wrapped class's —
this also explains why `Service` never keeps its own counter at all, the counter always sits in
the container.

The manually built `InMemoryStore` never receives this wrapping at all:
`Proxy.isProxyClass(manual.getClass())` returns `false`, so the `wrapping on a manually built
object` line comes out `default` — a situation the container never got involved in at all. The
first lesson had already noted this in advance; here the reason becomes fully visible: wrapping
is applied only inside the `get` method, right after the object is constructed. Since
`new InMemoryStore()` falls outside this method, the proxy never comes into play at all, and the
manually built object stays permanently unwrapped — it cannot be "converted" into a proxy
afterward, because in Java an object's identity does not change once it is constructed.

#### Limiting Measurement: A Marker That Shows But Does Not Work

The `InterfacelessComponent` class carries `@Container.Component @Container.Wrap` — exactly like
`InMemoryStore`. But with one difference: `InterfacelessComponent` implements no interface at all,
it is only a class on its own. The wrapping check in the `get` method looks for two conditions at
once: `type.isAnnotationPresent(Wrap.class) && type.getInterfaces().length == 1`. For
`InterfacelessComponent`, the first is true but the second is false —
`getInterfaces().length` is zero. The check reaches a `false` result and the wrapping block is
never entered at all; the object is returned in its bare form.

```
Object noIface = container.get(InterfacelessComponent.class);
Container.rule("wrap put on an interfaceless class", noIface instanceof Proxy ? "error" : "ineffective");
```

`noIface instanceof Proxy` comes out `false`, and the rule line falls into the `"ineffective"`
class. There is no exception at all — since `Proxy.newProxyInstance` is never called, there is
no error to throw either. Result: `InterfacelessComponent` is constructed, it is usable, it looks
in no way broken — but the `@Container.Wrap` marker standing on it is entirely **ineffective**.
This is the shared model's blind spot: an annotation standing on a class does not mean that
annotation is actually applied. Whoever reads the source might see `@Container.Wrap` and think
"every call on this class is being counted," but that is not true — the `Proxy` mechanism can
only work through an interface, because Java's dynamic proxy mechanism can only wrap interfaces,
not concrete classes.

The reason this defect stays in the `ineffective` class is exactly this technical boundary: the
container does not **refuse** to wrap `InterfacelessComponent`, it **cannot** wrap it at all, and
it never says so at all. Of the four defect families, this is the quietest one — `error` shouts,
`silent` produces a wrong value, but `ineffective` leaves no trace at all; the program appears to
work as expected, only the one expected behavior (the counter rising) never happens at all.

As CC27 points out, this measurement only runs the **zero**-interface edge case; it never tries
its symmetric counterpart — a class implementing **more than one** interface. But the check's own
code line (`type.getInterfaces().length == 1`) already tells us what would happen in that case
too: a class implementing two interfaces would also get `false` from the check, just like
`InterfacelessComponent`, and would return unwrapped — the reason is different (not a shortage of
interfaces, but an excess of them), but the result is the same, a silent ineffectiveness. This is
an example of a single condition line dropping two separate edge cases into the same defect
class: `Proxy.newProxyInstance` itself is capable of gathering more than one interface behind the
same proxy — its third argument accepts a `Class<?>[]` array — but this measurement's own check
never uses that capability at all, it only correctly handles the case of **exactly one**
interface. Whoever reads the source, on seeing `@Container.Wrap`, might think "this class will be
wrapped" without looking at how many interfaces the class carries; but the correct answer depends
only on whether `getInterfaces().length` is exactly 1.

#### Six Lessons, the Same Question

This topic's six lessons asked the same question over and over — did a call in the source
produce this behavior, an annotation, or neither — and each time measured a different corner.
The first lesson answered this question with `Container.java`'s full core: it measured, in a
single run, an object's construction and staying a singleton, injection through the constructor,
scope and the init method, setting sources, and wrapping, arriving at a total of twelve behaviors
and four defects: `3 call, 6 annotation, 3 default` and `1 error, 2 silent, 1 ineffective`. The
second, third, and this lesson rebuilt and ran only the slice of that same core touching their
own question: injection through the constructor and an unbound interface's error, scope and the
init method and an unrecognized scope name's silence, and now wrapping and an ineffective marker
— all three are subsets of the first lesson's total, none of them claims to arrive at the same
twelve/four total on its own. The fourth and fifth lessons stepped outside this core and built
their own measurements — the precedence order of three configuration sources, conditional
automatic defaults — but both kept the same vocabulary: continuing to ask where a behavior is
born from in the source, and to distinguish whether it stays noisy or silent once it breaks.

#### Summary

- A wrap annotation adds behavior by building a proxy that steps in during the call, without
  ever touching the call site; `Service`'s source code never changes at all.
- The wrapped behavior (a counter, in this measurement) appears nowhere in the source as a call —
  it can only be confirmed by running it, once the counter itself rises.
- A manually built object never passes through the container's `get` method, so it is not
  wrapped, and it stays permanently unwrapped.
- A wrap marker placed on a class with no interface stays ineffective without throwing any error
  at all — an annotation existing does not mean it is applicable.
- Of the twelve behaviors measured in the first lesson, three came from a call in the source, six
  from an annotation, three from the container's own default; the wrapping and the ineffective
  marker added in this lesson are two new observations layered on top of that total.

#### Next Step

This topic's six lessons measured a single object container's own internal workings: an object's
construction, a dependency being supplied, scope and lifecycle, where settings come from,
conditional defaults, and now behavior added without ever touching the call site. This last
lesson also drew a boundary: a proxy can only add behavior as far as the interface can
**reach**, it cannot go beyond that. The next topic follows the same container outside its own
internal workings, **while it serves a request** — endpoints, validation, and data access are
also born from exactly the same three sources measured throughout this topic: a call, an
annotation, or the container's own default.

## The Web and Data Layer

An embedded server building its request pipeline, endpoints and routing declared through annotations, validation and the common error response produced without ever showing at the call site, data access abstracted at the interface level, the lifecycle of the binding between an object and a table and the consequences of lazy loading, transaction boundaries drawn declaratively.

### Embedded Server and Request Processing Model

Source: https://academia.sh/en/courses/enterprise-application-framework/embedded-server-and-request-model

The links of the request pipeline split into three sources: the object container's own default, an annotation, and an explicit call. When the same set of links is declared in a different order, the run order the container produces does not change; when a class carrying an annotation is called by hand outside the container, the annotation has no effect at all.

The previous topic's last lesson showed that behavior could be added without ever touching
the call site, and that the limit of this was wherever the proxy could reach. This topic
follows the same container with one question: while the container is handling a request,
what share of it comes from each of the same three sources — call, annotation, default?
Endpoints, validation, and data access all arise from the same three sources too; this
lesson starts from the outermost layer of the chain and counts which link a request passes
through from the moment it enters the container to the moment it leaves.

The server is modeled here. No real socket is opened, no real port is listened on, no real
byte is read from the network — the path a request follows while passing through the
container is built as a set of small function objects that run in sequence over a string.
This simplification does not change the lesson's question: in a real embedded server too, a
request passes through a fixed sequence of links from the listening socket to the
application code, and the question of how many links in that sequence have a counterpart in
the source stays the same whether the socket is real or not. The server's own job is to
accept the connection and put the request into this chain — the Server-Side Fundamentals
course established this, and the chain's general idea, as a concept, and it is not repeated
here. The course's own term, request pipeline, is kept separate from the same course's term
middleware chain: a middleware chain is a sequence the caller lines up by hand, while a
request pipeline, as this lesson will show, is a sequence the container builds according to
its own rule.

There is another reason for modeling the server, one tied to the measurement itself: if a
real port were opened, which port is free would vary by machine, and the ban on
environment-dependent data that holds across this whole lesson series already rules out
writing such a value. The string-based model removes this problem from the start — the odds
of the same program giving two different outputs on two different machines drop to zero,
because what is measured is not a delay on the network or a port number, but the function
chain the container applies in a fixed order. In a real embedded server, the outermost link
of this chain is reading from and writing to the network; here that link is skipped, and the
chain starts directly from the first point the application code sees — the request itself.

#### Three Sources for the Links

- **WD1.** The request is modeled here as a string (`body`); every link adds its own
  signature to this string; reading from and writing to the network are not modeled.
- **WD2.** Discovering annotated classes does not perform a real classpath scan; the
  container goes through the candidate class list given to the `scan` call. This is a
  modeled counterpart of a real framework's classpath-walking scan.

A link in the chain can exist through one of three routes. A link written into the
container's own body, always running without putting any annotation on any class, is a
default — here the two links that add an identifier to the request and catch errors at the
end of the chain fall into this group; both are written inside the `Container` class's own
`run` method and need no outside call. A `@Link` annotation placed on a class **requests**
that the container add it to the chain once it recognizes that class; this request does
nothing unless the container's `scan` method is given a candidate list. The third route is
the barest of the three: the code itself puts a link directly into the chain with a
`container.add(...)` call — the exact line in the source that gives rise to it is this very
line.

```java
// Container.java — who added each request-pipeline link: the caller, an annotation, or the container's own default
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Container {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Link { int priority() default 100; }

    interface LinkFunction { String apply(String body, List<String> trace); }

    final List<Class<?>> annotatedClasses = new ArrayList<>();
    final List<LinkFunction> calledLinks = new ArrayList<>();
    final List<String> calledNames = new ArrayList<>();

    void scan(Class<?>... candidates) {
        for (Class<?> candidate : candidates)
            if (candidate.isAnnotationPresent(Link.class)) annotatedClasses.add(candidate);
    }

    void add(String name, LinkFunction l) {
        calledNames.add(name);
        calledLinks.add(l);
    }

    String run(String body, List<String> trace) {
        try {
            String s = body;
            trace.add("request-id(default)");

            List<Class<?>> sortedAnnotated = new ArrayList<>(annotatedClasses);
            sortedAnnotated.sort(Comparator.comparingInt(c -> c.getAnnotation(Link.class).priority()));
            for (Class<?> c : sortedAnnotated) {
                LinkFunction l = (LinkFunction) c.getDeclaredConstructor().newInstance();
                trace.add(c.getSimpleName() + "(annotation)");
                s = l.apply(s, trace);
            }
            for (int i = 0; i < calledLinks.size(); i++) {
                trace.add(calledNames.get(i) + "(call)");
                s = calledLinks.get(i).apply(s, trace);
            }
            trace.add("error-handling(default)");
            return s;
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Container c1 = new Container();
        c1.scan(LoggingLink.class, CompressionLink.class);
        c1.add("add-header", (b, t) -> b + "+header");
        c1.add("write-length", (b, t) -> b + "+length");
        List<String> t1 = new ArrayList<>();
        String r1 = c1.run("request", t1);

        Container c2 = new Container();
        c2.scan(CompressionLink.class, LoggingLink.class);
        c2.add("add-header", (b, t) -> b + "+header");
        c2.add("write-length", (b, t) -> b + "+length");
        List<String> t2 = new ArrayList<>();
        String r2 = c2.run("request", t2);

        Container c3 = new Container();
        c3.scan(LoggingLink.class, CompressionLink.class);
        c3.add("write-length", (b, t) -> b + "+length");
        c3.add("add-header", (b, t) -> b + "+header");
        List<String> t3 = new ArrayList<>();
        String r3 = c3.run("request", t3);

        System.out.println("-- setup-1: L,C scanned; header then length added --");
        t1.forEach(x -> System.out.println("  " + x));
        System.out.println("-- setup-2: C,L scanned (candidate order reversed); header then length added --");
        t2.forEach(x -> System.out.println("  " + x));
        System.out.println("-- setup-3: L,C scanned; length then header added (call order reversed) --");
        t3.forEach(x -> System.out.println("  " + x));

        System.out.println("setup-1 vs setup-2 order the same (annotated part): " + t1.equals(t2));
        System.out.println("setup-1 vs setup-3 order the same (call part included): " + t1.equals(t3));
        System.out.println("body results the same (1 and 2): " + r1.equals(r2));

        int byDefault = 0, byAnnotation = 0, byCall = 0;
        for (String line : t1) {
            if (line.contains("(default)")) byDefault++;
            else if (line.contains("(annotation)")) byAnnotation++;
            else if (line.contains("(call)")) byCall++;
        }
        System.out.printf("chain: %d default, %d annotation, %d call, %d links total%n",
                byDefault, byAnnotation, byCall, byDefault + byAnnotation + byCall);

        System.out.println("-- manually built chain: container never involved --");
        List<String> manualTrace = new ArrayList<>();
        CompressionLink comp1 = new CompressionLink();
        LoggingLink log1 = new LoggingLink();
        String manualBody = "request";
        manualTrace.add("CompressionLink(manual)");
        manualBody = comp1.apply(manualBody, manualTrace);
        manualTrace.add("LoggingLink(manual)");
        manualBody = log1.apply(manualBody, manualTrace);
        manualTrace.forEach(x -> System.out.println("  " + x));
        System.out.println("default links in manual setup: "
                + manualTrace.stream().filter(x -> x.contains("default")).count());
        System.out.println("first class called in manual setup is Compression (priority 20 in container, should come after Logging): "
                + manualTrace.get(0).startsWith("Compression"));
        System.out.println("defect type: ineffective (no exception, @Link.priority value never read, order unchanged)");
    }
}

@Container.Link(priority = 10)
class LoggingLink implements Container.LinkFunction {
    public String apply(String body, List<String> trace) { return body + "+log"; }
}

@Container.Link(priority = 20)
class CompressionLink implements Container.LinkFunction {
    public String apply(String body, List<String> trace) { return body + "+compress"; }
}
```

```
-- setup-1: L,C scanned; header then length added --
  request-id(default)
  LoggingLink(annotation)
  CompressionLink(annotation)
  add-header(call)
  write-length(call)
  error-handling(default)
-- setup-2: C,L scanned (candidate order reversed); header then length added --
  request-id(default)
  LoggingLink(annotation)
  CompressionLink(annotation)
  add-header(call)
  write-length(call)
  error-handling(default)
-- setup-3: L,C scanned; length then header added (call order reversed) --
  request-id(default)
  LoggingLink(annotation)
  CompressionLink(annotation)
  write-length(call)
  add-header(call)
  error-handling(default)
setup-1 vs setup-2 order the same (annotated part): true
setup-1 vs setup-3 order the same (call part included): false
body results the same (1 and 2): true
chain: 2 default, 2 annotation, 2 call, 6 links total
-- manually built chain: container never involved --
  CompressionLink(manual)
  LoggingLink(manual)
default links in manual setup: 0
first class called in manual setup is Compression (priority 20 in container, should come after Logging): true
defect type: ineffective (no exception, @Link.priority value never read, order unchanged)
```

The total six links split into three sources: `request-id` and `error-handling` are
defaults — behavior the `Container` class writes into its own body without needing any
outside input. `LoggingLink` and `CompressionLink` are annotations — there is no line
anywhere in the source that calls these two classes, only a `@Link` mark on top of them and
a `scan` call that reads that mark. `add-header` and `write-length` are calls — without a
`container.add(...)` line, neither of these two links would ever exist in the chain. The six
links split evenly across the three sources — two apiece — but this evenness is not a rule,
it is a property of the example this lesson chose; the shared definition's twelve behaviors
did not split evenly between call, annotation, and default — annotation dominated. The
question the course asks is the same here too: this time what is measured is not a
component's construction, but the path a request follows while passing through a container.

Someone reading the source text top to bottom sees only two of these six links directly. The
lines `container.add("add-header", ...)` and `container.add("write-length", ...)` are right
there, and a reader can infer from the source that these two links exist and in what order
they were added. The remaining four leave their trace somewhere else in the source: for
`LoggingLink` and `CompressionLink` the only trace is the `@Link` line above the class
definition; for `request-id` and `error-handling` there is no trace at all — these two can
only be found by reading the body of the `Container.run` method, and that method sits in a
file the calling code never sees. Someone reading the source only from the caller's own file
misses where two-thirds of the chain comes from.

#### The Container Applies Its Own Order

- **WD3.** The annotated link with the smaller `priority` value runs first; this is the
  container's own rule and is written nowhere in the source.

The only difference between setup-1 and setup-2 is the order of the classes given to the
`scan` call: one scans the logging class first, then the compression class; the other does
the opposite. Even though the source text is written differently, the trace list comes out
byte-for-byte identical, because the container re-sorts the scanned classes by its own
`priority` field and never uses the order of the array given to `scan`. **The run order of
the links is not the order the caller wrote in the source; the container applies its own
order, and two different orderings in the source produce the same run order.**

But this rule only holds for links found through an annotation. Setup-3 uses the same scan
order as setup-1; its only difference is the order of the two `add` calls: length first,
then header. The trace list comes out different this time — `write-length` appears first,
`add-header` second. The container applies its own ordering rule to the links it finds
through annotations, but it does no ordering at all for links added through an explicit
`add` call; it puts them into the chain in exactly the order they were called. The ordering
rule applies only to what the container **discovers**, not to what it is **told** — an
annotation is a request left to the container's own decision, while a call is the caller's
own decision, and the container does not change it.

This distinction has a practical consequence: if two developers give the same two annotated
classes to `scan` in a different order — one collected the files with a tool that lists them
alphabetically, the other with one that lists them by modification date — their source texts
look different, but the chain produced is byte-for-byte the same. The order does not live in
the source, it lives in the `priority` field; someone doing a code review has to look at the
annotation types' own definitions, not the order in the `scan` call, to see that both
versions produce the same behavior. There is no such guarantee for an explicit call —
changing the order of the `add` calls also changes the order of the chain, because the
container applies no rule there at all.

#### In a Manually Built Chain, the Annotation Has No Effect at All

- **WD4.** In the manually built call, `CompressionLink` and `LoggingLink` are instantiated
  directly and their `apply` methods are called in sequence; no `Container` object is
  created at all.
- **WD5.** The `@Link` annotation keeps standing on both classes; it was not removed, not
  deleted. The reason it has no effect in the manual setup is not that the annotation
  disappeared, but that there is no party left to read it.

The last measurement takes the container completely out of the picture: `CompressionLink`
and `LoggingLink` are constructed by hand with `new`, and their `apply` methods are called
by hand, in sequence. Two results stand out. First, there is no line tagged `default` in the
trace list at all — `request-id` and `error-handling` are written into the body of the
`Container.run` method, and since that method is never called, neither behavior ever
happens. Second, `CompressionLink` is called first and `LoggingLink` second — even though
their order inside the container was exactly the reverse, since `CompressionLink`'s
`priority` is 20 and `LoggingLink`'s is 10. In the manual call, this number is never read at
all; the `@Link(priority = 20)` annotation sits on the class, but there is no `scan` call, no
sorting step, left to read it. An annotation is not a property but a request; if there is no
container to fulfill the request, both classes are called exactly like two ordinary Java
objects, and the order stays entirely in the caller's hands.

This completes the three-source distinction from the start of the lesson. A default
behavior is part of the container's own code, so it does not exist without a
container; an annotation carries a request, so it is ineffective without a container to read
it; only a call — a method invoked directly — produces the same behavior whether or not a
container exists, because a call is already an expression written in the source. The
difference these three behaviors show inside and outside the container reveals what the
container actually adds: it does not add a new capability, it reads and applies two kinds of
request (default and annotation) that are never written in the source.

This result carries a practical warning for whoever tests it: calling the `LoggingLink` or
`CompressionLink` class in a unit test outside the container confirms that class's own
logic, but never tests its place in the chain, its order, or the default links that run
alongside it. Two measurements answer two separate questions: is the class's own behavior
correct, and does the container put it in the correct place. The first question can be
answered with a manually built object; the second can only be answered by running the
container itself — and that is exactly what setup-1, setup-2, and setup-3 do together in
this lesson.

#### Summary

- The request pipeline's six links split into three sources: two are the container's
  default, two are an annotation, two are an explicit call.
- The run order of links found through an annotation is decided by the container's own
  `priority` rule and is independent of the scan order in the source; two different scan
  orders produced the same result.
- The order of links added through an explicit call plays no part in the container's rule;
  the order the caller wrote is kept exactly.
- With the container taken out of the picture and the same classes called by hand, the
  default links never ran and the annotation's `priority` value was never read; without a
  party to read it, the annotation was ineffective.
- The body result the two setups produced came out byte-for-byte identical, as long as the
  order stayed the same.

#### Next Step

This lesson measured the chain a request follows while passing through the container, but
it never settled which job the request is met with at the end of the chain — the links only
changed the request, they never routed it. The next lesson closes that gap: it measures
which annotation, not which expression, declares an endpoint in the source, and what the
container answers when a request does not match any of them.

### Controllers and Routing

Source: https://academia.sh/en/courses/enterprise-application-framework/controllers-and-routing

All four of the four endpoints are declared in the source with only an annotation, no call at all. When an unmatched path is requested, the object container does not raise an error, it returns a fixed default response; when two components declaring the same path are scanned, the winner depends on the scan order.

The previous lesson measured which links a request passes through while going through the
container, but it never settled which job the request is met with at the end of the chain.
This lesson closes that gap: it measures which annotation, not which call, declares an
endpoint in the source, and what the container answers to a request that does not match.
Resource design — which resource and which action a path represents — was established as a
concept in the Web API Design course and is not repeated here; this lesson's question is
separate from that — how a path exists in the source at all.

The controller is modeled here too: no real HTTP method is parsed, no real request body is
decoded. A "path" is just a string (like `"/product/list"`), and an endpoint is a mapping
that binds this string to a Java method. The links in the previous lesson changed the
request, they did not route it; this lesson's controllers, in contrast, route the request to
the correct method without changing it at all — this is what the course's terminology
decision means by *routing*.

A controller here is an ordinary Java class; the only thing that makes it a controller is
that some of its methods carry the `@Endpoint` annotation. The class itself implements no
interface, extends no superclass — the container recognizing it as a "controller" comes not
from the class's type, but from it being handed to the `scan` call by hand. This is the web
layer's second modeling decision in this course: in real frameworks, class scanning usually
walks the entire classpath, while here the objects to be treated as controllers are given to
`scan` explicitly, as an array. The modeling difference does not change the outcome — in
both cases, there is no line in the source that calls the controller class, only an
annotation standing on top of it.

#### All Four of the Four Paths Arise from Annotations

- **WD6.** `@Endpoint` is only ever placed on a method and carries a single field: `path`.
  The party that reads this annotation is the `scan` method below.
- **WD7.** The routing table is kept as a `Map<String, Method>`; the key is the path
  string, the value is the method to be called through reflection. There is no real
  request-body parsing or content-type negotiation here.

The two controller classes together define four methods, all four carrying `@Endpoint`.
There is no call anywhere of the form `container.add("/product/list", ...)` — unlike the
previous lesson's links added through a call, here the entire routing table is built by the
`scan` call reading the annotations on the classes. This is one extreme, set apart from the
previous lesson's mixed source distribution (two default, two annotation, two call): all
four of the four endpoints arise from an annotation, there is neither a default endpoint nor
a manually added call.

```java
// Container.java — endpoints arise from annotations, collisions depend on registration order
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Container {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
    @interface Endpoint { String path(); }

    final Map<String, Method> routes = new LinkedHashMap<>();
    final Map<String, Object> owners = new LinkedHashMap<>();
    final List<String> registrationOrder = new ArrayList<>();

    void scan(Object... controllers) {
        for (Object c : controllers) {
            for (Method m : c.getClass().getDeclaredMethods()) {
                Endpoint e = m.getAnnotation(Endpoint.class);
                if (e == null) continue;
                routes.put(e.path(), m);
                owners.put(e.path(), c);
                registrationOrder.add(e.path() + " <- " + c.getClass().getSimpleName());
            }
        }
    }

    String request(String path) {
        Method m = routes.get(path);
        if (m == null) return "default-response: page not ready";
        try {
            return (String) m.invoke(owners.get(path));
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Exception {
        ProductController product = new ProductController();
        StockController stock = new StockController();

        Container container = new Container();
        container.scan(product, stock);

        String[] fourPaths = {"/product/list", "/product/add", "/stock/status", "/stock/alert"};
        System.out.println("-- all four paths arose from annotations --");
        int fromAnnotation = 0;
        for (String path : fourPaths) {
            String response = container.request(path);
            System.out.printf("%-16s%s%n", path, response);
            if (container.routes.containsKey(path)) fromAnnotation++;
        }
        System.out.println("paths that arose from an annotation: " + fromAnnotation + "/4");

        System.out.println("-- unmatched path --");
        System.out.println("/product/delete " + container.request("/product/delete"));
        System.out.println("defect type: silent (no exception, a fixed default response came back)");

        System.out.println("-- colliding endpoint: two components declare the same path --");
        BackupController backup = new BackupController();

        Container containerA = new Container();
        containerA.scan(product, backup);
        System.out.println("scan order product-backup, /product/list response: " + containerA.request("/product/list"));

        Container containerB = new Container();
        containerB.scan(backup, product);
        System.out.println("scan order backup-product, /product/list response: " + containerB.request("/product/list"));

        System.out.println("do the two scan orders give the same response: "
                + containerA.request("/product/list").equals(containerB.request("/product/list")));

        System.out.println("-- manually called controller: independent of container registration --");
        System.out.println("product.list() called directly: " + product.list());
        System.out.println("/product/list through containerA (last registered was backup): " + containerA.request("/product/list"));
        System.out.println("does the manual call match the container response: " + product.list().equals(containerA.request("/product/list")));
    }
}

class ProductController {
    @Container.Endpoint(path = "/product/list")
    public String list() { return "3 products"; }

    @Container.Endpoint(path = "/product/add")
    public String add() { return "product added"; }
}

class StockController {
    @Container.Endpoint(path = "/stock/status")
    public String status() { return "stock: 42"; }

    @Container.Endpoint(path = "/stock/alert")
    public String alert() { return "no alert"; }
}

class BackupController {
    @Container.Endpoint(path = "/product/list")
    public String list() { return "backup list"; }
}
```

```
-- all four paths arose from annotations --
/product/list   3 products
/product/add    product added
/stock/status   stock: 42
/stock/alert    no alert
paths that arose from an annotation: 4/4
-- unmatched path --
/product/delete default-response: page not ready
defect type: silent (no exception, a fixed default response came back)
-- colliding endpoint: two components declare the same path --
scan order product-backup, /product/list response: backup list
scan order backup-product, /product/list response: 3 products
do the two scan orders give the same response: false
-- manually called controller: independent of container registration --
product.list() called directly: 3 products
/product/list through containerA (last registered was backup): backup list
does the manual call match the container response: false
```

All four of the four endpoints are confirmed in the `paths that arose from an annotation:
4/4` line. Someone reading the source looks at the `ProductController` and `StockController`
classes and sees four `@Endpoint` annotations, but cannot **infer** from the source that
these four annotations really turn into a routing table — the `scan` method that does the
turning sits somewhere the calling code never sees. This reproduces the same reading problem
as the previous lesson: an annotation leaves a mark in the source, but whether that mark
really turns into a behavior can only be understood by reading the container's own code.

At this point, the two controller classes have no function on their own —
`ProductController` and `StockController` compile, can be instantiated, and their methods
can be called, all without ever being introduced to the container. What makes a class a
"controller" is not its own code, but the container recognizing it. This repeats the same
pattern seen with the `@Link` annotation in the previous lesson: an annotation is a request
as long as it sits on the class, and without a reader to fulfill the request, the class is
no different from an ordinary Java class. The difference is this: the reader that fulfills
the request in this lesson does not set the class up as a component, it only binds one of
its methods to a path string. The same annotation mechanism can produce a completely
different outcome depending on the topic — the container decides what to do by looking at
the information the annotation carries and at its own rule.

#### An Unmatched Path: A Silent Default

- **WD8.** The `request` method returns a fixed string when it finds no match, throwing no
  exception at all. Because this fixed string is of the same type (`String`) as a normal
  response, the caller gets a "response" without ever being forced to tell the two apart.

`/product/delete` is not defined in any controller, and the call
`container.request("/product/delete")` returns `"default-response: page not ready"` instead
of throwing an exception. This is an example of the silent one of the course's four defect
classes: the program does not say it is crashing, it does not print an error message, it
just continues with an unexpected response. If a developer makes a typo like
`/product/deletee`, nothing anywhere blows up — a response comes back, its form is correct,
but its content is never the content a real endpoint would have produced. The only thing
that catches the defect is deliberately comparing the response's content; the response's
mere existence proves nothing.

This behavior is a design decision, not a shortcoming — the container produces the same
fixed response for every unmatched path, and that is consistent. But consistency is not the
same as correctness: a path always producing the same response when it goes unmatched does
not mean that response is what the caller expected. In the Inversion of Control lesson, the
container threw an `IllegalStateException` when a missing type was requested — there, the
container was trying to construct a class it knew nothing about, and that was genuinely an
unexecutable state. Here, the container is in an executable state when it fails to find a
path, and it chooses to stay silent, because carrying the information "this path does not
exist" as a response body instead of an exception is also a decision the container makes.

The alternative to this decision can be seen not in the container but in the method's own
signature: if `request` returned an `Optional<String>` instead of a `String`, the caller
would be forced to check for the unmatched case — the type system would carry the unmatched
path as a separate state. The signature chosen here does not do that; "found" and "not
found" come back in the same type, in the same form. The bounding measurement sits exactly
at this point: the defect staying silent is not a weakness of the container, it is the
direct consequence of the return type not distinguishing the matched state. Whoever writes a
routing rule has to design this distinction deliberately; otherwise every typo results in a
response that looks error-free but is wrong.

#### Colliding Endpoints: The Winner Depends on the Scan Order

- **WD9.** `BackupController` declares the same path (`/product/list`) as
  `ProductController`. The container overwrites the entry previously written under the same
  key with whichever one is written second; which entry counts as "second" depends on the
  order of arguments given to the `scan` call.
- **WD10.** The container does not check for the collision — it prints no warning, throws
  no exception. Who the winner is can only be seen by comparing the two setups.

`containerA` scans `product` and then `backup`, in that order; `containerB` scans the same
two controllers in reverse. The response given to a `/product/list` request differs between
the two setups: in `containerA` the backup controller's response wins (it was scanned last),
and in `containerB` the original controller's response wins (it was scanned last). There is
no statement anywhere in the source text saying "this is the winner"; the only thing that
decides the winner is the order of the array given to the `scan` call.

This stands in direct contrast to the link ordering in the previous lesson. There, the order
of links found through an annotation was decided by the `priority` field and was completely
independent of the scan order — two different orderings in the source produced the same
result. Here the same `scan` pattern is used, but the routing table is built with a plain
`put` on a `Map`, and there is no tie-breaking rule at all. The difference is not in the
mechanism itself — both are a reflection scan reading annotations — but in whether the
container has an explicit rule to resolve a collision. In the link ordering, the `priority`
field supplied that rule; the routing table has no such field, so the collision falls not on
any statement in the source, but on the scan order. An annotation being independent of
ordering does not come for free; the container has to have a rule to resolve that ordering,
and without that rule, what decides the winner is not the last line in the source, but the
container's own registration order.

#### A Manually Called Controller Is Independent of Container Registration

Calling `product.list()` directly always returns `"3 products"` — this is the behavior of
`ProductController`'s own method and depends on no container registration at all. A
`/product/list` request through `containerA`, in contrast, returns `"backup list"`, because
`BackupController` was the one scanned last in that setup. The two responses differ, and
this difference shows exactly the distinction from the start of the lesson: the `product`
object's own methods always run its own logic, while a response coming through the container
depends on the container's current registration state. The `ProductController` class itself
never changed, its `list()` method still returns the same string — what changed is which
method the container bound to the `/product/list` path. Calling a controller class by hand
in a unit test therefore only confirms that class's own logic; it confirms neither which
path it is bound to nor whether that path collides with another component. The two questions
are separate: is the method correct, and did the container bind it to the correct path — the
first is answered by a manual call; the second can only be answered by running the container
itself.

The scope of this distinction is a little broader than what was seen in the first lesson:
there, a manual call only missed the default links (`request-id`, `error-handling`), because
that behavior was part of the container's own code. Here what gets missed is not a default,
but the registration state the container currently holds — which component scanned which
path last. A manual call never sees this state, because a manual call never asks the
container at all; it just runs whatever is in the object's own memory. Both kinds of
dependency come from the same root — the container stepping in and making a decision on the
work — but one is the container's fixed behavior, the other is a behavior that changes with
whatever state the container happens to be in at that moment.

#### Summary

- All four endpoints arose from the `@Endpoint` annotation; there is no call in the source
  that builds the routing table, the table is built by the annotations the `scan` method
  reads.
- When an unmatched path was requested, the container threw no exception, it returned a
  fixed default response; this is an example of the course's silent defect class.
- When two components declared the same path, the container did not check for the
  collision; the winner changed with the order of classes given to the scan call.
- This differs from the link ordering in the previous lesson: there, the annotation order
  was fixed by the container's own rule (priority); here, since there is no rule to resolve
  a path collision, the result stays dependent on the registration order.
- Calling a controller class by hand tests only that class's own logic; it does not test
  which path it is bound to or whether it wins a collision.

#### Next Step

This lesson measured which method a request reaches, but it never asked whether the data
reaching that method is in the correct form. The next lesson takes this up: it measures
whether a field's validation rule is a call or an annotation in the source, and, when the
rule is broken, which fields the common error response gathers and from where.

### Validation and Error Handling

Source: https://academia.sh/en/courses/enterprise-application-framework/validation-and-error-handling

Three of four fields take their validation rule from an annotation, one carries no rule at all. When the rule is broken, the common error response's envelope comes from the object container's default, while the field messages come from the annotation itself; when the same invalid object is given directly to the handler outside the container, no rule is applied at all and the defect stays silent.

The previous lesson measured which method a request reaches, but it never asked whether the
data reaching that method is in the correct form. This lesson takes this up: it measures
whether a field's validation rule is a call or an annotation in the source, and, when the
rule is broken, which fields the common error response gathers and from where.

Validation here is subject to the same three-source question too: is a field's rule written
as an expression in the source, is it only marked with a sign, or does the container pass
over the field without saying anything about it at all? This lesson adds a fourth
possibility — a field having no rule at all — and this adds a measurement different from the
previous two lessons: the container reveals not only where the rules come from, but also
which fields carry no rule at all.

Error handling is also this lesson's subject, in two senses: gathering which rule a request
broke (validation), and giving that gathered result back to the caller in some form (error
handling). Though they look like separate questions, both live inside the same container —
when the container rejects a request, it has to both find which fields are broken and turn
that into a single, consistent response. In this lesson, both jobs merge into the same
`request` method, but their sources stay separate: which rule was broken comes from the
annotation, how the broken rules get gathered together is the container's own decision.

#### Three of Four Fields Take Their Rule from an Annotation

- **WD11.** Rule checking is done only at the field level; method parameters, nested
  objects, or collection elements are not validated here.
- **WD12.** A field can have more than one rule, but in this lesson every field carries at
  most one; how rules order one another is a separate measurement's subject.

`RegistrationRequest` carries four fields: `name`, `quantity`, `category`, `code`. The first
three carry, respectively, the `@RequiredField`, `@PositiveValue`, and `@Options`
annotations; the fourth, `code`, carries no annotation at all. There is no call anywhere in
the source that validates these four fields — the `validate` method does not know which
fields are subject to which rule, it only goes through every field one by one, through
reflection, and checks which annotation sits on it. Since no annotation is found for the
`code` field, this field is never validated — any value requested for it is accepted.

```java
// Container.java — validation rules arise from annotations, the common error response is the container's default
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Container {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface RequiredField { String message() default "field cannot be empty"; }

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface PositiveValue { String message() default "value must be greater than zero"; }

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface Options { String[] value(); String message() default "unrecognized option"; }

    interface Handler { String run(Object request); }

    List<String> validate(Object object) throws ReflectiveOperationException {
        List<String> errors = new ArrayList<>();
        for (Field f : object.getClass().getDeclaredFields()) {
            f.setAccessible(true);
            Object value = f.get(object);

            RequiredField r = f.getAnnotation(RequiredField.class);
            if (r != null && (value == null || value.toString().isBlank()))
                errors.add(f.getName() + ": " + r.message());

            PositiveValue p = f.getAnnotation(PositiveValue.class);
            if (p != null && value instanceof Integer i && i <= 0)
                errors.add(f.getName() + ": " + p.message());

            Options o = f.getAnnotation(Options.class);
            if (o != null && !Arrays.asList(o.value()).contains(value))
                errors.add(f.getName() + ": " + o.message());
        }
        return errors;
    }

    String request(Object requestObject, Handler handler) throws ReflectiveOperationException {
        List<String> errors = validate(requestObject);
        if (!errors.isEmpty())
            return "error; field-count=" + errors.size() + "; fields=" + errors;
        return handler.run(requestObject);
    }

    static int ruledFieldCount(Class<?> type) {
        int n = 0;
        for (Field f : type.getDeclaredFields())
            if (f.isAnnotationPresent(RequiredField.class) || f.isAnnotationPresent(PositiveValue.class)
                    || f.isAnnotationPresent(Options.class)) n++;
        return n;
    }

    public static void main(String[] args) throws Exception {
        Container container = new Container();
        Handler handler = request -> {
            RegistrationRequest r = (RegistrationRequest) request;
            return "registration-ok: name=" + r.name + ", quantity=" + r.quantity + ", category=" + r.category;
        };

        System.out.println("-- how many rules come from an annotation --");
        int totalFields = RegistrationRequest.class.getDeclaredFields().length;
        int ruledFields = ruledFieldCount(RegistrationRequest.class);
        System.out.println("total fields: " + totalFields);
        System.out.println("fields carrying a rule via annotation: " + ruledFields);

        RegistrationRequest valid = new RegistrationRequest("screw", 12, "electronics", 501);
        System.out.println("-- valid request --");
        System.out.println(container.request(valid, handler));

        RegistrationRequest invalid = new RegistrationRequest("", -3, "furniture", 502);
        System.out.println("-- invalid request: through the container --");
        System.out.println(container.request(invalid, handler));

        System.out.println("-- same invalid object, given directly to the handler (container bypassed) --");
        System.out.println(handler.run(invalid));
        System.out.println("defect type: silent (no exception, invalid data reached the handler)");
    }
}

class RegistrationRequest {
    @Container.RequiredField String name;
    @Container.PositiveValue int quantity;
    @Container.Options(value = {"food", "electronics", "clothing"}) String category;
    long code;

    RegistrationRequest(String name, int quantity, String category, long code) {
        this.name = name;
        this.quantity = quantity;
        this.category = category;
        this.code = code;
    }
}
```

```
-- how many rules come from an annotation --
total fields: 4
fields carrying a rule via annotation: 3
-- valid request --
registration-ok: name=screw, quantity=12, category=electronics
-- invalid request: through the container --
error; field-count=3; fields=[name: field cannot be empty, quantity: value must be greater than zero, category: unrecognized option]
-- same invalid object, given directly to the handler (container bypassed) --
registration-ok: name=, quantity=-3, category=furniture
defect type: silent (no exception, invalid data reached the handler)
```

Three of the four fields carry a rule, one does not. This ratio is itself a measurement:
someone reading the source looks at the `code` field and sees no constraint, and that is a
correct reading — the rule really does not exist, the container never validates it. The
situation is different for the other three fields: there is no `if` anywhere in the source
that says `name` cannot be empty, no comparison that says `quantity` has to be greater than
zero. These rules are written only in the annotations, and without the `validate` method
that reads them, they have no effect at all. The difference between the valid request ending
in `registration-ok` and the invalid request coming back with three separate errors is proof
that these three rules — invisible in any line of the code — really run.

The `code` field carrying no rule can be read either as a shortcoming or as a deliberate
choice. The source text does not distinguish the two — when a field has no annotation on
it, the difference between "this field was deliberately left open" and "we forgot to write
the rule" leaves no trace in the source at all. In the previous two lessons, what was
missing was a behavior (a link, an endpoint); here what is missing is a **constraint**, and
the absence of a constraint never shows itself as an error — only as a silence in which
every value is accepted. Three of four fields carrying a rule does not mean the remaining
one was reviewed with the same care; this is why keeping a deliberate list of fields left
without a rule requires a discipline separate from the rule count itself.

#### The Common Error Response: Envelope Default, Content Annotation

- **WD13.** The error response's envelope (the form `"error; field-count=...;
  fields=..."`) is written into the body of the `request` method; no annotation decides
  this form.

The response given to an invalid request has two parts, and both come from a different
source. The envelope — the form that says how many errors the response carries and wraps
them as a list — is a string pattern written into the body of the `Container.request`
method itself; this is from the same family as the default behaviors in the previous
lessons: no field in the source requested it, the container produces this form on its own
for every invalid request. The content is different: the strings `"name: field cannot be
empty"` and `"category: unrecognized option"` come directly from the `message` field of the
`RequiredField` and `Options` annotations. Neither of these two annotations specified a
custom message, so their own default messages were used — but if a developer had written
`@Container.RequiredField(message = "a registration cannot be created without a name")`,
that text would have appeared in the response. The envelope is fixed, the content is in the
annotation's hands.

This distinction has a practical consequence: to change the common error response's form
(for instance, putting the field count at the end instead of the start), the container's own
code has to be changed; no annotation affects this. But to change a single field's error
message, there is no need to touch the container at all — the annotation's `message` field
is enough. The two kinds of change sit in two separate places in the source, and one is far
cheaper than the other: changing the message text is editing an annotation parameter,
changing the envelope is changing the container itself.

There is also a reverse side to this distinction: the `validate` method gathers the errors,
but it also decides which rule gets checked first — here this matches the field declaration
order, `name`, then `quantity`, then `category`. This order, too, is the container's own
decision; no annotation says "check me first." The ordering looks inconsequential, because
in this lesson all rules are gathered together and given in a single response — none of
them stops another. But if a container were designed to "stop at the first error," this
order would decide which error reaches the caller and which is never seen at all; the order
would then stop being an implementation detail and become the behavior itself. The "gather
all of them" approach chosen in this lesson does not carry that risk, but the choice belongs
to the container itself — nowhere in the source does it say "gather all errors."

#### Validation Does Not Work at All Outside the Container

- **WD14.** The call `handler.run(invalid)` never sees the `Container.request` method;
  `validate` is never called at all.
- **WD15.** The `code` field carries no rule to begin with, so it is never validated in
  any setup; the difference shows up only in the other three fields.

The last measurement gives the same invalid object — empty `name`, negative `quantity`,
unrecognized `category` — directly to the handler without ever putting the container into
play. The result: `registration-ok: name=, quantity=-3, category=furniture`. No exception,
no error message — the handler works with the same fidelity on invalid data, because its own
code contains no validation at all. The validation logic lives only inside the
`Container.request` method, and as long as that method is never called, none of the three
annotations is ever read.

This is another example of the course's recurring bounding rule: the guarantee a container
provides holds only along the path where that container is called. The annotations on the
`RegistrationRequest` class were not removed, they still stand there — but as long as the
`validate` call that would read them is not put into play, the class is no different from an
ordinary data carrier. This produces a somewhat riskier outcome than what was seen in the
previous two lessons, because here invalid data does not merely produce a wrong response —
it produces a response that looks valid, one that says "ok." When the rule is broken, a
request going through the container clearly sees an error; the same data coming from
outside the container continues with an "ok" response without any warning at all.
Validation existing is a guarantee only as long as there is a single entry point that
enforces it; once there is more than one entry point — here, the two paths of going through
the container and being called directly — the guarantee stays dependent only on which path
was used.

This traces to the same root as the bounding measurements in the previous two lessons, but
its consequence is a degree heavier. In the first lesson, a link left outside the container
only lost its order; in the second, a manually called controller only failed to see the
container's registration state — both kept working correctly at the functional level. Here,
though, a call left outside the container processes invalid data **as if it were valid**:
the string `handler` returns carries the same `registration-ok` prefix as the string a
correct request would return. The caller cannot tell the two cases apart by looking at the
response's form — telling them apart requires separately checking whether the `name` field
is empty, which itself amounts to rewriting validation outside the container. In an
application, this kind of second entry point usually shows up as a background job, a batch
import step, or a direct call from another component; whatever form it takes, the container
has no knowledge of its existence and the rule never runs on it at all.

#### Summary

- Three of four fields (`name`, `quantity`, `category`) took their validation rule from an
  annotation; the fourth (`code`) carried no rule and was never validated.
- When the invalid request went through the container, it produced a common error
  response; the response's envelope was fixed in the container's own code, while the field
  messages came from the annotations' `message` field.
- Changing an annotation's message required only editing the annotation; changing the
  envelope would have required changing the container's own code.
- When the same invalid object was given directly to the handler outside the container, no
  rule was applied at all, and the handler produced a response meaning "ok"; the defect
  stayed silent.
- Validation's guarantee holds only along the path where the container is called; a call
  outside the container can use the same class with no constraint at all.

#### Next Step

This lesson measured whether a request's data is correct, but it never asked where that
data gets written once it is processed. The next lesson takes this up: it measures how the
container supplies the implementation of a repository interface, and how the same caller
can run against two separate implementations without ever being recompiled.

### Data Access Abstraction

Source: https://academia.sh/en/courses/enterprise-application-framework/data-access-abstraction

The same compiled set of callers — the object container, the service, and the entry point — runs against two separate implementations of a repository interface, without ever being recompiled. The caller's class file mentions only the interface; the same call runs without a hitch under one implementation while falling with an exception under the other, because the interface's promise and the implementation's behavior are separate, and the container does not close this gap, it only hides it.

The previous lesson measured whether a request's data is correct, but it never asked where
that data gets written once it is processed. This lesson takes this up: it measures how the
container supplies the implementation of a **repository** interface, and how the same
caller can run against two separate implementations without ever being recompiled. The idea
of a repository — keeping business logic independent of persistence details — was
established in The Data Access Layer and Business Logic course and is not repeated here;
this lesson's question is separate from that: by what mechanism the container fills this
interface.

The database is modeled here too: no real connection is opened, no real SQL statement is
run. `Repository` is just an interface carrying two methods — reading a quantity, writing a
quantity — and what stands behind these two methods is invisible to the caller. The only
way to show how real this invisibility is, is to really run the same caller against two
separate implementations; the method used in this lesson is exactly that.

In the previous lessons, "running the same behavior two ways" meant comparing an object the
container built against a manually built object. This lesson carries the same principle one
step further: what is compared here is not two objects, but the same calling code run
against two separate **implementations**. This is also where an interface is truly tested —
imitating a single class's behavior is not enough; it also has to be seen whether a
completely different internal structure, holding the same contract, satisfies the same
calling code.

#### The Same Caller, Two Separate Implementations

- **WD16.** This measurement stages a real classpath distinction: the interface, the
  calling code, and each implementation are compiled into separate directories, then
  brought together in different combinations at runtime. Which classpath a class has to be
  required in was measured in the Build, Testing and the Ecosystem course; here the same
  mechanism is used for the interface–implementation distinction, and the numbers from
  there are not repeated.
- **WD17.** The `Container.create` method takes the implementation class's name as a
  string parameter and finds it with `Class.forName`; this is how the caller can construct
  an instance without mentioning any implementation class at compile time.

The `Container`, `Service`, and `Main` classes are compiled only against the `Repository`
interface — neither `MemoryRepository` nor `ArrayRepository` ever enters the compile-time
classpath. These three classes are compiled exactly once, then run with two separate
runtime classpaths, never recompiled: with `MemoryRepository` in one, `ArrayRepository` in
the other. The only difference between the two is the third classpath component given to
the `run` call and the implementation name passed to `Main.main`.

```java
// TwoImplementations.java — the same compiled caller runs with two separate implementations without changing the interface
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class TwoImplementations {

    static Path write(Path directory, String name, String source) throws IOException {
        Files.createDirectories(directory);
        Path p = directory.resolve(name + ".java");
        Files.writeString(p, source);
        return p;
    }

    static void compile(Path output, List<Path> classpath, List<Path> sources) throws IOException {
        Files.createDirectories(output);
        List<String> arg = new ArrayList<>(List.of("-d", output.toString()));
        if (!classpath.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : classpath) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        for (Path k : sources) arg.add(k.toString());
        ToolProvider compiler = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter outputText = new StringWriter();
        int result = compiler.run(new PrintWriter(outputText), new PrintWriter(outputText), arg.toArray(new String[0]));
        if (result != 0) throw new RuntimeException("compilation failed: " + outputText);
    }

    static String run(List<Path> classpath, String implementationName) {
        URL[] url = new URL[classpath.size()];
        try {
            for (int i = 0; i < url.length; i++) url[i] = classpath.get(i).toUri().toURL();
            try (URLClassLoader loader = new URLClassLoader(url, TwoImplementations.class.getClassLoader().getPlatformClassLoader())) {
                Class<?> main = Class.forName("Main", true, loader);
                ByteArrayOutputStream buffer = new ByteArrayOutputStream();
                PrintStream old = System.out;
                System.setOut(new PrintStream(buffer));
                try {
                    main.getMethod("main", String[].class).invoke(null, (Object) new String[]{implementationName});
                } finally {
                    System.setOut(old);
                }
                return buffer.toString().strip();
            }
        } catch (InvocationTargetException e) {
            return "exception: " + e.getCause().getClass().getSimpleName() + ": " + e.getCause().getMessage();
        } catch (Throwable t) {
            return "exception: " + t.getClass().getSimpleName();
        }
    }

    public static void main(String[] args) throws Exception {
        Path root = Files.createTempDirectory("twoimpl");
        Path source = root.resolve("source");

        Path sInterface = write(source.resolve("interface"), "Repository",
                "public interface Repository {\n"
              + "  int findQuantity(String product);\n"
              + "  void updateQuantity(String product, int quantity);\n"
              + "}\n");

        Path sContainer = write(source.resolve("caller"), "Container",
                "public class Container {\n"
              + "  static Service create(String implementationName) throws ReflectiveOperationException {\n"
              + "    Class<?> c = Class.forName(implementationName);\n"
              + "    Repository repository = (Repository) c.getDeclaredConstructor().newInstance();\n"
              + "    return new Service(repository);\n"
              + "  }\n"
              + "}\n");
        Path sService = write(source.resolve("caller"), "Service",
                "public class Service {\n"
              + "  private final Repository repository;\n"
              + "  public Service(Repository repository) { this.repository = repository; }\n"
              + "  public String apply(String product, int change) {\n"
              + "    int current = repository.findQuantity(product);\n"
              + "    int updated = current + change;\n"
              + "    repository.updateQuantity(product, updated);\n"
              + "    return product + \": \" + current + \" -> \" + updated;\n"
              + "  }\n"
              + "}\n");
        Path sMain = write(source.resolve("caller"), "Main",
                "public class Main {\n"
              + "  public static void main(String[] args) throws Exception {\n"
              + "    Service service = Container.create(args[0]);\n"
              + "    System.out.println(service.apply(\"screw\", 5));\n"
              + "    System.out.println(service.apply(\"screw\", -2));\n"
              + "    System.out.println(service.apply(\"nail\", 3));\n"
              + "  }\n"
              + "}\n");

        Path sMemory = write(source.resolve("memory"), "MemoryRepository",
                "import java.util.*;\n"
              + "public class MemoryRepository implements Repository {\n"
              + "  private final Map<String, Integer> data = new HashMap<>();\n"
              + "  public int findQuantity(String product) { return data.getOrDefault(product, 0); }\n"
              + "  public void updateQuantity(String product, int quantity) { data.put(product, quantity); }\n"
              + "}\n");
        Path sArray = write(source.resolve("array"), "ArrayRepository",
                "public class ArrayRepository implements Repository {\n"
              + "  private final String[] names = new String[1];\n"
              + "  private final int[] quantities = new int[1];\n"
              + "  private int filled = 0;\n"
              + "  public int findQuantity(String product) {\n"
              + "    for (int i = 0; i < filled; i++) if (names[i].equals(product)) return quantities[i];\n"
              + "    return 0;\n"
              + "  }\n"
              + "  public void updateQuantity(String product, int quantity) {\n"
              + "    for (int i = 0; i < filled; i++) if (names[i].equals(product)) { quantities[i] = quantity; return; }\n"
              + "    if (filled == names.length) throw new IllegalStateException(\"repository capacity full: \" + product + \" could not be added\");\n"
              + "    names[filled] = product; quantities[filled] = quantity; filled++;\n"
              + "  }\n"
              + "}\n");

        Path cInterface = root.resolve("output/interface");
        Path cCaller = root.resolve("output/caller");
        Path cMemory = root.resolve("output/memory");
        Path cArray = root.resolve("output/array");

        compile(cInterface, List.of(), List.of(sInterface));
        compile(cCaller, List.of(cInterface), List.of(sContainer, sService, sMain));
        compile(cMemory, List.of(cInterface), List.of(sMemory));
        compile(cArray, List.of(cInterface), List.of(sArray));

        System.out.println("-- same caller (Container+Service+Main), with the MemoryRepository implementation --");
        System.out.println(run(List.of(cCaller, cInterface, cMemory), "MemoryRepository"));

        System.out.println("-- same caller, without ever recompiling, with the ArrayRepository implementation --");
        System.out.println(run(List.of(cCaller, cInterface, cArray), "ArrayRepository"));
        System.out.println("defect type: error (failed with the exception name, ArrayRepository rejected the call that exceeded its capacity)");
    }
}
```

```
-- same caller (Container+Service+Main), with the MemoryRepository implementation --
screw: 0 -> 5
screw: 5 -> 3
nail: 0 -> 3
-- same caller, without ever recompiling, with the ArrayRepository implementation --
exception: IllegalStateException: repository capacity full: nail could not be added
defect type: error (failed with the exception name, ArrayRepository rejected the call that exceeded its capacity)
```

Nowhere in `Service`'s source does the name `MemoryRepository` or `ArrayRepository` appear —
there is only a `Repository repository` field, and that field's type is the interface.
`Container.create` works the same way: the only information it receives is a string, and it
casts the class it finds with `Class.forName` to `Repository`. These three classes are
compiled in the first `compile` call, in an environment where no implementation is present
on the classpath, and their compiled form is reused across two separate `run` calls. The
only difference is the third component of the classpath list given to `run`.

What confirms that this compilation is genuinely "without recompiling" is that the
`compile(cCaller, ...)` call is made only once. The `cCaller` directory is produced once and
given to both `run` calls exactly as is; before the second `run` call, there is no line
anywhere that goes back to the `Container`, `Service`, or `Main` source and calls `compile`
again. In a real setup, this means an application's deployment package (the part holding the
caller classes) can be produced once, and which implementation gets attached is decided by
the runtime classpath alone — the same `.class` files can be bound to a different repository
with no change in the source code, running no recompilation step at all.

#### The Interface's Promise and the Implementation's Behavior Are Separate

- **WD18.** `MemoryRepository` works on a `HashMap` and places no limit on how many
  distinct `product` names arrive. `ArrayRepository` uses two fixed-length arrays and can
  hold only one distinct `product` name.
- **WD19.** The `Repository` interface states this capacity difference nowhere; neither
  method's signature carries any promise about a limit.

In the first run, `screw` is updated twice (`0 -> 5`, then `5 -> 3`), and then a new record
is opened for a different product (`nail`) (`0 -> 3`). With `MemoryRepository`, all three
calls complete without a hitch. With `ArrayRepository`, the same three calls, in the same
order, are made through the same compiled `Service` class — but the third one falls with an
exception: `ArrayRepository` can hold only one distinct product name, and its capacity fills
once `nail` arrives as a second product name.

This is the lesson's bounding measurement: **the interface's promise and the
implementation's behavior are separate, and the container does not close this gap, it only
hides it.** The `Repository` interface nowhere promises that `updateQuantity` will always
succeed, but in practice, while working with `MemoryRepository`, this promise looks true
without ever being tested — since the limit is never exceeded, the problem never surfaces.
When the same code is bound to `ArrayRepository`, a capacity limit the interface never
promised suddenly becomes visible. The container hides from the caller which implementation
`Service` is working with, but it does not eliminate the behavioral difference between the
implementations — it only defers where that difference will surface, behind the interface,
to runtime.

There is also a reverse side to this distinction: both implementations give byte-for-byte
the same result for the two calls on the `screw` product. This is what the interface really
delivers — a read-write cycle done with a single key behaves the same regardless of
implementation. The problem arises only because an assumption the interface never states
(an unlimited number of distinct keys) holds under one implementation and fails under the
other. An interface promises the methods whose signatures it carries; everything not present
in the signature — capacity, concurrency, latency — is the implementation's own decision,
and the interface never carries it at all.

This also clarifies the container's real contribution in the data access layer. The
container decides which implementation will fill the `Service` class, but it nowhere
guarantees that this decision selected the **correct** implementation — `Container.create`
only learns, at runtime, that the string name it was given really points to a class
implementing `Repository`, and only as long as the cast at the line `(Repository)
c.getDeclaredConstructor().newInstance()` does not fail. Likewise, an implementation
violating an assumption the interface does not carry only surfaces once that assumption is
genuinely tested — a caller using a single product name with `ArrayRepository` would never
see the capacity limit. This lesson's third call carrying a different product name is a
deliberate choice: the limit only becomes visible once two distinct keys reach the same
repository, and this is exactly why testing an interface has to ask not only "is the method
being called" but also "where are the implementation's real limits."

#### Summary

- The `Container`, `Service`, and `Main` classes were compiled once against only the
  `Repository` interface and run against two separate implementations
  (`MemoryRepository`, `ArrayRepository`) without ever being recompiled.
- No implementation class's name appears in the caller's class file; `Container.create`
  finds the implementation only by a string name, through reflection.
- The same three calls completed without a hitch under `MemoryRepository`; under
  `ArrayRepository`, the third call fell with an `IllegalStateException` — the same
  compiled code, the same calls in the same order, two separate outcomes.
- The `Repository` interface carries no guarantee, such as capacity, that is absent from
  its signature; differences like this surface only when the implementation changes.
- The container hides the implementation choice from the caller but does not eliminate the
  behavioral difference between implementations; it only defers that difference to
  runtime.

#### Next Step

This lesson measured which implementation stands behind an interface, but it never asked
how the implementation's own fields match between an object and a repository. The next
lesson takes this up: it measures that the binding between an object's fields and a table's
columns comes from an annotation in the source, and that a field that is never accessed is
never loaded at all.

### Object and Table Binding

Source: https://academia.sh/en/courses/enterprise-application-framework/object-table-binding

Two of an entity object's four fields load at load time, two load on first access. A second access does not read the table again, it returns from the cache; but once the object has detached from the container, accessing a field that was never accessed before cannot reach the table, and the defect silently comes back as an empty value.

The previous lesson measured which implementation stands behind an interface, but it never
asked how the implementation's own fields match between an object and a repository. This
lesson takes this up: it measures that the binding between an object's fields and a table's
columns comes from an annotation in the source, and that a field that is never accessed is
never loaded at all. In the course's terminology decision, this connection is
**object-table binding**; the object itself is an **entity object**.

The table is modeled here too: no real database connection is opened, no real query is run.
A "table" is nothing but a column-name-to-value structure accessed by row identifier —
`Map<Integer, Map<String, Object>>`. An entity object is bound to one of these rows, and
which of the object's fields corresponds to which column is specified in the source with two
annotations: `@Column` for fields that always load, `@LazyColumn` for fields that load only
when accessed.

It should not be forgotten that this distinction is a question of cost. In a real
repository, every column can carry a disk access, a network delay; always reading only two
columns of a row and reading the rest only when needed means never paying anything for
fields of an object that will never be used. This lesson does not measure the cost itself —
no duration or delay is written — but it measures **where the cost falls**: the `loadOrder`
list is a log that records which column was read and when, and this log records every read,
both at load time and at access time, in the same form.

#### Two Fields at Load Time, Two Fields on Access

- **WD20.** `ProductCard` carries four fields: `name` and `quantity` are `@Column`,
  `descriptionCache` and `imagePathCache` are `@LazyColumn`. Field names do not have to
  match column names exactly; the annotation's `name` parameter makes the match
  independent of the field's own name in the source.
- **WD21.** A lazy field's real value is kept in a "cache" field; this field, `null` the
  first time, is never reset again after the first successful load.

When `Container.load` turns a row into an object, it fills only the fields carrying
`@Column`; it never touches the ones carrying `@LazyColumn`. The object carries which
container and which row it belongs to through the `session` and `rowId` fields inherited
from the `EntityObject` superclass — these two are not `@Column`, they are a link the object
container sets up itself.

```java
// Container.java — object-table binding arises from an annotation, a lazy column loads at access time
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Container {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Table { String name(); }

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface Column { String name(); }

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface LazyColumn { String name(); }

    final Map<Integer, Map<String, Object>> table = new LinkedHashMap<>();
    final List<String> loadOrder = new ArrayList<>();

    void addRow(int id, Map<String, Object> columns) { table.put(id, columns); }

    Object readColumn(int id, String columnName, String label) {
        loadOrder.add(columnName + ": " + label);
        return table.get(id).get(columnName);
    }

    <T> T load(Class<T> type, int id) throws ReflectiveOperationException {
        T object = type.getDeclaredConstructor().newInstance();
        for (Field f : type.getDeclaredFields()) {
            f.setAccessible(true);
            Column c = f.getAnnotation(Column.class);
            if (c != null) f.set(object, readColumn(id, c.name(), "at-load-time"));
        }
        Field sessionField = type.getSuperclass().getDeclaredField("session");
        Field idField = type.getSuperclass().getDeclaredField("rowId");
        sessionField.setAccessible(true);
        idField.setAccessible(true);
        sessionField.set(object, this);
        idField.set(object, id);
        return object;
    }

    void detach(EntityObject object) throws ReflectiveOperationException {
        Field sessionField = EntityObject.class.getDeclaredField("session");
        sessionField.setAccessible(true);
        sessionField.set(object, null);
    }

    public static void main(String[] args) throws Exception {
        Container container = new Container();
        container.addRow(1, Map.of("name", "screw", "quantity", 12, "description", "6mm screw with a nut", "imagePath", "image/screw.png"));

        System.out.println("-- loading through the container: only two columns are read at load time --");
        ProductCard product = container.load(ProductCard.class, 1);
        System.out.println("load order after loading: " + container.loadOrder);
        System.out.println("total fields: 4, read at load time: "
                + container.loadOrder.stream().filter(s -> s.endsWith("at-load-time")).count());

        System.out.println("-- accessing a lazy field: the first call reads the table --");
        System.out.println("description: " + product.description());
        System.out.println("description again: " + product.description());
        System.out.println("load order (after description): " + container.loadOrder);
        System.out.println("how many times the table was read for description: "
                + container.loadOrder.stream().filter(s -> s.startsWith("description:") && s.endsWith("at-access-time")).count());
        System.out.println("imagePath was never accessed, never appears in the record: "
                + container.loadOrder.stream().noneMatch(s -> s.startsWith("imagePath")));

        System.out.println("-- lazy field never accessed before detaching from the container --");
        container.detach(product);
        System.out.println("imagePath (first access after detaching from the container): '" + product.imagePath() + "'");
        System.out.println("defect type: silent (no exception, an empty value silently came back)");

        System.out.println("-- manually constructed object: the container is never involved --");
        ProductCard manual = new ProductCard();
        System.out.println("in a manually constructed object, description on first access: '" + manual.description() + "'");
    }
}

class EntityObject {
    transient Container session;
    transient int rowId;

    Object lazyRead(String columnName, String cache) {
        if (cache != null) return cache;
        if (session == null) return "";
        return String.valueOf(session.readColumn(rowId, columnName, "at-access-time"));
    }
}

@Container.Table(name = "product_card")
class ProductCard extends EntityObject {
    @Container.Column(name = "name") String name;
    @Container.Column(name = "quantity") int quantity;
    @Container.LazyColumn(name = "description") String descriptionCache;
    @Container.LazyColumn(name = "imagePath") String imagePathCache;

    String description() {
        descriptionCache = (String) lazyRead("description", descriptionCache);
        return descriptionCache;
    }

    String imagePath() {
        imagePathCache = (String) lazyRead("imagePath", imagePathCache);
        return imagePathCache;
    }
}
```

```
-- loading through the container: only two columns are read at load time --
load order after loading: [name: at-load-time, quantity: at-load-time]
total fields: 4, read at load time: 2
-- accessing a lazy field: the first call reads the table --
description: 6mm screw with a nut
description again: 6mm screw with a nut
load order (after description): [name: at-load-time, quantity: at-load-time, description: at-access-time]
how many times the table was read for description: 1
imagePath was never accessed, never appears in the record: true
-- lazy field never accessed before detaching from the container --
imagePath (first access after detaching from the container): ''
defect type: silent (no exception, an empty value silently came back)
-- manually constructed object: the container is never involved --
in a manually constructed object, description on first access: ''
```

The initial state of `container.loadOrder` carries only two entries: `name` and `quantity`.
`description` and `imagePath` do not appear in this list at all, because `load` never read
them — a four-field object was built by reading only two of the table's columns. This is not
a result that contradicts the source text, it is exactly what the source says: `@Column`
exists on only two fields, `@LazyColumn` on the other two, and `load` fills only the ones
carrying `@Column`. But someone reading the row knows all four of `ProductCard`'s fields
come from a table row; knowing which two come **when** requires reading the body of
`Container.load`.

#### A Lazy Field Loads Only Once

- **WD22.** When the `description()` method is called, it first checks its own cache
  field; if that field is filled, it never goes to the table at all. This is the second
  meaning of the method being "lazy": it loads not merely on first access, but at most
  **once**.

`description()` is called twice, and only one `"description: at-access-time"` entry is
added to the `loadOrder` list. The first call sees that the `descriptionCache` field is
`null`, reads the table, and writes the result into the same field. The second call sees
that the same field is now filled and never goes to the table at all. This is a behavior
from the same family as the singleton scope inside the container — there, a component's
second request returned the same instance; here, a field's second access returns the same
value — but its source is different: singleton scope lived in the container's own rule
table, here the caching is a check written into the `lazyRead` method's own body. Since the
`imagePath` field is never accessed, there is no entry for this column in the `loadOrder`
list at all — the fourth field of a four-field object can go the whole length of the program
without ever being asked of the table. This is lazy loading's real payoff: an object
carrying all of its fields does not mean all of them are actually read, and no cost is paid
for a field that is never read.

Caching carries not only a performance decision but a consistency decision too. If some
other path changed the `description` column of the same row between two calls to
`description()`, the `product` object would never see it — once the cache is filled,
`lazyRead` never looks at the table again. This lesson does not run this scenario, but the
result can be inferred from the source: a lazy field carries only the value it read at the
moment it was **first** read, and the object does not change even if the table changes after
that moment. For a caller that holds an entity object for a long time, this means the load
moment is a genuine snapshot, and nowhere is it promised that this snapshot will remain
correct indefinitely.

#### Lazy Loading Does Not Work Outside the Container: A Silent Gap

- **WD23.** `detach` only sets the `session` field to `null`; the object's other fields,
  including the already-loaded `name` and `quantity`, do not change at all.
- **WD24.** `lazyRead`, when it sees `session == null`, returns an empty string instead of
  throwing an exception. This is the lesson's second silent defect: the access does not
  fail, it merely produces a wrong value.

The call `container.detach(product)` cuts `product`'s link with the container's session — in
a real application, this corresponds to the object being carried outside the container, to
some other layer, once a request has finished processing. Up to this point, `imagePath` had
never been accessed; the first time it is called after `detach`, `lazyRead` now sees that
`session` is `null`, and there is no way left to ask the table. The method returns an empty
string instead of throwing an exception, and the caller cannot tell this empty string apart
from a genuine "there is no image path" state. The defect here is silent too: the program
does not crash, it prints no warning, it just continues with a wrong value.

This is the lesson's bounding measurement: **lazy loading does not work outside the
container; a field accessed after the object has left the container stays empty.** `name`
and `quantity` do not have this problem, because both were filled at load time and are
unaffected by `detach` — the problem surfaces only in lazy fields that were never accessed.
This also reveals a hidden rule of the loading strategy: how long an entity object stays
inside the container decides which of its lazy fields get accessed, and once that access
window closes, any lazy field left unaccessed can never reach its correct value again.

The last measurement tests this rule with an object that never had it set up in the first
place: an object manually constructed with `new ProductCard()` has a `session` field that
was never bound to a container, `null` from the start. Calling `description()` on this
object also gives an empty string — behavior identical to the detached object, the same
silent result. Both paths land on the same point: the object either was never bound to the
container's session or its bond broke later, and in either case accessing the lazy field
ends in the same empty value. The `@LazyColumn` annotation keeps standing on the
`ProductCard` class — it was not removed — but without a session to fulfill it, this once
again confirms the rule seen in the previous lessons: an annotation is a request, and if
there is no party to fulfill the request, nothing happens.

The only difference between the two is how surprising the defect is. In a manually
constructed object, it is already clear from the start that the container is never
involved — a developer constructing this object with `new` has to expect that lazy loading
will not work. But the object that went through `detach` was inside the container a moment
ago; its `name` and `quantity` fields carry correct values, and the object looks "real" in
every respect. This appearance gives the impression that a field never accessed, like
`imagePath`, is just as trustworthy — when in fact that field is still dependent on the
container's session, despite the object's apparent completeness. The bounding measurement's
real value is here: the problem does not surface in some random field, it surfaces exactly
at the intersection of **leaving the container most recently** and **never having been
touched**, and this intersection is marked nowhere in the source text.

#### Summary

- Two of `ProductCard`'s four fields (`name`, `quantity`) were read from the table at load
  time; two (`descriptionCache`, `imagePathCache`) were read only on first access.
- A second access to a lazy field never went to the table at all; when the cache field was
  filled, `lazyRead` returned it directly.
- A lazy field that was never accessed (`imagePath`) was never asked of the table for the
  rest of the program; this is the side of lazy loading that charges no cost for a field
  that is never read.
- When a lazy field that was never accessed was accessed after the object detached from
  the container, the table could not be reached and the method returned an empty string
  instead of an exception; the defect stayed silent.
- Accessing the same lazy field on a manually constructed object gave the exact same empty
  result as the detached object; what both were missing was the container's session.

#### Next Step

This lesson measured when an object's fields load, but it never asked where this loading,
and the changes made on top of it, begin and end. The next lesson takes this up: it
measures that a declarative transaction boundary is an annotation in the source, and how a
component calling itself skips this boundary.

### Transaction Management

Source: https://academia.sh/en/courses/enterprise-application-framework/transaction-management

Two call paths are run side by side, and how many times the declarative transaction boundary opens is counted. A component calling itself skips the proxy: the boundary never opens for the inner call, no error comes out, and this silence is marked nowhere in the source.

The previous lesson measured when an object's fields load, but it never asked where this
loading, and the changes made on top of it, begin and end. This lesson takes this up: it
measures that a declarative transaction boundary is an annotation in the source, and how a
component calling itself skips this boundary. The transaction itself — a set of changes
applying either completely or not at all, and isolation levels — was established as a
concept in the Relational Database Administration course and is not repeated here; this
lesson's question is separate from that: **where** the boundary opens and closes, and what
draws that boundary.

There is no real database connection here either. A **transaction boundary** is modeled as
the increment of a counter called `boundariesOpened` — in a real transaction, this is a
connection being put into transactional mode and then committed or rolled back at the end;
here only the question "how many times did the boundary open" is measured, not what happens
inside an opened boundary. The declarative boundary arises, with no `beginTransaction()`
call anywhere in the source, from a `@Transaction` annotation on top of a method alone.

The word "declarative" describes exactly this: the party that draws the boundary is not the
code that calls the method, it is the annotation standing on top of the method. An earlier
lesson saw this same declaration pattern in the validation rules — a field's constraint
stood in an annotation too, and the party reading it was the container. The difference in
the transaction boundary is this: whether validation applied depended only on which method
was called, while whether the transaction boundary opens depends also on **where the call
comes from**. This difference is the real subject of the rest of the lesson.

#### Two Call Paths, Two Separate Boundary Counts

- **WD25.** `@Transaction` stands on both the `deposit` and `depositAndNotify` methods.
  Both are members of the same implementation class.
- **WD26.** The proxy looks up the called method again in the target object's own class
  and checks for the `@Transaction` annotation there; this is done **before** the
  `m.invoke` call, so the decision to open a boundary is made before the real call.

`AccountImplementation.depositAndNotify` calls `deposit(amount)` inside itself — this is one
method of the same object calling another of its own methods, that is, a
**self-invocation**. `Container.wrap` puts both of these methods behind a proxy, and the
proxy only sees calls coming **from outside**.

The measurement is done with two separate setups, and both use the same implementation
class. The only difference between the setups is which methods the outside caller calls and
how many times; `AccountImplementation`'s own source code stays byte-for-byte the same
across both setups. This differs in one respect from the "two setups" measurements in the
previous lessons: there, what changed was usually the scan order or the implementation
choice; here, what changes is only the pattern of calls coming from outside.

```java
// Container.java — declarative transaction boundary arises from an annotation, self-invocation skips the proxy
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Container {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
    @interface Transaction {}

    static int boundariesOpened = 0;
    static List<String> log = new ArrayList<>();

    @SuppressWarnings("unchecked")
    static <T> T wrap(T target, Class<T> iface) {
        return (T) Proxy.newProxyInstance(
                Container.class.getClassLoader(),
                new Class<?>[]{iface},
                (v, m, args) -> {
                    Method real = target.getClass().getMethod(m.getName(), m.getParameterTypes());
                    if (real.isAnnotationPresent(Transaction.class)) {
                        boundariesOpened++;
                        log.add("boundary-opened: " + m.getName());
                    }
                    try {
                        return m.invoke(target, args);
                    } catch (InvocationTargetException e) {
                        throw e.getCause();
                    }
                });
    }

    public static void main(String[] args) throws Exception {
        AccountImplementation real = new AccountImplementation();
        Account proxy = wrap(real, Account.class);

        System.out.println("-- path A: only depositAndNotify called from outside --");
        System.out.println(proxy.depositAndNotify(100));
        System.out.println("boundaries opened: " + boundariesOpened);
        System.out.println("log: " + log);

        boundariesOpened = 0;
        log.clear();
        real = new AccountImplementation();
        proxy = wrap(real, Account.class);

        System.out.println("-- path B: deposit and depositAndNotify called separately from outside --");
        System.out.println(proxy.deposit(50));
        System.out.println(proxy.depositAndNotify(100));
        System.out.println("boundaries opened: " + boundariesOpened);
        System.out.println("log: " + log);

        System.out.println("-- the inner call itself never saw the proxy --");
        System.out.println("the 'deposit(amount)' call inside AccountImplementation.depositAndNotify "
                + "is made through 'this', not through the proxy");
        System.out.println("confirmation: path B had two calls from outside, and with the inner call "
                + "deposit/depositAndNotify ran three times in total, but the boundary opened only twice");
        System.out.println("defect type: silent (no exception, the inner call ran with no boundary opened at all)");

        System.out.println("-- manually constructed object: the container is never involved --");
        AccountImplementation manual = new AccountImplementation();
        boundariesOpened = 0;
        System.out.println(manual.depositAndNotify(100));
        System.out.println("boundaries opened (proxy never constructed): " + boundariesOpened);
    }
}

interface Account {
    String deposit(int amount);
    String depositAndNotify(int amount);
}

class AccountImplementation implements Account {
    int balance = 0;

    @Container.Transaction
    public String deposit(int amount) {
        balance += amount;
        return "balance=" + balance;
    }

    @Container.Transaction
    public String depositAndNotify(int amount) {
        String result = deposit(amount);
        return result + "; notified";
    }
}
```

```
-- path A: only depositAndNotify called from outside --
balance=100; notified
boundaries opened: 1
log: [boundary-opened: depositAndNotify]
-- path B: deposit and depositAndNotify called separately from outside --
balance=50
balance=150; notified
boundaries opened: 2
log: [boundary-opened: deposit, boundary-opened: depositAndNotify]
-- the inner call itself never saw the proxy --
the 'deposit(amount)' call inside AccountImplementation.depositAndNotify is made through 'this', not through the proxy
confirmation: path B had two calls from outside, and with the inner call deposit/depositAndNotify ran three times in total, but the boundary opened only twice
defect type: silent (no exception, the inner call ran with no boundary opened at all)
-- manually constructed object: the container is never involved --
balance=100; notified
boundaries opened (proxy never constructed): 0
```

In path A there is a single outside call — `proxy.depositAndNotify(100)` — and the boundary
opens once. In path B there are two outside calls — `proxy.deposit(50)` and
`proxy.depositAndNotify(100)` — and the boundary opens twice. The only difference between the
two is how many calls come from outside; `depositAndNotify` calling `deposit` inside itself
is never added to the boundary count in either setup. This is the plainest form of what the
course calls **propagation**: the question of whether an inner call opens its own boundary
or stays inside the outer boundary — here the answer is a third possibility, the inner call
**never entering any boundary at all**, because the proxy never sees it.

#### The Inner Call Never Sees the Proxy

- **WD27.** `wrap` returns only a proxy object implementing the `Account` interface;
  `AccountImplementation` itself never changes, `this` still refers to the real object.
- **WD28.** For the proxy to intercept anything, a call has to pass through the object it
  produced. The `deposit(amount)` call inside `depositAndNotify` never uses this object at
  all.

The reason for this lies in how the proxy works. `Proxy.newProxyInstance` produces a new
object — the proxy — and this object intercepts every call coming from outside, increments
`boundariesOpened`, and then forwards it to the real object (`target`). But when
`deposit(amount)` is written inside `AccountImplementation`'s own source code, this call is
not `target.deposit(amount)`, it is an ordinary Java method call made through the real
object's own `this`. The object itself never has the information "I am behind a proxy" — the
proxy is a separate layer placed around the object, not a part of the object. This is why
the `@Transaction` annotation on `deposit` is never read for this inner call: the only party
that would read it is the proxy's call handler, and the call handler only sees calls that
pass through the proxy.

This is the lesson's bounding measurement: **a component calling itself skips the proxy, and
the annotation never applies at all — because the call comes from inside, wrapping never
engages, no error comes out, no boundary opens.** Path B's log confirms this: `deposit` and
`depositAndNotify` ran three times in total (two from outside, one from inside), but the log
carries only two `boundary-opened` entries. No exception fires, no warning is printed — the
program produces a response that looks entirely normal, `balance=150; notified`. Someone
reading the source, seeing `@Transaction` on top of `deposit`, might assume it opens a
boundary every time it is called; this assumption is wrong for calls coming through
`depositAndNotify`, and the only thing that shows this wrongness is the counter, not any
error message.

The root of this behavior lies in how the proxy is set up. `Container.wrap` **produces** a
new object, it does not **modify** the existing one — not a single line was added to
`AccountImplementation`'s own bytecode, no method was replaced with an intervening call. The
proxy is a runtime-generated instance of a separate class that wraps `target`, and since this
instance implements only the `Account` interface, it is on no path at all except calls
coming through that interface. A call written inside an implementation class sees the only
object that class itself knows: itself. Being unaware of the proxy's existence is not a
shortcoming of the implementation class — the proxy is added entirely from outside, leaving
no trace on the class itself in the first place.

#### The Boundary Never Opens on a Manually Constructed Object

The last measurement is made with an `AccountImplementation` object constructed without ever
calling `wrap`. The call `manual.depositAndNotify(100)` completes normally, returning
`balance=100; notified` — but `boundariesOpened` stays at zero. This is the
transaction-management counterpart of a rule seen repeatedly in the previous lessons: the
`@Transaction` annotation keeps standing on the `AccountImplementation` class, but if no
proxy was ever set up to read it, no boundary ever opens at all. The difference between the
inner call in path B and this manually constructed object is subtle but important: in the
inner call, the proxy **exists**, it does not engage for that call; in the manually
constructed object, the proxy **does not exist at all**. Both produce the same outcome — the
boundary not opening — but one comes from the object container's own rule (only calls coming
from outside get wrapped), the other comes from the container never being involved at all.

What these three setups show together is this: knowing that a declarative boundary exists
does not mean knowing that it will always run. The boundary opening depends on three
conditions all at once — the annotation has to be on the class, the call has to pass through
the proxy, and the call has to come from outside. Unless all three are met, `@Transaction`
being written in the source guarantees nothing.

None of these three conditions is written in one single place in the source text. The
`@Transaction` annotation is inside `AccountImplementation`, the proxy setup is inside
`main`, and whether a call comes from outside or from inside can only be understood by
looking at which variable the call is made through — `proxy.deposit(...)`, or the bare
`deposit(...)` that amounts to `this.deposit(...)`. Holding these three pieces of
information at once is something a code review can miss: someone looking at the body of
`depositAndNotify` can pass over which object the `deposit` call is made through without
noticing, because Java's syntax does not distinguish `deposit(...)` from `this.deposit(...)`
— they are the same call, the same appearance.

#### Summary

- On the path where only `depositAndNotify` was called from outside, the boundary opened
  once; on the path where `deposit` and `depositAndNotify` were called separately from
  outside, the boundary opened twice.
- The `deposit(amount)` inner call inside `depositAndNotify` was never added to the
  boundary count in any setup; the proxy sees only calls coming from outside.
- Because the inner call was made through `this`, it never saw the proxy; even though the
  `@Transaction` annotation kept standing on the class, it was never read for this call.
- The defect was silent: the inner call threw no exception, printed no warning, it just
  completed without opening the expected boundary.
- On the same class called without ever setting up a proxy, the boundary did not open even
  once; the boundary working depends not on the annotation's existence, but on all three
  conditions being met together.

#### Next Step

This topic ends here. This lesson showed that an annotation says where the boundary opens, and
that a call coming from inside never sees that boundary. The next topic asks the same
question somewhere else: in security, testing, and distributed setup, a boundary too — this
time an authorization boundary — is drawn again by an annotation, and whether a call coming
from inside sees it is measured once more.

## Security, Testing and Distributed Setup

Authentication and authorization applied through a filter chain, the information a token in a stateless session carries and does not carry, which part of the container layer tests bring up, health and metrics endpoints that are never written at all, the centralization and refresh of configuration, service discovery and gateway taking over routing, resilience patterns added by annotation.

### Authentication and Authorization

Source: https://academia.sh/en/courses/enterprise-application-framework/authentication-and-authorization

Whether an authorization decision comes from an annotation, from the container's default, or from a check written directly in the source is separated by modeling a filter chain; the lesson runs the case of an endpoint carrying no rule at all and shows that whether it is open or closed is decided by a single parameter given when the container is constructed, a difference that never appears in the source.

The last lesson of the previous topic measured how a transaction boundary opens in the source:
an annotation placed the boundary, and a call the component made from within itself never saw
that boundary at all — once the proxy dropped out, the annotation dropped out with it. This
topic asks the same question for security, testing, and distributed setup; the first stop is
authorization. Someone searching the source for who can reach an endpoint most often finds
nothing at all: no check statement, no role comparison. The decision comes either from an
annotation or from nowhere at all — from the IoC container's own default. The concepts of
authentication and authorization — who is who, how a permission is represented, the difference
between a role and a right — were built in the Authentication and Authorization and the
Identity, Access and Cryptography courses and are not repeated here; this lesson measures only
which mechanism supplies the decision. K06's question does not change for this topic either:
did a call give rise to this behavior in the source, an annotation, or neither? A filter chain
is modeled and three sources are separated: call, annotation, default. The third is the most
dangerous, because the container's default leaves no trace at all in the source; the same
source text produces two different security postures under two different defaults, and this
lesson shows that by running it.

The boundaries of this lesson are drawn from the start as well: the distinction between
authentication (the caller proving who it is) and authorization (deciding what a proven
identity may do) was built in the Authentication and Authorization course; this lesson takes
the caller's identity as an already-resolved role string and measures only the source of the
authorization decision — how the identity was proven is the subject of the next lesson. There
is no real socket, no real request body, no real session store; only the filter chain and the
container that reads the decision are modeled.

#### The Decision the Filter Chain Applies

The modeled container is a small class that maps methods marked `@Endpoint` to a path name;
this is the same mechanism as the container in the Core Concepts topic — there, the annotations
that construct an object were measured, here it is the same container's decision about which
rule a request passes through that is measured. When `Gatekeeper` is constructed it scans every
method of the target object once, mapping the ones carrying `@Endpoint` to a path name; this
scan happens once at construction time, not on every call. When a call arrives, a single-link
filter chain takes over: if the method carries an `@Authorization` mark, it compares the
caller's role against the role in the mark; if there is no mark, it looks at the `defaultOpen`
field given when the container was constructed. There is no real network request, no real
session, no real server — only the chain itself and the reflection that reads the decision.

```java
// Gatekeeper.java — small IoC container that passes a request through a filter chain and gives the authorization decision from either the source annotation or the container's default (helper source, no main)
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
@interface Endpoint { String path(); }

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
@interface Authorization { String role(); }

class Gatekeeper {
    final Object target;
    final boolean defaultOpen;
    final Map<String, Method> endpoints = new LinkedHashMap<>();

    Gatekeeper(Object target, boolean defaultOpen) {
        this.target = target;
        this.defaultOpen = defaultOpen;
        for (Method m : target.getClass().getDeclaredMethods()) {
            Endpoint e = m.getAnnotation(Endpoint.class);
            if (e != null) endpoints.put(e.path(), m);
        }
    }

    // the filter chain's single link: reads the annotation if present, otherwise falls back to the container's default.
    boolean allow(String path, String callerRole) {
        Method m = endpoints.get(path);
        if (m == null) return false;
        Authorization a = m.getAnnotation(Authorization.class);
        if (a == null) return defaultOpen;
        if (a.role().equals("everyone")) return true;
        return a.role().equals(callerRole);
    }

    String call(String path, String callerRole) throws Exception {
        if (!allow(path, callerRole)) return "blocked";
        Method m = endpoints.get(path);
        m.setAccessible(true);
        return (String) m.invoke(target);
    }

    static void observe(String behavior, String source, Object result) {
        System.out.printf("%-40s %-11s: %s%n", behavior, source, result);
    }
}

class Endpoints {
    @Endpoint(path = "/panel") @Authorization(role = "manager")
    String panel() { return "panel-data"; }

    @Endpoint(path = "/status")
    String status() { return "running"; }

    @Endpoint(path = "/version") @Authorization(role = "everyone")
    String version() { return "version-3"; }
}
```

The source of the `Endpoints` class has no line that calls `allow`, nor any that compares
roles. The `@Authorization(role = "manager")` sitting above the `panel` method is only a mark;
the one who reads and applies it is `Gatekeeper`. The annotation types are marked
`@Retention(RetentionPolicy.RUNTIME)`, because the decision is given by reflection at run time,
not by the compiler — an annotation stripped from the class path at compile time leaves
nothing for `Gatekeeper` to read; the target is `ElementType.METHOD`, so the mark can only be
placed on methods, not carried by the class itself or by fields. **ST1.** The filter chain's
single link reads the authorization annotation and compares it against the caller's role; if
there is no annotation, it falls back to the container's own default. **ST2.** At an endpoint
carrying an authorization annotation, no call expression in the source gives rise to the
decision at all — the decision comes from the annotation.

```java
// FilterDecision.java — at an endpoint with an authorization annotation, the decision comes from the filter chain, which compares the caller's role against the annotation
public class FilterDecision {
    public static void main(String[] args) throws Exception {
        Gatekeeper gatekeeper = new Gatekeeper(new Endpoints(), true);
        Gatekeeper.observe("panel, caller manager", "annotation", gatekeeper.call("/panel", "manager"));
        Gatekeeper.observe("panel, caller employee", "annotation", gatekeeper.call("/panel", "employee"));
    }
}
```

```
panel, caller manager                    annotation : panel-data
panel, caller employee                   annotation : blocked
```

The same `gatekeeper` object, the same `call` invocation, the same `/panel` path — the only
thing that changes is the role given as an argument. The call arriving with the manager role
reaches the method and returns its body; the call arriving with the employee role never runs
the method and gets the result `blocked`. The decision sits not in the body of the `panel`
method but in the mark above it, and the party that reads the mark is `Gatekeeper` itself; the
`panel` method itself knows neither the word manager nor asks who is calling. Without rewriting
the method, the same behavior can be opened to a different role by changing only the mark
above it — for example to `role = "employee"` — and this change never touches the body at all.

The path mapping is extracted once when `Gatekeeper` is constructed; `call` does not repeat the
reflection scan on every invocation, it looks at the already-extracted `endpoints` map. When an
unrecognized path is requested, `allow` returns `false` directly — and this too is not a call
but the container's own rule: every path with no entry in the map is rejected without any
annotation being read at all. So there is a third default as well, but its direction is fixed
this time: an unknown path is closed under every condition, independent of the `defaultOpen`
field. This asymmetry between a known and an unknown path is also invisible in the source; it
can only be understood by looking at the contents of the `endpoints` map itself.

#### When No Rule Is Written: The Direction of the Default

The `status` method in the `Endpoints` class carries no authorization mark at all. This is the
case where someone reading the source finds nothing — and this is exactly why the most
restrictive measurement lies here. When `allow` finds no mark, it looks at the `defaultOpen`
field; this field is a constructor parameter given when `Gatekeeper` is constructed, a value
that appears nowhere in the source. The difference between the two setups is only this single
`boolean` value: no class is rewritten, no annotation is added.

```java
// DefaultDirection.java — an endpoint whose source never changes produces two different results from a single parameter given when the container is constructed
public class DefaultDirection {
    public static void main(String[] args) throws Exception {
        Gatekeeper openGatekeeper = new Gatekeeper(new Endpoints(), true);
        Gatekeeper closedGatekeeper = new Gatekeeper(new Endpoints(), false);

        Gatekeeper.observe("status, open-setup", "default", openGatekeeper.call("/status", "guest"));
        Gatekeeper.observe("status, closed-setup", "default", closedGatekeeper.call("/status", "guest"));
        System.out.println("source unchanged: Endpoints.status() carries no authorization mark");
    }
}
```

```
status, open-setup                       default    : running
status, closed-setup                     default    : blocked
source unchanged: Endpoints.status() carries no authorization mark
```

`Endpoints.java` did not change by a single character between the two runs; `openGatekeeper`
and `closedGatekeeper` are constructed from the same class. Even so, the same call with the
guest role returns data under one setup and is rejected under the other. **ST3.** At an
endpoint carrying no authorization annotation, the decision comes from the container's own
default; the source text shows neither of these two directions. **ST4.** The same source,
without being recompiled, can produce two different security postures from only a constructor
parameter given when the container is constructed — this is the lesson's limiting measurement.
An unmarked endpoint is neither secure nor insecure; which one it is can be understood not by
reading the source but by knowing which default the container was constructed with. This is the
lesson's most important observation: calling the difference between the two endpoints by a
fixed adjective such as "open" or "closed" is misleading, because the adjective belongs not to
the source but to the setup decision.

This decision does not belong to the party that wrote the `Endpoints` class either; whoever
constructs `Gatekeeper` holds the default in their hands. In a class with many endpoints, a
single method for which the authorization annotation was forgotten stays silently reachable
under a default-open setup — there is no error, no warning in the source, because a forgotten
mark leaves no trace at all by definition. Under a default-closed setup, the same oversight
works in the opposite direction: every unmarked method becomes unreachable, and the omission is
noticed only by the caller's observation of being "unexpectedly blocked." Both directions fill
the same gap, but the result they fill it with is exactly opposite; which direction was chosen
is written once, on the line that constructs the container, and it covers the entire unmarked
surface at once.

If the direction of the default does not appear in the source, can an annotation override it?
The `@Authorization(role = "everyone")` above the `version` method attempts exactly that: not a
specific role name, but an open-door mark.

```java
// ExplicitlyDeclared.java — an endpoint carrying an open-to-everyone mark passes through the annotation even when the container's default is closed
public class ExplicitlyDeclared {
    public static void main(String[] args) throws Exception {
        Gatekeeper closedGatekeeper = new Gatekeeper(new Endpoints(), false);
        Gatekeeper.observe("version, closed-setup", "annotation", closedGatekeeper.call("/version", "guest"));
        Gatekeeper.observe("status, closed-setup", "default", closedGatekeeper.call("/status", "guest"));
    }
}
```

```
version, closed-setup                    annotation : version-3
status, closed-setup                     default    : blocked
```

Two calls on the same `closedGatekeeper` object: `version` was declared open to everyone by an
annotation, so it overrides the default and passes; `status` carries no mark at all, so it
falls to the same container's closed default. When an annotation exists, the default never
comes into play at all — the order reaches the default only if there is no annotation. This
shows the precedence between the two sources: the annotation is read first, and only if it is
absent does the flow fall back to the default; a rule written in the source wins independent of
the direction of the default. This order of precedence, too, sits not in the source but in the
body of the `allow` method itself — whoever wrote `Gatekeeper` decided in advance which source
gets asked first.

#### Manually Written Authorization Checks

A third source sits in a class separate from the previous two: `ManualAuthorization`, an
ordinary method that never enters the filter chain, carrying neither `@Endpoint` nor
`@Authorization`. Here the decision is given not by the container but by a check expressed
directly in the method's own body.

```java
// ManualAuthorization.java — the authorization decision comes not through the IoC container or an annotation, but directly from a check written in the source (helper source, no main)
class ManualAuthorization {
    String sensitiveOperation(String callerRole) {
        if (!"manager".equals(callerRole)) throw new SecurityException("unauthorized access");
        return "sensitive-data";
    }

    String attempt(String callerRole) {
        try { return sensitiveOperation(callerRole); }
        catch (SecurityException e) { return e.getClass().getSimpleName(); }
    }
}
```

```java
// ManualCheck.java — called with the same two roles, showing that the decision comes from a condition in the source
public class ManualCheck {
    public static void main(String[] args) {
        ManualAuthorization manual = new ManualAuthorization();
        Gatekeeper.observe("sensitiveOperation, caller manager", "call", manual.attempt("manager"));
        Gatekeeper.observe("sensitiveOperation, caller employee", "call", manual.attempt("employee"));
    }
}
```

```
sensitiveOperation, caller manager       call       : sensitive-data
sensitiveOperation, caller employee      call       : SecurityException
```

The result looks the same as in the `FilterDecision` lesson — a manager passes, an employee is
rejected — but its source differs. **ST5.** A manually written check gives the decision
directly from the source; neither an annotation nor the container comes in between, the
decision comes from the comparison sitting in the first line of the `sensitiveOperation`
method. This third path lives in a class `Gatekeeper` never manages;
`ManualAuthorization` never registered with it, so neither the annotation nor the default
direction applies to it — the same boundary observed in the previous topic's closing lesson
holds here as well: an object left outside the container receives none of its mechanisms.

The three separate sources shown by the three lessons sit side by side here:

| Source | Where the decision is made | Its example in this lesson |
|---|---|---|
| annotation | the `@Authorization` mark, read by `Gatekeeper` | `panel`, `version` |
| default | `defaultOpen`, given when `Gatekeeper` is constructed | `status` |
| call | the check expressed in the method's own body | `sensitiveOperation` |

The same question of "manager or not" is answered in three different ways from three different
places, and someone who only reads the source — even seeing the annotations — sets out without
knowing which answer produces which behavior. For `status` specifically this lack of
information is even sharper: there is no mark to read there at all, the decision belongs
entirely to the moment the container is constructed. None of these three sources is "correct"
or "incorrect"; each is held in a different place, and the person reading the source needs to
know in advance which places to look — the measurement axis built through this course exists
precisely to make this need to know countable.

The separation also concerns scale: an annotation and a default cover an entire class or
container with a single decision, while a manual check covers only the method it is written
in. Ten methods like `sensitiveOperation` would repeat the same comparison ten times; the
mark-based approach removes this repetition but moves the decision from the source to the
container. This lesson took the caller's identity as an already-resolved role string —
`"manager"`, `"employee"`, `"guest"` — and never asked where that string came from.

#### Summary

- At an endpoint carrying an authorization annotation, the filter chain reads the decision from
  the mark and compares it against the caller's role; the source of this behavior is the
  annotation.
- At an endpoint carrying no authorization annotation, the decision comes from the container's
  own default; the source text never shows this behavior at all.
- The same source, without being recompiled, can produce two different security postures from
  only a parameter given when the container is constructed — this is the limiting measurement.
- When an annotation is present, the default never comes into play at all; the order of
  precedence is first the annotation, then the default.
- A manually written check gives the decision directly from the source; because this method
  never registers with the container, neither the annotation nor the default direction applies
  to it.

#### Next Step

This lesson took the caller's role as a ready-made string — who it was was already a known
input. A real caller has to carry this information from somewhere, and this course does not
keep a session on the server side. The next lesson models how this role and identity come not
from state stored on the server, but from a token the caller brings along with every request.

### Token-Based Authentication

Source: https://academia.sh/en/courses/enterprise-application-framework/token-based-authentication

In a stateless session, everything about identity stops at the claims a token carries; there is no session record on the server side at all. Whether an authorization question is answered by an annotation or by a comparison in the source is separated by running it; the limiting measurement shows, by count, how many steps a revoked token keeps being treated as valid until it expires, because the validator never looks at any revocation list at all.

The previous lesson took the caller's role as a ready-made string and never asked where that
string came from. This lesson asks exactly that question. A server can know who a caller is in
one of two ways: either it keeps a record for that caller on its own side for every request, or
it stores everything about identity in a token the caller carries, and keeps nothing on its own
side at all. The second is a **stateless session**: the server's memory holds zero records
about the caller, and the identity of every request arrives inside that request itself. This
lesson models the token — without writing a real key, a real signature algorithm, or a real
cryptography routine — and measures two things: the number of claims the token carries, and
whether the answer to an authorization question comes from an annotation or from a call in the
source. The limiting measurement is saved for last, because statelessness has a cost, and this
cost is visible only by running the token through its lifetime.

In a session-based approach the server keeps a record for every caller; deleting the record
invalidates the caller instantly, because every request already looks at that record. The
stateless approach never performs that lookup at all — proof of identity arrives with every
request, and there is no record to query on the server side. This difference is not just an
implementation detail; it is exactly what this lesson measures.

#### The Claims a Token Carries

The modeled token carries two fields: a subject (the caller's identity) and a map of claims —
every piece of information carried about the subject is a key-value pair in this map. There is
a third field, an expiry, but it is not a real timestamp: it is an integer that counts steps,
used later in the lesson. There is no real clock, no real duration, and no real signature; the
token itself is modeled as an ordinary data carrier.

```java
// Token.java — stateless data carrier holding everything about identity; expiry is not real time, it is an integer that counts steps (helper source, no main)
import java.util.*;

record Token(String subject, Map<String, String> claims, int expiry) { }

class Validator {
    // stateless validation: looks only at the token's own fields and the given moment,
    // never accessing any shared state (a revocation list, a session store).
    static boolean valid(Token t, int now) {
        return now <= t.expiry();
    }

    static void show(String label, Object value) {
        System.out.printf("%-42s: %s%n", label, value);
    }
}
```

```java
// ClaimCount.java — the number of claims carried by two tokens is counted; the number of session records kept server-side is zero
import java.util.*;

public class ClaimCount {
    public static void main(String[] args) {
        Token guest = new Token("user-7", Map.of("role", "guest"), 10);
        Token manager = new Token("user-2",
                Map.of("role", "manager", "department", "IT", "level", "3"), 10);

        Validator.show("guest token, number of claims carried", guest.claims().size());
        Validator.show("manager token, number of claims carried", manager.claims().size());
        Validator.show("number of session records kept server-side", 0);
    }
}
```

```
guest token, number of claims carried     : 1
manager token, number of claims carried   : 3
number of session records kept server-side: 0
```

The `guest` token carries one claim, `manager` carries three. This count is not fixed — every
token is as heavy as the information it carries, and there is no structure on the server side
that holds this information a second time. **ST6.** The token carries all information about
identity within its own claims, with no session record kept on the server side at all; this is
the definition of a stateless session. **ST7.** The number of claims carried can vary from
token to token, but the count of records kept on the server side is always zero — there is no
structure here like the `endpoints` map of the `Gatekeeper` measured in the previous lesson,
because there is no caller state left to measure in the first place.

A real token system protects its claims with a signature — a validation step that checks
whether the token's content has been tampered with. This lesson does not write that step;
signatures, keys, and cryptography routines were built in the Identity, Access and Cryptography
course and are not repeated here. Here `Token` is an ordinary record, unsigned and produced
directly; what is measured is not the signature itself but the two questions that remain even
when the signature is correct — how much information a claim carries, and how that information
is read.

The main gain of statelessness in this topic is that the party validating identity needs no
shared record at all. Which component handles a given call becomes irrelevant, because every
component can validate the token on its own, using only the information it already holds — it
never needs to ask a previous component for a record it kept. This property will reappear in
the next two lessons: the distributed configuration lesson will ask whether multiple components
see the same value, and the service discovery lesson will find that which component a call
reaches is never fixed at all. Stateless identity is one reason that variability never becomes
a problem.

#### The Source of Validation: Annotation or Call

Whether the claims a token carries are true is a separate question; this lesson measures only
how claims are **read** — where does the answer to "is the role claim manager" come from? Two
paths are modeled. The first is an annotation: a `@RequiredClaim` mark placed on a method
carries the required key and the expected value, and the party that reads and compares it is
not the caller but the check class itself.

```java
// RequiredClaim.java — annotation type stating which claim, with which value, a token must carry for a method to pass (helper source, no main)
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
@interface RequiredClaim { String name(); String value(); }
```

```java
// ClaimCheck.java — check that reads the annotation and compares it against the token's matching claim (helper source, no main)
import java.lang.reflect.*;

class ClaimCheck {
    static boolean matches(Method m, Token t) {
        RequiredClaim r = m.getAnnotation(RequiredClaim.class);
        if (r == null) return true;
        return r.value().equals(t.claims().get(r.name()));
    }
}
```

```java
// HiddenEndpoints.java — the mark above the panel method states which claim is required (helper source, no main)
class HiddenEndpoints {
    @RequiredClaim(name = "role", value = "manager")
    String panel() { return "panel-data"; }
}
```

The second path is a comparison written directly in the source; no mark is read, no reflection
runs.

```java
// ManualValidation.java — the same question is answered from a comparison written directly in the source (helper source, no main)
class ManualValidation {
    static boolean isManager(Token t) {
        return "manager".equals(t.claims().get("role"));
    }
}
```

```java
// ValidationSource.java — with the same two tokens, the annotation-based and the call-based check are compared, producing the same result
import java.lang.reflect.*;
import java.util.*;

public class ValidationSource {
    public static void main(String[] args) throws Exception {
        Token guest = new Token("user-7", Map.of("role", "guest"), 10);
        Token manager = new Token("user-2", Map.of("role", "manager"), 10);

        Method panel = HiddenEndpoints.class.getDeclaredMethod("panel");

        Validator.show("annotation: panel, guest token", ClaimCheck.matches(panel, guest));
        Validator.show("annotation: panel, manager token", ClaimCheck.matches(panel, manager));
        Validator.show("call: manualValidation, guest token", ManualValidation.isManager(guest));
        Validator.show("call: manualValidation, manager token", ManualValidation.isManager(manager));
    }
}
```

```
annotation: panel, guest token            : false
annotation: panel, manager token          : true
call: manualValidation, guest token       : false
call: manualValidation, manager token     : true
```

The results of the four lines are pairwise identical: `ClaimCheck.matches` and
`ManualValidation.isManager` give the same two answers for the same two tokens. **ST8.** The
same "is the role manager" question can be answered by reading an annotation: the source of the
`panel` method contains neither the word "role" nor a comparison, the decision sits in the
`@RequiredClaim` mark. **ST9.** The same question can also be answered from a comparison written
in the source: the first line of the `isManager` method carries the `claims().get("role")` call
and the comparison directly. Two sources produce the same result, but someone reading the
source of `HiddenEndpoints` never sees the word "manager" at all, while someone reading the
source of `ManualValidation` finds it in the first line.

`ClaimCheck.matches` also has a third case: when there is no `@RequiredClaim` mark at all, the
method returns `true` directly — the same as the default-open setup measured in the previous
lesson, here in a one-line shortcut. This shows the real difference between annotation and
call: annotation-based checking is defined in one place for the whole `HiddenEndpoints` class,
granting access by default when a new method is added and requiring a mark only where needed.
In call-based checking, every new method must write its own comparison; if a method forgets
this line, the omission appears nowhere at all — the source compiles cleanly, runs, and stays
unchecked.

#### The Cost of Statelessness: A Revoked Token's Lifetime

`Validator.valid` looks at only two things: the token's own `expiry` field and the `now` value
given to it. This gives rise to the lesson's most limiting measurement: even if a token is
"revoked," if the validator has no revocation list to look at, the revocation record never
affects the token's validity at all. An application can keep its own revocation list on its own
side, but `valid` never accesses that list at all — if it did, it would stop being stateless and
become a validator that looks at a shared structure on every call.

```java
// RevocationAndValidity.java — a revoked token keeps being counted as valid until it expires, because the validator never looks at the revocation list at all
import java.util.*;

public class RevocationAndValidity {
    public static void main(String[] args) {
        Token token = new Token("user-9", Map.of("role", "employee"), 10);
        Set<String> revoked = new HashSet<>();

        Validator.show("before revocation, moment=0, valid", Validator.valid(token, 0));

        revoked.add(token.subject());
        Validator.show("subject in revocation list", revoked.contains(token.subject()));

        int validRemainingSteps = 0;
        for (int moment = 1; moment <= 15; moment++) {
            if (Validator.valid(token, moment)) validRemainingSteps++;
        }
        Validator.show("steps validator counts as valid after revocation", validRemainingSteps);
        Validator.show("moment=11, valid (expired)", Validator.valid(token, 11));
    }
}
```

```
before revocation, moment=0, valid        : true
subject in revocation list                : true
steps validator counts as valid after revocation: 10
moment=11, valid (expired)                : false
```

The `revoked` set holds `token.subject()` — the application made a decision about this subject.
But `Validator.valid` knows nothing about this set's existence; it is never given as a
parameter, never queried at all. **ST10.** Stateless validation looks only at the token's own
fields and the given moment; it has no access to shared state such as a revocation list. **ST11.**
A revoked token keeps being counted as valid until it expires, because the validator never
looks at the revocation list — in this lesson that duration is ten steps, and at the eleventh
step `valid` returns `false` on its own because `expiry` is exceeded. This ten-step window comes
from the token's own `expiry` value; a shorter expiry narrows this window, but never to zero. To
verify this without leaving it as a bare claim, the same measurement is repeated with a shorter
expiry.

```java
// ShortWindow.java — when the expiry is shortened, the number of steps counted as valid after revocation shrinks by the same ratio
import java.util.*;

public class ShortWindow {
    public static void main(String[] args) {
        Token shortLived = new Token("user-11", Map.of("role", "employee"), 3);

        int validRemainingSteps = 0;
        for (int moment = 1; moment <= 15; moment++) {
            if (Validator.valid(shortLived, moment)) validRemainingSteps++;
        }
        Validator.show("expiry=3 token, valid steps counted after revocation", validRemainingSteps);
    }
}
```

```
expiry=3 token, valid steps counted after revocation: 3
```

When `expiry` is given as three instead of ten, the number of steps counted as valid after
revocation also becomes three instead of ten — the window narrows, proportionally. But it never
reaches zero, because the `valid` call itself is still only a number comparison; for the
validator to see revocation instantly, it would need to look at shared state, and adding that
look would take it out of being stateless. This is the lesson's limiting measurement:
statelessness is not free, its cost is measured by the delay of revocation, and this delay —
even as it shrinks with a shorter expiry — never reaches zero.

Placed side by side, the lesson's three measurements repeat the same pattern: information that
sits, or never sits, in the source only turns into a number by being run.

| Measurement | Observed in this lesson |
|---|---|
| number of claims (guest / manager) | 1 / 3 |
| validation source (annotation / call) | both produce the same result |
| valid steps remaining after revocation (expiry 10) | 10 |
| valid steps remaining after revocation (expiry 3) | 3 |

The last two rows run the same mechanism with two different parameters and show that the cost
is not fixed but proportional to the expiry — a relationship that emerges not from a number
written in the source but from comparing two runs.

#### Summary

- The token carries information about identity within its own claims with no session record
  kept on the server side; the number of claims carried varies from token to token.
- An authorization question can be answered by reading an annotation (`@RequiredClaim`); the
  decision gives rise to no call expression in the source.
- The same question can also be answered by a comparison written in the source (a call); the
  two sources produce the same result, from different places.
- Stateless validation looks only at the token's own fields and the given moment; it has no
  access to any shared state, such as a revocation list.
- A revoked token keeps being counted as valid until it expires, because the validator never
  looks at the revocation list; this is a cost whose duration in steps can be counted.

#### Next Step

These two lessons measured the source of the authorization decision through a single call or a
single token. How many components a container actually brings up, and which parts a given test
sets up, has not yet been asked at all. The next lesson puts the same container into a test
setting and counts two setups side by side: how many components a slice test brings up, how
many a full-context test brings up — and what happens to an annotation left outside the slice.

### Layer Tests

Source: https://academia.sh/en/courses/enterprise-application-framework/layer-tests

The difference between a slice test and a full context test is not an adjective but a number; the same container passing through two setups is placed side by side by counting how many components each one builds. The limiting measurement shows that even when a component's setup annotation left outside the slice never runs at all, the slice test's result never changes, and the test still passes.

The previous lesson measured the claims a token carries and the source of validation, through a
single call. This lesson steps back and enlarges the question: how many components does a
container actually bring up, and which of these does a given test actually build? Two test
setups are modeled — a slice test and a full context test — and the difference between them is
measured not by an adjective but by a number: the number of components the container builds. How
many components does the slice test require, how many does the full context test build, and
does the slice test notice when something outside the slice breaks? The scope and purpose of
layer tests were built in the Integration, Contract and End-to-End Testing and the Build,
Testing and the Ecosystem courses and are not repeated here; this lesson only shows, by number,
how different the two setups are from the container's point of view.

The word "layer" here points to the layers in the Web and Data Layer topic — controller,
service, data access. A slice test usually targets only one of these layers and replaces the
layer above or below it with a fake implementation; this lesson shows how that substitution is
done through the IoC container and how the result can be counted.

#### Placing Two Setups Side by Side: Component Count

The modeled application is built from four parts: a data store interface and its real
implementation, a service interface and its real implementation, a controller depending on that
service, and an independent logger with no relation to the service at all. The same `Container`
class is the same mechanism as the IoC container built in the Core Concepts topic — it resolves
its constructor dependencies by reflection, calling `@Setup`-marked methods after construction —
here only the part used in this lesson is rewritten.

```java
// Setup.java — annotation type marking a method to be called after construction (helper source, no main)
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
@interface Setup { }
```

```java
// Container.java — small IoC container that resolves its constructor dependencies by reflection and tracks the count of every component it builds (helper source, no main)
import java.lang.reflect.*;
import java.util.*;

class Container {
    final Map<Class<?>, Class<?>> bindings = new HashMap<>();
    final List<Object> built = new ArrayList<>();

    Container bind(Class<?> iface, Class<?> impl) { bindings.put(iface, impl); return this; }

    @SuppressWarnings("unchecked")
    <T> T build(Class<T> type) throws Exception {
        Class<?> real = type.isInterface() ? bindings.get(type) : type;
        Constructor<?> ctor = real.getDeclaredConstructors()[0];
        Class<?>[] paramTypes = ctor.getParameterTypes();
        Object[] arguments = new Object[paramTypes.length];
        for (int i = 0; i < arguments.length; i++) arguments[i] = build(paramTypes[i]);
        ctor.setAccessible(true);
        Object instance = ctor.newInstance(arguments);
        for (Method m : real.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Setup.class)) { m.setAccessible(true); m.invoke(instance); }
        }
        built.add(instance);
        return (T) instance;
    }

    static void show(String label, Object value) {
        System.out.printf("%-52s: %s%n", label, value);
    }
}
```

The application's four components sit in separate files. `RealService` carries a setup hook —
the `status` field starts at `not-ready`, and turns to `ready` once the `@Setup`-marked
`prepare` method runs. `FakeService`, by contrast, is the fake the slice test holds in place of
the real service — it carries no setup hook at all, because there is no need for one. Its
constructor is also empty: `RealService` requires a `DataStore`; `FakeService` requires nothing.
This difference also shrinks the slice test's component count — building `FakeService` never
triggers `InMemoryStore` at all, because there is no dependency to resolve.

```java
// DataStore.java — the interface for data access (helper source, no main)
interface DataStore { String read(); }
```

```java
// InMemoryStore.java — the real implementation of the data store (helper source, no main)
class InMemoryStore implements DataStore {
    public String read() { return "data"; }
}
```

```java
// Service.java — the interface for the business logic (helper source, no main)
interface Service { String process(); }
```

```java
// RealService.java — the real implementation of the service; its status stays 'not-ready' until the setup hook runs (helper source, no main)
class RealService implements Service {
    final DataStore store;
    String status = "not-ready";

    RealService(DataStore store) { this.store = store; }

    @Setup void prepare() { status = "ready"; }

    public String process() { return status + ":" + store.read(); }
}
```

```java
// FakeService.java — the fake implementation held by the slice test, carrying no setup hook at all (helper source, no main)
class FakeService implements Service {
    public String process() { return "fake-data"; }
}
```

```java
// Controller.java — depends on the service, the slice test's actual target (helper source, no main)
class Controller {
    final Service service;
    Controller(Service service) { this.service = service; }
    String respond() { return service.process(); }
}
```

```java
// Logger.java — has no dependency on the service at all, an independent part of the full context (helper source, no main)
class Logger {
    String status() { return "ready"; }
}
```

Two separate containers are built: `sliceContainer` binds `Service` to `FakeService`;
`fullContainer` binds the same interface to `RealService`. Both ask for the same `Controller`,
both call the same `build` method — the only difference is in the binding table.

```java
// ComponentCount.java — with the same Controller, how many components are built when requested from two different containers
public class ComponentCount {
    public static void main(String[] args) throws Exception {
        Container sliceContainer = new Container()
                .bind(Service.class, FakeService.class)
                .bind(DataStore.class, InMemoryStore.class);
        Controller sliceController = sliceContainer.build(Controller.class);

        Container fullContainer = new Container()
                .bind(Service.class, RealService.class)
                .bind(DataStore.class, InMemoryStore.class);
        Controller fullController = fullContainer.build(Controller.class);
        fullContainer.build(Logger.class);

        Container.show("slice test, number of components built", sliceContainer.built.size());
        Container.show("full context test, number of components built", fullContainer.built.size());
        Container.show("slice test, Controller.respond()", sliceController.respond());
        Container.show("full context test, Controller.respond()", fullController.respond());
    }
}
```

```
slice test, number of components built              : 2
full context test, number of components built       : 4
slice test, Controller.respond()                    : fake-data
full context test, Controller.respond()             : ready:data
```

**ST12.** Both the slice test and the full context test pass through the same `Container`
class; the difference between them is measured not in the structure of the code but in the
number of components the container builds. **ST13.** The slice test even builds the direct
dependency of the `Controller` it tests through the IoC container, using a fake implementation
— `FakeService` also goes into the `built` list — but the total component count is still lower
than the full context test's: two rather than four. **ST14.** The full context test builds the
entire dependency chain (`RealService`, `InMemoryStore`) as well as independent components that
never enter the chain at all (`Logger`); even a component the `Controller` has no need for
becomes part of the full context, because the full context test brings up the entire
application.

The `build` method itself does not know at all which test is behind which setup; all it knows
is the binding table and the constructor signatures. The same method, on seeing a `Service`
parameter, looks at the `bindings` map and builds whatever it finds there — `FakeService` for
`sliceContainer`, `RealService` for `fullContainer`. The difference is in the test's intent, not
in the code itself. This is also why the component count matters: the slice test runs fast
because it builds few components, since every component built by reflection carries a
constructor resolution and, if needed, a setup hook call; the full context test does all of
this, and a component count that quadruples means roughly a quadrupled build cost.

#### An Annotation Left Outside the Slice

`RealService`'s setup hook runs only when the IoC container builds it. If the same class is
constructed by hand with `new`, the `@Setup`-marked `prepare` method never runs at all — this
is the same limiting reading built in the Core Concepts topic, measured again here on a service
class.

```java
// OutsideTheSlice.java — RealService's setup hook never runs at all outside the container; the slice test is entirely independent of it
public class OutsideTheSlice {
    public static void main(String[] args) throws Exception {
        Container sliceContainer = new Container()
                .bind(Service.class, FakeService.class)
                .bind(DataStore.class, InMemoryStore.class);
        Controller sliceController = sliceContainer.build(Controller.class);

        Container fullContainer = new Container()
                .bind(Service.class, RealService.class)
                .bind(DataStore.class, InMemoryStore.class);

        RealService manuallyBuilt = new RealService(new InMemoryStore());
        Service builtByContainer = fullContainer.build(Service.class);

        Container.show("RealService, manually built, @Setup never ran", manuallyBuilt.process());
        Container.show("RealService, built via container, @Setup ran", builtByContainer.process());
        Container.show("slice test, Controller.respond() (independent of RealService)", sliceController.respond());
    }
}
```

```
RealService, manually built, @Setup never ran       : not-ready:data
RealService, built via container, @Setup ran        : ready:data
slice test, Controller.respond() (independent of RealService): fake-data
```

The first two lines show two different ways of constructing the same class: built by hand it is
`not-ready:data`, built through the container it is `ready:data` — the annotation applies only
to the second. **ST15.** A setup annotation is applied only when the IoC container builds it;
the same class built by hand never sees the annotation at all, because the party that reads and
invokes the annotation is the container itself. The third line is the lesson's real point: the
slice test's result — `fake-data` — is entirely independent of whether `RealService` was built
by hand or through the container, because the slice test never sees `RealService` at all. Even
if `RealService`'s setup hook broke, was removed, or the class itself was deleted entirely, the
slice test would never know.

**ST16.** Even when a component left outside the slice never has its annotation applied, the
slice test's result never changes, and the test still passes — this is the lesson's limiting
measurement. The slice test validates whether `Controller` calls `Service` correctly; it never
validates whether `RealService`'s setup hook works at all, because that lies outside its
verification surface. Building fewer components means verifying less — the slice test is fast
and focused, but every annotation, every setup hook belonging to `RealService` sits entirely
outside the slice's field of view. The common definition's defect classification covers this
case exactly: from the slice test's point of view, `RealService`'s broken setup hook is neither
an `error` nor `silent` — it is entirely **ineffective** from the test's own perspective,
because the test has no awareness that the hook even exists. The only thing that closes this
gap is the full context test, because only that setup actually brings `RealService` up and can
check whether its hook ran.

The two test types do not substitute for one another; they answer different questions:

| Question | Slice test | Full context test |
|---|---|---|
| Number of components built | 2 | 4 |
| Is it verified that `Controller` calls `Service` correctly | yes | yes |
| Is `RealService`'s setup hook verified | no | yes |
| Is `Logger`'s existence verified | no | yes |

The table comes down to a single conclusion: the slice test's cheapness comes with a gap, and
this gap is not random — every component the container never built in that test sits outside
the slice's field of view. If a codebase is protected only by slice tests, the real connection
between components is never actually exercised by running it; only the full context test, at a
cost proportional to the number of components it builds, can close that gap.

In practice a codebase keeps both, and not for the same purpose: slice tests run often on every
change, because building few components makes them fast, and they quickly validate the behavior
of a single layer such as `Controller`; full context tests run less often, because building more
components makes them slower, but in exchange they test the real connection between components.
The number measured in this lesson — two in the slice, four in the full context — is on a small
example; in a real application this ratio can range from the tens into the hundreds, and as the
gap grows, so does the surface the slice test never validates.

#### Summary

- The slice test and the full context test pass through the same container; the difference
  between them is measured by the number of components the container builds.
- The slice test can build even the direct dependency of the class it tests with a fake
  implementation; even so, the total component count stays lower than the full context test's.
- The full context test builds the entire dependency chain as well as independent components
  that never enter the chain at all.
- A setup annotation is applied only when the IoC container builds it; the same class built by
  hand never sees the annotation at all.
- Even when a component left outside the slice never has its annotation run, the slice test's
  result never changes, and the test still passes; building fewer components means verifying
  less.

#### Next Step

This lesson counted which components were built; the next lesson asks about something never
built, never written at all. There are endpoints that ask from the outside whether an
application is in working order, and most of these endpoints never appear in the source at all
— no class, no method, no annotation. The next lesson measures how many of these endpoints
exist without ever being written, and which information gets given out to the outside.

### Health and Metrics Endpoints

Source: https://academia.sh/en/courses/enterprise-application-framework/health-and-metrics-endpoints

An application comes up with endpoints that exist without ever being written — the container itself adds them; a new metrics endpoint can be added with an annotation. The limiting measurement closes in a separate narrowing section — the default endpoints the container builds do not open every piece of information at all, which field gets given out depends on an explicit mark, and if that mark is placed on the wrong field, the container gives it out too, with no error at all.

The previous lesson counted which components were built — the slice test built two, the full
context test built four, and both were the result of a decision written in the source: the
binding table. This lesson asks about something never built, never written at all: there are
endpoints that ask from the outside whether an application is in working order, and most of
these endpoints never appear in the source at all — no class, no method, no annotation. The
concept of health and metrics endpoints and the general framework of observability were built
in the Observability and Operations course and are not repeated here; this lesson measures only
how much trace these endpoints leave in the source and which information gets given out. No
real metric value, no real duration, and no real memory usage is written; what is counted is the
endpoint count and field presence. The third part of the measurement sits in a separate
narrowing section, because the existence of default endpoints does not mean every piece of
information gets given out — that is a separate decision.

In a real application a health endpoint usually returns a single word — is it working or not —
and a metrics endpoint carries counters, rates, internal state. This lesson models both, but the
question it asks is not the difference between health and metrics; it is what both have in
common: the ability to exist without a call in the source. `/health` is one health endpoint,
every endpoint carrying the `/metrics/…` prefix is a metrics endpoint, but both follow the same
rule — the container itself either adds them automatically or reads them from a mark.

#### Endpoints: Ones Never Appearing in the Source, and Ones Coming from an Annotation

The modeled IoC container is born with two endpoints the moment it is built: `/health` and
`/liveness`. Neither of these two endpoints is the result of any application class, of any
annotation — they are written into the container's constructor itself, an automatic pair
existing at every build. There is also a third kind of endpoint: a `@MetricsEndpoint` mark
placed on a component's field, read by the container and registered as a new endpoint.

```java
// MetricsEndpoint.java — annotation type marking a field as a metrics endpoint to be given out (helper source, no main)
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
@interface MetricsEndpoint { String name(); }
```

```java
// Container.java — small IoC container that brings two default endpoints into existence as soon as it is built, and can add a new endpoint from an annotation (helper source, no main)
import java.lang.reflect.*;
import java.util.*;
import java.util.function.*;

class Container {
    final Map<String, Supplier<String>> endpoints = new LinkedHashMap<>();
    final int defaultEndpointCount;

    Container() {
        // default endpoints: no application class ever calls these at all, the container itself adds them
        endpoints.put("/health", () -> "running");
        endpoints.put("/liveness", () -> "yes");
        defaultEndpointCount = endpoints.size();
    }

    void register(Object component) {
        for (Field f : component.getClass().getDeclaredFields()) {
            MetricsEndpoint m = f.getAnnotation(MetricsEndpoint.class);
            if (m == null) continue;
            f.setAccessible(true);
            endpoints.put("/metrics/" + m.name(), () -> {
                try { return String.valueOf(f.get(component)); }
                catch (Exception e) { return "?"; }
            });
        }
    }

    String call(String path) {
        Supplier<String> s = endpoints.get(path);
        return s == null ? "none" : s.get();
    }

    static void show(String label, Object value) {
        System.out.printf("%-46s: %s%n", label, value);
    }
}
```

```java
// Application.java — the request counter is marked to be given out, the remaining two fields are never marked at all (helper source, no main)
class Application {
    @MetricsEndpoint(name = "request-count") int requestCounter = 42;
    String internalKey = "internal-config";
    String version = "3";
}
```

```java
// DefaultEndpoints.java — the number of endpoints existing as soon as the container is built, and the total after a component is registered
public class DefaultEndpoints {
    public static void main(String[] args) {
        Container container = new Container();
        Container.show("number of endpoints existing as soon as the container is built", container.defaultEndpointCount);
        Container.show("number of application classes calling these endpoints in the source", 0);

        container.register(new Application());
        Container.show("total endpoint count after Application is registered", container.endpoints.size());
        Container.show("/health", container.call("/health"));
        Container.show("/liveness", container.call("/liveness"));
        Container.show("/metrics/request-count", container.call("/metrics/request-count"));
    }
}
```

```
number of endpoints existing as soon as the container is built: 2
number of application classes calling these endpoints in the source: 0
total endpoint count after Application is registered: 3
/health                                       : running
/liveness                                     : yes
/metrics/request-count                        : 42
```

**ST17.** The moment the container is built, endpoints exist that no application class ever
wrote at all; these are born from the default — the source-level counterpart of `/health` and
`/liveness` is two lines in `Container`'s constructor, and no application code calls these lines
at all. **ST18.** A new metrics endpoint is added by an annotation; the `@MetricsEndpoint` mark
above the `requestCounter` field gives rise to the `/metrics/request-count` endpoint, but the
call that reads this endpoint does not exist anywhere inside the `Application` class at all —
the party reading and registering it is the `Container` itself. Two of the total three
endpoints come into existence without ever appearing in the source, one comes into existence
with a mark; someone only reading the source sees a single line in `Application.java`, but the
surface the application gives out to the world consists of three endpoints.

The count of default endpoints is fixed and readable in `Container`'s constructor; learning how
many there are needs no scan of any application class at all — the `defaultEndpointCount` field
carries this number the moment it is built. The situation differs for annotation-derived
endpoints: their count depends on how many times and with which classes `register` is called,
and this count is only settled once every component of the application has been registered.
This difference between the two kinds — one fixed, one growing at build time — will reappear
later in the lesson: the distributed configuration lesson will also have some values fixed at
build time while others can change after construction.

#### What Gets Given Out Is a Separate Decision

The `Application` class carries three fields, but only one appears under `/metrics`. The
remaining two fields — `internalKey`, `version` — sit inside the class; no endpoint mentions
them at all, because they carry no mark. This is the lesson's most important distinction: the
amount of information a component carries and the amount of information that component gives
out are not the same thing.

```java
// FieldCount.java — the total number of fields Application carries is compared against the number of marked fields
import java.lang.reflect.*;

public class FieldCount {
    public static void main(String[] args) {
        Container container = new Container();
        Application application = new Application();
        container.register(application);

        int totalFields = Application.class.getDeclaredFields().length;
        long markedFields = 0;
        for (Field f : Application.class.getDeclaredFields()) {
            if (f.isAnnotationPresent(MetricsEndpoint.class)) markedFields++;
        }

        Container.show("total number of fields carried by the Application class", totalFields);
        Container.show("number of fields marked with @MetricsEndpoint", markedFields);
        Container.show("/metrics/internal-key (never marked)", container.call("/metrics/internal-key"));
        Container.show("/metrics/version (never marked)", container.call("/metrics/version"));
    }
}
```

```
total number of fields carried by the Application class: 3
number of fields marked with @MetricsEndpoint : 1
/metrics/internal-key (never marked)          : none
/metrics/version (never marked)               : none
```

Only one of the three fields becomes visible; for the remaining two, the `call` method returns
`none`, because `Container`'s `endpoints` map has no such key registered at all. **ST19.** The
number of fields a component carries and the number of fields given out are not the same;
only marked fields get given out, and every unmarked field stays a field the container never
knows about at all. **ST20.** This is the first half of the lesson's limiting measurement: the
existence of default endpoints does not mean every piece of information gets opened up.
`/health` and `/liveness` exist, but neither touches `Application`'s internal state at all;
what comes out of that internal state is determined solely by marking, and nothing comes out
without a mark. `Container`'s `register` method sees every field with
`Application.class.getDeclaredFields()` — `internalKey` and `version` also pass through this
scan — but every unmarked field is skipped in the loop with `continue`. Being seen and being
given out are therefore not the same thing: the container sees every field, and registers only
the marked one.

#### The Cost of Extension: A Field Marked by Mistake

The marking mechanism works in two directions: marking a field with `@MetricsEndpoint` makes it
visible, but placing the mark on the correct field is a separate responsibility. `Container`'s
`register` method processes every marked field the same way — it carries no distinction at all
between which field is "safe" and which is "sensitive."

```java
// SensitiveComponent.java — both fields are marked; one is the intended counter, the other is internal information marked by mistake (helper source, no main)
class SensitiveComponent {
    @MetricsEndpoint(name = "config-key") String internalKey = "internal-config-value";
    @MetricsEndpoint(name = "active-sessions") int activeSessionCount = 7;
}
```

```java
// WrongMark.java — the container processes both marks the same way, never distinguishing which one was placed by mistake
public class WrongMark {
    public static void main(String[] args) {
        Container container = new Container();
        container.register(new SensitiveComponent());

        Container.show("/metrics/active-sessions (the intended metric)", container.call("/metrics/active-sessions"));
        Container.show("/metrics/config-key (marked by mistake)", container.call("/metrics/config-key"));
        Container.show("did the container reject this mark", "no, no error was raised at all");
    }
}
```

```
/metrics/active-sessions (the intended metric): 7
/metrics/config-key (marked by mistake)       : internal-config-value
did the container reject this mark            : no, no error was raised at all
```

`activeSessionCount` measures exactly what was intended — how many sessions are open.
`internalKey` was given out by mistake with the same mark, and `Container` does not
distinguish between the two cases at all: both carry `@MetricsEndpoint`, both pass through the
same path and get registered the same way. The `register` method itself sees no difference
between these two fields at all, because it has no information available to observe any
difference in the first place — all it holds is a field name, an annotation, and a value;
whether the field is "sensitive" or "harmless" has no classification present in the reflection
itself. The only party that can make this classification is whoever places the
`@MetricsEndpoint` mark, and once that decision is made wrong even once, the container itself
never corrects it. **ST21.** A metrics mark placed on the wrong field is given out by the IoC
container with no error at all; this is the second half of the lesson's limiting measurement.
The first half showed what does **not** get given out — an unmarked field never comes out at
all; the second half shows what **can** get given out by mistake — everything marked comes out,
whether it was marked correctly or not. The visibility built by health and metrics endpoints is
therefore not a one-way safety net; which information gets given out depends as much on the sum
of the marks in the source as on whether those marks were placed correctly.

The lesson's four measurements gather where each piece of information comes from and where it
ends up into a single table:

| Question | Answer | Its counterpart in the source |
|---|---|---|
| How many endpoints existed without any code ever being written | 2 (`/health`, `/liveness`) | none — the container's constructor |
| How many endpoints were added by an annotation | 1 (`/metrics/request-count`) | the `@MetricsEndpoint` mark |
| How many fields are carried, how many are given out | 3 fields, 1 given out | the presence of the mark |
| Is a mistakenly marked field rejected | no | the correctness of the mark is never checked |

The last row of the table belongs to a different class than the first three: the first three
answer "what exists" with a number, the last row answers "how reliable is this number" — and
the answer is negative. An endpoint's existence does not mean it exists for the correct purpose.

#### Summary

- The moment the container is built, endpoints exist that no application class ever wrote;
  these are born from the default.
- A new metrics endpoint is added by an annotation; no line in the source calls this endpoint.
- The total number of fields a component carries and the number of fields given out are not the
  same; only marked fields get given out.
- The existence of default endpoints does not mean every piece of information gets given out;
  which information is opened up is a separate, explicit marking decision.
- A metrics mark placed on the wrong field is given out by the IoC container with no error at
  all; the marking mechanism does not distinguish "correct" marking from "incorrect."

What the four measurements have in common is that all three share the same source: the
container's own decision. The two default endpoints sit in the container's constructor; the
acceptance of annotation-based endpoints and the failure to notice a wrong mark both sit in what
the `register` method does, or does not do. The application code gives neither of these three
decisions; it only places the mark, or does not, and the rest is the container's rule.

#### Next Step

This lesson measured how a component's internal state leaks to the outside. The next lesson
asks the reverse: how does a value from the outside get in, and once it gets in, which component
sees it when that value changes. If several parts of an application look at the same
configuration value, whether all of them find out at the same moment the value changes is not
obvious at all — the next lesson measures this by running it.

### Distributed Configuration

Source: https://academia.sh/en/courses/enterprise-application-framework/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.

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.

```java
// 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); }
}
```

```java
// 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; }
}
```

```java
// 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; }
}
```

```java
// 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.

```java
// 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.

```java
// 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"); }
}
```

```java
// 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?

```java
// 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.

### Service Discovery and Gateway

Source: https://academia.sh/en/courses/enterprise-application-framework/service-discovery-and-gateway

Who determined a call's target — a fixed reference in the source, a registry, or the container's own default resolution? Discovery and routing are modeled, there is no real network call. The limiting measurement measures the difference between discovery finding a target and verifying that the target still responds — a dead target never removed from the registry is found in a lookup the same way, the defect drops only at call time.

The previous lesson measured which component a value reaches. This lesson asks which target a
call reaches. When one component wants to call another, where does it find that target? Three
paths are modeled: a direct reference written fixed in the source, a target read from a
registry, and the container's own default resolution, which kicks in when nothing is found in
the registry. The concepts of service discovery and gateway were built in the Service
Architectures course and are not repeated here; this lesson measures only where the target comes
from in the source. There is no real network call — several classes implementing the `Service`
interface are wired together with direct Java calls — but the question asked is the same as in a
real distributed system: who chose the target, and is that target still there?

In a real distributed system a target's "address" is a hostname and a port, and both of these
can change over time — one replica shuts down, a new one opens at a different address. This
lesson models that variability not with a hostname but with a Java object: what `ServiceRegistry`
holds is not a network address but a `Service` instance directly, but the question it asks is
the same — what does a given name correspond to, and can that correspondence change over time?

#### Who Determined the Target: Direct Reference, Registry, Default

Four classes implement the same `Service` interface: `InventoryServiceV1`, `InventoryServiceV2`,
`FallbackService`, and `DeadService`, used in the section that follows. A `ServiceRegistry` maps
names to service instances; a `Gateway`, given a name, looks at the registry first, and falls
back to its own default service if the name is not there.

```java
// Service.java — common interface for every target that can be called (helper source, no main)
interface Service { String respond(); }
```

```java
// InventoryServiceV1.java — one target implementation (helper source, no main)
class InventoryServiceV1 implements Service {
    public String respond() { return "inventory-v1"; }
}
```

```java
// InventoryServiceV2.java — a second implementation of the same logical name (helper source, no main)
class InventoryServiceV2 implements Service {
    public String respond() { return "inventory-v2"; }
}
```

```java
// FallbackService.java — the container's default resolution for any target with no counterpart in the registry (helper source, no main)
class FallbackService implements Service {
    public String respond() { return "fallback"; }
}
```

```java
// ServiceRegistry.java — registry mapping names to service instances (helper source, no main)
import java.util.*;

class ServiceRegistry {
    final Map<String, Service> entries = new HashMap<>();
    void register(String name, Service s) { entries.put(name, s); }
    Service find(String name) { return entries.get(name); }
}
```

```java
// Gateway.java — router that, given a name, looks at the registry first and falls back to its default if it finds nothing (helper source, no main)
class Gateway {
    final ServiceRegistry registry;
    final Service defaultService;

    Gateway(ServiceRegistry registry, Service defaultService) {
        this.registry = registry;
        this.defaultService = defaultService;
    }

    String call(String targetName) {
        Service s = registry.find(targetName);
        return (s != null ? s : defaultService).respond();
    }
}
```

```java
// 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);
    }
}
```

```java
// WhoDeterminedTheTarget.java — the same call shape is shown getting its target from three separate sources
public class WhoDeterminedTheTarget {
    public static void main(String[] args) {
        Service direct = new InventoryServiceV1();
        Measurement.show("call with a direct reference", direct.respond());

        ServiceRegistry registry = new ServiceRegistry();
        registry.register("inventory", new InventoryServiceV2());
        Gateway gateway = new Gateway(registry, new FallbackService());

        Measurement.show("via the gateway, target in the registry", gateway.call("inventory"));
        Measurement.show("via the gateway, target not in the registry", gateway.call("billing"));
    }
}
```

```
call with a direct reference                        : inventory-v1
via the gateway, target in the registry             : inventory-v2
via the gateway, target not in the registry         : fallback
```

**ST28.** The target of a call made with a direct reference is fixed in the source — this is a
call: the line `new InventoryServiceV1()` directly determines which implementation is used, and
this decision is fixed the moment the `WhoDeterminedTheTarget` class is compiled. **ST29.** The
target of a call made through the gateway comes from a registry; what is fixed in the source is
only a name — `"inventory"` — not which object stands behind that name.
`gateway.call("inventory")` never changes at all, even if the object placed in the registry
changes from `InventoryServiceV1` to `InventoryServiceV2`, and the same call reaches a different
implementation. **ST30.** When a name with no counterpart in the registry is requested, the
gateway falls back to the container's own default service; this third path never appears in the
source at all — `"billing"` not being registered cannot be understood without looking inside the
`Gateway.call` method.

A direct reference is the fastest path but the most rigid: changing `InventoryServiceV1` to
`InventoryServiceV2` requires changing the `WhoDeterminedTheTarget` class itself and
recompiling. The registry adds an indirection layer — the calling side knows only a name and
has no idea at all which class the target comes from — and the cost of this indirection is that
finding the target now requires a map lookup. The default service offers a third option: even
if the registry is missing an entry, the calling side gets a response instead of an exception,
but in exchange it becomes impossible to tell from the source whether that response came from
the real target or from the fallback.

None of the three paths is "more correct" than the others. The `direct.respond()` call carries
no indirection at all, so it is the fastest and most predictable, but it is also the one most
closed to change — there is no way to change the target without changing the source text.
Calling through the registry removes this rigidity: `ServiceRegistry`'s contents can change
during a run, and the line `gateway.call("inventory")` can be written once and never change
again while still being routed to different targets. The default service offers a third
trade-off — even with no registry entry at all, the calling side does not come away empty-
handed, but the cost of this convenience is that "a response arrived" is no longer the same
thing as "a response arrived from the correct target."

#### The Limiting Measurement: Discovery Finds a Target, It Does Not Verify It

The only job of `ServiceRegistry.find` is to turn a name into an object; it has no information
at all about whether that object is still working correctly. Looking at the registry class's
own source makes this plain: `entries.get(name)` is a map lookup, and reading from a map never
calls the object it holds at all — it only checks whether that object exists. To see this, a
deliberately broken target is added to the registry — `DeadService`, an implementation that
always throws an exception when called, but was never removed from the registry; as far as the
registry is concerned, the name `billing` is still a valid entry, exactly like `inventory`.

```java
// DeadService.java — a target that sits in the registry but always fails when called (helper source, no main)
class DeadService implements Service {
    public String respond() { throw new IllegalStateException("target unreachable"); }
}
```

```java
// DiscoveryDoesNotVerify.java — the registry says 'found' for both a healthy and a dead target the same way; the difference only comes out at call time
public class DiscoveryDoesNotVerify {
    public static void main(String[] args) {
        ServiceRegistry registry = new ServiceRegistry();
        registry.register("inventory", new InventoryServiceV1());
        registry.register("billing", new DeadService());

        Measurement.show("did the registry find the /inventory target", registry.find("inventory") != null);
        Measurement.show("did the registry find the /billing target (actually dead)", registry.find("billing") != null);

        Gateway gateway = new Gateway(registry, new FallbackService());
        try {
            gateway.call("billing");
            Measurement.show("was the call successful", "yes");
        } catch (IllegalStateException e) {
            Measurement.show("was the call successful", "no, " + e.getClass().getSimpleName() + " fell at call time");
        }
    }
}
```

```
did the registry find the /inventory target         : true
did the registry find the /billing target (actually dead): true
was the call successful                             : no, IllegalStateException fell at call time
```

The first two lines are identical: `registry.find` returns `true` for both the healthy and the
dead target, because both correspond to a key in the map — the registry checks only a mapping,
not the target's liveness. **ST31.** Discovery finds a target but does not verify whether it
still responds; a dead target never removed from the registry is found in a lookup the same way
as a healthy one, the defect drops only at call time. Nothing tells anyone that the `billing`
target is broken until `Gateway.call` throws an `IllegalStateException` on the third line —
neither the registry, nor the gateway, nor the earlier `find` call. This is another of the
course's before-the-fact-silent defects: the discovery step passes successfully, and the defect
drops one step later, at a completely unexpected moment.

This distinction matters in practice, because the moment a system appears "healthy" and the
moment it is "actually working" are not necessarily the same moment. A structure like
`ServiceRegistry` can only list registered names; asking whether the target behind a registered
name is still standing is an entirely separate responsibility, and this lesson shows that this
responsibility is **absent** from the `find` method. One way to close this gap is to keep the
registry together with liveness information — attaching a "last responded" field next to each
target and writing a separate method that returns only targets whose information is current,
instead of `find`. This lesson does not build that structure; it measures only the
**consequence** of its absence, because the point of a limiting measurement is not to show the
fix but to count what happens when it is not made.

The three sections' measurements can be gathered into a single table:

| Question | Answer | Its counterpart in the source |
|---|---|---|
| Who determined the target (direct reference) | `InventoryServiceV1` | `new InventoryServiceV1()` — a call |
| Who determined the target (gateway) | whatever is in the registry | the name `"inventory"` — a registry |
| If an unregistered name is requested | `FallbackService` | no line at all — a default |
| Does `find("billing")` find the target | yes, `true` | discovery looks only at the mapping |
| Is `call("billing")` successful | no, an exception | liveness is only checked at call time |

The last two rows of the table give different results for the same target — this is the
lesson's summary: whether a target **exists** and whether that target is **working** are two
separate questions, and discovery answers only the first.

#### Summary

- The target of a call made with a direct reference is fixed in the source; this is the case
  where the source of the behavior is a call.
- The target of a call made through the gateway comes from a registry; what is fixed in the
  source is only a name, not which object corresponds to that name.
- When a name with no counterpart in the registry is requested, the gateway falls back to the
  container's own default service; this path never appears in the source at all.
- Discovery finds a target but does not verify whether it still responds; a healthy and a dead
  target are found in a lookup the same way.
- The defect of a dead target never removed from the registry drops not at discovery time but
  at call time.

#### Next Step

This lesson measured whether a call reached its target, and if it did, when it failed. The
next lesson treats this failure not as a single data point but as a repeating event: after how
many consecutive failures should the calling side stop trying, and where does that decision come
from? The closing of the course and of the M08 curriculum measures this final boundary.

### Resilience Patterns

Source: https://academia.sh/en/courses/enterprise-application-framework/resilience-patterns

Circuit breaking and retry are added by an annotation; the call site never changes at all. In a manufactured failure, how many real attempts are made and at which threshold the circuit opens are measured as numbers. The limiting measurement shows that the annotation applies only to a call passing through the proxy, and a call the same class makes from within itself stays unprotected — the same boundary web-and-data-layer/06 measured, with a different outcome. This lesson closes the course and the M08 curriculum.

The previous lesson measured whether a call reached its target. This lesson treats that
failure not as a single event but as a repeating pattern: when a target fails over and over,
what should the calling side do — give up immediately, retry a few times, or cut off retrying
entirely past some point? The concepts of circuit breaker and retry patterns were built in the
Resilience and Reliability course and are not repeated here; this lesson measures only how these
patterns are added by an annotation and what they leave in the source. No real duration is
written — what is measured is the attempt count and the threshold, both plain integers. This is
the closing lesson of the course and of the M08 curriculum.

#### Resilience Added by Annotation: Attempt Count and Threshold

`FragileService` is a target manufactured to fail every time it is called; it carries two
annotations — `@Retry(count = 3)` and `@CircuitBreaker(threshold = 5)`. The party that reads and
applies these two marks is not `FragileService` itself, but the `ResilienceProxy` wrapping it.

```java
// Retry.java — annotation type stating how many times a target should be retried (helper source, no main)
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
@interface Retry { int count(); }
```

```java
// CircuitBreaker.java — annotation type stating after how many consecutive failures the circuit should open (helper source, no main)
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
@interface CircuitBreaker { int threshold(); }
```

```java
// OutsideService.java — interface for the called target (helper source, no main)
interface OutsideService { String call(); }
```

```java
// FragileService.java — target manufactured to fail every time it is called (helper source, no main)
@Retry(count = 3) @CircuitBreaker(threshold = 5)
class FragileService implements OutsideService {
    int callCounter = 0;

    public String call() {
        callCounter++;
        throw new RuntimeException("target failed");
    }

    String otherOperation() {
        // calls directly from within itself, never seeing the proxy at all
        return call();
    }
}
```

```java
// ResilienceProxy.java — proxy that reads the annotations and applies the retry and circuit breaker behavior (helper source, no main)
class ResilienceProxy implements OutsideService {
    final OutsideService target;
    final int retryCount;
    final int threshold;
    int consecutiveFailures = 0;
    boolean circuitOpen = false;
    int realCallCounter = 0;

    ResilienceProxy(OutsideService target) {
        this.target = target;
        Class<?> type = target.getClass();
        Retry r = type.getAnnotation(Retry.class);
        CircuitBreaker cb = type.getAnnotation(CircuitBreaker.class);
        this.retryCount = r != null ? r.count() : 1;
        this.threshold = cb != null ? cb.threshold() : Integer.MAX_VALUE;
    }

    public String call() {
        if (circuitOpen) {
            throw new IllegalStateException("circuit open, target was never reached at all");
        }
        RuntimeException lastError = null;
        for (int i = 1; i <= retryCount; i++) {
            realCallCounter++;
            try {
                String result = target.call();
                consecutiveFailures = 0;
                return result;
            } catch (RuntimeException e) {
                lastError = e;
                consecutiveFailures++;
                if (consecutiveFailures >= threshold) { circuitOpen = true; break; }
            }
        }
        throw lastError;
    }
}
```

```java
// 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);
    }
}
```

The body of `FragileService.call` contains neither an attempt counter nor a threshold
comparison; both live on the `ResilienceProxy` side. The same `proxy.call()` line is called
three times in a row.

```java
// AttemptsAndThreshold.java — the same call line reveals, in a manufactured continuous failure, the real attempt count and the threshold at which the circuit opens
public class AttemptsAndThreshold {
    public static void main(String[] args) {
        ResilienceProxy proxy = new ResilienceProxy(new FragileService());

        attempt(proxy, "call 1");
        attempt(proxy, "call 2");
        attempt(proxy, "call 3");

        Measurement.show("total number of real attempts (reaching the target)", proxy.realCallCounter);
        Measurement.show("is the circuit open", proxy.circuitOpen);
    }

    static void attempt(ResilienceProxy proxy, String label) {
        try {
            proxy.call();
            Measurement.show(label, "successful");
        } catch (Exception e) {
            Measurement.show(label, e.getMessage());
        }
    }
}
```

```
call 1                                              : target failed
call 2                                              : target failed
call 3                                              : circuit open, target was never reached at all
total number of real attempts (reaching the target) : 5
is the circuit open                                 : true
```

**ST32.** Circuit breaker and retry are added by an annotation; the `proxy.call()` call site
never changes at all across the three `attempt` calls, the only thing determining the behavior
is the two marks on `FragileService`. **ST33.** In a manufactured continuous failure, the real
attempt count and the threshold at which the circuit opens are both numbers: the first call
makes three attempts (`count = 3`), and because it has not yet reached the threshold (`threshold
= 5`), the circuit stays closed; the second call makes two more attempts, and the moment
consecutive failures reach five, the circuit opens — the total real attempt count is exactly
**5**. **ST34.** The third call, after the circuit has opened, is rejected without ever reaching
the target; `realCallCounter` is still 5 after the third call, it does not increase.

#### The Limiting Measurement: A Call Not Passing Through the Proxy Stays Unprotected

`FragileService` has a second method that calls `call` from within itself: `otherOperation`.
This call never passes through `ResilienceProxy` at all — the object calls its own method
directly.

```java
// UnprotectedCall.java — otherOperation() calls call() directly from within itself, the proxy is never involved, and no retry happens at all
public class UnprotectedCall {
    public static void main(String[] args) {
        FragileService inner = new FragileService();
        try {
            inner.otherOperation();
        } catch (RuntimeException e) {
            Measurement.show("result of the otherOperation() call", e.getMessage());
        }
        Measurement.show("direct call made from within otherOperation, how many attempts were made", inner.callCounter);
    }
}
```

```
result of the otherOperation() call                 : target failed
direct call made from within otherOperation, how many attempts were made: 1
```

`inner.callCounter` increases only once, not three times. **ST35.** The annotation applies only
to a call passing through the proxy; a call the same class makes from within itself stays
unprotected, because the party reading the `@Retry` and `@CircuitBreaker` marks is
`ResilienceProxy`, and `otherOperation` never calls it at all. This is the same boundary
measured by web-and-data-layer/06 — a component calling itself skipped the proxy there too —
appearing here with a different outcome: there, a wrapped transaction boundary never opened at
all; here, a wrapped resilience pattern never applies at all. Someone reading the source might
assume `otherOperation` carries the same protection, because it calls the same class's same
`call` method; there is no mark in the source at all to disprove that assumption.

#### Summary

- Circuit breaker and retry are added by an annotation; the call site never changes at all.
- In a manufactured continuous failure, the real attempt count and the threshold at which the
  circuit opens are measured as numbers, neither of them a duration.
- Calls made after the circuit opens are rejected without ever reaching the target.
- The annotation applies only to a call passing through the proxy; a call the same class makes
  from within itself stays unprotected.
- This boundary is the same as the self-calling boundary measured by web-and-data-layer/06, with
  a different outcome.

#### Course Wrap-Up

The course opened with a single question: most of the code in a program using an application
framework is never called by anyone at all, because the expression that constructs an object,
the assignment that supplies a dependency, the line that calls a setup routine are, in their
place, only annotations. Nineteen lessons asked this question across three topics — core
concepts, the web and data layer, and security and distributed setup — and each time returned to
the same triad: did a call give rise to this behavior in the source, an annotation, or neither.

| Lesson | Behavior measured | Source of the behavior | Limiting measurement |
|---|---|---|---|
| Inversion of Control | 3 of 12 behaviors are call, 6 are annotation, 3 are default | call · annotation · default | An object left outside the container receives none of the annotations |
| Dependency Injection | The same `Service` is built both through the container and by hand, and both work | annotation (supply) · call (manual supply) | An unbound interface error drops not while the container is constructed, but at the first request |
| Component Scopes and Lifecycle | Singleton scope is the default, per-request is an explicit annotation | default (singleton) · annotation (per-request) | An unrecognized scope name raises no error; the component silently stops being a singleton |
| Configuration Sources | 1 of 4 settings comes from an external source, 2 from a file, 1 from an embedded default | the container's code order (precedence chain) | A spelling difference falls through to the embedded default with no error at all |
| Automatic Configuration | The number of behaviors born without an annotation drops from 3 to 2 | default (conditional component selection) | Adding one component silently disables another |
| Aspect-Oriented Programming | An advice annotation adds a counter without touching the call site | annotation (through a proxy) | A mark placed on an interface-less class stays ineffective with no error |
| Embedded Server and Request Processing Model | 2 of 6 links are default, 2 are annotation, 2 are call | default · annotation · call | In manually called classes, the precedence in the annotation is never read at all |
| Controllers and Routing | All 4 of 4 endpoints are born from an annotation | annotation (`@Endpoint`) | An unmatched path silently returns a default response; on a colliding path the winner depends on scan order |
| Validation and Error Handling | 3 of 4 fields get their rule from an annotation, 1 carries no rule at all | annotation (rule) · default (envelope) | Invalid data given to a handler outside the container is never validated at all, and returns "ok" |
| Data Access Abstraction | The same compiled caller runs with two implementations, without being recompiled | the caller refers only to the interface | `ArrayStore` throws at the capacity limit, `InMemoryStore` does not on the same call |
| Object and Table Binding | 2 of 4 fields load at load time, 2 load on first access | annotation (`@Column` / `@LazyColumn`) | An unaccessed lazy field on an object detached from the container silently returns empty |
| Transaction Management | The boundary opens on every call coming from outside, an internal call is never counted at all | annotation (`@Transactional`, through a proxy) | A component calling itself skips the proxy, and the boundary never opens at all |
| Authentication and Authorization | The authorization decision comes from an annotation (panel) or from a default (status) | annotation · default · call (manual) | The same source produces two different security postures under two different defaults |
| Token-Based Authentication | Claim count is 1/3; validation gives the same result by annotation and by call | annotation (`@RequiredClaim`) · call (manual) | A revoked token stays valid until it expires, because the revocation list is never looked at |
| Layer Tests | The slice test builds 2 components, the full context test builds 4 | the number of components the container builds | Even when a component left outside the slice never has its setup hook run, the test still passes |
| Health and Metrics Endpoints | 2 endpoints come from the default, 1 from an annotation; 1 of 3 fields is given out | default (2 endpoints) · annotation (1 endpoint) | A mistakenly marked field is given out too, with no error at all |
| Distributed Configuration | A singleton component sees the old value, a per-request component sees the current value | default (scope) · call (`refresh()`) | Two components at the same moment give two different, both error-free, answers to the same value |
| Service Discovery and Gateway | The target comes from a direct reference, from a registry, or from the container's default | call · registry · default | A dead target never removed from the registry is found in a lookup; the defect drops at call time |
| Resilience Patterns | The real attempt count is 5, the circuit opens at threshold 5 | annotation (`@Retry`, `@CircuitBreaker`) | A self-call that never passes through the proxy stays unprotected |

The table's third column confirms a single pattern: in five of the nineteen lessons the behavior
comes entirely from an annotation (advice wrapping, four endpoints, column binding, the
transaction boundary, the resilience patterns), in three it comes entirely or mostly from a
default, and in the rest all three appear together. The limiting measurement column carries
another pattern: in nearly all nineteen lessons the defect is **silent** — only two raise an
error at all (the unbound interface, the capacity limit), the rest either return an unexpected
value or carry on as if nothing changed. Someone reading the source top to bottom cannot see
most of the behavior these nineteen lessons show, and cannot see that what they cannot see is
broken either.

The M08 Java curriculum's six courses asked six separate questions, and none took the place of
another. Java Fundamentals counted which step, unwritten in the source, the compiler adds to the
class file. Object-Oriented Java separated which type gives a decision at run time. The
Standard Library and Streams measured who gives a guarantee — the caller or the library. JVM,
Concurrency and Performance tracked which thread carries out a step. Build, Testing and the
Ecosystem counted which type must be present on which class path and showed the difference
between that and the run. This course, The Enterprise Application Framework, asked the sixth and
final question: what gives rise to a behavior in the source — a call, an annotation, or neither.
All six questions look at the same source text, but each searches for a different gap; none is
a generalization of another, and none claims that reading the source top to bottom is enough.

M08 closes here. The source text tells only part of what a Java program does — the step it
adds, the type that gives the decision, the party that gives the guarantee, the thread that
carries it out, the required class path, the source of the behavior; all six are at least partly
hidden from the eyes of someone reading the source, and all six become visible only by being
run and counted.

