Petter Graff, Jonathan Kamke, Barrett Cervenka, Matthew Adams

Version: 0.1.0

Last Updated: 2017-02-07 23:06:14 UTC

1. Meta

This section provides a brief overview of this Yaktor Reference Guide.

1.1. About

This reference guide is available in source form in Github at or on the Yaktor docs site at

1.2. Getting help

If you need help with Yaktor, it’s available.

  • Try the Yaktor FAQs for answers to the most common questions.

  • Ask questions with the tag yaktor on Stack Overflow.

  • Discuss Yaktor at our Yaktor Gitter room. Yaktor committer hours vary but are generally U.S. business hours.

  • Contact Yaktor’s primary sponsor, SciSpike, for training, consulting, or custom software development.

1.3. First steps

1.4. Working with Yaktor

Ready to start working with Yaktor? Then write some code!

2. Getting Started

This section aims to quickly get you up & running with Yaktor using its Node.js mapping.

2.1. tl;dr (using Node.js)

Install Docker 1.11 or later, then create a new Yaktor application or work on an existing one.

If You’re Creating a New Yaktor Application: If You’re Coming Aboard an Existing Yaktor Application:
$ mkdir myapp && cd myapp           (1)
$ curl | sh  (2)
$ ./yak gen-src gen-views start     (3)
1 Create & change into new app directory
2 Initialize Yaktor application
3 Generate source & views then start app
$ git clone ...                  (1)
$ cd ...                         (2)
$ ./yak gen-src gen-views start  (3)
1 Get source
2 Change into source directory
3 Generate source & views then start app

To see the generated application UI, if you’re using Mac OS X or macOS, open http://www.myapp.yaktor in a browser.

If you’re on Windows or Linux, open http://localhost:8888 or see the URL in the server console’s log for the IP address to connect to.

2.2. Introducing Yaktor

Yaktor is a tool to assist in the rapid creation of highly scalable, event-driven, agent-based applications. It leverages domain-specific languages (DSLs) that decouple much of the business logic from the actual implementation technology. In this way, your application is future-proofed against changes in technology; as new technologies & patterns emerge, the Yaktor team (or your team) can define new Yaktor mappings to generate code according to each technology’s syntax & best practices.

Although Yaktor includes a code generator, it has been carefully designed to preserve any code customizations you make.

Yaktor was originally prototyped with a Spring Roo mapping, so it supported Spring-based Java technology. The Spring Roo mapping was later abandoned in favor of using Node.js and its npm-based ecosystem. Yaktor’s Node.js mapping is currently the primary (and only) supported mapping. The Yaktor team is considering other mappings, including Spring Boot-based Java, Akka/Scala, and others.

Yaktor leverages a feature-based design. The Yaktor team has authored features that can be added to existing Yaktor-based applications. For example, if your Yaktor Node.js application needs authentication & authorization, you can use yaktor-auth to add those features. Other development teams can add other Yaktor features as well.

2.2.1. What can Yaktor do?

Here’s a quick hit list of some of the cool things Yaktor can do. With Yaktor, you can

  • build applications easily that leverage interoperating state machines,

  • create & secure REST endpoints with a trivial amount of effort, and

  • effortlessly generate thorough application documentation, including state transition diagrams.

2.3. Requirements

There are two ways that you can run Yaktor applications:

The Yaktor team strongly encourages you to use the Docker approach. It eliminates many problems related to platform-specifics & dependencies, requiring almost no configuration aside from Docker itself.

2.3.1. Using Docker

The minimum requirement to use Yaktor with Docker is to have Docker 1.11 or later installed.

Yaktor is supported on Docker Toolbox 1.11 with VirtualBox or Dlite, or Docker for Mac, Docker for Windows, or Docker for Linux 1.12 or later. Running Yaktor on prerelease or other versions of Docker may work but is unsupported.

Once you have Docker installed and configured for your environment, continue to Installation.

2.3.2. Running Natively

The minimum requirements to run Yaktor applications natively on your platform include:

Additional, optional dependencies include

  • Cassandra 2.1 or later (for event stream storage)

Make sure that you have these dependencies installed and configured for your environment, then continue to Installation.

2.4. Installation

There are two types of Yaktor installations: Docker (recommended) & native.

2.4.1. Installation with Docker

"Installation" with Docker is almost a misnomer, thanks to the beauty of Docker. We’ve already prepared Docker images that contain all of Yaktor’s requirements & dependencies. If you’ve already gotten Docker installed & configured, then your Yaktor installation steps are complete. Move on to writing code!

2.4.2. Native installation

TODO: give pointers to installation instructions for dependent software here

2.5. Writing code

This section details how to get started authoring a Yaktor application.

2.5.1. Brand new Yaktor project

If you’re using Docker to create a new Yaktor project, see New Yaktor project with Docker.

If you’re running natively, see [new-native-yaktor-application]. New Yaktor project with Docker
$ mkdir myapp                                   (1)
$ cd myapp                                      (2)
$ curl \| sh              (3)
$ ./yak gen-src gen-views                       (4)
1 Create a directory to hold your Yaktor application.
2 Change into the Yaktor application directory.
3 Initialize a new Yaktor application in the current directory using the yaktor/node image.
4 Use the ./yak command to generate source & views.
You can issue command ./yak by itself to get help.

You might want to consider creating the following alias for convenience in the future:

alias yaktor-init='docker run -it -v "$PWD":/app --rm --entrypoint bash \
  --user node yaktor/node:latest -c '\''npm install yaktor && $(npm bin)/yaktor init'\'''

At this point, simply open a text editor in directory myapp and start poking around the code, or you can start & view your app in action.

You should consider using our eclipse plugin, which includes an intelligent editor for the Yaktor DSLs. New native Yaktor project
$ mkdir myapp                                  (1)
$ cd myapp                                     (2)
$ npm install yaktor && $(npm bin)/yaktor init (3)
$ npm run gen-src gen-views                    (4)
1 Create a directory to hold your Yaktor application.
2 Change into the Yaktor application directory.
3 Initialize a new Yaktor application in the current directory.
4 Generate source & views.

At this point, simply open a text editor in directory myapp and start poking around the code, or you can start & view the app in action.

You should consider using our eclipse plugin, which includes an intelligent editor for the Yaktor DSLs.

2.5.2. Coming aboard an existing Yaktor project

If you’re using Docker to work on an existing Yaktor project, see onboarding with Docker.

If you’re running an existing Yaktor project natively, see onboarding natively. Onboarding with Docker
$ git clone ...           (1)
$ cd ...                  (2)
$ ./yak gen-src gen-views (3)
1 Clone the source repository containing the Yaktor application via git, svn, etc.
2 Change into the Yaktor application directory.
3 Use the yak command to generate source & views.

At this point, simply open a text editor in your source directory and start poking around the code, or you can start & view the app in action.

