standardizing data layer strategy #269
Replies: 2 comments
-
|
i'd like to evaluate the viability of using accessors to interface with the metadata object |
Beta Was this translation helpful? Give feedback.
-
Implementation Feasibility AnalysisI've analyzed the current codebase against this STI proposal. Here's what we found: Current State: Class Table Inheritance (CTI)The framework currently implements one table per class with field inheritance: @smrt()
class Event extends SmrtObject {
title: string = '';
startTime: Date = new Date();
}
@smrt()
class Meeting extends Event {
roomId = foreignKey(Room);
}
@smrt()
class HockeyGame extends Event {
homeTeamId = foreignKey(Team);
arenaName: string = '';
}Creates three separate tables:
Each child table duplicates all inherited fields. Proposed: STI as Optional FeatureMaking STI opt-in keeps the change non-breaking while enabling the benefits where needed: // Set strategy once on base class
@smrt({ tableStrategy: 'sti' })
class Event extends SmrtObject {
title: string = '';
startTime: Date = new Date();
}
// Children automatically inherit strategy
@smrt()
class Meeting extends Event {
roomId = foreignKey(Room);
}
@smrt()
class HockeyGame extends Event {
homeTeamId = foreignKey(Team);
awayTeamId = foreignKey(Team);
arenaName = meta(); // Explicit JSONB storage
}Creates one shared table: CREATE TABLE events (
id TEXT PRIMARY KEY,
type TEXT NOT NULL, -- Discriminator
meta JSON, -- Flexible storage
title TEXT NOT NULL,
start_time TIMESTAMP,
room_id TEXT, -- Meeting (nullable)
home_team_id TEXT, -- HockeyGame (nullable)
away_team_id TEXT, -- HockeyGame (nullable)
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL
);
CREATE INDEX idx_events_type ON events(type);Implementation Plan: 4-5 WeeksWeek 1: Foundation - Add Strategy Inheritance Behavior// Set once on base - children inherit automatically
@smrt({ tableStrategy: 'sti' })
class Event extends SmrtObject { }
@smrt() // Inherits 'sti' from Event
class Meeting extends Event { }
// Can override if needed
@smrt({ tableStrategy: 'cti' })
class SpecialEvent extends Event { } // Opts back to CTIKey Differences
Fail-Fast ValidationPhilosophy: Throw descriptive errors early. Developer fixes schema, not the framework. // Example validation
async save() {
if (strategy === 'sti' && !data.type) {
throw new Error(
`STI class '${this.constructor.name}' missing 'type' discriminator.`
);
}
}Clear errors guide developers:
Benefits of Optional Approach
Use Cases for STIGood fits: Event hierarchies, polymorphic queries, agent-driven schema evolution Better with CTI: Stable hierarchies, no polymorphic queries needed Questions
Bottom line: Feasible in 4-5 weeks. Optional approach gives maximum flexibility while maintaining backward compatibility. Full analysis: STI_IMPLEMENTATION_ANALYSIS.md |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Here is a summary of the database design approach, formatted as a technical proposal or ticket for your team's evaluation.
Ticket: Proposal for
smrtData Layer - Hybrid STI PatternObjective:
To specify a robust, flexible, and scalable database pattern for the smrt framework. This pattern must support a Single Source of Truth (SSOT) for primitive types (e.g., Event) while allowing extended agent-specific types (e.g., HockeyGame, Meeting) to co-exist.
1. Executive Summary: The Hybrid Pattern
This proposal advocates for a Hybrid Single Table Inheritance (STI) Pattern. This model is not a strict STI implementation but a pragmatic hybrid that combines two patterns to get the best of both:
Single Table Inheritance (STI): A base table (e.g.,
events) holds all shared fields and atypediscriminator column.1JSON "Property Bag": A single
meta(orproperties) column of typeJSONBholds all non-relational, unstructured data.This hybrid approach will be managed by the
smrtframework, which will introspect TypeScript types to correctly map fields to either theJSONBbag or a dedicated top-level column.2. Core Problem
Agents in the ecosystem need to create specialized types that extend a common primitive. For example:
A base
Eventprimitive.An agent-defined
Meetingtype that extendsEvent.An agent-defined
HockeyGametype that extendsEvent.We need to store
MeetingandHockeyGameobjects in the sameeventstable to allow for simple, polymorphic queries (e.g., "fetch all events for today"). However,HockeyGamemay have relationships (e.g., to aTeamtable) thatMeetingdoes not.3. Proposed
smrtFramework LogicThe
smrtframework's introspection logic will be responsible for mapping TypeScript class fields based on the following rules:Rule 1: Common Fields
What: Any field defined on the base primitive (e.g.,
Event.title,Event.start_time).Action: Mapped to a dedicated, top-level column on the base table (e.g.,
events.title).Rule 2: Unstructured Properties (The "Property Bag")
What: Any field on an extended type (e.g.,
HockeyGame.arena_name) that is not a foreign key or critical, indexed search field.Action: Mapped into the
metaJSONBcolumn.Example: A
HockeyGamewith{ arena_name: 'The Dome' }is saved inevents.metaas{"arena_name": "The Dome"}.Benefit: High flexibility.2 New properties can be added by agents without requiring
ALTER TABLEmigrations.Rule 3: Relational Foreign Keys (The "STI" Part)
What: Any field on an extended type that represents a relationship to another table (e.g.,
HockeyGame.home_team_id).Action: Mapped to a dedicated,
NULLable, top-level column on the base table (e.g.,events.home_team_id).Benefit: Enables database-level integrity (Foreign Key constraints) and, most importantly, allows for fast, indexed SQL
JOINs.4. Example Schema (
eventstable)This hybrid approach would result in the following schema:
Column | Type | Purpose -- | -- | -- id | PK | Primary Key type | String | STI Discriminator ("Meeting", "HockeyGame") title | String | Common Field (from base Event) start_time | DateTime | Common Field (from base Event) meta | JSONB | Property Bag (for non-relational data) meeting_room_id | FK | STI Relation (NULL for HockeyGame) home_team_id | FK | STI Relation (NULL for Meeting) away_team_id | FK | STI Relation (NULL for Meeting)5. Trade-offs Analysis
This approach is a deliberate trade-off that aligns with our ecosystem goals.
Benefits:
SSOT / Polymorphism: Agents can query the
eventstable and get all event types in a single, fast query.Relational Integrity: We retain the ability to use
FOREIGN KEYconstraints for critical relationships.Query Performance: We can perform fast, indexed
JOINs on relational columns (e.g.,JOIN teams ON events.home_team_id = teams.id).Flexibility: Simple properties (
arena_name) can be added by agents without database schema migrations, increasing developer velocity.Acknowledged Costs (Cons):
Schema Rigidity (for Relations): When an agent needs a new relationship,
smrtmust generate and run anALTER TABLEmigration to add the newNULLable FK column. This is the main cost, but it's an acceptable one for the benefit of real relationships.Sparse Columns: The
eventstable will haveNULLable FK columns that are only populated for their specifictype. This is a standard and acceptable trade-off for STI.Application-Layer Integrity: The
smrtframework (not the DB) must enforce that aHockeyGamehas a non-nullhome_team_id. The DB can only enforce that if a value exists, it's valid.6. Recommendation
Approve this Hybrid STI Pattern as the standard for
smrtprimitives. This model provides the best balance of flexibility (viaJSONB), performance (viaJOINs), and SSOT (via a single table).The
smrtcore library must be updated to introspect types and differentiate between property-bag fields and relational fields, generating migrations only when new relational columns are required.Beta Was this translation helpful? Give feedback.
All reactions