Skip to content

Using DDD (Domain-Driven Design) with Help from Flow

Examples of Prompts and Their Results (With Analysis)

Example of an Inefficient Prompt

Prompt:

Give me an example of Java code for a feedback system using DDD.

The result might be something like this:

// FeedbackService.java
import org.springframework.stereotype.Service;
// ... other imports

@Service
public class FeedbackService {

    public void createFeedback(String collaboratorEmail, String evaluatorEmail, String message) {
        // Simple feedback creation logic
        System.out.println("Feedback created for " + collaboratorEmail + " from " + evaluatorEmail + ": " + message);
    }

    public void approveFeedback(Long feedbackId) {
        // Feedback approval logic
        System.out.println("Feedback " + feedbackId + " approved.");
    }
}

// FeedbackRepository.java (Simple interface)
public interface FeedbackRepository {
    // Basic CRUD methods
    void save(Object feedback);
    Object findById(Long id);
}

// Feedback.java (Simple class)
public class Feedback {
    private Long id;
    private String collaboratorEmail;
    private String evaluatorEmail;
    private String message;
    // Getters and Setters
}

Shortcomings of the Prompt

  1. Lack of Specificity: The prompt is generic ("feedback system using DDD"). It doesn't specify which part of the system, which entities, or which domain actions are relevant.
  2. Omits Key Concepts: It doesn't mention the specific DDD concepts that should be applied (Entity, Value Object, Repository, Domain Service).
  3. No Business Context: There is no description of business rules, workflows, or important domain interactions.

Shortcomings of the Generated Code

  1. Superficial DDD: The code doesn't demonstrate a real application of DDD concepts:
    • Feedback.java appears to be an Entity, but there's no clarity on its identity distinction or behavior methods.
    • FeedbackRepository is a generic interface (Object feedback) and not specific to a domain aggregate or entity.
    • FeedbackService is an Application Service (or even a disguised Infrastructure Service), not a pure Domain Service, as it contains "simple creation" and "approval" logic that should be richer behaviors within the entities or orchestrated by a Domain Service that interacts with multiple aggregates. The business logic is anemic and not encapsulated in the entities.
  2. Doesn't Use Value Objects: There is no indication of Value Objects to encapsulate data types like emails, which is a common practice in DDD to ensure immutability and validity.
  3. Coupling: The FeedbackService directly manipulates strings and IDs, without interacting with behavior-rich Entities.

Example of an Efficient Prompt

Prompt:

Considering a feedback system where a collaborator requests feedback from external evaluators, and a PDM approves this request. Model the following DDD concepts in Java code:

1.  **Entity**: A `FeedbackForm` that has a unique ID, custom questions, and a status (pending approval, awaiting responses, finalized).
2.  **Value Object**: A `Question` with text and type, and an `ExternalEvaluator` with a name and email, both being immutable.
3.  **Repository**: A `FeedbackFormRepository` to persist and retrieve forms by ID, and to list forms pending PDM approval.
4.  **Domain Service**: A `FormApprovalService` that orchestrates the business logic of approving or rejecting a `FeedbackForm` by the PDM, changing its status and notifying the evaluators if approved, or the collaborator if rejected.

Provide only the skeletons of the classes and interfaces, without detailed database persistence logic in the repositories, focusing on the domain structure.

Result of this prompt:

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

import com.example.domain.service.FormApprovalService;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

public class FeedbackForm {
    private UUID id;
    private String collaboratorId; // ID of the Collaborator who requested it
    private List<Question> questions;
    private List<ExternalEvaluator> evaluators;
    private FormStatus status;
    private LocalDateTime creationDate;
    private LocalDateTime approvalDate; // To control the response deadline

    public enum FormStatus {
        IN_CREATION, PENDING_APPROVAL, AWAITING_RESPONSES, FINALIZED, REJECTED
    }

    // Constructor
    public FeedbackForm(String collaboratorId, List<Question> questions, List<ExternalEvaluator> evaluators) {
        this.id = UUID.randomUUID();
        this.collaboratorId = collaboratorId;
        this.questions = questions != null ? List.copyOf(questions) : Collections.emptyList();
        this.evaluators = evaluators != null ? List.copyOf(evaluators) : Collections.emptyList();
        this.status = FormStatus.IN_CREATION;
        this.creationDate = LocalDateTime.now();
    }

