Skip to content

Latest commit

 

History

History
502 lines (389 loc) · 13.6 KB

File metadata and controls

502 lines (389 loc) · 13.6 KB
title For Platform Architects
description Deep dive into ObjectStack's architecture, design decisions, and how it compares to industry standards.

Welcome architects! If you're evaluating ObjectStack for your platform or investigating metadata-driven architectures, this guide will help you understand the design philosophy and technical decisions.

Architectural Overview

ObjectStack is a protocol-first, metadata-driven platform that virtualizes the application stack into three declarative layers:

┌──────────────────────────────────────┐
│         AI Protocol (Optional)        │  ← Agent definitions, tools, knowledge
├──────────────────────────────────────┤
│         API Protocol                  │  ← REST/GraphQL contracts
├──────────────────────────────────────┤
│      UI Layer (ObjectUI)              │  ← Server-Driven UI definitions
├──────────────────────────────────────┤
│    Data Layer (ObjectQL)              │  ← Schema, queries, business logic
├──────────────────────────────────────┤
│   System Layer (ObjectOS)             │  ← Runtime kernel, plugins, drivers
└──────────────────────────────────────┘

Design Philosophy

1. Intent over Implementation

Problem: Traditional development couples what you want with how to do it.

ObjectStack Solution: Separate business intent (metadata) from technical execution (kernel).

// INTENT (what you want)
const Task = ObjectSchema.create({
  name: 'task',
  fields: {
    title: Field.text({ required: true }),
  }
});

// EXECUTION (handled by kernel)
// ✓ SQL schema generation
// ✓ API route creation
// ✓ Validation logic
// ✓ UI rendering

2. Local-First, Cloud-Optional

Inspired by: Local-First Software Manifesto

Benefits:

  • ✅ Offline-capable by default
  • ✅ No vendor lock-in
  • ✅ Data ownership
  • ✅ Instant responsiveness

3. Protocol, Not Framework

Like HTTP or SQL, ObjectStack defines a protocol that multiple implementations can follow.

Protocol Definition (this repo)
       ↓
┌──────────────────────────────────────────────────────┐
│  Implementations (bring your own)                    │
├──────────────────────────────────────────────────────┤
│  • Kernel: Node.js, Deno, Bun, Go, Python           │
│  • Driver: PostgreSQL, MongoDB, Redis, Custom       │
│  • Renderer: React, Vue, Flutter, Native            │
└──────────────────────────────────────────────────────┘

Industry Comparison

Salesforce Platform

Feature Salesforce ObjectStack
Metadata-Driven ✅ Yes ✅ Yes
Custom Objects ✅ Yes ✅ Yes
Formula Fields ✅ Yes ✅ Yes
Workflows ✅ Yes ✅ Yes
Open Source ❌ No ✅ Yes
Self-Hosted ❌ No ✅ Yes
Type-Safe ❌ No ✅ TypeScript + Zod
Database Agnostic ❌ No ✅ Yes (Driver Model)

ObjectStack = Salesforce's metadata model + Open Source + Type Safety

ServiceNow Platform

Feature ServiceNow ObjectStack
Configuration as Code ✅ Yes ✅ Yes
Server-Driven UI ✅ Yes ✅ Yes
Business Rules Engine ✅ Yes ✅ Yes
Plugin Architecture ✅ Yes ✅ Yes
Open Protocol ❌ No ✅ Yes
Local-First ❌ No ✅ Yes
Modern DX ⚠️ XML-based ✅ TypeScript + JSON

Hasura / PostgREST (Auto-API)

Feature Hasura ObjectStack
Auto-Generated API ✅ Yes ✅ Yes
GraphQL Support ✅ Yes ✅ Yes
Database First ✅ Yes ⚠️ Schema-first
Business Logic ⚠️ External ✅ Built-in (formulas, workflows)
UI Generation ❌ No ✅ Yes
Multi-DB Support ⚠️ Limited ✅ Via drivers

Strapi / Directus (Headless CMS)

