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
- 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.
- Omits Key Concepts: It doesn't mention the specific DDD concepts that should be applied (Entity, Value Object, Repository, Domain Service).
- No Business Context: There is no description of business rules, workflows, or important domain interactions.
Shortcomings of the Generated Code
- Superficial DDD: The code doesn't demonstrate a real application of DDD concepts:
Feedback.javaappears to be an Entity, but there's no clarity on its identity distinction or behavior methods.FeedbackRepositoryis a generic interface (Object feedback) and not specific to a domain aggregate or entity.FeedbackServiceis 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.
- 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.
- Coupling: The
FeedbackServicedirectly 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
- 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.
- 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.
- Detailed Definition of Each Concept: For each concept, specific characteristics and examples are provided (
FeedbackFormwith a unique ID and status for the Entity, immutableQuestionandExternalEvaluatorfor Value Objects, specific methods forFeedbackFormRepository, and the logic orchestration inFormApprovalService). This directs the AI to create classes with the correct behavior and structure. - 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.
- 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
- Rich Entity: The
FeedbackFormclass is a well-defined Entity with aUUIDas an identifier and behavior methods (sendForApproval,approve,reject,finalizeForm) that modify its own state (status), demonstrating encapsulation of business logic. - Immutable Value Objects:
QuestionandExternalEvaluatorare modeled as Value Objects with all fields beingfinal, constructors that perform validations, and implementations ofequals()andhashCode(), ensuring their immutability and value-based equality. The email validation and the business rule for CI&T emails withinExternalEvaluatordemonstrate a rich Value Object. - Abstract Repository:
FeedbackFormRepositoryis an interface that defines persistence and retrieval operations specific toFeedbackForm, without exposing database implementation details, aligning with the Repository pattern. - Orchestrating Domain Service:
FormApprovalServiceacts as a legitimate Domain Service. It injects theFeedbackFormRepositoryto 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. - 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.