    // Entity behavior methods
    public void sendForApproval() {
        if (this.status == FormStatus.IN_CREATION || this.status == FormStatus.REJECTED) {
            this.status = FormStatus.PENDING_APPROVAL;
            // Notification to the PDM will be orchestrated by the application or domain service
        } else {
            throw new IllegalStateException("Form cannot be sent for approval in its current status: " + this.status);
        }
    }

    public void approve() {
        if (this.status == FormStatus.PENDING_APPROVAL) {
            this.status = FormStatus.AWAITING_RESPONSES;
            this.approvalDate = LocalDateTime.now();
        } else {
            throw new IllegalStateException("Form cannot be approved in its current status: " + this.status);
        }
    }

    public void reject(String reason) {
        if (this.status == FormStatus.PENDING_APPROVAL) {
            this.status = FormStatus.REJECTED;
            // The reason can be stored somewhere if necessary
        } else {
            throw new IllegalStateException("Form cannot be rejected in its current status: " + this.status);
        }
    }

    public void finalizeForm() {
        if (this.status == FormStatus.AWAITING_RESPONSES) {
            this.status = FormStatus.FINALIZED;
        } else {
            throw new IllegalStateException("Form cannot be finalized in its current status: " + this.status);
        }
    }

    // Getters
    public UUID getId() { return id; }
    public String getCollaboratorId() { return collaboratorId; }
    public List<Question> getQuestions() { return questions; }
    public List<ExternalEvaluator> getEvaluators() { return evaluators; }
    public FormStatus getStatus() { return status; }
    public LocalDateTime getCreationDate() { return creationDate; }
    public LocalDateTime getApprovalDate() { return approvalDate; }
}

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

import java.util.Objects;

public class Question {
    private final String text;
    private final QuestionType type; // E.g., OPEN_ENDED, MULTIPLE_CHOICE

    public enum QuestionType {
        OPEN_ENDED, MULTIPLE_CHOICE, YES_NO
    }

    public Question(String text, QuestionType type) {
        if (text == null || text.trim().isEmpty()) {
            throw new IllegalArgumentException("Question text cannot be empty.");
        }
        this.text = text;
        this.type = type;
    }

    public String getText() { return text; }
    public QuestionType getType() { return type; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Question question = (Question) o;
        return Objects.equals(text, question.text) && type == question.type;
    }

    @Override
    public int hashCode() {
        return Objects.hash(text, type);
    }
}

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

import java.util.Objects;
import java.util.regex.Pattern;

public class ExternalEvaluator {
    private final String name;
    private final String email; // Could be an Email Value Object

    private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");

    public ExternalEvaluator(String name, String email) {
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("Evaluator name cannot be empty.");
        }
        if (email == null || !EMAIL_PATTERN.matcher(email).matches()) {
            throw new IllegalArgumentException("Invalid evaluator email.");
        }
        // Example of a business rule: internal emails are not allowed
        if (email.contains("@ciandt.com")) {
            throw new IllegalArgumentException("External evaluators cannot have a CI&T email.");
        }
        this.name = name;
        this.email = email;
    }

    public String getName() { return name; }
    public String getEmail() { return email; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ExternalEvaluator that = (ExternalEvaluator) o;
        return Objects.equals(name, that.name) && Objects.equals(email, that.email);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, email);
    }
}

// src/main/java/com/example/domain/repository/FeedbackFormRepository.java
package com.example.domain.repository;

import com.example.domain.model.FeedbackForm;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

public interface FeedbackFormRepository {
    void save(FeedbackForm form);
    Optional<FeedbackForm> findById(UUID id);
    List<FeedbackForm> findPendingForPdmApproval(String pdmId);
    // Other search methods as per domain needs
}

// src/main/java/com/example/domain/service/FormApprovalService.java
package com.example.domain.service;

import com.example.domain.model.ExternalEvaluator;
import com.example.domain.model.FeedbackForm;
import com.example.domain.repository.FeedbackFormRepository;

import java.util.List;
import java.util.UUID;

public class FormApprovalService {

    private final FeedbackFormRepository formRepository;
    // Ideally, a notification service would be injected here
    // private final NotificationService notificationService;

    public FormApprovalService(FeedbackFormRepository formRepository /*, NotificationService notificationService*/) {
        this.formRepository = formRepository;
        // this.notificationService = notificationService;
    }

