402 lines
13 KiB
Markdown
402 lines
13 KiB
Markdown
# Structive Design Philosophy and Principles
|
|
|
|
[中文](DESIGN.zh-CN.md)
|
|
|
|
## 1. Purpose
|
|
|
|
Structive exists to give ordinary C++ structs explicit structural meaning while preserving the native C++ object model.
|
|
|
|
It is not a replacement language, reflection runtime, security boundary, ORM or object framework. It is a structural metadata and managed-property layer that remains close to normal C++.
|
|
|
|
The design target is:
|
|
|
|
```text
|
|
ordinary C++ data
|
|
+ explicit schema
|
|
+ intrinsic property capability
|
|
+ metadata
|
|
+ optional managed synchronization
|
|
+ optional runtime adaptation
|
|
```
|
|
|
|
## 2. Primary principle: enhance, do not replace
|
|
|
|
The first rule is:
|
|
|
|
> **Enhance the struct; do not replace the struct.**
|
|
|
|
A registered field remains a real member. Unregistered state remains normal C++. Raw access remains possible when the C++ type itself permits it.
|
|
|
|
Structive must not require every field to become a wrapper such as `Property<T>`, nor should it force application objects into a second object model.
|
|
|
|
### Rule
|
|
|
|
If a Structive feature can be implemented as metadata or a thin managed layer without changing the native member model, prefer that design.
|
|
|
|
## 3. Type information and instance behavior are separate
|
|
|
|
Structive has two architectural layers.
|
|
|
|
### 3.1 Type layer
|
|
|
|
`Type_Descriptor<T>` and `Object_Schema` contain structural facts:
|
|
|
|
- registered properties;
|
|
- keys;
|
|
- intrinsic readable/writable capability;
|
|
- Attributes;
|
|
- Constraints;
|
|
- dependency graph;
|
|
- default synchronization description.
|
|
|
|
This information belongs to the type.
|
|
|
|
### 3.2 Instance layer
|
|
|
|
`Property_Object<T>` provides instance behavior:
|
|
|
|
- managed read/write;
|
|
- lock storage for writable synchronization domains;
|
|
- multi-property guards;
|
|
- traversal;
|
|
- type-erased runtime access;
|
|
- optional per-instance synchronization override.
|
|
|
|
### Rule
|
|
|
|
Do not move type-level facts into every object instance unless the fact genuinely varies per instance.
|
|
|
|
## 4. Registration is explicit
|
|
|
|
Structive does not assume every member is a property.
|
|
|
|
```cpp
|
|
struct Device : Property_Object<Device> {
|
|
int temperature;
|
|
int internal_cache;
|
|
};
|
|
```
|
|
|
|
If only `temperature` is registered, `internal_cache` does not exist in the Structive schema.
|
|
|
|
### Rule
|
|
|
|
Registration defines participation. Absence from the schema means absence from Structive.
|
|
|
|
## 5. Intrinsic capability belongs to the property itself
|
|
|
|
Structive has no built-in access-control subsystem.
|
|
|
|
A property only describes what it intrinsically supports:
|
|
|
|
```text
|
|
none
|
|
read
|
|
write
|
|
read_write
|
|
```
|
|
|
|
The capability is normally derived from the accessor. Metadata may explicitly narrow it.
|
|
|
|
```cpp
|
|
field<&Device::serial_number>(key<"serial_number">, read_only)
|
|
```
|
|
|
|
This says:
|
|
|
|
> Within the Structive managed model, this property is readable and not writable.
|
|
|
|
It does not say which user, service, GUI or process is allowed to see it.
|
|
|
|
### Rule
|
|
|
|
Property capability describes structure, not authorization.
|
|
|
|
## 6. Core must not own external access policy
|
|
|
|
Structive deliberately does not define:
|
|
|
|
```text
|
|
internal
|
|
external
|
|
persistence
|
|
role
|
|
context
|
|
permission
|
|
```
|
|
|
|
as managed access modes.
|
|
|
|
A GUI can decide which properties are editable. An RPC service can decide which fields are exposed. A persistence layer can decide which fields it saves. Those decisions belong to those systems.
|
|
|
|
Core exposes structural facts; consumers define policy.
|
|
|
|
### Rule
|
|
|
|
Do not add access policy to Core merely because an adapter needs a policy. The adapter owns that policy.
|
|
|
|
## 7. Raw access and managed access are distinct contracts
|
|
|
|
These are intentionally different:
|
|
|
|
```cpp
|
|
device.temperature = 30;
|
|
device.write<&Device::temperature>(30);
|
|
```
|
|
|
|
Raw access follows normal C++ rules. Managed access follows the Structive schema and synchronization model.
|
|
|
|
If code writes a `read_only` public member directly, it has intentionally bypassed the Structive contract.
|
|
|
|
### Rule
|
|
|
|
Structive protects cooperative managed code. It does not pretend to prevent deliberate raw C++ access.
|
|
|
|
## 8. Read-only metadata must produce a real optimization
|
|
|
|
A stored property that cannot be written through Structive cannot race with another Structive managed writer, because no managed writer exists.
|
|
|
|
Therefore a stored intrinsic read-only property:
|
|
|
|
- receives no lock slot;
|
|
- contributes no mutex;
|
|
- ignores broad synchronization defaults;
|
|
- performs managed reads without lock lookup;
|
|
- performs managed reads without `shared_lock` construction.
|
|
|
|
This is a structural optimization derived from schema information.
|
|
|
|
### Rule
|
|
|
|
If the schema proves that synchronization state is unnecessary, do not allocate or execute it.
|
|
|
|
## 9. Compile-time knowledge should remove runtime work
|
|
|
|
Typed APIs know the selected property at compile time:
|
|
|
|
```cpp
|
|
device.read<&Device::serial_number>();
|
|
```
|
|
|
|
For a stored read-only property, the compiler-visible implementation path bypasses synchronization entirely.
|
|
|
|
Similarly, typed writes to a read-only property are removed by constraints rather than accepted and rejected at runtime.
|
|
|
|
Dynamic key APIs use runtime checks because the key is not known until runtime.
|
|
|
|
### Rule
|
|
|
|
Static facts should become `constexpr`, `requires` or `if constexpr`, not runtime branches.
|
|
|
|
## 10. Synchronization describes mutable consistency domains
|
|
|
|
Synchronization exists to coordinate mutable managed state.
|
|
|
|
The default rules are:
|
|
|
|
```text
|
|
independent
|
|
shared
|
|
unsynchronized
|
|
```
|
|
|
|
Groups allow several mutable properties to share one lock domain.
|
|
|
|
Read-only stored properties are removed from the final resolved lock topology even if a broad rule names them.
|
|
|
|
### Rule
|
|
|
|
Synchronization topology is about mutable consistency, not property visibility.
|
|
|
|
## 11. Computed read-only properties are a special case
|
|
|
|
A computed property may itself be read-only while depending on writable fields.
|
|
|
|
The computed property does not contain writable storage. However, its read may require a stable snapshot of mutable dependencies.
|
|
|
|
Dependencies must be explicit Schema facts. Every Property Accessor declares a `dependency_spec`; no-dependency accessors use `No_Property_Dependencies` explicitly. The `Synchronized_Computed_Accessor` read view can access only declared direct dependencies, and the computed property's read slot is derived from the dependency graph instead of requiring its key to be repeated in a synchronization group. The dependency graph is a DAG invariant and cycles are rejected at compile time when the Schema is formed.
|
|
|
|
Writable dependencies that require one atomic snapshot must share one synchronization domain with each other. Read-only stored dependencies are safe to read directly through the synchronized view because they have no managed writer.
|
|
|
|
### Rule
|
|
|
|
Do not give a read-only stored field a mutex. A computed read uses synchronization only for mutable dependencies that require consistency.
|
|
|
|
## 12. One property metadata store
|
|
|
|
A Property Descriptor has one metadata store. Its entries are either Attributes or Constraints.
|
|
|
|
Core and extensions share one Attribute mechanism for descriptive metadata. An Attribute owns a category and declares whether it is single-valued and inheritable. Constraints use the validation protocol but live in the same descriptor metadata store.
|
|
|
|
Core must not create parallel metadata storage systems for UI, serialization, diagnostics or domain-specific features.
|
|
|
|
### Rule
|
|
|
|
New descriptive metadata domains should extend the Attribute protocol; new validation rules should extend the Constraint protocol. Both remain entries in the same Property metadata store.
|
|
|
|
## 13. Category ownership must be clear
|
|
|
|
The component that defines an Attribute category owns its semantics.
|
|
|
|
Presentation owns presentation categories. Core can store them, traverse them and expose them generically, but it must not interpret them.
|
|
|
|
This keeps dependency direction one-way:
|
|
|
|
```text
|
|
Extension → Core
|
|
Core -X→ Extension
|
|
```
|
|
|
|
### Rule
|
|
|
|
Core stores unknown extension metadata without learning extension semantics.
|
|
|
|
## 14. Defaults are metadata inheritance only
|
|
|
|
`defaults(...)` is for inheritable Attributes. It should not mutate objects or hide procedural behavior.
|
|
|
|
Property-level metadata can override an inheritable default for the same single-valued category.
|
|
|
|
Intrinsic capability is not inheritable because the capability describes each property itself.
|
|
|
|
### Rule
|
|
|
|
Defaults may reduce metadata repetition, but they must not become an invisible behavior engine.
|
|
|
|
## 15. Validation is explicit
|
|
|
|
Constraints describe valid values. They do not automatically execute inside every write.
|
|
|
|
```cpp
|
|
auto error = validate_property_value<&Device::temperature>(schema, candidate);
|
|
```
|
|
|
|
Field validation, cross-field invariants, transaction boundaries and rollback are separate operations.
|
|
|
|
### Rule
|
|
|
|
Do not turn `write()` into an implicit workflow containing validation, events, transactions and rollback.
|
|
|
|
## 16. Runtime access is adaptation, not the primary programming model
|
|
|
|
`Property_Object_Base` provides key-based type-erased access for dynamic systems.
|
|
|
|
Runtime access only uses intrinsic capability. There is no runtime permission mode.
|
|
|
|
An adapter decides whether it should expose a property and whether it should invoke runtime read or write.
|
|
|
|
### Rule
|
|
|
|
Use member-pointer typed access inside normal C++ business code. Use runtime access at dynamic boundaries.
|
|
|
|
## 17. Runtime and compile-time errors have different jobs
|
|
|
|
Compile-time typed operations should reject impossible structural operations through constraints.
|
|
|
|
Examples:
|
|
|
|
- writing an intrinsic read-only property;
|
|
- requesting a typed unique guard for a read-only property;
|
|
- reading a write-only property.
|
|
|
|
Runtime key APIs report dynamic failures through `Runtime_Access_Result`. The runtime write boundary is copy-input by design: intrinsic `writable` remains a structural fact, while `runtime_copy_writable` states whether the accessor can participate in that type-erased copy boundary. Move-only typed writes therefore do not get mislabeled as structurally non-writable.
|
|
|
|
### Rule
|
|
|
|
Do not defer a statically knowable property error to runtime.
|
|
|
|
## 18. Synchronization policy remains replaceable
|
|
|
|
`Shared_Mutex_Policy` provides real shared/exclusive locking. `No_Lock_Policy` removes real mutex storage.
|
|
|
|
NoLock should not allocate fake mutex arrays or construct meaningless lock objects.
|
|
|
|
### Rule
|
|
|
|
A policy that removes a capability should remove its storage and hot-path cost where possible.
|
|
|
|
## 19. Per-instance synchronization overrides preserve object-local policy
|
|
|
|
The default resolved topology is shared per type. An object may explicitly receive a `Property_Synchronization` override.
|
|
|
|
Only such an object stores an override layout. Copy/move construction preserves that layout; assignment preserves the target object's synchronization policy.
|
|
|
|
### Rule
|
|
|
|
Do not make the default path pay the storage cost of a feature that only some instances use.
|
|
|
|
## 20. Extensions decide domain behavior
|
|
|
|
A persistence extension may choose fields based on its own metadata. A GUI may decide editability. An RPC layer may implement authorization.
|
|
|
|
Those systems may inspect Structive metadata such as `readable`, `writable`, `sensitive` or custom extension Attributes, but Structive does not decide their policy for them.
|
|
|
|
### Rule
|
|
|
|
Core describes structure. Consumers decide behavior at their boundary.
|
|
|
|
## 21. Non-goals
|
|
|
|
Property Core is not intended to become:
|
|
|
|
- an authorization engine;
|
|
- an ORM;
|
|
- a JSON library;
|
|
- an RPC framework;
|
|
- a GUI binding framework;
|
|
- a transaction manager;
|
|
- an event bus;
|
|
- a scripting engine.
|
|
|
|
Such systems can consume Structive but should remain separate.
|
|
|
|
## 22. Evolution rules
|
|
|
|
When extending Structive, review the change against these rules:
|
|
|
|
1. Does it enhance normal C++ rather than replace it?
|
|
2. Is the information type-level or instance-level?
|
|
3. Is this intrinsic structural capability or external policy?
|
|
4. Can compile-time information remove runtime work?
|
|
5. Does a read-only stored property remain outside the lock topology?
|
|
6. Is synchronization limited to mutable consistency?
|
|
7. Is validation still explicit?
|
|
8. Does the extension own its own category semantics?
|
|
9. Is runtime adaptation kept separate from typed business APIs?
|
|
10. Is a new Core concept genuinely universal?
|
|
|
|
## 23. Architectural summary
|
|
|
|
The intended architecture is:
|
|
|
|
```text
|
|
Native C++ struct
|
|
│
|
|
├── raw C++ access
|
|
│
|
|
└── Structive schema
|
|
│
|
|
├── intrinsic readable/writable capability
|
|
├── Attributes
|
|
├── Constraints
|
|
├── synchronization description
|
|
│ └── only mutable consistency domains create locks
|
|
└── managed object
|
|
├── typed read/write
|
|
├── guards
|
|
├── traversal
|
|
└── intrinsic runtime access
|
|
|
|
External systems
|
|
├── GUI policy
|
|
├── RPC policy
|
|
├── persistence policy
|
|
└── other domain policy
|
|
|
|
External policies consume Structive facts; they are not Structive Core access modes.
|
|
```
|
|
|
|
The compact statement of the design is:
|
|
|
|
> **Structive describes what a property is and what it intrinsically supports. It does not decide who may use it.**
|