Pular para conteúdo

Usando o DDD (Domain-Driven Design) com a Ajuda do Flow

Exemplos de Prompts e Seus Resultados (Com Análise)

Exemplo de Prompt Ineficiente

Prompt:

Me dê um exemplo de código Java para um sistema de feedback usando DDD.

O resultado pode ser algo como:

// FeedbackService.java
import org.springframework.stereotype.Service;
// ... outras importações

@Service
public class FeedbackService {

    public void criarFeedback(String colaboradorEmail, String avaliadorEmail, String mensagem) {
        // Lógica de criação de feedback simples
        System.out.println("Feedback criado para " + colaboradorEmail + " de " + avaliadorEmail + ": " + mensagem);
    }

    public void aprovarFeedback(Long feedbackId) {
        // Lógica de aprovação de feedback
        System.out.println("Feedback " + feedbackId + " aprovado.");
    }
}

// FeedbackRepository.java (Interface simples)
public interface FeedbackRepository {
    // Métodos CRUD básicos
    void save(Object feedback);
    Object findById(Long id);
}

// Feedback.java (Classe simples)
public class Feedback {
    private Long id;
    private String colaboradorEmail;
    private String avaliadorEmail;
    private String mensagem;
    // Getters e Setters
}

Deficiências do Prompt

  1. Falta de Especificidade: O prompt é genérico ("sistema de feedback usando DDD"). Não especifica qual parte do sistema, quais entidades, quais ações de domínio são relevantes.
  2. Omite Conceitos Chave: Não menciona os conceitos específicos do DDD que deveriam ser aplicados (Entity, Value Object, Repository, Domain Service).
  3. Sem Contexto de Negócio: Não há descrição de regras de negócio, fluxos ou interações importantes do domínio.

Deficiências do Código Gerado

  1. DDD Superficial: O código não demonstra a aplicação real dos conceitos de DDD:
    • Feedback.java parece ser uma Entity, mas não há clareza na distinção de sua identidade ou métodos de comportamento.
    • FeedbackRepository é uma interface genérica (Object feedback) e não específica para um agregado ou entidade de domínio.
    • FeedbackService é um Application Service (ou até mesmo um Infrastructure Service disfarçado), não um Domain Service puro, pois contém lógica de "criação simples" e "aprovação" que deveriam ser comportamentos mais ricos nas entidades ou orquestradas por um Domain Service que interage com múltiplos agregados. A lógica de negócio está anêmica e não encapsulada nas entidades.
  2. Não Utiliza Value Objects: Não há indicação de Value Objects para encapsular tipos de dados como e-mails, o que é uma prática comum em DDD para garantir imutabilidade e validade.
  3. Acoplamento: A FeedbackService diretamente manipula strings e IDs, sem interagir com Entities ricas em comportamento.

Exemplo de Prompt Eficiente

Prompt:

Considerando um sistema de feedback onde um colaborador solicita feedback de avaliadores externos, e um PDM aprova essa solicitação. Modele em código Java os seguintes conceitos do DDD:

1.  **Entity**: Um `FormularioDeFeedback` que possui um ID único, perguntas personalizadas e o status (pendente aprovação, aguardando respostas, finalizado).
2.  **Value Object**: Uma `Pergunta` com texto e tipo, e um `AvaliadorExterno` com nome e email, sendo ambos imutáveis.
3.  **Repository**: Um `FormularioDeFeedbackRepository` para persistir e buscar formulários por ID, e listar formulários pendentes para aprovação do PDM.
4.  **Domain Service**: Um `ServicoDeAprovacaoDeFormulario` que orquestra a lógica de negócio de aprovar ou rejeitar um `FormularioDeFeedback` pelo PDM, alterando seu status e notificando os avaliadores se aprovado, ou o colaborador se rejeitado.

Forneça apenas os esqueletos das classes e interfaces, sem lógica de persistência de banco de dados detalhada nos repositórios, focando na estrutura do domínio.

Resultado desse prompt:

// src/main/java/com/example/domain/model/FormularioDeFeedback.java
package com.example.domain.model;

