Prisma.io 加 Go 的教程

Prisma.io是什么?

prisma.png
    • 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等)

SUH6AqW.png
    • 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_playground.png

当您访问以下地址时,将打开可进行数据管理的Prisma管理界面。

prisma_admin.png

创建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。

gql-app.png

我试一试,添加用户和文章

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:
广告
将在 10 秒后关闭
bannerAds