268 lines
7.5 KiB
Markdown
268 lines
7.5 KiB
Markdown
# Structive Extension Architecture
|
|
|
|
[中文](EXTENSIONS.zh-CN.md)
|
|
|
|
## 1. Extension role
|
|
|
|
Structive extensions add domain-specific interpretation without changing the Core structural model.
|
|
|
|
The intended dependency is:
|
|
|
|
```text
|
|
application / adapter
|
|
↓
|
|
Structive extension
|
|
↓
|
|
Structive Property Core
|
|
```
|
|
|
|
Core does not include or link extension code.
|
|
|
|
## 2. Extension principle
|
|
|
|
An extension should normally add two things:
|
|
|
|
1. one or more Attribute categories that express domain metadata;
|
|
2. interpretation code that consumes those Attributes.
|
|
|
|
It should not create a parallel property registry when the existing `Object_Schema` already contains the required structure.
|
|
|
|
## 3. Current presentation extension
|
|
|
|
The current extension module defines four Attribute categories:
|
|
|
|
```cpp
|
|
presentation::Label_Category
|
|
presentation::Description_Category
|
|
presentation::Group_Category
|
|
presentation::Order_Category
|
|
```
|
|
|
|
Convenience Attribute values:
|
|
|
|
```cpp
|
|
presentation::label<"Temperature">
|
|
presentation::description<"Current device temperature">
|
|
presentation::group<"Environment">
|
|
presentation::order<10>
|
|
```
|
|
|
|
They can be attached directly to a normal Core property:
|
|
|
|
```cpp
|
|
field<&Device::temperature>(
|
|
key<"temperature">,
|
|
unit<"C">,
|
|
presentation::label<"Temperature">,
|
|
presentation::description<"Current device temperature">,
|
|
presentation::group<"Environment">,
|
|
presentation::order<10>
|
|
)
|
|
```
|
|
|
|
No wrapper such as `hint(...)` is required.
|
|
|
|
## 4. Presentation interpretation
|
|
|
|
Include:
|
|
|
|
```cpp
|
|
#include <structive/property/extensions/presentation.hpp>
|
|
```
|
|
|
|
Link:
|
|
|
|
```cmake
|
|
target_link_libraries(my_target PRIVATE structive::property_extensions)
|
|
```
|
|
|
|
Describe a property by member pointer:
|
|
|
|
```cpp
|
|
auto info = presentation::describe<&Device::temperature>(device.schema());
|
|
```
|
|
|
|
The result contains:
|
|
|
|
```cpp
|
|
struct Presentation_Info {
|
|
std::string_view key;
|
|
std::string_view label;
|
|
std::string_view description;
|
|
std::string_view group;
|
|
std::size_t order;
|
|
bool has_order;
|
|
};
|
|
```
|
|
|
|
If no presentation label is declared, the extension uses the property key as the display label. This fallback is presentation policy and intentionally lives outside Core.
|
|
|
|
## 5. Inheritable extension Attributes
|
|
|
|
The presentation `group` Attribute is inheritable, so it may be placed in `defaults(...)`:
|
|
|
|
```cpp
|
|
return object<Device>(
|
|
defaults(
|
|
external_access<External_Access::read>,
|
|
presentation::group<"Environment">
|
|
),
|
|
field<&Device::temperature>(
|
|
key<"temperature">,
|
|
presentation::label<"Temperature">
|
|
),
|
|
field<&Device::pressure>(
|
|
key<"pressure">,
|
|
presentation::label<"Pressure">
|
|
)
|
|
);
|
|
```
|
|
|
|
The extension resolves an effective declared value through the same Core default mechanism.
|
|
|
|
`label`, `description` and `order` are not inheritable and therefore cannot be placed in `defaults(...)`.
|
|
|
|
## 6. Designing a new extension Attribute
|
|
|
|
Example:
|
|
|
|
```cpp
|
|
namespace my_adapter {
|
|
struct Json_Name_Category {};
|
|
template <Fixed_String Value>
|
|
struct Json_Name_Attribute {
|
|
using attribute_category = Json_Name_Category;
|
|
static constexpr bool single_valued = true;
|
|
static constexpr bool inheritable = false;
|
|
static constexpr auto value = Value;
|
|
};
|
|
template <Fixed_String Value>
|
|
inline constexpr Json_Name_Attribute<Value> json_name{};
|
|
}
|
|
```
|
|
|
|
Use it in a schema:
|
|
|
|
```cpp
|
|
field<&Device::temperature>(
|
|
key<"temperature">,
|
|
my_adapter::json_name<"temp">
|
|
)
|
|
```
|
|
|
|
The Core stores it without needing to know what JSON means.
|
|
|
|
## 7. Reading extension Attributes
|
|
|
|
For a property descriptor:
|
|
|
|
```cpp
|
|
using Property = std::remove_cvref_t<decltype(property)>;
|
|
if constexpr (Property::has_attribute<my_adapter::Json_Name_Category>) {
|
|
const auto& value = property.attribute<my_adapter::Json_Name_Category>();
|
|
}
|
|
```
|
|
|
|
For inheritable categories, use effective declared lookup:
|
|
|
|
```cpp
|
|
if constexpr (has_declared_effective_attribute_v<Schema, Index, My_Category>) {
|
|
const auto& value = declared_effective_attribute<Index, My_Category>(schema);
|
|
}
|
|
```
|
|
|
|
This checks the property declaration first and then object defaults.
|
|
|
|
## 8. Multi-valued metadata
|
|
|
|
Core uniqueness is category-driven. An Attribute with `single_valued = true` and a non-void `attribute_category` may only appear once in one declaration.
|
|
|
|
When a domain needs repeated annotations, design that metadata so it does not claim single-valued category uniqueness, then consume it through `for_each_attribute(...)`.
|
|
|
|
Do not force naturally repeated metadata into one large unrelated object merely to satisfy a single-valued design.
|
|
|
|
## 9. Recommended extension boundaries
|
|
|
|
A future extension may reasonably own metadata and interpretation for areas such as:
|
|
|
|
- serialization naming and omission rules;
|
|
- RPC exposure rules;
|
|
- UI labels, groups and editor hints;
|
|
- database column mapping;
|
|
- configuration-file mapping;
|
|
- domain documentation generation.
|
|
|
|
These are examples of extension domains, not currently implemented features.
|
|
|
|
The important boundary is that Core should not gain a direct dependency on their libraries or domain types.
|
|
|
|
## 10. Core metadata may still be consumed
|
|
|
|
An extension is allowed to interpret Core-owned categories when those categories are part of the extension's input contract.
|
|
|
|
For example, an external adapter may use:
|
|
|
|
- property `key` as the default protocol name;
|
|
- `External_Access` to determine exposure;
|
|
- `sensitive` to decide whether its own logs should omit a value.
|
|
|
|
The extension may choose a policy based on those attributes, but it should not change what Core itself means by them.
|
|
|
|
## 11. Fallback behavior belongs to the consumer
|
|
|
|
The presentation extension demonstrates the intended rule:
|
|
|
|
```text
|
|
no label Attribute
|
|
↓
|
|
presentation extension chooses property key as label
|
|
```
|
|
|
|
Core does not invent that fallback because a different consumer may want a different behavior.
|
|
|
|
The same principle should apply to future adapters. Defaults that exist only to make one domain pleasant should remain in that domain.
|
|
|
|
## 12. Avoid extension-to-Core semantic leakage
|
|
|
|
Bad direction:
|
|
|
|
```text
|
|
JSON adapter needs alias support
|
|
↓
|
|
Core adds JSON_Alias_Category and JSON naming logic
|
|
```
|
|
|
|
Preferred direction:
|
|
|
|
```text
|
|
JSON extension defines Json_Name_Category
|
|
↓
|
|
JSON extension interprets it
|
|
↓
|
|
Core remains domain-neutral
|
|
```
|
|
|
|
## 13. Linked code versus header-only metadata
|
|
|
|
Attribute definitions can often be header-only. Interpretation may be header-only or linked depending on implementation needs.
|
|
|
|
The current presentation extension demonstrates a linked extension target. `presentation::describe()` performs compile-time Attribute selection and calls linked `make_presentation_info()` for the concrete result construction/fallback step.
|
|
|
|
Do not move linked implementation into Core merely because the extension is small.
|
|
|
|
## 14. Extension review checklist
|
|
|
|
Before accepting a new extension feature, ask:
|
|
|
|
1. Does this concept belong to Core or to one consumer domain?
|
|
2. Can it be represented through the existing Attribute protocol?
|
|
3. Does the extension own the Attribute category it interprets?
|
|
4. Is an inheritable Attribute truly an object-wide default policy?
|
|
5. Is fallback behavior kept inside the extension?
|
|
6. Is the existing schema reused rather than duplicated?
|
|
7. Does the dependency still point from extension to Core?
|
|
8. Has Core remained free of third-party domain types?
|
|
9. Does the extension preserve the existing meaning of Core access, validation and synchronization?
|
|
|
|
If the answer to the dependency or semantic-boundary questions is no, the layering should be redesigned before code is added.
|