Warehouse Tutorial 2 ‐ Structure
Introduction
In this tutorial, we introduce the core concepts of DMLA related to model structure. We will model a simple warehouse that stocks products by defining fundamental concepts, including products, warehouse, and stock. Throughout the tutorial, the main concepts of DMLA are gradually introduced and subsequently expanded upon. For foundational information on DMLA, please refer to the basics tutorial.
Estimated time: 20 min
Step 1: Creating a Package
We start by creating a package for the domain. Packages are organization units similarly to Java packages or C# namespaces.
The Webshop package imports the Bootstrap package which defines the basic entities that can be used when modeling a domain. For example, the previously mentioned type and cardinality constraints are also defined in the Bootstrap. Each package must import the Bootstrap.
Step 2: Creating the Product Entity
The warehouse contains products, which is a concept in our domain and thus is modeled by the Product entity:
Product refines Classifier which is defined in the Bootstrap. In general a classifier acts as a blueprint for its refinements as we will see in the following steps. The Classifier entity is a general purpose base for all domain entities.
Step 3: Adding Slots to Product
At this level of abstraction, we have little information about products, but we do know that each product has an identifier, a price and a description:
entity Product: Classifier {
@Type = {TypeName = String;}
new slot ProductID;
@Type = {TypeName = Number;}
new slot Price;
@Type = {TypeName = String;}
new slot Description;
}
When defining slots, the new keyword is used (the rationale behind this will be discussed later). Unlike conventional programming languages, slot types are determined by a type constraint annotation applied to the slot.
Note
Each annotation (e.g. type annotation) is a modeled entity, which is refined to create the concrete annotation (e.g. we refine the generic Type annotation to specify that the type of the slot ProductID is String). When an annotation is added to a slot, an anonym entity is created ({...}) and its slots are set as displayed by the syntax.
Step 4: Refining the Product Entity
As next, we refine the Product entity to accommodate both books and vehicles within the warehouse. The Book and Vehicle entities are refinements of Product, introducing additional slots. Books have slots for Author and Title, while vehicles feature a MaxSpeed slot and a boolean RequiresLicense slot to indicate whether a driving license is necessary to drive the vehicle.
entity Book: Product {
@Type = {TypeName = String;}
new slot Author;
@Type = {TypeName = String;}
new slot Title;
}
entity Vehicle: Product {
@Type = {TypeName = Number;}
new slot MaxSpeed;
@Type = {TypeName = Bool;}
new slot RequiresLicence;
}
Books and Vehicles inherit the ProductID, Price and Description slots from the Product entity.
Step 5: Refining the Vehicle Entity
We can refine concepts in an arbitrary number of steps. For instance, we can further refine the Vehicle entity to represent Cars, introducing additional slots during this process:
entity Car: Vehicle {
@Type = {TypeName = String;}
new slot Manufacturer;
@Type = {TypeName = String;}
new slot Model;
@Type = {TypeName = Number;}
new slot YearOfManufacture;
}
Step 6: Refining the Car Entity
The Car entity contains numerous slots, each serving as placeholders for values that are to be specified later on the refinement chain. In certain scenarios, we may want to define only a subset of these values, leaving the remaining aspects open for further customization. For example, if we intend to handle cars manufactured by Toyota, but the other attributes remain unspecified, we can refine the Car entity and assign a value to the Manufacturer slot.
Step 7: Introducing Bicycles and Wheels
Up to this point, the types of all slots have been assigned primitive, built-in types like Number, Bool, or String. Now, let us extend the warehouse by adding bicycles and specifying that a bicycle has two wheels. It is important to note that the concept of a Wheel is also part of the domain, represented as a refinement of the Product entity. A wheel has a Size.
entity Wheel: Product {
@Type = {TypeName = Number;}
new slot Size;
}
entity Bicycle: Vehicle {
@Type = {TypeName = Wheel;}
@Cardinality = {Min = 2; Max = 2;}
new slot Wheels;
}
This example not only demonstrates how to reference another domain type within a type constraint, but also highlights the use of cardinality constraints. By default, slots have a cardinality of 0..*, which can be restricted by refining the minimum and/or maximum values. This means that certain slots, such as the ProductID of a product, need further refinement to reflect their correct cardinality—specifically 1..1, since a product must have exactly one ID.
Step 8: Slot Refinements
Let us explore the concept of slots further. We will create the "n-cycle" concept to unify unicycles and bicycles, allowing for a more generalized representation:
entity NCycle: Vehicle {
@Type = {TypeName = Wheel;}
@Cardinality = {Min = 1; Max = 2;}
new slot Wheels;
}
entity UniCycle: NCycle {
@Cardinality = {Min = 1; Max = 1;}
new slot Wheels;
}
entity Bicycle: NCycle {
@Cardinality = {Min = 2; Max = 2;}
new slot Wheels;
}
The refinement chain is maintained not only between entities, but also between slots. Although not present in the example until now, all slots have a classifier slot. Until now, implicit classifier slots were used in the examples, but in the background relations between the slots were managed by the compiler. Implicit classifier slots are identified by their name (e.g. Bicycle.Wheels finds its classifier slot since it is also called Wheels in NCycle). If name-based matching fails, the general purpose Base.Fields slot is used, which can be found in the Bootstrap. For example, Product.ID or Vehicle.MaxSpeed, or any other slot created with implicit classifiers refines Base.Fields. The Classifier entity refines the Base entity in the Bootstrap, thus we can always reach Base.Fields. Base.Fields allows for an arbitrary number of values of any type.
Classifier slots can always be specified explicitly:
entity Wheel: Product {
@Type = {TypeName = Number;}
new slot Size: Base.Fields;
}
entity UniCycle: NCycle {
@Cardinality = {Min=1; Max=1;}
new slot Wheels: NCycle.Wheels;
}
Unicycle.Wheels (since it is inherited from NCycle.Wheels). This was also the reason why we didn't specify the type of the Manufacturer slot in ToyotaCar.
Step 9: Slot Division
The refinement chain between slots enables not only the inheritance of annotations from the classifier slot but also the ability to divide a slot. Slot division refers to breaking a feature into more specific features. For instance, a bicycle does not have two general wheels, it has designated front and rear wheels. In this scenario, implicit classification slots cannot be utilized, therefore, the classifier slot must be specified explicitly:
entity Bicycle: NCycle {
@Cardinality = {Min = 1; Max = 1;}
slot FrontWheel: NCycle.Wheels;
@Cardinality = {Min = 1; Max = 1;}
slot RearWheel: NCycle.Wheels;
}
By defining the bicycle in this manner, we ensure that the front and rear wheels are distinct and can be accurately identified. Additionally, we can retrieve all the wheels of the bicycle by referencing NCycle.Wheels.
It is important to note that we intentionally omitted the "new" keyword before the slots. The "new" keyword indicates that we want to create a new slot from the original without consuming it. In this context, we specify that NCycle.Wheels should not be accessible further down the refinement chain of Bicycle. Consequently, the definitions of Unicycle.Wheels and ToyotaCar.Manufacturer should also be adjusted accordingly.
entity UniCycle: NCycle {
@Cardinality = {Min = 1; Max = 1;}
slot Wheels;
}
entity ToyotaCar: Car {
slot Manufacturer = "Toyota";
}
Conversely, removing the "new" keyword from the ID slot in Product would result in an error, as it would render Base.Fields unavailable along the refinement chain (e.g. in Vehicle).
Step 10: Enumerations in DMLA
We can expand the range of product types within the warehouse to include mobile phones. In this case, it is beneficial to utilize an enumeration to define the device's operating system. In DMLA, enumerations are represented as entities, allowing for refinement. This approach enables the creation of a hierarchical structure (tree) of possible values, rather than relying on a simple list.
entity MobilePhone: Product {
@Type = {TypeName = String;}
new slot Manufacturer;
@Type = {TypeName = String;}
new slot Model;
@Type = {TypeName = OpSystemType;}
new slot OpSystem;
@Type = {TypeName = Number;}
new slot RamSize;
}
entity OpSystemType: Classifier {}
entity IOS: OpSystemType {}
entity Android: OpSystemType {}
entity Android11: Android {}
entity Android12: Android {}
entity Android13: Android {}
TODO: explain ... switch support?
Step 11: Defining the Warehouse
Having established support for multiple product types, we can now proceed to define the warehouse itself:
Note that we could have also done this step after Step 2 which would have fit the top-down modeling principle better but would have interrupted the flow of this tutorial. After modeling the warehouse and its products, we can now focus on warehouse management and implementing validation mechanisms for the products. Thus, the next step is creating the dynamic behavior of the Warehouse.