Chapter 5: Tightening the Rules
Dr. Portbridge had been entering patient data all morning. Then she noticed: the system had accepted an Animal with no name. Another had two species. An Appointment existed with zero dates. Nothing prevented nonsense because every attribute had cardinality “any”, zero or more, no questions asked. The sketch had been useful, but now she needed precision.
The problem
So far, every has declaration is unconstrained. The default cardinality is “any”, meaning zero to infinity. That’s great for rapid prototyping, but a real system needs rules:
- An animal must have exactly one name
- A species is required, and there’s only one
- An age might be unknown (strays, for instance)
- An owner is not always present (think of Pixel, the stray kitten)
Dolfin lets you tighten these rules with cardinality keywords placed between the colon and the type.
Cardinality keywords
| Keyword | Meaning | Example |
|---|---|---|
any (default) | Any (0 to ∞), the default | has task: any Task |
one | Exactly one (required) | has name: one string |
optional | Zero or one | has age: optional int |
The keyword goes after the colon and before the type:
has <attribute>: <cardinality> <Type>
Applying cardinality to Animal
Let’s revisit our Animal concept with proper constraints:
concept Animal:
has name: one string
has species: one Species
has age: optional int
has weight: optional float
has owner: optional Owner
Now:
nameandspeciesare required: exactly one value, always presentage,weight, andownerare optional: they can be absent
Choosing the right cardinality
This is a modeling decision, not a technical one. Ask yourself: “How many of this attribute can an entity reasonably have? And how many must it have?”
| Attribute | Cardinality | Reasoning |
|---|---|---|
name | one | Every animal must have a name, even “Unknown” |
species | one | You always know if it’s a dog or a cat |
age | optional | Strays often have unknown ages |
weight | optional | Not always measured on first visit |
owner | optional | Strays exist |
Applying cardinality to Owner and Appointment
Let’s apply the same thinking to Owner:
concept Owner:
has first_name: one string
has last_name: one string
has phone: one string
has email: optional string
has address: optional string
And to Appointment:
concept Appointment:
has animal: one Animal
has date: one string
has reason: one string
has urgency: one Urgency
has status: one AppointmentStatus
has diagnosis: optional string
has treatment: optional string
has notes: string
A diagnosis and treatment are unknown when the appointment is first scheduled: they only get filled in during or after the visit.
Leaving an attribute unconstrained
If you omit the cardinality keyword, you get the default: any (zero or more, no upper limit). This is useful for attributes where you genuinely don’t know the bounds yet, or where any number of values is acceptable:
concept Appointment:
has notes: string
Here notes has no constraint: an Appointment can have zero notes, one note, or a hundred. That’s sometimes exactly what you want.
The story so far
package <http://happypaws.com/clinic>:
dolfin_version "1"
version "0.1.0"
author "Dr. Helen Portbridge"
description "The Happy Paws veterinary clinic data model"
concept Species:
only values:
Dog
Cat
Bird
Rabbit
Reptile
Other
concept Urgency:
only values:
Routine
Urgent
Emergency
concept AppointmentStatus:
only values:
Scheduled
InProgress
Completed
Cancelled
concept Owner:
has first_name: one string
has last_name: one string
has phone: one string
has email: optional string
has address: optional string
concept Veterinarian:
has name: one string
has license_number: one string
concept Animal:
has name: one string
has species: one Species
has age: optional int
has weight: optional float
has owner: optional Owner
concept Appointment:
has animal: one Animal
has date: one string
has reason: one string
has urgency: one Urgency
has status: one AppointmentStatus
has diagnosis: optional string
has treatment: optional string
has notes: optional string
property treatedBy:
Animal -> Veterinarian
Try it
The Veterinarian concept currently requires name and license_number. Add an optional specialization attribute (as a string) and an optional phone:
concept Veterinarian:
has name: one string
has license_number: one string
The model finally rejected nonsense. An Animal without a name? Error. An Appointment with no date? Error. Pixel, the stray kitten, was registered with no owner and no known age, and the system accepted her gracefully.
A week later, the clinic ran a vaccination drive. Thirty animals in one day. Dr. Portbridge needed to record which vaccines each animal had received, not one, not two, but a variable number. She tried adding
has vaccine: one stringbut that only held one. She needed a list.