Feature Strapi ObjectStack
Schema Builder ✅ Yes ✅ Yes
Auto Admin Panel ✅ Yes ✅ Yes
API Generation ✅ Yes ✅ Yes
Business Logic ⚠️ Code-based ✅ Declarative
Type Safety ⚠️ Partial ✅ Full (Zod)
Enterprise Features ⚠️ Limited ✅ Workflows, validation, permissions

Technical Architecture

Layer 1: Data Protocol (ObjectQL)

Inspired by: SQL, GraphQL, Apache Calcite

Abstraction: Represent queries as an Abstract Syntax Tree (AST).

// High-level query
const query = Query.create({
  object: 'order',
  filters: { total: { gt: 1000 } },
  include: ['customer'],
});

// Compiles to AST
const ast = {
  type: 'SELECT',
  from: { object: 'order' },
  where: { 
    type: 'BINARY_OP',
    operator: 'GT',
    left: { type: 'FIELD', name: 'total' },
    right: { type: 'LITERAL', value: 1000 },
  },
  joins: [
    { object: 'customer', type: 'LEFT', on: 'customer_id' },
  ],
};

// Driver translates AST → SQL/NoSQL/etc.

Benefits:

  • ✅ Database agnostic (SQL, NoSQL, SaaS APIs)
  • ✅ Query optimization at AST level
  • ✅ Consistent semantics across backends

Layer 2: UI Protocol (ObjectUI)

Inspired by: Salesforce Lightning, React Server Components, Flutter

Server-Driven UI (SDUI): Client is a "dumb renderer" that interprets JSON configs.

// Server defines UI as JSON
const view = ListView.create({
  viewType: 'grid',
  columns: [{ field: 'title' }],
});

// Client renders based on schema
<Renderer schema={view} />

Benefits:

  • ✅ Update UI without app rebuild
  • ✅ A/B testing at configuration level
  • ✅ Consistent UX across platforms
  • ✅ AI can generate/modify UI

Layer 3: System Protocol (ObjectOS)

Inspired by: Kubernetes, OSGi, Eclipse RCP

Plugin Architecture: Kernel loads and orchestrates plugins.

interface Plugin {
  name: string;
  version: string;
  
  // Lifecycle hooks
  onLoad(kernel: IKernel): void;
  onUnload(): void;
  
  // Capabilities
  provides?: {
    drivers?: Driver[];
    apis?: ApiContract[];
    ui?: Component[];
  };
}

Benefits:

  • ✅ Modular, composable system
  • ✅ Hot-swappable components
  • ✅ Ecosystem of community plugins

Zod-First Approach

Decision: All schemas are defined using Zod.

// Source of truth
export const TaskSchema = z.object({
  title: z.string().max(200),
  status: z.enum(['todo', 'done']),
});

// Derived artifacts
type Task = z.infer<typeof TaskSchema>;        // TypeScript type
const jsonSchema = zodToJsonSchema(TaskSchema); // JSON Schema
const sqlSchema = zodToSql(TaskSchema);        // SQL DDL

Benefits:

  • Runtime validation (Zod validates at runtime)
  • Compile-time types (TypeScript inference)
  • Single source of truth (no drift between types and validators)
  • Tooling ecosystem (JSON Schema for OpenAPI, IDEs, etc.)

Formula Engine

Inspired by: Salesforce Formula Fields, Excel Formulas

ObjectStack includes a declarative formula language:

Field.formula({
  expression: 'IF(discount_code != NULL, subtotal * 0.9, subtotal)',
})

Implementation:

  1. Parse formula → AST
  2. Type-check AST
  3. Compile to JavaScript (or other runtime)
  4. Execute safely (sandboxed)

Security:

  • ✅ No arbitrary code execution
  • ✅ Whitelist of allowed functions
  • ✅ Type-safe by construction

Multi-Tenancy Strategy

ObjectStack supports multiple multi-tenancy models:

1. Schema-per-Tenant (PostgreSQL)

CREATE SCHEMA tenant_acme;
CREATE SCHEMA tenant_beta;

