Product
Domain Driven Design

The Premier Domain-Driven Design (DDD) Modeling Tool, Powered by AI

A step-by-step guide to building a complete DDD model—from prompt to code—with Qlerify.
A diagram illustrating the concept of bounded contexts in Domain-Driven Design. Ovals represent different business areas, including Sales, Billing, Customer Care, and Itinerary Design.

Why Use a Tool for Domain-Driven Design?

Domain-Driven Design (DDD) is a powerful method for building complex software that truly aligns with business needs. However, turning business knowledge into a well-structured, actionable domain model is a significant challenge. It requires deep collaboration, expertise, and a lot of time. This is where a specialized DDD modeling tool becomes essential.

Qlerify is an AI-powered domain-driven design tool built to solve this problem. It accelerates the entire modeling process, enabling teams to visually collaborate, define bounded contexts, and generate API-ready models from a simple text prompt. As showcased in a recent Virtual DDD Meetup by Qlerify co-founder Staffan Palopää, our tool bridges the gap between business concepts and executable code.

The session, which even drew questions from DDD creator Eric Evans himself, demonstrated how AI can generate a complete workflow, identify domain events, map out commands and aggregates, and ultimately produce a tangible domain model. This article provides a detailed, step-by-step guide to replicate that process, showing you how Qlerify can serve as your central DDD modeling tool.

Getting Started with Our DDD Modeling Tool

To follow along, log in to Qlerify or sign up for a free trial. Once logged in, create a new project, start a blank workflow, and follow this walkthrough.

Note: This guide focuses on using Qlerify as a domain-driven design tool. For a broader introduction to our features, see the Event Storming walkthrough.

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".

Screenshot of the Qlerify user interface for a new, empty workflow. Callouts point to the 'Generate workflow with AI' button on the canvas and the 'Workflow settings' cogwheel icon in the top toolbar.

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, ensure that the Aggregate Root card type is selected.
The 'Card Type Settings' dialog in Qlerify, found under the 'Cards' tab. The dialog shows a list of card types with configuration options, highlighting that 'Use AI' should be enabled for 'Command' and 'Aggregate Root' cards for this tutorial.

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 following checkboxes are checked:

  • "Generate Command Attributes with AI"
  • "Generate Entities with AI"

Deselect any other check boxes. Then click the "Generate Workflow" button. The generation process may take a few minutes to complete.

Screenshot of the 'Generate workflow with AI' dialog box in Qlerify. The main input field shows the example prompt 'A CRM process used by Salesforce', and the checkboxes for 'Command', 'Aggregate Root', and 'Generate Entities with AI' are selected.

Once the process finishes, the LLM will have generated Domain Events, along with cards for Commands, Aggregate Roots and Entities. 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).

A screenshot of the workflow diagram generated by Qlerify's AI. The diagram maps a sales process using horizontal swimlanes for roles like 'Sales Rep', 'Sales Manager', and 'Customer'. The process flows from left to right, starting with the 'Created lead' event and progressing through events like 'Converted lead to opportunity' and 'Accepted quote'.

Inspect Events

At this point, each step (Event) 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.

Screenshot of the Qlerify interface showing what happens when an event is selected. The 'Created lead' event is highlighted in the workflow, and the details sidebar on the right displays its associated cards: a 'Command' named 'Create Lead' and an 'Aggregate Root' linked to the 'Lead' entity.

The Aggregate Root card can either contain a simple text label with a descriptive name, or it can be linked to an Entity. If the card displays an Entity name within a grey, rounded element (with a dropdown icon to the right), it is linked to that Entity. If the Aggregate Root card displays a link icon (🔗) next to a text label, it is not linked to an Entity.

A diagram comparing two states of an Aggregate Root card. On the left, a card is shown 'linked to an entity,' which displays 'Lead' as a formal, selectable element. On the right, a card is shown with a 'free text label (not linked),' which displays 'Lead' as simple text with a link icon.

If not linked, you can click the link icon (🔗) and select an Entity from the dropdown list (or generate an Entity). Selecting an Entity replaces the simple text label with a link to the formally defined Entity.

