If you're new to Model-Driven Engineering, then you may appreciate a quick overview of the approach, and how it may help you. Here, we explain what the fuss is all about, and how ReMoDeL is different from other tools.
ReMoDeL is for software engineers working in Model-Driven Engineering. That is, you develop your software designs as models, and manage them using automatic tools. You seek to extract models, repair or enhance models, translate models, or indeed generate code from models. You have the vision that all software should be generated from models.
You may have tried Model-Driven Architecture, but the OMG's set of standards UML, MOF, OCL, XMI, QVT is too complex and only partly implemented. You may have tried transformation languages such as ATL, ETL or AgileUML embedded in the Eclipse Modelling Framework, but the technical debt of plugins and multiple runtimes is too taxing. You are tired of struggling with infrastructure.
ReMoDeL helps you focus on the interesting part, which is on the actual models and transformations.
You don't need to learn complex tree-structured pattern-matching rules, nor get mired in problems controlling the order of rule firing! Let's show this using the "Hello World" example of model transformation, which maps between two tree-like representations.
The following is a metamodel for an InTree, a kind of tree whose nodes point upwards towards the root. It is usually expressed as text in ReMoDeL, but the same metamodel may also be visualised as a diagram:
metamodel InTree { concept Node { attribute label : String reference parent : Node operation isRoot : Boolean { parent = null } } concept Tree { component nodes : Node[] operation root : Node { nodes.detect(node | node.isRoot) } } }
So, an InTree metamodel consists of a Node concept, which has a label and a reference to its parent Node, and a Tree concept, which contains a list of Nodes. A Node is a root, if it has no parent. The root Node of the Tree is the first Node you detect that is a root.
The following is a model, an instance of the InTree metamodel. It is expressed only as text in ReMoDeL, which is a simple serialisation of the model stored in memory:
model tree1 : InTree { t1 : Tree(nodes = Node[ n1 : Node(label = "Root"), n2 : Node(label = "Branch1", parent = n1), n3 : Node(label = "Branch2", parent = n1), n4 : Node(label = "Leaf1", parent = n2), n5 : Node(label = "Leaf2", parent = n2), n6 : Node(label = "Leaf3", parent = n3) ]) }
So, this model consists of a particular Tree t1, which contains the list of Nodes n1..n6. The tree structure can be seen in the way that the Nodes n2, n3 each refer to the same parent Node n1, which also happens to be the root, since it has no parent.
The following is a metamodel for a Graph, a kind of tree in which the vertices and edges are made explicit. It is usually expressed as text in ReMoDeL, but the same metamodel may also be visualised as a diagram:
metamodel Graph { concept Graph { component vertices : Vertex[] component edges : Edge[] operation root : Vertex { vertices.detect(vertex | not edges.exists(edge | edge.source = vertex)) } } concept Vertex { attribute label : String } concept Edge { reference source : Vertex reference target : Vertex } }
So, a Graph metamodel consists of a Vertex concept, which has a label, an Edge concept, which has references to its source and target Vertex, and a Graph concept, which contains lists of its Vertices and Edges.
To find the root of a Graph is a bit more involved. You need to detect a given Vertex satisfying a property, which is that there must not exist any Edge, whose source refers to that Vertex.
The following is a model, an instance of the Graph metamodel.
model graph1 : Graph { g1 : Graph(vertices = Vertex[ v1 : Vertex(label = "Root"), v2 : Vertex(label = "Branch1"), v3 : Vertex(label = "Branch2"), v4 : Vertex(label = "Leaf1"), v5 : Vertex(label = "Leaf2"), v6 : Vertex(label = "Leaf3") ], edges = Edge[ e1 : Edge(source = v2, target = v1), e2 : Edge(source = v3, target = v1), e3 : Edge(source = v4, target = v2), e4 : Edge(source = v5, target = v2), e5 : Edge(source = v6, target = v3) ]) }
So, this model consists of a Graph g1 containing a list of Vertices v1..v6 and a list of Edges e1..e5. The tree structure can be seen in the way that the Edges e1, e2 respectively link source Vertices v2, v3 back to the same target Vertex v1. The Vertex v1 also happens to be the root because there is no Edge having this as its source.
The two models are similar, except that tree1 : InTree has implicit references from each Node to its parent, whereas graph1 : Graph has explicit Edges linking each child Vertex to its parent. Otherwise, exactly the same nodes exist in each model, linked in the same way.
In principle, it should be possible to translate from one representation to the other. The following transformation converts from an InTree to a Graph representation:
transform InTreeToGraph : Trees { metamodel source : InTree metamodel target : Graph mapping inTreeToGraph (inTree : InTree_Tree) : Graph_Graph { create Graph_Graph( vertices := inTree.nodes.collect(node | inNodeToVertex(node)), edges := inTree.nodes .without(inTree.root) .collect(node | inNodeToEdge(node)) ) } mapping inNodeToVertex(inNode : InTree_Node) : Graph_Vertex { create Graph_Vertex( label := inNode.label) } mapping inNodeToEdge (inNode : InTree_Node) : Graph_Edge { create Graph_Edge( source := inNodeToVertex(inNode), target := inNodeToVertex(inNode.parent) ) } }
The transformation declares the source metamodel InTree and the target metamodel Graph. The body of the transformation consists of mapping rules, which each map a source to a target element. The first rule is the top rule of the transformation.
The rule inNodeToVertex maps a Node to a Vertex. This rule is simple: it creates a Vertex whose name is initialised to the name of the supplied InNode.
The rule inNodeToEdge likewise maps a Node to an Edge. This rule is slightly more elaborate. It creates an Edge, whose source and target are respectively the result of mapping the supplied Node and its parent to Vertices, using the previous rule inNodeToVertex.
The rule inTreeToGraph maps the whole InTree to a Graph. This rule uses the previous two rules. It creates a Graph, whose vertices are initialised to a list obtained by mapping inNodeToVertex over each Node of the InTree, and whose edges are initialised to a list obtained by mapping inNodeToEdge over every Node except the root.
A ReMoDeL transformation is a pure functional mapping from a source to a target model. The whole target is always created afresh by the transformation.
A ReMoDeL model is a Directed Acyclic Graph, to ensure that transformations always terminate. If a model were to contain cycles, the rules would call each other in infinite regress.
A ReMoDeL transformation is unidirectional, mapping from the source to the target. It is possible to define an inverse transformation mapping in the opposite direction.
See ReMoDeL Explained and ReMoDeL Compiled for a full tutorial on ReMoDeL. These cover the language, and the cross-compilation to Java.