home / skills / emvnuel / skill.md / ddd-da-massa

ddd-da-massa skill

/ddd-da-massa

This skill helps you apply Domain-Driven Design patterns to Jakarta EE apps, balancing cognitive load across controllers, services, entities, and repositories.

npx playbooks add skill emvnuel/skill.md --skill ddd-da-massa

Review the files below or copy the command above to add this skill to your agents.

Files (9)
SKILL.md
6.6 KB
---
name: ddd-da-massa
description: Practical DDD patterns for Jakarta EE web applications with cognitive load distribution. Use when designing controllers, entities, services, or evaluating cohesion and load balance.
---

# DDD da Massa for Jakarta EE

Practical Domain-Driven Design patterns for web applications, applying Cognitive Load Theory to create maintainable code. Based on Alberto Souza's "DDD da Massa".

## Philosophy

> Enable web application development that does what's necessary, has sufficiently flexible design, and where understanding most of the project requires low effort.

## Key Principles

1. **Leverage frameworks** — Don't ignore them, maximize their value
2. **Contextual load limits** — Different components have different limits
3. **Logical responsibility division** — Based on cognitive load, not feelings
4. **100% cohesive components** — Every attribute used by every method

## Contextual Cognitive Load Limits

Different parts of a web application have different complexity budgets:

| Component               | Max Points | Rationale                                 |
| ----------------------- | ---------- | ----------------------------------------- |
| **Controller/Resource** | 7          | Handles information flow, must be clear   |
| **Domain Service**      | 7          | Business flow should be easily understood |
| **Form/Request DTO**    | 9          | Transient state with transformation logic |
| **Entity**              | 9          | Persistent state with behavior            |
| **Repository**          | 3          | Framework does the heavy lifting          |
| **Infrastructure**      | ∞          | Rarely touched, OK to be complex          |
| **Configuration**       | ∞          | Template, rarely modified                 |

## Core Patterns

### 1. 100% Cohesive Controllers

Every method in a controller must use ALL injected dependencies.

```java
@Path("/orders/{orderId}/payments")
@RequestScoped
public class OrderPaymentResource {

    @Inject
    private OrderRepository orders;      // Used by all methods

    @Inject
    private PaymentGateway gateway;      // Used by all methods

    @Inject
    private Event<PaymentEvent> events;  // Used by all methods

    @POST
    @Transactional
    public Response processPayment(
            @PathParam("orderId") Long orderId,
            @Valid PaymentRequest request) {

        Order order = orders.findById(orderId)
            .orElseThrow(NotFoundException::new);

        Payment payment = request.toPayment(order);
        PaymentResult result = gateway.process(payment);

        events.fire(new PaymentEvent(order, result));

        return Response.ok(PaymentResponse.from(result)).build();
    }
}
// All 3 dependencies used ✓
```

### 2. Form Value Objects (Smart DTOs)

DTOs that know how to convert themselves to domain objects.

```java
public record CreateOrderRequest(
    @NotBlank String customerId,
    @NotEmpty List<@Valid OrderItemRequest> items,
    String notes
) {
    // Conversion logic lives HERE, not in a separate Mapper
    public Order toEntity(Customer customer) {
        return new Order(
            customer,
            items.stream().map(OrderItemRequest::toEntity).toList(),
            notes
        );
    }
}
```

### 3. Rich Entities

Entities hold business logic, not just data.

```java
@Entity
public class Bolao {

    private Instant expiresAt;

    @ElementCollection
    private Set<String> invitedEmails;

    // ✓ Logic on state lives in the entity
    public Participation accept(User participant) {
        if (expiresAt.isBefore(Instant.now())) {
            throw new InvitationExpiredException();
        }

        if (!invitedEmails.contains(participant.getEmail())) {
            throw new NotInvitedException(participant);
        }

        return new Participation(this, participant);
    }
}
```

### 4. Domain Service Controllers

When controller + service roles merge (for simple flows):

```java
@Path("/payments/pagseguro/{orderId}")
@RequestScoped
public class PagseguroPaymentCallbackResource {

    @Inject
    private OrderRepository orders;

    @Inject
    private PaymentRepository payments;

    @Inject
    private Event<NewPaymentEvent> paymentEvents;

    @POST
    @Transactional
    public void processCallback(
            @PathParam("orderId") Long orderId,
            @Valid PagseguroCallbackRequest request) {

        Order order = orders.findById(orderId)
            .orElseThrow(NotFoundException::new);

        Payment payment = request.toPayment(order);
        payments.save(payment);

        paymentEvents.fire(new NewPaymentEvent(payment));
    }
}
// Acts as both controller AND domain service
```

