Product
Domain Driven Design
Domain-Driven Design with AI
How to Do DDD with AI and Qlerify

Insights from Virtual DDD Meetup

At a recent Virtual DDD Meetup, Staffan showcased how Qlerify accelerates domain modeling using AI.

From a simple text prompt, AI generated a CRM process featuring roles such as Salesperson and Sales Manager. The tool automatically generated domain events, placed them on a timeline, and mapped out commands and entities. The workflow was then turned into a Domain Model with Bounded Contexts, from which working API code could be produced.

The session sparked engaging discussions, including a surprise appearance by Eric Evans, the father of Domain-Driven Design (DDD), who asked insightful questions about the tool and its approach.

In this article we'll expand on the video with detailed step-by-step instructions you can follow. Whether you’re an architect, a domain expert, or a developer, Qlerify provides an intuitive and collaborative way to define bounded contexts, aggregates, and commands—helping you accelerate domain modeling and implementation.

About Domain-Driven Design

DDD helps teams build software that closely aligns with business needs by structuring code around real-world business concepts. A common challenge, however, is that creating effective domain models takes time and expertise. That’s where AI-assisted modeling, as demonstrated in Qlerify, becomes an exciting innovation.

Getting Started with Qlerify for DDD

To follow along, log in to Qlerify or sign up for an account using the link in the footer. Once logged in, create a new project, start a blank workflow, and follow this walkthrough.

Note: This guide focuses specifically on AI-assisted domain modeling. For a more comprehensive introduction to Qlerify’s features, including EventStorming, see the EventStorming walkthrough (linked in the footer).

Step 1: Review Card Types

Before you begin, ensure you have created a new blank workflow and have an empty canvas in front of you. In an empty swim lane, you should see two buttons: "Add start point" and "Generate workflow with AI".

Next, open Workflow Settings by clicking the cogwheel icon (⚙️) located above the empty diagram. Navigate to the Cards tab and review the Card Type Settings:

  • Use AI: Under this heading, verify that Command and Aggregate Root are selected. Additional card types are optional. For this walkthrough, we will deselect Read Model and Given-When-Then. You can read more about these specific card types in the Event Modeling guide (linked in the footer).
  • Command: Under this heading, ensure that the Command card type is selected.
  • Aggregate Root: Under this heading, confirm that the Aggregate Root card type is selected.

Step 2: Generate with AI

If you wish to change the LLM (Large Language Model), you can do so in Workflow Settings → AI. In this example, we use ChatGPT-4o.

Close the Workflow Settings window. If your workflow is empty, the "Generate Workflow with AI" button will be visible. Click this button and select the following prompt (or enter your own):

  • "A CRM process used by Salesforce"

Next, ensure the checkbox for "Generate Command Attributes with AI" is checked, then click the "Generate Workflow" button. The generation process may take a few minutes to complete.

Once the process finishes, the LLM will have generated Domain Events, along with cards for Commands and Aggregate Roots. The Domain Events are placed on a timeline and distributed across swim lanes. Your resulting workflow should resemble the example image shown below (though variations due to the AI are expected).

In the following steps, we will continue using AI to generate Entities, Read Models, and finally, API code.

Step 3: Generate Entities

At this point, each step in the generated workflow should include exactly one Command card and one Aggregate Root card. Click on an Event to select it and inspect its cards in the sidebar.

The text you see on the cards consists of text labels. However, Qlerify allows for more detailed definitions. Instead of simply using a text label to name the Aggregate Root, you can define formal Entities, including details such as field names, primary key identification, relationships between Entities, and example data. Once defined, these structured Entities can replace the basic text labels on the Aggregate Root cards.

TIP: If you have an existing data store you want to use, you can import your Entities before generating the workflow diagram. The generated workflow will then be based on your imported Entities.

Now, let's use AI to create detailed Entity definitions based on the Aggregate Root names generated in the previous step. Feel free to review and modify these Aggregate Root names on the cards (in the side bar) bar before proceeding, if needed.

