Files
rustybeds/wiki/08-template-system.md
gramps 2a9afe7d77 Add MariaDB IPL validation, topology docs in beds.toml, and developer wiki
- Add MariaDB (REL) IPL validation — master required, secondary non-fatal
- Add RelNodeConfig / RelInstanceConfig structs with master/secondary pattern
- Add rel_services section to beds.toml and test fixture
- Add detailed topology commentary to beds.toml covering standalone,
  master/replica, Galera cluster, and multi-DB-per-node configurations
- Add developer wiki (wiki/) covering:
    - Origin story — PHP Namaste history, production record, why Rust
    - Architecture overview — full system diagram, all layers explained
    - The four nodes — appServer, admin, segundo, tercero with real-world context
    - IPL sequence — every step documented with rationale for ordering
    - Configuration system — layering, env selection, adding new sections
    - Queue topology — exchanges, routing keys, broker bindings, vhost isolation
    - Template system — REC/REL, TLA convention, cache map, warehousing
    - Event lineage — compound event IDs, parent/child tracking, msLogs schema
    - Glossary
- Update README with wiki index and MariaDB status

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 15:41:28 -07:00

194 lines
7.4 KiB
Markdown

# Template System
## What Is a Template?
A BEDS template is a TOML configuration file that defines a data collection — its schema, indexes, behavioural flags, access controls, and lifecycle policy. The template is the contract between the DBA who owns the data and the framework that serves it.
Templates live in `templates/`. Filename convention: `{wbid}{CollectionName}_{schema}.toml`
```
templates/
├── example_rec.toml ← canonical self-documenting REC template
└── mst_logger_rec.toml ← logger collection template
```
## Two Template Types
| Type | Schema | Database | Use Case |
|---|---|---|---|
| REC | `rec` | MongoDB | Document store — logs, events, user profiles, audit records |
| REL | `rel` | MariaDB | Relational store — anything needing SQL joins or transactions |
## The TLA Convention
Every collection has a **three-letter abbreviation (TLA)** declared in the template as `extension`. The TLA is:
1. Appended to the collection name: `msUsers``msUsers_usr`... wait, no. The collection name is `msUsers`. The TLA `_usr` is appended to **every field name** in the collection.
2. Applied to every field: `email_usr`, `status_usr`, `created_usr`
```toml
extension = "_usr" # the TLA for this collection
[fields]
email_usr = "string"
status_usr = "string"
created = "integer" # system fields like created don't carry the TLA
```
**Why TLA?** In a complex query that joins or aggregates across multiple collections, field names collide. `status` from users and `status` from orders are the same name. `status_usr` and `status_ord` never collide. In log output, `status_usr=active` immediately tells you which collection the field came from without having to trace the query.
This convention was enforced in the PHP Namaste framework from the beginning. BEDS validates TLA compliance at template load time — templates that violate the naming convention are rejected.
## The `wbid` Convention
The `wbid` (white-box identifier, 2 characters) is prepended to every collection name:
```
wbid = "ms" + CollectionName "Users" = collection "msUsers"
wbid = "ms" + CollectionName "Logs" = collection "msLogs"
```
The `wbid` namespaces your data within a shared MongoDB instance. If you ever run multiple BEDS deployments against the same MongoDB, different `wbid` values keep their collections from colliding.
## REC Template Anatomy
See `templates/example_rec.toml` for the full self-documenting example. Key sections:
### Identity
```toml
version = 1 # increment when schema changes
service = "app_server" # which node owns this collection
schema = "rec" # always "rec" for MongoDB
template_class = "Users" # human-readable class name
collection = "msUsers" # MongoDB collection name
extension = "_usr" # TLA — appended to all field names
wh_template = "" # warehousing destination template (empty = none)
```
### Behavioural Flags
```toml
closed_class = true # internal only vs. externally accessible
hard_deletes = false # soft delete (status=inactive) vs. permanent removal
updates_enabled = true # can records be updated after insert?
auditing = "full" # disabled | destructive | nondestructive | full
journaling = true # record journal entries for destructive ops?
record_history = false # maintain full version history per record?
record_locking = false # optimistic locking for concurrent writes?
query_timers = true # record query execution times as metric events?
primary_key = "token" # "token" (BEDS GUID) or "mongo_id" (native _id)
tokens = true # generate BEDS GUID token for every record?
cache_ttl = 300 # seconds in in-process cache (0 = disabled)
is_internal = false # exclude from public REST API catalog?
```
### Field Types
```toml
[fields]
_id = "object" # MongoDB native ID — never returned to clients
db_token = "string" # BEDS GUID — the externally-exposed primary key
status = "string"
created = "integer"
accessed = "integer"
email_usr = "string"
score_usr = "double"
count_usr = "integer"
flag_usr = "boolean"
tags_usr = "array"
meta_usr = "object"
```
Valid types: `string`, `integer`, `double`, `boolean`, `object`, `array`, `date`
### The `db_token` Field
Every BEDS record has a `db_token` field — a BEDS-generated GUID that serves as the externally-exposed primary key. The MongoDB native `_id` is never returned to clients.
This is intentional security-through-obscurity. Every script-kiddie who finds an exposed API searches for `id` in the response payload. `db_token` is non-intuitive. It is also structurally meaningful: it is the BEDS-controlled identifier, distinct from the database's internal identifier, which means BEDS controls its format, generation, and uniqueness guarantees.
### Protected Fields
Fields that clients cannot modify:
```toml
protected_fields = ["_id", "db_token", "event_guid", "created", "accessed"]
```
Attempts to update protected fields are silently dropped.
### Index Declarations
BEDS validates all incoming queries against declared indexes at submission time. A query that cannot be satisfied by a declared index is rejected before execution — no full collection scans in production.
```toml
# which fields participate in any index
index_fields = ["db_token", "status", "created", "email_usr"]
# single-field indexes
[single_field_indexes]
db_token = 1
status = -1
created = -1
email_usr = 1
# compound indexes (named — must appear in index_name_list)
index_name_list = ["cIdx1Usr"]
[compound_indexes]
cIdx1Usr = [["status", 1], ["created", -1]]
# unique constraints
[unique_indexes]
db_token = 1
email_usr = 1
# TTL — automatic record expiry
[ttl_indexes]
# accessed = 86400 # expire records not accessed in 24 hours
```
### Cache Map
The cache map controls field name translation between internal schema names and external client-facing names. Schema column names are never exposed to clients.
```toml
[cache_map]
db_token = "id"
status = "status"
created = "createdDate"
email_usr = "email"
```
A client sees `{ "id": "...", "email": "..." }`. The internal `db_token` and `email_usr` names never appear in API responses.
### Warehousing
```toml
[warehouse]
supported = true
automated = true
interval = "M" # D=daily, M=monthly, Q=quarterly, Y=yearly
delete = "H" # H=hard delete source after warehousing, S=soft delete
[warehouse.qualifier]
created = { operand = "null", operator = "lt", value = "" } # caller supplies cutoff date
status = { operand = "null", operator = "eq", value = "active" }
logical_op = "and"
```
## The Logger Template
`templates/mst_logger_rec.toml` is the canonical example of an internal system collection. Key differences from a standard REC template:
- `updates_enabled = false` — log records are immutable
- `auditing = "disabled"` — never audit the logger (infinite recursion)
- `journaling = false` — never journal the logger (same reason)
- `cache_ttl = 0` — no caching — log data is never stale-read
- `is_internal = true` — excluded from the public REST API catalog
- `hard_deletes = true` — log pruning permanently removes records
- TTL index on `created` — automatic log expiry (30 days default)
These choices are documented in the template file with explanations. When in doubt about why a flag is set a certain way on the logger template, the template is the authoritative source.