Pros: Strong isolation, easy backup/restore per tenant
Cons: Schema proliferation, migration complexity

2. Row-Level Security (RLS)

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
  USING (tenant_id = current_tenant_id());

Pros: Efficient, single schema
Cons: Query performance overhead

3. Database-per-Tenant

Pros: Ultimate isolation
Cons: Resource intensive

ObjectStack Recommendation: Configurable per deployment.

Scalability Considerations

Horizontal Scalability

┌─────────────────────────────────────────┐
│         Load Balancer                    │
└─────────────┬───────────────────────────┘
              │
    ┌─────────┼─────────┬─────────┐
    ▼         ▼         ▼         ▼
 [Kernel] [Kernel] [Kernel] [Kernel]  ← Stateless kernels
    │         │         │         │
    └─────────┴─────────┴─────────┘
              │
              ▼
        [Database]  ← Centralized or sharded

Stateless Kernel:

  • All state in database or cache
  • Session in Redis/Memcached
  • File uploads in S3/Minio

Caching Strategy

// Multi-layer cache
const data = await cache.get('order:123') // L1: Redis
  ?? await dbCache.get('order:123')       // L2: DB connection pool
  ?? await database.query({ ... });       // L3: Source

cache.set('order:123', data, { ttl: 60 });

Database Sharding

ObjectStack AST allows transparent sharding:

// Driver detects shard key
const driver = new ShardedDriver({
  shardKey: 'customer_id',
  shards: [db1, db2, db3],
});

// Query automatically routed
await driver.find({
  object: 'order',
  filters: { customer_id: { eq: 'acme-corp' } }, // → shard 1
});

Security Architecture

1. Authentication

Pluggable providers:

  • OAuth 2.0 / OIDC
  • SAML
  • JWT
  • API Keys
  • Custom

2. Authorization

Multi-level access control:

// Object-level permissions
Permission.create({
  profile: 'sales_user',
  object: 'opportunity',
  create: true,
  read: true,
  update: { owned: true }, // Only own records
  delete: false,
});

// Field-level security
FieldPermission.create({
  profile: 'sales_user',
  object: 'account',
  field: 'annual_revenue',
  read: false, // Hidden field
});

// Row-level sharing
SharingRule.create({
  object: 'account',
  sharedTo: 'sales_team',
  accessLevel: 'read',
  criteria: { region: 'US' },
});

3. Data Encryption

At rest: Database-level encryption (LUKS, TDE)
In transit: TLS 1.3
Field-level: Encrypt sensitive fields (PII)

Field.text({
  label: 'SSN',
  encrypted: true,
  encryptionKey: 'field_encryption_key',
})

Observability

Structured Logging

logger.info('Query executed', {
  object: 'order',
  duration: 45,
  rowsReturned: 100,
  userId: 'user-123',
});

Metrics (OpenTelemetry)

  • Request rate, latency, errors (RED metrics)
  • Database query performance
  • Cache hit rate
  • Formula evaluation time

Tracing

Distributed tracing across:

  1. API request
  2. Kernel processing
  3. Driver query
  4. Database execution

When to Use ObjectStack

✅ Good Fit

  • Internal tools (admin panels, dashboards)
  • CRUD-heavy applications (CRM, ERP, ticketing)
  • Multi-tenant SaaS (need customization per tenant)
  • Rapid prototyping (MVP in hours/days)
  • Low-code platforms (enable non-developers)

⚠️ Consider Alternatives

  • Real-time gaming (too much abstraction overhead)
  • Ultra-high throughput (raw SQL might be faster)
  • Greenfield with simple needs (might be overkill)

Migration Path

Incremental Adoption

Existing App
    ↓
1. Wrap existing DB with ObjectStack driver
2. Generate ObjectStack schemas from DB
3. Replace one module at a time
4. Gradual migration

Coexistence Strategy

// Use ObjectStack for new features
const newFeature = ObjectSchema.create({ ... });

// Keep existing code for legacy features
app.get('/legacy', legacyController);

Next Steps for Architects

Resources