| name | repository-guide |
| description | Guide for implementing Backend.AI repository patterns (create, get, search, update, delete, purge, batch operations, Querier, BatchQuerier, Creator, Updater, Purger, SearchScope, with_tables) |
| invoke_method | automatic |
| auto_execute | false |
| enabled | true |
| tags | ["repository","data-access","querier","search-scope"] |
Repository Development Guide
Guide for implementing Backend.AI repositories using base patterns and standard operations.
Standard Operations
Repositories implement 6 standard operations:
- create - Create new entity
- get - Retrieve single entity by ID
- search - Query with filters and pagination
- update - Update existing entity
- delete - Soft delete (status change)
- purge - Hard delete (permanent removal)
Target semantics:
- Single (
create/get/update/delete/purge) — one row by PK.
- Batch (
batch_*) — many rows in one SQL statement, atomic (all-or-nothing); returns only the affected-row count.
- Bulk (
bulk_*) — rows processed individually, partial failures allowed (returns successes + errors).
Method naming (no prefix):
await repository.create(data)
await repository.get(id, scope=None)
await repository.search(scope, filters, pagination)
await repository.update(id, data)
await repository.delete(id)
await repository.purge(id)
await repository.batch_update(ids, data)
await repository.batch_delete(ids)
await repository.batch_purge(ids)
await repository.bulk_create(specs)
await repository.bulk_upsert(specs)
Base Utilities
Located in: src/ai/backend/manager/repositories/base/
All repositories use these base utilities for standard operations:
SearchScope
Multi-tenant access control for queries.
Implementations: repositories/{domain}/types.py
- Example:
repositories/fair_share/types.py - DomainFairShareSearchScope
- Example:
repositories/group/types.py - GroupSearchScope
Pattern:
- Frozen dataclass with scope parameters
to_conditions() method converts to query conditions
Base Utility Classes
Available utilities:
Creator[TRow] - Create operations
Querier[TRow] - Single entity retrieval (get)
BatchQuerier[TRow] - Search with filters and pagination
Updater[TRow] - Update with OptionalState pattern
Purger[TRow] - Hard delete operations
BatchUpdater[TRow], BatchPurger[TRow] - atomic multi-row (single statement)
BulkCreator[TRow], BulkUpserter[TRow] - per-row with partial failures (execute_bulk_*_partial)
See implementations:
repositories/base/ - Base utility source code
repositories/fair_share/repository.py - Usage examples
DB Ops Wrapper (ops/) — Preferred for new code
ops/provider.py wraps ExtendedAsyncSAEngine and exposes a spec-only operations
surface. Migration direction: db_sources are moving to this wrapper. New or modified
db_sources SHOULD use DBOpsProvider rather than holding the engine and calling
session.execute directly. Existing db_sources are migrated gradually when touched.
Entry points
class FooDBSource:
def __init__(self, db: ExtendedAsyncSAEngine) -> None:
self._ops = DBOpsProvider(db)
async def foo(self, foo_id: FooID) -> FooData:
async with self._ops.read_ops() as r:
result = await r.query(Querier(row_class=FooRow, pk_value=foo_id))
if result is None:
raise FooNotFound()
return result.row.to_data()
async def create(self, spec: FooCreatorSpec) -> FooData:
async with self._ops.write_ops() as w:
result = await w.create(Creator(spec=spec))
return result.row.to_data()
read_ops() / write_ops() both open a READ COMMITTED session; the raw engine/
session is never exposed.
- ops methods accept only spec types — never raw
Select/Insert/Update/Delete.
Reads with scope (RBAC)
async with self._ops.read_ops() as r:
result = await r.batch_query_with_scopes(sa.select(FooRow), querier, scopes)
result = await r.batch_query_in_global(sa.select(FooRow), querier)
Use batch_query_in_global only for superadmin-only endpoints or internal system
operations; it bypasses RBAC scope filtering. Empty scopes for the scoped variant
raises EmptySearchScopeError (400).
Single-table specs + multi-table coordination
Each spec owns exactly one table — never build child-table rows inside a creator/
updater spec. For a parent + dependent children, the repository coordinates the
sequence procedurally (no tree object):
async with self._ops.write_ops() as w:
parent = (await w.create(Creator(spec=parent_spec))).row
dep = ChildDependency(parent_id=parent.id)
children = (await w.bulk_create_dependent(child_specs, dep)).rows
DependentCreatorSpec[TDependency, TRow].build_row(dependency) builds one row from a
dependency resolved at execution time (e.g. the parent's generated id).
create_dependent (single) / bulk_create_dependent (multiple) execute them.
- An update that must replace child rows = update parent + purge old children +
bulk_create_dependent new children, all inside one write_ops() block.
Implementation Pattern
Pattern:
- Initialize base utilities in
__init__
- Implement 6 standard operations using base utilities
- Use
begin_session() for writes, begin_readonly_session() for reads
- Pass scope to base utilities
- Add batch operations if needed
See complete implementation:
repositories/fair_share/repository.py - Full repository example
repositories/group/repository.py - Scope usage patterns
repositories/domain/repository.py - Standard operations
DB Source and Cache Source Architecture
Architecture principle:
- Repositories are transitioning to delegate data access to DB Source pattern
- Repository acts as coordinator between DB Source and Cache Source (if present)
- Actual database queries and transactions are handled by DB Source
- Cache operations are handled by Cache Source (optional)
Directory structure:
repositories/{domain}/
├── repository.py # Public interface, source delegation
├── db_source/
│ ├── __init__.py
│ └── db_source.py # DB operations implementation
└── cache_source/ # (Optional)
├── __init__.py
└── cache_source.py # Cache operations implementation
Pattern:
- Repository imports and uses
db_source module
- Repository public methods delegate to DB Source methods
- Transactions are managed at DB Source level (using
begin_session() or begin_readonly_session())
- Cache Source handles cache invalidation and retrieval (when present)
Responsibilities:
- Repository: Public API, coordination between sources, business logic orchestration
- DB Source: Database queries, transaction management, ORM operations
- Cache Source: Cache reads/writes, invalidation strategies
See complete examples:
repositories/fair_share/repository.py:39 - DB Source import and usage
repositories/fair_share/db_source/db_source.py - DB Source implementation
repositories/scheduler/cache_source/cache_source.py - Cache Source example
repositories/README.md:78 - Cache management reference
Migration status:
- Not all repositories have migrated to this pattern yet
- New repositories SHOULD use this pattern
- Existing repositories can be migrated gradually when modified
Implementation Steps
1. Define Types
File: repositories/{domain}/types.py
Define SearchScope dataclass with to_conditions() method.
See examples:
repositories/fair_share/types.py - Multiple scope types
repositories/group/types.py - Scope with conditions
2. Implement Repository
File: repositories/{domain}/repository.py
- Initialize base utilities in
__init__
- Implement standard 6 operations
- Add batch operations if needed
- Add domain-specific methods
3. Write Tests
File: tests/unit/manager/repositories/{domain}/test_{operation}.py
- Use
with_tables fixture for real DB
- Test all standard operations
- Test scope filtering
- Test error cases
See: /test-guide skill for testing workflow
Transaction Management
Pattern:
- Read operations:
begin_readonly_session()
- Write operations:
begin_session()
- Pass
db_sess to base utilities
See examples: repositories/fair_share/repository.py
Query Optimization
Tips:
- Select only needed columns
- Use scope filtering at DB level
- Add indexes for frequently filtered columns
- Use batch operations for multiple entities
- Avoid N+1 queries
See: repositories/base/ for optimization utilities
Scope Usage
Pattern:
- Pass scope to base utilities (
query(), search(), purge())
- Scope
to_conditions() converts to query filters
- Domain scope → domain_name filter
- Project scope → project_id filter
- User scope → user_uuid filter
See examples: repositories/fair_share/repository.py
Type Safety
Use domain types:
FairShareId instead of str
DomainName instead of str
- Frozen dataclasses for immutability
- Comprehensive type hints
Error Handling
Pattern:
- Define domain exceptions inheriting from
BackendAIError
- Raise specific exceptions (e.g.,
EntityNotFound, EntityAlreadyExists)
- Provide clear error messages
See examples: repositories/fair_share/repository.py
Example Repositories
Study these implementations:
src/ai/backend/manager/repositories/fair_share/ - Complete implementation
src/ai/backend/manager/repositories/group/ - Scope usage
src/ai/backend/manager/repositories/domain/ - Standard operations
Cross-References
- Service Layer:
/service-guide - Service methods using repositories
- API Layer:
/api-guide - API handlers calling services
- Testing:
/test-guide - Scenario-first testing with repositories
- Base README:
repositories/README.md - Architecture overview
Troubleshooting
SearchScope not filtering:
- Verify
to_conditions() returns correct conditions
- Check scope passed to base utility
- Verify column names match table
FK constraint violations:
- Create parent entities first in tests
- Use
with_tables fixture
- Check cascade settings
Type errors:
- Use correct generic type:
Querier[YourRow]
- Match PK column type with table definition
- Use domain types consistently
Performance issues:
- Add indexes for filtered columns
- Use
begin_readonly_session() for reads
- Batch operations for multiple entities
- Profile queries with EXPLAIN
Summary
Standard operations:
- create, get, search, update, delete, purge
- batch_update, batch_delete, batch_purge
Base utilities:
- Creator, Querier, BatchQuerier, Updater, Purger
- SearchScope for multi-tenant filtering
Key principles:
- Public methods use standard names (no prefix)
- Base utilities handle DB operations
- Scope passed for access control
- Transaction management via session context
- Type safety with domain types
Next steps:
- Study example repositories
- Define types and scope
- Implement standard operations
- Write tests with
/test-guide
- Integrate with service layer (
/service-guide)