Skip to content

Master Database CRUD Architecture Decision

Date: December 16, 2025
Status: Architecture Decision
Related: BFF Tenant Aware, Tenant APIs


Decision

CRUD operations for master_db should be maintained in the Python APIs layer (FastAPI microservices), NOT in the BFF layer.

The BFF layer should call FastAPI services for all master_db operations.


Architecture

Data Flow

┌─────────────────────────────────────────────────────────┐
│              Frontend (React)                           │
│  - User Management UI                                   │
│  - Tenant Management UI                                 │
└────────────────────┬────────────────────────────────────┘
                     │ tRPC
                     ▼
┌─────────────────────────────────────────────────────────┐
│              BFF Layer (Node.js/TypeScript)             │
│                                                          │
│  Responsibilities:                                       │
│  • Request routing                                      │
│  • Response aggregation                                  │
│  • Response transformation                              │
│  • Caching (Redis)                                       │
│  • Error handling                                        │
│                                                          │
│  ❌ NOT: Business logic                                  │
│  ❌ NOT: Direct database access                         │
│  ❌ NOT: CRUD operations                                 │
└────────────────────┬────────────────────────────────────┘
                     │ HTTP/REST
                     │ (calls FastAPI services)
                     ▼
┌─────────────────────────────────────────────────────────┐
│         FastAPI Services (Python)                       │
│                                                          │
│  ┌──────────────────────────────────────────────────┐  │
│  │  User Management API                              │  │
│  │  - Master DB CRUD operations                       │  │
│  │  - Tenant DB CRUD operations                       │  │
│  │  - Business logic                                   │  │
│  │  - Validation                                       │  │
│  │  - Authorization                                    │  │
│  └──────────────────────────────────────────────────┘  │
│                                                          │
│  ┌──────────────────────────────────────────────────┐  │
│  │  Tenant Management API                            │  │
│  │  - Master DB CRUD operations                       │  │
│  │  - Tenant registry                                 │  │
│  │  - MongoDB endpoint management                    │  │
│  └──────────────────────────────────────────────────┘  │
└────────────────────┬────────────────────────────────────┘
                     │
         ┌───────────┴───────────┐
         │                       │
┌────────▼────────┐    ┌────────▼────────┐
│  Master MongoDB │    │ Tenant MongoDB │
│  Cluster        │    │ Clusters       │
│  - master_db    │    │ - tenant_*     │
└─────────────────┘    └─────────────────┘

Rationale

✅ Why Python APIs Layer?

  1. Separation of Concerns
  2. BFF is a proxy/aggregation layer
  3. Business logic belongs in backend services
  4. Database operations belong in backend services

  5. Security

  6. Authentication/authorization logic in backend
  7. Data validation in backend
  8. Security boundary at API layer
  9. BFF should not have direct database access

  10. Reusability

  11. Other services may need master_db access
  12. Centralized business logic
  13. Consistent API across services

  14. Consistency

  15. Aligns with microservices architecture
  16. All database operations in Python services
  17. Single pattern for all CRUD operations

  18. Maintainability

  19. Single source of truth for business logic
  20. Easier to test and debug
  21. Clear ownership boundaries

❌ Why NOT BFF Layer?

  1. Business Logic Duplication
  2. Would duplicate logic if other services need access
  3. Harder to maintain consistency

  4. Security Concerns

  5. BFF should not have direct database credentials
  6. Authentication/authorization should be in backend
  7. Harder to audit and secure

  8. Architecture Violation

  9. BFF is meant to aggregate, not contain business logic
  10. Violates microservices principles
  11. Creates tight coupling

Implementation

BFF Layer Responsibilities

File: apps/bff/src/trpc/routers/master.ts

import { router, protectedProcedure } from '../router';
import { fastAPIClient } from '../clients/fastapi';
import { z } from 'zod';

