Over the past few months, I have been fully dedicated to developing a software platform for ESG (Environmental, Social, Governance) rating, dealing with all the architectural and domain complexities that come with it.
At the same time, I experienced the greatest joy of my life: the birth of my daughter. I consciously decided to pause the writing of this blog to fully enjoy her first months.

Now, with great enthusiasm, I am resuming sharing thoughts and experiences in software design.

Today’s topic is one of the core pillars of my architecture for this project: Use Cases in Hexagonal Architecture, a pattern that guarantees order, consistency, and maximum code evolvability.

What are Use Cases?

In Hexagonal Architecture, a Use Case is:

A primary port interface that exposes a business action, orchestrates the application logic, and defines application policies without depending on external technologies or frameworks.

This approach provides:

  • Clear separation between API and logic
  • Excellent testability (interfaces are easy to mock)
  • Total independence from frameworks or infrastructure

Use Case + Command: the style I prefer

In my recent projects, I have adopted and refined a very practical pattern: Command as an inner class inside the same Use Case interface.
This keeps the contract and the Command DTO in a single, cohesive location, improving readability and cohesion.

public interface SubmitOrderUseCase {

OrderResult handle(SubmitOrderCommand command);

@Value  
class SubmitOrderCommand {

    @NotNull  
    UUID customerId;

    @NotEmpty  
    List<@Valid OrderItemDto> items;

    public SubmitOrderCommand(UUID customerId, List<OrderItemDto> items) {
        this.customerId = customerId;
        this.items = items;
        ValidatorUtil.validate(this); // immediate validation
    }

    public CustomerId toCustomerId() {
        return new CustomerId(customerId);
    }

    public List<OrderItem> toOrderItems() {
        return items.stream()
                    .map(OrderItemDto::toDomain)
                    .toList();
    }
}


Validation at construction time

The core of this pattern is ValidatorUtil, a simple utility that centralises Jakarta Validation when the Command is instantiated.

public final class ValidatorUtil {

  private static final Validator validator =  
      Validation.buildDefaultValidatorFactory().getValidator();
  
  private ValidatorUtil() {}
  
  public static <T> void validate(T object) {
      Set<ConstraintViolation<T>> violations = validator.validate(object);
      if (!violations.isEmpty()) {
          throw new ConstraintViolationException(violations);
      }
}

Use Case implementation

The true strength of the pattern lies in the implementation: the Use Case works exclusively with objects that are already valid and consistent.

@Service
@RequiredArgsConstructor
public class SubmitOrderService implements SubmitOrderUseCase {

  private final OrderPort orderPort;
  
  @Override
  public Order handle(SubmitOrderCommand command) {
      var order = Order.create(command.toCustomerId(), command.toOrderItems());
      return orderPort.save(order);
  }
  
}

Benefits of this approach

  • Maximum cohesion: all data and validation logic are encapsulated inside the Command
  • Purity: the Use Case only orchestrates, without any additional control logic
  • Single Responsibility Principle (SRP): the Use Case has exactly one responsibility, coordinating the business logic of a specific scenario, without handling validation or data transformation
  • Testability: Commands and Use Cases can be tested independently
  • Maintainability: changes to business rules or validation only affect the Command
  • Clean architecture: no framework dependencies within the core components

Conclusion

Use Cases in Hexagonal Architecture, combined with the self-validating Command pattern at construction time, represent for me the best possible synthesis of:

  • architectural rigor
  • code clarity
  • operational simplicity

This approach has proven to be extremely robust and reliable under the complexity of the ESG project I worked on, confirming once again its effectiveness in building evolvable and maintainable enterprise systems.

Summary flow

Client ➔ SubmitOrderCommand (already validated) ➔ SubmitOrderUseCase ➔ Domain ➔ Repository ➔ Result

Simplicity, safety, maintainability.
Three fundamental values that every modern enterprise system should aim for.

Comments are closed