Skip to content
This repository was archived by the owner on Oct 21, 2020. It is now read-only.

Conversation

steebchen
Copy link
Contributor

@steebchen steebchen commented Sep 27, 2019

This PR introduces a complete rewrite of the Photon Go spec.

Rendered view
Rendered diff

TODO:

  • update links to the new spec in the docs

Copy link
Contributor

@matthewmueller matthewmueller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! This can definitely help us form a basis for discussion tomorrow 👏

What I like:

  1. Fluent API on columns (CreatedAt.Lt(...))
  2. Better type-safety on inputs
  3. New idea with PQL

What I don't like:

  1. Exec(...), it bothers me that an edge case leaks into the entire API.
  2. user.Name.Valid my preference is still pointers. I don't think this adds safety or clarity, it just adds extra methods
  3. photon.Comment.CreatedAt.ASC, I'd still like to split this into separate comment.CreatedAt.ASC.

CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Title string `json:"title"`
Desc NullString `json:"desc"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you provide a full working snippet? It's hard to know where these types are coming from.

photon.User.ID.Set("7d42"),
photon.User.Email.Set("bob@prisma.io"),
photon.User.Name.Set("Bob"),
).CreatePost(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above example creates multiple posts. How would I create multiple posts in your proposal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, that's currently missing. I'll add it soon

}
}

err := client.User.Select(&u,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a proposal for Select?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you just want to select multiple things, you can query that by using the .With() methods.

map[string]interface{}{
"id": "alice@prisma.io",
},
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we probably won't want to expose our internal protocol since it will change, but it's an interesting idea once we decide to stabilize it. Most likely it'll be a JSON-based protocol.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, interesting. I thought maybe it would be cool for advanced usage, but in theory everything which exists in PQL would also be quickly available in photon clients. So yeah, we should probably not expose this

err := client.User.Raw(
ctx,
&u,
`SELECT * FROM users WHERE email = ?`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a huge fan of the question marks since they're easy to mess up. Also MySQL specific. Have you seen this?

https://github.com/prisma/specs/blob/raw-spec/photon-raw-api/Readme.md#photon-go

Do you like this syntax? Any adjustments you'd like to make?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fully agree here, thanks for the raw spec link. I just added a small example to have something.

photon.User.Email.Equals("alice@prisma.io"),
).Data(
photon.User.Email.Set("alice@prisma.io"),
photon.User.Role.Set(user.Role.ADMIN),
Copy link
Contributor

@matthewmueller matthewmueller Oct 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we go with the multi-package route, could we do user.Role(...)?

I thought about this more last night and even though it's a really cool idea, I think it'll be too verbose in practice. We have to remember the context of where this code lives and who we're catering for. Even if we made photon incredibly type-safe in Go, the rest of their application logic won't be. Personally, I'd rather work with a succinct API and cater to professionals who write tests where these tests will catch the occasional runtime error.

Another tidbit: we can't ever make row-level violations type-safe (unique constraints). We're only tackling a subset of possible runtime errors with this API change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I should have clarified on what's too verbose:

// upsert the user by their slack ID
u := user.New().
	SlackID(slackUser.SlackID).
	Username(slackUser.Username).
	Email(slackUser.Email).
	FirstName(slackUser.FirstName).
	LastName(slackUser.LastName).
	AvatarURL(slackUser.AvatarURL).
	Timezone(slackUser.Timezone).
	IsTeamOwner(true).
	TeamID(tm.ID)
usr, err := user.UpsertBySlackIDAndTeamID(tx, res.UserID, tm.ID, u)
if err != nil {
	return errors.Wrap(err, "unable to upsert the user")
}

This is where I'm coming from, let's maybe reformat this in the new syntax and we can discuss further 🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pulling the dot syntax argument here again.. The only char difference you have is .Set, which is negligible.

And, more importantly, my solution enforces the user to specify required fields with fixed parameters.

// generated code
func (UserChain) Data(id UserIDParam, username UserUsernameParam /* ... */) {
  // ...
}

so you can do

user, err := client.User.Upsert.Where(
  photon.User.Email.Equals("alice@prisma.io"),
).Data(
  photon.User.ID.Set("alice@prisma.io"), // .ID returns UserIDParam, so you are forced to sepcify required fields
  photon.User.Email.Set("alice@prisma.io"),
  // ...
)

Copy link
Contributor

@matthewmueller matthewmueller Oct 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to see examples of the exact syntactic differences. I think it's more than .Set. Here's my rough translation:

u, err := user.UpsertBySlackIDAndTeamID(tx, res.UserID, tm.ID, user.New().
	SlackID(slackUser.SlackID).
	Username(slackUser.Username).
	Email(slackUser.Email).
	FirstName(slackUser.FirstName).
	LastName(slackUser.LastName).
	AvatarURL(slackUser.AvatarURL).
	Timezone(slackUser.Timezone).
	IsTeamOwner(true).
	TeamID(tm.ID),
)

// vs

user, err := client.User.Upsert.Where(
	photon.User.SlackID.Equals("alice@prisma.io"),
	photon.User.TeamID.Equals("alice@prisma.io"),
).Data(
	photon.User.SlackID.Set(slackUser.SlackID),
	photon.User.Username.Set(slackUser.Username),
	photon.User.Email.Set(slackUser.Email),
	photon.User.FirstName.Set(slackUser.FirstName),
	photon.User.LastName.Set(slackUser.LastName),
	photon.User.AvatarURL.Set(slackUser.AvatarURL),
	photon.User.Timezone.Set(slackUser.Timezone),
	photon.User.IsTeamOwner.Set(true),
	photon.User.TeamID.Set(tm.ID),
)

Please correct if wrong 🙏

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ disregard UpsertBySlackIDAndTeamID , it's more like user.Where().SlackID().ID()

Copy link
Contributor Author

@steebchen steebchen Oct 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's much better with dot imports:

import (
	. "github.com/user/repo/photon"
)

// ...

user, err := client.User.Upsert(
	User.SlackID.Set(u.SlackID),
	User.Email.Set(u.Email),
	User.TeamID.Equals(u.TeamID),
	User.SlackID.Set(u.SlackID),
	User.Username.Set(u.Username),
	User.FirstName.Set(u.FirstName),
	User.LastName.Set(u.LastName),
	User.AvatarURL.Set(u.AvatarURL),
	User.Timezone.Set(u.Timezone),
	User.IsTeamOwner.Set(true),
	User.TeamID.Set(tm.ID),
)

I agree that this is still more verbose than yours, but I think it's worth to have the type safety here. This is the only reliable way to verify that all required fields are in fact specified. If you change or add a new required field in your data model, you will get warnings or errors for migrations if it would involve a breaking change. I would like to reflect this in the Go client as well, so you are forced to also adapt your code. I bet you that people (myself included) will forget to adapt their code for some .Create or .Upsert calls, Go would default to "" and users will wonder why their data is inconsistent or their queries won't work afterwards.

I think Go is more about simplicity and type safety than verbosity. Of course, it tries to be terse, but not when you have additional type safety – that's why you have verbose error checks because it may be easier to debug later, no generics which forces you to write specific functions for specific data types which means you have extremely high type safety.

Also, another advantage of using methods on each field, is perfect for nullable fields, regardless of queries (.Equals etc.) or setters. For example, if you have a nullable field, we may add .SetOptional/.Opt/whatever to nullable fields which accepts a string instead of a NullString/pointer if you want to set an optional value to a fixed string so there's no need a need for extra helper methods.

If we want to most simple method, I think that would be using structs, especially because IDEs can fill all fields with a single click, and you can even omit the field names. This would have the equal amount of type safety as your proposal, but the same drawbacks (not forced to specify required fields).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@janpio janpio changed the title Refactor Photon Go spec Rewrite/Redesign Photon Go spec Oct 9, 2019
@janpio janpio added kind/spec Something about an actual spec (file) spec/change An existing spec should be changed or adapted labels Oct 9, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area/prisma-client-go kind/spec Something about an actual spec (file) spec/change An existing spec should be changed or adapted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants