Simple Domain Modeling Language
Language Primer

Table of Contents

square-dark.svg

This short primer introduces the Simple Domain Modeling Language (SDML) and how you can apply it to model your business domain. This is not a replacement for the main SDML documentation but instead a quick tour of the language, it’s features and application. This introduction will focus only on the SDML surface syntax, the textual representation that most people will interact with directly, whereas the main documentation also covers the semantics of the language and mappings between SDML and other key models. The primer also covers the core concepts that almost everyone will need to know, with pointers to the more advanced topics in the main documentation (notes marked with 🎛).

So, let’s start…

1. Why do I want a Domain Model?

Modeling in software development has had a difficult history, in some technical domains it is considered an essential tool. However, in many others, and especially with the emergence of agile development practices, it is considered an unnecessary overhead. George Box (statistician) famously wrote, “All models are wrong, some are useful.”, and later expanded it to

All models are approximations. Assumptions, whether implied or clearly stated, are never exactly true. All models are wrong, but some models are useful. So the question you need to ask is not “Is the model true?” (it never is) but “Is the model good enough for this particular application?” — George E. P. Box, 1976.

So why would we build a domain model? Well, from the introduction to the guidance topic Domain Modeling in the Scaled Agile Framework we get the following.

Domain modeling is one of the key models used in software engineering: if you only model one thing in Agile, model the domain. A relatively small domain-modeling effort is a great tool for controlling the complexity of the system under development. It may help in resolving countless ambiguities in both the requirements and the design intent. Domain modeling simply reflects our understanding of real-world entities and their relationships and responsibilities that cover the problem domain.

It is the essential characteristic of the domain model that it must reflect real-world entities, the tangible and intangible artifacts that are the common vocabulary of the domain. For example, in a Hotel domain model we may introduce Hotel, Room, Customer, and Booking as entities whereas a car rental domain model would have Location, Vehicle, Customer, and Booking. These entities should not be abstracted more than necessary, while the term Location in the rental model could be considered as a generic form for Hotel it is not, they are not the same and the choice of Location was not for genericity or abstraction but because that is the term that the rental car industry uses.

As such, a domain model is an essential communication tool, not only between the business and technology sides of an organization but also between the business and it’s customers and partners. Some industry standards for example use a common domain model to help facilitate the understanding of the standards themselves and how they apply between parties.

1.1. Where do I start?

One answer to that question is whether existing domain models already exist for your industry, often from standards organizations. If not then you would want to get a group of subject-matter experts (SMEs) together to talk through the key components of your business and how they relate to each other. You want to identify first those real-world entities that comprise the core of the business, and start with the ones your customers would be able to articulate – only because that simple test helps to identify the truly essential elements.

Include stakeholders across your business and with different roles and perspectives. This is not a time to be parochial, to codify existing organizational structures or silos. Ask yourself, “If we were to fundamentally reorganize our internal structure, would these entities have to change?” and if the answer is yes you almost certainly have something wrong. The danger is that your modeling effort falls foul of Conway’s law:

Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure. — Melvin E. Conway, 1967.

Another good place to start is to ask participants to describe the business in only 1 or 2 sentences. In the following we have highlighted obvious candidate entities in bold, and phrases that may hide entities in italics.

  • Retail: Customers go to a store to purchase items, they may have a specific item in mind or expect to find choices they can choose between. Depending on the kind of item this choice may be variations (in size or color for example) of the same item or the same kind of item from different manufacturers.
  • Hotel: A customer books a room according to their choice of size, bed type, or other criteria, for specific dates at a particular property. A customer may make certain kinds of purchase against their room which are then added to the final bill on completion of their stay.
  • Long-Haul Logistics: The business comprises matching available transportation capacity on one or more routes to deliver a customer’s load from source to destination within some time constraint. A load may be one or more standard palettes, or one or more standard containers. This results in one or more delivery contracts.

The next step may be to ask questions such as “does the (candidate) entity X have a natural and unique identifier?”, “what properties does the (candidate) entity X have?”, or “if we don’t think X is an entity, what is it?”. All of these help to elaborate an initial framework that you can hang more detail on as you spend more time with more stakeholders. Whether you capture this in a structured form such as SDML, a graphical form such as a Visio diagram or data model diagram, or even natural language notes, you now have a domain model.

The first of these questions is actually the most important as we can define an entity according to the following rules.

An Entity is:

  1. the name of a real-world concept,
  2. usually being a noun in the language used to describe the domain,
  3. that has an independent lifecycle (is not dependent on the lifecycle of another),
  4. and has a natural and unique identifier.

2. What is SDML?

SDML is a textual language that allows you to describe a domain model in a relatively simple structured manner. We say relatively because the language allows you to build models iteratively, capturing the essentials first and elaborating on them over time. It is common for domain models to be the output of workshop sessions and so the ability to capture a valid model in real-time even if it lacks detail is a useful feature. With this in mind we will elaborate on the module definition in listing 1 during this primer.

1: module retail is end

This is, obviously, not a very informative definition of the retail domain; however, it is a valid model and a good enough place to start. But, as you’ve been tasked with being the scribe at a workshop outlining the domain model for a retail business you’re on track so far. We will see more features in the following sections.

One question you may have, why a textual language, why not a fancy graphical one, or an even fancier web-based graphical one? The reasons are purely pragmatic; 1. simpler to build, 2. now you have a persistent format, 3. easier to agree details, 4. not everyone is a visual thinker, and 5. it’s usually easier to define a visual representation of textual form than a textual representation of a visual one. One (and not necessarily the most urgent) of the problems that has hampered the adoption of the UML language for example is that there is no standard representation and so all tools have to write import/export capabilities to share models leading to errors or inconsistencies. SDML has a native representation, the surface syntax (it actually has two, more on that in a moment) and all tool builders can rely on being able to read and write that syntax.

What is the other representation? The textual language, shown in listing 1 has a direct mapping to, and can be considered an alternate syntax for, a particular RDF/OWL ontology [need link]. This means that the semantics of SDML are then described by the OWL ontology, then OWL, and then RDF. Understanding this is not necessary to using or understanding the SDML syntax.

The SDML project, which is more than just the language definition, provides editor integration for authoring and tools for drawing and documentation. It also produces this primer and the main documentation.

2.1. What it isn’t

  • SDML is not, as we said above, a graphical language. It relies on a defined mapping to the UML language that allows the core elements of the textual language to be represented visually in UML.
  • SDML is not a product, it’s freely available and open source.
  • SDML is not a standard, there’s no committee managing changes. Feel free to file an enhancement request, or if you have a change a pull request, because…
  • SDML is not done, we expect evolution as we see interesting opportunities to help you, and you see things we missed.

3. What can I do with it?

The value of a domain model is in being able to describe the domain in an unambiguous way, so that it can become a shared vocabulary which in turn requires some rigor. One of the goals of SDML is to allow you to apply rigor as needed and elaborate the model over time. Some modeling tools, techniques, and processes, seem to require you to front-load the details and it can be hard to get past the blank page problem as it feels like you need to do so much just to start.

This section will show this elaboration process in action as we build the models for our example domains: Retail, Hospitality, and Logistics.

3.1. Capture Entities

Let’s add the entities from the previous natural language description to our module. In each of the following sections we will repeat the descriptive text and follow it with an initial model. Note that all we need to do at this stage is list the entities we have identified so far using the rather obvious keyword entity.

3.1.1. Retail Example

Customers go to a store to purchase items, they may have a specific item in mind or expect to find choices they can choose between. Depending on the kind of item this choice may be variations (in size or color for example) of the same item or the same kind of item from different manufacturers.

When we look at the italic text above we note the possible entities Kind, Variation, Size, Color, and Manufacturer. However, as we talk them through with the stakeholders we feel that they do not meet the criteria outlined in the section Where do I start?, except Manufacturer.

 1: module retail is
 2: 
 3:   entity Customer
 4: 
 5:   entity Store
 6: 
 7:   entity Purchase
 8: 
 9:   entity Item
10: 
11:   entity Manufacturer
12: 
13: end

Note that, depending on how you state the initial part of the description above you may end up with a different name for the entity Purchase; “A store sells items to customers” for example will result in essentially the same entity but with the name Sale only because the subject of the sentence has changed. Beware of this, try and always pick a subject to use for all descriptions to avoid models that include both purchase and sale! In general, we recommend that you pick the customer as the primary subject as it means you end up with fewer internal names or jargon names for things.

3.1.2. Hospitality Example

A customer books a room according to their choice of size, bed type, or other criteria, for specific dates at a particular property. A customer may make certain kinds of purchases against their room which are then added to the final bill on completion of their stay.

The only additional entity beyond the set already identified in bold is possibly Stay, which at first looks a lot like Booking in that it has a start and end date and references a Customer and Property. However, a booking reference the type of room where as the stay references the actual room, and the start and end dates may not be the same if the customer left early for example.

In listing 3 we will fill in some details, just to see how it’s done. Specifically note the use of the arrow operator -> or → that denotes the type (right-hand side) of a member (left-hand side). Also, if you remember that a part of the definition of an entity is that it has strong identity, so as we are adding details to entities we must provide an identity member.

 1: module hospitality is
 2: 
 3:   import xsd
 4: 
 5:   entity Customer
 6: 
 7:   entity Booking is
 8:     identity booking_code -> string
 9:     customer -> Customer
