Skip to content

Commit 03067a9

Browse files
committed
Add Go-Python gRPC bookmark experiment
Create a cross-language bookmark service experiment under experiments/. Includes a Python gRPC server, a Go CLI client, shared protobuf stubs, unit tests, and VS Code debug configurations for both sides.
1 parent 4bb504f commit 03067a9

28 files changed

Lines changed: 2850 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ A space for experimentation and learning 🚀
1515

1616
- [Flask dependency injection](flask-dependency-injection-poc/README.md): Experimenting with dependency injection in Flask: clean setups, nested dependencies, dev experience, and pitfalls. Conflict-related issues between [Flask-Injector](https://github.com/python-injector/flask_injector) and ([Flask-Pydantic](https://github.com/bauerji/flask-pydantic) or [Flask OpenAPI3](https://github.com/luolingchun/flask-openapi3)). ![Custom Badge](https://img.shields.io/badge/Outcome-Unresolved-yellow)
1717

18+
- [gRPC: Go client + Python server](experiments/grpc-go-python-bookmarks/README.md): Exploring a cross-language gRPC setup with a Go CLI client, a Python server, and a shared protobuf contract for bookmark management. ![Custom Badge](https://img.shields.io/badge/Outcome-Success-brightgreen)
19+
1820
- [Flutter + PowerSync integration](flutter/flutter_powersync_integration/README.md): Offline-first todo app integrating Flutter with PowerSync, FastAPI, and PostgreSQL. Explores real-time sync, JWT auth, and SQLite/Postgres type mapping. ![Custom Badge](https://img.shields.io/badge/Outcome-Success-brightgreen)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
// {
5+
// "name": "Python: Debug bookmark server",
6+
// "type": "debugpy",
7+
// "request": "launch",
8+
// "module": "app.server",
9+
// "cwd": "${workspaceFolder}/python-server",
10+
// "python": "${workspaceFolder}/python-server/.venv/bin/python",
11+
// "console": "integratedTerminal",
12+
// "justMyCode": false,
13+
// "env": {
14+
// "PYTHONPATH": "${workspaceFolder}/python-server/src:${workspaceFolder}/python-server/src/app/generated"
15+
// }
16+
// },
17+
{
18+
"name": "Python: Debug bookmark server (50055)",
19+
"type": "debugpy",
20+
"request": "launch",
21+
"module": "app.server",
22+
"cwd": "${workspaceFolder}/python-server",
23+
"python": "${workspaceFolder}/python-server/.venv/bin/python",
24+
"console": "integratedTerminal",
25+
"justMyCode": false,
26+
"args": [
27+
"--port",
28+
"50055"
29+
],
30+
"env": {
31+
"PYTHONPATH": "${workspaceFolder}/python-server/src:${workspaceFolder}/python-server/src/app/generated"
32+
}
33+
},
34+
{
35+
"name": "Go: Debug bookmark CLI",
36+
"type": "go",
37+
"request": "launch",
38+
"mode": "debug",
39+
"program": "${workspaceFolder}/go-client/cmd/bookmarks",
40+
"cwd": "${workspaceFolder}/go-client",
41+
"args": [
42+
"--addr",
43+
"127.0.0.1:50055",
44+
"list"
45+
]
46+
},
47+
{
48+
"name": "Go: Debug bookmark CLI tests",
49+
"type": "go",
50+
"request": "launch",
51+
"mode": "test",
52+
"program": "${workspaceFolder}/go-client/cmd/bookmarks",
53+
"cwd": "${workspaceFolder}/go-client"
54+
}
55+
],
56+
// "compounds": [
57+
// {
58+
// "name": "Debug server + Go CLI",
59+
// "configurations": [
60+
// "Python: Debug bookmark server",
61+
// "Go: Debug bookmark CLI"
62+
// ]
63+
// }
64+
// ]
65+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
PROTO_SRC := ./proto/bookmarks/v1/bookmarks.proto
2+
PROTO_INC := ./proto
3+
4+
PY_OUT := ./python-server/src/app/generated
5+
GO_OUT := ./go-client
6+
GO_DEST := ./go-client/internal/generated
7+
8+
.PHONY: gen gen-python gen-go
9+
10+
# Regenerate all protobuf stubs (Python + Go)
11+
gen: gen-python gen-go
12+
13+
# Generate Python stubs using grpc_tools bundled with the python-server venv
14+
gen-python:
15+
cd python-server && uv run python -m grpc_tools.protoc \
16+
-I ../$(PROTO_INC) \
17+
--python_out=src/app/generated \
18+
--grpc_python_out=src/app/generated \
19+
../$(PROTO_SRC)
20+
21+
# Generate Go stubs then move them into the internal package layout
22+
gen-go:
23+
protoc \
24+
-I $(PROTO_INC) \
25+
--go_out=$(GO_OUT) \
26+
--go_opt=paths=source_relative \
27+
--go-grpc_out=$(GO_OUT) \
28+
--go-grpc_opt=paths=source_relative \
29+
$(PROTO_SRC)
30+
mkdir -p $(GO_DEST)/bookmarks/v1
31+
mv $(GO_OUT)/bookmarks/v1/*.go $(GO_DEST)/bookmarks/v1/
32+
rmdir $(GO_OUT)/bookmarks/v1
33+
rmdir $(GO_OUT)/bookmarks
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Go + Python gRPC Bookmark Experiment
2+
3+
This experiment explores a small cross-language gRPC setup where a Go CLI talks to a Python service using a shared protobuf contract.
4+
5+
## Learning goal
6+
7+
Understand the core mechanics of gRPC across languages:
8+
9+
- define a shared `.proto` contract
10+
- generate language-specific stubs
11+
- implement a Python gRPC server
12+
- call it from a Go client with deadlines and typed request/response messages
13+
14+
## Architecture
15+
16+
- `proto/bookmarks/v1/bookmarks.proto` is the source of truth
17+
- `python-server/` hosts the gRPC `BookmarkService` and stores bookmarks in memory
18+
- `go-client/` contains a small CLI for create/get/list/tag/delete operations
19+
20+
The transport is local and insecure on `127.0.0.1:50051` because the experiment is intended for one-machine development.
21+
22+
## Prerequisites
23+
24+
- Go
25+
- Python 3.12+
26+
- `uv`
27+
- `protoc`
28+
- `protoc-gen-go`
29+
- `protoc-gen-go-grpc`
30+
31+
Install the Go plugins if needed:
32+
33+
```bash
34+
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
35+
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
36+
```
37+
38+
Ensure `$GOPATH/bin` is on your `PATH`.
39+
40+
## Generate protobuf code
41+
42+
From the experiment root:
43+
44+
```bash
45+
make gen
46+
```
47+
48+
To regenerate stubs for one language only:
49+
50+
```bash
51+
make gen-python
52+
make gen-go
53+
```
54+
55+
## Install Python dependencies
56+
57+
```bash
58+
cd python-server
59+
make install
60+
```
61+
62+
## Run the Python server
63+
64+
```bash
65+
cd python-server
66+
make run
67+
```
68+
69+
The server binds to `127.0.0.1:50051`. Override the port with:
70+
71+
```bash
72+
make run PORT=50055
73+
```
74+
75+
## Run the Go CLI
76+
77+
```bash
78+
cd go-client
79+
make run ARGS="list"
80+
```
81+
82+
Example flow:
83+
84+
```bash
85+
make run ARGS="create --url https://grpc.io/docs/ --title 'gRPC Docs' --description 'Official docs' --tag grpc --tag docs"
86+
make run ARGS="list"
87+
make run ARGS="get --id bkm_0001"
88+
make run ARGS="tag --id bkm_0001 --tag learning --tag grpc"
89+
make run ARGS="delete --id bkm_0001"
90+
```
91+
92+
To compile a binary instead of using `go run`:
93+
94+
```bash
95+
make build # produces ./bookmarks
96+
make clean # removes it
97+
```
98+
99+
## Expected error examples
100+
101+
- `create` with a missing URL fails locally in the Go CLI
102+
- `create` with `ftp://...` reaches the server and returns `INVALID_ARGUMENT`
103+
- `get`, `tag`, or `delete` with an unknown bookmark ID return `NOT_FOUND`
104+
- if the server is not running, the Go CLI exits non-zero with the connection error
105+
106+
## Tests
107+
108+
Run Python tests:
109+
110+
```bash
111+
cd python-server
112+
make test
113+
```
114+
115+
Run Go tests:
116+
117+
```bash
118+
cd go-client
119+
make test
120+
```
121+
122+
Manual Go acceptance flow:
123+
124+
1. Start the Python server.
125+
2. Create a bookmark from the Go CLI.
126+
3. Get the created bookmark by ID.
127+
4. List bookmarks.
128+
5. Add tags and verify duplicates are removed.
129+
6. Delete the bookmark and verify later `get` fails.
130+
131+
## Follow-up ideas
132+
133+
- add `ListByTag`
134+
- add server streaming for live bookmark updates
135+
- back the service with SQLite
136+
- add metadata-based auth
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
BINARY := bookmarks
2+
CMD := ./cmd/bookmarks
3+
ADDR ?= 127.0.0.1:50051
4+
5+
.PHONY: run build clean test
6+
7+
# Run directly without building (pass CLI args with: make run ARGS="list")
8+
run:
9+
go run $(CMD) --addr $(ADDR) $(ARGS)
10+
11+
# Compile to a binary at ./bookmarks
12+
build:
13+
go build -o $(BINARY) $(CMD)
14+
15+
# Run the Go unit tests
16+
test:
17+
go test ./cmd/bookmarks
18+
19+
# Remove compiled binary
20+
clean:
21+
rm -f $(BINARY)
13.9 MB
Binary file not shown.

0 commit comments

Comments
 (0)