Steps to Generate Entities:

  1. Navigate to the Entities tab located under the workflow diagram area.
  2. Click the "+ Entity" button, select the option "Generate all missing entities based on the workflow", and click "Add".
  3. Qlerify will use AI to generate the Entity definitions.

After the AI completes the generation, you should see a list of defined Entities (similar to the examples shown below):

Step 4: Connect Entities to Your Domain Model

Next, we will connect the defined Entities to become Aggregate Roots within the Domain Model.

  1. Navigate to the Domain Model tab (located under the workflow diagram area).
  2. Ensure no Event is selected so that all events are visible in the Domain Model view. (If an event is selected, clear the selection).
  3. You can hide Read Models for a more focused view by deselecting "Show read models".
  4. Under the Aggregate Root heading, you will see boxes (with yellow borders) containing the Aggregate Root text labels. The link icon (🔗) in a box indicates that the text label is not yet linked to an Entity. Click inside each box and select the corresponding Entity from the dropdown list (select the Entity with the same name). This replaces the simple text label with a link to the formally defined Entity.
  5. If a matching Entity does not exist in the dropdown list for a given Aggregate Root label, you can select the option "Create a new entity with AI" to generate and link a new Entity based on the text label provided (or your own description).

Note: The screenshot above shows multiple Commands linked to the same Aggregate Root. If you need to change the Aggregate Root for only a single Command, first select that Command's corresponding Event (by clicking it in the diagram or using the Event dropdown menu). Then, change the Aggregate Root specifically for that Command.

Step 5: Review the Event

Now it's time to inspect each Event and review its details. Iterate through Steps 5, 6, and 7 for each event. It is recommended to perform this review collaboratively with Domain Experts to validate assumptions and refine the model.

Let's start by selecting the first event: "Created lead". Inspect the workflow and the Domain Model. Ask the following questions and refine the model based on the answers.

1. Event Name & Actor:
  • Is it accurate that Sales Reps create Leads? (Or is the actor different?)
  • Does the term "Created lead" reflect the actual language and terminology used within the business domain (the Ubiquitous Language)?
  • Or is a different term or phrasing used, such as "Account Manager added Prospect"?
  • Action: If necessary, update the event name to accurately reflect the business process and language. Consider updating the swim lane (actor) if needed
2. Aggregate Root & Entity:
  • What is the core concept or entity being created or modified by this event?
  • Is "Lead" the correct term (Ubiquitous Language) for this core concept?
  • Should Leads be managed as distinct records (e.g., stored in a dedicated database table or collection)?
  • Should Leads have their own lifecycle – meaning they can be created, updated, and potentially deleted independently of other Entities? Does this event represent the start of that lifecycle?
  • Action: Discuss and validate this concept with Domain Experts. Confirm that the correct Aggregate Root is linked to this event in the Domain Model. You might need to rename the linked Entity, select a different existing Entity, or even generate/create a new Entity via the Entities tab or the "Create a new entity with AI" option discussed in Step 4.
Important Note

Ensure the event you are reviewing represents a genuine state-changing action (like a create, update, or delete) on the identified Aggregate Root / Entity. If the event describes a query, a read operation, or something that doesn't fundamentally change the state of the core entity, it might not be a true Domain Event for this workflow. Consider removing it from this primary sequence for now (e.g., by deleting it or moving it to a separate analysis area/branch in Qlerify) and potentially modeling it differently (perhaps as a Read Model interaction, discussed later).

Step 6: Review the Command Details

Now, let's focus on the Command associated with the event you are reviewing (the box with blue borders in the Domain Model). We need to define the data fields (parameters) required to execute this Command.

What information does a Sales Rep need to provide when creating a Lead? Open the sidebar in Qlerify and navigate to the "Data Fields" tab. This tab shows a simple mockup of the input form based on the Command.

Are the fields correct? Are any fields missing? Add, remove, or reorder fields in the Domain Model, directly inside the blue Command box, not in the mockup.

Note: The mockup is there to visualize what the system might look like, it shows example data but is not fully functional.

