Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

KeywordMeaningExample
any (default)Any (0 to ∞), the defaulthas task: any Task
oneExactly one (required)has name: one string
optionalZero or onehas 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:

  • name and species are required: exactly one value, always present
  • age, weight, and owner are 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?”

AttributeCardinalityReasoning
nameoneEvery animal must have a name, even “Unknown”
speciesoneYou always know if it’s a dog or a cat
ageoptionalStrays often have unknown ages
weightoptionalNot always measured on first visit
owneroptionalStrays 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 string but that only held one. She needed a list.