Cómo implementar código DDD usando IA (Cursor)

Una frase muy común hoy en día es "el código hecho por la IA no es de tan buena calidad como el que hago yo".

Esta frase podría ser válida hace un año o incluso hace unos meses, pero desde el lanzamiento de Claude 3.7 Sonnet, esto ha cambiado. Esto, combinado con herramientas con Agentes como podrían ser Cursor, Cline o Jetbrains Junie.

En este post, vamos a ver cómo con un prompt (algo complejo, eso sí) podemos generar código DDD de calidad. Para aplicarlo no hace falte tener ningún .cursor/rules o .junie/guidelines.md, aunque tenerlos hará que el código generado sea aún mejor.

Todo en base a definir un agregado, con sus propiedades, eventos de dominio, invariantes, políticas correctivas y formas de acceso.

Para generarlo, simplemente hará falta rellenar la siguiente plantilla y añadirla al final del prompt:

* Name:
* Description:
* Context:
* Properties:
	* …
* Enforced Invariants:
	* …
* Corrective Policies:
	* …
* Domain Events: …
* Ways to access: …

A esta plantilla la hemos llamado Codely's Aggregate Design Blueprint y es la que usaremos para generar el código. Es una versión en texto e iterada del aggregate design canvas.

🔢 Estructura del prompt

El prompt puede parecer muy complejo, pero en realidad está compuesto de 5 piezas sencillas de entender:

1️⃣ Quién eres

Indicarle a la IA que eres un experto programador y experto en DDD. Esto hará que la IA sepa que cuál es su rol.

2️⃣ Cómo es la estructura del Codely Blueprint

Aquí le comentamos qué campos tiene y qué definición hay de cada uno. Esta parte la podríamos migrar a un JSON Schema, lo que haría que fuera más estricto. En este caso hemos preferido no hacerlo así, ya que es más sencillo de editar cuando es markdown directo.

3️⃣ Cómo transformar el Codely Blueprint a código

Aquí le indicamos paso a paso cómo tiene que transformar el Codely Blueprint a código.

4️⃣ Protocolo para ejecutar la transformación

Esta parte es la que hace que el código generado pase de estar un 60% correcto a un ~80% de cómo lo haríamos a mano. Una de las cosas interesantes que le decimos es que haga TDD, algo que no es común en la generación de código. Esto hace que vaya caso por caso y que el código resultante sea de más calidad.

Aquí es importante recalcar que en ningún momento le estamos diciendo explícitamente que lance los tests. Dependiendo el modelo y configuraciones que tengas previas es posible que lo haga, pero aunque no los lance, mejora mucho la calidad del código resultante.

5️⃣ Variables del usuario

Aquí es donde tú puedes modificar el prompt para que se adapte a tu código. En este caso, hemos definido dos variables que son $FOLDERS_CASE y $FILES_FORMAT. La primera es para indicar cómo se deben nombrar las carpetas y la segunda para indicar el formato de los ficheros.

Luego tenemos el Codely Aggregate Design Blueprint, que es donde se rellenan los datos del agregado.

¿Te interesa DDD y la IA?

Suscríbete a nuestra newsletter para recibir contenido relacionado

✍️ Prompt para generar un agregado DDD con IA

Copia y pega el siguiente prompt en tu editor y modifica a partir de la sección User variables para que encaje con tu código.

Es importante ejecutarlo en modo agente:

You are an expert programmer and a DDD expert. You'll be given a Codely's Aggregate Design Blueprint and have to
transform it to code.

# Codely Aggregate Design Blueprint structure:

'''
* Name: The name of the aggregate.
* Description: A brief description of the aggregate.
* Context: The context where the aggregate belongs.
* Properties: A list of properties that the aggregate has. Optionally, you can specify the type of each property.
* Enforced Invariants: A list of invariants that the aggregate enforces.
* Corrective Policies: A list of policies that the aggregate uses to correct the state of the aggregate when an invariant is violated.
* Domain Events: A list of events that the aggregate emits.
* Ways to access: A list of ways to access the aggregate.
'''


# Instructions to transform the Aggregate Design Blueprint to code:

You have to create:
* A module for the aggregate:
    * The module name should be the name of the aggregate in plural.
    * Should be written in $FOLDERS_CASE.
    * Should be inside the `src/contexts/$CONTEXT_NAME` directory.
* Every module contains 3 folders: `domain`, `application`, and `infrastructure`.
* Inside the `domain` folder, you'll have to create:
    * An `$AGGREGATE_NAME.$FILES_FORMAT file that contains the aggregate class:
        * The file name should be the name of the aggregate in PascalCase.
        * The aggregate class should have the properties, invariants, policies, and events that the aggregate has.
        * You should take a look to other aggregates to see the format.
    * A `$DOMAIN_EVENT.$FILES_FORMAT file per every event that the aggregate emits:
        * The file name should be the name of the event in PascalCase.
        * The event should have only the mutated properties.
        * You should take a look to other events to see the format.
    * A `$DOMAIN_ERROR.$FILES_FORMAT file per every invariant that the aggregate enforces:
        * The file name should be the name of the invariant in PascalCase.
        * You should take a look to other errors to see the format.
    * A `$REPOSITORY.$FILES_FORMAT file that contains the repository interface:
        * The file name should be the name of the aggregate in PascalCase with the suffix `Repository`.
        * The repository should have the methods to save and retrieve the aggregate.
        * You should take a look to other repositories to see the format.
