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:
model Booking {id: UUIDorg_id: String @indexslot_id: UUID @belongs_to(AppointmentSlot)patient_email: String @emailnotes: String?version: Int @versioncreated_at: Timeupdated_at: Time} policy Booking {tenant_field: org_idread: 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: exactpatient_email: exact} searchable {patient_email
notes
}}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?