

In this walkthrough, we take one part of an existing application, reverse-engineer it into a structured domain model, validate that model with humans in the loop, and reimplement it on a stack of our choice.
The goal is to capture the important business behavior in a form that people can understand, modify, maintain, and use as the basis for a new implementation.
To make the process concrete, we use the cart module from Medusa, an open-source commerce platform. The same steps can be applied to any open-source or closed-source application you have access to.
We use Claude Code as the coding agent, but any similar agent should work.
We will go through four steps:
If you skip the two middle steps and jump directly from old code to generated code, you can move fast, but you may struggle to keep humans in the loop. You also risk carrying forward accidental complexity, unclear boundaries, and undocumented assumptions.
A structured domain model lets you move fast while keeping stakeholders on board.
For this walkthrough, we needed an existing codebase from a realistic domain. E-commerce is a good fit because the business rules are rich enough to be interesting, while still being widely understood.
After a quick search for open-source e-commerce projects, Medusa came up as a good candidate. We decided to start with the cart module because it gives us a focused slice of functionality, while leaving room to expand later into related modules such as product catalog, promotions, order management, and payments.
We start by cloning the Medusa repository. No installation is required; the goal is not to run the platform. The goal is to analyze the codebase and extract the behavior of a specific part of the system.
❯ git clone git@github.com:medusajs/medusa.git
...
❯ cd medusa
❯ claudeInside the cloned repository, we open Claude and use a special prompt for extracting DDD-style aggregates. Below, you can see two versions of the prompt: first, the exact one we used in this example, and then a generic template that you can copy and modify for your own use case. The generic template is also available as a skill.
Copy the Extract Aggregate From Codebase Medusa prompt into Claude and run it.

Note: If you are new to DDD terminology, you can think of an aggregate as a cohesive domain component with a clear consistency boundary. “Module” is not a precise substitute, but it is close enough for intuition.
A key feature of the prompt is that it zooms in on an aggregate as a coherent piece of functionality and “peels off” the service layer. Legacy systems often mix core domain behavior with cross-aggregate orchestration, infrastructure concerns, and historical decisions that no longer belong together.
For modernization, we want a clean boundary first. Cross-aggregate coordination can be added back later in a more explicit and robust way.
After less than five minutes with Opus 4.7, Claude provides the result: an extracted aggregate description: cart-aggregate.md. Some details will vary slightly from run to run. Medusa overloads cart service methods, so a single command can surface as several payload shapes when you reverse-engineer the code. These differences are expected, and we will soon let AI validate and correct the model and fix any unwanted deviations.
In legacy modernization, there is not always one “correct” model. Some decisions are based on judgment rather than strict rules. Some behavior should be preserved, and some behavior may be deliberately improved. However, as you will see, we will make sure to capture the key functionality in great detail.
At this stage, if something is obviously wrong, discuss it with the AI and refine the specification before moving on.
The output from the first step is useful, but it is still just text.
That is enough for an engineer to read. However, it is not ideal for a broader group of non-technical stakeholders to validate, discuss, modify, maintain, and evolve.
A plain-text specification becomes hard to work with over time because the structure is implicit. Missing attributes, misspelled field names, inconsistent naming, and unclear relationships become harder to spot.
This is where Qlerify comes in:

Note: This step can also be done with Claude through MCP. At the time of writing, using the Qlerify frontend is still faster for this particular task because batch operations are better optimized there. MCP gives other benefits, such as the possibility of using an agentic loop.
After around four minutes, the workflow is completed. Here is a public read-only link where you can inspect the domain model and clone it to your own account:
Qlerify > Ecommerce > Cart Workflow.
Once imported, the cart functionality becomes much easier to understand and reason about. Instead of reading a long text file, you can inspect commands, entities, test cases, attributes, and their relationships in a structure designed for human review.

This is where domain experts, product stakeholders, and developers can collaborate around the same representation.
You can review all the entities. Looking at the Cart entity, you can see detailed descriptions of attributes such as regionId, salesChannelId, and shippingMethodAdjustments. This helps you decide whether to keep these attributes, remove them, or expand the functionality.