You should consider using our eclipse plugin, which includes an intelligent editor for the Yaktor DSLs. Onboarding natively
$ git clone ...             (1)
$ cd ...                    (2)
$ npm install               (3)
$ npm run gen-src gen-views (4)
1 Clone the source repository containing the Yaktor application via git, svn, etc.
2 Change into the Yaktor application directory.
3 Install node modules.
4 Generate source & views.

At this point, simply open a text editor in your source directory and start poking around the code, or you can start & view the app in action.

You should consider using our eclipse plugin, which includes an intelligent editor for the Yaktor DSLs.

2.6. Starting

This section describes how to start & view a Yaktor-based application. This will be done either by using Docker or running natively.

2.6.1. Starting with Docker

If you’re using Docker, then starting your application, assuming you’ve already generated source & views, is as easy as

$ ./yak start
If you add literally ./node_modules/.bin to your PATH, you won’t have to include the ./ prefix to ./yak anymore.

At this point, you are ready to begin editing your Yaktor application. It’s crucial that you understand Yaktor’s DSLs and technology mappings, so make sure you check them out.

2.6.2. Starting natively

If you’re running natively, assuming you’ve already generated source & views, starting your application is achieved with

$ npm run start

At this point, you are ready to begin editing your Yaktor application. It’s crucial that you understand Yaktor’s DSLs and technology mappings, so make sure you check them out.

3. The Yaktor Sample Conversation Explained

Most Yaktor applications come with a sample conversation, of which this section provides an introductory-level discussion.

3.1. Code generators

Yaktor provides a set of code generators. We’ll focus on a subset here.

3.1.1. gen-src

The first code generator we’ll look at is gen-src, which reads the Yaktor DSL files and generates source code from them.

If you run Yaktor using the Docker image, the command is:

$ ./yak gen-src

A set of artifacts are being produced by this command. We’ll discuss these in a different article. For now, we’ll just say that this command generates:

  • The Node.js code required to execute the agents' behaviors

  • Visualization of the language files

Because we’ll only explain the demo file in this article, we’ll focus on what is generated from the *.yc files, AKA "Yaktor conversation" files.

3.1.2. gen-views

The gen-views command is used to generate user interfaces. It can be run as:

$ ./yak gen-views

3.1.3. start

You can start the server after having completed the code generation commands as follows:

$ ./yak start

3.2. Study the conversation language file

Use your favorite editor to open the demo.yc file.

You should see a complete listing looking something like this:

 * This is a simple demo of Yaktor.
 * The demo describes two simple agents:
 * - Switch. Represents a simple switch that when receiving a command to 'flip' turns itself on or off
 * - Outlet. Represents some electrical outlet controlled by the switch