Inspect Entities

Navigate to the Entities tab located under the workflow diagram area. Here you can see the generated Entities including details such as field names, and primary key, required and example data. Here you can also generate additional Entities base on free text descriptions. Later we will see how to define relationships between Entities.

Screenshot of the 'Entities' tab in Qlerify, which lists the data models for the domain. The view contains tables for the 'Lead', 'Opportunity', and 'Quote' entities. Each table displays the entity's fields as columns (e.g., 'Lead Name', 'Estimated Value') and is populated with rows of example data.
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.

Inspect 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 Roots. If the AI generation was successfully completed the Aggregate Root cards should be linked to Entities (as mentioned earlier in this article).
Screenshot of the 'Domain Model' tab in Qlerify, filtered to show 'All events'. The diagram has four columns: Role, Command, Aggregate Root, and Resulting event. It visually maps how commands, like 'Create Lead' and 'Update Lead Status', are initiated by roles, operate on the central 'Lead' aggregate root, and result in domain events.

If more than one Command operates on one Aggregate Root, you will see Commands (boxes with blue borders) grouped around the Aggregate Roots. If you need to move a single Command to a different Aggregate Root, first select the corresponding Event, then change the Aggregate Root specifically for that Command (in the box with yellow borders).

The Qlerify Domain Model view, now demonstrating how to focus on a single event. The diagram is filtered by the 'Created lead' event, simplifying the view to only show that event's specific flow from Role to Command to Aggregate Root. A callout explains that in this filtered view, any changes made to the Aggregate Root will only affect the selected event.

Step 3: Review the Events

Now it's time to review each Event and its details. Iterate through Steps 3 - 5 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.
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 4: 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.

Screenshot demonstrating how a Command's data fields generate a UX mockup in Qlerify. On the left, the 'Create Lead' command lists its required fields. On the right, the 'Data Fields' tab displays an automatically generated input form based on those fields, populated with example data.
Note: The mockup is there to visualize what the system might look like, it shows example data but is not fully functional. For example a field might contain a drop down, but items cannot be selected.

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.
A detailed diagram explaining how to model single input fields in Qlerify. Callouts illustrate that a field is added to the 'Create Lead' command and should match a corresponding field on the 'Lead' entity. The right panel shows how this creates a simple input field in the UX mockup. The diagram also points out where to 'Add field' and 'Edit entity', and notes that a warning will appear if fields don't match.

2. Single Reference to a Related Entity (By ID)

