Skip to content

5-Minute Quickstart

This tutorial takes you from zero to a working RIDDL model. By the end, you'll have a simple e-commerce domain with a product catalog and shopping cart.

Every example on this page is validated against the RIDDL compiler, so you can copy any of them and expect them to work.


Step 1: Create Your First Domain

A domain is a knowledge boundary. Start with a file called shop.riddl:

domain OnlineShop is {
  ???
} with {
  briefly "A simple e-commerce system"
}

The with clause adds metadata. briefly is a short description. The ??? is RIDDL's placeholder for "not specified yet" — it lets an incomplete model still parse.


Step 2: Add a Bounded Context

A context is a self-contained subsystem with its own terminology:

domain OnlineShop is {
  context Catalog is {
    ???
  } with {
    briefly "Product catalog and inventory"
  }
}

Step 3: Define Types

Types describe your data. Here is a product:

context Catalog is {
  type ProductId is Id(Catalog.Product)

  record ProductInfo is {
    id is ProductId,
    name is String(1, 200),
    price is Decimal(10, 2),
    inStock is Boolean
  } with {
    briefly "A product available for purchase"
  }

  entity Product is { ??? }
}

Id(Catalog.Product) creates an identifier tied to the Product entity. String(1, 200) is a string between 1 and 200 characters.

Note this is a record, not a plain type. A record is aggregate-shaped data, and it is what an entity's state must be built from — which is exactly what Step 4 needs.


Step 4: Create an Entity

An entity is something with identity that persists and responds to messages:

context Catalog is {
  type ProductId is Id(Catalog.Product)

  record ProductInfo is {
    id is ProductId,
    name is String(1, 200),
    price is Decimal(10, 2),
    inStock is Boolean
  }

  command CreateProduct is {
    name is String,
    price is Decimal(10, 2)
  }

  event ProductCreated is {
    id is ProductId,
    name is String,
    price is Decimal(10, 2),
    at is TimeStamp
  }

  entity Product is {
    outlet ProductEvents is event ProductCreated

    initial state Active of record ProductInfo is {
      handler Main is {
        on command CreateProduct {
          do "create the product with a new ID"
          send event ProductCreated to outlet ProductEvents
        }
      }
    }
  } with {
    option is event-sourced
    briefly "A product in the catalog"
  }
}

Key concepts:

  • Commands request changes (imperative: CreateProduct)
  • Events record what happened (past tense: ProductCreated)
  • State holds the entity's data, and is always typed by a record
  • Handlers define behavior when messages arrive
  • Outlets are how an entity publishes its events into a stream

Three details that are easy to get wrong:

  • Options go in with { }, not in the body. option is event-sourced inside the entity body is a parse error.
  • do "..." describes work for a human or AI to implement later. A bare quoted string on its own is not a statement.
  • initial marks the starting state. Without it the first state declared wins, so reordering states would silently change behavior.

Step 5: Add a Shopping Cart

Let's add another context for shopping:

context Catalog is {
  type ProductId is Id(Catalog.Product)
  entity Product is { ??? }
}

context Shopping is {
  type CartId is Id(Shopping.Cart)

  record CartItem is {
    productId is Catalog.ProductId,
    quantity is Integer,
    price is Decimal(10, 2)
  }

  record CartState is {
    items is CartItem*
  }

  command AddItem is {
    productId is Catalog.ProductId,
    quantity is Integer
  }

  event ItemAdded is {
    cartId is CartId,
    productId is Catalog.ProductId,
    quantity is Integer,
    at is TimeStamp
  }

  query GetContents is { cartId is CartId }
  result CartContents is { items is CartItem* }

  entity Cart is {
    outlet CartEvents is event ItemAdded

    initial state Active of record CartState is {
      handler Main is {
        on command AddItem {
          do "add or update the item in the cart"
          send event ItemAdded to outlet CartEvents
        }
        on query GetContents {
          yield result CartContents
        }
      }
    }
  } with {
    option is event-sourced
  }
}

Note: CartItem* means a list of zero or more items. You can reference types from other contexts with Catalog.ProductId.

yield produces a query's answer without the handler needing to know who asked.


Complete Example

Here is the full model in one file:

domain OnlineShop is {

  context Catalog is {
    type ProductId is Id(Catalog.Product)

    record ProductInfo is {
      id is ProductId,
      name is String(1, 200),
      price is Decimal(10, 2),
      inStock is Boolean
    }

    command CreateProduct is {
      name is String,
      price is Decimal(10, 2)
    }

    event ProductCreated is {
      id is ProductId,
      name is String,
      price is Decimal(10, 2),
      at is TimeStamp
    }

    entity Product is {
      outlet ProductEvents is event ProductCreated

      initial state Active of record ProductInfo is {
        handler Main is {
          on command CreateProduct {
            do "create the product with a new ID"
            send event ProductCreated to outlet ProductEvents
          }
        }
      } with {
        briefly "The live product record"
      }
    } with {
      option is event-sourced
      briefly "A product in the catalog"
    }
  } with {
    briefly "Product catalog and inventory"
  }

  context Shopping is {
    type CartId is Id(Shopping.Cart)

    record CartItem is {
      productId is Catalog.ProductId,
      quantity is Integer,
      price is Decimal(10, 2)
    }

    record CartState is {
      items is CartItem*
    }

    command AddItem is {
      productId is Catalog.ProductId,
      quantity is Integer
    }

    event ItemAdded is {
      cartId is CartId,
      productId is Catalog.ProductId,
      quantity is Integer,
      at is TimeStamp
    }

    query GetContents is { cartId is CartId }
    result CartContents is { items is CartItem* }

    entity Cart is {
      outlet CartEvents is event ItemAdded

      initial state Active of record CartState is {
        handler Main is {
          on command AddItem {
            do "add or update the item in the cart"
            send event ItemAdded to outlet CartEvents
          }
          on query GetContents {
            yield result CartContents
          }
        }
      } with {
        briefly "The live cart contents"
      }
    } with {
      option is event-sourced
      briefly "A customer's shopping cart"
    }
  } with {
    briefly "Shopping cart management"
  }

} with {
  briefly "A simple e-commerce system"
}

Validate Your Model

Use the RIDDL compiler to check your model for errors.

Install riddlc (macOS):

brew install ossuminc/tap/riddlc

For other platforms, see the installation guide.

Validate your model:

riddlc validate shop.riddl

A clean model exits 0. RIDDL also reports non-fatal findings — style, missing descriptions, completeness — which are worth reading even when the model validates.

You can also get real-time validation in your editor with RIDDL IDE support:


What's Next?

You've built a working RIDDL model! Here's where to go from here:


Quick Reference

Concept Purpose Example
domain Knowledge boundary domain Shop is { ??? }
context Bounded subsystem context Catalog is { ??? }
type Data shape type Name is String(1, 80)
record Aggregate data record Info is { name is String }
entity Stateful object entity Cart is { ??? }
command Request to change command AddItem is { qty is Integer }
event Record of change event ItemAdded is { at is TimeStamp }
query Request for info query GetContents is { id is CartId }
result Answer to a query result Contents is { items is Item* }
state Entity's data state Active of record Info
handler Message behavior handler Main is { on command X { ??? } }
outlet Publishes events outlet CartEvents is event ItemAdded
do Describe work to do do "recalculate the total"
yield Answer a query yield result Contents