← Persistence

PERSISTENCE FEATURE

Repository Workflow

Business code should work with domain models, while ResourceModel and mapper logic stay inside the persistence layer.

Feature Guide

A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.

What this does

The canonical Semitexa repository workflow is domain-first: application code depends on repository contracts and works with domain models, not raw ORM resources.

How it works

Repository contracts return domain objects. ORM repository implementations sit behind the contract, convert through explicit mappers, and keep persistence models behind the boundary.

Why it matters

This keeps the demo honest about best practices. Resource-level CRUD still exists, but it is not what we should teach as the primary architectural path.

Key concepts

Repository contract
Application-facing interface that expresses persistence in domain language.
Domain-first read path
Default repository reads return business models instead of persistence resources.
DomainRepository
New ORM entry point that coordinates ResourceModel queries, mapping, and aggregate writes.

Canonical Path

Business code should not pass ORM resources around

In Semitexa, handlers and services depend on repository contracts and receive domain models back. Resource models stay behind the persistence boundary unless you deliberately opt into a resource-level read.

What the demo should teach

  • Handlers should ask for repository contracts, not ORM implementations.
  • The default read path should return domain/business models through the explicit ResourceModel -> mapper -> domain pipeline.
  • insert(domainModel) and update(domainModel) are the happy path; low-level ResourceModel reads are an explicit infrastructure concern.

Best practice

Application / domain path

Contract repository returns MachineCredential domain objects with business behavior intact.

MachineCredentialRepositoryInterface MachineCredential insert(domain) update(domain)

Infrastructure-only

Persistence path

ResourceModel and mapper classes still matter, but their job is to describe storage and convert data between persistence and domain.

MachineCredentialResourceModel MachineCredentialMapper DomainRepository mapping only
Step Canonical flow Why it stays clean
Read Handler -> repository contract -> fetchOne()/findById() -> domain model Business code gets behavior and invariants, not ORM metadata.
Mutate Domain object methods change state, e.g. revoke() or recordUsage() The business rule lives on the business object, not in a persistence DTO.
Persist Repository implementation converts domain -> resource model via explicit mapper and persists through the ORM core Storage mapping stays in the persistence layer where it belongs.
repository contract domain model ResourceModel mapper #[SatisfiesRepositoryContract]

Verified against Semitexa Ultimate 2026.09.19.1020

Repository Workflow

The canonical Semitexa persistence path keeps business code working with domain models and confines ResourceModel and mapper logic to the persistence layer.

How it works

Handlers receive repository dependencies through #[InjectAsReadonly], preferably via contract interfaces where the module defines them. The repository implementation performs the read via ResourceModel → mapper → domain model, and persists through insert(domainModel) or update(domainModel). Low-level ResourceModel reads remain available but are an explicit infrastructure concern.

Why this matters

When business code depends on repository contracts and domain models, the storage mapping stays reviewable and isolated. Swapping a persistence implementation never forces changes in handlers or services, and the business rules stay on the business objects where they belong.

© Linus Torvalds: "Talk is cheap. Show me the code."

Repository Contract Persistence adapter
<?phpdeclare(strict_types=1);namespace App\Domain\Auth;interface MachineCredentialRepositoryInterface{    public function findById(string $id): ?MachineCredential;}

Low-Level Escape Hatch

When resource-level reads are acceptable

ResourceModel-level persistence is a capability, not the headline practice that the demo should teach first.

If a screen is demonstrating application architecture, show repository contracts and domain models, not direct ResourceModel mutation.

Mapper and ResourceModel code should stay clearly visible, but behind the repository boundary.

If a feature is mostly about domain behavior, the source tabs should foreground the domain model and contract before the persistence model.

How it works

Repository contracts return domain objects. ORM repository implementations sit behind the contract, convert through explicit mappers, and keep persistence models behind the boundary.

Why it matters

This keeps the demo honest about best practices. Resource-level CRUD still exists, but it is not what we should teach as the primary architectural path.

Key concepts

Repository contract
Application-facing interface that expresses persistence in domain language.
Domain-first read path
Default repository reads return business models instead of persistence resources.
DomainRepository
New ORM entry point that coordinates ResourceModel queries, mapping, and aggregate writes.

Support Semitexa
Built for developers who prefer control over magic. Your support helps keep it fast, open, and evolving.

Donate via PayPal