Skip to content

ADR-0004: Import Atomicity

Accepted Brick Inventory

Date: 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:

  1. Save pages 1-2 to the database (partial data)
  2. Stop the pagination loop silently
  3. 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

ScenarioUser sees
Complete importSuccess message with summary
Partial importWarning message with summary + "Retry to get the rest" prompt
Total failure (0 sets)Error message + retry option

Options Considered

OptionVerdictReason
All-or-nothing (fetch all, then persist)RejectedA 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)RejectedMisleading. A partial import reported as complete is worse than an honest partial import.
Save-what-you-can with honest reportingAcceptedPreserves 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

Architecture documentation for contributors and collaborators.