Magento 2 Repository Pattern and Data Models

Magento 2 Repository Pattern and Data Models

December 26, 2025 ยท By Magento Company
Magento 2 Repository Pattern and Data Models

Magento gives you three ways to touch data - repositories, models, and collections - and they are not interchangeable. Each sits at a different altitude: stability, convenience, or power. Picking the wrong altitude is behind both fragile modules and slow ones. Here is the map.

The Three Layers

Repository (ProductRepositoryInterface): the stable API. get/getById/save/delete/getList. Per-entity, deliberate, backward-compatible. This is the default choice for business logic.

Model (Product::load, ->save()): the active-record legacy layer. Still everywhere in core, but deprecated for new code in favour of repositories - and model save() triggers a waterfall of legacy events.

Collection (ProductCollection): the power tool. SQL-level control: joins, conditions, field selection, pagination. The right tool for reading many records efficiently - and the wrong tool for saving (collections do not save; iterate repositories or use mass actions).

getList and SearchCriteria

Repository list methods take a SearchCriteriaInterface:

$criteria = $this->searchCriteriaBuilder
    ->addFilter('status', 1)
    ->addFilter('price', 50, 'gt')
    ->setPageSize(25)
    ->setCurrentPage(2)
    ->create();
$result = $this->productRepository->getList($criteria);

Filters, or groups, sorting and paging - the same shape the REST API exposes. For standard filtered lists, getList is the right altitude: portable, cache-friendly, API-consistent.

When Repositories Are Too Slow

Repositories load full entities: EAV joins, extension attribute resolution, event dispatch. Per-entity overhead is real:

  • Reading 5,000 products for a feed? Use a collection with addAttributeToSelect of exactly the fields needed - an order of magnitude faster
  • Updating one attribute on 10,000 products? updateAttributes mass action, not repository saves
  • Writing in a loop at import scale? You are in import-engine territory, not repository territory (see our import workflows post)

The rule: repositories for transactional, per-entity business logic; collections and mass actions for bulk reads and batch updates; the import engine for imports.

Data Models and Extension Attributes

Your own modules should follow the same shape: a data interface (Api/Data), a repository interface, a model/resource/collection implementing it. It looks ceremonial for a small module; it pays off the first time another system consumes your entity via the API for free.

Attaching custom data to core entities: extension attributes, registered in extension_attributes.xml and populated via plugins on the repository. Never join your table into core collections ad hoc in five places - extension attributes give the join one owner.

The One-Line Version

Repositories are for correctness, collections are for speed, models are for legacy. Know which you need before you write the constructor - switching altitudes later is a refactor, not a tweak.

Architecture Development Database