10:     property -> Property
11:     room_type -> unknown ;; don't know what this is yet.
12:     start_date -> xsd:date
13:     end_date -> xsd:date
14:     number_of_adults -> integer
15:     number_of_children -> integer
16:   end
17: 
18:   entity Room
19: 
20:   entity Property
21: 
22:   entity Purchase
23: 
24:   entity Bill is
25:     identity transaction_id -> unknown
26:     for_stay -> Stay
27:     charges -> {0..} Purchase
28:   end
29: 
30:   entity Stay is
31:     identity id -> unknown
32:     from_booking -> {0..1} Booking
33:   end
34: 
35: end

The following are useful pointers to features of the entity definition.

  1. Lines 9-10: the members named customer and property reference other entities in the same module even though neither of those entities are complete.
  2. Line 11: the member named room_type has the type *unknown* which is an explicit signal that this model is incomplete as the type has not yet been decided.
  3. Line 11: the semi-colon introduces a line comment, so we added a little note to ourselves about the unknown type for this member. The use of two semi-colons is a style preference as it makes the comment easier to find.
  4. Lines 8,11-15: the type names string and integer (and unknown) are builtin, the type xsd:date is not, it has been imported from another module named xsd. The name xsd:date is a qualified name which uses the colon character to combine module and definition name.
  5. Line 27: the member named in charges in Bill is a sequence type, which means it can have a varying number of values. The specification for upper and lower bounds on a sequence type are it’s cardinality, the lack of a upper bound means that the range is unbounded. The default cardinality being 1.
  6. Line 32: the member named from_booking in Stay is another sequence type, although with the cardinality of 0..1 may be seen as optional.

The builtin datatypes in SDML are derived from those in the XML Schema, Part 2 specification, the same datatypes used in RDF. The major datatypes are listed in table 1 and compared to corresponding datatypes in SQL, Java and Rust. SQL’s Data Definition Language (DDL) is a useful comparison given that it may be familiar to less technical stakeholders.

Table 1: Datatype Comparisons
Datatype SQL Equivalent Java Equivalent Rust Equivalent
binary BINARY or VARBINARY byte[] Vec<u8> or [u8]
boolean BIT boolean bool
decimal DECIMAL BigDecimal in java.math  
double DOUBLE PRECISION double f64
integer INTEGER long i64
string CHAR or VARCHAR String String or str
unsigned     u64
uri      
xsd:date DATE    
xsd:time TIME    
xsd:dateTime DATETIME    
xsd:duration INTERVAL    

3.1.3. Logistics Example

The business comprises matching available transportation capacity on one or more routes to deliver a customer’s load from source to destination within some time constraint. A load may be one or more standard palettes, or one or more standard containers. This results in one or more delivery contracts.

Note also that we have dropped some of the entities from the original

You can use Annotation Properties to attach documentation properties to your model. In the following example we have added a preferred label “Logistics” (line 6), an alternate label “Shipping” (line 7), and a description (line 8) annotation to the module itself. Don’t worry about the syntax of these for now, that’s covered in an advanced topic later. We have added some to the logistics example in listing 4.

 1: module logistics is
 2: 
 3:   import [ dc skos uuid ]
 4:   import [ gs1 ]
 5: 
 6:   @skos:prefLabel = "Logistics"@en
 7:   @skos:altLabel = "Shipping"@en
 8:   @dc:description = "A domain model for long-haul logistics."@en
 9: 
10:   entity Location
11:   ; Global Location Number GLN
12: 
13:   enum LocationKind
14: 
15:   entity Consignor is
16:     identity gln -> GlobalLocationNumber
17:     lei -> {0..1} gs1:LegalEntityIdentifier
18:   end
19: 
20:   entity Consignment is
21:     identity ginc -> gs1:GlobalIdentificationNumberForConsignment
22:     consignor -> Consignor
23:     shipments -> {1..} Shipment
24:   end
25: 
26:   entity Shipment is
27: 
28:     @editorialNote =
29:       "need to add logistic units -- are these entities?"
30: 
31:     identity gsin -> gs1:GlobalShipmentIdentificationNumber
32:     from -> Location
33:     to -> Location
34:   end
35: 
36: end

It is also time to deal with the import statement (lines 3,4) that we have already seen in a number of examples. An import statement is used to make definitions in other modules available to us in the current module. Import statements are simpler than a lot of programming languages in that you can import either a module by name or individual definitions within a module. In either case SDML requires you to use the qualified name of any imported definition. A qualified name is simply the name of the module followed by a colon and then the name of the definition. In listing 4 we see a single import statement with a list of module names, dc and skos. These modules allow us to reference the annotation properties dc:description, skos:altLabel, skos:definition, and skos:prefLabel.

Annotation properties are like constants in programming, they become attached to their enclosing definition and are immutable. A property is introduced by the initial @ character and the property name follows. This property is then assigned a value, using the datatypes in table 1. Some annotations, such as those in listing 4 above, are purely descriptive while others actually affect the semantics of the model element they are attached to.

Strings, as you can see in listing 4 may be followed by another @ character which introduces a language tag that identifies the natural language used in the string constant.

3.2. Add details with Structures and Enums

Now that you have the key elements of your domain model in place, you can start to see how this becomes the core vocabulary shared between customers, business, and technical stakeholders. However, a Store, Property, or Shipment are really not interesting as entities without some more detail. While we did add some of the inter-entity relationships in the previous section the only entity we added any additional members to was Booking. In the following sections we will use two more definition types, structures and enumerations to add clarity and structure to the models we have developed so far.

A structure will look very much like an entity in that it has a name and a set of members, however it does not have an identity member. As this means we cannot include them in an entity by reference to an identifier all structures are included within any enclosing structure or entity.

An enumeration is a list of unique variants that represent distinct options.

3.2.1. Retail Example

In listing 5 we have added a common Address structure (line 13) that is used by the customer, manufacturer, and store entities. This example does not introduce an enumeration directly; however, the imported type AlphaTwoCountryCode is an enumeration (described in listing 6 below). Additionally, the cardinality expression on line 25 introduces a new keyword ordered that is a constraint over the sequence that it’s members must maintain their order, thus the sequence acts as an array or vector. In this example we assume that the 7 elements of the sequence represent the days of the week so ordering would be important (although is the first element Sunday or Monday?).

 1: module retail is
 2: 
 3:   import [ skos xsd ]
 4:   import gs1
 5:   import iso_17442
 6:   import iso_3166_1
 7: 
 8:   entity Customer is
 9:     identity customer_id -> unknown
10:     address -> Address
11:   end
12: 
13:   structure Address is
14:     street {1..2} -> string
15:     apartment {0..1} -> string
16:     city -> string
17:     state_or_district -> string
18:     postal_code -> string
19:     country -> iso_3166_1:AlphaTwoCountryCode
20:   end
21: 
22:   entity Store is
23:     identity location_number -> gs1:GlobalLocationNumber
24:     address -> Address
25:     open_hours -> {ordered 7} Hours
26:     has_pickup -> boolean
27:   end
28: 
29:   structure Hours is
30:     open -> xsd:time
31:     close -> xsd:time
32:   end
33: 
34:   entity Purchase
35: 
36:   entity Item is
37:     @skos:note = "See following advanced topic."
38:     identity gtin -> gs1:GlobalTradeItemNumber
39:     description -> string
40:     manufacturer -> Manufacturer
41:   end
42: 
43:   entity Manufacturer is
44:     identity manufacturer_id -> iso_17442:LegalEntityId
45:     address -> Address
46:   end
47: 
48: end

The following listing shows the definition of the enumeration AlphaTwoCountryCode. The whitespace in this example should be ignored, it is just useful to layout a grid of 26 rows of 26 columns for all the possible combination of two ASCII upper case letters.

 1: module iso_3166_1 is
 2: 
 3:   enum AlphaTwoCountryCode of
 4:              AD AE AF AG    AI       AL AM    AO    AQ AR AS AT AU    AW AX    AZ
 5:     BA BB    BD BE BF BG BH BI BJ    BL BM BN BO    BQ BR BS BT    BV BW    BY BZ
 6:     CA    CC CD    CF CG CH CI    CK CL CM CN CO       CR       CU CV CW CX CY CZ
 7:                 DE             DJ DK    DM    DO                               DZ
 8:           EC    EE    EG EH                            ER ES ET 
 9:                             FI FJ FK    FM    FO       FR
10:     ;; ...
11:     ;; ...
12:     ;; ...
13:     VA    VC    VE    VG    VI             VN                   VU
14:                    WF                                     WS
15:                 YE                                           YT
16:     ZA                                  ZM                            ZW
17:   end
18: 
19: end

Given this module name, iso_3166_1, we should explain the use of underscores in module names. While any name in SDML may include underscores, typically as a word separator, the convention in module names is to use the underscore to note any logical hierarchy in definitions. So we have all ISO standards start with iso_ followed by the standard number 3166 and then in this case the part number _1.

Consider using custom datatypes to constrain basic types to avoid errors. For example, in listing 5 the identifier for an Item (line 36) is a GTIN or Global Trade Item Number. This standardized identifier has a number of specific formats but the shortest of those is 8 characters and the longest is 13 characters. So, we create a new datatype which restricts the basic string to between 8 and 13 characters only using a regex pattern using an annotation property. This datatype and another standard from GS1 is shown in listing 7.

 1: module gs1 is
 2: 
 3:   import xsd
 4: 
 5:   datatype GlobalLocationNumber <- string is
 6:     @xsd:pattern = "^[0-9]{13}$"
 7:   end
 8: 
 9:   datatype GlobalTradeItemNumber <- string is
