Skip to main content

DDD Layer Details

Domain Layer

The innermost layer — pure business logic with no external dependencies.

Entities

Entities have identity and lifecycle:

namespace WPECommerce\Core\Domain\Entities;

class Product
{
private int $id;
private string $title;
private Money $price;
private ?Money $salePrice;
private int $stockQuantity;

public function getTitle(): string { return $this->title; }
public function getPrice(): Money { return $this->price; }
public function isOnSale(): bool { return $this->salePrice !== null; }
public function getEffectivePrice(): Money {
return $this->isOnSale() ? $this->salePrice : $this->price;
}
}
caution

Always use getter methods to access entity properties. Never use direct property access or array syntax.

// Correct
$product->getTitle();
$product->getFeaturedImageId();

// Wrong — will cause errors
$product['title'];
$product->title; // Only 'title' field is public

Repository Interfaces

namespace WPECommerce\Core\Domain\Repositories;

interface ProductRepositoryInterface
{
public function find(int $id): ?Product;
public function findBySlug(string $slug): ?Product;
public function save(Product $product): bool; // Returns bool, not object
public function delete(int $id): bool;
}

Application Layer

Orchestrates use cases by coordinating domain objects:

namespace WPECommerce\Core\Application\Services;

class CartService
{
public function addItem(
int $productId,
?int $variantId, // variant BEFORE quantity!
int $quantity
): Cart {
// Business logic here
}
}
danger

CartService::addItem() signature: addItem($product_id, $variant_id, $quantity) — variant before quantity! This is a common source of bugs.

Infrastructure Layer

Implements repository interfaces with actual database operations:

namespace WPECommerce\Core\Infrastructure\Persistence;

class ProductRepository implements ProductRepositoryInterface
{
public function find(int $id): ?Product
{
// Uses ProductRepository::find($id), NOT findById()
global $wpdb;
$row = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}ec_products WHERE id = %d",
$id
));
return $row ? $this->hydrate($row) : null;
}

public function save(Product $product): bool
{
// Returns bool — ID is set internally on the entity
}
}

Presentation Layer

REST API controllers that handle HTTP and delegate to services:

namespace WPECommerce\Core\Presentation\Api;

class ProductApiController
{
public function __construct(
private ProductService $productService
) {}

public function register_routes(): void
{
register_rest_route('ec/v1', '/products', [...]);
}
}