Skip to content

20. Backend Repository Structure

20.1 Overview

The Identity Platform backend follows hexagonal architecture (ports and adapters) with FastAPI and Python.

20.2 Directory Structure

identity-platform/
├── src/
│   ├── main.py                     # FastAPI app entrypoint
│   ├── config/
│   │   ├── __init__.py
│   │   ├── settings.py             # Pydantic settings
│   │   └── constants.py            # App constants
│   │
│   ├── domain/                     # Core business logic (no external deps)
│   │   ├── __init__.py
│   │   ├── entities/
│   │   │   ├── __init__.py
│   │   │   ├── user.py             # User entity
│   │   │   ├── identity.py         # Identity (email, phone, social)
│   │   │   ├── kyc.py              # KYC entity
│   │   │   ├── device.py           # Device entity
│   │   │   ├── challenge.py        # Challenge entity
│   │   │   ├── consent.py          # Consent entity
│   │   │
│   │   ├── value_objects/
│   │   │   ├── __init__.py
│   │   │   ├── user_id.py
│   │   │   ├── mobile.py
│   │   │   ├── email.py
│   │   │   ├── kyc_level.py
│   │   │   ├── kyc_status.py
│   │   │   └── palm_template.py
│   │   │
│   │   ├── services/
│   │   │   ├── __init__.py
│   │   │   ├── auth_service.py     # Auth business logic
│   │   │   ├── user_service.py     # User business logic
│   │   │   ├── kyc_service.py      # KYC business logic
│   │   │   ├── enrollment_service.py
│   │   │   ├── verification_service.py
│   │   │   └── consent_service.py
│   │   │
│   │   ├── events/
│   │   │   ├── __init__.py
│   │   │   ├── user_events.py      # Domain events
│   │   │   ├── kyc_events.py
│   │   │   └── enrollment_events.py
│   │   │
│   │   └── exceptions/
│   │       ├── __init__.py
│   │       ├── auth.py
│   │       ├── user.py
│   │       ├── kyc.py
│   │       └── enrollment.py
│   │
│   ├── ports/                      # Interfaces (abstractions)
│   │   ├── __init__.py
│   │   ├── inbound/                # Driving ports
│   │   │   ├── __init__.py
│   │   │   ├── auth_port.py
│   │   │   ├── user_port.py
│   │   │   ├── kyc_port.py
│   │   │   ├── enrollment_port.py
│   │   │   └── device_port.py
│   │   │
│   │   └── outbound/               # Driven ports
│   │       ├── __init__.py
│   │       ├── user_repository.py
│   │       ├── kyc_repository.py
│   │       ├── device_repository.py
│   │       ├── challenge_repository.py
│   │       ├── consent_repository.py
│   │       ├── kyc_provider.py     # KYC provider interface
│   │       ├── palm_verifier.py    # Palm verification interface
│   │       ├── otp_sender.py       # OTP interface
│   │       ├── email_sender.py     # Email interface
│   │       ├── event_publisher.py  # Event publishing interface
│   │       └── token_store.py      # Refresh token store
│   │
│   ├── adapters/                   # Implementations
│   │   ├── __init__.py
│   │   ├── inbound/                # Driving adapters (API)
│   │   │   ├── __init__.py
│   │   │   ├── rest/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── auth_router.py
│   │   │   │   ├── user_router.py
│   │   │   │   ├── kyc_router.py
│   │   │   │   ├── enrollment_router.py
│   │   │   │   ├── challenge_router.py
│   │   │   │   ├── device_router.py
│   │   │   │   ├── consent_router.py
│   │   │   │   └── health_router.py
│   │   │   │
│   │   │   ├── middleware/
│   │   │   │   ├── __init__.py
│   │   │   │   ├── auth_middleware.py
│   │   │   │   ├── tenant_middleware.py
│   │   │   │   └── logging_middleware.py
│   │   │   │
│   │   │   └── schemas/            # Pydantic request/response
│   │   │       ├── __init__.py
│   │   │       ├── auth.py
│   │   │       ├── user.py
│   │   │       ├── kyc.py
│   │   │       ├── enrollment.py
│   │   │       ├── challenge.py
│   │   │       ├── device.py
│   │   │       └── consent.py
│   │   │
│   │   └── outbound/               # Driven adapters
│   │       ├── __init__.py
│   │       ├── persistence/
│   │       │   ├── __init__.py
│   │       │   ├── postgres/
│   │       │   │   ├── __init__.py
│   │       │   │   ├── models.py           # SQLAlchemy models
│   │       │   │   ├── user_repository.py
│   │       │   │   ├── kyc_repository.py
│   │       │   │   ├── device_repository.py
│   │       │   │   ├── challenge_repository.py
│   │       │   │   └── consent_repository.py
│   │       │   │
│   │       │   └── redis/
│   │       │       ├── __init__.py
│   │       │       ├── token_store.py
│   │       │       └── otp_store.py
│   │       │
│   │       ├── kyc_providers/
│   │       │   ├── __init__.py
│   │       │   ├── nafath_adapter.py
│   │       │   ├── onfido_adapter.py
│   │       │   ├── sumsub_adapter.py
│   │       │   └── mock_adapter.py         # For testing
│   │       │
│   │       ├── palm_providers/
│   │       │   ├── __init__.py
│   │       │   ├── biowave_adapter.py      # X-Telcom BioWave Pass palm verification
│   │       │   └── mock_adapter.py         # For testing
│   │       │
│   │       ├── social/
│   │       │   ├── __init__.py
│   │       │   ├── google_adapter.py
│   │       │   └── apple_adapter.py
│   │       │
│   │       ├── messaging/
│   │       │   ├── __init__.py
│   │       │   ├── twilio_sms.py
│   │       │   ├── unifonic_sms.py
│   │       │   └── sendgrid_email.py
│   │       │
│   │       └── events/
│   │           ├── __init__.py
│   │           ├── kafka_publisher.py
│   │           └── webhook_publisher.py
│   │
│   ├── core/                       # Shared utilities
│   │   ├── __init__.py
│   │   ├── security/
│   │   │   ├── __init__.py
│   │   │   ├── jwt.py              # JWT handling
│   │   │   ├── jwks.py             # JWKS generation
│   │   │   ├── password.py         # Password hashing
│   │   │
│   │   ├── database.py             # DB connection
│   │   ├── redis.py                # Redis connection
│   │   ├── logging.py              # Structured logging
│   │   └── utils.py
│   │
│   └── di/                         # Dependency injection
│       ├── __init__.py
│       ├── container.py            # DI container
│       └── providers.py            # Dependency providers
├── tests/
│   ├── __init__.py
│   ├── conftest.py                 # Pytest fixtures
│   ├── unit/
│   │   ├── domain/
│   │   │   ├── test_auth_service.py
│   │   │   ├── test_user_service.py
│   │   │   └── test_kyc_service.py
│   │   │
│   │   └── adapters/
│   │       ├── test_nafath_adapter.py
│   │       └── test_biowave_adapter.py
│   │
│   ├── integration/
│   │   ├── test_auth_flow.py
│   │   ├── test_kyc_flow.py
│   │   └── test_enrollment_flow.py
│   │
│   └── e2e/
│       └── test_full_onboarding.py
├── migrations/                     # Alembic migrations
│   ├── versions/
│   ├── env.py
│   └── alembic.ini
├── scripts/
│   ├── generate_jwks.py
│   └── seed_tenants.py
├── docker/
│   ├── Dockerfile
│   ├── Dockerfile.dev
│   └── docker-compose.yml
├── k8s/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── configmap.yaml
│   └── secrets.yaml
├── .env.example
├── .gitignore
├── pyproject.toml
├── poetry.lock
├── Makefile
└── README.md

