You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+30Lines changed: 30 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -58,6 +58,36 @@ The CI pipeline behavior is controlled by boolean switches in `.github/workflows
58
58
- Run `./build.sh Format` locally before committing.
59
59
- All warnings are treated as errors (`TreatWarningsAsErrors`).
60
60
61
+
## Module Taxonomy
62
+
63
+
New work should follow the logical taxonomy `FrameworkCore / Application / Extension / Host`:
64
+
65
+
-`FrameworkCore`: broadly reusable technical capabilities and modularity primitives.
66
+
-`Application`: reusable business capabilities and bounded contexts.
67
+
-`Extension`: optional modules that attach a capability to a concrete technology or transport, such as `Persistence`, `Web`, `Memory`, or future `Redis` and `MongoDb` adapters.
68
+
-`Host`: runnable shells that compose modules and provide environment configuration.
69
+
70
+
Use responsibility to classify a module, not directory name alone. A project under `src/Framework/` can still be an `Extension` if it is technology-specific, such as `ChengYuan.Caching.Memory`.
71
+
72
+
### Module Base Classes
73
+
74
+
New production modules must inherit a category-specific base class:
75
+
76
+
-`FrameworkCoreModule` for framework capabilities.
77
+
-`ApplicationModule` for business capabilities.
78
+
-`ExtensionModule` for persistence, web, memory, worker, or other technology adapters.
79
+
-`HostModule` for host shells and composition modules.
80
+
81
+
Direct `ModuleBase` inheritance is reserved for low-level modularity infrastructure only. The shared service-registration entry point `ConfigureServices(IServiceCollection services)` remains the same across all categories.
82
+
83
+
During the current architecture refactor:
84
+
85
+
- Keep explicit `DependsOn` module composition.
86
+
- Do not introduce automatic module discovery.
87
+
- Move application persistence registration out of Hosts and back into the corresponding `*.Persistence` extension modules.
88
+
- Keep Host internals layered. For WebHost, separate framework composition, application composition, and HTTP/runtime glue into different composition modules instead of one all-knowing host module.
89
+
- Keep `Program.cs` thin by routing setup through host composition seams such as `AddWebHostComposition(...)` and `UseWebHostComposition()`.
90
+
61
91
## Versioning
62
92
63
93
- Version is managed via `VersionPrefix` in `Directory.Build.props`.
Copy file name to clipboardExpand all lines: docs/guide/architecture.md
+61-6Lines changed: 61 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,7 +7,8 @@ When existing code and this guide differ, use this guide as the default rule for
7
7
## Design Principles
8
8
9
9
- Module first, layer second.
10
-
- Split reusable slices into `Framework` modules and `Application` modules so technical systems stay separate from reusable application capabilities.
10
+
- Keep the logical taxonomy explicit: `FrameworkCore`, `Application`, `Extension`, and `Host`.
11
+
- Split reusable slices so technical systems stay separate from reusable application capabilities and optional extensions.
11
12
- Allow uneven module depth. Do not force every module to expose the same layers or transports.
12
13
- Keep Web and CLI as optional transport facets.
13
14
- Require explicit dependencies and architecture tests.
@@ -55,6 +56,45 @@ Apply the same mirroring rule to tests. Keep module tests under paths such as `t
55
56
56
57
Family words such as `Framework`, `Applications`, and `Hosts` belong in directories, not in project names. Facet words such as `Application`, `Persistence`, and `Web` stay in project names when they describe the project role.
57
58
59
+
## Logical Taxonomy
60
+
61
+
ChengYuan currently keeps the physical top-level folders `Framework`, `Applications`, and `Hosts`, but new design work should follow the logical taxonomy below:
62
+
63
+
| Logical Class | Meaning | Typical Examples |
64
+
|---|---|---|
65
+
|`FrameworkCore`| Reusable technical capabilities and modularity primitives that are broadly reusable across business modules |`Core`, `ExecutionContext`, `MultiTenancy`, `Authorization`, `Settings`, `Features`, `Auditing`, `Outbox`|
66
+
|`Application`| Reusable business capabilities and bounded contexts |`Identity`, `TenantManagement`, `SettingManagement`, `PermissionManagement`, `FeatureManagement`, `AuditLogging`|
67
+
|`Extension`| Optional, on-demand modules that connect a capability to a concrete technology or transport |`*.Persistence`, `*.Web`, `MemoryCaching`, future `Redis`, `MongoDb`, HTTP tenant source adapters |
68
+
|`Host`| Runnable shells that compose modules and provide environment-specific configuration |`WebHost`, `CliHost`|
69
+
70
+
The classification is based on responsibility, not directory name. For example, `ChengYuan.Caching.Memory` lives under `src/Framework/`, but its logical class is `Extension`, not `FrameworkCore`.
71
+
72
+
### Module Base Classes
73
+
74
+
Each logical class maps to a dedicated abstract base class. Production modules must inherit the corresponding category-specific base class instead of `ModuleBase` directly:
75
+
76
+
| Logical Class | Base Class | When to Use |
77
+
|---|---|---|
78
+
|`FrameworkCore`|`FrameworkCoreModule`| Shared technical capabilities and modularity primitives |
79
+
|`Application`|`ApplicationModule`| Business capabilities and bounded contexts |
80
+
|`Extension`|`ExtensionModule`| Storage, transport, adapter, or technology bindings |
81
+
|`Host`|`HostModule`| Runnable composition shells and environment glue |
82
+
83
+
`ModuleBase` remains the low-level engine primitive. Direct `ModuleBase` inheritance is reserved for low-level modularity infrastructure only. All other modules should choose one of the four category-specific bases.
84
+
85
+
The service-registration entry point is shared: every module receives a `ServiceConfigurationContext` that wraps `IServiceCollection` along with an `IInitLoggerFactory` for pre-DI logging and an `Items` dictionary for cross-module state. `ModuleBase` provides `Configure<TOptions>()` helpers and owns the shared lifecycle template: `OnLoaded`, `PreConfigureServices`, `ConfigureServices`, `PostConfigureServices`, `PreInitializeAsync`, `InitializeAsync`, `PostInitializeAsync`, and `ShutdownAsync`. During module initialization, any log entries buffered through `IInitLogger<T>` during the service-registration phase are replayed through the real `ILoggerFactory`.
86
+
87
+
During `OnLoaded`, `ModuleBase` caches the current module descriptor, direct dependencies, direct dependents, resolved category, and root-module state. Category-specific base classes should use this cached topology instead of querying the catalog again.
88
+
89
+
The category-specific base classes are not markers only. They add category-aware behavior during the load stage and expose category-scoped lifecycle hooks for derived modules:
90
+
91
+
-`FrameworkCoreModule` validates that framework modules only depend on other `FrameworkCore` modules, then distinguishes shared foundations from leaf foundations based on cached dependents.
92
+
-`ApplicationModule` validates that application modules only depend on `FrameworkCore` or other `Application` modules, then exposes capability-owner state such as whether they are extended by `Extension` modules or composed directly by `Host` modules.
93
+
-`ExtensionModule` validates that extension modules only depend on `FrameworkCore`, `Application`, or other `Extension` modules, then resolves the capability they attach to from the dependency graph. Persistence remains an `Extension` shape, not a fifth category.
94
+
-`HostModule` validates that host modules are only depended on by other `Host` modules, then branches behavior between root startup hosts and inner host-composition modules by using cached root-module state.
95
+
96
+
`ModuleDescriptor.Category` remains the runtime-visible metadata for inspection, tests, and host diagnostics. New modules should override the template methods or category-scoped hooks directly.
97
+
58
98
## Module Families
59
99
60
100
| Family | Purpose | Typical Examples | Notes |
@@ -65,6 +105,8 @@ Family words such as `Framework`, `Applications`, and `Hosts` belong in director
65
105
66
106
`ChengYuan.Core` is the only framework module family member allowed to own foundational modularity, failure, DDD, and shared extension seams. `ChengYuan.Hosting` stays as a thin composition helper and must not own the module model.
67
107
108
+
Within these physical families, the logical rule is: `FrameworkCore` and `Extension` may both live under `src/Framework/`, while `Application` and its optional `Extension` projects may both live under `src/Applications/`. `Host` projects remain under `src/Hosts/`.
109
+
68
110
## Facet Model
69
111
70
112
Every module is a vertical slice, but not every slice needs the same facets.
@@ -90,6 +132,8 @@ Every module is a vertical slice, but not every slice needs the same facets.
90
132
|`Web`| Optional HTTP adapter |
91
133
|`Cli`| Optional CLI adapter |
92
134
135
+
`Persistence`, `Web`, `Cli`, `Memory`, and similar technology-facing facets are treated as `Extension` modules in the logical taxonomy. They are optional modules that can be attached to an `Application` or `FrameworkCore` capability when needed.
136
+
93
137
### Uneven Depth Is Intentional
94
138
95
139
Examples:
@@ -136,6 +180,9 @@ The core rule is to separate technical systems from the management modules built
136
180
- Fallback is only applied when **no source provides a candidate**; if a candidate is supplied but lookup fails, the outcome is `NotFound`, not fallback.
137
181
- Custom contributors can be registered via `builder.AddContributor<T>()` for semantic resolution.
138
182
- Custom sources can be registered via `builder.AddSource<T>()` for host-specific input extraction.
183
+
-`WebHost` keeps one thin public composition seam: `AddWebHostComposition(...)` for service registration and `UseWebHostComposition()` for pipeline activation.
184
+
- Internally, `WebHost` should be split into three explicit composition modules: framework composition, application composition, and HTTP composition. Do not let a single host module accumulate all transport, runtime, and application wiring.
185
+
- HTTP tenant resolution sources remain Host-side extensions. Register them through a dedicated Host builder/module, not by pushing HTTP concerns down into `ChengYuan.MultiTenancy.Runtime`.
139
186
- CLI hosts do not participate in the current multi-tenancy design. The CLI host runs without tenant resolution and does not expose CLI-specific tenant input adapters.
140
187
141
188
## Core Foundation Design
@@ -154,7 +201,7 @@ The rule is simple:
154
201
| Module | Owns | Must Not Own | Depends On |
155
202
|---|---|---|---|
156
203
|`ChengYuan.Core`| Base exceptions, error codes, `Result`, DDD primitives, strongly typed IDs, object extension contracts, `IClock`, `IGuidGenerator`| Json, EF Core, tenant context, current user context, caching, outbox | Nothing internal |
157
-
|`ChengYuan.Core.Runtime`| Module descriptors, module catalog, lifecycle hooks, module bootstrap and ordering | Domain primitives, serializer providers, data providers |`ChengYuan.Core`|
|`ChengYuan.Core.Localization`| Resource registration and exception or error localization seams | Validation runtime policy, persistence providers |`ChengYuan.Core`|
@@ -249,13 +296,21 @@ User business modules created later from templates also belong in `src/Applicati
249
296
250
297
### Web Host
251
298
252
-
`ChengYuan.WebHost` is an API-first ASP.NET Core shell. It composes:
299
+
`ChengYuan.CliHost` is a thin command-oriented shell. It composes:
253
300
254
301
- selected framework runtime facets
255
-
- selected application `Web` facets
256
-
- middleware, auth integration, health checks, and transport policies
302
+
- selected application modules or future `Cli` facets
303
+
- CLI-specific runtime glue and scripting-friendly output
304
+
305
+
Internally, `CliHost` should follow the same host layering rule as `WebHost`:
306
+
307
+
-`CliHostFrameworkCompositionModule` for framework runtime dependencies
308
+
-`CliHostApplicationCompositionModule` for selected application modules
309
+
-`CliHostRuntimeGlueModule` for CLI-only runtime glue
310
+
311
+
`Program.cs` should stay minimal and delegate to host composition seams such as `AddCliHostComposition(...)` and `RunCliHostCompositionAsync()`.
257
312
258
-
It does not own application use cases.
313
+
CLI scenarios may intentionally omit modules that are irrelevant to command execution. Web transport facets should not be pulled into `CliHost`, but persistence-backed application modules are acceptable when the command workload needs them.
|`ModuleBase`| Base type for framework and application modules. |
42
+
|---|---|
43
+
|`ModuleBase`| Low-level module engine primitive and unified lifecycle template. Owns load, service-registration, initialization, and shutdown entry points, and caches direct topology metadata during load for derived modules. |
44
+
|`FrameworkCoreModule`| Category-specific base class for reusable technical capabilities and modularity primitives. Validates `FrameworkCore -> FrameworkCore` dependencies and exposes shared-foundation versus leaf-foundation load hooks. |
45
+
|`ApplicationModule`| Category-specific base class for reusable business capabilities and bounded contexts. Validates `Application -> FrameworkCore/Application` dependencies and exposes capability-owner composition state such as host and extension dependents. |
46
+
|`ExtensionModule`| Category-specific base class for storage, transport, adapter, and technology-binding modules. Validates `Extension -> FrameworkCore/Application/Extension` dependencies, resolves the attached capability from the graph, and exposes binding-specific lifecycle hooks. |
47
+
|`HostModule`| Category-specific base class for runnable composition shells and environment glue. Validates that only other `Host` modules depend on host modules and branches lifecycle hooks between root hosts and inner host-composition modules. |
48
+
|`ModuleCategory`| Runtime-visible logical category for a loaded module descriptor. |
49
+
|`IModuleLoadContext` / `ModuleLoadContext`| Load-stage context carrying the current module descriptor, the full catalog, and direct dependents for category validation, topology caching, and diagnostics. |
44
50
|`DependsOnAttribute`| Declares direct module dependencies for composition ordering. |
45
51
|`ModuleCatalog`| Exposes the ordered module list loaded by a host. |
52
+
|`IModuleDescriptor.Category`| Reports whether a loaded module is `FrameworkCore`, `Application`, `Extension`, or `Host`. |
53
+
|`ServiceConfigurationContext`| Service-registration context wrapping `IServiceCollection`, `IInitLoggerFactory`, and a cross-module `Items` dictionary. Passed to every module during `ConfigureServices`. |
54
+
|`IInitLoggerFactory`| Factory for creating `IInitLogger<T>` instances that buffer log entries before DI is built. Exposed via `ServiceConfigurationContext.InitLoggerFactory`. |
55
+
|`IInitLogger<T>`|`ILogger<T>` implementation that buffers `InitLogEntry` records during module loading for later replay once DI is available. |
56
+
|`InitLogEntry`| Sealed record carrying category name, log level, event id, formatted message, and optional exception for replay. |
57
+
|`IHasLogLevel`| Interface for exceptions or objects that carry a self-declared `LogLevel`. |
58
+
|`IExceptionWithSelfLogging`| Interface for exceptions that provide structured self-logging via `Log(ILogger)`. |
59
+
|`HasLogLevelExtensions`| Fluent `WithLogLevel()` extension for `IHasLogLevel` exceptions. |
|`FeatureContext`| Carries the current tenant, user, and correlation values into feature evaluation. |
476
490
|`FeatureValue`| Carries a resolved raw value plus its provider source. |
477
491
492
+
### `ChengYuan.Caching.Abstractions`
493
+
494
+
Pluggable caching abstraction layer with typed cache contract, cache naming, and global options.
495
+
496
+
#### Key Types
497
+
498
+
| Type | Description |
499
+
|---|---|
500
+
|`IChengYuanCache<TCacheItem>`| Typed cache interface exposing `GetAsync`, `SetAsync`, `ExistsAsync`, `RemoveAsync`, and `GetOrCreateAsync` operations with `ValueTask` returns. |
501
+
|`CacheNameAttribute`| Attribute-driven cache name resolution. Falls back to stripping the `CacheItem` suffix from the type's full name. |
502
+
|`ChengYuanCacheOptions`| Global cache options: `HideErrors` (default `true`), `KeyPrefix`, `GlobalCacheEntryOptions` (default 20 min sliding), and per-type `CacheConfigurators`. |
503
+
|`ChengYuanCacheEntryOptions`| Per-entry expiration settings with absolute and sliding expiration. |
504
+
|`IChengYuanCache`| Non-generic low-level cache interface for raw byte operations. |
505
+
|`IChengYuanCacheStore`| Provider-agnostic store abstraction consumed by cache implementations. |
506
+
|`IChengYuanCacheKeyNormalizer`| Normalizes cache keys with prefix and scope information. |
507
+
|`ChengYuanCacheKey`| Value carrying the raw key, normalized key, and optional scope. |
508
+
|`IChengYuanCacheSerializer`| Serialization abstraction for cache item types. |
509
+
510
+
### `ChengYuan.Caching.Runtime`
511
+
512
+
Default caching runtime with stampede protection and error hiding.
513
+
514
+
#### Key Types
515
+
516
+
| Type | Description |
517
+
|---|---|
518
+
|`CachingModule`| Registers default `ChengYuanCacheOptions` with 20-minute sliding expiration. |
519
+
|`AddCaching()`| Registers the open-generic `IChengYuanCache<>` backed by `DefaultChengYuanTypedCache<>`, the non-generic cache, serializer, and key normalizer. |
520
+
|`DefaultChengYuanTypedCache<TCacheItem>`| Internal typed cache with `SemaphoreSlim`-based stampede protection in `GetOrCreateAsync` (double-check pattern), `HideErrors` exception filtering, and `[LoggerMessage]`-based warning for swallowed cache errors. |
521
+
522
+
### `ChengYuan.Caching.Memory`
523
+
524
+
In-memory cache store backed by `IMemoryCache`.
525
+
526
+
#### Key Types
527
+
528
+
| Type | Description |
529
+
|---|---|
530
+
|`MemoryCachingModule`| Registers the memory cache store on top of the Caching runtime module. |
531
+
|`MemoryCacheStore`|`IChengYuanCacheStore` implementation backed by `IMemoryCache`. |
0 commit comments