10:     @xsd:pattern = "(^[0-9]{8}$)|(^[0-9]{13}$)|(^[0-9]{14}$)"
11:   end
12: 
13: end

Table 1 lists the annotation properties that can you can use to restrict existing datatypes. All scalar types have the following additional constraints; maxInclusive, maxExclusive, minInclusive, and minExclusive.

Table 2: Datatype Constraints
Datatype Builtin Constraints
binary minLength, maxLength, and length
boolean  
decimal pattern, totalDigits, fractionDigits, and scalar
double pattern, and scalar
integer pattern, and scalar
string pattern, minLength, maxLength, length, and langRange
unsigned pattern, and scalar
uri pattern, minLength, maxLength, and length
xsd:date pattern, explicitTimezone and scalar
xsd:time pattern, explicitTimezone and scalar
xsd:dateTime pattern, explicitTimezone and scalar
xsd:duration pattern, explicitTimezone and scalar
Sequence Types pattern, minLength, maxLength, and length

3.2.2. Hospitality Example

Listing 8 adds 2 datatype definitions, 1 enum definition, and 7 structure definitions. We have also made the previous entity Room a structure as it is dependent wholly on the valid entity Property. Lines 79 and 85 introduce a new sequence constraint with the keyword unique that requires members of the sequence to be unique, effectively defining the sequence as a set.

  1: module hospitality is
  2: 
  3:   import xsd
  4: 
  5:   entity Customer
  6: 
  7:   entity Booking is
  8:     identity booking_code -> string
  9:     customer -> Customer
 10:     property -> Property
 11:     room_style -> RoomStyle
 12:     room_rate -> Currency
 13:     dates -> DateRange
 14:     number_of_adults -> integer
 15:     number_of_children -> integer
 16:   end
 17: 
 18:   entity Property is
 19:     identity property_id -> unsigned
 20:     name -> string
 21:     styles -> {1..} RoomStyle
 22:     rooms -> {1..} Room
 23:   end
 24: 
 25:   entity Bill is
 26:     identity transaction_id -> unknown
 27:     for_stay -> Stay
 28:     charges -> {0..} Purchase
 29:   end
 30: 
 31:   entity Stay is
 32:     identity id -> unknown
 33:     booking -> {0..1} Booking
 34:     room_number -> unsigned
 35:     room_rate -> {0..1} Currency ;; if different from booking
 36:     dates -> DateRange
 37:     tax_percent -> RatePercentage
 38:     discount_percent -> RatePercentage
 39:   end
 40: 
 41:   datatype Currency <- decimal is
 42:     @xsd:totalDigits = 9
 43:     @xsd:fractionDigits = 3
 44:   end
 45: 
 46:   datatype RatePercentage <- decimal is
 47:     @xsd:totalDigits = 5
 48:     @xsd:fractionDigits = 3
 49:   end
 50: 
 51:   enum RoomFeature of
 52:     View
 53:     NonSmoking
 54:     Smoking
 55:     Accessible
 56:     OneSingleBed
 57:     OneDoubleBed
 58:     OneQueenBed
 59:     OneKingBed
 60:     TwoSingleBeds
 61:     TwoDoubleBeds
 62:     TwoQueenBeds
 63:     TwoKingBeds
 64:     Suite
 65:     Fridge
 66:     Kitchen
 67:     Balcony
 68:   end
 69: 
 70:   structure DateRange is
 71:     starts -> xsd:date
 72:     ends -> xsd:date
 73:   end
 74: 
 75:   structure Room is
 76:     number -> unsigned
 77:     floor -> unsigned
 78:     style -> RoomStyle
 79:     additional_features -> {unique 0..*} RoomFeature
 80:     rate -> Currency
 81:   end
 82: 
 83:   structure RoomStyle is
 84:     name -> string
 85:     features -> {unique 1..*} RoomFeature
 86:   end
 87: 
 88:   structure Charge is
 89:     item -> string
 90:     provider -> string
 91:     charge -> decimal
 92:     tax_percent -> RatePercentage
 93:     discount_percent -> RatePercentage
 94:   end
 95: 
 96:   structure Purchase is
 97:     stay -> Stay
 98:     charges -> {0..} Charge
 99:     payment -> Payment
100:   end
101: 
102:   structure Payment is
103:     amount -> Currency
104:     instrument -> PaymentInstrument
105:   end
106: 
107:   structure PaymentInstrument
108: end

