Skip to content

Commit 6178cde

Browse files
committed
feat: add init logging utilities, typed cache with stampede protection, and update docs
- Add IInitLoggerFactory, IInitLogger<T>, InitLogEntry for pre-DI log buffering - Add IHasLogLevel, IExceptionWithSelfLogging, HasLogLevelExtensions - Add ServiceConfigurationContext wrapping IServiceCollection + IInitLoggerFactory - Integrate init log replay into ModuleManager.InitializeAsync - Add IChengYuanCache<T> typed cache with GetOrCreateAsync stampede protection - Add CacheNameAttribute for attribute-driven cache naming - Add ChengYuanCacheOptions with HideErrors, KeyPrefix, GlobalCacheEntryOptions - Add DefaultChengYuanTypedCache<T> with SemaphoreSlim double-check pattern - Register open-generic IChengYuanCache<> in CachingServiceCollectionExtensions - Update architecture.md and api.md (EN + ZH) with new logging and caching APIs - Add 18 new tests: InitLogger, LoggingUtility, TypedCache, CacheErrorHiding, CacheNameAttribute
1 parent 14cf184 commit 6178cde

File tree

243 files changed

+6696
-1986
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

243 files changed

+6696
-1986
lines changed

CONTRIBUTING.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,36 @@ The CI pipeline behavior is controlled by boolean switches in `.github/workflows
5858
- Run `./build.sh Format` locally before committing.
5959
- All warnings are treated as errors (`TreatWarningsAsErrors`).
6060

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+
6191
## Versioning
6292

6393
- Version is managed via `VersionPrefix` in `Directory.Build.props`.

ChengYuan.slnx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<Solution>
22
<Folder Name="/src/Framework/">
33
<Project Path="src/Framework/Core/ChengYuan.Core.csproj" />
4+
<Project Path="src/Framework/Data/ChengYuan.EntityFrameworkCore/ChengYuan.EntityFrameworkCore.csproj" />
5+
<Project Path="src/Framework/Data/ChengYuan.Data.Sqlite/ChengYuan.Data.Sqlite.csproj" />
6+
<Project Path="src/Framework/Data/ChengYuan.Data.PostgreSql/ChengYuan.Data.PostgreSql.csproj" />
47
<Project Path="src/Framework/Hosting/ChengYuan.Hosting.csproj" />
58
<Project Path="src/Framework/Caching/ChengYuan.Caching.Abstractions/ChengYuan.Caching.Abstractions.csproj" />
69
<Project Path="src/Framework/Caching/ChengYuan.Caching.Runtime/ChengYuan.Caching.Runtime.csproj" />

Directory.Packages.props

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
1111
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
1212
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
13+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
1314
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.0" />
1415
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="10.0.0" />
1516
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
1617
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.0" />
18+
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0" />
19+
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
1720
<!-- Analyzers -->
1821
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="5.0.0-1.25277.114" />
1922
<!-- Testing -->

docs/guide/architecture.md

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ When existing code and this guide differ, use this guide as the default rule for
77
## Design Principles
88

99
- 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.
1112
- Allow uneven module depth. Do not force every module to expose the same layers or transports.
1213
- Keep Web and CLI as optional transport facets.
1314
- 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
5556

5657
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.
5758

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+
5898
## Module Families
5999

60100
| Family | Purpose | Typical Examples | Notes |
@@ -65,6 +105,8 @@ Family words such as `Framework`, `Applications`, and `Hosts` belong in director
65105

66106
`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.
67107

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+
68110
## Facet Model
69111

70112
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.
90132
| `Web` | Optional HTTP adapter |
91133
| `Cli` | Optional CLI adapter |
92134

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+
93137
### Uneven Depth Is Intentional
94138

95139
Examples:
@@ -136,6 +180,9 @@ The core rule is to separate technical systems from the management modules built
136180
- Fallback is only applied when **no source provides a candidate**; if a candidate is supplied but lookup fails, the outcome is `NotFound`, not fallback.
137181
- Custom contributors can be registered via `builder.AddContributor<T>()` for semantic resolution.
138182
- 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`.
139186
- 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.
140187

141188
## Core Foundation Design
@@ -154,7 +201,7 @@ The rule is simple:
154201
| Module | Owns | Must Not Own | Depends On |
155202
|---|---|---|---|
156203
| `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` |
204+
| `ChengYuan.Core.Runtime` | Module descriptors, module catalog, lifecycle hooks, module bootstrap and ordering, `ServiceConfigurationContext`, init logging (`IInitLoggerFactory`, `IInitLogger<T>`), logging utilities (`IHasLogLevel`, `IExceptionWithSelfLogging`) | Domain primitives, serializer providers, data providers | `ChengYuan.Core` |
158205
| `ChengYuan.Core.Json` | Serializer abstractions, System.Text.Json integration, strongly typed ID converters | EF Core, repository or UoW logic | `ChengYuan.Core` |
159206
| `ChengYuan.Core.Validation` | Validation contracts and default validation pipeline | Localization resources, persistence providers | `ChengYuan.Core` |
160207
| `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
249296

250297
### Web Host
251298

252-
`ChengYuan.WebHost` is an API-first ASP.NET Core shell. It composes:
299+
`ChengYuan.CliHost` is a thin command-oriented shell. It composes:
253300

254301
- 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()`.
257312

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.
259314

260315
### CLI Host
261316

docs/reference/api.md

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,24 @@ Core module composition primitives.
3939
#### Key Types
4040

4141
| Type | Description |
42-
|---|---|---|
43-
| `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. |
4450
| `DependsOnAttribute` | Declares direct module dependencies for composition ordering. |
4551
| `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. |
4660

4761
### `ChengYuan.Core.Json`
4862

@@ -475,6 +489,47 @@ Definition-driven runtime feature evaluation.
475489
| `FeatureContext` | Carries the current tenant, user, and correlation values into feature evaluation. |
476490
| `FeatureValue` | Carries a resolved raw value plus its provider source. |
477491

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`. |
532+
478533
#### Example
479534

480535
```csharp

0 commit comments

Comments
 (0)