2024-11-15

Metadata-First Manifesto: Zašto Kod Nije Više Source of Truth

Problem: Proveli ste 6 meseci razvijajući ERP modul za nabavku. Klijent sada traži da dodamo dodatno approval step-a. Procena? 2 nedelje development + 1 nedelja testiranja. Zašto?

Zato što ste hardkodovali workflow logiku u TypeScript/Java kodu. Svaka izmena zahteva:

  1. Pronalaženje svih relevantnih fajlova (controllers, services, database migrations)
  2. Izmena logike na više mesta
  3. Dodavanje novih database kolona
  4. Ponovno testiranje celog modula
  5. Deployment sa downtime

Šta Je Metadata-First Arhitektura?

Umesto da pišete kod koji implementira business logic, vi pišete YAML deklaracije koje opisuju business logic. Platforma onda automatski generiše:

  • Database schema (PostgreSQL tabele, indeksi, foreign keys)
  • TypeScript tipove (interfejsi, validacije, enumi)
  • REST API endpoints (CRUD operacije, filteri, sortiranje)
  • Workflow state machine (approval steps, notifikacije, transitions)
  • UI forme (input fields, validacije, layout)

Primer: Tradicionalni Pristup

TypeScript kod za Purchase Order workflow (200+ linija):

// models/purchase-order.ts
export interface PurchaseOrder {
  id: string;
  status: 'draft' | 'pending_approval' | 'approved' | 'rejected';
  requestedBy: string;
  approvedBy?: string;
  items: PurchaseOrderItem[];
  totalAmount: number;
  createdAt: Date;
  updatedAt: Date;
}

// services/purchase-order.service.ts
export class PurchaseOrderService {
  async create(data: CreatePurchaseOrderDto) {
    // Validate items
    if (!data.items || data.items.length === 0) {
      throw new Error('At least one item required');
    }
    
    // Calculate total
    const total = data.items.reduce((sum, item) => 
      sum + (item.quantity * item.unitPrice), 0
    );
    
    // Create in database
    const po = await this.db.purchaseOrders.create({
      ...data,
      status: 'draft',
      totalAmount: total,
    });
    
    return po;
  }
  
  async submitForApproval(id: string, userId: string) {
    const po = await this.findOne(id);
    
    if (po.status !== 'draft') {
      throw new Error('Can only submit draft orders');
    }
    
    if (po.requestedBy !== userId) {
      throw new Error('Only requester can submit');
    }
    
    await this.db.purchaseOrders.update(id, {
      status: 'pending_approval'
    });
    
    // Send notification to approver
    await this.notificationService.notify(
      po.approvedBy,
      'New purchase order awaiting approval'
    );
  }
  
  async approve(id: string, userId: string) {
    // ... još 50 linija logike
  }
  
  async reject(id: string, userId: string, reason: string) {
    // ... još 30 linija logike
  }
}

// controllers/purchase-order.controller.ts
@Controller('purchase-orders')
export class PurchaseOrderController {
  @Post()
  async create(@Body() data: CreatePurchaseOrderDto) {
    return this.service.create(data);
  }
  
  @Post(':id/submit')
  async submit(@Param('id') id: string, @CurrentUser() user: User) {
    return this.service.submitForApproval(id, user.id);
  }
  
  // ... još 10+ endpoint-a
}

Database migration (SQL):