conversation demo {

   * The Circuit is what we call a `conversation type`.
   * Agents collaborate over the same instance of an object; here, the object is an instance of a Circuit.
  message Circuit {
    String name!

   * A simple switch agent
  infinite agent Switch concerning Circuit {
    sends turnOn
    sends turnOff

    initially becomes off {
      on {
        flip -> off > turnOff
      off {
        flip -> on > turnOn

   * A simple electrical outlet agent
  infinite agent Outlet concerning Circuit {

    initially becomes off {
      off {
        Switch.turnOn -> flowing
      flowing {
        Switch.turnOff -> off


The file above defines a Yaktor conversation, the top-level declaration in the file.

conversation demo {
// content goes here

You may also note that the conversation language allows two kinds of comments:

/* c-style comment */
// comment to the end of the line

The conversation defines a collaboration between two agents, Switch and Outlet.

Let’s start by studying the agent Switch. Agents are defined with the keyword agent.

In our example:

infinite agent Switch {
  // definition of the agent

The infinite qualifier in front of agent means that this agent doesn’t have an ending state; once created, we expect it to exist forever.

The Switch agent has two states:

  • on

  • off

We define these states in the Switch agent as follows:

initially becomes off {
  off { /* ... */ }
  on { /* ... */ }

The statement initially becomes off states that the agent will be in the off state when first created (usually called the "initial" state).

The Switch agent changes state on the event flip.

Which events the agent produces and consumes are typically defined in the body of the agent. The agent Switch defines the following events:

sends turnOn
sends turnOf

sends means that the agent produces an event that itself or other agents reacts to. The switch sends two events, turnOn and turnOff.

The next thing to look at is the definition of a state. Let’s look at the definition of the state on.

on {
  flip -> off > turnOff

We are using a shorthand notation here. Another way to write the same thing would be:

on {
  receives flip becomes off sends turnOff

In other words, we’re saying "if an instance of the agent Switch is in the state on and it receives the event flip, it changes state to off and sends the event `turnOff`".

flip is an event that the agent or its user-agent (like a UI), will internally produce and consume; it will never be visible to other agents in the conversation. This makes sense for flip, as the switch does not expect to receive this event from any other agent. We could, for instance, imagine that we’ll build a mobile app with a button labeled Flip. This mobile app would act as a user-agent to for the agent Switch.

Now, let’s look at the complete agent definition again:

infinite agent Switch concerning Circuit {
  sends turnOn
  sends turnOff

  initially becomes off {
    on {
      flip -> off > turnOff
    off {
      flip -> on > turnOn

A natural language description of the agent Switch would go something like this:

  • A Switch is an agent that collaborates with other agents on the topic of an instance of a Circuit.

  • When created, it starts in the off state.

  • The Switch reacts to the event flip and produces the events turnOn and turnOff.

  • If the Switch receives the flip event when in the on state, it changes state to off and produces the event turnOff.

  • If the Switch receives the flip event when in the off state, it changes state to on and produces the event turnOn.

The agent Outlet, representing an electrical outlet wired to the Switch, has a definition similar to that of agent Switch. What is new is that the Outlet reacts to events produced by the Switch. Note the following definition in the Outlet state:

off {
  Switch.turnOn -> flowing

This definition could also have been written as:

off {
  receives Switch.turnOn becomes flowing

What we are defining is a collaboration between two agents. We’re saying that when the Switch produces the event turnOn, we will change our Outlet state to flowing.

3.2.1. Running the sample

Let’s see the conversation in action. If you haven’t already done so, we have to run the code generators and start the server.

You can do them all with this command:

$ ./yak gen-src gen-views start
The first time you run gen-views might take some time.

If you’re on Mac OS X/macOS, your app should be available at http://www.<appName>.yaktor. For example, if you named your application myapp, try http://www.myapp.yaktor.

If that doesn’t work, you will see the IP of the running application in the console log. With Docker, it would typically be something like 172.x.x.x. Without Docker, it’ll probably be http://localhost:3000.

Next, simply open the browser on the IP with the path /demo/test.html, so either http://www.myapp.yaktor/demo/test.html or, say, if that’s the IP that Docker assigned to your Yaktor container.

Now you should see an HTML form with the two agents, Switch and Outlet.

Click on the button connect followed by Init All.

You should now see two state machines, one for each agent. You should also be able to click on the button to flip the Switch agent with the event flip.

If you now see the Outlet change state as a result of flip, we have a working application!

4. Overview of Yaktor Domain-Specific Languages & Technology Mappings

This section only describes the high-level concepts behind a Yaktor application. For a comprehensive treatment, see XXX.

4.1. Yaktor domain-specific languages

Yaktor includes two domain-specific languages (DSLs) used to capture & express much of your business domain’s data & logic. These DSLs enable Yaktor to remain independent of the underlying implementation technology. They serve as input not only to generate the application’s implementation using a particular technology stack, but also to generate incredibly useful documentation, even for nontechnical business analysts, which is one of Yaktor’s great strengths.

Yaktor includes code generation features in order to support rapid application development. One of the key design feature’s of Yaktor’s code generation facilities is to explicitly accommodate custom code. As such, Yaktor will always preserve your code customizations.

Yaktor’s DSLs consist of

Both languages, like most DSLs, are declarative in nature, not imperative. In other words, they don’t describe how something is implemented, they describe what is to be implemented. It is very useful to think of them as a specification of data structures & conversational behavior, respectively.

4.1.1. Yaktor domain modeling DSL

Yaktor supports the definition of a canonical information model, which Yaktor calls a domain model. In a Yaktor-based application, the domain model is intended to be composed of data structures, called entitities, that

  • have an id,

  • are persisted to a datastore,

  • have primarily schema-oriented validation logic, and

  • are devoid of behaviorally rich business logic.

Having said that, Yaktor does allow you to add arbitrary instance & static methods to your entities, but that must be done in the language of your current Yaktor technology mapping, due to the aforementioned declarative & technology-agnostic nature of Yaktor’s DSLs.

The key point here is that the state of your business data is described by Yaktor’s domain modeling DSL.

Yaktor domain models are optional; you can author Yaktor applications with no underlying entities if you so choose.
For more information on Yaktor’s domain modeling DSL, see the Domain Model DSL Reference.

4.1.2. Yaktor conversation DSL

In a Yaktor-based application, much (but not necessarily all) of the application’s behavior is manifested conceptually as conversations. Conversations are just what you might intuit: a collaboration between multiple parties (called agents) on some topic (usually a data type).

The key point here is that the behavior of your application is described primarily by Yaktor’s conversation DSL. For now, just know that the conversation DSL provides for the specification of a collection of types, endpoints (like REST endpoints), and agents. Agents may have state machines and may source & sink events.

For more information on Yaktor’s conversation DSL, see the Conversation DSL Reference.

4.2. Technology-specific mappings

Since Yaktor is based on technology-agnostic, declarative DSLs, it needs to eventually do something. To do something, you have to have an implementation using some technology stack. In Yaktor, a technology mapping is what provides that.

While Yaktor was prototyped initially with a Spring Roo mapping, the current primary (and only supported) mapping is Node.js.

Writing a technology mapping is a big deal. The Yaktor team, due to limited resources, chose to focus on a Node.js stack, using JavaScript (ok, ECMAScript) & including Express, MongoDB & Mongoose. However, the team will continue to consider other mappings, in particular, Spring Boot, Akka/Scala, and others.

Although it would be ambitious, anyone could create a Yaktor technology mapping.

4.2.1. Node.js mapping

The Node.js mapping is Yaktor’s primary mapping. This section attempts to give a brief treatment of a project’s source organization & how the Node.js code generation technique supports code customization.

See here for the full Yaktor Node.js mapping reference. Source organization

TODO: briefly describe the directory structure of a Yaktor app after generating source & views Feature-based modules

Yaktor leverages a design whereby functionality can be orthogonally added to a Yaktor application on a feature-by-feature basis. Currently, there are two officially supported Yaktor feature modules of the Node.js mapping: Cassandra event streaming persistence

All events and state transitions that take place within Yaktor conversations can be persisted. This module persists this information into a set of Cassandra tables. This provides you with the ability to replay conversations for recovery or offline analysis.

Before adding features to your Yaktor application, ensure that it is under source control so that it’s easy to roll back the code if necessary. A simple git init followed by git commit -a -m "bang" would suffice; Yaktor provides a .gitignore file out of the box for your convenience.

To add this feature, change into your Yaktor application’s root directory and issue command

$ ./yak cassandra Security

Many web applications need authentication & authorization in order to prevent unauthorized access or use. Yaktor’s Node.js mapping offers yaktor-auth, a feature module that adds Passport-based security to your Yaktor application.

Before adding features to your Yaktor application, ensure that it is under source control so that it’s easy to roll back the code if necessary. A simple git init followed by git commit -a -m "bang" would suffice; Yaktor provides a .gitignore file out of the box for your convenience.

To secure your Yaktor application, change into the application’s root directory and issue command

./yak npm install yaktor-auth
./yak yaktor-auth secure

5. Yaktor Architectural Overview

This section provides a high-level, conceptual overview of Yaktor’s architecture.

5.1. Software agents

TODO: briefly describe agents (& their predecessors, actors, here)

5.2. State machines

TODO: briefly describe a state machine

5.3. Lambda architecture

TODO: briefly describe the lambda architecture & provide pointers.

5.3.1. Event stream

TODO: briefly describe how Yaktor events & messages are processed.

5.3.2. Data persistence

TODO: briefly describe how domain models are persisted.

5.4. Core platform

TODO: briefly describe the features of the core platform.

6. Yaktor DSL Reference

This section contains the complete reference of the Yaktor DSLs.

6.1. Yaktor Domain Modeling DSL Reference

6.1.1. Introduction

Yaktor is a tool that leverages two domain-specific languages: a domain modeling language and a conversation language. This document describes the domain modeling language.

In almost every software system there is a need for defining an information model. In most cases, these information models are the foundation for the schema for the persistent storage. Yaktor calls these models domain models.

In addition to providing the foundation for the schema governing persistence, the domain model is often created to share the vocabulary and key concepts, or "ubiquitous language", of the problem domain.

Although some modeling techniques allow the domain model to contain behavior, Yaktor’s domain model is generally devoid of behavior. The reason why Yaktor favors behaviorless domain models is purely pragmatic; data structures tend to be independent of the underlying implementation, whereas behavior is typically imperative and is influenced by the implementation language.

With this, perhaps a better name for Yaktor’s domain model is "information domain modeling language" or "logical information model", but we’ll stick to simply "domain modeling language" here.

The simplest way to think of the domain modeling language is that it allows you to define the rules for a data graph. This data graph contains objects, properties and relationships.

  • Object: An object is a typed tuple of information that can be referenced from other objects. The object has unique identity; that is, in the domain, it is important to be able to refer to the object uniquely. If you are familiar with graph theory, think of the object as a vertex or node in a graph. Examples include a person with name "John Smith" or a company with name "SciSpike".

  • Relationship: A relationship is a typed association between two objects. In graph theory, this would be called an edge. One important restriction between Yaktor’s relationships and graph edges is that relationships deliberately do not have properties. Yaktor takes the stance that if edges had properties, then they’d be indistinguishable from objects.

  • Property: A property defines some datum that partially describes an object. Properties may be arbitrarily complex, but they may not contain relationships.

Here are some parallels to other modeling techniques:

  • Unified Modeling Language (UML): If you know how to model in UML, think of the models we create as UML class diagrams where there are no methods.

  • Entity-Relationship Model (ERD): If you know how to model databases using ERDs or data definition language (DDL), our domain models are similar except for a few important differences.

    • Yaktor supports relationships natively; foreign keys are unnecessary.

    • Yaktor supports single inheritance among types. That is, a type may be derived from exactly on other type.

Since the goal of Yaktor’s domain modeling DSL is to reuse domain models across different technologies, it attempts keep the model platform-independent.

6.1.2. Defining a Domain Model Introduction

The domain model is defined in a simple text file that conforms to a set of grammar rules. Yaktor domain model files can have any extension, but the preferred extension is .ydm.

Although you may use any text editor you prefer to edit this file, the Yaktor team strongly recommends that you use the editor provided by Yaktor. It is provided as an eclipse plugin, available at

The editor will parse the file interactively and mark any errors you may have. It also provides autocompletion, syntax highlighting, and an outline view, which are quite useful. Declaration

The domain model starts with the declaration domain-model followed by the name of the domain model, then the definition of the domain model in curly braces.

domain-model MyDomainModel {
    // The definition of the model will go here
} What Is Contained in the Domain Model?

The domain model allows you to specify the following type of elements:

  • Entities. A type defining kinds of objects.

  • Associations. A type of relationship between objects.

  • Types. A tuple of fields, possibly based on an entity.

  • Enums. A type with fixed values defined at design time.

Let’s take a look at an example:

domain-model MyDomainModel {

    type Address {              (1)
        String street!          (2)
        String postCode!
        String place!

    entity Pet {                (3)
        String name!            (4)

    entity PetOwner {
        String name!
        Address address?        (5)

    association PetOwnership {  (6)
        PetOwner owner!   (7)
        <-->              (8)
        Pet pets*         (9)
1 type defines a data structure, consisting of properties of simple types or other type s.
2 This is a simple example of a primitive property on the type Address of type String called street. The exclamation mark means the that the street property is required; in other words, all properties of type Address must have a street.
3 entity defines a Pet as a persistent object. An entity can be a vertex in a graph.
4 Notice that we can attach simple properties to the entity directly…​
5 …​ or we can attach a complex type property like Address
6 To allow for links between objects, you can define an association.
7 This association starts at the PetOwner. It plays the role owner in this relationship, and all pet s have an owner, by virtue of the exclamation point.
8 The ←→ symbol defines the association to be bidirectional. If you wanted the association to be unidirectional from PetOwner to Pet, you would would use -→ instead.
9 The association ends in the Pet, which plays the role pets to the PetOwner. A PetOwner may have many pets, denoted by the cardinality indicator *, meaning "0 or more".

6.1.3. Entities

Basically an entity is something that you can retrieve (by id) from storage (like a Database or Document Repository). Entities are made of Fields.

6.1.4. Types

Types are another basic construct which in a simular sense to Entities encapsulate Fields. However Types may not be directly retrieved. They can more-or-less be seen as a logical grouping of fields, which are otherwise fully contained within an enclosing Entity.

6.1.5. Fields

Fields can express value, reference, or composition. Value fields are either of Primitive Types or Enums. reference are made using ref to a Entity. Finally, compostion is made by using a named type construct to a Type Primitive Types

Both an entity and a type may define properties. Properties may be a type or one of the following primitives:

  • Amount. A monetary value.

  • Any. Untyped information.

  • Boolean. A literal true or false.

  • Count. A positive integer value.

  • Date. An instant in time.

  • EntityReference. A reference to an entity.

  • GeoLocation. A map location.

  • Id. A type which is suitable for the target environment to be auto-generated and unique (system-wide).

  • Integer. An integral value.

  • Numeric. Any numeric value, including decimal values.

  • Price. A price.

  • ShortId. A generated user friendly identifier.

  • String. A sequence of characters.

Properties may define further constraints on their values. Cardinality

All properties may define a cardinality. The cardinality is defined using a suffix on the property. We currently support the following cardinalities:




Optional: zero or one. The field may or may not be present.


Required: exactly one. The field must be present & non-null.


Many: zero or more. The field can contain a collection of any number of values, including 0, or it may be null or not present at all.


At least one: one or more. The field contains a collection of at least one value. Uniqueness

Each of the properties may define uniqueness. This is simply defined with the keyword unique succeeding the property definition.

entity Person {
    String ssn! unique      (1)
    String name!
1 The ssn property is defined to be unique. That is, there should not be two instances of entity Person with the same ssn value. Constraints on Strings

String can be constrained based on a regular expression or by simply defining its length. Reg-Ex Constraints
entity Person {
    String ssn! pattern "^\\d{3}-\\d{2}-\\d{4}$"    (1)
1 The constraint on the ssn property is to be a valid US social security number.

Notice that to constrain a string based on a reg-ex pattern, we simply provide the keyword pattern followed by a quoted string.

When using regular expressions, backslashes must be escaped. String Length Constraint
entity Person {
    String lastName! [2..100]   (1)
1 The last name has to be at least 2 characters but no more than 100

To specify the string length, use the following format:

[ min .. max ]

The min and max values are optional.

Here are some examples:

  • [..1000]: the string must be less than 1000 characters in length.

  • [2..]: the string must be at least 2 characters in length and as long as the underlying platform allows. Constraints on Dates

The dates can be constrained to the past or future relative to the current system time, or to a specified date range. Past and Future
entity Person {
    Date birthdate! past     (1)
    Date nextFollowup future (2)
1 The birthdate property must be in the past.
2 The nextFollowup property must be in the future. Specifying Date Ranges

TBD (we’re using the square bracket, but we don’t seem to use it right now. We need to define the format of the date…​) Constraints on Integers and Numerics

Integer only supports a simple constraint defining a range.

entity Person {
    Integer age [0..150]        (1)
1 The age is constrained to be a value between 0 and 150 (inclusive).

To constrain an integer or a numeric to specified range, use square brackets and the format:

[ min .. max ]

The min and max values are optional.

6.1.6. Keys

For some models, you may not need to concern yourself with keys; in general, an id field appropriate for the target environment will be created automatically. However, you may want to control the name or type of the id field throughout the domain. To do so, simply define a unique key as follows:

entity SomethingWithId {
    Id id                  (1)
    key (id)               (2)
1 A field of any type or name, representing the unique key.
2 Describe which field you want to be the key of the entity.

6.1.7. Enums

The language also allows you to define enumerated types. An enumerated type allows you to define a domain consisting of a set of named values.

enum Gender {                                         (1)
    M = "Biological Male Self-Identifying as Male"    (2)
    F = "Biological Female Self-Identifying as Female"
    MF = "Biological Male Self-Identifying as Female"
    FM = "Biological Female Self-Identifying as Male"
    HM = "Hermaphrodaic Self-Identifying as Male"
    HF = "Hermaphrodaic Self-Identifying as Female"
    A = "Ambiguous"
entity Person {
    enum Gender gender!                                (3)
1 Declare an enumerated type called Gender.
2 Define the only valid values of the enum Gender.
3 The Person type defines that it has a gender field of type Gender.

Enumerated types are introduced using the keyword enum. The enumerated values are defined in the following format:

Symbol = " Value "

Enumerated properties can be used in an entity and a type.

Enumerated properties on an entity must be prefaced with enum. On a type, simply use the value; there must be no introduction.

6.1.8. Associations

Associations can be defined two ways:

  • by an explicit construct, using the keyword association (recommended), or

  • by using a reference property with keyword ref.

Although explicit association declarations are recommended, there are domains where the use of ref properties may be more natural. Using the association keyword
domain-model MyDomainModel {

    entity Pet {
        String name!

    entity PetOwner {
        String name!

    association PetOwnership {      (1)
        PetOwner owner!             (2)
        <-->                        (3)
        Pet pets*                   (4)
1 Define an association named PetOwnership.
2 Define the originating type to be the PetOwner and the role it plays to be owner.
3 ←→ indicates that the association is bidirectional.
4 Define the destination type to be the Pet and the role it plays to be pets.

For most platforms, this distinction is typically not important, like in a relational database. However, on some platforms, like graph databases, the direction of the association may be significant.

You can also define unidirectional associations. Unidirectionality may end up being just a hint to the code generator, but the implied semantic is:

  • I need to resolve the destination object(s) when I know an instance of the originating object.

  • I don’t need to resolve the originating object(s) given an instance of destination object.

As mentioned, this would be nonsensical in a relational database, but it may very well be a good hint for when you want to generate some object-relational mapping. We may want to be able to lookup the pets from the pet owner, but not allow for pets to effectively know their owner directly.

If we wanted to define this, we would have to change the association as follows:

association PetOwnership {
    PetOwner owner!
    -->             (1)
    Pet pets*
1 Using the -→ associator to indicate that we believe the lookup would be only from owner to pets Using the ref keyword

We said that another way, although not preferred, to define associations is to use references via the keyword ref. Let’s say we want to define the same association as befroe (PetOwner to Pet). We could do so by simply defining an property on either side.

domain-model MyDomainModel {

    entity Pet {
        String name!

    entity PetOwner {
        String name!
        ref Pet pets*               (1)
1 Define the PetOwner will have a property which is a reference to all of its pets.

An alternative, of course, would be to define the reference on the Pet side:

domain-model MyDomainModel {

    entity Pet {
        String name!
        ref PetOwner owner!         (1)

    entity PetOwner {
        String name!
1 Define the relationship on the pet side of the association. Although somewhat imprecise, this indicates that the pet maintains a reference to its owner.

This method may be appealing to someone accustomed to a language with no explicit support for relationship (for example, Java & C++, that only have unidirectional references or pointers, respectively). However, it is important to understand that we are providing a rather incomplete association and that the code generator has to use the implementation for most general case.

When using references, we are not specifying the role or multiplicity of the destination side. In other words, when we read the examples above, one doesn’t know the multiplicity of the non-specified case.

Let’s take the case where the PetOwner defines the reference to his pets.

  • Can a Pet have multiple owners?

  • Can a Pet not have an owner?

In the case when we defined the association explicitly, even if it is unidirectional, we had to make a decision.

Because we have not specified the opposite cardinality, the code generator typically has to assume the most general case. In our example, the generator would use:

association PetOwnership {
    PetOwner petOwner*    (1)
    -->                   (2)
    Pet pets*
1 We don’t really know the cardinality or the role name. We use the more general case * ("0 or more") and guess that the role name is the same as the type petOwner in this case.
2 Unidirectional: navigability is from petOwner to pets

Hopefully that has convinced you to use the association form when you have options.

There is, however, a case where the ref construct is useful. This is when you have to provide a link to an entity from within a type.

As we mentioned before, we can only link from one entity to another entity. However, sometimes, we may want to have a simple property on a type that refers to another entity.

6.1.9. Composite Uniqueness

Sometimes we want to express that a combination of properties makes an entity unique. Let’s say for instance that we have a location where we want the city name and state to be unique. For example, since Rochester exists in both Minnesota and New York, the city name and state combination should always be unique.

entity City {
    String name!
    String state!
    unique-constraint name state    (1)
1 The uniqueness constraint specifying that the combination of name and state should be unique

To specify the cross-field uniqueness constraint, you must define the following:

unique-constraint property1 property2 …​

unique-constraint must be the last declaration in an entity body.

6.1.10. Index Hint

You can define in the domain language that one or more properties shall be indexed. The indexing is just a hint to the code generators. To suggest the use of indexing, simply add the keyword indexed to the property.

entity City {
    String name! indexed    (1)
    String state!
1 Suggest to the code generators that name should be indexed.

6.1.11. Partition of Models

Sometimes it is practical to split the model into several files. If you want to use a model from another model, you will need to import it. To import another model, you have to declare import statements at the top of your file in the following syntax:

import ModelName

Wherever you use a type or entity from the foreign model, you’ll have to fully qualify its name by using the following syntax:

ModelName . TypeName

Here is a simple example:

import ForeignModel                             (1)

domain-model MyModel {

    entity MyEntity {
        ForeignModel.ForeignType someField!     (2)

    association SomeAssociation {
        MyEntity m?
        ForeignModel.ForeignEntity fe!      (3)
1 Import another model called ForeignModel
2 Use the externally defined type called ForeignType in this model
3 Use the externally defined entity called ForeignEntity in this association.

6.1.12. Extensions

There are several features that only present themselves through the use of the extension section of the output configuration. Single Table Root

When you express a model with polymorphic entities, it may be advantageous to have the entire inheritance hierarchy persist to a single storage location (table, collection, column family, etc). single-table-root overrides the default behavior, which is to persist to a different location for all subclasses. When a entity (abstract or not) is specified as the single-table-root, all of its subclasses are persisted in the same location.

When using single-table-root, all fields of subclasses should be optional.
In MongoDB, schema enforcement is done on the client side. Therefore, you can have required fields in a subclass with single-table-root.
Single table root example
domain-model Folks {

    node-mongo-options {
      extensions {
        Person {
          single-table-root       (1)

    abstract entity Person {
      String superAttr

    entity Superhero extends Person {
      String strength?

    entity Employee extends Person {
      String badgeId              (2)
1 Specify that there should be a single storage location for Person and all of its subclasses.
2 This takes advantage of the fact that if you’re using MongoDB, you can have a required field on a subclass even with single-table-root. Time To Live

Another feature of some stores (like MongoDB) is to have records deleted at a prescribed date. This is achieved by specifying a Time To Live (TTL) index. Basically, there is a background task run on the storage engine which cleans up records which have outlived their TTL. In some stores, TTL can be expressed as seconds past the date value of a field.

Fixed time to live example using MongoDB
domain-model FixedTimeToLive {

    node-mongo-options {
      extensions {
        Session {
          ttl expires 0       (1)

    entity Session {
      Date expires            (2)
1 We wish to delete records at a specified instant.
2 A field whose value specifies when the record should be removed.
Relative time to live example using MongoDB
domain-model RelativeTimeToLive {

    node-mongo-options {
      extensions {
        Session {
          ttl born 86400    (1)

    entity Session {
      Date born             (2)
1 We wish to delete records after a specified time has passed.
2 A field whose value plus the ttl seconds could indicate when the record should be removed.
One advantage of the ttl expires 0 example is that it leaves the lifetime of a record as a runtime decision, whereas, with the ttl born 86400 pattern, the lifetime is determined at design time.

6.1.13. Graphical View of the Model

As you build up your model, the editor (assuming you’re editing in Eclipse) will keep your model synchronized with a graphical view. The graphical view is a UML class diagram in standard DOT notation. You’ll find your model under your project root in the directory src-gen/dot/domain/${DomainModelName}.dot.

There are multiple tools that can render this notation. We’ve tested with and recommend GraphViz, a free tool.

6.2. Yaktor Conversation DSL Reference

6.2.1. Introduction

Yaktor is a tool that leverages two domain-specific languages: a domain modeling language and a conversation language. This document covers the semantics & grammar of the conversation language.

Conversations are defined in a text file. The default extension is .yc. They are simple text files which abide by the conversation grammar.

You may edit the text files with any text editor, but we recommend that you use the Eclipse editor provided by Yaktor, which will provide you with autocompletion, syntax highlighting and other productivity features.

For information regarding runtime concepts please see Artifacts and Concepts The conversation language refers to another language, the Yaktor Domain Modeling DSL.

6.2.2. Conversation Collaborative Distributed Behavior

In the space of event-driven, asynchronous, distributed, multi-party, state-based behavior, there is a significant gap in the ability to describe and constrain interaction between moving parts. Practically speaking the world of event based programming leans either toward the tight coupling of traditional pub/sub (coupled responses) or the loose coupling of a virtual chat room.

Toward that end, we provide both a communication grammar and conditional state-based behavior, allowing user-agents, like web clients or web-enabled devices, to connect to well ordered back-end services, receive data/updates, and indicate choices and activity. Collaboration is facilitated by the Yaktor engine is predictable and as well as visually verifiable. It also allows for the use of rich feature sets afforded by standard protocols like AJAX, WebSockets & MQTT.

In the end, what we achieve predictable pub/sub without the coupling. Conversation Example

Lets get started with some examples.

conversation MyConversation (1)
1 The conversation keyword followed by the name of the conversation.
2 We’ll define the conversation inside the curly brackets

The above example is a non-populated conversation. A .yc file always starts with the conversation keyword, followed by the name of the conversation (MyConversation in our example). The definition of the conversation itself is defined inside curly braces.

A conversation can define a set of agents. An agent is a participant in a transaction that has at least the following features:

  • A definition of a set of events

  • A context, topic or subject of an type

  • A definition of its state machine

Optionally, you may also define a set of dependencies and type definitions. More about those later.

6.2.3. Agents Event Driven Behavior

Agents were designed to implement both the behavior and state of a system. As such, they expose a rich state model and event modeling facility. Let look at the anatomy of an agent more closely. Agent Anatomy
conversation MyConversation {

    infinite                       (1)
      agent SomeAgent              (2)
      concerning SomeType          (3)
    {                              (4)

1 optional The lifecycle for this agent: infinite or finite.
2 The agent keyword followed by the name of the agent (SomeAgent) defines a participant in the conversation.
3 The data type that this agent collaborates over.
4 (Everything up to the leading { is normally declared on a single line)
5 The definition of the agent (details to follow). Lifecycle

All agents have a lifecycle. The agent’s lifecycle is either finite or infinite.

  • An agent is finite if and only if it has at least one reachable end state.

  • If there are no reachable end states, the agent must be declared as infinite.

Use an infinite agent if the agent instance should stay around forever after being instantiated. Use a finite agent if the agent represents handles some task that can be completed.

Sometimes, you have the option to design conversations with either finite or infinite agents. If given a choice, we recommend to design the conversation such that it uses finite agents. Agent Data Type

Typically, agents collaborate around, about, on or over particular data. Each agent references some type.

The type of collaborating agents must be related. The rules are:

  • If the data types are identical, the case is trivial (not discussed below).

  • Two agents may declare different data types even though they participate in the same conversation.

    • If the types are different, they must be derivable/mapped to each other (via inheritance or composition).

      • Composition will be evaluated using a mapping on the causing side of an interagent transition. This mapping may evaluate against the event data or the type. If no mapping exists, the type’s inheritance will be inferred.

  • If the types are not derivable or mapped, then the agents will not receive massages from each other. Event definitions

An agent may define a set of events. An event is a definition of some stimulus that the agent reacts to or produces for other agents to react to or take action on.

An event is defined by:

  • Its name

  • The nature of the event in relationship to the agent: sends, receives or privately receives.

    • sends means that the agent broadcasts the event. Any other agent may react to this event.

    • receives means that an agent can receive this message from another agent.

    • privately receives means the event is either a decision value made by the agent or a command that the agent obeys. A command is typically produced by a "user-agent", which is any external program, UI or system that acts upon or on behalf of the agent.

  • Optionally, the type of the event. More on this later.


conversation MyConversation {
    agent SomeAgent {
        sends eventTypeA              (1)
        receives eventTypeB           (2)
        privately receives eventTypeC (3)
1 SomeAgent sends (or broadcasts) events of type eventTypeA.
2 SomeAgent receives events of type eventTypeB.
3 SomeAgent privately receives eventTypeC. That is, the agent may react to events of type eventTypeC or obeys commands of type eventTypeC originating from its user-agent. State Machine

An agent also defines a state machine. The state machine defines an external guarantee for how the agent behaves. The LightBulb as described here would be expecting other agents (yet to be defined) to trigger turnOn and turnOff, and yet not know who did it.

We typically place the definition of an event where he have the most specific details, but you have the freedom to choose. Often if you consider the analog as well as the data requirements it is easy to see who owns the definition. If all else fails, choose consumes and refactor when you find multiple consumers. Multiple producers and consumers requires an additional agent to act as mediator.

conversation MyConversation {
    agent LightBulb {
        receives turnOn
        receives turnOff
        privately receives fail

        initially becomes off {  (1)
            off {                (2)
                turnOn -> ^on    (3)
                fail -> broken
            ^on {                (2)
                turnOff -> off
                fail -> broken
            broken {}            (2)
1 We use the keywords initially becomes to introduce the state machine as well as the initial state the agent. In the example, a LightBulb begins in the off state.
2 Declares states off, on, broken. Notice that the state the state on is written as ^on, because on is a conversation DSL keyword. To escape the keyword, you use ^.
3 Defines a transition. A transition is defined as eventName resultingState. In this example, we are saying that "If the LightBulb is in the state off and the turnOn event is received, then the LightBulb will change to the state on." We are using an abbreviated syntax here. An alternative syntax is perhaps more descriptive. You could have written the same statement as turnOn becomes ^on Dependencies

It is possible for agents to subscribe or produce events to agents defined in other conversations. If they do, they have to declare their dependency on the other agents. It is important to note that you only have to declare dependencies on external agents, which are agents defined in other conversations.

conversation MyConversation {
    imports {
      agent SingleSwitchCircuit.PowerSwitch as Switch (1)
    agent LightBulb {

        initially becomes off {
            off {
                Switch.turnOn -> ^on                  (2)
            ^on {
1 Here we have declared an imports block and declared a dependency on an agent called Switch.
2 Because we imported Switch, we are now allowed to consume events in Switch. Agent Example Description

In this example we have defined two agents:

  • Switch

  • LightBulb

The two agents have been defined in separate conversations. We may have preferred to define both in the same conversation, but without doing so, we would not be able to show the definition of dependencies.

Both agents have a simple state machine (basically they are either on or off, but we added broken also to LightBulb).

There is an event dependency between the two agents. We want LightBulb to change state when the switch state is changed. We have specifically chosen to reverse the sends/receives relationship of turnOn and turnOff to convey the notion of data flow and control of Switch. Switch
conversation s {
    agent Switch {
        sends turnOn              (1)
        sends turnOff             (1)
        privately receives on                  (2)
        privately receives off                 (2)

        initially becomes off {
            off {
                on -> on > turnOn       (3)
            on {
                off -> off > turnOff    (3)
1 Switch sends events that LightBulb may receive (when it is turned on or off)
2 Switch is capable of telling when it has been turned on or off
3 We are also specifying the event transitions, but notice that we now have an additional syntax construct >. This specifies when the Switch produces such events. LightBulb
conversation lb {
    imports {
        agent s.Switch                (1)
    agent LightBulb {
        privately receives broken

        initially becomes off {
            off {
                Switch.turnOn -> on   (2)
            on {
                Switch.turnOff -> off (2)
                broken -> broken
            broken {
1 LightBulb has to declare a dependency on Switch to be able to subscribe to the events it produces.
2 LightBulb changes state when Switch produces the events turnOn or turnOff.

6.2.4. Data Structures The Problem

An event may carry information. To ensure compliance (that is, a consistent interpretation on both the producer and the consumer side), we have to be able to specify the structure of this information.

We’ll refer to the definition of this data structure as a data transfer object, or DTO.

It is important that DTOs are based on some canonical information model for the complete system. The reason for this is to ensure that related information is kept consistent.

For this reason, we have also created a language for defining these canonical information: the domain modeling DSL. Often, DTOs are projections of your domain model’s entity data. You can define DTOs based on your entities' definitions, define them independently, or mix & match according to your needs.

The type language should be quite trivial to most programmers. It is fairly consistent with the way you would define data structures in other languages. The type language supports

  • definition of types,

  • definition of data fields,

  • definition of a hierarchical structure (but no support for graphs),

  • definition of cardinality of fields,

  • definition of constraints on fields,

  • containment of other types, and

  • referencing entities from the domain model.

References to entities are supported via the ref keyword. You can also base a DTO directly, either partially or wholly, on the definition of an entity with the from keyword. Defining a type

You may define the rules for a data structure. This data structure

  • is an external facing contract of an agent, and

  • may map to an entity

Let’s start by showing an example:

conversation MyConversation {

    type SomeEventData { (1)
        Date whenSent! (2)
        String someMessage? (3)
        val SomeOtherData other* (4)

        val Domain.SomeType someDomainType {} (5)
    type SomeOtherData {} (6)
1 The declaration of a type (here called SomeEventData)
2 Example of a declaration of a Date field. The exclamation mark means that the field is required.
3 Example of a declaration of a String field. The question mark means the field is optional.
4 This field contains an array of contents as defined by another DTO; this one happens to be empty.
5 This field contains content as defined by a domain entity.
6 SomeOtherData type convenient for the other field. Fields

A type contains a set of fields. A field is constrained by the same language as defined in the domain modeling DSL.

In short you can use

  • its type (Date, String, etc.),

  • its name, and

  • various constraints, including

    • cardinality (required, optional, many, one or more), or

    • value constraints (regex for strings, value ranges for integers, etc.)

The format of a field is

TypeName fieldName [cardinality] [constraints] DTOs Based on Domain Model Entities

The most typical way to define DTOs is to base the DTO on an entity in your domain model.

When you’ve identified which entity in the domain model you want to base your DTO on, you now need to figure out which fields you want to include from the entity. We declare mapping and projections in the following way.

Projections are defined on any reference in the domain model. A projection mapping may be empty represented by {}, which results in mapping to all of the fields in a "flat" manner, meaning all fields only at the topmost level of the entity. This will include references as identifiers, but will not recurse downward. types will be expanded as though they were named with {}.

A projection mapping which includes at least one field will restrict the projection to only include the mapped fields. If you want all fields, you will need to map all of them, or have an empty projection mapping.

A mapping to a reference field without declaring a submapping will result in just a reference (containing the entity’s id).

A mapping to a type must be followed by {}. If you wish to limit or deepen the mapping, fill in the {}.

Mappings to a reference field which lack {} will be mapped flat.

You may establish a reference to an entity with a ref field. Which will always be a flat reference.

You may establish containment of a type with a val field with {} same rules as above.

So, let’s start with a simple domain model

domain-model MyDomainModel {
    entity Company {
        Address address
        String name!
    entity Opening {
        Date startDate?
        String name!
        String description!
    entity Candidate {
        String firstName!
        String lastName!
        String resume!
    association Company2Openings {
        start Company company!
        end Opening openings*
    association Opening2Candidates {
        start Opening opening!
        end Candidate candidates*

Now, let’s say we have the need to define a DTO that sets up a position. We have a domain model, called Opening that we can use as the base.

conversation HR {
    type NewOpening from Opening {} (1)
1 A DTO called NewOpening which is derived from the domain object Opening.

We would now expect a NewOpening to follow the exact same top-level structure as Opening. In other words, it would be the same as if we defined a type as follows:

conversation HR {
    type NewOpening from Opening {
        startDate (1)
        name (2)
        candidates (3)
1 mapping of the Date field startDate
2 mapping of the String field name
3 mapping array of references (ids) for candidates

What if we only wanted to bring in a subset of the properties from Opening and alter the presentation? Say we only wanted the startDate and rename name. No problem, we can simply open some curly braces and define the mapping.

conversation HR {
    type AvailablePosition from Opening { (1)
        name as openingName      (2)
1 Specifies that we want to include the startDate and name fields
2 Specifies that we want to include the name field under the name of openingName

Now, perhaps we need to provide an attribute that does not exist in the domain model. Say for instance that we want to pass in some additional information

conversation HR {
    type AvailablePosition from Opening {
        String authToken! (1)
        ref Opening previouslyViewed* (2)
1 Specifies that we require a String field named authToken.
2 An optional list of references (key or _id) of the Opening entities.

Notice that in the examples so far we made shallow definitions of the DTO. That is, we did not include objects linked to the domain object. What if we wanted to see a position with all the candidates?

conversation HR {
    type AvailablePosition from Opening {
        String authToken!
        candidates {} (1)
1 Specifying that we also want to include all fields from candidates (i.e. populate candidates).

In the example above, we would get all the attributes of the candidate. What if we only wanted the first and last name of the candidate?

conversation HR {
    type AvailablePosition from Opening {
        String authToken!
        candidates {
            firstName   (1)
            lastName    (1)
1 Notice that we can simply specify any available field what we want from entity Candidate.

6.2.5. Defining Event Data

We’ve seen how we can define data structures. We said that the data structures would be used to define DTOs. The DTOs are used to pass information. Let’s now look at where we would use these DTOs. Typed Events

Each of the events may be typed. What we mean by that is that an event may carry data.

Say we have a conversation agent as below:

conversation HR {
    agent ApplicationHandler {
        receives jobApplicationReceived

        begins in idle {
            idle {
                jobApplicationReceived -> processingApplication
            processingApplication {

The jobApplicationReceived event would probably have to carry the information about the candidate.

We could now define a DTO and type the event as follows.

conversation HR {
    type JobApplication from Candidate {                        (1)
        opening {
    agent ApplicationHandler {
        receives jobApplicationReceived : JobApplication     (2)

        begins in idle {
            idle {
                jobApplicationReceived -> processingApplication
            processingApplication {

1 Notice that we’ve derived the JobApplication from the Candidate.
2 Here we type the event by simply adding : JobApplication

6.2.6. Remote Endpoints Resources

In general, resources allow you to CRUD a DTO. For example, a JSON PUT over HTTP.

Agents act on DTOs as well. However, we wish to reserve agents for more interesting logic. When your remote endpoint is not behavior-oriented but more-or-less data entry and retrieval tasks, use a resource. To make this more compelling we have baked in some best practices and reasonable default implementations for persistence logic and paging. That would be tricky at best over sockets and would pollute your event/state model with unnecessary stuff.

We’ve already seen how we can add DTOs, so let’s step right into it.

When we define resources, we generally intend CRUD to be consumed programmatically, but using the proper action along with text/html,application/xhtml+xml you can allow the consumption by a browser user-agent.
Resources may be implemented using a more complex protocol such as SOAP, but due to limitations of this spec there may be a number of assumptions required before this would work.
Religion Alert: We don’t necessarily worship REST, but recognize is practicality. However, we have followed sound principles which have proven useful to many. On the other hand, if you think some behavior makes sense as a resource, then go for it. Be on the lookout for services that are intended for non-event-driven behavior. Resource Example
conversation HR {

    type Application from Candidate {
        opening {
            company {
    resource                            (1)
      /applications                     (2)
      for HR.Application                (3)
      offers (read find)                (4)
      interchanges ( json ) (5)
1 The resource begins the declaration for an endpoint. A typical resource is all on one line.
2 The URL for this endpoint will be /application.
3 Defines type HR.Application will be the DTO interchanged across actions for this resource.
4 Lists the actions supported for this resource.
5 Lists the mime-types supported by this resource.

The resulting resource will * be available under the URL fragment of /applications, * produce and consume the DTO of HR.Application, * support the read and find actions (and therefore is read-only), and * produce and consume data according to the mime-type application/json. Resource Function Supported
Action HTTP Semantic



Allow for creation of new resources, or form action="post".



Allow users to read (or lookup) resources based on their id.



Allow users to modify a resource by posting updates based on their id.



Allow users to delete resources based on their id.



Provide a search API for the resource or, by extension, allow basic get functionality. Mime Types Supported
Keyword Type Semantic



Interchange JavaScript Object Notation (JSON)



Interchange XML



Interchange YAML



Interchange html (ususally produced but not consumed).



Standard form post



Complex form data (file uploads)

7. Yaktor Technology Mapping Reference

This section contains the complete reference of Yaktor’s technology mappings.

7.1. Node.js mapping reference

This section contains the complete reference of the Yaktor Node.js mapping.

7.1.1. Directory structure

TODO: complete description of the Node.js directory structure, including generated & non-generated source directories.

7.1.2. Code customization

TODO: complete description of the gap pattern & its implementation in the Node.js mapping.

8. Appendix

8.1. Tooling

8.1.1. Yaktor Eclipse Plugin Introduction

This section explains how to install the Yaktor plugin for Eclipse.

The Yaktor Eclipse Plugin for Node.js includes

  • intelligent editors for the Yaktor domain model & conversation DSLs, and

  • builders that generate Node.js mappings based on your DSL code.

These editors may help you be more productive when using Yaktor. The editor supports features such as

  • syntax highlighting,

  • continuous source analysis with error markers,

  • autocompletion,

  • document outlines, and

  • click-through navigation to referenced elements. tl;dr Installation Download Eclipse

The first thing to do is to download a recent version of Eclipse that fits your needs. Pretty much any version of Eclipse will do.

We would recommend perhaps downloading one of the versions that supports JavaScript development. However, you may also download any of the other versions and then extend it later.

You can find all the eclipse versions at Ensure Xtext Eclipse plugins are installed

We depend on Xtext & Xtend plugins that are not available in all Eclipse versions. If you don’t already have Xtext (or you aren’t sure), install Xtext from Add the Yaktor Plugin

Yaktor provides a set of features that can be added to Eclipse. Eclipse allows us to do this through what they call an update site.

For Eclipse to find our features, you’ll have to add the Yaktor Update Site to the places where Eclipse searches for features.

The way to add an update site varies slightly from platform to platform, so to make sure we’re current, we suggest you simply search for "how to add an update site to Eclipse".

The update site for Yaktor is Try it out

If you succeeded installing the plugin, you should now be able to import one of your projects and try out the editors.

The easiest way to get started is to take one of your project and import them by using the following steps:

  1. Select File

  2. Select Import

  3. Expand General

  4. Select Existing Projects into Workspace

  5. Click Next

  6. Browse to the directory where you have your Yaktor project

  7. Click Finish

You should now be able to open the .yc and .ydm files using the Yaktor editors.

8.1.2. Docker Introduction

The easiest way to use Yaktor is via Docker. That way, you don’t have to install any of Yaktor’s dependencies directly; they’re already there in the Yaktor Docker Node.js image. Installation

Install Docker. We officially support Docker 1.11 or later. They have dandy installation instructions and support.