While this module has added a lot of detail, one structure at the end, PaymentInstrument is not yet complete. Be patient, we will come back to that in the section 3.4 later.

You can add a specific type of annotation, a constraint, to capture conditions that cannot be expressed by the model’s structure alone. For example, in listing 9 we have a simple structure that contains a start date and an end date. They are correctly typed so we know they have to be dates, but what if we were to set the start date to be after the end date, how do we validate that? Can we?

 1: module hospitality is
 2: 
 3:   import xsd
 4: 
 5:   structure DateRange is
 6:     assert valid_range = "range starts before it ends"
 7: 
 8:     assert formal_range is
 9:       forall self, self.starts <= self.ends
10:     end
11: 
12:     starts -> xsd:date
13:     ends -> xsd:date
14:   end
15: 
16: end

3.2.3. Logistics Example

The detail added to the logistics example is extensive, but some highlights are the use of “kind” enumerations for Location (line 10), LogisticsUnit (line 48), and LogisticsAsset (line 103). We have also added a constraint to the logistics unit structure which enforces a rule that a box may not include other units. The more astute reader will note that this is woefully insufficient as it does not stop us defining a unit of kind palette which includes a unit of kind rail car – that’s some big palette.

  1: module logistics is
  2: 
  3:   import [ dc owl rdf skos uuid ]
  4:   import [ gs1 ]
  5: 
  6:   @skos:prefLabel = "Logistics"@en
  7:   @skos:altLabel = "Shipping"@en
  8:   @dc:description = "A domain model for long-haul logistics."@en
  9: 
 10:   entity Location is
 11:     identity gln -> gs1:GlobalLocationNumber
 12:     kind -> LocationKind
 13:     within -> {0..1} Location
 14:   end
 15: 
 16:   enum LocationKind of
 17:     Office
 18:     Warehouse
 19:     WarehouseYard
 20:     WarehouseDock
 21:     RailDepot
 22:     RailYard
 23:     Port
 24:     PortLoading
 25:     Airport
 26:     AirportTransfer
 27:   end
 28: 
 29:   entity Consignor is
 30:     identity gln -> gs1:GlobalLocationNumber
 31:     lei -> {0..1} gs1:LegalEntityIdentifier
 32:     name -> string
 33:   end
 34: 
 35:   entity Consignment is
 36:     identity ginc -> gs1:GlobalIdentificationNumberForConsignment
 37:     consignor -> Consignor
 38:     shipments -> {1..} Shipment
 39:   end
 40: 
 41:   entity Shipment is
 42:     identity gsin -> gs1:GlobalShipmentIdentificationNumber
 43:     from -> Location
 44:     to -> Location
 45:     units -> {1..} LogisticsUnit
 46:   end
 47: 
 48:   structure LogisticsUnit is
 49:     assert can_include_others = "not empty(includes) implies not kind = Box"
 50:     sscc -> gs1:SerialShippingContainerCode
 51:     kind -> LogisticsUnitKind
 52:     asset -> {1..} LogisticsAsset
 53:     dimension -> Dimensions
 54:     weight -> decimal
 55:     hazmat_classification -> {unique 0..9} DotHazardClassification
 56:     includes -> {0..} LogisticsUnit
 57:   end
 58: 
 59:   enum LogisticsUnitKind of
 60:     Box
 61:     Pallet
 62:     PalletStack
 63:     Container
 64:     RailCar
 65:   end
 66: 
 67:   enum DotHazardClassification of
 68:     @owl:equivalentClass = unsigned
 69:     Explosives is
 70:       @rdf:value = 1
 71:     end
 72:     Gasses is
 73:       @rdf:value = 2
 74:     end
 75:     FlammableLiquids is
 76:       @rdf:value = 3
 77:     end
 78:     FlammableSolids is
 79:       @rdf:value = 4
 80:     end
 81:     OxodizingSubstances is
 82:       @rdf:value = 5
 83:     end
 84:     ToxicSubstances is
 85:       @rdf:value = 6
 86:     end
 87:     RadioactiveMaterials is
 88:       @rdf:value = 7
 89:     end
 90:     CorrosiveSubstances is
 91:       @rdf:value = 8
 92:     end
 93:     MiscellaneousDangerousMaterials is
 94:       @rdf:value = 9
 95:     end
 96:   end
 97: 
 98:   structure Dimensions is
 99:     height -> decimal
100:     width -> decimal
101:     depth -> decimal
102:   end
103: 
104:   structure LogisticsAsset is
105:     grai -> gs1:GlobalReturnableAssetIdentifier
106:     kind -> LogisticsAssetKind
107:   end
108: 
109:   enum LogisticsAssetKind of
110:     Pallet
111:     Container
112:     RailCar
113:   end
114: 
115: end