CREATE TABLE purchase_orders (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  status VARCHAR(50) NOT NULL CHECK (status IN ('draft', 'pending_approval', 'approved', 'rejected')),
  requested_by UUID NOT NULL REFERENCES users(id),
  approved_by UUID REFERENCES users(id),
  total_amount DECIMAL(15,2) NOT NULL,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE purchase_order_items (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  purchase_order_id UUID NOT NULL REFERENCES purchase_orders(id) ON DELETE CASCADE,
  product_id UUID NOT NULL REFERENCES products(id),
  quantity INTEGER NOT NULL CHECK (quantity > 0),
  unit_price DECIMAL(10,2) NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_po_status ON purchase_orders(status);
CREATE INDEX idx_po_requested_by ON purchase_orders(requested_by);

Ukupno: ~400 linija koda rasutog u 5+ fajlova.


Isti Funkcionalnost: PMFA Metadata Pristup

Jedan YAML fajl (60 linija):

# metadata/entities/purchase-order.yml
entity: PurchaseOrder
table: purchase_orders
description: "Nabavne narudžbenice sa approval workflow-om"

fields:
  - name: id
    type: uuid
    primary: true
    generated: auto

  - name: status
    type: enum
    values: [draft, pending_approval, approved, rejected]
    default: draft
    indexed: true

  - name: requestedBy
    type: relation
    target: User
    required: true
    indexed: true

  - name: approvedBy
    type: relation
    target: User
    required: false

  - name: items
    type: relation
    target: PurchaseOrderItem
    relation: one-to-many
    cascade: delete

  - name: totalAmount
    type: decimal
    precision: 15
    scale: 2
    computed: "SUM(items.quantity * items.unitPrice)"

  - name: createdAt
    type: timestamp
    generated: auto

  - name: updatedAt
    type: timestamp
    generated: auto-update

workflow:
  states:
    - name: draft
      transitions: [submit]
      permissions: [requester]

    - name: pending_approval
      transitions: [approve, reject]
      permissions: [approver]
      notifications:
        on_enter:
          - target: approvedBy
            message: "New purchase order awaiting approval"

    - name: approved
      transitions: []
      final: true

    - name: rejected
      transitions: [resubmit]
      final: false

  transitions:
    - name: submit
      from: draft
      to: pending_approval
      condition: "items.length > 0"
      
    - name: approve
      from: pending_approval
      to: approved
      
    - name: reject
      from: pending_approval
      to: rejected
      requires_comment: true

validations:
  - field: items
    rule: min_length
    value: 1
    message: "Najmanje jedna stavka je obavezna"

  - field: totalAmount
    rule: min_value
    value: 0
    message: "Ukupan iznos mora biti pozitivan"

permissions:
  create: [authenticated]
  read: [requester, approver, admin]
  update: [requester:draft, admin]
  delete: [admin]

Rezultat?

PMFA automatski generiše:

✅ PostgreSQL tabele sa svim indeksima i foreign key-ovima
✅ TypeScript interfejse i enume
✅ REST API sa /purchase-orders endpoint-ima:

  • POST /purchase-orders - kreiranje
  • GET /purchase-orders - listing sa filterima
  • GET /purchase-orders/:id - detalji
  • POST /purchase-orders/:id/submit - submit za approval
  • POST /purchase-orders/:id/approve - odobri
  • POST /purchase-orders/:id/reject - odbij
    ✅ Workflow state machine sa validacijama
    ✅ Notifikacioni sistem
    ✅ Permission checks
    ✅ Validation rules

Ukupno: 60 linija YAML-a umesto 400+ linija koda.


Šta Kada Treba Izmena?

Scenario: Klijent traži DODATNI approval step

Tradicionalni pristup:

  • 2 nedelje development (izmena 10+ fajlova)
  • 1 nedelja testiranja
  • Rizik od breaking existing funkcionalnosti

PMFA pristup:

Dodamo 5 linija u workflow sekciju:

workflow:
  states:
    # ... postojeći states ...
    
    - name: pending_manager_approval  # NOVO
      transitions: [manager_approve, manager_reject]
      permissions: [manager]
      notifications:
        on_enter:
          - target: manager
            message: "Purchase order requires your approval"

  transitions:
    - name: submit
      from: draft
      to: pending_manager_approval  # Izmenjeno: bilo 'pending_approval'
      
    - name: manager_approve  # NOVO
      from: pending_manager_approval
      to: pending_approval

Rezultat: Izmena instant deployment (5 minuta). Sve ostalo se automatski regeneriše.


Prednosti Metadata-First Pristupa

1. 10x Brže Izmene

Tradicionalni ERP:

  • Dodavanje novog polja: 2-3 dana (migration, backend, frontend, testiranje)
  • Izmena workflow-a: 1-2 nedelje
  • Novi modul: 3-6 meseci

PMFA:

  • Dodavanje novog polja: 5 minuta (jedna linija YAML-a)
  • Izmena workflow-a: 10 minuta
  • Novi modul: 1-2 nedelje (samo YAML definicije)

2. Zero Tehnički Dug

Kod koji ne postoji ne može biti legacy.

  • Nema "tech debt refactoring" backloga
  • Nema breaking changes u API-ju
  • Nema deprecated funkcija koje treba zamenjivati

3. Perfect Documentation

YAML metadata JE dokumentacija. Nema "docs out of sync" problema.

# Ovo je i kod i dokumentacija istovremeno
field: approvalLimit
type: decimal
description: "Maksimalni iznos koji approver može odobriti bez eskalacije"
default: 10000
validation:
  min: 0
  max: 1000000

4. Tenant-By-Architecture Multi-Tenancy

Svaki tenant može imati svoje custom polje bez izmene shared database schema:

# Tenant A: Prodavnica elektronike
customFields:
  - name: warrantyPeriod
    type: integer
    unit: months

# Tenant B: Auto servis
customFields:
  - name: vehicleVIN
    type: string
    format: alphanumeric

PMFA dinamički generiše storage strategy (JSONB kolona ili separate schema).


Kada NIJE Metadata-First?

Ovaj pristup NIJE za:

❌ Unique business logic koji se ne može generalizovati (AI algoritmi, complex financial calculations)
❌ Performance-critical inner loops (real-time trading systems)
❌ Custom UI/UX requirements (marketing websites, consumer apps)

Metadata-first JE za:

✅ CRUD operacije (90% enterprise sistema)
✅ Workflow-based procese (approvals, state machines)
✅ Forme, validacije, permissions
✅ Reporting i dashboards
✅ Multi-tenant SaaS platforme


Zaključak

Kod je imperativ. Metadata je deklaracija.

Imperativni kod kaže platformi KAKO da uradi nešto. Deklarativna metadata kaže platformi ŠTA da uradi.

PMFA filozofija: Business logic pripada metadata deklaracijama, ne TypeScript fajlovima.

Rezultat?

  • 10x brži development
  • Zero tehnički dug
  • Instant izmene bez deployment-a
  • Perfect documentation

Metadata-first je budućnost enterprise software-a.


Zainteresovani za PMFA? Kontaktirajte nas na office@nonnotech.com za early access program.