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', [...]);
}
}