Skip to content

Commit 79bb009

Browse files
authored
Merge pull request #60 from n9te9/feat/nested-key-directive
Feat/nested key directive
2 parents 6a49958 + 557a192 commit 79bb009

21 files changed

Lines changed: 1973 additions & 17 deletions

_example/Makefile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: test-ec test-fintech test-saas test-social test-travel test-all
1+
.PHONY: test-ec test-fintech test-saas test-social test-travel test-nestkey test-all
22
.PHONY: partial-test-ec partial-test-fintech partial-test-saas partial-test-social partial-test-travel partial-test-all
33
.PHONY: setup benchmark benchmark-ec benchmark-fintech benchmark-saas benchmark-social benchmark-travel compare-benchmark all-compare-benchmark
44

@@ -68,7 +68,10 @@ test-social:
6868
test-travel:
6969
bash success_test_runner.sh travel
7070

71-
test-all: test-ec test-fintech test-saas test-social test-travel
71+
test-nestkey:
72+
bash success_test_runner.sh nestkey
73+
74+
test-all: test-ec test-fintech test-saas test-social test-travel test-nestkey
7275

7376
# Partial response test targets (service failure scenarios)
7477
partial-test-ec:
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
supergraph:
2+
path: /graphql
3+
4+
cors:
5+
allow_any_origin: true
6+
7+
headers:
8+
all:
9+
request:
10+
- propagate:
11+
matching: .*
12+
13+
include_subgraph_errors:
14+
all: true
15+
16+
telemetry:
17+
instrumentation:
18+
spans:
19+
mode: spec_compliant
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
services:
2+
apollo-router:
3+
image: ghcr.io/apollographql/router:v1.52.0
4+
ports:
5+
- "9701:4000"
6+
volumes:
7+
- ./apollo:/apollo
8+
command: -s /apollo/supergraph.graphql -c /apollo/router.yaml --listen 0.0.0.0:4000
9+
networks:
10+
- default
11+
- subgraph_net
12+
13+
networks:
14+
subgraph_net:
15+
name: nestkey_default
16+
external: true
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
services:
2+
go-gateway:
3+
build:
4+
context: ../..
5+
dockerfile: _example/Dockerfile.gateway
6+
ports:
7+
- "9000:9000"
8+
volumes:
9+
- ./gateway.docker.yaml:/app/gateway.yaml
10+
networks:
11+
- default
12+
- subgraph_net
13+
deploy:
14+
resources:
15+
limits:
16+
cpus: '2.0'
17+
memory: 512M
18+
reservations:
19+
cpus: '0.5'
20+
memory: 128M
21+
22+
networks:
23+
subgraph_net:
24+
name: nestkey_default
25+
external: true
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
services:
2+
locations:
3+
build:
4+
context: ./locations
5+
dockerfile: Dockerfile
6+
ports:
7+
- "8601:8080"
8+
environment:
9+
- PORT=8080
10+
11+
weather:
12+
build:
13+
context: ./weather
14+
dockerfile: Dockerfile
15+
ports:
16+
- "8602:8080"
17+
environment:
18+
- PORT=8080
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
endpoint: /graphql
2+
port: 9000
3+
service_name: nestkey-gateway
4+
timeout_duration: "5s"
5+
request_timeout: "30s"
6+
services:
7+
- name: locations
8+
host: http://locations:8080/query
9+
retry:
10+
attempts: 3
11+
timeout: "5s"
12+
- name: weather
13+
host: http://weather:8080/query
14+
retry:
15+
attempts: 3
16+
timeout: "5s"

_example/nestkey/gateway.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
endpoint: /graphql
2+
port: 9000
3+
service_name: nestkey-gateway
4+
timeout_duration: "5s"
5+
request_timeout: "30s"
6+
services:
7+
- name: locations
8+
host: http://localhost:8601/query
9+
retry:
10+
attempts: 3
11+
timeout: "5s"
12+
- name: weather
13+
host: http://localhost:8602/query
14+
retry:
15+
attempts: 3
16+
timeout: "5s"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM golang:1.25-alpine
2+
3+
WORKDIR /app
4+
5+
COPY go.mod ./
6+
RUN go mod download
7+
8+
COPY . .
9+
RUN go build -o server .
10+
11+
CMD ["./server"]

_example/nestkey/locations/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/n9te9/go-graphql-federation-gateway/_example/nestkey/locations
2+
3+
go 1.25.4

