Skip to content

Commit 3c8a6a2

Browse files
committed
WIP: docs: add CONFIGURATION_RESOLUTION.md
1 parent 19622df commit 3c8a6a2

1 file changed

Lines changed: 96 additions & 0 deletions

File tree

CONFIGURATION_RESOLUTION.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Configuration Resolution: Scope-First vs. Type-First
2+
3+
## The Two Strategies
4+
5+
Given `Foo extends Entity` with configurations at four points in the matrix:
6+
7+
| | Instance | Global |
8+
|---|---|---|
9+
| **Foo** | I·Foo | G·Foo |
10+
| **Entity** | I·Entity | G·Entity |
11+
12+
**Scope-first** exhausts all instance settings before looking at global ones:
13+
```
14+
I·Foo → I·Entity → G·Foo → G·Entity → Default
15+
```
16+
17+
**Type-first** exhausts all Foo-specific settings before looking at Entity:
18+
```
19+
I·Foo → G·Foo → I·Entity → G·Entity → Default
20+
```
21+
22+
Both agree on `I·Foo` being first and `G·Entity` being last. The dispute is the middle two.
23+
24+
---
25+
26+
## Where Scope-First Makes Sense
27+
28+
**Setting: `textAlign` or `nullRepresentation`**
29+
30+
A developer creates a reporting grid and writes:
31+
```java
32+
easyGrid.configuration.forType(Entity.class).setTextAlign(RIGHT);
33+
```
34+
The intent is *"every Entity-like column in this grid should be right-aligned."* Since `Foo IS-AN Entity`, Foo columns should also be right-aligned. Instance context wins: the developer explicitly opted every Entity column into this layout decision, and Foo, being substitutable for Entity, should inherit it.
35+
36+
If type-first were applied, `G·Foo` would sit between `I·Foo` and `I·Entity`. An unrelated global renderer for Foo would silently block the instance-level alignment — which is the opposite of what the developer intended.
37+
38+
---
39+
40+
## Where Type-First Makes Sense
41+
42+
**Setting: `rendererFactory`**
43+
44+
Globally, `Foo` has a specialized renderer registered:
45+
```java
46+
GlobalEasyGridConfiguration.forType(Foo.class)
47+
.setRendererFactory(FooRenderers.of(...));
48+
```
49+
Independently, an instance configures Entity columns with a generic formatter:
50+
```java
51+
easyGrid.configuration.forType(Entity.class).setFormatter(e -> e.getId().toString());
52+
```
53+
54+
Under scope-first, `I·Entity` wins over `G·Foo`. The Foo-specific renderer — which encodes *what a Foo looks like*, arguably a type invariant — gets silently replaced by a generic Entity formatter that the developer may not have intended to apply to Foo.
55+
56+
Type-first preserves the type contract: `G·Foo` sits above `I·Entity`, so Foo's rendering semantics are honoured even across scope boundaries.
57+
58+
---
59+
60+
## Liskov Substitution Principle
61+
62+
LSP says: a `Foo` must be usable wherever an `Entity` is expected, without callers needing to know the difference.
63+
64+
Applied to configuration, it has two implications that pull in opposite directions:
65+
66+
**LSP supports scope-first for behavioural settings.** If you configure `Entity` at instance level — null representation, alignment, a locale-specific formatter — you are specifying how *this grid* handles all Entity values. A `Foo` substituting for `Entity` should satisfy those same observable postconditions. Refusing to inherit `I·Entity` because Foo has a `G·Foo` entry would mean the grid behaves differently for `Foo` vs `Entity` in ways the calling code did not anticipate.
67+
68+
**LSP supports type-first for type-defining settings.** LSP also requires that subtypes honour their own invariants. If `Foo` has a type-level rendering contract — *this is how a Foo is displayed* — then substituting a generic `Entity` renderer for it violates the Foo invariant. The renderer is not just a postcondition on the grid; it is a property of `Foo` itself.
69+
70+
The tension resolves to: **scope-first is correct as a default ordering**, but a setting that constitutes a type invariant (renderer factory, type-specific formatter) has stronger claims when registered at global-type specificity than a setting that constitutes an instance context decision (null representation, alignment).
71+
72+
---
73+
74+
## The Current Bug
75+
76+
The current `InstanceEasyGridConfiguration` constructs the parent of any instance-level type config by calling `GlobalEasyGridConfiguration.resolve(type)` — jumping straight to global scope. For a `Foo` column, the effective chain is:
77+
78+
```
79+
I·Foo → G·Foo → G·Entity → Default
80+
```
81+
82+
`I·Entity` never appears. Instance-level Entity configuration is invisible to Foo columns within the same instance. This is wrong under either strategy.
83+
84+
---
85+
86+
## What Should Be Implemented
87+
88+
The correct chain for scope-first (the generally right default) is:
89+
90+
```
91+
I·Foo → I·Entity → I·Object → G·Foo → G·Entity → G·Object → Default
92+
```
93+
94+
Concretely, when `InstanceEasyGridConfiguration` creates a config for `Foo`, its parent should be `I·Entity` (obtained by walking the instance's *own* class-map up the hierarchy), not `G·Foo` directly. Only after the instance hierarchy is exhausted should the chain cross into the global scope — at the matching type level.
95+
96+
This means `EasyGridConfigurationClassMap.getOrCreate` needs to resolve parents by walking the *same map's* hierarchy first, and the cross-scope link (`resolve` jumping to global) should only be installed at the root of each instance's hierarchy chain, not at every node.

0 commit comments

Comments
 (0)