You will probably be thinking that all this detail is making the module quite large and possibly cumbersome. In the following section (3.3) we will discuss approaches to organizing modules to allow not only ease of navigation but also parallel editing.

The enumeration DotHazardClassification in listing 10 above probably looks intimidating compared to the previous examples, and that’s OK. This enumeration represents the U.S. Department of Transportation’s Federal Motor Carrier Safety Administration’s “Nine Classes of Hazardous Material” classification. As such the values one through nine are defined by government and so we want to be able to associate those values with the variants of the enum.

We use two key annotation properties that affect the semantics of the enum.

rdf:value
this denotes the relationship between the variant and a specific value. It is not important that the values are numbers, only that they are of the same type and unique.
owl:equivalentClass
this denotes that the enumeration may be considered equivalent to the some type. It is important that all values attached to variants are valid for the type attached to the enum.

Also, the cardinality expression on member hazmat_classification is {unique 1..9}, to denote that while a unit may contain a combination of coded materials each code appears only once.

3.3. Describe Events emitted by Entities

SDML does not have a language for describing behavior, so no sequence, activity, state machine, or other diagrams. Given that the domain model should describe the what of a domain, not the how it is too tempting to start to describe the behavior inside the domain, it’s operating rules. Additionally, when describing a domain model it is often the case that the language used by stakeholders to describe any functional aspect of the model tends to be asynchronous and loosely coupled. To support this SDML provides the event definition, a way to describe events emitted by entities on state changes or other internal rules, but importantly these are part of the natural description/vocabulary of the domain.

An event looks very much like a structure, but is has a source keyword followed by the name of the entity which emits this event. In fact the similarity between an event and a structure is not accidental as it’s possible to see an event definition as some additional syntax for a structure which, and this is the important part, contains a reference to an instance of the entity type. In listing 11 we have a simple entity (line 5) and a simple event (line 10) emitted by the entity. If we were to create an equivalent structure it might look like that defined on line 13, which in turn is equivalent to the second structure on line 18.

 1: module example is
 2: 
 3:   import uuid
 4: 
 5:   entity Example is
 6:     identity id -> uuid:Uuid
 7:     some_other_thing -> integer
 8:   end
 9: 
10:   event ExampleCreated source Example
11: 
12:   ;; the above event is "equivalent" to the structure below
13:   structure ExampleCreatedStructure is
14:     source_entity -> Example
15:   end
16: 
17:   ;; the above is also "equivalent" to the structure below
18:   structure ExampleCreatedStructure2 is
19:     source_entity_id -> uuid:Uuid
20:   end
21: 
22: end

One specific different between a structure definition and an event definition is that the former without an explicit body (between is and end) is incomplete, an event does not need a body as it does already have a single member, the source entity. In many cases events need only be a distinct type with a reference to the particular entity and no other information.

3.3.1. Retail Example

 1: module retail_customers is
 2: 
 3:   entity Customer
 4: 
 5:   entity Purchase
 6: 
 7:   structure ItemQuantity is
 8:     item -> Item
 9:     quantity -> integer
10:   end
11: 
12:   event InventoryAdjustment source Purchase is
13:     adjustments -> {1..} ItemQuantity
14:   end
15: 
16: end
 1: module retail is
 2: 
 3:   entity Store
 4: 
 5:   event StoreNowOpen source Store is
 6:     at -> xsd:dateTime
 7:   end
 8:   event StoreNowClosed source Store is
 9:     at -> xsd:dateTime
10:   end
11: 
12: end
1: module retail is
2: 
3:   entity Item
4: 
5:   entity Manufacturer
6: 
7: end

3.3.2. Hospitality Example

 1: module hospitality_customers is
 2: 
 3:   entity Customer
 4: 
 5:   entity Booking
 6: 
 7:   enum BookingChange of
 8:     RoomStyle
 9:     Dates
10:     Occupancy
11:   end
12: 
13:   event BookingCreated source Booking
14:   event BookingUpdated source Booking is
15:     what_changed -> BookingChange
16:   end
17:   event BookingCanceled source Booking is
18:     reason -> string
19:   end
20: 
21: end
 1: module hospitality_properties is
 2: 
 3:   entity Property
 4: 
 5:   entity Stay
 6: 
 7:   event CheckedIn source Stay
 8:   event CheckedOut source Stay
 9:   event RoomCharged source Stay is
10:     charge -> Charge
11:   end
12: 
13:   entity Bill
14: 
15:   event BillReady source Bill
16:   event PaymentMade source Bill is
17:     payment -> Payment
18:   end
19:   event Settled source Bill
20: 
21: end

3.3.3. Logistics Example

 1: module logistics is
 2: 
 3:   import [ dc skos uuid ]
 4:   import [ gs1 ]
 5: 
 6:   @skos:prefLabel = "Logistics"@en
 7:   @skos:altLabel = "Shipping"@en
 8:   @dc:description = "A domain model for long-haul logistics."@en
 9: 
