ADR-0004: Import Atomicity
Accepted Brick InventoryDate: 2026-02-11
Context
The ImportOwnedSetsAction fetches a user's LEGO sets from the Rebrickable API (paginated) and persists them to the database. If the API fails mid-pagination (e.g., network timeout on page 3 of 5), the implementation would:
- Save pages 1-2 to the database (partial data)
- Stop the pagination loop silently
- Return a result summary that reports only the imported sets — as if the import was complete
The user had no way to know the import was partial. The summary was lying by omission.
Decision
Save-What-You-Can with Honest Reporting
Partial imports are acceptable. Every set saved is valuable, and the import is idempotent (re-running it skips existing sets and picks up missing ones). However, the user must be clearly informed when an import was incomplete.
Result Data Structure
php
final readonly class ImportOwnedSetsResultData
{
public function __construct(
public int $created,
public int $updated,
public int $skipped,
public int $total,
public bool $complete, // Was the full import completed?
public ?string $error, // Error message if incomplete
) {}
}Per-Page Transactions
Each page's data is persisted within its own transaction:
- Successfully fetched pages are saved even if later pages fail
- Individual page processing is atomic (no half-processed pages)
- No need to hold all pages in memory before writing
php
foreach ($paginatedResults as $page) {
$this->db->transaction(function () use ($page, &$created, &$updated, &$skipped) {
foreach ($page as $userSet) {
// upsert set, create/update family set...
}
});
}Frontend Behavior
| Scenario | User sees |
|---|---|
| Complete import | Success message with summary |
| Partial import | Warning message with summary + "Retry to get the rest" prompt |
| Total failure (0 sets) | Error message + retry option |
Options Considered
| Option | Verdict | Reason |
|---|---|---|
| All-or-nothing (fetch all, then persist) | Rejected | A user with 1000+ sets would get nothing on a transient API failure. Partial data is more valuable than no data. |
| Save-what-you-can with silent failure (status quo) | Rejected | Misleading. A partial import reported as complete is worse than an honest partial import. |
| Save-what-you-can with honest reporting | Accepted | Preserves data, informs the user, and supports idempotent retry. |
Consequences
Positive
- Partial data preserved on failure
- User clearly informed of import status
- Idempotent retry handles gaps naturally
- No memory pressure from holding large collections in memory
- Per-page transactions prevent half-processed pages
Negative
- Frontend must handle three states instead of two
- Partial imports may confuse users who don't read the warning
Risks
- If the API is consistently unreliable, users may get stuck in a loop of partial imports