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?¶
- Separation of Concerns
- BFF is a proxy/aggregation layer
- Business logic belongs in backend services
-
Database operations belong in backend services
-
Security
- Authentication/authorization logic in backend
- Data validation in backend
- Security boundary at API layer
-
BFF should not have direct database access
-
Reusability
- Other services may need
master_dbaccess - Centralized business logic
-
Consistent API across services
-
Consistency
- Aligns with microservices architecture
- All database operations in Python services
-
Single pattern for all CRUD operations
-
Maintainability
- Single source of truth for business logic
- Easier to test and debug
- Clear ownership boundaries
❌ Why NOT BFF Layer?¶
- Business Logic Duplication
- Would duplicate logic if other services need access
-
Harder to maintain consistency
-
Security Concerns
- BFF should not have direct database credentials
- Authentication/authorization should be in backend
-
Harder to audit and secure
-
Architecture Violation
- BFF is meant to aggregate, not contain business logic
- Violates microservices principles
- 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:
- User Management
- Create user (
POST /api/v1/master/users) - Get user (
GET /api/v1/master/users/{user_id}) - Update user (
PUT /api/v1/master/users/{user_id}) - Delete user (
DELETE /api/v1/master/users/{user_id}) -
List users (
GET /api/v1/master/users) -
Tenant Management
- Create tenant (
POST /api/v1/master/tenants) - Get tenant (
GET /api/v1/master/tenants/{tenant_id}) - Update tenant (
PUT /api/v1/master/tenants/{tenant_id}) - Delete tenant (
DELETE /api/v1/master/tenants/{tenant_id}) -
List tenants (
GET /api/v1/master/tenants) -
User-Tenant Mapping
- Create mapping (
POST /api/v1/master/user-tenant-mappings) - Get mapping (
GET /api/v1/master/user-tenant-mappings/{mapping_id}) - Update mapping (
PUT /api/v1/master/user-tenant-mappings/{mapping_id}) - Delete mapping (
DELETE /api/v1/master/user-tenant-mappings/{mapping_id}) - List mappings (
GET /api/v1/master/user-tenant-mappings)
BFF Layer Responsibilities¶
The BFF layer should:
- Route requests to appropriate FastAPI services
- Aggregate responses from multiple services if needed
- Transform responses for frontend consumption
- Cache responses using Redis
- Handle errors and provide user-friendly messages
The BFF should NOT:
- ❌ Access
master_dbdirectly - ❌ Contain business logic
- ❌ Perform data validation (beyond basic type checking)
- ❌ 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_dboperations 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:
- Phase 1: Create FastAPI endpoints for
master_dboperations - Phase 2: Update BFF to call FastAPI services instead of direct DB access
- Phase 3: Remove direct database access from BFF
- Phase 4: Add caching in BFF for frequently accessed data
- 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