Qlerify supports five main ways to model Command fields, corresponding to different data structures:

1. Single Input Fields (Primitive Types)

These represent single values like numbers, dates, text, or booleans. In the UI mockup they are shown as plain fields with labels and example data.

  • Example: For creating a Lead, we might have fields like "Lead Name", "Email", "Phone Number", etc.
  • How to Add: Simply add a new field using the plus icon (+) at the bottom left corner of the blue Command box (displayed on hover).
  • Relation to Entity: Often, each Command field correspond directly to a field on a target Entity. Qlerify shows a warning (e.g., a yellow or red triangle ⚠️) if a Command field does not have a matching field (by name and type) on one of the Entities inside the Aggregate boundary (more about this later).
  • Important: Not every Command field must map directly to an Entity field. Commands orchestrate actions and might take inputs used for logic or side effects without being stored directly on an Entity. We are modeling more than just simple CRUD operations.
  • Edit Entity: You can modify Entities by clicking the edit icon (pen symbol 🖊️ at the bottom left in the box with yellow borders), or access it via the Entities tab.
2. Single Reference to a Related Entity (By ID)

This is used when the Command needs to link to an instance of another Entity that exists outside the current Aggregate boundary. The link is typically made using the target Entity's ID.

In the UX mockup shown as a single select drop down.

  • Example: Let's say Leads should be associated with a predefined "Lead Source" (e.g., 'Website', 'Referral'). The Sales Rep selects one source from a list, but doesn't modify the list itself via this Command. "Lead Source" would likely be its own Entity outside the Aggregate boundary.
  • Modeling Steps:
    1. Add Field: If not already present, add a field named "Lead Source ID" (or similar) to the Command. We add "ID" to the name to make it clear we are referencing the related Entity by its identifier. This is standard practice for referencing external Aggregates.
    2. Link Entity: Click the menu icon (three dots ⋮) next to the new "Lead Source ID" field you just created. Then click on "Select Entity".
    3. Generate/Select Entity: An Entity named "Lead Source" doesn't exist yet (most likely), so click "Create a new entity with AI". Type the name name "Lead Source", leave the description blank and generate. Once generated, select it from the list to link it to the Command field.
    4. Set Cardinality: Ensure the cardinality for this field is set to 1:1 (indicating a single selection). This setting is available at the bottom of the same menu that was used in the previous step.
    5. Update Entity Field: Qlerify might now warn (⚠️) that the "Lead Source ID" field is missing or mismatched on the Lead Entity. Edit the Entity definition (via the edit icon 🖊️ ):
      • Add a field named "Lead Source ID". (Or update an existing field.)
      • Set this field's type to "Reference" using the three dots menu next to the new field.
      • Using the same menu, for this field, select the entity "Lead Source" previously generated.
      • Again in the same menu, set the cardinality to 1:1.
      • Note: The cardinality can be set independently on the Entity and the Command. In most cases you will assign the same cardinality to both.
    6. Result: In the "Data Fields" mockup, it should now appear a dropdown list visualizing selection of a Lead Source (populated from the Entity's example data). The "Lead Source" Entity itself remains outside the Lead Aggregate boundary for this Command.

3. Multiple References to Related Entities (By IDs)

This field is similar to a single reference, but allows linking to multiple instances of another Entity. In the UI mockup it is shown as a drop down with checkboxes to indicate multi-select.

  • Example: If a Sales Rep could select multiple Lead Sources for a single Lead.
  • Modeling Steps: Follow the steps for a Single Reference, but when editing the "Lead Source ID" field on the Command, change the cardinality from 1:1 to 1:N (one-to-many).
  • Result: The mockup shows a multi-select control instead of a single dropdown.

4. Collection of Entities (Nested items within Aggregate Boundary)

This type is used for lists of related entities that reside inside the Aggregate boundary for this command. In the UI mockup it is shown as a list of items, like order rows, where items can be added, edited and removed.

  • Example: Multiple comments or meeting notes to be included when creating a Lead. Each note might have text, date, author.
  • Modeling Steps:
    1. Define Collection on Entity: Click on the pen icon (🖊️) at the bottom left of the entity to open the edit dialog. Add a field named "Lead Notes". Generate a new entity with AI called "Lead Note" and connect it to this field. Set the cardinality to 1:N. Crucially, do not set set this field as a "Reference" type. Not being a reference indicates it's part of the Lead Aggregate.
    2. Add Nested Fields on Command: On the Command "Create Lead" open the field selector. Add nested fields representing the data needed for each Lead Note by expanding Lead Notes and clicking the checkboxes for fields like: Created by, Content, Timestamp (the field names will usually differ).
  • Result: The lead notes become a part of the same Command that creates the lead. Thus this collection is within the Lead Aggregate boundary. The UI mockup will show the notes as a list of items.

5. Single nested Entity (Item within Aggregate Boundary)

This represents a single related entity (with its own fields) that is located inside the Aggregate boundary for this command. In the UI mockup shown as a sub form.

  • Example: Storing an Address (with Street, City, State, Postal Code) as part of the Lead Entity.
  • Modeling Steps:
    1. Define Entity: Go to the Lead Entity definition. Add a field named "Address". Generate and connect a new Entity named "Address" to this field. Set the cardinality to 1:1. Don't set it as a "Reference" type.
    2. Add Nested Fields: On the Command card, open the field selector. Add nested fields for Street, City, State, Postal Code.
  • Result: Just like collections, the Address fields will be located inside the Aggregate boundary. The UI mockup will show the address fields grouped together.

Now we can review the Aggregate boundary, for this Command, and and the Aggregate which is displayed inside the boundary.

  • References (like "Lead Source ID") point outside the boundary. The referenced Entity ("Lead Source") is not included within the Aggregate.
  • Collections (like "Lead Notes") and Embedded Objects (like "Address") reside inside the boundary for this command.

Note: Aggregate boundaries are dynamic. The boundary visualized for a specific Command/Event shows only what's directly manipulated or included by that operation. When viewing the overall Domain Model without filtering by a specific Event, Qlerify typically shows an accumulated view, including all Entities that fall within the Aggregate boundary for any of its associated Commands.

Step 7: Define Read Models

The next step is to identify Read Models. Read Models represent the information (or views of data) an actor needs before executing a specific command. Think of them as queries that fetch relevant context to support decision-making or populate a user interface.

Not every event/command necessarily needs a preceding Read Model fetched from the system. For instance, the "Created Lead" event might be triggered externally (e.g., by a phone call) without the user first querying data within an application.

Let's now use a different event from our example workflow: "Converted lead to opportunity". Before a Sales Rep converts a Lead to an Opportunity, what data should the system present to them?

  1. In the workflow diagram, select the event "Converted lead to opportunity".
  2. Navigate to the Domain Model tab.
  3. Ensure the checkbox "Show read models" is checked.

Now, let's define the necessary Read Models for this event:

Example 1: Listing Leads Available for Conversion

To convert a Lead, the Sales Rep first needs to select one. Let's define a Read Model to list relevant Leads.

  1. Click the "+Read Model" button associated with the selected event.
  2. Select Entity: Choose the Lead Entity.
  3. Select Fields: Open the field selector and check the fields relevant for display in the list, such as "Lead Name", "Contact Email", "Company Name", and "Status".
  4. Add Filter: Add a filter icon on the field "Status". This indicates to the model which fields we need to be able to filter on.
  5. Query Type: Leave the query type as the default: "List", as we expect multiple results, and not "Single Item".
  6. Update the description: "List of open Leads available for conversion to an Opportunity. Filtered on status open."

Example 2: Listing Related Opportunities for Context

When selecting a Lead to convert, it might be helpful for the Sales Rep to also see if any Opportunities already exist for the same Company. Let's add a second Read Model for this.

  1. Click the "+ icon visible below the previously created Read Model (on hover), to add another Read Model for the same event.
  2. Select Entity: Choose the Opportunity.
  3. Select Fields: Open the field selector and choose the fields to display, such as "Opportunity Name", and "Estimated Value".
  4. Add Company as a new entity: On the entity "Opportunity", let's add "Company" as a related entity referenced by ID. Open the entity Opportunity, add a field Company ID, generate a new entity "Company" and connect it to the field, now finally set it as a reference and cardinality 1:1. (Just like we did with Lead Source earlier.)
  5. Add Contextual Filter: Back to our new Read Model. We want to filter the existing Opportunities based on the Company Name of the Lead we are about to convert.
    • On the Read Model, open the field selector and open the company sub fields in the query. Mark company name as a filter.
    • If we don't have company name available on the Lead then we know that we need to add it.
  6. Update Description: We will use the description to explain with natural language how we want the filter to work. Add a clear description, for example: "List of existing open Opportunities with a company name similar company name on the Lead."
  7. Query Type: Set the query type to "List".

Conclusion:

By defining Read Models like these, you specify the data needed before commands are executed. This helps design more user-friendly interfaces and ensures actors have the necessary context, improving the efficiency and effectiveness of the system.

Step 8: Define Bounded Contexts

A core concept in Domain-Driven Design (DDD) is the Bounded Context. We define Bounded Contexts to divide a large system into more manageable, logically consistent parts, helping to clarify scope, ownership, and the meaning of model elements within each context.

Now, let's assign Entities to their respective Bounded Contexts.

  1. Navigate back to the Domain Model tab.
  2. Let's say that we are building a new Microservice for handling Leads. So assign the Lead entity to a new Bounded Context named "Lead Management Service" along with the entities Notes and Address.
  3. Let's say Opportunity and Quote will reside in the CRM context,  so assign the these entities entity to a new Bounded Context named "CRM".
  4. Finally assign the Order and Invoice Entities to a new Bounded Context named "Order Management".

Observing the Results:

After assigning contexts, the Domain Model view should visually reflect this structure:

  • Aggregates grouped together by their assigned Bounded Context.
  • Commands visible next to their respective Aggregates.

Context Map:

Qlerify also provides a part of a Context Map view. This view helps visualize the relationships and dependencies between different Bounded Contexts.

When viewing a single Bounded Context you will see integration points – places where commands in the current context trigger updates on Entities in other Bounded Contexts. These integration points are highlighted with the label "Integration with other bounded contexts".

Step 9: Generating API Code from the Domain Model

You have now successfully defined a comprehensive Domain Model within Qlerify, encompassing:

  • Domain Events
  • Entities (including Aggregate Roots, Collections, and Embedded Objects)
  • Commands (with detailed fields and references)
  • Read Models (queries for contextual data)
  • Roles (actors/swim lanes)
  • Bounded Contexts

This Domain Model serves as powerful, living system documentation and provides a solid foundation for development, provided that it accurately reflects your actual domain logic. It's crucial to iterate on this model and keep it up-to-date as the understanding of the domain evolves or the system changes.

Now, one of the most exciting capabilities remains: leveraging this detailed Domain Model for code generation. Based on the structure you've meticulously defined throughout this walkthrough, Qlerify can automatically generate API definitions (like OpenAPI specifications) and even foundational code snippets.

This generation process is typically performed per Bounded Context, reinforcing the modularity of your design.

The specific steps for initiating and configuring AI-assisted code generation are described in detail in a separate guide: the "AI Generated Code" article. You can find a link to this article in the footer of the page.

Takeaways from Eric Evans’ Discussion

Eric Evans, the creator of DDD, joined the discussion and raised thought-provoking questions. Key takeaways included:

  • AI-generated models serve as a powerful starting point but require iterative refinement.
  • There is potential for “round-trip engineering,” where updates to the codebase reflect back into the domain model.
  • While generic subdomains work well with AI, core domains require more human involvement to capture complex business logic

Get Involved & Try Qlerify

To explore Qlerify and see how it can enhance your domain modeling process, sign up and start modeling today! Use the sign up link in the footer of this page.

Watch the full Virtual DDD Meetup session: YouTube Video

///SOCIAL SHARE