classArticle{private$title;private$content;private$tags;publicfunction__construct(string$title,string$content,array$tags){$this->title =$title;$this->content =$content;$this->tags =$tags}publicfunctiongetTitle():string{return$this->title;}publicfunctionsetTitle(string$title){$this->title =$title;} // other getters and setters}
Of course, each solution can be used in other languages and is not always representative of its developer community.
All of these solutions do the same thing: describe some information as a list of attributes. Some solutions are more verbose because of the language restrictions or conventions.
How could we see that these elements are data structures?
There is a list of attributes
All attributes are accessible from outside (directly or indirectly)
For each attribute, the same pattern appears: attribute definition, declaration inside constructor, getters and setters (when a pattern is inexistent, it is inexistent for all attribute)
So we have a common pattern. We could use for instance the version with the least characters to write.
Information validation
We looked at how to structure the information. For most of the structures, we can pass to the language syntax. However, there is another notion to look at.
Look at the following code:
What is the issue about this code? Should we consider this type valid as soon as we have two dates?
To ensure the data validation, we could do in several ways:
One more time, you can use some solutions in other languages.
It answers our question of how to validate data consistency. However, did you see that we check that start < end and not that start <= end? What happens if this restriction was added afterward and some data in your database are now invalid (where start == end) but you need to accept the value for any reason? Your code and your data are not compatible anymore and will fail each time you query these values.
To avoid this case, you will surely define another function to deserialize data from your database and remove the validation. It is not an issue as long as you accept it. However, it creates some boilerplate that could be avoid.
Type syntax
Type declaration
You can declare a type by writing the following:
You can also give an alias to an existing type:
For more information about attribute declaration, please refer to part Attributes.
Verification reference
If you want to specify a verification that your type must validate, you can do it with a verification reference:
You can also do it with alias types:
Please see page Verifications for more information about using them.
Inline verification
If you want to define the verification inside the type, you can also do it:
Or with alias types:
Note that you do not need (and cannot) to give the type of the verification parameter. It is induced by the surrounding type.
Type parameters
If you want to declare parameters to your types in order to reuse their structure and logic, you can do it this way:
In concrete usage, top-level structures has few value to have type parameters. They mostly come in handy for alias types to increase the level of abstraction of common elements (NumberHigherThan, ListOfSize…).
Type verification parameters
You can also specify that you type verification need an external value. In this case, you will declare a named verification with parameters.
When you check the full aggregate of Order by using withConfiguration, all verifications with this name will be included into the verification. So in above code, both withConfiguration of Order and OrderItem will be call.
The top-level function in targeted language will require all parameters identified by their name. An error will occur if you use a name with different type in the same aggregate.
Type parameters or type verification parameters?
You could do what you want in both cases. However, these features were designed for two different cases:
Type parameters were designed for static invariants inside code, top-level aggregates should not have type parameters
Type verification parameters were designed for external parameters (configuration, user settings, options taken from an API…)
When you use one or the other, please think about where the value comes from and why you define this parameter.
Attributes
We talked about types but not their attributes specifically.
Attribute declaration
Their default usage is:
Attribute verifications
You can add verification references to attributes:
If you want to add a constraint to each tag, you need to define an alias type:
Also, if alias type has parameters, you can specify them:
Attribute usage
In the rest of the code, to access to an attribute, just name it:
All attributes are public and immutable.
Enumerations
Like most language, Definiti has an enumeration system.
You can describe them like:
Line returns are not required. You can also write the enumeration like:
You can compare enumeration values between them but you cannot use them another way.
public class Article {
private final String title;
private final String content;
private final List<String> tags;
public Article(String title, String content, List<String> tags) {
this.title = title;
this.content = content;
this.tags = tags;
}
public String getTitle() {
return title;
}
// other getters
}
case class Period(
start: Date,
end: Date
)
class Period private (val start: Date, val end: Date)
object Period {
def apply(start: Date, end: Date): Either[String, Period] = {
if (start.isBefore(end)) {
Right(new Period(start, end))
} else {
Left("start > end 😱")
}
}
}
interface Period {
start: Date
end: Date
}
function isPeriodValid(period: Period): boolean {
return period.start < period.end
}
class Period {
private $start;
private $end;
public function __constsruct(Date $start, Date $end) {
if ($start->isBefore($end)) {
$this->start = $start;
$this->end = $end;
} else {
throw new Exception("start > end 😱")
}
}
public function setStart(Date $start) {
if ($start->isBefore($this->end)) {
$this->start = $start;
} else {
throw new Exception("start > end 😱")
}
}
// other setters and getters
}
class Period {
private Date start;
private Date end;
public Period(Date start, Date end) {
if (start.isBefore(end)) {
this.start = start;
this.end = end;
} else {
throw new RuntimeException("start > end 😱")
}
}
// getters
}
type Article {
title: String
content: String
tags: List[String]
}
type ArticleId = String
type Period verifying IsValidPeriod {
start: Date
end: Date
}
// Could also be in another file
verification IsValidPeriod {
"start > end 😱"
(period: Period) => {
period.start.isBefore(period.end)
}
}
type NonEmptyString = String verifying IsNonEmptyString
verification IsNonEmptyString {
"Please give a non empty string"
(value: String) => {
value.nonEmpty()
}
}
type Period {
start: Date
end: Date
verify {
"start > end 😱"
(period) => {
period.start.isBefore(period.end)
}
}
}
type NonEmptyString = String {
verify {
"Please give a non empty string"
(string) => {
string.nonEmpty()
}
}
}
type User(minAge: Number) {
name: String
age: NumberHigherThan(minAge)
roles: ListOfMaxSize[String](5) // Maximum 5 roles
}
type NumberHigherThan(min: Number) = Number {
verify {
message("number.higher.than", Number, Number)
(number) => {
if (number > min) {
ok
} else {
ko(number, min)
}
}
}
}
type Order {
id: String
items: List[OrderItem]
verify withConfiguration(orderIdFormat: String) => {
"The id should respect the configured format"
(order) => {
order.id.matches(orderIdFormat)
}
}
}
type OrderItem {
product: String
quantity: Number
verify withConfiguration(allowedQuantity: Number) => {
"The quantity is too important"
(orderItem) => {
orderItem.quantity <= allowedQuantity
}
}
}
type Article {
title: String
content: String
tags: List[String]
}
type Article {
title: String verifying IsNonEmptyString // Will use default error message
content: String verifying IsNonEmptyString("Please give a content")
tags: List[String] verifying IsListOfMinSize(3) verifying HasNoDuplicatedElements
}
type Article {
title: String
content: String
tags: List[Tag]
}
type Tag = String verifying IsValidTag
type Article {
title: String
content: String
tags: ListOfMinSize[Tag](3)
}
type Period verifying IsValidPeriod {
start: Date
end: Date
}
// Could also be in another file
verification IsValidPeriod {
"start > end 😱"
(period: Period) => {
period.start.isBefore(period.end)
}
}