* Inside the `application` folder, you'll have to create:
    * A folder using $FOLDERS_CASE for every mutation that the aggregate has (inferred by the domain events) and for every query that the aggregate has.
    * Inside every query/mutation folder, you'll have to create an `$USE_CASE.$FILES_FORMAT file that contains the query/mutation use case.
        * The file name should be the name of the query/mutation in PascalCase in a service mode. For example:
            * For a `search` query for a `User` aggregate, the class should be `UserSearcher.$FILES_FORMAT.
            * For a `create` mutation for a `User` aggregate, the class should be `UserCreator.$FILES_FORMAT.
        * You should take a look to other queries/mutations to see the format.
* Inside the `infrastructure` folder, you'll have to create:
    * A `$REPOSITORY.$FILES_FORMAT file that contains the repository implementation:
        * The file name should be the name of the aggregate in PascalCase with the suffix `Repository`.
        * Also, the file should have an implementation prefix. For example, for a `User` aggregate and a Postgres implementation, the file should be `PostgresUserRepository.$FILES_FORMAT.
        * The repository should implement the repository interface from the domain layer.
        * You should take a look to other repositories to see the format and use the most used implementation.
* You'll have to create a test per every use case:
    * The test should be inside the `tests/contexts/$CONTEXT_NAME/$MODULE_NAME/application` directory.
    * You should create an Object Mother per every aggregate and value object that you create inside `tests/contexts/$CONTEXT_NAME/$MODULE_NAME/domain`.
    * Take a look inside the `tests/contexts` folder to see the format of the Object Mothers and the tests.
    * You should only create a test per every use case, don't create any extra test case.
* You should create a test for the repository implementation:
    * The test should be inside the `tests/contexts/$CONTEXT_NAME/$MODULE_NAME/infrastructure` directory.

# Protocol to execute the transformation:

## 1. Search for the examples of the files that you have to create in the project
Execute `tree` to see the current file structure. Then use `cat` to see the content of similar files.

## 2. Create the test folders structure
If the module folder doesn't fit inside any of the existing contexts, create a new one.

## 3. Create the test for the first use case
* We should create use case by use case, starting with the first one.
* We're doing TDD, so we'll create the first use case test first.
* Also, we'll create all the object mothers.
* Then all the domain objects (if needed).
* Then the use case.
* Do it until the created test passes.
* Repeat this per every use case.

## 4. Create the repository implementation test
* We should create the repository implementation test after all the use cases are created.
* First, create the repository implementation test.
* Then, create the repository implementation.
* Do it until the created test passes.

# User variables:

$FOLDERS_CASE = kebab-case
$FILES_FORMAT = ts

# User Codely Aggregate Design Blueprint:


'''
* Name: Naive Bank Account
* Description: An aggregate modelling in a very naive way a personal bank account. The account once it's opened will aggregate all transactions until it's closed (possibly years later).
* Context: Banking
* Properties:
	* Id: UUID
	* Balance
	* Currency
	* Status
	* Transactions
* Enforced Invariants:
	* Overdraft of max £500
	* No credits or debits if account is frozen
* Corrective Policies:
	* Bounce transaction to fraudulent account
* Domain Events: Opened, Closed, Frozen, Unfrozen, Credited
* Ways to access: search by id, search by balance
'''

✨ Mejoras a futuro

Sería ideal tener el Blueprint de cada agregado en un fichero en la raíz de su módulo y que, cuando quieras añadir un nuevo caso de uso, simplemente tengas que modificar ese blueprint para añadir un nuevo evento o forma de acceso.

Esto haría que la IA pudiera inferir qué es lo que tienes que hacer y que, simplemente con ejecutar el prompt, se generara el código necesario. Y si ya existe ese caso de uso, lo omite. Próximamente. 🙌

Paga según tus necesidades

lite (sólo mensual)

19 €
al mes
  • Acceso a un subconjunto de cursos para sentar las bases para un código mantenible, escalable y testable
  • Factura de empresa
Popular

standard

24,92 €
Ahorra 121
Pago anual de 299
al mes
  • Catálogo completo de cursos
  • Retos de diseño y arquitectura
  • Vídeos de soluciones destacadas de los retos
  • Recibir ofertas de empleo verificadas por Codely
  • Factura de empresa

premium

41,58 €
Ahorra 89
Pago anual de 499
al mes
  • Todos los beneficios anteriores
  • Acceso anticipado a nuevos cursos
  • Más beneficios próximamente

No subiremos el precio mientras mantengas tu suscripción activa