export const masterRouter = router({
  // Get tenant from master database
  getTenant: protectedProcedure
    .input(z.object({ tenantId: z.string() }))
    .query(async ({ ctx, input }) => {
      // BFF calls FastAPI service - NO direct DB access
      const response = await fastAPIClient.get(
        'userManagement',
        `/api/v1/master/tenants/${input.tenantId}`,
        {},
        'master' // Use master MongoDB endpoint
      );

      return response.data;
    }),

  // List all tenants
  listTenants: protectedProcedure
    .input(z.object({
      limit: z.number().default(10),
      offset: z.number().default(0),
    }))
    .query(async ({ ctx, input }) => {
      // BFF calls FastAPI service
      const response = await fastAPIClient.get(
        'userManagement',
        '/api/v1/master/tenants',
        { params: input },
        'master'
      );

      return response.data;
    }),

  // Get user from master database
  getUser: protectedProcedure
    .input(z.object({ userId: z.string() }))
    .query(async ({ ctx, input }) => {
      // BFF calls FastAPI service
      const response = await fastAPIClient.get(
        'userManagement',
        `/api/v1/master/users/${input.userId}`,
        {},
        'master'
      );

      return response.data;
    }),

  // Create user in master database
  createUser: protectedProcedure
    .input(z.object({
      email: z.string().email(),
      name: z.string(),
      password: z.string(),
      tenants: z.array(z.string()).default([]),
    }))
    .mutation(async ({ ctx, input }) => {
      // BFF calls FastAPI service - NO direct DB access
      const response = await fastAPIClient.post(
        'userManagement',
        '/api/v1/master/users',
        input,
        {},
        'master'
      );

      return response.data;
    }),
});

FastAPI Service Responsibilities

File: bast-ai/src/python/user_management_api/routers/master.py

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime

from ..db.master_mongo import MasterMongoDB
from ..auth import get_current_user

router = APIRouter(prefix="/api/v1/master", tags=["master"])

class MasterUserCreate(BaseModel):
    email: str
    name: str
    password: str
    tenants: List[str] = []

class MasterTenantResponse(BaseModel):
    tenant_id: str
    name: str
    slug: str
    mongodb_endpoint: str
    database_name: str
    status: str
    subscription_tier: str
    created_at: datetime

@router.get("/tenants/{tenant_id}", response_model=MasterTenantResponse)
async def get_tenant(
    tenant_id: str,
    current_user = Depends(get_current_user)
):
    """Get tenant from master database"""
    db = MasterMongoDB()
    tenant = await db.tenants.find_one({"tenant_id": tenant_id})

    if not tenant:
        raise HTTPException(status_code=404, detail="Tenant not found")

    return tenant

@router.get("/tenants", response_model=List[MasterTenantResponse])
async def list_tenants(
    limit: int = 10,
    offset: int = 0,
    current_user = Depends(get_current_user)
):
    """List all tenants from master database"""
    db = MasterMongoDB()
    tenants = await db.tenants.find({}).skip(offset).limit(limit)
    return list(tenants)

@router.post("/users", response_model=dict)
async def create_user(
    user_data: MasterUserCreate,
    current_user = Depends(get_current_user)
):
    """Create user in master database"""
    # Business logic: validation, hashing, etc.
    db = MasterMongoDB()

    # Check if user already exists
    existing = await db.users.find_one({"email": user_data.email})
    if existing:
        raise HTTPException(status_code=400, detail="User already exists")

    # Hash password (business logic)
    from passlib.context import CryptContext
    pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
    password_hash = pwd_context.hash(user_data.password)

    # Create user document
    user = {
        "_id": generate_user_id(),
        "email": user_data.email,
        "name": user_data.name,
        "password_hash": password_hash,
        "tenants": user_data.tenants,
        "status": "active",
        "created_at": datetime.utcnow(),
        "updated_at": datetime.utcnow(),
    }

    # Insert into master database
    await db.users.insert_one(user)

    return {"id": user["_id"], "email": user["email"]}

