Architectural Best Practices
Conventions followed by enterprise engineering teams building maintainable, high-throughput Dart backends and responsive Flutter frontends.
Full-Stack Project Structure
Bloom encourages a unified monorepo or full-stack layout where both the Dart backend and Flutter client share models, validation schemas, and protocol types without code duplication.
Backend Architectural Rules
Key engineering principles for the Bloom Server ecosystem (bloom_db, bloom_rest, bloom_realtime, bloom_jobs):
Organize backend domain features into self-contained modular directories containing models/, views.dart, serializers.dart, urls.dart, and permissions.dart.
Never execute related database mutations sequentially on the base connection. Wrap multi-model operations inside db.transaction((tx) async { ... }) to guarantee atomic rollbacks.
Compose endpoint authorization using reusable boolean algebra combinators like IsAuthenticated().and(IsOwnerOrAdmin()) rather than manual imperative if-checks in view handlers.
Use type-safe QuerySet chains (filter, exclude, orderBy, limit) with generated ModelMeta rather than raw string SQL queries to ensure automatic SQL injection prevention and dialect portability.
Pass lightweight scalar identifiers (e.g., {"user_id": 42}) in background job queues; allow worker isolates to fetch fresh database state inside their own isolated execution contexts.
Stream large file uploads and binary assets directly through BloomStorageAdapter to S3/disk rather than buffering entire request bodies into RAM.
Transaction & Database Discipline
Always pass the transactional DbExecutor down to model queries and nested service calls to guarantee atomicity:
Client & UI Conventions
Group domain models, controllers, and specific widgets inside lib/features/<name>/, keeping lib/routes/ lightweight for page composition.
Use fine-grained Watch((context) => ...) wrappers around leaf nodes rather than making entire routes StatefulWidget.
Construct query keys with deterministic lists like ['entity', id, filter] so cache hits and invalidations match reliably.
Instantiate signals with createSignal and avoid storing raw mutable variables outside of signals in controllers.
Never manually edit generated native manifests; declare permissions in bloom.yaml and run bloom prebuild in CI.