20.3 Key Architecture Principles

1. Hexagonal Architecture (Ports & Adapters)

┌─────────────────────────────────────────────────────────────┐
│                      Adapters (Inbound)                     │
│   REST API │ gRPC │ CLI │ Message Consumer                  │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                      Ports (Inbound)                        │
│   AuthPort │ UserPort │ KYCPort │ EnrollmentPort           │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                      Domain Services                        │
│   AuthService │ UserService │ KYCService │ EnrollmentService│
│                                                             │
│   Entities: User, Identity, KYC, Device, Challenge          │
│   Value Objects: UserId, Mobile, Email, KYCLevel            │
│   Domain Events: UserCreated, KYCVerified                   │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                      Ports (Outbound)                       │
│   UserRepository │ KYCProvider │ PalmVerifier │ OTPSender   │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                      Adapters (Outbound)                    │
│   PostgreSQL │ Redis │ Nafath │ X-Telcom BioWave Pass │ Twilio │ Kafka  │
└─────────────────────────────────────────────────────────────┘

2. Domain-Driven Design

  • Entities have identity and lifecycle
  • Value objects are immutable
  • Domain services contain business logic
  • Domain events for decoupling

3. Dependency Injection

The application uses a declarative dependency injection container to wire all components together:

  • Repositories are registered as singletons (e.g., UserRepository backed by PostgreSQL) and injected into services that need data access.
  • KYC Provider is resolved dynamically via a selector based on the tenant's configured provider name (e.g., nafath, onfido, mock). Each provider adapter is instantiated as a singleton with its own configuration.
  • Palm Verifier is resolved dynamically via a selector based on the tenant's configured palm vendor (e.g., biowave, mock). Each vendor adapter is instantiated as a singleton with its own configuration — identical pattern to KYC providers.
  • Domain Services (e.g., KYCService, EnrollmentService) receive their dependencies — repositories, provider/vendor adapters, and event publishers — through constructor injection, keeping them decoupled from concrete implementations.

