commentAdded
구독은 특정 블로그 게시물에 새 댓글이 추가될 때마다 구독 클라이언트에게 알립니다.
Apollo Client Subscription 예제 구현을 위해 GraphQL 스키마를 정의합니다.
schema.graphqls
type Post {
id: ID!
}
type Comment {
id: ID!
postId: ID!
content: String!
}
type Query {
comments(where:CommentsWhere!): [Comment]!
}
type Mutation {
createPost:Post!
createComment(input: CreateCommentInput!): Comment!
}
type Subscription {
commentAdded:Comment!
}
input CreateCommentInput{
postId: ID!
content: String!
}
input CommentsWhere {
postId: ID!
}
createComment
: Comment 생성comments
: Comment 리스트commentAdded
: Comment 가 추가될 때마다 클라이언트에 구독 정보를 전달docker-compose.yml
version: "3.7"
services:
mysql:
image: mysql:5.7
container_name: example_mysql
environment:
MYSQL_USER: test
MYSQL_PASSWORD: test
MYSQL_ROOT_PASSWORD: test
MYSQL_DATABASE: test
ports:
- 33006:3306
docker-compose
를 up 하고 MySQL 을 시작 합니다.
$ docker-compose -f ./docker-compose.yml up --build -d
성공할 경우:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
306240f2708d mysql:5.7 "docker-entrypoint.s…" About an hour ago Up About an hour 33060/tcp, 0.0.0.0:33006->3306/tcp example_mysql
gorm 과 MySQL 드라이버를 설치합니다.
$ go get -u gorm.io/gorm
$ go get -u gorm.io/driver/mysql
MySQL을 연결합니다.
dsn := "test:test@tcp(127.0.0.1:33006)/test?charset=utf8mb4&parseTime=True&loc=Local"
// refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalf("failed to connect database: %v", err)
}
GraphQL 스키마로 table 을 생성합니다.
if err = db.AutoMigrate(&model.Post{}, &model.Comment{}); err != nil {
log.Fatalf("failed to auto migration schema: %v", err)
}
server.go
package main
import (
"github.com/99designs/gqlgen/graphql/handler/transport"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/gorilla/websocket"
"github.com/songtomtom/gqlgen-apollo-subscriptions/graph/model"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
"net/http"
"os"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/songtomtom/gqlgen-apollo-subscriptions/graph"
)
const defaultPort = "8080"
func main() {
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}
dsn := "test:test@tcp(127.0.0.1:33006)/test?charset=utf8mb4&parseTime=True&loc=Local"
// refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalf("failed to connect database: %v", err)
}
if err = db.AutoMigrate(&model.Post{}, &model.Comment{}); err != nil {
log.Fatalf("failed to auto migration schema: %v", err)
}
http.Handle("/", playground.Handler("GraphQL playground", "/query"))
http.HandleFunc("/query", query(db))
http.HandleFunc("/subscriptions", subscription(db))
log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
func query(db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{DB: db}}))
srv.ServeHTTP(w, r)
}
}
func subscription(db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
srv := handler.New(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{DB: db}}))
srv.AddTransport( // <---- This is the important part!
&transport.Websocket{
Upgrader: websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
ReadBufferSize: 1024,
WriteBufferSize: 1024,
},
},
)
srv.ServeHTTP(w, r)
}
}
schema.resolvers.go
// CreatePost is the resolver for the createPost field.
func (r *mutationResolver) CreatePost(ctx context.Context) (*model.Post, error) {
_, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
post := model.Post{
ID: uuid.UUIDv4(),
}
r.DB.Create(post)
return &post, nil
}
// CreateComment is the resolver for the createComment field.
func (r *mutationResolver) CreateComment(ctx context.Context, input model.CreateCommentInput) (*model.Comment, error) {
_, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
comment := model.Comment{
ID: uuid.UUIDv4(),
PostID: input.PostID,
Content: input.Content,
}
r.DB.Create(comment)
return &comment, nil
}
// Comments is the resolver for the comments field.
func (r *queryResolver) Comments(ctx context.Context, where model.CommentsWhere) ([]*model.Comment, error) {
_, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
var comments []*model.Comment
r.DB.Where("post_id = ?", where.PostID).Find(&comments)
return comments, nil
}
// CommentAdded is the resolver for the commentAdded field.
func (r *subscriptionResolver) CommentAdded(ctx context.Context) (<-chan *model.Comment, error) {
// TODO
panic(fmt.Errorf("not implemented: CommentAdded - commentAdded"))
}
CommentAdded 리졸버는 다음 포스트에서 구현합니다.
$ go run server.go
connect to http://localhost:8080/ for GraphQL playground
mutation CreatePost {
createPost(input: {id: "test_post_id"}) {
id
}
}
{
"data": {
"createPost": {
"id": "test_post_id"
}
}
}
mutation CreateComment {
createComment(input: {postId: "test_post_id", content: "test_hello~"}) {
id
postId
content
}
}
{
"data": {
"createPost": {
"id": "test_post_id"
}
}
}
query ListComments {
comments(where: {postId: "test_post_id"}) {
id
postId
content
}
}
{
"data": {
"comments": [
{
"id": "0b501dff-7d61-4c4a-8f20-a75cfda64461",
"postId": "test_post_id",
"content": "test_hello~"
},
{
"id": "2fec1731-b1a1-441d-97e6-a26c1b15145a",
"postId": "test_post_id",
"content": "test_hello~"
},
{
"id": "6c8bd39b-a3a5-416a-b3a7-afbad931c1c2",
"postId": "test_post_id",
"content": "test_hello~"
},
{
"id": "dade23e2-973f-4069-bb2e-d8c9d3b7a3b2",
"postId": "test_post_id",
"content": "test_hello~"
}
]
}
}