Ran Wei/MDE Series
中文
Model-Driven Engineering with Eclipse Epsilon — Ran Wei

Capstone: MiniJDL

Build a JHipster-style entity language end to end: metamodel, models, validation, generation and transformation.

4 hours4 sessions9 runnable examplesEpsilon Playground

By the end you can

  • Define a small entity DSL with an Emfatic metamodel.
  • Build, validate and generate three artefact formats from a model.
  • Transform the model to a relational model and assess the result.

Before you start

Modules 1–9 are recommended. Examples run in the Playground unless noted.

Download all examples of this module (zip)

Contents

Study plan

4 hours

Times are estimates and include running the examples. Progress is saved in this browser and shared by both language editions.

Session 160 min
MiniJDL brief: define the language
Session 260 min
Build, validate and generate SQL, Java and HTML
Session 360 min
Transform and assess the relational model
Session 460 min
Practice
1

MiniJDL brief

The capstone asks you to design a small language for applications with entities, fields and directed relations. From one model, you will check domain rules, generate SQL DDL, Java data classes and HTML documentation, then transform the entities into a relational model. This MiniJDL is a teaching language, not JHipster’s JDL, and its generated Java classes are simple data declarations rather than a working web application.

The idea of generating several coordinated artefacts from an entity description is adapted from Dimitris Kolovos, Minimal JHipster JDL Monolith Example (2021). The MiniJDL metamodel, shop model and Epsilon programs here are written for this tutorial. You can use the Module 10 bundle locally or open each example in the Playground.

Aim for a repeatable pipeline: a metamodel defines the language; a model records the application; EVL rejects bad data; EGL/EGX generate readable artefacts; ETL creates another model with explicit mappings. Each stage gives you evidence to inspect before trusting the next one.

2

Define the language

Open the first example’s minijdl.emf. An Application contains Entity objects; an Entity contains Field and Relation objects. A field has a FieldType of STRING, INTEGER or BOOLEAN. A relation references a target entity. val makes ownership clear; ref means the relation points to an existing entity rather than owning another copy.

The Flexmi model describes Shop: Customer has email and active, Product has title and price, and Purchase has quantity plus relations to the other two entities. The EOL query prints the counts so you can check that the model loaded as intended.

ExampleInspect the MiniJDL language and modelOpen in Playground ↗
inspect.eol
var app = Application.all.first();
("Application: " + app.name).println();
for (e in app.entities) {
  var fieldNoun = "fields";
  if (e.fields.size() = 1) { fieldNoun = "field"; }
  var relationNoun = "relations";
  if (e.relations.size() = 1) { relationNoun = "relation"; }
  (e.name + ": " + e.fields.size() + " " + fieldNoun + ", " + e.relations.size() + " " + relationNoun).println();
}
minijdl.emf
@namespace(uri="minijdl", prefix="minijdl")
package minijdl;
enum FieldType { STRING; INTEGER; BOOLEAN; }
class Application {
  attr String[1] name;
  val Entity[*] entities;
}
class Entity {
  attr String[1] name;
  val Field[*] fields;
  val Relation[*] relations;
}
class Field {
  attr String[1] name;
  attr FieldType type;
}
class Relation {
  attr String[1] name;
  ref Entity target;
}
shop.flexmi
<?nsuri minijdl?>
<application name="Shop">
  <entity name="Customer">
    <field name="email" type="STRING"/>
    <field name="active" type="BOOLEAN"/>
  </entity>
  <entity name="Product">
    <field name="title" type="STRING"/>
    <field name="price" type="INTEGER"/>
  </entity>
  <entity name="Purchase">
    <field name="quantity" type="INTEGER"/>
    <relation name="customer" target="Shop.Customer"/>
    <relation name="product" target="Shop.Product"/>
  </entity>
</application>
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
Application: Shop
Customer: 2 fields, 0 relations
Product: 2 fields, 0 relations
Purchase: 1 field, 2 relations

The metamodel captures structure, not every business rule. It does not, for example, prevent two fields in one entity from sharing a name. That rule belongs in validation. Adding a new field type later would require updating both the metamodel enum and every generator’s type mapping.

3

Build and validate a model

The next model deliberately has two Customer.email fields. EVL checks uniqueness of entity names and of field names within each entity, and checks that each relation has a target. The duplicate field produces two findings because the constraint runs on both offending field elements. This is a validation result, not a parser failure.

ExampleDetect duplicate fieldsOpen in Playground ↗
rules.evl
context Entity {
  constraint UniqueEntityName {
    check: Entity.all.select(e | e.name = self.name).size() = 1
    message: "Duplicate entity: " + self.name
  }
}
context Field {
  constraint UniqueFieldName {
    check: self.eContainer().fields.select(f | f.name = self.name).size() = 1
    message: "Duplicate field: " + self.eContainer().name + "." + self.name
  }
}
context Relation {
  constraint HasTarget {
    check: not self.target.isUndefined()
    message: "Missing relation target: " + self.name
  }
}
More files: shop.flexmi, minijdl.emf
shop.flexmi
<?nsuri minijdl?>
<application name="Shop">
  <entity name="Customer">
    <field name="email" type="STRING"/>
    <field name="email" type="BOOLEAN"/>
  </entity>
  <entity name="Product">
    <field name="title" type="STRING"/>
    <field name="price" type="INTEGER"/>
  </entity>
  <entity name="Purchase">
    <field name="quantity" type="INTEGER"/>
    <relation name="customer" target="Shop.Customer"/>
    <relation name="product" target="Shop.Product"/>
  </entity>
</application>
minijdl.emf
@namespace(uri="minijdl", prefix="minijdl")
package minijdl;
enum FieldType { STRING; INTEGER; BOOLEAN; }
class Application {
  attr String[1] name;
  val Entity[*] entities;
}
class Entity {
  attr String[1] name;
  val Field[*] fields;
  val Relation[*] relations;
}
class Field {
  attr String[1] name;
  attr FieldType type;
}
class Relation {
  attr String[1] name;
  ref Entity target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
Error [UniqueFieldName]: Duplicate field: Customer.email
Error [UniqueFieldName]: Duplicate field: Customer.email

Repair the duplicate before generating deliverables. The worked solution in Exercise 4 runs the same rules on a corrected model. In a real project, add checks for identifier syntax, names reserved by target languages, relation-name clashes, required fields and cycles where relevant. A passing result from this small rule set does not imply those extra policies.

4

Generate SQL, Java and HTML

EGL maps the shop model to SQL DDL. Each entity becomes a table with an id column; fields map to SQL column types; relations become _id columns and foreign-key declarations. Inspect the generated SQL and verify that Purchase refers to Customer and Product by name. This template targets a simple, generic SQL dialect; database-specific types and naming rules would need a deliberate choice in a production generator.

ExampleGenerate SQL DDLOpen in Playground ↗
generate.egl
[%
var ddl = "-- MiniJDL schema for " + Application.all.first().name + "\n";
for (e in Entity.all) {
  ddl += "CREATE TABLE " + e.name + " (\n  id INTEGER PRIMARY KEY";
  for (f in e.fields) {
    var sqlType = "INTEGER";
    if (f.type.toString() = "STRING") { sqlType = "VARCHAR(255)"; }
    else if (f.type.toString() = "BOOLEAN") { sqlType = "BOOLEAN"; }
    ddl += ",\n  " + f.name + " " + sqlType;
  }
  for (r in e.relations) {
    ddl += ",\n  " + r.name + "_id INTEGER";
  }
  for (r in e.relations) {
    ddl += ",\n  FOREIGN KEY (" + r.name + "_id) REFERENCES " + r.target.name + "(id)";
  }
  ddl += "\n);\n";
}
%][%=ddl%]
More files: shop.flexmi, minijdl.emf
shop.flexmi
<?nsuri minijdl?>
<application name="Shop">
  <entity name="Customer">
    <field name="email" type="STRING"/>
    <field name="active" type="BOOLEAN"/>
  </entity>
  <entity name="Product">
    <field name="title" type="STRING"/>
    <field name="price" type="INTEGER"/>
  </entity>
  <entity name="Purchase">
    <field name="quantity" type="INTEGER"/>
    <relation name="customer" target="Shop.Customer"/>
    <relation name="product" target="Shop.Product"/>
  </entity>
</application>
minijdl.emf
@namespace(uri="minijdl", prefix="minijdl")
package minijdl;
enum FieldType { STRING; INTEGER; BOOLEAN; }
class Application {
  attr String[1] name;
  val Entity[*] entities;
}
class Entity {
  attr String[1] name;
  val Field[*] fields;
  val Relation[*] relations;
}
class Field {
  attr String[1] name;
  attr FieldType type;
}
class Relation {
  attr String[1] name;
  ref Entity target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
-- MiniJDL schema for Shop
CREATE TABLE Customer (
  id INTEGER PRIMARY KEY,
  email VARCHAR(255),
  active BOOLEAN
);
CREATE TABLE Product (
  id INTEGER PRIMARY KEY,
  title VARCHAR(255),
  price INTEGER
);
CREATE TABLE Purchase (
  id INTEGER PRIMARY KEY,
  quantity INTEGER,
  customer_id INTEGER,
  product_id INTEGER,
  FOREIGN KEY (customer_id) REFERENCES Customer(id),
  FOREIGN KEY (product_id) REFERENCES Product(id)
);

EGX invokes an EGL template once per entity, producing Customer.java, Product.java and Purchase.java. The template maps field types to Java types and relations to typed references. These files illustrate generation; they are not compiled by the Playground, and relationship persistence is outside this small example.

ExampleGenerate Java entity classesOpen in Playground ↗
generate.egx
rule Entity2Java
  transform e : Entity {
  template: "template.egl"
  target: e.name + ".java"
}
template.egl
public class [%=e.name%] {
  public int id;
[% for (f in e.fields) { %]  public [% if (f.type.toString() = "STRING") { %]String[% } else if (f.type.toString() = "BOOLEAN") { %]boolean[% } else { %]int[% } %] [%=f.name%];
[% } %][% for (r in e.relations) { %]  public [%=r.target.name%] [%=r.name%];
[% } %]}
More files: shop.flexmi, minijdl.emf
shop.flexmi
<?nsuri minijdl?>
<application name="Shop">
  <entity name="Customer">
    <field name="email" type="STRING"/>
    <field name="active" type="BOOLEAN"/>
  </entity>
  <entity name="Product">
    <field name="title" type="STRING"/>
    <field name="price" type="INTEGER"/>
  </entity>
  <entity name="Purchase">
    <field name="quantity" type="INTEGER"/>
    <relation name="customer" target="Shop.Customer"/>
    <relation name="product" target="Shop.Product"/>
  </entity>
</application>
minijdl.emf
@namespace(uri="minijdl", prefix="minijdl")
package minijdl;
enum FieldType { STRING; INTEGER; BOOLEAN; }
class Application {
  attr String[1] name;
  val Entity[*] entities;
}
class Entity {
  attr String[1] name;
  val Field[*] fields;
  val Relation[*] relations;
}
class Field {
  attr String[1] name;
  attr FieldType type;
}
class Relation {
  attr String[1] name;
  ref Entity target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
--- Customer.java ---
public class Customer {
  public int id;
  public String email;
  public boolean active;
}
--- Product.java ---
public class Product {
  public int id;
  public String title;
  public int price;
}
--- Purchase.java ---
public class Purchase {
  public int id;
  public int quantity;
  public Customer customer;
  public Product product;
}

The HTML template provides a third view of the same model. It lists entities, fields and relation counts. Open its generated output and compare the names with the SQL and Java outputs. If one generator omits an entity, the common model makes the mismatch detectable.

ExampleGenerate HTML documentationOpen in Playground ↗
generate.egl
<h1>[%=Application.all.first().name%] entities</h1>
[% for (e in Entity.all) { %]<section><h2>[%=e.name%]</h2><ul>
[% for (f in e.fields) { %]<li>[%=f.name%]: [%=f.type%]</li>
[% } %]</ul><p>Relations: [%=e.relations.size()%]</p></section>
[% } %]
More files: shop.flexmi, minijdl.emf
shop.flexmi
<?nsuri minijdl?>
<application name="Shop">
  <entity name="Customer">
    <field name="email" type="STRING"/>
    <field name="active" type="BOOLEAN"/>
  </entity>
  <entity name="Product">
    <field name="title" type="STRING"/>
    <field name="price" type="INTEGER"/>
  </entity>
  <entity name="Purchase">
    <field name="quantity" type="INTEGER"/>
    <relation name="customer" target="Shop.Customer"/>
    <relation name="product" target="Shop.Product"/>
  </entity>
</application>
minijdl.emf
@namespace(uri="minijdl", prefix="minijdl")
package minijdl;
enum FieldType { STRING; INTEGER; BOOLEAN; }
class Application {
  attr String[1] name;
  val Entity[*] entities;
}
class Entity {
  attr String[1] name;
  val Field[*] fields;
  val Relation[*] relations;
}
class Field {
  attr String[1] name;
  attr FieldType type;
}
class Relation {
  attr String[1] name;
  ref Entity target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
<h1>Shop entities</h1>
<section><h2>Customer</h2><ul>
<li>email: STRING</li>
<li>active: BOOLEAN</li>
</ul><p>Relations: 0</p></section>
<section><h2>Product</h2><ul>
<li>title: STRING</li>
<li>price: INTEGER</li>
</ul><p>Relations: 0</p></section>
<section><h2>Purchase</h2><ul>
<li>quantity: INTEGER</li>
</ul><p>Relations: 2</p></section>
5

Transform to a relational model

Text generation is not the only route. The ETL program maps Application to Schema, Entity to Table, and Field to Column. The target conforms to its own relational.emf metamodel, which can be validated or consumed by another generator. The initial mapping leaves relations out deliberately so you can see what information is lost.

ExampleMap entities to relational tablesOpen in Playground ↗
transform.etl
rule Application2Schema
  transform a : Source!Application
  to s : Target!Schema {
  s.name = a.name;
  s.tables ::= a.entities;
}
rule Entity2Table
  transform e : Source!Entity
  to t : Target!Table {
  t.name = e.name;
  var id = new Target!Column;
  id.name = "id";
  id.sqlType = "INTEGER";
  t.columns ::= e.fields;
  t.columns.add(id);
}
rule Field2Column
  transform f : Source!Field
  to c : Target!Column {
  c.name = f.name;
  if (f.type.toString() = "STRING") { c.sqlType = "VARCHAR(255)"; }
  else if (f.type.toString() = "BOOLEAN") { c.sqlType = "BOOLEAN"; }
  else { c.sqlType = "INTEGER"; }
}
relational.emf
@namespace(uri="relational", prefix="relational")
package relational;
class Schema {
  attr String name;
  val Table[*] tables;
}
class Table {
  attr String name;
  val Column[*] columns;
  val ForeignKey[*] foreignKeys;
}
class Column {
  attr String name;
  attr String sqlType;
}
class ForeignKey {
  attr String name;
  attr String column;
  ref Table target;
}
More files: shop.flexmi, minijdl.emf
shop.flexmi
<?nsuri minijdl?>
<application name="Shop">
  <entity name="Customer">
    <field name="email" type="STRING"/>
    <field name="active" type="BOOLEAN"/>
  </entity>
  <entity name="Product">
    <field name="title" type="STRING"/>
    <field name="price" type="INTEGER"/>
  </entity>
  <entity name="Purchase">
    <field name="quantity" type="INTEGER"/>
    <relation name="customer" target="Shop.Customer"/>
    <relation name="product" target="Shop.Product"/>
  </entity>
</application>
minijdl.emf
@namespace(uri="minijdl", prefix="minijdl")
package minijdl;
enum FieldType { STRING; INTEGER; BOOLEAN; }
class Application {
  attr String[1] name;
  val Entity[*] entities;
}
class Entity {
  attr String[1] name;
  val Field[*] fields;
  val Relation[*] relations;
}
class Field {
  attr String[1] name;
  attr FieldType type;
}
class Relation {
  attr String[1] name;
  ref Entity target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
Schema name="Shop"
  Table name="Customer"
    Column name="email" sqlType="VARCHAR(255)"
    Column name="active" sqlType="BOOLEAN"
    Column name="id" sqlType="INTEGER"
  Table name="Product"
    Column name="title" sqlType="VARCHAR(255)"
    Column name="price" sqlType="INTEGER"
    Column name="id" sqlType="INTEGER"
  Table name="Purchase"
    Column name="quantity" sqlType="INTEGER"
    Column name="id" sqlType="INTEGER"

Compare the target tree with the source: three tables have five declared-field columns and three generated id columns, but no foreign keys. The ETL rule adds id after t.columns ::= e.fields, because that assignment replaces the earlier column collection. Exercise 6 adds relation key columns and a Relation2ForeignKey rule, attaching its products to the right table. A successful transformation run does not mean the mapping is complete; decide the target invariants before calling it done.

6

Assess and extend the solution

Use this checklist on your own MiniJDL variant:

