Carrier
Part Two · Core Language Model
Chapter 62 min read

Models and Enterprise Data Design

Models are where enterprise architecture becomes durable. Routes may change, workflows may be redesigned, and integration patterns may evolve, but data tends to survive. A poorly chosen data model can constrain an organization for years.

In Carrier, model declarations describe persistent business data owned by a service. That makes them architectural declarations, not just implementation details.

A Real Model with Policy and CRUD

Below is a real Booking model from the booking-service example. Notice how it declares not only fields and indexes, but also tenant scoping, role-based read/write rules, and the CRUD surface — all in one structured place:

excerpt
model Booking {
id: UUID
org_id: String @index
slot_id: UUID @belongs_to(AppointmentSlot)
patient_email: String @email
notes: String?
version: Int @version
created_at: Time
updated_at: Time
}
policy Booking {
tenant_field: org_id
read: roles [viewer, scheduler]
write: roles [scheduler]
}
crud Booking at "/bookings" {
create: protect StaffAuth roles [scheduler]
list: protect StaffAuth roles [viewer]
get: protect StaffAuth roles [viewer]
update: protect StaffAuth roles [scheduler]
delete: protect StaffAuth roles [scheduler]
filters {
slot_id: exact
patient_email: exact
}
searchable {

patient_email

notes

excerpt
}
}

This is the central enterprise advantage of Carrier modeling: the data, the access rule, the tenant boundary, and the API surface are co-located and machine-readable. A reviewer or governance tool can see, in one place, that Booking is tenant-scoped on org_id, that scheduler role is required for writes, and that patient_email and notes are searchable — which is itself a privacy-relevant fact.

Modeling Business Entities

Good model design starts with domain meaning. A patient, encounter, observation, consent, questionnaire response, referral, or care plan carries meaning beyond storage. Enterprise architects should be cautious with vague models such as Record, Item, Data, or Entry — those names often signal that the domain has not been understood clearly enough.

Schema Evolution

Schemas change because enterprises change. New products appear. Regulations shift. Integrations demand new identifiers. Carrier connects model changes to migration generation:

carrier migrate generate

This gives teams a structured path from source-level model evolution to database change. The generated migration should still be reviewed. Carrier can help produce the change, but the enterprise must decide whether the change is operationally safe — table size, required fields, indexes, backfills, compatibility, rollback behavior, and downtime risk.

Migration Strategy

A safe enterprise migration often happens in stages: add compatible schema; deploy code that can read old and new forms; backfill data; switch writers to the new shape; remove obsolete fields only after consumers have moved. Carrier helps by keeping schema impact close to the model source. But architects should still require migration discipline for high-risk domains, especially regulated data, tenant-scoped data, and high-volume operational tables.

System Tables and Generated Metadata

Carrier projects produce generated metadata under .carrier/, especially .carrier/manifest.json. This metadata is valuable because it lets architects inspect the declared system shape without reverse-engineering every source file. Models, routes, policies, and types become available as machine-readable evidence — useful for checks such as: which services own sensitive models? which models changed in this release? which APIs expose which entities?

Contents