_example/nestkey/locations/main.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// locations service: owns Location @key(fields: "coordinate { lat lng }")
2+
package main
3+
4+
import (
5+
"encoding/json"
6+
"log"
7+
"net/http"
8+
"os"
9+
"strings"
10+
)
11+
12+
const sdl = `extend schema
13+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])
14+
15+
type Location @key(fields: "coordinate { lat lng }") {
16+
coordinate: Coordinate!
17+
name: String!
18+
}
19+
20+
type Coordinate @shareable {
21+
lat: Float!
22+
lng: Float!
23+
}
24+
25+
type Query {
26+
location(id: ID!): Location
27+
}`
28+
29+
type coordinate struct {
30+
Lat float64 `json:"lat"`
31+
Lng float64 `json:"lng"`
32+
}
33+
34+
type location struct {
35+
Name string `json:"name"`
36+
Coordinate coordinate `json:"coordinate"`
37+
}
38+
39+
// locationDB stores all known locations indexed by id.
40+
var locationDB = map[string]location{
41+
"loc1": {Name: "Tokyo Station", Coordinate: coordinate{Lat: 35.6762, Lng: 139.6503}},
42+
"loc2": {Name: "Shibuya", Coordinate: coordinate{Lat: 35.6580, Lng: 139.7016}},
43+
"loc3": {Name: "Shinjuku", Coordinate: coordinate{Lat: 35.6896, Lng: 139.6917}},
44+
}
45+
46+
// findByCoordinate returns the location that has the given (lat, lng) coordinate.
47+
func findByCoordinate(lat, lng float64) *location {
48+
for _, loc := range locationDB {
49+
if loc.Coordinate.Lat == lat && loc.Coordinate.Lng == lng {
50+
l := loc
51+
return &l
52+
}
53+
}
54+
return nil
55+
}
56+
57+
type gqlRequest struct {
58+
Query string `json:"query"`
59+
Variables map[string]interface{} `json:"variables"`
60+
}
61+
62+
func writeJSON(w http.ResponseWriter, v interface{}) {
63+
w.Header().Set("Content-Type", "application/json")
64+
json.NewEncoder(w).Encode(v) //nolint:errcheck
65+
}
66+
67+
func handleQuery(w http.ResponseWriter, r *http.Request) {
68+
var req gqlRequest
69+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
70+
writeJSON(w, map[string]interface{}{"errors": []map[string]interface{}{{"message": err.Error()}}})
71+
return
72+
}
73+
74+
switch {
75+
case strings.Contains(req.Query, "_service"):
76+
writeJSON(w, map[string]interface{}{
77+
"data": map[string]interface{}{"_service": map[string]interface{}{"sdl": sdl}},
78+
})
79+
case strings.Contains(req.Query, "_entities"):
80+
handleEntities(w, req)
81+
case strings.Contains(req.Query, "location"):
82+
handleLocationQuery(w, req)
83+
default:
84+
writeJSON(w, map[string]interface{}{"data": map[string]interface{}{"__typename": "Query"}})
85+
}
86+
}
87+
88+
func handleLocationQuery(w http.ResponseWriter, req gqlRequest) {
89+
id, _ := req.Variables["id"].(string)
90+
loc, ok := locationDB[id]
91+
if !ok {
92+
writeJSON(w, map[string]interface{}{"data": map[string]interface{}{"location": nil}})
93+
return
94+
}
95+
writeJSON(w, map[string]interface{}{
96+
"data": map[string]interface{}{
97+
"location": map[string]interface{}{
98+
"__typename": "Location",
99+
"name": loc.Name,
100+
"coordinate": map[string]interface{}{
101+
"lat": loc.Coordinate.Lat,
102+
"lng": loc.Coordinate.Lng,
103+
},
104+
},
105+
},
106+
})
107+
}
108+
109+
func handleEntities(w http.ResponseWriter, req gqlRequest) {
110+
repsRaw, _ := req.Variables["representations"].([]interface{})
111+
entities := make([]interface{}, 0, len(repsRaw))
112+
for _, repRaw := range repsRaw {
113+
repMap, ok := repRaw.(map[string]interface{})
114+
if !ok {
115+
entities = append(entities, nil)
116+
continue
117+
}
118+
coordRaw, ok := repMap["coordinate"].(map[string]interface{})
119+
if !ok {
120+
entities = append(entities, nil)
121+
continue
122+
}
123+
lat, _ := coordRaw["lat"].(float64)
124+
lng, _ := coordRaw["lng"].(float64)
125+
loc := findByCoordinate(lat, lng)
126+
if loc == nil {
127+
entities = append(entities, nil)
128+
continue
129+
}
130+
entities = append(entities, map[string]interface{}{
131+
"__typename": "Location",
132+
"name": loc.Name,
133+
"coordinate": map[string]interface{}{"lat": loc.Coordinate.Lat, "lng": loc.Coordinate.Lng},
134+
})
135+
}
136+
writeJSON(w, map[string]interface{}{
137+
"data": map[string]interface{}{"_entities": entities},
138+
})
139+
}
140+
141+
func main() {
142+
port := os.Getenv("PORT")
143+
if port == "" {
144+
port = "8080"
145+
}
146+
http.HandleFunc("/query", handleQuery)
147+
log.Printf("locations service listening on :%s", port)
148+
log.Fatal(http.ListenAndServe(":"+port, nil))
149+
}

0 commit comments

Comments
 (0)