| @@ -3,6 +3,7 | |||
|
|
3 | 3 | "java.compile.nullAnalysis.mode": "automatic", |
|
|
4 | 4 | "cSpell.words": [ |
|
|
5 | 5 | "implab", |
|
|
6 | "materializer", | |
|
|
6 | 7 | "rawtypes" |
|
|
7 | 8 | ] |
|
|
8 | 9 | } No newline at end of file |
| @@ -10,3 +10,33 | |||
|
|
10 | 10 | - `find*` ΡΠ°ΡΡΠΌΠ°ΡΡΠΈΠ²Π°Π΅ΡΡΡ ΠΊΠ°ΠΊ ΡΠΈΠ½ΠΎΠ½ΠΈΠΌ legacy `get*` (ΠΏΠΎΠΈΡΠΊ Π±Π΅Π· `fail-fast`). |
|
|
11 | 11 | - `require*` ΡΡΠΎ `find*` + `fail-fast` Ρ ΠΏΠΎΠ½ΡΡΠ½ΠΎΠΉ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ Π² ΠΌΠ΅ΡΡΠ΅ Π²ΡΠ·ΠΎΠ²Π°. |
|
|
12 | 12 | - ΠΠ»Ρ Π½ΠΎΠ²ΠΎΠ³ΠΎ API ΠΏΡΠ΅Π΄ΠΏΠΎΡΡΠΈΡΠ΅Π»ΡΠ½Ρ ΡΠΎΡΠΌΡ `find/require`; Π½ΠΎΠ²ΡΠ΅ `get*` ΠΏΠΎ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ Π½Π΅ Π΄ΠΎΠ±Π°Π²Π»ΡΡΡ. |
|
|
13 | ||
|
|
14 | ## Identity-first modeling | |
|
|
15 | ||
|
|
16 | Prefer an **identity-first** split between: | |
|
|
17 | ||
|
|
18 | - **identity objects** used for discovery and selection | |
|
|
19 | - **stateful/materialized objects** obtained through separate API calls | |
|
|
20 | ||
|
|
21 | ### Rules | |
|
|
22 | ||
|
|
23 | - Objects intended for replayable observation (`all(...)`, similar collection APIs) should be **identity objects**. | |
|
|
24 | - Identity objects must be: | |
|
|
25 | - cheap to create | |
|
|
26 | - effectively immutable | |
|
|
27 | - limited to identity and cheap selection metadata | |
|
|
28 | - Heavy, computed, provider-based, or runtime-bound state must be resolved separately. | |
|
|
29 | - Aggregate content should be accessed through dedicated lookup/materialization APIs. | |
|
|
30 | - Eager observation of identity is acceptable. | |
|
|
31 | - Eager observation of computed state is not. | |
|
|
32 | ||
|
|
33 | ### Do not | |
|
|
34 | ||
|
|
35 | - Do not store heavy computed state inside identity objects. | |
|
|
36 | - Do not store runtime references to foreign domains inside identity objects. | |
|
|
37 | - Do not use custom events when replayable identity registries plus on-demand lookup are sufficient. | |
|
|
38 | ||
|
|
39 | ### Rule of thumb | |
|
|
40 | ||
|
|
41 | **Identity objects contain selection metadata. | |
|
|
42 | Aggregate content is obtained separately, on demand.** | |
| @@ -5,6 +5,7 import java.util.function.Supplier; | |||
|
|
5 | 5 | |
|
|
6 | 6 | public class LazyValue<T> implements Supplier<T> { |
|
|
7 | 7 | private volatile T value; |
|
|
8 | private volatile boolean initialized = false; | |
|
|
8 | 9 | |
|
|
9 | 10 | private final Supplier<T> innerSupplier; |
|
|
10 | 11 | |
| @@ -15,7 +16,7 public class LazyValue<T> implements Sup | |||
|
|
15 | 16 | @Override |
|
|
16 | 17 | public T get() { |
|
|
17 | 18 | var v = value; |
|
|
18 |
if ( |
|
|
|
19 | if (initialized) { | |
|
|
19 | 20 | return v; |
|
|
20 | 21 | } |
|
|
21 | 22 | |
| @@ -50,6 +50,11 public class Strings { | |||
|
|
50 | 50 | throw new IllegalArgumentException(String.format("Argument %s can't be null or blank", argumentName)); |
|
|
51 | 51 | } |
|
|
52 | 52 | |
|
|
53 | public static String requireNonBlank(String value) { | |
|
|
54 | argumentNotNullOrBlank(value, "value"); | |
|
|
55 | return value; | |
|
|
56 | } | |
|
|
57 | ||
|
|
53 | 58 | public static String sanitizeName(String value) { |
|
|
54 | 59 | return INVALID_NAME_CHAR.matcher(value).replaceAll("_"); |
|
|
55 | 60 | } |
| @@ -7,11 +7,9 import org.implab.gradle.variants.model. | |||
|
|
7 | 7 | public abstract class VariantsPlugin implements Plugin<Project> { |
|
|
8 | 8 | @Override |
|
|
9 | 9 | public void apply(Project target) { |
|
|
10 | var extension = target.getExtensions().create("variants", VariantsExtension.class); | |
|
|
11 | 10 | |
|
|
12 | target.afterEvaluate(project -> { | |
|
|
13 | ||
|
|
14 | }); | |
|
|
11 | target.getExtensions().create("variants", VariantsExtension.class); | |
|
|
15 | 12 | |
|
|
16 | 13 | } |
|
|
14 | ||
|
|
17 | 15 | } |
| @@ -1,35 +1,8 | |||
|
|
1 | 1 | package org.implab.gradle.variants.model; |
|
|
2 | 2 | |
|
|
3 | import org.gradle.api.Named; | |
|
|
4 | import org.gradle.api.provider.SetProperty; | |
|
|
5 | ||
|
|
6 | /** | |
|
|
7 | * Binds a role to a set of layers inside a particular variant. | |
|
|
3 | /** A binding between a role and a layer inside a specific variant. | |
|
|
8 | 4 | * |
|
|
9 | * The binding name is the role name, e.g. "production", "test", "tool". | |
|
|
10 |
|
|
|
|
11 | public interface RoleBinding extends Named { | |
|
|
12 | ||
|
|
13 | /** | |
|
|
14 | * Layer names participating in this (variant, role) selection. | |
|
|
15 | * | |
|
|
16 | * Core model keeps names here deliberately: | |
|
|
17 | * source/materialization semantics live elsewhere. | |
|
|
18 | */ | |
|
|
19 | SetProperty<String> getLayerNames(); | |
|
|
20 | ||
|
|
21 | /** | |
|
|
22 | * Adds one layer to this binding. | |
|
|
23 | */ | |
|
|
24 | void layer(String name); | |
|
|
25 | ||
|
|
26 | /** | |
|
|
27 | * Adds several layers to this binding. | |
|
|
28 | */ | |
|
|
29 | void layers(String... names); | |
|
|
30 | ||
|
|
31 | /** | |
|
|
32 | * Adds several layers to this binding. | |
|
|
33 | */ | |
|
|
34 | void layers(Iterable<String> names); | |
|
|
5 | * @see {@link VariantDefinition} for the context of this binding. | |
|
|
6 | */ | |
|
|
7 | public record RoleLayerBinding(String name, String layerName) { | |
|
|
35 | 8 | } No newline at end of file |
| @@ -1,11 +1,6 | |||
|
|
1 | 1 | package org.implab.gradle.variants.model; |
|
|
2 | 2 | |
|
|
3 | import org.gradle.api.Action; | |
|
|
4 | 3 | import org.gradle.api.Named; |
|
|
5 | import org.gradle.api.NamedDomainObjectContainer; | |
|
|
6 | import org.implab.gradle.common.core.lang.Closures; | |
|
|
7 | ||
|
|
8 | import groovy.lang.Closure; | |
|
|
9 | 4 | |
|
|
10 | 5 | /** |
|
|
11 | 6 | * A named variant, e.g. "browser", "electron". |
| @@ -14,31 +9,4 import groovy.lang.Closure; | |||
|
|
14 | 9 | * It owns a set of role bindings. |
|
|
15 | 10 | */ |
|
|
16 | 11 | public interface Variant extends Named { |
|
|
17 | ||
|
|
18 | /** | |
|
|
19 | * Role bindings declared inside this variant. | |
|
|
20 | * | |
|
|
21 | * The binding name is the role name. | |
|
|
22 | */ | |
|
|
23 | NamedDomainObjectContainer<RoleBinding> getRoleBindings(); | |
|
|
24 | ||
|
|
25 | /** | |
|
|
26 | * Creates or returns an existing role binding and configures it. | |
|
|
27 | */ | |
|
|
28 | default RoleBinding role(String name) { | |
|
|
29 | return getRoleBindings().maybeCreate(name); | |
|
|
30 | } | |
|
|
31 | ||
|
|
32 | /** | |
|
|
33 | * Creates or returns an existing role binding and configures it. | |
|
|
34 | */ | |
|
|
35 | default RoleBinding role(String name, Action<? super RoleBinding> action) { | |
|
|
36 | var role = role(name); | |
|
|
37 | action.execute(role); | |
|
|
38 | return role; | |
|
|
39 | } | |
|
|
40 | ||
|
|
41 | default RoleBinding role(String name, Closure<?> closure) { | |
|
|
42 | return role(name, Closures.action(closure)); | |
|
|
43 | } | |
|
|
44 | 12 | } No newline at end of file |
| @@ -10,14 +10,14 import groovy.lang.Closure; | |||
|
|
10 | 10 | * Root extension: |
|
|
11 | 11 | * |
|
|
12 | 12 | * variants { |
|
|
13 |
* |
|
|
|
14 |
* |
|
|
|
13 | * layers { ... } | |
|
|
14 | * roles { ... } | |
|
|
15 | 15 | * |
|
|
16 |
* |
|
|
|
17 |
* |
|
|
|
18 |
* |
|
|
|
19 | * } | |
|
|
20 |
* |
|
|
|
16 | * variant("browser") { | |
|
|
17 | * role("production") { | |
|
|
18 | * layers("main", "generated", "mainRjs") | |
|
|
19 | * } | |
|
|
20 | * } | |
|
|
21 | 21 | * } |
|
|
22 | 22 | */ |
|
|
23 | 23 | public interface VariantsExtension { |
| @@ -33,27 +33,33 public interface VariantsExtension { | |||
|
|
33 | 33 | NamedDomainObjectContainer<Role> getRoles(); |
|
|
34 | 34 | |
|
|
35 | 35 | /** |
|
|
36 | * Domain of variants. | |
|
|
37 | */ | |
|
|
38 | NamedDomainObjectContainer<Variant> getVariants(); | |
|
|
39 | ||
|
|
40 | /** | |
|
|
36 | 41 | * Declared variants. |
|
|
37 | 42 | */ |
|
|
38 | NamedDomainObjectContainer<Variant> getVariantDefinitions(); | |
|
|
43 | NamedDomainObjectContainer<VariantDefinition> getVariantDefinitions(); | |
|
|
39 | 44 | |
|
|
40 | 45 | /** |
|
|
41 | 46 | * Creates or returns an existing variant and configures it. |
|
|
42 | 47 | */ |
|
|
43 | default Variant variant(String name) { | |
|
|
48 | default VariantDefinition variant(String name) { | |
|
|
49 | ||
|
|
44 | 50 | return getVariantDefinitions().maybeCreate(name); |
|
|
45 | 51 | } |
|
|
46 | 52 | |
|
|
47 | 53 | /** |
|
|
48 | 54 | * Creates or returns an existing variant and configures it. |
|
|
49 | 55 | */ |
|
|
50 | default Variant variant(String name, Action<? super Variant> action) { | |
|
|
56 | default VariantDefinition variant(String name, Action<? super VariantDefinition> action) { | |
|
|
51 | 57 | var variant = variant(name); |
|
|
52 | 58 | action.execute(variant); |
|
|
53 | 59 | return variant; |
|
|
54 | 60 | } |
|
|
55 | 61 | |
|
|
56 | default Variant variant(String name, Closure<?> closure) { | |
|
|
62 | default VariantDefinition variant(String name, Closure<?> closure) { | |
|
|
57 | 63 | return variant(name, Closures.action(closure)); |
|
|
58 | 64 | } |
|
|
59 | 65 | } No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now
