REF-102-01: Persistence & WordPress Adapter Spec
ADR: ADR-102 — Architecture FoundationEscopo: CPT slugs, wp_posts mapping, repository pattern implementation, WordPress adapter sub-namespaces, dependency direction matrix, naming conventions, PSR-4
1. Persistence — wp_posts + Custom Post Types
All domain entities are stored as WordPress Custom Post Types in wp_posts + wp_postmeta (EAV pattern). CPTs use the slug convention middag_{domain} and are registered with 'show_ui' => false (admin UI provided by Inertia.js, not the WordPress editor).
wp_posts field mapping:
| wp_posts Column | Domain Meaning |
|---|---|
post_title | Display name of the entity |
post_status | Entity status (publish=active, draft=inactive) |
post_author | Owner (WP user_id) |
post_date | Creation date |
post_modified | Last modification date |
post_parent | Parent entity (for hierarchies) |
| All other fields | Stored in wp_postmeta |
Registered CPTs:
| Domain | post_type slug |
|---|---|
| Organization | middag_organization |
| Collaborator | middag_collaborator |
| Invoice | middag_invoice |
| TaxInvoice | middag_taxinvoice |
| Quote | middag_quote |
| Contract | middag_contract |
| Document | middag_document |
| Entitlement | middag_entitlement |
| Environment | middag_environment |
| Service | middag_service |
| ServiceRequest | middag_service_request |
| License | middag_license |
Performance optimizations:
update_post_meta_cache()for batch meta loading in list viewsregister_meta()withshow_in_restfor REST API exposure- Indexed
meta_querykeys for high-traffic queries - WordPress object cache (
wp_cache_get/wp_cache_set) for hot entities - Pattern: single
meta_query+post__ininstead of multiple JOINs
2. Repository Pattern — Implementation
Domain code defines repository interfaces. WordPress adapter layer provides concrete implementations using QueryBuilder and MetaRepository. The DI Container binds interface to implementation.
// Domain/Organization/OrganizationRepository.php (INTERFACE)
namespace Middag\Account\Domain\Organization;
interface OrganizationRepository {
public function findById(int $id): ?OrganizationEntity;
public function findByUserId(int $userId): array;
public function save(OrganizationEntity $entity): int;
public function delete(int $id): bool;
}
// WordPress/Repository/WpOrganizationRepository.php (IMPLEMENTATION)
namespace Middag\Account\WordPress\Repository;
use Middag\Account\Domain\Organization\OrganizationRepository;
use Middag\Account\Domain\Organization\OrganizationEntity;
class WpOrganizationRepository implements OrganizationRepository {
public function __construct(
private QueryBuilder $query,
private MetaRepository $meta,
) {}
public function findById(int $id): ?OrganizationEntity {
$post = $this->query->postType('middag_organization')->find($id);
if (!$post) return null;
return OrganizationEntity::fromPost($post, $this->meta->getAllForPost($id));
}
}No domain code calls WP_Query, get_post_meta(), or any WordPress function directly.
3. WordPress Adapter Sub-Namespaces
Beyond the 6 core adapters, the WordPress abstraction layer includes additional sub-namespaces:
| Sub-Namespace | Key Classes | Responsibility |
|---|---|---|
WordPress\PostType\ | PostTypeRegistrar | Registers all CPTs on init hook, centralised |
WordPress\Database\ | QueryBuilder, MetaRepository, MigrationService | Fluent WP_Query wrapper, batch meta ops, CCT migration |
WordPress\Hook\ | HookRegistrar, HookInterface, domain-specific *Hooks | Declarative hook registration, auto-discovery of hook classes |
WordPress\Rest\ | RouteRegistrar, RestResponse | REST route registration, standardised JSON responses |
WordPress\Cron\ | CronRegistrar, CronHandler | Cron scheduling, callback dispatch to domain services |
WordPress\Email\ | EmailSender, EmailTemplate | Transactional email via wp_mail(), template resolution |
WordPress\Admin\ | AdminRegistrar, InertiaAdapter | WP Admin menu/assets, Inertia.js bridge for React admin UI |
WordPress\WooCommerce\ | OrderAdapter, SubscriptionAdapter, GatewayAdapter, ProductAdapter | WC order/subscription/product abstraction for domain use |
WordPress\User\ | UserRepository, UserMeta | WP_User queries, user meta operations |
WordPress\Repository\ | Wp{Domain}Repository for each domain | Concrete implementations of domain repository interfaces |
What this layer is NOT:
- Not a framework — no extension system, no plugin architecture, no custom hooks
- Not a full ORM — QueryBuilder is a thin WP_Query wrapper, not Eloquent or Doctrine
- Not a WordPress replacement — uses WP APIs integrally, just encapsulates calls to isolate the domain
4. Dependency Direction Matrix
| Namespace | Can Access | Cannot Access |
|---|---|---|
Core/ | Everything (bootstrap) | — |
Domain/ | Only Core/ base classes | WordPress/, Api/, Integration/, UI/ |
Integration/ | Core/, Domain/ (entities/DTOs) | WordPress/, Api/, UI/ |
Api/ | Core/, Domain/, Integration/ | WordPress/, UI/ |
WordPress/ | Core/, Domain/, Integration/ | UI/ |
UI/ | Core/, Domain/, WordPress/ | Api/, Integration/ |
Any violation of these rules is an architectural defect that must be fixed before merge.
5. Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Domains | PascalCase singular | Organization, Invoice |
| Services | {Domain}Service.php | OrganizationService.php |
| Repositories | {Domain}Repository.php | OrganizationRepository.php |
| Entities | {Domain}Entity.php | OrganizationEntity.php |
| API Controllers | {Domain}Controller.php | OrderController.php |
| Integrations | {Provider}Client.php | StripeClient.php |
| WP Abstractions | {Context}{Function}.php | PostTypeRegistrar.php |
| Middleware | {Role}Middleware.php | AuthMiddleware.php |
| Page Controllers | {Domain}PageController.php | QuotePageController.php |
6. PSR-4 Namespace
| Projeto | Namespace | PSR-4 Root |
|---|---|---|
| Plugin middag-account | Middag\Account\ | src/ |