    public void approveForm(UUID formId, String pdmId) {
        FeedbackForm form = formRepository.findById(formId)
                .orElseThrow(() -> new IllegalArgumentException("Form not found."));

        // Business rule: only the responsible PDM can approve
        // if (!form.getPdmId().equals(pdmId)) {
        //     throw new SecurityException("PDM not authorized to approve this form.");
        // }

        form.approve();
        formRepository.save(form); // Persists the status change

        // Notify evaluators
        // for (ExternalEvaluator evaluator : form.getEvaluators()) {
        //     notificationService.sendEmailToEvaluator(evaluator.getEmail(), form.getId());
        // }
        System.out.println("Form " + formId + " approved. Evaluators notified.");
    }

    public void rejectForm(UUID formId, String pdmId, String reason) {
        FeedbackForm form = formRepository.findById(formId)
                .orElseThrow(() -> new IllegalArgumentException("Form not found."));

        // Business rule: only the responsible PDM can reject
        // if (!form.getPdmId().equals(pdmId)) {
        //     throw new SecurityException("PDM not authorized to reject this form.");
        // }

        form.reject(reason);
        formRepository.save(form); // Persists the status change

        // Notify collaborator
        // notificationService.sendEmailToCollaborator(form.getCollaboratorId(), "rejected", reason);
        System.out.println("Form " + formId + " rejected. Collaborator notified.");
    }
}

Efficiency of the Prompt

  1. Clarity and Domain Context: The prompt begins with a clear description of the business scenario ("feedback system where a collaborator requests feedback... and a PDM approves"). This establishes the ubiquitous language necessary for DDD.
  2. Specificity of DDD Concepts: It explicitly requests the modeling of Entity, Value Object, Repository, and Domain Service, ensuring the response focuses on applying these patterns.
  3. Detailed Definition of Each Concept: For each concept, specific characteristics and examples are provided (FeedbackForm with a unique ID and status for the Entity, immutable Question and ExternalEvaluator for Value Objects, specific methods for FeedbackFormRepository, and the logic orchestration in FormApprovalService). This directs the AI to create classes with the correct behavior and structure.
  4. Constraints and Exclusions: The instruction to provide "only the skeletons of the classes and interfaces, without detailed database persistence logic in the repositories" is crucial. This prevents the AI from adding unnecessary infrastructure details and keeps the focus on the domain.
  5. Emphasized Behavior: When describing the FormApprovalService, the prompt details the actions it should orchestrate ("approve or reject, changing status and notifying"), encouraging the creation of methods with domain logic.

Quality of the Generated Code

  1. Rich Entity: The FeedbackForm class is a well-defined Entity with a UUID as an identifier and behavior methods (sendForApproval, approve, reject, finalizeForm) that modify its own state (status), demonstrating encapsulation of business logic.
  2. Immutable Value Objects: Question and ExternalEvaluator are modeled as Value Objects with all fields being final, constructors that perform validations, and implementations of equals() and hashCode(), ensuring their immutability and value-based equality. The email validation and the business rule for CI&T emails within ExternalEvaluator demonstrate a rich Value Object.
  3. Abstract Repository: FeedbackFormRepository is an interface that defines persistence and retrieval operations specific to FeedbackForm, without exposing database implementation details, aligning with the Repository pattern.
  4. Orchestrating Domain Service: FormApprovalService acts as a legitimate Domain Service. It injects the FeedbackFormRepository to get the Entity, calls behavior methods on the Entity itself (form.approve(), form.reject()), and orchestrates external notification, without containing the business logic intrinsic to the form or evaluator. It manages the approval flow that involves multiple entities.
  5. Package Structure: The code suggests a good package organization (domain.model, domain.repository, domain.service), which is fundamental in DDD projects.

Conclusion

This example demonstrates that to obtain code aligned with the principles of Domain-Driven Design from an AI, precision and richness of detail in the prompt are indispensable. By clearly specifying the domain context, the DDD concepts to be applied (Entity, Value Object, Repository, Domain Service), and even the expected business rules and behaviors within each concept, the AI is capable of generating a domain model that truly reflects the DDD philosophy.

The main deficiency in generic prompts lies in the AI's inability to infer the domain intentionality and the separation of responsibilities that are characteristic of DDD. A well-crafted prompt acts as a "designer" for the AI, guiding it to build a model that encapsulates business logic in the right entities, uses value objects for clarity and immutability, abstracts persistence, and orchestrates complex operations through domain services, resulting in a much more robust and understandable software design.