Skip to content

Generated Architecture

Understanding the project structure and patterns generated by FCube.


Project Overview

my_project/
├── app/                     # Application code
│   ├── apis/
│   │   └── v1.py            # API versioning
│   └── core/                # Core infrastructure
│       ├── __init__.py
│       ├── database.py      # Database connection
│       ├── models.py        # Base models
│       ├── settings.py      # Configuration
│       ├── crud.py          # Base CRUD class
│       ├── exceptions.py    # Custom exceptions
│       ├── logging.py       # Logging configuration
│       ├── main.py          # FastAPI entry point
│       ├── dependencies.py  # Dependency injection
│       ├── alembic_models_import.py  # For migrations
│       └── celery_app.py    # Celery configuration
├── migrations/              # Database migrations (Alembic)
├── docker/                  # Docker configuration
│   ├── Dockerfile
│   └── docker-entrypoint.sh
├── docker-compose.yml       # Multi-container setup
├── alembic.ini              # Alembic configuration
├── pyproject.toml           # Python dependencies
├── .env.example             # Environment variables template
├── .gitignore               # Git ignore rules
└── README.md                # Project documentation

Layered Architecture

FCube follows the Layered Architecture pattern:

┌─────────────────────────────────────┐
│          Routes (HTTP Layer)        │
│  - Request validation               │
│  - Authentication/Authorization     │
│  - Response serialization           │
└───────────────┬─────────────────────┘
┌───────────────▼─────────────────────┐
│        Services (Business Logic)    │
│  - Business rules                   │
│  - Transaction boundaries           │
│  - Orchestration                    │
└───────────────┬─────────────────────┘
┌───────────────▼─────────────────────┐
│         CRUD (Data Access)          │
│  - Pure database operations         │
│  - NO session.commit()              │
│  - flush() and refresh() only       │
└───────────────┬─────────────────────┘
┌───────────────▼─────────────────────┐
│        Models (Database Schema)     │
│  - SQLAlchemy ORM models            │
│  - Relationships                    │
└─────────────────────────────────────┘

Request Flow

HTTP Request
┌─────────────────┐
│     Route       │  Validates input, calls service
└────────┬────────┘
┌─────────────────┐
│    Service      │  Orchestrates business logic, manages transactions
└────────┬────────┘
┌─────────────────┐
│      CRUD       │  Executes database operations (no commit)
└────────┬────────┘
┌─────────────────┐
│     Model       │  Maps to database tables
└─────────────────┘

Core Components

main.py

The application entry point:

from fastapi import FastAPI
from app.apis import v1
from app.core.config import settings
from app.core.database import engine

app = FastAPI(
    title=settings.PROJECT_NAME,
    version="1.0.0",
    docs_url="/docs",
    redoc_url="/redoc",
)

# Include API router
app.include_router(v1.router, prefix="/api/v1")


@app.on_event("startup")
async def startup():
    # Initialize database connection
    pass


@app.on_event("shutdown")
async def shutdown():
    # Cleanup
    pass

settings.py

Centralized settings using Pydantic:

from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    # Database
    DATABASE_URL: str

    # Security
    SECRET_KEY: str
    JWT_SECRET_KEY: str
    JWT_ALGORITHM: str = "HS256"
    ACCESS_TOKEN_EXPIRE_MINUTES: int = 30

    # Application
    PROJECT_NAME: str = "MyApp"
    DEBUG: bool = False

    class Config:
        env_file = ".env"
        case_sensitive = True


settings = Settings()

database.py

Async database connection:

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker, declarative_base

from app.core.config import settings

# Create async engine
engine = create_async_engine(
    settings.DATABASE_URL,
    echo=settings.DEBUG,
    pool_pre_ping=True,
)

# Session factory
AsyncSessionLocal = sessionmaker(
    engine,
    class_=AsyncSession,
    expire_on_commit=False,
)

# Base class for models
Base = declarative_base()


async def get_db():
    """Dependency for database sessions."""
    async with AsyncSessionLocal() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise
        finally:
            await session.close()

Module Structure

Each module follows the same pattern:

Model (models/product.py)

from datetime import datetime
from uuid import UUID, uuid4
from sqlalchemy import String, Numeric, Boolean, DateTime, func
from sqlalchemy.orm import Mapped, mapped_column

from app.core.database import Base


class Product(Base):
    __tablename__ = "products"

    id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
    name: Mapped[str] = mapped_column(String(255))
    description: Mapped[str | None] = mapped_column(String(1000))
    price: Mapped[Decimal] = mapped_column(Numeric(10, 2))
    is_active: Mapped[bool] = mapped_column(default=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        server_default=func.now()
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        server_default=func.now(),
        onupdate=func.now()
    )

Schemas (schemas/product_schemas.py)

from datetime import datetime
from decimal import Decimal
from uuid import UUID
from pydantic import BaseModel, Field


class ProductBase(BaseModel):
    name: str = Field(..., min_length=1, max_length=255)
    description: str | None = Field(None, max_length=1000)
    price: Decimal = Field(..., gt=0)


class ProductCreate(ProductBase):
    pass


class ProductUpdate(BaseModel):
    name: str | None = Field(None, min_length=1, max_length=255)
    description: str | None = None
    price: Decimal | None = Field(None, gt=0)
    is_active: bool | None = None


class ProductResponse(ProductBase):
    id: UUID
    is_active: bool
    created_at: datetime
    updated_at: datetime

    model_config = {"from_attributes": True}

CRUD (crud/product_crud.py)

from uuid import UUID
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.product import Product
from app.schemas.product_schemas import ProductCreate, ProductUpdate


