Skip to content

Latest commit

 

History

History
157 lines (130 loc) · 8.13 KB

File metadata and controls

157 lines (130 loc) · 8.13 KB

Backend architecture and folder structure

The backend follows a modular-monolith structure. Domain boundaries are explicit, while deployment and transactions remain simple enough for an in-house team. A module can be extracted into a service later only when scale, ownership, or isolation requires it.

Current structure

backend/
├── alembic.ini
├── alembic/
│   ├── env.py                         # Async Alembic environment
│   ├── script.py.mako                 # Revision template
│   └── versions/                      # Ordered schema revisions
├── app/
│   ├── api/
│   │   └── v1/
│   │       ├── applications.py        # Application HTTP endpoints
│   │       ├── ai.py                  # Optional advisory AI endpoints
│   │       ├── audit.py               # Audit-event endpoints
│   │       ├── documents.py           # Private storage ticket endpoints
│   │       ├── migration_manifests.py # Safe mapping validation endpoint
│   │       ├── password_recovery.py   # Local reset and SSO recovery endpoints
│   │       ├── portfolio.py
│   │       ├── user_management.py     # Directory, profile, and invitation endpoints
│   │       └── router.py              # v1 router composition
│   ├── core/
│   │   └── config.py                  # Environment configuration
│   ├── db/
│   │   ├── base.py                    # Declarative metadata
│   │   └── session.py                 # Async engine and transaction scope
│   ├── integrations/
│   │   ├── ai/                         # Governed model-provider adapters
│   │   ├── email/                      # Server-only SMTP delivery adapter
│   │   ├── sms/                        # Server-only generic HTTP SMS adapter
│   │   └── storage/                    # Private S3 document adapter
│   ├── legacy_migration/
│   │   ├── cli.py                     # Manifest validation/planning CLI
│   │   └── manifest.py                # Mapping and safety contracts
│   ├── models/                        # SQLAlchemy persistence models
│   ├── repositories/
│   │   └── users.py                   # Shared persistence contract, no HTTP imports
│   ├── schemas/
│   │   ├── password_recovery.py       # Reset request/response contracts
│   │   └── user_management.py         # User profile and lifecycle contracts
│   ├── services/
│   │   ├── password_recovery.py       # Token, email, hash, and SSO routing use cases
│   │   ├── recovery_notifications.py  # Independent email/SMS channel orchestration
│   │   └── user_management.py         # Directory and profile lifecycle use cases
│   ├── fixtures.py                    # Prototype-only records
│   ├── user_fixtures.py               # Prototype-only user/auth records
│   └── main.py                        # FastAPI application factory
├── tests/
├── requirements.txt
└── requirements-dev.txt

Target structure as domains grow

backend/app/
├── api/                 # Transport only: validation, auth dependency, response mapping
├── core/                # Config, security, observability, errors, idempotency
├── db/                  # Engine, sessions, metadata, transaction helpers
├── models/              # Persistence models only
├── schemas/             # External and internal data contracts
├── repositories/        # Database queries; no HTTP/provider code
├── services/            # Use cases and transaction boundaries
├── workflows/           # Validated loan state machine and orchestration
├── integrations/        # CKYC, PAN, bureau, AA, e-sign, OCR adapters
├── legacy_migration/    # Read-only extract/transform/reconcile pipeline
├── tasks/               # Async jobs and scheduled reconciliation
└── api/v2/              # Only when a breaking API contract is required

Dependency rules

api -> services -> repositories -> models/db
                  -> integrations
                  -> workflows

legacy_migration -> canonical schemas -> repositories
  • API routers must not execute SQL or call providers directly.
  • A use-case router must never import another router or the composed api_router.
  • Shared persistence is accessed through repository contracts, not through another API endpoint.
  • router.py is composition-only: removing one included router must not break imports or startup for the others.
  • Repositories must not import FastAPI or provider clients.
  • Provider adapters return canonical schemas; services do not parse vendor payloads.
  • Workflows own allowed stage transitions, required conditions, and audit events.
  • Legacy migration code may write only through staging/repository interfaces and must never update the source system.
  • SQLAlchemy models must not be returned directly from public APIs.

Standalone router contract

Each versioned router is independently mountable on a minimal FastAPI application. This keeps transport concerns isolated and allows a use case to be extracted into its own deployment later without rewriting its HTTP contract.

Use case Router module Service/repository dependency Imports another router
Applications api/v1/applications.py Demonstration application data No
Audit ingestion api/v1/audit.py Audit schema No
Portfolio api/v1/portfolio.py Portfolio calculation No
Migration validation api/v1/migration_manifests.py Legacy-manifest validator No
Application intelligence api/v1/ai.py AI assessment service No
Document storage api/v1/documents.py S3 storage adapter No
User management api/v1/user_management.py User management service and repository contract No
Password recovery api/v1/password_recovery.py Password recovery service, independent email/SMS adapters, and repository contract No

backend/tests/test_router_isolation.py and the standalone cases in backend/tests/test_users.py mount these routers one at a time, call their primary endpoint, and verify that unrelated routes are not present.

API conventions

Versioning

  • All business endpoints use /api/v1.
  • Additive response fields are backward compatible.
  • Removing/renaming fields, changing meaning, or changing required inputs requires /api/v2.
  • Provider and database schema versions are independent from API versions.

Request controls

  • Require authenticated actor, tenant/entity, role, and correlation ID.
  • Require an Idempotency-Key for create, payment, provider-pull, sanction, and disbursement commands.
  • Validate consent and permissible purpose before regulated provider requests.
  • Use optimistic concurrency (record_version or ETag) for updates.

Standard error envelope

{
  "error": {
    "code": "APPLICATION_STAGE_CONFLICT",
    "message": "Application cannot move from Documents to Sanctioned",
    "correlation_id": "uuid",
    "details": []
  }
}

Do not return stack traces, SQL errors, provider credentials, or raw vendor payloads.

Observability

Every request should emit structured logs and metrics containing correlation ID, route, status, duration, tenant, actor/service identity, and non-sensitive entity identifiers. PII and provider payload logging is disabled by default.

Transaction boundaries

  • One API command owns one database transaction.
  • External provider calls do not remain inside an open database transaction.
  • Use an outbox for reliable events/tasks after commit.
  • Use inbox/idempotency records for provider webhooks and retried commands.
  • Audit events are written in the same transaction as the business state change where possible.

Prototype boundary

The current application endpoints still use fixtures.py and user_fixtures.py. The new models and migration foundation establish the target persistence contract; repositories and production authentication are the next implementation step.