Master Database Operations

Operations in Python APIs

All CRUD operations for master_db should be in FastAPI services:

  1. User Management
  2. Create user (POST /api/v1/master/users)
  3. Get user (GET /api/v1/master/users/{user_id})
  4. Update user (PUT /api/v1/master/users/{user_id})
  5. Delete user (DELETE /api/v1/master/users/{user_id})
  6. List users (GET /api/v1/master/users)

  7. Tenant Management

  8. Create tenant (POST /api/v1/master/tenants)
  9. Get tenant (GET /api/v1/master/tenants/{tenant_id})
  10. Update tenant (PUT /api/v1/master/tenants/{tenant_id})
  11. Delete tenant (DELETE /api/v1/master/tenants/{tenant_id})
  12. List tenants (GET /api/v1/master/tenants)

  13. User-Tenant Mapping

  14. Create mapping (POST /api/v1/master/user-tenant-mappings)
  15. Get mapping (GET /api/v1/master/user-tenant-mappings/{mapping_id})
  16. Update mapping (PUT /api/v1/master/user-tenant-mappings/{mapping_id})
  17. Delete mapping (DELETE /api/v1/master/user-tenant-mappings/{mapping_id})
  18. List mappings (GET /api/v1/master/user-tenant-mappings)

BFF Layer Responsibilities

The BFF layer should:

  1. Route requests to appropriate FastAPI services
  2. Aggregate responses from multiple services if needed
  3. Transform responses for frontend consumption
  4. Cache responses using Redis
  5. Handle errors and provide user-friendly messages

The BFF should NOT:

  1. ❌ Access master_db directly
  2. ❌ Contain business logic
  3. ❌ Perform data validation (beyond basic type checking)
  4. ❌ Handle authentication/authorization (delegate to FastAPI)

Example: User Creation Flow

Frontend → BFF → FastAPI → Master DB

1. Frontend calls BFF:
   trpc.master.createUser.useMutation({
     email: "user@example.com",
     name: "John Doe",
     password: "secure123",
     tenants: ["acme"]
   })

2. BFF routes to FastAPI:
   POST /api/v1/master/users
   {
     "email": "user@example.com",
     "name": "John Doe",
     "password": "secure123",
     "tenants": ["acme"]
   }

3. FastAPI service:
   - Validates input (business logic)
   - Checks if user exists (business logic)
   - Hashes password (business logic)
   - Creates user in master_db (database operation)
   - Returns response

4. BFF transforms response:
   - Adds metadata if needed
   - Formats for frontend
   - Caches if appropriate

5. Frontend receives response:
   {
     "id": "user123",
     "email": "user@example.com",
     "name": "John Doe"
   }

Benefits

✅ Centralized Business Logic

  • All master_db operations in one place
  • Easier to maintain and test
  • Consistent behavior across services

✅ Security

  • Authentication/authorization in backend
  • Database credentials secured in backend
  • Audit trail in backend services

✅ Reusability

  • Other services can use same APIs
  • Consistent API across all consumers
  • No duplication of logic

✅ Scalability

  • Backend services can scale independently
  • BFF can cache responses
  • Database connections managed in backend

✅ Maintainability

  • Clear separation of concerns
  • Easier to debug and test
  • Single source of truth

Migration Path

If master_db CRUD operations are currently in BFF:

  1. Phase 1: Create FastAPI endpoints for master_db operations
  2. Phase 2: Update BFF to call FastAPI services instead of direct DB access
  3. Phase 3: Remove direct database access from BFF
  4. Phase 4: Add caching in BFF for frequently accessed data
  5. Phase 5: Add monitoring and logging

Summary

Decision: Maintain CRUD operations for master_db in Python APIs layer (FastAPI), not in BFF.

BFF Role: Proxy/aggregation layer that calls FastAPI services.

Benefits: Security, reusability, maintainability, consistency.


Document created: December 16, 2025
Master Database CRUD Architecture Decision