The visual language of UML class diagrams supports defining integrity constraints either with the help of special modeling elements, such as multiplicity expressions, or with the help of invariants shown in a special type of rectangle attached to the model element concerned. UML invariants can be expressed in plain English or in the Object Constraint Language (OCL). We use UML class diagrams for making design models with integrity constraints that are independent of a specific programming language or technology platform.
UML class diagrams provide special support for expressing multiplicity (or cardinality) constraints. This
type of constraint allows to specify a lower multiplicity
(minimum cardinality) or an upper
multiplicity (maximum cardinality), or both, for
a property or an association end. In UML, this takes the form of a multiplicity expression
l..u
where the lower multiplicity l
is a non-negative integer and the
upper multiplicity u
is either a positive integer or the special value
*
, standing for unbounded. For showing property multiplicity constrains in a class
diagram, multiplicity expressions are enclosed in brackets and appended to the property name in
class rectangles, as shown in the Person
class rectangle in the class diagram
below.
Integrity constraints may take many different forms. For instance, property constraints define conditions on the admissible property values of an object. They are defined for an object type (or class) such that they apply to all objects of that type. We concentrate on the most important kinds of property constraints:
require that the length of a string value for an attribute is less than a certain maximum number, or greater than a minimum number.
require that a property must have a value. For instance, a person must have a name, so the name attribute must not be empty.
require that an attribute must have a value from the value space of the type that has been defined as its range. For instance, an integer attribute must not have the value "aaa".
require that the value of a numeric attribute must be in a specific interval.
require that a string attribute's value must match a certain pattern defined by a regular expression.
apply to multi-valued properties, only, and require that the cardinality of a multi-valued property's value set is not less than a given minimum cardinality or not greater than a given maximum cardinality.
require that a property's value is unique among all instances of the given object type.
require that the values of a reference property refer to an existing object in the range of the reference property.
require that the value of a property must not be changed after it has been assigned initially.
The visual language of UML class diagrams supports defining integrity constraints with the help of invariants expressed either in plain English or in the Object Constraint Language (OCL) and shown in a special type of rectangle attached to the model element concerned. We use UML class diagrams for modeling constraints in design models that are independent of a specific programming language or technology platform.
UML class diagrams provide special support for expressing multiplicity (or cardinality)
constraints. This type of constraint allows to specify a lower multiplicity ( minimum
cardinality) or an upper multiplicity (maximum cardinality), or both, for a property or an
association end. In UML, this takes the form of a multiplicity expression l..u
where the lower multiplicity l
is either a non-negative integer or the special
value *
, standing for unbounded, and the upper multiplicity u
is
either a positive integer not smaller than l
or the special value *
.
For showing property multiplicity constrains in a class diagram, multiplicity expressions are
enclosed in brackets and appended to the property name in class rectangles, as shown in the
Person
class rectangle below.
Since integrity maintenance is fundamental in database management, the data definition language part of the relational database language SQL supports the definition of integrity constraints in various forms. On the other hand, however, integrity constraints and data validation are not supported at all in common programming languages such as PHP, Java, C# or JavaScript. It is therefore important to choose an application development framework that provides sufficient support for constraint validation. Notice that in HTML5, there is some support for validation of user input data in form fields.
In the following sections, we discuss the different types of property constraints listed above in more detail. We also show how to express them in UML class diagrams, in SQL table creation statements, in JavaScript model class definitions, as well as in the annotation-based frameworks Java Persistency API (JPA) and ASP.NET's DataAnnotations.
Any systematic approach to constraint validation also requires to define a set of error (or 'exception') classes, including one for each of the standard property constraints listed above.
The length of a string value for a property such as the title of a book may have to be
constrained, typically rather by a maximum length, but possibly also by a minimum length. In an
SQL table definition, a maximum string length can be specified in parenthesis appended to the
SQL datatype CHAR
or VARCHAR
, as in VARCHAR(50)
.
UML does not define any special way of expressing string length constraints in class diagrams. Of course, we always have the option to use an invariant for expressing any kind of constraint, but it seems preferable to use a simpler form of expressing these property constraints. One option is to append a maximum length, or both a minimum and a maximum length, in parenthesis to the datatype name, like so
Another option is to use min/max constraint keywords in the property modifier list:
A mandatory value constraint requires that a property
must have a value. This can be expressed in a UML class diagram with the help of a multiplicity
constraint expression where the lower multiplicity is 1. For a single-valued property, this
would result in the multiplicity expression 1..1
, or the simplified expression
1
, appended to the property name in brackets. For example, the following class
diagram defines a mandatory value constraint for the property name
:
Whenever a class rectangle does not show a multiplicity expression for a property, the
property is mandatory (and single-valued), that is, the multiplicity expression 1
is the default for properties.
In an SQL table creation statement, a mandatory value constraint is expressed in a table
column definition by appending the key phrase NOT NULL
to the column definition as
in the following example:
CREATE TABLE persons(
name VARCHAR(30) NOT NULL,
age INTEGER
)
According
to this table definition, any row of the persons
table must have a value in the
column name
, but not necessarily in the column age
.
In JavaScript, we can encode a mandatory value constraint by a class-level check function that tests if the provided argument evaluates to a value, as illustrated in the following example:
Person.checkName = function (n) { if (n === undefined) { return "A name must be provided!"; // constraint violation error message } else return ""; // no constraint violation };
In JPA, the mandatory property name
is annotated with NotNull
in the following way:
@Entity
public class Person {
@NotNull
private String name;
private int age;
}
The equivalent ASP.NET's Data Annotation is Required
as shown in
public class Person{
[Required]
public string name { get; set; }
public int age { get; set; }
}
A range constraint requires that a property must have a value from the value space of the
type that has been defined as its range. This is implicitly expressed by defining a type for a
property as its range. For instance, the attribute age
defined for the object type
Person
in the class diagram above has the range Integer
, so it must
not have a value like "aaa", which does not denote an integer. However, it may have values like
-13 or 321, which also do not make sense as the age of a person. In a similar way, since its
range is String
, the attribute name
may have the value "" (the empty
string), which is a valid string that does not make sense as a name.
We can avoid allowing negative integers like -13 as age values, and the empty string as a
name, by assigning more specific datatypes as range to these attributes, such as
NonNegativeInteger
to age
, and NonEmptyString
to
name
. Notice that such more specific datatypes are neither predefined in SQL nor
in common programming languages, so we have to implement them either in the form of user-defined
types, as supported in SQL-99 database management systems such as PostgreSQL, or by using
suitable additional constraints such as interval constraints,
which are discussed in the next section. In a UML class diagram, we can simply define
NonNegativeInteger
and NonEmptyString
as custom datatypes and then
use them in the definition of a property, as illustrated in the following diagram:
In JavaScript, we can encode a range constraint by a check function, as illustrated in the following example:
Person.checkName = function (n) { if (typeof(n) !== "string" || n.trim() === "") { return "Name must be a non-empty string!"; } else return ""; };
This
check function detects and reports a constraint violation if the given value for the
name
property is not of type "string" or is an empty string.
In JPA, for declaring empty strings as non-admissible we must set the context parameter
javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL
to true
in the web deployment descriptor file web.xml
.
In ASP.NET's Data Annotations, empty strings are non-admissible by default.
An interval constraint requires that an attribute's value must be in a specific interval,
which is specified by a minimum value or a maximum value, or both. Such a constraint can be
defined for any attribute having an ordered type, but normally we define them only for numeric
datatypes or calendar datatypes. For instance, we may want to define an interval constraint
requiring that the age
attribute value must be in the interval [0,120]. In a class
diagram, we can define such a constraint as an "invariant" and attach it to the
Person
class rectangle, as shown in Figure 5.1 below.
In an SQL table creation statement, an interval constraint is expressed in a table column
definition by appending a suitable CHECK
clause to the column definition as in the
following example:
CREATE TABLE persons(
name VARCHAR(30) NOT NULL,
age INTEGER CHECK (age >= 0 AND age <= 120)
)
In JavaScript, we can encode an interval constraint in the following way:
Person.checkAge = function (a) { if (a < 0 || a > 120) { return "Age must be between 0 and 120!"; } else return ""; };
InJPA, we express this interval constraint by adding the annotations Min(0)
and Max(120)
to the property age
in the following way:
@Entity public class Person { @NotNull private String name; @Min(0)@Max(120) private int age; }
The equivalent ASP.NET's Data Annotation is Range(0,120)
as shown in
public class Person{
[Required]
public string name { get; set; }
[Range(0,120)]
public int age { get; set; }
}
A pattern constraint requires that a string attribute's value must match a certain pattern,
typically defined by a regular expression. For instance, for
the object type Book
we define an isbn
attribute with the datatype
String
as its range and add a pattern constraint requiring that the
isbn
attribute value must be a 10-digit string or a 9-digit string followed by "X"
to the Book
class rectangle shown in Figure 5.2 below.
In an SQL table creation statement, a pattern constraint is expressed in a table column
definition by appending a suitable CHECK
clause to the column definition as in the
following example:
CREATE TABLE books(
isbn VARCHAR(10) NOT NULL CHECK (isbn ~ '^\d{9}(\d|X)$'),
title VARCHAR(50) NOT NULL
)
The
~
(tilde) symbol denotes the regular expression matching predicate and the regular
expression ^\d{9}(\d|X)$
follows the syntax of the POSIX standard (see, e.g. the
PostgreSQL
documentation).
In JavaScript, we can encode a pattern constraint by using the built-in regular expression
function test
, as illustrated in the following
example:
Person.checkIsbn = function (id) { if (!/\b\d{9}(\d|X)\b/.test( id)) { return "The ISBN must be a 10-digit string or a 9-digit string followed by 'X'!"; } else return ""; };
In JPA, this pattern constraint for isbn
is expressed with the annotation
Pattern
in the following way:
@Entity
public class Book {
@NotNull
@Pattern(regexp="^\\(\d{9}(\d|X))$")
private String isbn;
@NotNull
private String title;
}
The equivalent ASP.NET's Data Annotation is RegularExpression
as shown
in
public class Book{
[Required]
[RegularExpression(@"^(\d{9}(\d|X))$")]
public string isbn { get; set; }
public string title { get; set; }
}
A cardinality constraint requires that the cardinality of a multi-valued property's value set is not less than a given minimum cardinality or not greater than a given maximum cardinality. In UML, cardinality constraints are called multiplicity constraints, and minimum and maximum cardinalities are expressed with the lower bound and the upper bound of the multiplicity expression, as shown in Figure 5.3 below, which contains two examples of properties with cardinality constraints.
The attribute definition nickNames[0..3]
in the class Person
specifies a minimum cardinality of 0 and a maximum cardinality of 3, with the meaning that a
person may have no nickname or at most 3 nicknames. The reference property definition
members[3..5]
in the class Team
specifies a minimum cardinality of 3
and a maximum cardinality of 5, with the meaning that a team must have at least 3 and at most 5
members.
It's not obvious how cardinality constraints could be checked in an SQL database, as there is no explicit concept of cardinality constraints in SQL, and the generic form of constraint expressions in SQL, assertions, are not supported by available DBMSs. However, it seems that the best way to implement a minimum (resp. maximum) cardinality constraint is an on-delete (resp. on-insert) trigger that tests the number of rows with the same reference as the deleted (resp. inserted) row.
In JavaScript, we can encode a cardinality constraint validation for a multi-valued property by testing the size of the property's value set, as illustrated in the following example:
Person.checkNickNames = function (nickNames) { if (nickNames.length > 3) { return "There must be no more than 3 nicknames!"; } else return ""; };
With Java Bean Validation annotations, we can specify
@Size( max=3) List<String> nickNames @Size( min=3, max=5) List<Person> members
A uniqueness constraint (or key
constraint) requires that a property's value is unique among all instances of
the given object type. For instance, in a UML class diagram with the object type
Book
we can define the isbn
attribute to be unique, or, in other
words, a key, by appending the (user-defined) property modifier keyword key
in
curly braces to the attribute's definition in the Book
class rectangle shown in
Figure 5.4
below.
In an SQL table creation statement, a uniqueness constraint is expressed by appending the
keyword UNIQUE
to the column definition as in the following example:
CREATE TABLE books(
isbn VARCHAR(10) NOT NULL UNIQUE,
title VARCHAR(50) NOT NULL
)
In JavaScript, we can encode this uniqueness constraint by a check function that tests if
there is already a book with the given isbn
value in the books
table
of the app's database.
An attribute (or, more generally, a combination of attributes) can be declared to be the
standard identifier for objects of a given type, if it is mandatory and unique. We can
indicate this in a UML class diagram with the help of the property modifier id
appended to the declaration of the attribute isbn
as shown in Figure 5.5 below.
Notice that such a standard identifier declaration implies both a mandatory value and a uniqueness constraint on the attribute concerned.
Standard identifiers are called primary keys in
relational databases. We can declare an attribute to be the primary key in an SQL table creation
statement by appending the phrase PRIMARY KEY
to the column definition as in the
following example:
CREATE TABLE books(
isbn VARCHAR(10) PRIMARY KEY,
title VARCHAR(50) NOT NULL
)
In JavaScript, we cannot easily encode a standard identifier declaration, because this would have to be part of the metadata of the class definition, and there is no standard support for such metadata in JavaScript. However, we should at least check if the given argument violates the implied mandatory value or uniqueness constraints by invoking the corresponding check functions discussed above.
A referential integrity constraint requires that the values of a reference property refer to an existing object in the range of the reference property. Since we do not deal with reference properties in this part of the tutorial, we postpone the discussion of referential integrity constraints to the next part of our tutorial.
A frozen value constraint defined for a property requires that the value of this property must not be changed after it has been assigned initially. This includes the special case of a read-only value constraint applying to mandatory properties that are initialized at object creation time. Typical examples of properties with a frozen value constraint are event properties, since the semantic principle that the past cannot be changed prohibits that the property values of past events can be changed.
In Java, a frozen value constraint can be enforced by declaring the property to be
final
. However, while in Java a final
property must be mandatory, a
frozen value constraint may also apply to an optional property.
In JavaScript, a read-only property can be defined with
Object.defineProperty( obj, "teamSize", {value: 5, writable: false, enumerable: true})
by making it unwritable, while an entire object o
can be frozen by stating
Object.freeze( o)
.
We postpone the further discussion of frozen value constraints to Part 6 of our tutorial.
So far, we have only discussed how to define and check property constraints. However, in certain cases there may be also integrity constraints that do not just depend on the value of a particular property, but rather on
the values of several properties of a particular object
both the values of a property before and after a change attempt
the set of all instances of a particular object type
the set of all instances of several object types
We plan to discuss these more advanced types of integrity constraints in a forthcoming book on web application engineering.