10:   entity Consignor
11: 
12:   ;; based uopn ANSI X.12 204
13:   event CarrierLoadTender source Consignor
14: 
15:   entity Carrier
16: 
17:   ;; based uopn ANSI X.12 990
18:   event CarrierLoadTenderResponse source Carrier
19: 
20:   entity Consignment
21: 
22:   ;; based uopn ANSI X.12 211
23:   event BillOfLading source Consignment
24:   ;; based uopn ANSI   X.12 210
25:   event FreightDetailsAndInvoice source Consignment
26: 
27:   entity Shipment
28: 
29:   ;; based uopn ANSI X.12 856
30:   event AdvanceShippingNotice source Shipment
31:   ;; based uopn ANSI X.12 214
32:   event TransportStatusNotification source Shipment
33:   event TransferRequest source Shipment
34:   event TransferConfirmation source Shipment
35: 
36:   enum TransferKind of
37:     Pickup
38:     Dropoff
39:   end
40: 
41: end

3.4. Describe “This or That” with Unions

So far we have ween entities, structures, events, and enumerations.

3.4.1. Hospitality Example

 1: module hospitality_properties is
 2: 
 3:   import pii
 4: 
 5:   structure PaymentInstrument is
 6:     assert only_one =
 7:       "one, and only one, member may be present"
 8:     cash -> {0..1} Currency
 9:     credit_balance -> {0..1} Currency
10:     points -> {0..1} unsigned
11:     card -> {0..1} pii:CardToken
12:   end
13: 
14: end
 1: module hospitality_properties is
 2: 
 3:   import pii
 4: 
 5:   union PaymentInstrument of
 6:     Currency as Cash
 7:     Currency as CreditBalance
 8:     unsigned as Points
 9:     pii:CardToken as Card
10:   end
11: 
12: end

3.4.2. Logistics Example

 1: module gs1 is
 2: 
 3:   import [ xsd ]
 4: 
 5:   datatype UniversalProductCode <- string
 6:   datatype EuropeanArticleNumber <- string
 7:   datatype InternationalStandardBookNumber <- string
 8:   datatype InternationalStandardMusicNumber <- string
 9:   datatype InternationalStandardSerialNumber <- string
10: 
11:   union GlobalTradeIdentificationNumber of
12:     @skos:prefLabel = "Global Trade Identification Number"@en
13:     @skos:altLabel = "GTIN"@en
14:     UniversalProductCode
15:     EuropeanArticleNumber
16:     InternationalStandardBookNumber
17:     InternationalStandardMusicNumber
18:     InternationalStandardSerialNumber
19:   end
20: 
21: end

4. How do I use it?

DDD

4.1. Editing SDML Source

TBD

4.2. Drawing Diagrams

primer-hospitality-concepts.svg

Figure 1: Hospitality Concepts (Entities)

primer-hospitality-uml.svg

Figure 2: Hospitality UML

4.3. Document Modules

TBD

5. What do I do next?

TBD

5.1. For Authors

TBD

5.2. For Tool Builders

TBD

5.3. More Reading

6. A Brief Glossary

Annotation
A property or constraint attached to a definition that may document, classify, or constrain the definition in ways the SDML syntax cannot. For example, attaching a descriptive property, a prescriptive definition, or categorization labels.
Constraint
A mechanism to describe correctness, or well-formedness, rules for a particular model and specifically those rules that may be hard or impossible to capture in the SDML syntax itself.
Datatype
An unstructured type that describes the atomic values (strings, numbers, etc.) in a model. Making a datatype more specific such as saying a string has 4-10 characters only, or a quantity is 1-100 only, documents more information than string/number alone.
Definition
In SDML all elements in a model such as modules, entities, structures, fields in a structure, or variants of an enumeration, are definitions. Definitions all have an identity which is unique within it’s parent namespace.
Domain
Domain Model
Domain-Driven Development
Entity
Enumeration
Features
Formal Constraint
Informal Constraint
Member
A member is a field within a product type, consisting of a name and target type. A member may alternatively be a reference to a property role.
Model
Module
Namespace
Ordering (Sequence)
A constraint that denotes whether a sequence is ordered or unordered.
Product Type
A product type is one of entity, event, or structure.
Property (Annotation)
Property (Member)
Role
Sequence
Structure
Sum Type
A sum type is one of enumeration, property, or union.
Type Variant
UML
Union
Uniqueness (Sequence)
A constraint that denotes whether a sequence contains only unique elements or is non-unique.
Value Variant
Variant
A unique

Author: Simon Johnston

Created: 2024-02-26 Mon 11:41