Rules Hub
Code Ruleset
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Make check-then-create atomic with a transaction and row lock
Make check-then-create atomic with a unique constraint and upsert, or serialize it by locking an existing shared row before lookup.
Bad example
| 1 | async addItem(cartId: number, productId: number, dto: ItemDto) { |
| 2 | // Read outside any transaction |
| 3 | let item = await this.itemRepo.findOne({ where: { cartId, productId } }); |
| 4 |
|
| 5 | if (!item) { |
| 6 | item = await this.itemRepo.save({ cartId, productId }); |
| 7 | } |
| 8 |
|
| 9 | // ... continue with item |
| 10 | } |
Explanation (EN)
Locking an empty lookup result does not serialize two concurrent creators.
Objašnjenje (HR)
Zaključavanje praznog rezultata upita ne serijalizira dva istodobna stvaranja.
Good example
| 1 | await dataSource.transaction(async (manager) => { |
| 2 | await manager.getRepository(Cart).createQueryBuilder('cart') |
| 3 | .setLock('pessimistic_write') |
| 4 | .where('cart.id = :cartId', { cartId }).getOneOrFail(); |
| 5 | const repository = manager.getRepository(Item); |
| 6 | const item = await repository.findOneBy({ cartId, productId }); |
| 7 | return item ?? repository.save({ cartId, productId }); |
| 8 | }); |
Explanation (EN)
Every cooperating writer locks the existing cart first; a unique (cartId, productId) constraint remains the database safeguard.
Objašnjenje (HR)
Svi surađujući upisivači prvo zaključavaju postojeću košaricu. UNIQUE ograničenje nad cartId i productId ostaje dodatna zaštita baze.
Notes (EN)
For concurrent create-if-absent operations, enforce a unique database key and use an atomic upsert, or lock an existing shared parent row before the lookup and insert in one transaction.
Exceptions / Tradeoffs (EN)
Alternatively use an idempotent upsert (INSERT ... ON CONFLICT DO NOTHING / UPDATE) where the database guarantees atomicity; the key point is the check and create must not be two separate non-atomic round-trips. SELECT FOR UPDATE locks returned rows; an absent row is not protected by a row lock in PostgreSQL. Handle conflict retries and database-specific isolation behavior.
Iznimke / Tradeoffi (HR)
Može se koristiti atomski upsert ako ga baza jamči. Provjera i stvaranje ne smiju ostati dvije odvojene neatomske operacije. PostgreSQL SELECT FOR UPDATE zaključava vraćene retke, pa ne štiti redak koji još ne postoji. Obradi sukobe, ponovne pokušaje i izolacijska pravila konkretne baze.