Creating robust and maintainable applications that align closely with business requirements is a constant challenge in software development. Domain-Driven Design (DDD) has been quite successful in tackling this challenge. By placing the domain at the heart of the development process, DDD empowers developers to create highly scalable, modular, and business-oriented solutions. In this article, we will examine the potential of using Generative AI in Domain Driven Design.
In our experiment, we developed a time-tracking application using Generative AI tools to explore their capabilities and limitations in software engineering. By leveraging OpenAI’s chat completion API in Python, we generated the domain model and service APIs for the application. OpenAI’s API empowers developers to interact with models, apply prompt engineering techniques, and create effective prompts that seamlessly interact with language models and related tools. For more details on the methods and tools used in our experimentation, refer to the documentation.
Techniques of DDD
In Domain-Driven Design (DDD), several techniques play a crucial role in developing a robust domain model. Entity-Relationship Modelling helps visualise and document the relationships and dependencies between entities and value objects in the domain, ensuring a clear understanding of the structure. Bounded Contexts are identified for each subdomain to limit the functionality of each subdomain. Domain services handle complex operations or orchestrate interactions between entities, providing a centralised place for business logic that doesn’t fit naturally within entities or value objects.
Important steps involved in arriving at a good domain model are:
- Identify and define epics, which represent high-level business objectives or requirements.
- Decompose epics into smaller user-stories for better manageability.
- Analyse user stories to identify distinct subdomains within the overall domain.
- Create a context map to visualise relationships and interactions between subdomains.
- Define bounded contexts, representing isolated and self-contained areas with specific models and concepts.
- Refine the domain model, capturing essential entities, relationships, behaviours and domain services.
- Collaborate with domain experts, developers, and stakeholders to validate and adjust the model.
- Iterate on the model to ensure it aligns with business requirements.
- Develop well-structured domain model diagrams, ER diagrams, and service API definitions that will aid the development team.
The DDD way, using Generative AI
Some of these steps could be automated with the help of Generative AI. Read about how we generated epics and user stories (Steps 1–3) for the time-tracking application using Generative AI.
Identifying Domain Model
For the next few steps (steps 4–6), we defined a prompt for analysing the subdomains, context map and bounded context for the application utilising OpenAI’s chat completion endpoint. By identifying subdomains, DDD helps break down complex domains into manageable and focused parts.
The domain context response from this prompt (variable: `domain_context_response`) was further used in generating the domain model using another prompt (variable: `domain_model_prompt`).
domain_model_prompt = f"""
Epics and User Story list of application: ```{epics_story_response}```
Domain Context = ```{domain_context_response}```
Your task is to analyse the product epics, story list, and domain context in
detail and generate the following :
1. Using Domain Driven Design techniques, Identify the Entities, Value Objects with list
of attributes and behaviours for each.
2. List the services, repositories, etc. required for the application.
"""
domain_model_response = get_completion(domain_model_prompt)
The ‘get_completion’ method talks to OpenAI’s chat completion endpoint. It was defined as per the Prompt Engineering course provided by deeplearning.ai.
The response came back with a list of entities with their attributes and behaviours. The complete response, including services and repositories, can be found here. Partial output below:
Domain Model Entities and Value Objects:
User:
- Attributes: id, name, email, password, role
- Behaviours: register, login, reset password
Project:
- Attributes:id, name, description, client
- Behaviours: create, update, delete
Task:
- Attributes: id, name, description, project
- Behaviours: create, update, delete
Time Entry:
- Attributes: id, user, project, task, start_time, end_time, notes
- Behaviours: create, update, delete
Report:
- Attributes: id, project, task, employee, date range
- Behaviours: generate, customise
Invoice:
- Attributes: id, client, project, billing_rate, payment_terms, status
- Behaviours: create, customise, preview, send
Generating ER Diagram and Domain Model Diagram
The complete response with domain model context was passed into the prompt for generating domain model and ER diagram PlantUML codes. The generated PlantUML code could be copy pasted into a PlantUML editor such as PlantText (https://www.planttext.com), generating a very detailed domain model diagram) with the required domain services and repositories.
The ER diagram generated for the application from the PlantUML code:
The Entities identified and their attributes in the domain model are well-suited for addressing the problem statement at hand. The behaviours or actions of the Entities align with the requirements and functionalities specified in the problem statement.
Generating RESTful API Designs
The complete response with domain model context was further passed into the prompt for generating the service API endpoints with OpenAPI documentation in yaml.
service_api_endpoints_prompt = f"""
Epics and User Story list for the application : ```{story_response}```
Domain Model = ```{domain_model_response}```
Your task is to analyse the entities and services listed in the domain model
and generate:
1. List the RESTful service API endpoints for the application.
2. Generate OpenAPI documentation in YAML for the services.
"""
service_api_endpoints_response = get_completion(service_api_endpoints_prompt)
The response for service APIs contained a list of the required services and the restful endpoints. One of the best practices to keep in mind while designing a restful API is to use nouns instead of verbs. HTTP verbs were correctly used in the generated API endpoints. The details provided on how to parameterize and use the API were also quite good. A few of the endpoints:
Project:
- GET /projects/{id}
- GET /projects?client={client}
Task:
- GET /tasks?project={project}
Time Entry:
- GET /time-entries?user={user}&project={project}
Report:
- GET /reports?project={project}&task={task}&employee={employee}&start_date={start_date}&end_date={end_date}
We were able to generate swagger documentation using editor.swagger.io.
Insights and Challenges
Merging these prompts does not yield satisfactory outcomes, resulting in truncated results and missing expected information. The prompts have to be clear and specific. For eg: initially, in the prompt for generating service APIs, the prompt instructions did not mention ‘RESTful service API endpoints’, which resulted in endpoints that were RPC-style methods (see the API list below).
AuthService API:
registerUser(userDetails)
loginUser(email, password)
resetPassword(email)
UserService API:
getUserById(userId)
updateUser(userId, userDetails)
deleteUser(userId)
ProjectService API:
createProject(projectDetails)
getProjectById(projectId)
getProjectsByClient(clientId)
updateProject(projectId, projectDetails)
deleteProject(projectId)
........
The clarity of the instructions in the prompts helps in generating useful responses. It required iterative experimentation to refine and arrive at these prompts.
A Handy Tool for Domain Design
In this straightforward illustration, we successfully obtained a well-defined domain model. However, in more intricate domains, this may involve an iterative approach where the LLM is provided with increasingly detailed domain context in each iteration. This process enables expedited delivery and increased value generation for stakeholders while fostering a structured and systematic approach to domain design, promoting efficiency and effectiveness throughout the development lifecycle.
The utilisation of OpenAI’s chat completion endpoint within the OpenAPI framework proves to be a valuable tool for developers during domain design. Although Generative AI tools possess significant power, developers should keep in mind that it remains essential for them to apply design thinking and domain expertise to verify and improve the outputs generated by these tools.