Skip to content
Sarj Code Standards

service-singleton-delegates-to-bulk

Service singleton CRUD operations must delegate to a bulk primitive.

Why

A singleton-only API forces callers into serial operations. Separate singleton and bulk implementations duplicate filtering, validation and mutation behavior; making bulk loop through singleton preserves that cost and can recurse when the singleton is later made an adapter.

Fix

Implement get_many, create_many, insert_many, upsert_many, update_many or delete_many as the primitive. Delegate the matching singleton through a one-item collection, forwarding shared context and awaiting async operations. Preserve result projection, missing-item, authorization and transaction semantics; use a reasoned exact-code suppression for a boundary that cannot support batching.

Examples

Before — flagged A singleton operation needs a bulk primitive
app/users.py
class UserService:
async def get(self, key: str):
return await self.store.get(key)
After — preferred Implement singleton access as a one-item bulk operation
app/users.py
class UserService:
async def get(self, key: str):
rows = await self.get_many([key])
return rows[0] if rows else None
async def get_many(self, keys: list[str]):
return await self.store.get_many(keys)
Before — flagged Bulk writes must not call back through the singleton
app/users.py
class UserService:
def upsert(self, user):
return self.upsert_many([user])[0]
def upsert_many(self, users):
return [self.upsert(user) for user in users]
After — preferred Write through the bulk primitive
app/users.py
class UserService:
def upsert(self, user):
return self.upsert_many([user])[0]
def upsert_many(self, users):
return self.store.upsert_many(users)