A reference is used when the Command carries a link to another Entity, located outside the current Aggregate boundary. The reference is typically made using the target Entity's ID.

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

  • Example: In our example we got a field called Lead Source on both the Command and the Entity (if you didn't get it you can add it manually). Let's say Lead Source should not be a free text field, but instead be picked from a predefined list of lead sources (e.g., 'Website', 'Referral' etc). The Sales Rep will select one lead source from a list, without modifying the list.
  • Modeling Steps:
    1. Add Field: Change the name of the field Lead Source to Lead Source ID on the Command. This name indicates that the field should contain a reference instead of a string value.
    2. Link Entity: Click the menu icon (three dots ⋮) next to the "Lead Source ID" field. Then click on "Select Entity".
    3. Generate/Select Entity: An Entity named "Lead Source" doesn't exist yet, so click "Create a new entity with AI". Name the entity "Lead Source", leave the description blank and click "Create". 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 will now warn (⚠️) that the "Lead Source ID" field is missing or mismatched on the Lead Entity. Edit the Entity definition (via the edit icon 🖊️ ):
      • Change the field named "Lead Source" to Lead Source ID (or add a new field).
      • Set this field's type to "Reference" using the three dots menu next to the new field.
      • Using the same menu, select the Entity "Lead Source" previously generated.
      • Again in the same menu, ensure the cardinality is set 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 of Lead Sources (populated from the Entity's example data). The "Lead Source" Entity itself remains outside the Lead Aggregate boundary for this Command. The purpose of the mockup is just to visualize the interaction, it does not allow you to select items.
An annotated screenshot demonstrating how to model a single entity reference. Instructional callouts explain the process: both the 'Create Lead' Command and the 'Lead' Entity are updated by renaming the field to 'Lead Source ID' and linking it to a separate 'Lead Source' entity. The result, shown in the UX mockup, is that the 'Lead Source ID' field is now a single-select dropdown menu instead of a text box.

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 should be able to 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). In most cases you would also change the cardinality on the Entity.
  • Result: The mockup shows a multi-select control instead of a single dropdown.
Screenshot demonstrating how to model multiple entity references. A callout explains the key action: setting the 'Lead Source ID' field's cardinality to 1:N. This updates the UX mockup on the right, transforming the field from a single-select dropdown into a multi-select list with checkboxes.

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

This type is used for collections 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 the 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 (at the bottom inside the blue Command box). 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.
An annotated screenshot explaining how to model a nested collection of entities. Callouts detail the process: a 'Lead Notes' collection is added to the main 'Lead' entity, and then specific nested fields like 'Content' and 'Timestamp' are selected on the 'Create Lead' command. The UX mockup on the right renders this as a 'Lead Notes Collection,' which is an editable list of items where new notes can be added.

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: Click on the pen icon (🖊️) at the bottom left of the Entity "Lead" to open the edit dialog. Add a field named "Address". Click on Select Entity for the new Address field. 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.
Screenshot explaining how to model a single nested entity, using an Address as an example. Callouts show the 'Address' entity included within the 'Lead' aggregate boundary. The 'Create Lead' command includes nested address fields, which results in the UX mockup rendering the address as an embedded sub-form.

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. We can see that 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 5: 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. (Located under the Read Model heading the left side of the Domain Model tab.)
  2. Select Entity: Choose the Lead Entity.
  3. Select Fields: Open the field selector (visible at the bottom of the green Read Model box on hover) 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" using the menu (three dots ⋮) next to the "Status" field. This indicates to the model which fields we need to be able to filter on. By dragging all fields that should have filter functionality to the top you can make the model easier to read.
  5. Query Type: At the bottom of the green Read Model box, leave the query type as the default: "List", as we expect multiple results, and not just"Single Item".
  6. Update the description: Describe the query using natural language in the text field above the list of fields: "List of open Leads available for conversion to an Opportunity. Filtered on status open."
An annotated screenshot explaining how to define a Read Model in Qlerify. A new 'Read Model' column on the far left contains a query to get a 'List of open Leads'. This provides the context a 'Sales Rep' needs before executing the 'Convert Lead' command. Callouts highlight that the Read Model will show a list of leads to pick from and can be filtered by status.

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 ID" as a related entity referenced by ID. Open the entity Opportunity, add a field Company ID. Now use the meny (three dots ⋮) next to the newCompany ID field to generate a new entity "Company" and connect it to the field. Using the same menu, 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 again and expand the company sub fields by clicking Company ID. Include Company ID and Company Name in the query.
    • Close the field selector, back on the Read Model (green box) mark Company Name as a filter using the meny (three dots ⋮) next to the field.
    • If we don't have company name available on the Lead then now we can see that we probably should to add it.
  6. Update Description: We will use the description (the textbox inside the green Read Model box) 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: Ensure the query type is set 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 6: 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 Lead Note 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".

Screenshot showing the domain model organized by Bounded Contexts. The diagram is visually partitioned into two large sections. A callout labels the top section as the 'Lead Management Service' Bounded Context, which contains the Lead aggregate. The bottom section is labeled as the 'CRM' Bounded Context, containing other aggregates and their commands.

Step 7: Generating API Code from the Domain Model

You have now 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 (at the Virtual DDD Meetup mentioned earlier in this article) 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
A conceptual diagram of four concentric circles, illustrating that software is built in layers to serve a core purpose. The innermost circle is 'Application', surrounded by 'Architecture', then 'User need'. The outermost circle, 'Business need', encompasses all others, showing that it is the ultimate driver for the entire system.

Get Involved & Try Qlerify

To explore Qlerify and see how it can enhance your domain modeling process, sign up and start modeling today!

Watch the full Virtual DDD Meetup session: YouTube Video

///SOCIAL SHARE