## Load Distribution Check

If entity has LOW load but calling code has HIGH load → bad distribution:

```java
// ❌ Entity too thin, controller too fat
@POST
public Response accept(@Valid AcceptRequest request) {
    Bolao bolao = bolaoRepo.findById(request.bolaoId()).get();

    // All this logic should be IN the entity
    if (bolao.getExpiresAt().isBefore(Instant.now())) {
        return Response.status(422).entity("Expired").build();
    }
    if (!bolao.getEmails().contains(request.email())) {
        return Response.status(422).entity("Not invited").build();
    }
    // ... more external logic
}

// ✓ Move logic to entity
@POST
public Response accept(@Valid AcceptRequest request) {
    Bolao bolao = bolaoRepo.findById(request.bolaoId()).get();
    User user = userRepo.findByEmail(request.email()).orElseThrow();

    Participation participation = bolao.accept(user);  // Entity has the logic
    participationRepo.save(participation);

    return Response.ok().build();
}
```

## When to Create New Classes

Only create new files when:

1. **Cognitive load exceeded** — Must distribute
2. **New domain concept** — Entity, Value Object
3. **Language feature** — Enum, sealed type

Do NOT create classes just because it "feels cleaner".

## Cookbook Index

### Controller Patterns

- [cohesive-resources](cookbook/cohesive-resources.md) - 100% cohesive JAX-RS resources
- [domain-service-controller](cookbook/domain-service-controller.md) - Merged controller/service pattern

### DTO Patterns

- [form-value-objects](cookbook/form-value-objects.md) - Smart DTOs with conversion
- [request-validation](cookbook/request-validation.md) - Validation in DTOs

### Domain Patterns

- [rich-entities](cookbook/rich-entities.md) - Entities with behavior
- [load-distribution](cookbook/load-distribution.md) - Balancing load across layers

### Service Patterns

- [cohesive-services](cookbook/cohesive-services.md) - 100% cohesive CDI services
- [splitting-load](cookbook/splitting-load.md) - When and how to split services

Overview

This skill provides practical Domain-Driven Design patterns for Jakarta EE web applications, applying cognitive load distribution to keep code maintainable and easy to understand. It focuses on designing controllers, entities, services, and DTOs so that each component stays within a clear complexity budget and responsibilities are logically divided.

How this skill works

The skill inspects component responsibilities and suggests refactors when cognitive load limits are exceeded. It enforces 100% cohesion (every injected dependency used by every method), promotes smart DTOs that convert themselves, and pushes behavior into rich entities instead of bloated controllers. It also offers patterns for when controllers should act as lightweight domain services and when to split classes.

When to use it

  • Designing or reviewing JAX-RS controllers/resources to ensure clear responsibility and cohesion
  • Modeling entities and deciding whether behavior belongs in the entity or caller
  • Creating DTOs that handle validation and conversion to domain objects
  • Evaluating where to split services when a component exceeds its cognitive load budget
  • Refactoring fat controllers or thin entities to improve maintainability

Best practices

  • Keep controllers and domain services under a 7-point complexity budget; DTOs and entities can go up to 9
  • Make controllers 100% cohesive: every injected dependency must be used by every method
  • Implement smart Form/Request DTOs that convert to domain types instead of separate mappers
  • Place business logic inside entities (rich entities) so callers remain simple
  • Create new classes only when cognitive load is exceeded, a new domain concept exists, or a language feature requires it

Example use cases

  • A payment endpoint that stays readable by keeping repository, gateway, and events cohesive and used across methods
  • A create-order form DTO that validates input and converts itself to an Order entity with items
  • Refactoring an acceptance flow by moving validation checks into an entity.accept(user) method
  • Merging a simple callback controller with domain service responsibilities when the flow is straightforward
  • Spotting a controller with excessive branching and extracting behavior into the corresponding entity

FAQ

What if a repository needs specialized behavior?

Keep repositories small (3-point budget) and push domain logic into entities or domain services; use repositories only for persistence concerns.

When should a controller act as a domain service?

When the flow is simple, stateless, and involves a direct orchestration of repositories and events—merge roles to reduce indirection while respecting cohesion.