  • Language: Does every class, containment and reference have a purpose? Is each model element owned exactly where intended?
  • Model: Do all named references resolve? Can a second, different application be written without changing the metamodel?
  • Validation: Do invalid names and relations produce clear findings? Does the corrected model pass? Which rules remain unimplemented?
  • SQL: Are all entities, fields and relation keys present? Is the output valid for the database you choose?
  • Java: Are the expected files and types present? If you need compilable code, have you compiled it and addressed packages/imports?
  • HTML: Does the documentation describe the same entities and relations as the model?
  • ETL: Do table and column counts match? Does each foreign key point to the intended target table? Can the target be validated?
  • Repeatability: Can you rerun the pipeline after a model edit and compare the outputs without manual patching?

The reference examples give a small end-to-end solution. They do not generate a complete JHipster application. Extending MiniJDL with multiplicities, constraints, application configuration and persistence choices would be a new design exercise.

7

Exercises

Exercise 1★★★conceptual5 min

Trace ownership. Why does an Entity contain its Field objects, while Relation.target is a reference?

Show solution

Fields belong to the entity that declares them. A relation points at another entity already owned by the application; containing that target would duplicate or move it.

Exercise 2★★★conceptual5 min

Trace one element. Follow Purchase.customer through the SQL, Java and HTML examples. Which representation carries the target name, and what does the HTML example omit?

Show solution

SQL declares customer_id with a foreign key to Customer; Java declares a Customer customer field. The HTML example reports two relations for Purchase but does not list their names or targets, so it is a summary rather than a full schema specification.

Exercise 3★★★conceptual5 min

Find information loss. What does the first relational ETL target omit, and why should that be checked even though ETL finishes?

Show solution

It omits the two relations from Purchase, so the target has no foreign keys. ETL’s successful execution confirms that its rules ran; it does not establish that every required source concept was mapped.

Exercise 4★★★coding10 min

Repair the model. Replace the duplicate Customer.email field with active: BOOLEAN, then run the same EVL rules.

Show solution

The corrected model passes all three constraints in this reference rule set:

SolutionValidate the repaired modelOpen in Playground ↗
rules.evl
context Entity {
  constraint UniqueEntityName {
    check: Entity.all.select(e | e.name = self.name).size() = 1
    message: "Duplicate entity: " + self.name
  }
}
context Field {
  constraint UniqueFieldName {
    check: self.eContainer().fields.select(f | f.name = self.name).size() = 1
    message: "Duplicate field: " + self.eContainer().name + "." + self.name
  }
}
context Relation {
  constraint HasTarget {
    check: not self.target.isUndefined()
    message: "Missing relation target: " + self.name
  }
}
More files: shop.flexmi, minijdl.emf
shop.flexmi
<?nsuri minijdl?>
<application name="Shop">
  <entity name="Customer">
    <field name="email" type="STRING"/>
    <field name="active" type="BOOLEAN"/>
  </entity>
  <entity name="Product">
    <field name="title" type="STRING"/>
    <field name="price" type="INTEGER"/>
  </entity>
  <entity name="Purchase">
    <field name="quantity" type="INTEGER"/>
    <relation name="customer" target="Shop.Customer"/>
    <relation name="product" target="Shop.Product"/>
  </entity>
</application>
minijdl.emf
@namespace(uri="minijdl", prefix="minijdl")
package minijdl;
enum FieldType { STRING; INTEGER; BOOLEAN; }
class Application {
  attr String[1] name;
  val Entity[*] entities;
}
class Entity {
  attr String[1] name;
  val Field[*] fields;
  val Relation[*] relations;
}
class Field {
  attr String[1] name;
  attr FieldType type;
}
class Relation {
  attr String[1] name;
  ref Entity target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
All constraints are satisfied.
Exercise 5★★★coding10 min

Extend Java generation. Add an entityName() method to every generated Java class that returns its entity name.

Show solution

The EGX rule still creates one file per entity; the EGL template adds the method:

SolutionGenerate Java classes with relation targetsOpen in Playground ↗
generate.egx
rule Entity2Java
  transform e : Entity {
  template: "template.egl"
  target: e.name + ".java"
}
template.egl
public class [%=e.name%] {
  public int id;
[% for (f in e.fields) { %]  public [% if (f.type.toString() = "STRING") { %]String[% } else if (f.type.toString() = "BOOLEAN") { %]boolean[% } else { %]int[% } %] [%=f.name%];
[% } %][% for (r in e.relations) { %]  public [%=r.target.name%] [%=r.name%];
[% } %]  public String entityName() { return "[%=e.name%]"; }
}
More files: shop.flexmi, minijdl.emf
shop.flexmi
<?nsuri minijdl?>
<application name="Shop">
  <entity name="Customer">
    <field name="email" type="STRING"/>
    <field name="active" type="BOOLEAN"/>
  </entity>
  <entity name="Product">
    <field name="title" type="STRING"/>
    <field name="price" type="INTEGER"/>
  </entity>
  <entity name="Purchase">
    <field name="quantity" type="INTEGER"/>
    <relation name="customer" target="Shop.Customer"/>
    <relation name="product" target="Shop.Product"/>
  </entity>
</application>
minijdl.emf
@namespace(uri="minijdl", prefix="minijdl")
package minijdl;
enum FieldType { STRING; INTEGER; BOOLEAN; }
class Application {
  attr String[1] name;
  val Entity[*] entities;
}
class Entity {
  attr String[1] name;
  val Field[*] fields;
  val Relation[*] relations;
}
class Field {
  attr String[1] name;
  attr FieldType type;
}
class Relation {
  attr String[1] name;
  ref Entity target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
--- Customer.java ---
public class Customer {
  public int id;
  public String email;
  public boolean active;
  public String entityName() { return "Customer"; }
}
--- Product.java ---
public class Product {
  public int id;
  public String title;
  public int price;
  public String entityName() { return "Product"; }
}
--- Purchase.java ---
public class Purchase {
  public int id;
  public int quantity;
  public Customer customer;
  public Product product;
  public String entityName() { return "Purchase"; }
}
Exercise 6★★★coding15 min

Complete the relational mapping. Add an ETL rule that makes a foreign key for each relation and resolves its target table via the transformation trace.

Show solution

Entity2Table adds each _id column and attaches each transformed relation. Relation2ForeignKey maps its target entity to the corresponding table. The target tree now contains customer and product foreign keys under Purchase:

SolutionMap relations to foreign keysOpen in Playground ↗
transform.etl
rule Application2Schema
  transform a : Source!Application
  to s : Target!Schema {
  s.name = a.name;
  s.tables ::= a.entities;
}
rule Entity2Table
  transform e : Source!Entity
  to t : Target!Table {
  t.name = e.name;
  var id = new Target!Column;
  id.name = "id";
  id.sqlType = "INTEGER";
  t.columns ::= e.fields;
  t.columns.add(id);
  for (r in e.relations) {
    var column = new Target!Column;
    column.name = r.name + "_id";
    column.sqlType = "INTEGER";
    t.columns.add(column);
  }
  t.foreignKeys ::= e.relations;
}
rule Field2Column
  transform f : Source!Field
  to c : Target!Column {
  c.name = f.name;
  if (f.type.toString() = "STRING") { c.sqlType = "VARCHAR(255)"; }
  else if (f.type.toString() = "BOOLEAN") { c.sqlType = "BOOLEAN"; }
  else { c.sqlType = "INTEGER"; }
}
rule Relation2ForeignKey
  transform r : Source!Relation
  to fk : Target!ForeignKey {
  fk.name = r.name;
  fk.column = r.name + "_id";
  fk.target ::= r.target;
}
relational.emf
@namespace(uri="relational", prefix="relational")
package relational;
class Schema {
  attr String name;
  val Table[*] tables;
}
class Table {
  attr String name;
  val Column[*] columns;
  val ForeignKey[*] foreignKeys;
}
class Column {
  attr String name;
  attr String sqlType;
}
class ForeignKey {
  attr String name;
  attr String column;
  ref Table target;
}
More files: shop.flexmi, minijdl.emf
shop.flexmi
<?nsuri minijdl?>
<application name="Shop">
  <entity name="Customer">
    <field name="email" type="STRING"/>
    <field name="active" type="BOOLEAN"/>
  </entity>
  <entity name="Product">
    <field name="title" type="STRING"/>
    <field name="price" type="INTEGER"/>
  </entity>
  <entity name="Purchase">
    <field name="quantity" type="INTEGER"/>
    <relation name="customer" target="Shop.Customer"/>
    <relation name="product" target="Shop.Product"/>
  </entity>
</application>
minijdl.emf
@namespace(uri="minijdl", prefix="minijdl")
package minijdl;
enum FieldType { STRING; INTEGER; BOOLEAN; }
class Application {
  attr String[1] name;
  val Entity[*] entities;
}
class Entity {
  attr String[1] name;
  val Field[*] fields;
  val Relation[*] relations;
}
class Field {
  attr String[1] name;
  attr FieldType type;
}
class Relation {
  attr String[1] name;
  ref Entity target;
}
Output captured from Epsilon 2.8.0 when this page was built. The Playground may word messages differently and does not show warnings about the model.
Output
Schema name="Shop"
  Table name="Customer"
    Column name="email" sqlType="VARCHAR(255)"
    Column name="active" sqlType="BOOLEAN"
    Column name="id" sqlType="INTEGER"
  Table name="Product"
    Column name="title" sqlType="VARCHAR(255)"
    Column name="price" sqlType="INTEGER"
    Column name="id" sqlType="INTEGER"
  Table name="Purchase"
    Column name="quantity" sqlType="INTEGER"
    Column name="id" sqlType="INTEGER"
    Column name="customer_id" sqlType="INTEGER"
    Column name="product_id" sqlType="INTEGER"
    ForeignKey name="customer" column="customer_id" target->Customer
    ForeignKey name="product" column="product_id" target->Product
8

Self-check quiz

1
What is MiniJDL in this module?
2
Which feature owns a Field?
3
Why does the invalid model produce a validation finding?
4
What does the SQL generator add for each relation?
5
What does EGX contribute to Java generation?
6
What is missing from the first relational target?
7
How does Relation2ForeignKey find the target table?
8
Which check is needed before claiming production-ready Java output?
9

Further reading