Test cases written as Given-When-Then statements can be inspected, updated, and added for each command.
Once the model is in Qlerify, validation becomes much easier.
Use the built-in validation tool. It will help you visualize and resolve both critical and minor issues. This catches structural problems early and makes them visible so the team can collaborate on fixes.
As we can see below, when we arrived at this step, an issue was raised: the attribute eligiblePromotions on the query getCart had no matching field on the Cart entity. After some consideration, we decided that eligiblePromotions should belong to a query in the Promotions module, which is outside the scope for now. We removed the attribute and will deal with it when the time comes to connect the Promotions module with the Cart module.
We also get notified that there are four queries/read models that are not connected to an entity. These entities belong to other modules that are out of scope. We will ignore these notifications for now.

For an extra level of validation, and for the most critical review, validate the model one more time using Claude through MCP.
qlerify MCP and mcp-companion under “installed” when running the slash command /plugin.
> Read the Qlerify domain model for the workflow “Cart” in the project “Ecommerce”.> Can you review whether this domain model correctly represents the Medusa cart at its aggregate boundary?Next, move on to validate the model with human stakeholders. Modify, validate, and iterate. If new warnings show up, fix them again manually or by using the AI assistant.
Let AI do the work. Let humans review and decide the scope and the business rules.
The final test: once the model has been extracted and validated, it should be portable. If the model truly captures the business behavior, we should be able to reimplement everything and get it right on the first pass. Let’s push the domain model to a new empty repository and build it.
Navigate to Push Spec — the button next to Domain View — and walk through the four steps. In the last step, name the model file and push it. For example, name it:
cart-domain-model.json
Clone the repository to your local machine. It should only contain your cart-domain-model.json file.
❯ git clone git@github.com:Qlerify/ecommerce-demo.git
Start Claude in the cloned folder and pick a tech stack of your choice. For this demo, a simple Express application with SQLite should be enough to prove the point. It is a stack we know well, although ideally we would use something more different from Medusa.
A prompt at this stage could be:
> Please review the domain model specification I will provide and propose an Express/SQLite architecture for implementing it, with a clear migration path to PostgreSQL later. Include a simple testing frontend that allows me to view the customer-facing cart, list existing carts, and load a selected cart. The solution should also make it easy to run all Given-When-Then test cases from the specification.
Finally, after receiving and tweaking the recommendations to your liking, run something like:
> Sounds good! Go ahead and build everything!After around 10 minutes, the build is completed. All tests are passing. We only had to iterate a few times on some frontend details. The backend worked on the first try, indicating that the model is robust.


By saving the model file, cart-domain-model.json, in the version control system together with the code, we keep the specification tied to the implementation. Every pull request shows the updated model together with the corresponding code changes. The file cart-domain-model.json is reversible and can be imported into Qlerify at any time.
So how do we maintain our application? Let’s look at an example.
After testing the cart, we can see that the total tax amount is missing. After inspecting Medusa, we can see why. Items are added without tax at the aggregate boundary, and the service layer interacts with the tax module, which is outside our scope, before setting the tax lines. We’ll keep it like this for now. We’ll have to add tax manually with an extra click in the Tax & Discount section. However, let’s at least add computed fields to sum up and display the tax.
We can see that Medusa uses a function, decorateCartTotals, for this purpose.
Start Claude in the Medusa folder if it is not already running. Then copy, paste, and run this prompt. If the suggestions look good, let Claude update Qlerify.
In the Qlerify "Cart" workflow in the "Ecommerce" project, we want to extend the Get Cart Query / Read Model to include computed tax fields for item lines and cart totals. Can you help suggest additions? Use Medusa’s decorateCartTotals function as a reference.Verify the changes in Qlerify. Under Review Spec, you should now see the exact diff and no other changes. If it looks good, push the updated cart-domain-model.json file, on a feature branch if you prefer.
Switch over to Claude in the ecommerce-demo folder. Pull the updates from the previous step and continue with something like:
> Can you see the modifications made to cart-domain-model.json in the last commit? If so, implement the new functionality that was added.Now the totals are working at the bottom of the cart. A restart might be needed if you do not have hot reload active. If everything looks good, commit and push the updates to ecommerce-demo.
And that’s it. We have completed our first update, or iteration.

We have taken one part of an enterprise-grade commerce platform, extracted its behavior into a structured domain model, validated that model, and reimplemented it in a tech stack of our choice. We stayed in control of every attribute and its behavior, and we now have a practical way to evolve the system.
The cart is just one slice of the e-commerce platform. The same steps can be applied to adjacent parts of the domain, including product catalog, promotions, order management, and payments.
Once the modeling and validation loop is in place, modernization stops being a single high-risk rewrite project and becomes a repeatable way to move functionality from a legacy architecture into a cleaner one.
Quick links to all the resources mentioned in the article:



