acc-check-aggregate-consistency
1
总安装量
1
周安装量
#43688
全站排名
安装命令
npx skills add https://github.com/dykyi-roman/awesome-claude-code --skill acc-check-aggregate-consistency
Agent 安装分布
opencode
1
claude-code
1
Skill 文档
Aggregate Consistency Audit
Analyze PHP code for DDD aggregate design compliance â ensuring proper boundaries, invariant enforcement, and transactional consistency.
Detection Patterns
1. Cross-Aggregate Transaction
// CRITICAL: Multiple aggregates modified in single transaction
class TransferUseCase
{
public function execute(TransferCommand $command): void
{
$this->entityManager->beginTransaction();
try {
$source = $this->accountRepo->find($command->sourceId());
$target = $this->accountRepo->find($command->targetId());
$source->debit($command->amount()); // Aggregate 1
$target->credit($command->amount()); // Aggregate 2 â violation!
$this->accountRepo->save($source);
$this->accountRepo->save($target);
$this->entityManager->commit();
} catch (\Throwable $e) {
$this->entityManager->rollback();
throw $e;
}
}
}
// CORRECT: One aggregate per transaction + eventual consistency
class DebitAccountUseCase
{
public function execute(DebitCommand $command): void
{
$account = $this->accountRepo->find($command->accountId());
$account->debit($command->amount());
$this->accountRepo->save($account);
// Domain event triggers CreditAccountUseCase asynchronously
}
}
2. Missing Aggregate Root Identity
// ANTIPATTERN: Child entity accessed directly (bypassing root)
class OrderItemRepository
{
public function findById(OrderItemId $id): OrderItem
{
// Direct access to child entity â violates aggregate boundary!
return $this->entityManager->find(OrderItem::class, $id);
}
}
// CORRECT: Access child through aggregate root
class OrderRepository
{
public function findById(OrderId $id): Order
{
return $this->entityManager->find(Order::class, $id);
}
}
// Then: $order->getItem($itemId);
3. Invariant Not Enforced by Root
// ANTIPATTERN: Business rule checked outside aggregate
class AddItemToOrderUseCase
{
public function execute(AddItemCommand $command): void
{
$order = $this->orderRepo->find($command->orderId());
$item = new OrderItem($command->productId(), $command->quantity());
// Business rule in UseCase instead of Aggregate!
if (count($order->items()) >= 50) {
throw new TooManyItemsException();
}
$order->items()->add($item); // Direct collection manipulation
$this->orderRepo->save($order);
}
}
// CORRECT: Aggregate root enforces invariants
final class Order
{
private const int MAX_ITEMS = 50;
public function addItem(ProductId $productId, Quantity $quantity): void
{
if (count($this->items) >= self::MAX_ITEMS) {
throw new TooManyItemsException(self::MAX_ITEMS);
}
if ($this->hasProduct($productId)) {
throw new DuplicateProductException($productId);
}
$this->items[] = new OrderItem($this->id, $productId, $quantity);
$this->recordEvent(new ItemAddedToOrder($this->id, $productId));
}
}
4. Large Aggregate (God Aggregate)
// ANTIPATTERN: Aggregate with too many children
final class User
{
private Collection $orders; // Large collection
private Collection $notifications; // Large collection
private Collection $activityLog; // Unbounded collection
private Collection $preferences;
private Collection $addresses;
private Collection $paymentMethods;
// Loading this aggregate loads ALL related data!
}
// CORRECT: Separate into smaller aggregates
final class User { /* core identity, profile */ }
final class UserOrders { /* reference User by ID, not object */ }
final class UserActivity { /* separate aggregate */ }
5. Public Setters on Aggregate
// ANTIPATTERN: Public setters bypass invariants
final class Order
{
public function setStatus(OrderStatus $status): void
{
$this->status = $status; // No validation! Can go from SHIPPED â DRAFT
}
public function setTotal(Money $total): void
{
$this->total = $total; // External code can set wrong total
}
}
// CORRECT: Named methods enforcing state transitions
final class Order
{
public function confirm(): void
{
if ($this->status !== OrderStatus::DRAFT) {
throw new InvalidOrderTransitionException($this->status, OrderStatus::CONFIRMED);
}
if ($this->items->isEmpty()) {
throw new EmptyOrderException();
}
$this->status = OrderStatus::CONFIRMED;
$this->confirmedAt = new \DateTimeImmutable();
$this->recordEvent(new OrderConfirmed($this->id));
}
}
6. Reference by Object Instead of ID
// ANTIPATTERN: Direct object reference between aggregates
final class Order
{
private User $user; // Object reference â tight coupling
private Product $product; // Object reference â loads entire aggregate
}
// CORRECT: Reference by identity
final class Order
{
private UserId $userId; // ID reference â loose coupling
private ProductId $productId; // ID reference â load only when needed
}
Grep Patterns
# Cross-aggregate transaction
Grep: "beginTransaction|->flush\(\)" --glob "**/UseCase/**/*.php"
Grep: "->save\(.*\n.*->save\(" --glob "**/UseCase/**/*.php"
# Direct child entity repository
Grep: "interface.*Item.*Repository|interface.*Line.*Repository" --glob "**/Domain/**/*.php"
# Public setters on aggregates
Grep: "public function set[A-Z]" --glob "**/Domain/**/*Entity*.php"
Grep: "public function set[A-Z]" --glob "**/Domain/**/*Aggregate*.php"
# Large collections in entity
Grep: "private.*Collection.*\$|OneToMany|ManyToMany" --glob "**/Domain/**/*.php"
# Object reference between aggregates
Grep: "private.*[A-Z][a-z]+Entity \$|private.*[A-Z][a-z]+Aggregate \$" --glob "**/Domain/**/*.php"
# Invariants outside aggregate
Grep: "count\(.*->items\(\)\)|->getTotal\(\).*>|->getStatus\(\).*===" --glob "**/UseCase/**/*.php"
Severity Classification
| Pattern | Severity |
|---|---|
| Cross-aggregate transaction | ð´ Critical |
| Direct child entity access | ð´ Critical |
| Invariant outside aggregate | ð Major |
| Public setters on aggregate | ð Major |
| Large aggregate (god object) | ð Major |
| Object reference between aggregates | ð¡ Minor |
Output Format
### Aggregate Consistency: [Description]
**Severity:** ð´/ð /ð¡
**Location:** `file.php:line`
**Aggregate:** [Aggregate Root name]
**DDD Rule Violated:**
[Which aggregate design rule is broken]
**Issue:**
[Description of the consistency violation]
**Code:**
```php
// Violating code
Fix:
// Compliant with aggregate rules