4. Clean Separation

Layer Can depend on Cannot depend on
Domain Nothing external Adapters, frameworks
Ports Domain Adapters
Adapters Ports, Domain Other adapters

20.4 Key Files

Domain Entity Example — User:

The User entity contains the following fields:

Field Type Description
id string Unique user identifier
tenant_id string Owning tenant
identities list of Identity Linked auth identities (email, phone, social)
profile object (optional) User profile data
kyc KYCData (optional) KYC verification data
palm_enrolled boolean Whether palm biometric is enrolled
status string active, suspended, or deleted
created_at timestamp Account creation time
updated_at timestamp Last modification time

The entity exposes business logic methods: - is_kyc_verified() — returns true if the user has completed KYC with a verified status. - can_enroll_palm(require_kyc) — returns true if the user has not already enrolled and, when KYC is required by tenant config, has a verified KYC status.

Port Example — KYC Provider & adapters — [POST-MVP]:

The KYCProvider port and its adapters (Nafath, Onfido, Sumsub) follow the same ports-&-adapters pattern as PalmVerifier below, but the detailed port interface and adapter behavior are deferred until KYC is architected — see §7.

Port Example — PalmVerifier:

The PalmVerifier port defines the contract that every palm verification vendor adapter must fulfill. Any adapter (X-Telcom BioWave Pass, or a future vendor) must implement:

Method Purpose
enroll(user_id, palm_template) Store a palm template for a user at the vendor
verify(user_id, palm_scan) 1:1 match: compare scan against user's stored template
identify(palm_scan, tenant_id) 1:N search: find matching user across all templates
get_status(user_id) Check if user has an enrolled template
delete_template(user_id) Remove a stored palm template from the vendor

Adapter Example — X-Telcom BioWave Pass:

The BioWaveAdapter implements the PalmVerifier port for X-Telcom BioWave Pass's palm vein verification service. It is initialized with vendor-specific configuration (base_url, request_id, timeout_ms). When enroll is called, the adapter:

  1. Sends an HTTP POST to the X-Telcom BioWave Pass enrollment API with the user reference and palm template data.
  2. Maps the X-Telcom BioWave Pass response into a standard EnrollmentResult domain entity (with user_id, status, vendor: "biowave", and timestamp).

For verify (1:1) and identify (1:N), the adapter submits the palm scan to the appropriate X-Telcom BioWave Pass endpoint and maps the response into VerificationResult or IdentificationResult domain entities including confidence scores and latency metrics.

This pattern is identical for any future palm adapter — each translates between the external vendor's API and the platform's domain entities.

20.5 Testing Strategy

Type Location Purpose
Unit tests/unit/ Test domain logic in isolation
Integration tests/integration/ Test adapters with real deps
E2E tests/e2e/ Full flow testing