Usando DDD (Domain-Driven Design) con la Ayuda de Flow
Ejemplos de Prompts y Sus Resultados (Con Análisis)
Ejemplo de un Prompt Ineficiente
Prompt:
Dame un ejemplo de código Java para un sistema de feedback usando DDD.
El resultado podría ser algo como esto:
// FeedbackService.java
import org.springframework.stereotype.Service;
// ... otras importaciones
@Service
public class FeedbackService {
public void crearFeedback(String colaboradorEmail, String evaluadorEmail, String mensaje) {
// Lógica simple para la creación de feedback
System.out.println("Feedback creado para " + colaboradorEmail + " de " + evaluadorEmail + ": " + mensaje);
}
public void aprovarFeedback(Long feedbackId) {
// Lógica para la aprobación de feedback
System.out.println("Feedback " + feedbackId + " aprobado.");
}
}
// FeedbackRepository.java (Interfaz simple)
public interface FeedbackRepository {
// Métodos CRUD básicos
void save(Object feedback);
Object findById(Long id);
}
// Feedback.java (Clase simple)
public class Feedback {
private Long id;
private String colaboradorEmail;
private String evaluadorEmail;
private String mensaje;
// Getters y Setters
}
Deficiencias del Prompt
- Falta de Especificidad: El prompt es genérico ("sistema de feedback usando DDD"). No especifica qué parte del sistema, qué entidades o qué acciones de dominio son relevantes.
- Omite Conceptos Clave: No menciona los conceptos específicos de DDD que deberían aplicarse (Entity, Value Object, Repository, Domain Service).
- Sin Contexto de Negocio: No hay descripción de reglas de negocio, flujos o interacciones importantes del dominio.
Deficiencias del Código Generado
- DDD Superficial: El código no demuestra la aplicación real de los conceptos de DDD:
Feedback.javaparece ser una Entidad (Entity), pero no hay claridad en la distinción de su identidad o métodos de comportamiento.FeedbackRepositoryes una interfaz genérica (Object feedback) y no específica para un agregado o entidad de dominio.FeedbackServicees un Application Service (o incluso un Infrastructure Service disfrazado), no un Domain Service puro, ya que contiene lógica de "creación simple" y "aprobación" que deberían ser comportamientos más ricos en las entidades u orquestados por un Domain Service que interactúa con múltiples agregados. La lógica de negocio es anémica y no está encapsulada en las entidades.
- No Utiliza Value Objects: No hay indicios de Value Objects para encapsular tipos de datos como correos electrónicos, lo cual es una práctica común en DDD para garantizar inmutabilidad y validez.
- Acoplamiento: El
FeedbackServicemanipula directamente cadenas de texto e IDs, sin interactuar con Entidades ricas en comportamiento.
Ejemplo de un Prompt Eficiente
Prompt:
Considerando un sistema de feedback donde un colaborador solicita feedback de evaluadores externos, y un PDM aprueba esta solicitud. Modela en código Java los siguientes conceptos de DDD:
1. **Entity**: Un `FormularioDeFeedback` que posee un ID único, preguntas personalizadas y el estado (pendiente de aprobación, esperando respuestas, finalizado).
2. **Value Object**: Una `Pregunta` con texto y tipo, y un `AvaliadorExterno` con nombre y email, siendo ambos inmutables.
3. **Repository**: Un `FormularioDeFeedbackRepository` para persistir y buscar formularios por ID, y listar formularios pendientes de aprobación del PDM.
4. **Domain Service**: Un `ServicioDeAprobacionDeFormulario` que orquesta la lógica de negocio de aprobar o rechazar un `FormularioDeFeedback` por el PDM, alterando su estado y notificando a los evaluadores si es aprobado, o al colaborador si es rechazado.
Proporciona solo los esqueletos de las clases e interfaces, sin lógica de persistencia de base de datos detallada en los repositorios, enfocándote en la estructura del dominio.
Resultado de este prompt:
// src/main/java/com/example/domain/model/FormularioDeFeedback.java
package com.example.domain.model;
import com.example.domain.service.ServicioDeAprobacionDeFormulario;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
public class FormularioDeFeedback {
private UUID id;
private String colaboradorId; // ID del Colaborador que solicitó
private List<Pregunta> preguntas;
private List<AvaliadorExterno> avaliadores;
private StatusFormulario status;
private LocalDateTime dataCriacao;
private LocalDateTime dataAprovacao; // Para controlar el plazo de respuesta
public enum StatusFormulario {
EM_CRIACAO, PENDENTE_APROVACAO, AGUARDANDO_RESPOSTAS, FINALIZADO, REJEITADO
}
// Constructor
public FormularioDeFeedback(String colaboradorId, List<Pregunta> preguntas, List<AvaliadorExterno> avaliadores) {
this.id = UUID.randomUUID();
this.colaboradorId = colaboradorId;
this.perguntas = preguntas != null ? List.copyOf(perguntas) : Collections.emptyList();
this.avaliadores = avaliadores != null ? List.copyOf(avaliadores) : Collections.emptyList();
this.status = StatusFormulario.EM_CRIACAO;
this.dataCriacao = LocalDateTime.now();
}
// Métodos de comportamiento de la Entidad
public void enviarParaAprovacao() {
if (this.status == StatusFormulario.EM_CRIACAO || this.status == StatusFormulario.REJEITADO) {
this.status = StatusFormulario.PENDENTE_APROVACAO;
// La notificación al PDM será orquestada por el servicio de aplicación o de dominio
} else {
throw new IllegalStateException("El formulario no puede ser enviado para aprobación en el estado actual: " + this.status);
}
}
public void aprovar() {
if (this.status == StatusFormulario.PENDENTE_APROVACAO) {
this.status = StatusFormulario.AGUARDANDO_RESPOSTAS;
this.dataAprovacao = LocalDateTime.now();
} else {
throw new IllegalStateException("El formulario no puede ser aprobado en el estado actual: " + this.status);
}
}
public void rejeitar(String motivo) {
if (this.status == StatusFormulario.PENDENTE_APROVACAO) {
this.status = StatusFormulario.REJEITADO;
// El motivo se puede almacenar en algún lugar si es necesario
} else {
throw new IllegalStateException("El formulario no puede ser rechazado en el estado actual: " + this.status);
}
}
public void finalizar() {
if (this.status == StatusFormulario.AGUARDANDO_RESPOSTAS) {
this.status = StatusFormulario.FINALIZADO;
} else {
throw new IllegalStateException("El formulario no puede ser finalizado en el estado actual: " + this.status);
}
}
// Getters
public UUID getId() { return id; }
public String getColaboradorId() { return colaboradorId; }
public List<Pregunta> getPerguntas() { return perguntas; }
public List<AvaliadorExterno> getAvaliadores() { return avaliadores; }
public StatusFormulario getStatus() { return status; }
public LocalDateTime getDataCriacao() { return dataCriacao; }
public LocalDateTime getDataAprovacao() { return dataAprovacao; }
}
// src/main/java/com/example/domain/model/Pregunta.java
package com.example.domain.model;
import java.util.Objects;
public class Pregunta {
private final String texto;
private final TipoPergunta tipo; // Ej: ABIERTA, SELECCIÓN_MÚLTIPLE
public enum TipoPergunta {
ABERTA, SELECCION_MULTIPLE, SI_NO
}
public Pregunta(String texto, TipoPergunta tipo) {
if (texto == null || texto.trim().isEmpty()) {
throw new IllegalArgumentException("El texto de la pregunta no puede estar vacío.");
}
this.texto = texto;
this.tipo = tipo;
}
public String getTexto() { return texto; }
public TipoPergunta getTipo() { return tipo; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pregunta pergunta = (Pregunta) o;
return Objects.equals(texto, pergunta.texto) && tipo == pergunta.tipo;
}
@Override
public int hashCode() {
return Objects.hash(texto, tipo);
}
}
// src/main/java/com/example/domain/model/AvaliadorExterno.java
package com.example.domain.model;
import java.util.Objects;
import java.util.regex.Pattern;
public class AvaliadorExterno {
private final String nome;
private final String email; // Podría ser un Value Object de tipo Email
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
public AvaliadorExterno(String nome, String email) {
if (nome == null || nome.trim().isEmpty()) {
throw new IllegalArgumentException("El nombre del evaluador no puede estar vacío.");
}
if (email == null || !EMAIL_PATTERN.matcher(email).matches()) {
throw new IllegalArgumentException("Email del evaluador inválido.");
}
// Ejemplo de regla de negocio: no se permiten correos electrónicos internos
if (email.contains("@ciandt.com.br")) {
throw new IllegalArgumentException("Los evaluadores externos no pueden tener un e-mail de CI&T.");
}
this.nome = nome;
this.email = email;
}
public String getNome() { return nome; }
public String getEmail() { return email; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AvaliadorExterno that = (AvaliadorExterno) o;
return Objects.equals(nome, that.nome) && Objects.equals(email, that.email);
}
@Override
public int hashCode() {
return Objects.hash(nome, email);
}
}
// src/main/java/com/example/domain/repository/FormularioDeFeedbackRepository.java
package com.example.domain.repository;
import com.example.domain.model.FormularioDeFeedback;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface FormularioDeFeedbackRepository {
void save(FormularioDeFeedback formulario);
Optional<FormularioDeFeedback> findById(UUID id);
List<FormularioDeFeedback> findPendentesParaAprovacaoDoPdm(String pdmId);
// Otros métodos de búsqueda según las necesidades del dominio
}
// src/main/java/com/example/domain/service/ServicioDeAprobacionDeFormulario.java
package com.example.domain.service;
import com.example.domain.model.AvaliadorExterno;
import com.example.domain.model.FormularioDeFeedback;
import com.example.domain.repository.FormularioDeFeedbackRepository;
import java.util.List;
import java.util.UUID;
public class ServicioDeAprobacionDeFormulario {
private final FormularioDeFeedbackRepository formularioRepository;
// Lo ideal sería inyectar un servicio de notificación aquí
// private final NotificacaoService notificacaoService;
public ServicioDeAprobacionDeFormulario(FormularioDeFeedbackRepository formularioRepository /*, NotificacaoService notificacaoService*/) {
this.formularioRepository = formularioRepository;
// this.notificacaoService = notificacaoService;
}
public void aprovarFormulario(UUID formularioId, String pdmId) {
FormularioDeFeedback formulario = formularioRepository.findById(formularioId)
.orElseThrow(() -> new IllegalArgumentException("Formulario no encontrado."));
// Regla de negocio: solo el PDM responsable puede aprobar
// if (!formulario.getPdmId().equals(pdmId)) {
// throw new SecurityException("PDM no autorizado para aprobar este formulario.");
// }
formulario.aprovar();
formularioRepository.save(formulario); // Persiste el cambio de estado
// Notificar a los evaluadores
// for (AvaliadorExterno avaliador : formulario.getAvaliadores()) {
// notificacaoService.enviarEmailAvaliador(avaliador.getEmail(), formulario.getId());
// }
System.out.println("Formulario " + formularioId + " aprobado. Evaluadores notificados.");
}
public void rejeitarFormulario(UUID formularioId, String pdmId, String motivo) {
FormularioDeFeedback formulario = formularioRepository.findById(formularioId)
.orElseThrow(() -> new IllegalArgumentException("Formulario no encontrado."));
// Regla de negocio: solo el PDM responsable puede rechazar
// if (!formulario.getPdmId().equals(pdmId)) {
// throw new SecurityException("PDM no autorizado para rechazar este formulario.");
// }
formulario.rejeitar(motivo);
formularioRepository.save(formulario); // Persiste el cambio de estado
// Notificar al colaborador
// notificacaoService.enviarEmailColaborador(formulario.getColaboradorId(), "rechazado", motivo);
System.out.println("Formulario " + formularioId + " rechazado. Colaborador notificado.");
}
}
Eficiencia del Prompt
- Claridad y Contexto de Dominio: El prompt comienza con una descripción clara del escenario de negocio ("sistema de feedback donde un colaborador solicita feedback... y un PDM aprueba"). Esto establece el lenguaje ubicuo necesario para DDD.
- Especificidad de los Conceptos DDD: Solicita explícitamente el modelado de Entity, Value Object, Repository y Domain Service, garantizando que el foco de la respuesta sea la aplicación de estos patrones.
- Definición Detallada de Cada Concepto: Para cada concepto, se proporcionan características y ejemplos específicos (
FormularioDeFeedbackcon ID único y estado para la Entidad,PreguntayAvaliadorExternoinmutables para el Value Object, métodos específicos paraFormularioDeFeedbackRepository, y la orquestación de lógica en elServicioDeAprobacionDeFormulario). Esto dirige a la IA para crear clases con el comportamiento y la estructura correctos. - Restricciones y Exclusiones: La instrucción de proporcionar "solo los esqueletos de las clases e interfaces, sin lógica de persistencia de base de datos detallada en los repositorios" es crucial. Esto evita que la IA agregue detalles de infraestructura innecesarios y mantiene el enfoque en el dominio.
- Comportamiento Enfatizado: Al describir el
ServicioDeAprobacionDeFormulario, el prompt detalla las acciones que debe orquestar ("aprobar o rechazar, alterando el estado y notificando"), incentivando la creación de métodos con lógica de dominio.
Calidad del Código Generado
- Entidad Rica: La clase
FormularioDeFeedbackes una Entidad (Entity) bien definida con unUUIDcomo identificador, y métodos de comportamiento (enviarParaAprovacao,aprovar,rejeitar,finalizar) que modifican su propio estado (status), demostrando la encapsulación de la lógica de negocio. - Value Objects Inmutables:
PreguntayAvaliadorExternose modelan como Value Objects con todos los camposfinal, constructores que realizan validaciones e implementanequals()yhashCode(), garantizando su inmutabilidad y su igualdad basada en el valor. La validación del correo electrónico y la regla de negocio para los correos de CI&T dentro deAvaliadorExternodemuestran un Value Object rico. - Repositorio Abstracto:
FormularioDeFeedbackRepositoryes una interfaz que define operaciones de persistencia y recuperación específicas paraFormularioDeFeedback, sin exponer detalles de implementación de la base de datos, lo que se alinea con el patrón Repository. - Servicio de Dominio Orquestador:
ServicioDeAprobacionDeFormularioactúa como un Domain Service legítimo. Inyecta elFormularioDeFeedbackRepositorypara obtener la Entidad, llama a métodos de comportamiento en la propia Entidad (formulario.aprovar(),formulario.rejeitar()) y orquesta la notificación externa, sin contener la lógica de negocio intrínseca al formulario o al evaluador. Gestiona el flujo de aprobación que involucra a varias entidades. - Estructura de Paquetes: El código sugiere una buena organización de paquetes (
domain.model,domain.repository,domain.service), lo cual es fundamental en proyectos DDD.
Conclusión
Este ejemplo demuestra que para obtener un código alineado con los principios de Domain-Driven Design de una IA, la precisión y la riqueza de detalles en el prompt son indispensables. Al especificar claramente el contexto del dominio, los conceptos de DDD a aplicar (Entity, Value Object, Repository, Domain Service), e incluso las reglas de negocio y comportamientos esperados dentro de cada concepto, la IA es capaz de generar un modelo de dominio que realmente refleja la filosofía de DDD.
La principal deficiencia en los prompts genéricos reside en la incapacidad de la IA para inferir la intencionalidad del dominio y la separación de responsabilidades que son características de DDD. Un prompt bien elaborado actúa como un "diseñador" para la IA, guiándola para construir un modelo que encapsula la lógica de negocio en las entidades correctas, usa objetos de valor para mayor claridad e inmutabilidad, abstrae la persistencia y orquesta operaciones complejas a través de servicios de dominio, resultando en un diseño de software mucho más robusto y comprensible.