agentsclimarketplace

Domain driven design

Skill rrezartprebreza/spring-boot-skills/skills/spring-boot-3/domain-driven-design

Use when working with domain models, aggregates, value objects, domain events, or repositories in a DDD-style project. Ensures rich domain model over anemic CRUD.From its SKILL.md

Install
npx -y skills add rrezartprebreza/spring-boot-skills --skill domain-driven-design

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

6.4 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it

Domain-Driven Design

Aggregate Rules

  • One repository per aggregate root
  • External code only accesses aggregate through root — never child entities directly
  • Aggregates reference other aggregates by ID only, not direct object reference
  • Keep aggregates small — if it has more than 3-4 child entities, split it
// ✅ Aggregate root controls all access to children
order.addItem(productId, quantity); // through root
order.removeItem(itemId);           // through root

// ❌ Direct child access from outside
order.getItems().add(new OrderItem(...)); // bypasses invariants

Value Objects

Immutable, no identity, equality by value:

public record Money(BigDecimal amount, Currency currency) {
    public Money {
        if (amount.compareTo(BigDecimal.ZERO) < 0)
            throw new IllegalArgumentException("Amount cannot be negative");
        Objects.requireNonNull(currency);
    }

    public Money add(Money other) {
        if (!currency.equals(other.currency))
            throw new CurrencyMismatchException(currency, other.currency);
        return new Money(amount.add(other.amount), currency);
    }

    public static Money of(String amount, String currency) {
        return new Money(new BigDecimal(amount), Currency.getInstance(currency));
    }
}

public record EmailAddress(String value) {
    public EmailAddress {
        if (!value.matches("^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$"))
            throw new InvalidEmailException(value);
    }
}

Domain Events

// Event — immutable record
public record OrderPlaced(OrderId orderId, CustomerId customerId, Money total, Instant occurredAt) {
    public static OrderPlaced of(Order order) {
        return new OrderPlaced(order.getId(), order.getCustomerId(), order.getTotal(), Instant.now());
    }
}

// Collect events in aggregate, publish after save
@Entity
public class Order {
    @Transient
    private final List<Object> domainEvents = new ArrayList<>();

    public void place() {
        this.status = OrderStatus.PLACED;
        domainEvents.add(OrderPlaced.of(this));
    }

    public List<Object> pullDomainEvents() {
        var events = List.copyOf(domainEvents);
        domainEvents.clear();
        return events;
    }
}

// Publish after successful save
@Service
@RequiredArgsConstructor
public class OrderApplicationService {
    private final OrderRepository orderRepository;
    private final ApplicationEventPublisher eventPublisher;

    @Transactional
    public Order placeOrder(PlaceOrderCommand command) {
        Order order = orderRepository.findById(command.orderId()).orElseThrow();
        order.place();
        Order saved = orderRepository.save(order);
        saved.pullDomainEvents().forEach(eventPublisher::publishEvent); // publish after commit
        return saved;
    }
}

// Listen to events — bind to commit, not just publish.
// @EventListener fires synchronously inside the TX; if the TX later rolls back you've
// already sent the email. Prefer @TransactionalEventListener(AFTER_COMMIT) — see [[transactional-patterns]].
@Component
@RequiredArgsConstructor
public class OrderPlacedHandler {
    private final EmailService emailService;

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    @Async
    public void onOrderPlaced(OrderPlaced event) {
        emailService.sendOrderConfirmation(event.customerId(), event.orderId());
    }
}

Let Spring Data publish for you. Instead of calling pullDomainEvents() by hand, expose a @DomainEvents method (returns the collected events) and an @AfterDomainEventPublication method (clears them) on the aggregate root. Spring Data's repository drains and publishes them automatically on every save() — no manual wiring in the service.

Specifications (complex queries)

public class OrderSpecifications {
    public static Specification<Order> byStatus(OrderStatus status) {
        return (root, query, cb) -> cb.equal(root.get("status"), status);
    }

    public static Specification<Order> byCustomer(UUID customerId) {
        return (root, query, cb) -> cb.equal(root.get("customerId"), customerId);
    }

    public static Specification<Order> placedAfter(Instant date) {
        return (root, query, cb) -> cb.greaterThan(root.get("placedAt"), date);
    }
}

// Compose
Specification<Order> spec = OrderSpecifications.byStatus(PLACED)
    .and(OrderSpecifications.byCustomer(customerId))
    .and(OrderSpecifications.placedAfter(lastWeek));

orderRepository.findAll(spec, pageable);

Anti-Corruption Layer (ACL)

  • When integrating with external systems or legacy code, don't let their models leak into your domain
  • Create an ACL — a translation layer that converts external data to your domain language
  • ACL lives in infrastructure layer, not domain
// ✅ GOOD — ACL translates external payment API to domain concepts
@Component
@RequiredArgsConstructor
public class PaymentGatewayAdapter implements PaymentPort {

    private final ExternalPaymentClient client;  // third-party SDK

    @Override
    public PaymentConfirmation charge(OrderId orderId, Money amount) {
        // Translate domain → external
        PaymentApiRequest apiRequest = new PaymentApiRequest(
            orderId.value().toString(),
            amount.amount().doubleValue(),
            amount.currency().getCurrencyCode());

        // Call external system
        PaymentApiResponse apiResponse = client.charge(apiRequest);

        // Translate external → domain
        return new PaymentConfirmation(
            PaymentId.of(apiResponse.getTransactionId()),
            apiResponse.isSuccessful() ? PaymentStatus.CONFIRMED : PaymentStatus.DECLINED);
    }
}

Gotchas

  • Agent creates anemic models with only getters/setters — put behavior on domain objects
  • Agent uses Long for entity IDs — use typed value objects (OrderId, CustomerId)
  • Agent puts domain logic in services — services should orchestrate, not decide
  • Agent accesses child entities directly from outside — always go through aggregate root
  • Agent publishes events before saving — publish after successful save/commit
  • Agent lets external API models into domain — use an Anti-Corruption Layer to translate

What ships with it: 4 files

7.6 KB alongside SKILL.md

templates/

Gives 0 of the 12 instructions most design frontend skills give in ~1.3k tokens

Counted across 1,179 of the 2,086 authors here whose files we hold, read 2026-09-06

  • Commit to a bold aesthetic directionin 31 of 1179, across 24 files
  • Prefer component composition over inheritancein 28 of 1179, across 14 files
  • Animate only transform and opacity propertiesin 27 of 1179, across 22 files
  • Memoize expensive computations with useMemoin 26 of 1179, across 13 files
  • Use semantic HTML elementsin 24 of 1179, across 23 files
  • Virtualize long lists for performancein 21 of 1179, across 10 files
  • Use CSS variables for design tokensin 20 of 1179, across 14 files
  • Implement loading, empty, and error statesin 20 of 1179
  • Lazy load heavy components with Suspensein 19 of 1179, across 8 files
  • Respect prefers-reduced-motion media queriesin 18 of 1179, across 10 files
  • Prioritize CSS-only animations for HTMLin 18 of 1179, across 16 files
  • Use compound components for related UI elementsin 18 of 1179, across 7 files

Said here and by no other author read

  • Access aggregate children only through the root
  • Split aggregates with more than four child entities
  • Implement equality by value for value objects
  • Use typed value objects for entity IDs
  • Place domain logic on domain objects
  • Use an anti-corruption layer for external systems

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.