Bỏ qua

Architecture Patterns

Light Stack Overview

Kiến trúc tổng quan

┌─────────────────────────────────────────────────────────────┐
│                     LIGHT STACK ARCHITECTURE                 │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                    CLIENTS                           │    │
│  │  Browser / Mobile App / API Consumer                │    │
│  └───────────────────────┬─────────────────────────────┘    │
│                          │                                   │
│  ┌───────────────────────▼─────────────────────────────┐    │
│  │              CLOUDFLARE EDGE                         │    │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  │    │
│  │  │   Workers   │  │  KV Cache   │  │   R2 CDN    │  │    │
│  │  │  (Next.js)  │  │             │  │             │  │    │
│  │  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘  │    │
│  │         │                │                │          │    │
│  │  ┌──────▼────────────────▼────────────────▼──────┐  │    │
│  │  │              Queue / Cron                      │  │    │
│  │  └──────────────────────┬────────────────────────┘  │    │
│  └─────────────────────────┼────────────────────────────┘    │
│                            │                                  │
│  ┌─────────────────────────▼────────────────────────────┐    │
│  │                     SUPABASE                          │    │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐            │    │
│  │  │PostgreSQL│  │   Auth   │  │ Storage  │            │    │
│  │  │ + RLS    │  │          │  │          │            │    │
│  │  └──────────┘  └──────────┘  └──────────┘            │    │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐            │    │
│  │  │ Realtime │  │Edge Func │  │   pgmq   │            │    │
│  │  │          │  │          │  │ pg_cron  │            │    │
│  │  └──────────┘  └──────────┘  └──────────┘            │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Pattern 1: Direct Supabase Access

Khi nào dùng?

✅ Simple CRUD operations
✅ Client-side with RLS protection
✅ Real-time subscriptions
✅ Quick prototyping

❌ Complex business logic
❌ External API calls
❌ Multi-step transactions

Implementation

// Client-side direct access
// RLS handles authorization

// app/tasks/page.tsx (Server Component)
import { createClient } from '@/lib/supabase/server';

export default async function TasksPage() {
  const supabase = await createClient();

  const { data: tasks } = await supabase
    .from('tasks')
    .select('*, project:projects(name)')
    .order('created_at', { ascending: false });

  return <TaskList tasks={tasks} />;
}

// Client component for mutations
'use client';

export function CreateTaskButton({ projectId }: { projectId: string }) {
  const supabase = createClient();

  const handleCreate = async () => {
    const { error } = await supabase
      .from('tasks')
      .insert({ title: 'New Task', project_id: projectId });

    if (error) toast.error(error.message);
  };

  return <button onClick={handleCreate}>Add Task</button>;
}

Pattern 2: Worker API Gateway

Khi nào dùng?

✅ External API integration (Stripe, SendGrid)
✅ Complex business logic
✅ Rate limiting / caching
✅ Multi-service orchestration

❌ Simple CRUD (overkill)
❌ Real-time features

Architecture

┌──────────────────────────────────────────────────────────┐
│                   WORKER API GATEWAY                      │
├──────────────────────────────────────────────────────────┤
│                                                           │
│  Client                                                   │
│    │                                                      │
│    ▼                                                      │
│  Cloudflare Worker (Hono)                                │
│    ├── Authentication (validate JWT)                     │
│    ├── Rate Limiting (KV counter)                        │
│    ├── Request Validation                                │
│    │                                                      │
│    ├── Business Logic                                    │
│    │   ├── Call Stripe API                               │
│    │   ├── Call Supabase (service role)                  │
│    │   └── Send Email                                    │
│    │                                                      │
│    └── Response                                          │
│                                                           │
└──────────────────────────────────────────────────────────┘

Implementation

// Worker as API gateway
import { Hono } from 'hono';
import { createClient } from '@supabase/supabase-js';
import Stripe from 'stripe';

const app = new Hono<{ Bindings: Env }>();

// Authentication middleware
app.use('/api/*', async (c, next) => {
  const authHeader = c.req.header('Authorization');
  if (!authHeader) {
    return c.json({ error: 'Unauthorized' }, 401);
  }

  const supabase = createClient(
    c.env.SUPABASE_URL,
    c.env.SUPABASE_ANON_KEY
  );

  const { data: { user }, error } = await supabase.auth.getUser(
    authHeader.replace('Bearer ', '')
  );

  if (error || !user) {
    return c.json({ error: 'Invalid token' }, 401);
  }

  c.set('user', user);
  await next();
});

