Today we want to give you a sneak peak at some upcoming changes to Prisma's Datamodel. We've spent the last couple months iterating on a design that we're excited to share here today.
The goals of this design are the following:
- Readable: Anybody in your organization should be able to look at your datamodel for the first time and roughly understand your data's structure and relationships.
- Complete: Your datamodel should be the source of truth for your data
sources. You should be able to copy your datamodel from 1 computer to another,
run
prisma deploy
and get an exact replica of your data sources. - Flexible: Your datamodel should support all kinds of workflows and environments. Your datamodel should feel small to an indie hacker, but be powerful enough to scale up to the needs of large organizations.
- Multilingual: As we move further into the world of polyglot persistence, your datamodel should be able to span and thread many different data sources without losing the unique features that make each data source great.
Today I want to focus on readability. In the past, we've relied on GraphQL's SDL syntax for our Datamodel.
type Report @db(name: "reports") {
createdAt: DateTime! @db(name: "created_at")
id: Int! @id(strategy: SEQUENCE) @sequence(name: "reports_id_seq", initialValue: 1, allocationSize: 1)
standup: Standup! @db(name: "standup_id")
status: ReportStatus!
updatedAt: DateTime! @db(name: "updated_at")
user: User! @db(name: "user_id")
}
enum ReportStatus {
ASKED
COMPLETE
PENDING
SKIP
}
type Standup @db(name: "standups") {
channelId: String! @db(name: "channel_id")
createdAt: DateTime! @db(name: "created_at")
id: Int! @id(strategy: SEQUENCE) @sequence(name: "standups_id_seq", initialValue: 1, allocationSize: 1)
isThreaded: Boolean! @db(name: "is_threaded") @default(value: false)
posts: [Post]
questions: [Question]
reports: [Report]
standupsUsers: [StandupsUser]
team: Team! @db(name: "team_id")
timezone: String!
updatedAt: DateTime! @db(name: "updated_at")
}
type StandupsUser @db(name: "standups_users") {
createdAt: DateTime! @db(name: "created_at")
isStandupOwner: Boolean! @db(name: "is_standup_owner") @default(value: false)
standup: Standup! @db(name: "standup_id")
status: StandupUserStatus!
updatedAt: DateTime! @db(name: "updated_at")
user: User! @db(name: "user_id")
}
enum StandupUserStatus {
ACTIVE
INACTIVE
INVITED
}
type Team @db(name: "teams") {
botAccessToken: String! @db(name: "bot_access_token") @unique
botSlackId: String! @db(name: "bot_slack_id")
costPerUser: Int! @db(name: "cost_per_user") @default(value: 100)
createdAt: DateTime! @db(name: "created_at")
id: Int! @id(strategy: SEQUENCE) @sequence(name: "teams_id_seq", initialValue: 1, allocationSize: 1)
minimumMonthlyCost: Int! @db(name: "minimum_monthly_cost") @default(value: 0)
standups: [Standup]
stripeId: String @db(name: "stripe_id")
teamAccessToken: String! @db(name: "team_access_token") @unique
teamName: String! @db(name: "team_name")
teamSlackId: String! @db(name: "team_slack_id") @unique
trialEnds: DateTime! @db(name: "trial_ends")
updatedAt: DateTime! @db(name: "updated_at")
users: [User]
}
type User @db(name: "users") {
avatarUrl: String @db(name: "avatar_url")
createdAt: DateTime! @db(name: "created_at")
email: String
firstName: String @db(name: "first_name")
id: Int! @id(strategy: SEQUENCE) @sequence(name: "users_id_seq", initialValue: 1, allocationSize: 1)
isTeamOwner: Boolean! @db(name: "is_team_owner") @default(value: false)
lastName: String @db(name: "last_name")
reports: [Report]
reviews: [Review]
slackId: String! @db(name: "slack_id")
standupsUsers: [StandupsUser]
team: Team! @db(name: "team_id")
timezone: String!
updatedAt: DateTime! @db(name: "updated_at")
username: String!
}
This foundation has served us well, but as time went on a couple issues became more clear in our space:
- Default optional makes sense in GraphQL, but is a bad default for databases. Database fields should be required by default.
- We need to be able to support many model-level attributes. Database indexes, constraints, even primary keys can span multiple fields. GraphQL doesn't have this problem, but for us it meant too much clutter up front.
Additionally, we wanted to take some insights from auto-formatters like gofmt
and prettier
to make it easier to read large datamodels, temper distracting
syntax debates, and reduce visual noise in pull requests.
Without further ado, here's what we came up with. I hope you'll like it as much as we do.
model Report {
id Int @id @pg.serial("reports_id_seq")
createdAt DateTime
standup Standup
status ReportStatus
updatedAt DateTime
user User
}
enum ReportStatus {
Asked = "ASKED"
Complete = "COMPLETE"
Pending = "PENDING"
Skip = "SKIP"
}
model Standup {
id Int @id @pg.serial("standups_id_seq")
channelId String
createdAt DateTime
isThreaded Boolean @default(false)
name Citext
posts Post[]
questions Question[]
reports Report[]
standupsUsers StandupsUser[]
team Team
time Time
timezone String
updatedAt DateTime
}
enum StandupUserStatus {
Active = "ACTIVE"
Inactive = "INACTIVE"
Invited = "INVITED"
}
model StandupUser {
@@id([ standup, user ])
createdAt DateTime
isStandupOwner Boolean @default(false)
standup Standup
status StandupUserStatus
time Time
updatedAt DateTime
user User
}
model Team {
id Int @id @pg.serial("teams_id_seq")
botAccessToken String @unique
botSlackId String
costPerUser Int @default(100)
createdAt DateTime
minimumMonthlyCost Int @default(0)
standups Standup[]
stripeId String?
teamAccessToken String @unique
teamName String
teamSlackId String @unique
trialEnds DateTime
updatedAt DateTime
users User[]
}
model User {
id Int @id @pg.serial("users_id_seq")
avatarUrl String?
createdAt DateTime
email String?
firstName String?
isTeamOwner Boolean @default(false)
lastName String?
reports Report[]
reviews Review[]
slackId String
standupsUsers StandupsUser[]
team Team
timezone String
updatedAt DateTime
username String
}
At this point, you may be asking yourself, do I have to manually space the columns to make it look nice like that? We're going to be shipping with an auto-formatter on day 1, so you'll get nice readability without breaking your spacebar.
If you'd like to take a look at more examples, we've open-sourced a repository with many more examples and syntax variations: https://github.com/prisma/database-schema-examples
We'd love to hear your thoughts and feedback. You can find us in the #datamodel-2 channel in https://prisma.slack.com. In future posts, we'll be covering more of the syntax and design decisions in greater detail.
Stay sharp!