Prisma.io 加 Go 的教程
Prisma.io是什么?
-
- SQLサーバにGraphQLを生やします。
-
- 今の所、MySQL、PostgreSQL、MongoDBに対応。
-
- PrismaサーバはDockerコンテナとして起動。
-
- GraphQLなので、クライアントはHTTPが使えればPrismaサーバを操作できる。
-
- Prismaサーバの操作をしやすくするPrismaクライアント(GO, TypeScript, JavaScript)を自動生成できる。
- Prisma Adminでブラウザからデータベースの照会、更新などができる。
建议您查看官方网站,因为这篇总结比较粗略。
另外,这篇文章非常有帮助。非常感谢。
prisma – 最快的GraphQL服务器实现 — Qiita
使用Prisma.io轻松创建GraphQL API服务器 — Qiita
构成
由于Prisma服务器能够执行SQL服务器的CRUD操作,因此直接公开它是危险的。
因此,需要添加应用程序/API服务器层。
这个层面可以使用Prisma客户端自行创建,所以API不一定非得是GraphQL。
(可以是REST、gRPC等)
-
- Database
-
- MySQL。Dockerで構築。prisma initで自動的に作ってくれる。
-
- Data Access Layer(Prisma)
-
- Dockerで構築。prisma initで自動的に作ってくれる。
-
- Application / API Service
-
- 今回はGoで作成。 GraphQLサーバのフレームワークはgqlgenを使用。
-
- 最初はホストで直接起動。後からDocker化。
-
- Client(ブラウザ)
-
- gqlgenでGraphQL Playgroundを追加できるので、そこから操作。
- 自分はGraphiQLも使ってます。
基本上按照官方教程进行,但在某些地方进行了改编。
完成后的源代码在这里。
环境概况
无论环境和版本不同,我认为它会正常工作。
-
- macOS Mojave
-
- Docker version 18.09.2, build 6247962
-
- docker-compose version 1.21.2, build a133471
- Node.js v8.15.1 ※prismaインストールのため
在公式文档中也提到了brew,并且我遇到了以下错误,因此我选择了在npm上重新安装。
brew tap prisma/prisma
brew install prisma
Error: Cannot find module 'generate'
步骤1:搭建Prisma
安装Prisma
$ npm install -g prisma
$ prisma -v
prisma/1.30.0 (darwin-x64) node-v8.15.1
创建Prisma项目
構築的目录应为GOHOME目录下。(例如:~/go/src/prisma-hello-world)
首先,在prisma命令中创建一个基础框架。
在过程中会有一些问题需要选择以下选项。
-
- Create new database
-
- MySQL
- Go
$ cd ~/go/src
$ prisma init prisma-hello-world
? Set up a new Prisma server or deploy to an existing server? Create new database
? What kind of database do you want to deploy to? MySQL
? Select the programming language for the generated Prisma client Prisma Go Client
Created 3 new files:
prisma.yml Prisma service definition
datamodel.prisma GraphQL SDL-based datamodel (foundation for database)
docker-compose.yml Docker configuration file
Next steps:
1. Open folder: cd prisma-hello-world
2. Start your Prisma server: docker-compose up -d
3. Deploy your Prisma service: prisma deploy
4. Read more about Prisma server:
http://bit.ly/prisma-server-overview
运行完成后,会创建一些文件。
首先,运行Docker Compose文件来启动DB服务器和Prisma服务器。
version: '3'
services:
prisma:
image: prismagraphql/prisma:1.30
restart: always
ports:
- "4466:4466"
environment:
PRISMA_CONFIG: |
port: 4466
# uncomment the next line and provide the env var PRISMA_MANAGEMENT_API_SECRET=my-secret to activate cluster security
# managementApiSecret: my-secret
databases:
default:
connector: mysql
host: mysql
user: root
password: prisma
rawAccess: true
port: 3306
migrations: true
mysql:
image: mysql:5.7
restart: always
environment:
MYSQL_ROOT_PASSWORD: prisma
volumes:
- mysql:/var/lib/mysql
volumes:
mysql:
数据模型定义的路径,以及Prisma客户端的输出目标等设置文件。
endpoint: http://localhost:4466
datamodel: datamodel.prisma
generate:
- generator: go-client
output: ./generated/prisma-client/
数据模型定义。
type User {
id: ID! @unique
name: String!
}
启动·部署
先尝试在初始状态下启动。
$ docker-compose up -d
$ prisma deploy
当您访问以下内容时,将打开Prisma服务器的GraphQL Playground。
当您访问以下地址时,将打开可进行数据管理的Prisma管理界面。
创建Go客户端
因为数据是空的,所以我将实现一个Go客户端来添加数据。
首先,我们需要初始化GO MODULES。
$ export GO111MODLUE=on
$ go init
使用Prisma客户端创建一个注册数据的源代码。
package main
import (
"context"
"fmt"
prisma "prisma-hello-world/generated/prisma-client"
)
func main() {
client := prisma.New(nil)
ctx := context.TODO()
// Create a new user
name := "Alice"
newUser, err := client.CreateUser(prisma.UserCreateInput{
Name: name,
}).Exec(ctx)
if err != nil {
panic(err)
}
fmt.Printf("Created new user: %+v\n", newUser)
users, err := client.Users(nil).Exec(ctx)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", users)
}
执行后,将会注册一条数据。
ID将根据cuid自动分配。
$ go run index.go
Created new user: &{ID:cjuc0tk8f001l07165y3waxtt Name:Alice}
[{ID:cjuc0tk8f001l07165y3waxtt Name:Alice}]
第二步:更改数据模型
在数据模型中添加项目。
type User {
id: ID! @unique
email: String @unique
name: String!
posts: [Post!]!
}
type Post {
id: ID! @unique
title: String!
published: Boolean! @default(value: "false")
author: User
}
我将进行部署和更新Prisma客户端。
$ prisma deploy
$ prisma generate
因为在generated/prisma-client/prisma.go中新增了新的API,所以我们将使用它来创建一个源代码来注册数据。
package main
import (
"context"
"fmt"
prisma "prisma-hello-world/generated/prisma-client"
)
func main() {
client := prisma.New(nil)
ctx := context.TODO()
// Create a new user with two posts
name := "Bob"
email := "bob@prisma.io"
title1 := "Join us for GraphQL Conf in 2019"
title2 := "Subscribe to GraphQL Weekly for GraphQL news"
newUser, err := client.CreateUser(prisma.UserCreateInput{
Name: name,
Email: &email,
Posts: &prisma.PostCreateManyWithoutAuthorInput{
Create: []prisma.PostCreateWithoutAuthorInput{
prisma.PostCreateWithoutAuthorInput{
Title: title1,
},
prisma.PostCreateWithoutAuthorInput{
Title: title2,
},
},
},
}).Exec(ctx)
if err != nil {
panic(err)
}
fmt.Printf("Created new user: %+v\n", newUser)
allUsers, err := client.Users(nil).Exec(ctx)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", allUsers)
allPosts, err := client.Posts(nil).Exec(ctx)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", allPosts)
}
执行后,会添加一个名为Bob的新用户和两篇新文章。
$ go run index.go
Created new user: &{ID:cjuc1dwo1002207164z8feea9 Email:0xc000093520 Name:Bob}
[{ID:cjuc0tk8f001l07165y3waxtt Email:<nil> Name:Alice} {ID:cjuc1dwo1002207164z8feea9 Email:0xc000146320 Name:Bob}]
[{ID:cjuc1dwp4002307162oty6fva Title:Join us for GraphQL Conf in 2019 Published:false} {ID:cjuc1dwpv00250716kvvo5xab Title:Subscribe to GraphQL Weekly for GraphQL news Published:false}]
接下来,我们将尝试通过指定电子邮件来搜索注册的文章。
package main
import (
"context"
"fmt"
prisma "prisma-hello-world/generated/prisma-client"
)
func main() {
client := prisma.New(nil)
ctx := context.TODO()
email := "bob@prisma.io"
postsByUser, err := client.User(prisma.UserWhereUniqueInput{
Email: &email,
}).Posts(nil).Exec(ctx)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", postsByUser)
}
$ go run index.go
[{ID:cjuc1dwp4002307162oty6fva Title:Join us for GraphQL Conf in 2019 Published:false} {ID:cjuc1dwpv00250716kvvo5xab Title:Subscribe to GraphQL Weekly for GraphQL news Published:false}]
第三步 构建一个应用程序
接下来,我们将创建一个用于公开的GraphQL服务器。
首先,您需要注册gqlgen包并输入初始化配置命令。
$ go get github.com/99designs/gqlgen
$ go run github.com/99designs/gqlgen init
执行后将生成以下文件。
-
- gqlgen.yml
-
- gqlgenの設定。自動生成コードの出力先とかを設定。
-
- schema.graphql
-
- 公開するGraphQLのスキーマ。この定義からコードが自動生成される。
-
- generated.go
-
- gqlgenで自動生成されるコード。
-
- 自動生成するので、一旦削除。
-
- models_gen.go
-
- prisma-clientで作成された構造体を使うので不要。削除。
-
- resolver.go
-
- GraphQLのリゾルバ。自分で作る必要があるが、必要な関数などのテンプレートは自動生成してくれる。
-
- 自動生成するので、一旦削除。
-
- server/server.go
- GraphQLサーバ起動のコード
由于杂乱不堪,我会创建文件夹进行整理。
-
- gqlgen/
gqlgen.yml
schema.graphql
server/server.go
接下来,需要根据Prisma的要求重新调整gqlgen的设置。
schema: schema.graphql
exec:
filename: generated.go
models:
Post:
model: prisma-hello-world/generated/prisma-client.Post
User:
model: prisma-hello-world/generated/prisma-client.User
resolver:
filename: resolver.go
type: Resolver
创建一个公开的GraphQL模式。
type Query {
publishedPosts: [Post!]!
post(postId: ID!): Post
postsByUser(userId: ID!): [Post!]!
}
type Mutation {
createUser(name: String!): User
createDraft(title: String!, userId: ID!): Post
publish(postId: ID!): Post
}
type User {
id: ID!
email: String
name: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
published: Boolean!
author: User
}
当文件生成后,运行gqlgen自动产生源代码。
$ cd gqlgen
$ go run github.com/99designs/gqlgen
以下的文件已经完成。
-
- gqlgen/
generated.go
resolver.go
由于自动生成的GraphQL解析器仅提供了框架,因此我们将使用Prisma客户端进行实现。
//go:generate go run github.com/99designs/gqlgen
package gqlgen
import (
"context"
"prisma-hello-world/generated/prisma-client"
)
type Resolver struct {
Prisma *prisma.Client
}
func (r *Resolver) Mutation() MutationResolver {
return &mutationResolver{r}
}
func (r *Resolver) Post() PostResolver {
return &postResolver{r}
}
func (r *Resolver) Query() QueryResolver {
return &queryResolver{r}
}
func (r *Resolver) User() UserResolver {
return &userResolver{r}
}
type mutationResolver struct{ *Resolver }
func (r *mutationResolver) CreateUser(ctx context.Context, name string) (*prisma.User, error) {
return r.Prisma.CreateUser(prisma.UserCreateInput{
Name: name,
}).Exec(ctx)
}
func (r *mutationResolver) CreateDraft(ctx context.Context, title string, userId string) (*prisma.Post, error) {
return r.Prisma.CreatePost(prisma.PostCreateInput{
Title: title,
Author: &prisma.UserCreateOneWithoutPostsInput{
Connect: &prisma.UserWhereUniqueInput{ID: &userId},
},
}).Exec(ctx)
}
func (r *mutationResolver) Publish(ctx context.Context, postId string) (*prisma.Post, error) {
published := true
return r.Prisma.UpdatePost(prisma.PostUpdateParams{
Where: prisma.PostWhereUniqueInput{ID: &postId},
Data: prisma.PostUpdateInput{Published: &published},
}).Exec(ctx)
}
type postResolver struct{ *Resolver }
func (r *postResolver) Author(ctx context.Context, obj *prisma.Post) (*prisma.User, error) {
return r.Prisma.Post(prisma.PostWhereUniqueInput{ID: &obj.ID}).Author().Exec(ctx)
}
type queryResolver struct{ *Resolver }
func (r *queryResolver) PublishedPosts(ctx context.Context) ([]prisma.Post, error) {
published := true
return r.Prisma.Posts(&prisma.PostsParams{
Where: &prisma.PostWhereInput{Published: &published},
}).Exec(ctx)
}
func (r *queryResolver) Post(ctx context.Context, postId string) (*prisma.Post, error) {
return r.Prisma.Post(prisma.PostWhereUniqueInput{ID: &postId}).Exec(ctx)
}
func (r *queryResolver) PostsByUser(ctx context.Context, userId string) ([]prisma.Post, error) {
return r.Prisma.Posts(&prisma.PostsParams{
Where: &prisma.PostWhereInput{
Author: &prisma.UserWhereInput{
ID: &userId,
}},
}).Exec(ctx)
}
type userResolver struct{ *Resolver }
func (r *userResolver) Posts(ctx context.Context, obj *prisma.User) ([]prisma.Post, error) {
return r.Prisma.User(prisma.UserWhereUniqueInput{ID: &obj.ID}).Posts(nil).Exec(ctx)
}
当我们需要在go generate命令中执行gqlgen时,开头的//go:generate go run github.com/99designs/gqlgen是一个用于注释的指示。
当我们修改了模式后,可以使用以下指令更新代码。
$ go generate gqlgen/resolver.go
接下来,我们将创建应用程序启动部分。
package main
import (
"log"
"net/http"
"os"
prisma "prisma-hello-world/generated/prisma-client"
"prisma-hello-world/gqlgen"
"github.com/99designs/gqlgen/handler"
)
const defaultPort = "4000"
func main() {
port := os.Getenv("PORT")
if len(port) == 0 {
port = defaultPort
}
client := prisma.New(nil)
resolver := gqlgen.Resolver{
Prisma: client,
}
http.Handle("/", handler.Playground("GraphQL Playground", "/query"))
http.Handle("/query", handler.GraphQL(gqlgen.NewExecutableSchema(
gqlgen.Config{Resolvers: &resolver})))
log.Printf("Server is running on http://localhost:%s", port)
err := http.ListenAndServe(":"+port, nil)
if err != nil {
log.Fatal(err)
}
}
如果可以的话,我会尝试启动。
$ go run server/server.go
当您访问以下内容时,将会打开 GraphQL Playground。
我试一试,添加用户和文章
mutation {
createUser(name: "otanu") {
id
name
}
}
{
"data": {
"createUser": {
"id": "cjuc3vysh000d0744f8n94vw4",
"name": "otanu"
}
}
}
mutation {
createDraft(title: "テスト", userId: "cjuc3vysh000d0744f8n94vw4") {
id
title
published
author {
id
name
}
}
}
{
"data": {
"createDraft": {
"id": "cjuc42km0000j07441ucjddnd",
"title": "テスト",
"published": false,
"author": {
"id": "cjuc3vysh000d0744f8n94vw4",
"name": "otanu"
}
}
}
}
mutation {
publish(postId: "cjuc42km0000j07441ucjddnd") {
id
title
published
author {
name
}
}
}
{
"data": {
"publish": {
"id": "cjuc42km0000j07441ucjddnd",
"title": "テスト",
"published": true,
"author": {
"name": "otanu"
}
}
}
}
query {
publishedPosts {
id
title
}
}
{
"data": {
"publishedPosts": [
{
"id": "cjuc42km0000j07441ucjddnd",
"title": "テスト"
}
]
}
}
将应用程序进行Docker化
我们将把这些应用程序Docker化,以便能够使用DockerCompose一次性启动它们。
将应用程序Docker化后,无法通过localhost连接到Prisma服务器,因此需要添加环境变量ENDPOINT,以便能够更改端点。
package main
import (
"log"
"net/http"
"os"
prisma "prisma-hello-world/generated/prisma-client"
"prisma-hello-world/gqlgen"
"github.com/99designs/gqlgen/handler"
)
const defaultPort = "4000"
func main() {
port := os.Getenv("PORT")
if len(port) == 0 {
port = defaultPort
}
// 追加
var opt *prisma.Options
endpoint := os.Getenv("ENDPOINT")
if len(endpoint) != 0 {
opt = &prisma.Options{
Endpoint: endpoint,
}
}
client := prisma.New(opt)
resolver := gqlgen.Resolver{
Prisma: client,
}
http.Handle("/", handler.Playground("GraphQL Playground", "/query"))
http.Handle("/query", handler.GraphQL(gqlgen.NewExecutableSchema(
gqlgen.Config{Resolvers: &resolver})))
log.Printf("Server is running on http://localhost:%s", port)
err := http.ListenAndServe(":"+port, nil)
if err != nil {
log.Fatal(err)
}
}
接下来,我们准备Dockerfile。
同时,添加fresh以进行热重载。
FROM golang:1.11-alpine AS build_base
RUN apk add bash ca-certificates git gcc g++ libc-dev
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
RUN go get github.com/pilu/fresh
COPY . .
EXPOSE 4000
CMD cd server; fresh server.go
将应用程序的配置添加到DockerCompose中。
现在,您可以一次性启动它们。
version: '3'
services:
prisma:
image: prismagraphql/prisma:1.30
restart: always
ports:
- "4466:4466"
environment:
PRISMA_CONFIG: |
port: 4466
# uncomment the next line and provide the env var PRISMA_MANAGEMENT_API_SECRET=my-secret to activate cluster security
# managementApiSecret: my-secret
databases:
default:
connector: mysql
host: mysql
user: root
password: prisma
rawAccess: true
port: 3306
migrations: true
mysql:
image: mysql:5.7
restart: always
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: prisma
volumes:
- mysql:/var/lib/mysql
app:
build:
context: .
dockerfile: ./Dockerfile
ports:
- "4000:4000"
volumes:
- .:/app
depends_on:
- prisma
environment:
ENDPOINT: http://prisma:4466
volumes:
mysql: