|
| 1 | +# data-sync |
| 2 | + |
| 3 | +A Go data synchronization service that provides authenticated, versioned record storage with real-time change notifications over gRPC. Built by Breez. |
| 4 | + |
| 5 | +## Purpose |
| 6 | + |
| 7 | +Synchronizes user data across client applications. Each user's records are isolated, versioned, and protected by ECDSA signature verification. Clients can create/update records, poll for changes, or subscribe to a real-time notification stream. |
| 8 | + |
| 9 | +## Tech Stack |
| 10 | + |
| 11 | +- **Go 1.22+** (toolchain 1.23.1) |
| 12 | +- **gRPC** with Protocol Buffers for the API layer |
| 13 | +- **gRPC-Web proxy** on a second HTTP port for browser clients |
| 14 | +- **SQLite** or **PostgreSQL** as pluggable storage backends |
| 15 | +- **ECDSA (secp256k1)** signature-based authentication — user identity is derived from the public key |
| 16 | + |
| 17 | +## API (gRPC) |
| 18 | + |
| 19 | +Defined in `proto/sync.proto`: |
| 20 | + |
| 21 | +| RPC | Description | |
| 22 | +|-----|-------------| |
| 23 | +| `SetRecord` | Create or update a record. Returns `CONFLICT` if the client's revision doesn't match the server's (optimistic concurrency). | |
| 24 | +| `ListChanges` | List all records changed since a given revision. | |
| 25 | +| `ListenChanges` | Server-streaming RPC that pushes real-time notifications when a user's data changes. | |
| 26 | +| `SetLock` | Acquire or release a named distributed lock. Supports per-instance identity and TTL-based expiration (default 30s). | |
| 27 | +| `GetLock` | Check if any instance currently holds an active (non-expired) lock. | |
| 28 | + |
| 29 | +Every request must include a signature over the request payload. The server derives the user ID from the recovered public key (compressed, hex-encoded). |
| 30 | + |
| 31 | +## Directory Structure |
| 32 | + |
| 33 | +``` |
| 34 | +config/ Environment-based configuration (ports, DB path, certs) |
| 35 | +middleware/ ECDSA signature verification and optional X.509 cert validation |
| 36 | +proto/ Protobuf definitions and generated Go code |
| 37 | +store/ Storage interface and implementations |
| 38 | + sqlite/ SQLite backend with golang-migrate migrations |
| 39 | + postgres/ PostgreSQL backend with golang-migrate migrations |
| 40 | +main.go Entry point — starts gRPC server and web proxy |
| 41 | +syncer_server.go Core server logic: SetRecord, ListChanges, ListenChanges, SetLock, GetLock, EventsManager |
| 42 | +``` |
| 43 | + |
| 44 | +## Configuration (environment variables) |
| 45 | + |
| 46 | +| Variable | Default | Description | |
| 47 | +|----------|---------|-------------| |
| 48 | +| `GRPC_LISTEN_ADDRESS` | `0.0.0.0:8080` | gRPC server address | |
| 49 | +| `GRPC_WEB_LISTEN_ADDRESS` | `0.0.0.0:8081` | gRPC-Web/HTTP proxy address | |
| 50 | +| `SQLITE_DIR_PATH` | `db` | Directory for SQLite database file | |
| 51 | +| `DATABASE_URL` | — | PostgreSQL connection string (uses Postgres when set) | |
| 52 | +| `CA_CERT` | — | Base64-encoded CA certificate for client cert validation | |
| 53 | + |
| 54 | +## Build and Run |
| 55 | + |
| 56 | +```bash |
| 57 | +# Build |
| 58 | +go build -v -o data-sync . |
| 59 | + |
| 60 | +# Run (SQLite, default) |
| 61 | +./data-sync |
| 62 | + |
| 63 | +# Run (PostgreSQL) |
| 64 | +DATABASE_URL="postgres://user:pass@host:5432/dbname" ./data-sync |
| 65 | +``` |
| 66 | + |
| 67 | +## Docker |
| 68 | + |
| 69 | +```bash |
| 70 | +docker build -t data-sync . |
| 71 | +docker run -p 8080:8080 -p 8081:8081 data-sync |
| 72 | +``` |
| 73 | + |
| 74 | +## Deploy |
| 75 | + |
| 76 | +Deployed to **Fly.io** (primary region: `lhr`). See `fly.toml` for configuration. CI/CD via GitHub Actions (`.github/workflows/fly-deploy.yml`). |
| 77 | + |
| 78 | +## Key Design Decisions |
| 79 | + |
| 80 | +- **Optimistic concurrency control**: Clients must supply the current revision when updating. Mismatches return `CONFLICT` — no automatic merge. |
| 81 | +- **Signature-based identity**: No passwords or tokens. User ID is the compressed public key recovered from the ECDSA signature on each request. |
| 82 | +- **Pluggable storage**: The `store.SyncStorage` interface allows swapping between SQLite (single-instance) and PostgreSQL (multi-instance) without changing server logic. |
| 83 | +- **Serializable transactions**: Both storage backends use serializable isolation to prevent race conditions. |
| 84 | +- **Channel-based streaming**: `EventsManager` maintains per-user Go channels to fan out real-time change notifications. |
| 85 | + |
| 86 | +## Testing |
| 87 | + |
| 88 | +```bash |
| 89 | +go test ./... |
| 90 | +``` |
| 91 | + |
| 92 | +Tests cover the storage layer (SQLite and PostgreSQL) and end-to-end server behavior including conflict detection and real-time notifications. |
| 93 | + |
| 94 | +## Regenerate Protobuf |
| 95 | + |
| 96 | +```bash |
| 97 | +cd proto && ./genproto.sh |
| 98 | +``` |
0 commit comments