class ProductCRUD:
    def __init__(self, db: AsyncSession):
        self.db = db

    async def create(self, data: ProductCreate) -> Product:
        product = Product(**data.model_dump())
        self.db.add(product)
        await self.db.flush()
        await self.db.refresh(product)
        return product

    async def get(self, id: UUID) -> Product | None:
        stmt = select(Product).where(Product.id == id)
        result = await self.db.execute(stmt)
        return result.scalar_one_or_none()

    async def get_all(
        self,
        skip: int = 0,
        limit: int = 100,
        active_only: bool = True,
    ) -> list[Product]:
        stmt = select(Product)
        if active_only:
            stmt = stmt.where(Product.is_active == True)
        stmt = stmt.offset(skip).limit(limit)
        result = await self.db.execute(stmt)
        return result.scalars().all()

    async def update(self, id: UUID, data: ProductUpdate) -> Product | None:
        product = await self.get(id)
        if not product:
            return None

        update_data = data.model_dump(exclude_unset=True)
        for key, value in update_data.items():
            setattr(product, key, value)

        await self.db.flush()
        await self.db.refresh(product)
        return product

    async def delete(self, id: UUID) -> bool:
        product = await self.get(id)
        if not product:
            return False
        await self.db.delete(product)
        return True

Service (services/product_service.py)

from uuid import UUID
from fastapi import HTTPException, status

from app.crud.product_crud import ProductCRUD
from app.schemas.product_schemas import ProductCreate, ProductUpdate, ProductResponse


class ProductService:
    def __init__(self, crud: ProductCRUD):
        self.crud = crud

    async def create_product(self, data: ProductCreate) -> ProductResponse:
        product = await self.crud.create(data)
        return ProductResponse.model_validate(product)

    async def get_product(self, id: UUID) -> ProductResponse:
        product = await self.crud.get(id)
        if not product:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Product not found"
            )
        return ProductResponse.model_validate(product)

    async def list_products(
        self,
        skip: int = 0,
        limit: int = 100,
    ) -> list[ProductResponse]:
        products = await self.crud.get_all(skip=skip, limit=limit)
        return [ProductResponse.model_validate(p) for p in products]

    async def update_product(
        self,
        id: UUID,
        data: ProductUpdate,
    ) -> ProductResponse:
        product = await self.crud.update(id, data)
        if not product:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Product not found"
            )
        return ProductResponse.model_validate(product)

    async def delete_product(self, id: UUID) -> None:
        deleted = await self.crud.delete(id)
        if not deleted:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Product not found"
            )

Routes (routes/public/product.py)

from uuid import UUID
from fastapi import APIRouter, Depends, status

from app.schemas.product_schemas import ProductCreate, ProductUpdate, ProductResponse
from app.services.product_service import ProductService
from app.core.dependencies import get_product_service

router = APIRouter()


@router.post("/", response_model=ProductResponse, status_code=status.HTTP_201_CREATED)
async def create_product(
    data: ProductCreate,
    service: ProductService = Depends(get_product_service),
):
    """Create a new product."""
    return await service.create_product(data)


@router.get("/", response_model=list[ProductResponse])
async def list_products(
    skip: int = 0,
    limit: int = 100,
    service: ProductService = Depends(get_product_service),
):
    """List all products."""
    return await service.list_products(skip=skip, limit=limit)


@router.get("/{id}", response_model=ProductResponse)
async def get_product(
    id: UUID,
    service: ProductService = Depends(get_product_service),
):
    """Get a product by ID."""
    return await service.get_product(id)


@router.patch("/{id}", response_model=ProductResponse)
async def update_product(
    id: UUID,
    data: ProductUpdate,
    service: ProductService = Depends(get_product_service),
):
    """Update a product."""
    return await service.update_product(id, data)


@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_product(
    id: UUID,
    service: ProductService = Depends(get_product_service),
):
    """Delete a product."""
    await service.delete_product(id)

Dependency Injection

dependencies.py

from functools import lru_cache
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.database import get_db
from app.crud.product_crud import ProductCRUD
from app.services.product_service import ProductService


@lru_cache()
def get_product_crud(
    db: AsyncSession = Depends(get_db),
) -> ProductCRUD:
    return ProductCRUD(db)


@lru_cache()
def get_product_service(
    crud: ProductCRUD = Depends(get_product_crud),
) -> ProductService:
    return ProductService(crud)

Benefits

  1. Testability — Mock dependencies easily
  2. Clarity — All dependencies are explicit
  3. Singleton Pattern@lru_cache() ensures single instance
  4. Flexibility — Swap implementations without changing code

Key Patterns

Transaction Management

# CRUD: No commit
async def create(self, session, obj_in):
    db_obj = self.model(**obj_in.model_dump())
    session.add(db_obj)
    await session.flush()
    await session.refresh(db_obj)
    # NO commit here
    return db_obj

# Service: Owns commit
async def create_product(self, session, data):
    product = await product_crud.create(session, obj_in=data)
    await session.commit()
    await session.refresh(product)
    return product

Permission System

# permissions.py
PRODUCTS_READ = "products:read"
PRODUCTS_WRITE = "products:write"

def require_product_write_permission():
    return require_permission(PRODUCTS_WRITE)

# In routes
@router.post("/", dependencies=[Depends(require_product_write_permission)])
async def create_product(...):
    ...

Role-Based Routes

app/product/routes/
├── public/              # Public endpoints
│   ├── __init__.py
│   └── product.py
└── admin/               # Admin endpoints
    ├── __init__.py
    └── product_management.py

Key Architecture Decisions

Decision Reason
Async SQLAlchemy Better scalability for I/O bound operations
No Commit in CRUD Services manage transaction boundaries
@lru_cache() for DI Simple singleton pattern with minimal overhead
Separate Route Files Clear distinction between public and admin endpoints
Alembic Migrations Industry standard for database version control
Docker Compose Easy development and production setup