// Payment endpoint
app.post('/api/payments', async (c) => {
  const user = c.get('user');
  const { amount, productId } = await c.req.json();

  // 1. Validate product exists (Supabase)
  const adminClient = createClient(
    c.env.SUPABASE_URL,
    c.env.SUPABASE_SERVICE_KEY
  );

  const { data: product } = await adminClient
    .from('products')
    .select('*')
    .eq('id', productId)
    .single();

  if (!product) {
    return c.json({ error: 'Product not found' }, 404);
  }

  // 2. Create Stripe payment
  const stripe = new Stripe(c.env.STRIPE_SECRET_KEY);
  const paymentIntent = await stripe.paymentIntents.create({
    amount,
    currency: 'usd',
    metadata: { userId: user.id, productId },
  });

  // 3. Record transaction
  await adminClient.from('transactions').insert({
    user_id: user.id,
    product_id: productId,
    stripe_payment_id: paymentIntent.id,
    status: 'pending',
  });

  return c.json({ clientSecret: paymentIntent.client_secret });
});

export default app;

Pattern 3: Queue-based Processing

Khi nào dùng?

✅ Long-running tasks
✅ Batch processing
✅ Email/notification sending
✅ Retry-able operations

❌ Synchronous responses needed
❌ Simple operations

Architecture

┌──────────────────────────────────────────────────────────┐
│              QUEUE-BASED PROCESSING                       │
├──────────────────────────────────────────────────────────┤
│                                                           │
│  Client                                                   │
│    │                                                      │
│    ▼                                                      │
│  API (Worker or Supabase Edge Function)                  │
│    │                                                      │
│    ▼                                                      │
│  ┌─────────────────────────────────────────────────┐     │
│  │                    QUEUE                         │     │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐         │     │
│  │  │ Message │  │ Message │  │ Message │  ...    │     │
│  │  └─────────┘  └─────────┘  └─────────┘         │     │
│  └─────────────────────────────────────────────────┘     │
│                          │                                │
│                          ▼                                │
│  Consumer (Worker or Edge Function)                      │
│    ├── Process message                                   │
│    ├── Update database                                   │
│    ├── Send notification                                 │
│    └── Acknowledge / Retry                               │
│                                                           │
└──────────────────────────────────────────────────────────┘

Chọn Queue nào?

Scenario Cloudflare Queue Supabase pgmq
High throughput Limited by DB
Transaction with data Complex ✅ Native
Edge processing ✅ Native Needs Edge Func
Simple setup Moderate ✅ Easy

Pattern 4: Event-Driven Architecture

Khi nào dùng?

✅ Loose coupling between services
✅ Multiple consumers for same event
✅ Audit trail / logging
✅ Realtime updates

❌ Simple request-response
❌ Tight latency requirements

Architecture

┌──────────────────────────────────────────────────────────┐
│               EVENT-DRIVEN ARCHITECTURE                   │
├──────────────────────────────────────────────────────────┤
│                                                           │
│  Database Trigger                                        │
│    │                                                      │
│    ▼                                                      │
│  Event                                                    │
│    │                                                      │
│    ├──▶ Supabase Realtime ──▶ Client UI Update          │
│    │                                                      │
│    ├──▶ pgmq ──▶ Email Worker ──▶ Send Email            │
│    │                                                      │
│    ├──▶ pg_notify ──▶ Edge Function ──▶ External API    │
│    │                                                      │
│    └──▶ Audit Table ──▶ Analytics                        │
│                                                           │
└──────────────────────────────────────────────────────────┘

Implementation

-- Trigger when task is completed
CREATE OR REPLACE FUNCTION on_task_completed()
RETURNS TRIGGER AS $$
BEGIN
  IF NEW.status = 'completed' AND OLD.status != 'completed' THEN
    -- 1. Enqueue notification
    PERFORM pgmq.send('notifications', jsonb_build_object(
      'type', 'task_completed',
      'task_id', NEW.id,
      'user_id', NEW.user_id
    ));

    -- 2. Insert audit log
    INSERT INTO audit_log (entity, entity_id, action, changed_by)
    VALUES ('task', NEW.id, 'completed', auth.uid());

    -- 3. Notify for external integration
    PERFORM pg_notify('task_events', json_build_object(
      'event', 'completed',
      'task_id', NEW.id
    )::text);
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER task_completed_trigger
  AFTER UPDATE ON tasks
  FOR EACH ROW
  EXECUTE FUNCTION on_task_completed();

Tổng kết

Chọn Pattern phù hợp

Pattern Use Case Complexity
Direct Supabase Simple CRUD, prototyping Low
Worker Gateway External APIs, complex logic Medium
Queue-based Async processing, retries Medium
Event-driven Loose coupling, multi-consumer High

Combine Patterns

Thực tế: Combine nhiều patterns

Example: E-commerce checkout
├── Direct Supabase: Cart management
├── Worker Gateway: Payment processing
├── Queue: Order fulfillment
└── Event-driven: Notifications

Best Practices

  1. Start simple - Direct Supabase, add complexity when needed
  2. RLS first - Let database handle authorization
  3. Queue for async - Don't block user requests
  4. Monitor everything - Logs, metrics, alerts

Q&A

  1. Pattern nào phù hợp với use case của bạn?
  2. Có experience với event-driven architecture?
  3. Challenges khi integrate nhiều services?