import com.example.domain.service.ServicoDeAprovacaoDeFormulario;

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 do Colaborador que solicitou
    private List<Pergunta> perguntas;
    private List<AvaliadorExterno> avaliadores;
    private StatusFormulario status;
    private LocalDateTime dataCriacao;
    private LocalDateTime dataAprovacao; // Para controle de prazo de resposta

    public enum StatusFormulario {
        EM_CRIACAO, PENDENTE_APROVACAO, AGUARDANDO_RESPOSTAS, FINALIZADO, REJEITADO
    }

    // Construtor
    public FormularioDeFeedback(String colaboradorId, List<Pergunta> perguntas, List<AvaliadorExterno> avaliadores) {
        this.id = UUID.randomUUID();
        this.colaboradorId = colaboradorId;
        this.perguntas = perguntas != 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 comportamento da Entity
    public void enviarParaAprovacao() {
        if (this.status == StatusFormulario.EM_CRIACAO || this.status == StatusFormulario.REJEITADO) {
            this.status = StatusFormulario.PENDENTE_APROVACAO;
            // Notificação ao PDM será orquestrada pelo serviço de aplicação ou domínio
        } else {
            throw new IllegalStateException("Formulário não pode ser enviado para aprovação no status atual: " + this.status);
        }
    }

    public void aprovar() {
        if (this.status == StatusFormulario.PENDENTE_APROVACAO) {
            this.status = StatusFormulario.AGUARDANDO_RESPOSTAS;
            this.dataAprovacao = LocalDateTime.now();
        } else {
            throw new IllegalStateException("Formulário não pode ser aprovado no status atual: " + this.status);
        }
    }

    public void rejeitar(String motivo) {
        if (this.status == StatusFormulario.PENDENTE_APROVACAO) {
            this.status = StatusFormulario.REJEITADO;
            // O motivo pode ser armazenado em algum lugar se necessário
        } else {
            throw new IllegalStateException("Formulário não pode ser rejeitado no status atual: " + this.status);
        }
    }

    public void finalizar() {
        if (this.status == StatusFormulario.AGUARDANDO_RESPOSTAS) {
            this.status = StatusFormulario.FINALIZADO;
        } else {
            throw new IllegalStateException("Formulário não pode ser finalizado no status atual: " + this.status);
        }
    }

    // Getters
    public UUID getId() { return id; }
    public String getColaboradorId() { return colaboradorId; }
    public List<Pergunta> 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/Pergunta.java
package com.example.domain.model;

import java.util.Objects;

public class Pergunta {
    private final String texto;
    private final TipoPergunta tipo; // Ex: ABERTA, MULTIPLA_ESCOLHA

    public enum TipoPergunta {
        ABERTA, MULTIPLA_ESCOLHA, SIM_NAO
    }

    public Pergunta(String texto, TipoPergunta tipo) {
        if (texto == null || texto.trim().isEmpty()) {
            throw new IllegalArgumentException("Texto da pergunta não pode ser vazio.");
        }
        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;
        Pergunta pergunta = (Pergunta) 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; // Pode ser um Value Object 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("Nome do avaliador não pode ser vazio.");
        }
        if (email == null || !EMAIL_PATTERN.matcher(email).matches()) {
            throw new IllegalArgumentException("Email do avaliador inválido.");
        }
        // Exemplo de regra de negócio: e-mails internos não são permitidos
        if (email.contains("@ciandt.com.br")) {
            throw new IllegalArgumentException("Avaliadores externos não podem ter e-mail 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);
    // Outros métodos de busca conforme necessidade do domínio
}

// src/main/java/com/example/domain/service/ServicoDeAprovacaoDeFormulario.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 ServicoDeAprovacaoDeFormulario {

    private final FormularioDeFeedbackRepository formularioRepository;
    // O ideal seria injetar um serviço de notificação aqui
    // private final NotificacaoService notificacaoService;

    public ServicoDeAprovacaoDeFormulario(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("Formulário não encontrado."));

        // Regra de negócio: apenas o PDM responsável pode aprovar
        // if (!formulario.getPdmId().equals(pdmId)) {
        //     throw new SecurityException("PDM não autorizado a aprovar este formulário.");
        // }

        formulario.aprovar();
        formularioRepository.save(formulario); // Persiste a mudança de status

        // Notificar avaliadores
        // for (AvaliadorExterno avaliador : formulario.getAvaliadores()) {
        //     notificacaoService.enviarEmailAvaliador(avaliador.getEmail(), formulario.getId());
        // }
        System.out.println("Formulário " + formularioId + " aprovado. Avaliadores notificados.");
    }

    public void rejeitarFormulario(UUID formularioId, String pdmId, String motivo) {
        FormularioDeFeedback formulario = formularioRepository.findById(formularioId)
                .orElseThrow(() -> new IllegalArgumentException("Formulário não encontrado."));

        // Regra de negócio: apenas o PDM responsável pode rejeitar
        // if (!formulario.getPdmId().equals(pdmId)) {
        //     throw new SecurityException("PDM não autorizado a rejeitar este formulário.");
        // }

        formulario.rejeitar(motivo);
        formularioRepository.save(formulario); // Persiste a mudança de status

        // Notificar colaborador
        // notificacaoService.enviarEmailColaborador(formulario.getColaboradorId(), "rejeitado", motivo);
        System.out.println("Formulário " + formularioId + " rejeitado. Colaborador notificado.");
    }
}

Eficiência do Prompt

  1. Clareza e Contexto de Domínio: O prompt inicia com uma descrição clara do cenário de negócio ("sistema de feedback onde um colaborador solicita feedback... e um PDM aprova"). Isso estabelece o contexto ubíquo necessário para o DDD.
  2. Especificidade dos Conceitos DDD: Ele solicita explicitamente a modelagem de Entity, Value Object, Repository e Domain Service, garantindo que o foco da resposta seja na aplicação desses padrões.
  3. Definição Detalhada de Cada Conceito: Para cada conceito, são fornecidas características e exemplos específicos (FormularioDeFeedback com ID único e status para Entity, Pergunta e AvaliadorExterno imutáveis para Value Object, métodos específicos para FormularioDeFeedbackRepository, e a orquestração de lógica no ServicoDeAprovacaoDeFormulario). Isso direciona a IA a criar classes com o comportamento e a estrutura corretos.
  4. Restrições e Exclusões: A instrução para fornecer "apenas os esqueletos das classes e interfaces, sem lógica de persistência de banco de dados detalhada nos repositórios" é crucial. Isso evita que a IA adicione detalhes de infraestrutura desnecessários e mantém o foco no domínio.
  5. Comportamento Enfatizado: Ao descrever o ServicoDeAprovacaoDeFormulario, o prompt detalha as ações que ele deve orquestrar ("aprovar ou rejeitar, alterando status e notificando"), incentivando a criação de métodos com lógica de domínio.

Qualidade do Código Gerado

  1. Entity Rica: A classe FormularioDeFeedback é uma Entity bem definida com um UUID como identificador, e métodos de comportamento (enviarParaAprovacao, aprovar, rejeitar, finalizar) que modificam seu próprio estado (status), demonstrando encapsulamento da lógica de negócio.
  2. Value Objects Imutáveis: Pergunta e AvaliadorExterno são modelados como Value Objects com todos os campos final, construtores que realizam validações e implementam equals() e hashCode(), garantindo sua imutabilidade e baseados em valor. A validação de e-mail e a regra de negócio para e-mails CI&T dentro de AvaliadorExterno demonstram um Value Object rico.
  3. Repository Abstrato: FormularioDeFeedbackRepository é uma interface que define operações de persistência e recuperação específicas para a FormularioDeFeedback, sem expor detalhes de implementação de banco de dados, alinhado com o padrão Repository.
  4. Domain Service Orquestrador: ServicoDeAprovacaoDeFormulario atua como um Domain Service legítimo. Ele injeta o FormularioDeFeedbackRepository para obter a Entity, chama métodos de comportamento na própria Entity (formulario.aprovar(), formulario.rejeitar()) e orquestra a notificação externa, sem conter a lógica de negócio intrínseca ao formulário ou avaliador. Ele gerencia o fluxo de aprovação que envolve várias entidades.
  5. Estrutura de Pacotes: O código sugere uma boa organização de pacotes (domain.model, domain.repository, domain.service), o que é fundamental em projetos DDD.

Conclusão

Este exemplo demonstra que para obter um código alinhado com os princípios do Domain-Driven Design de uma IA, a precisão e a riqueza de detalhes no prompt são indispensáveis. Ao especificar claramente o contexto do domínio, os conceitos do DDD a serem aplicados (Entity, Value Object, Repository, Domain Service), e até mesmo as regras de negócio e comportamentos esperados dentro de cada conceito, a IA é capaz de gerar um modelo de domínio que realmente reflete a filosofia do DDD.

A deficiência principal em prompts genéricos reside na incapacidade da IA de inferir a intencionalidade do domínio e a separação de responsabilidades que são características do DDD. Um prompt bem elaborado atua como um "designer" para a IA, guiando-a para construir um modelo que encapsula a lógica de negócio nas entidades certas, usa objetos de valor para clareza e imutabilidade, abstrai a persistência e orquestra operações complexas através de serviços de domínio, resultando em um design de software muito mais robusto e compreensível.