feat: add NATS chatroom example with embedded server
Demonstrates pub/sub messaging as an alternative to custom Rooms implementation. Uses delaneyj/toolbelt/embeddednats to run NATS with JetStream inside the binary - no external server required.
This commit is contained in:
109
internal/examples/nats-chatroom/README.md
Normal file
109
internal/examples/nats-chatroom/README.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# NATS Chatroom Example (Embedded)
|
||||
|
||||
A chatroom built with Via and an **embedded NATS server**, demonstrating pub/sub messaging as an alternative to the custom `Rooms` implementation in `../chatroom`.
|
||||
|
||||
Uses `delaneyj/toolbelt/embeddednats` to run NATS inside the same binary - no external server required.
|
||||
|
||||
## Key Differences from Original Chatroom
|
||||
|
||||
| Aspect | Original (`../chatroom`) | This Example |
|
||||
|--------|-------------------------|--------------|
|
||||
| Pub/sub | Custom `Rooms` struct (~160 lines) | NATS subjects |
|
||||
| Member tracking | Manual `map[TU]Syncable` | NATS handles subscribers |
|
||||
| Publish timing | Ticker every 100ms + dirty flag | Instant delivery |
|
||||
| Durability | None (in-memory) | JetStream persists to disk |
|
||||
| Multi-instance | Not supported | Works across server instances |
|
||||
| External deps | None | **None** (NATS embedded in binary) |
|
||||
|
||||
## Run the Example
|
||||
|
||||
```bash
|
||||
go run ./internal/examples/nats-chatroom
|
||||
```
|
||||
|
||||
That's it. No separate NATS server needed.
|
||||
|
||||
Open multiple browser tabs at http://localhost:7331 to see messages broadcast across all clients.
|
||||
|
||||
## How Embedded NATS Works
|
||||
|
||||
```go
|
||||
// Start embedded NATS server (JetStream enabled by default)
|
||||
ns, err := embeddednats.New(ctx,
|
||||
embeddednats.WithDirectory("./data/nats"),
|
||||
)
|
||||
ns.WaitForServer()
|
||||
|
||||
// Get client connection to embedded server
|
||||
nc, err := ns.Client()
|
||||
```
|
||||
|
||||
Data is persisted to `./data/nats/` for JetStream durability.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Single Binary │
|
||||
│ │
|
||||
│ Browser A Embedded NATS Browser B │
|
||||
│ │ │ │ │
|
||||
│ │-- Via Action ---> │ │ │
|
||||
│ │ (Send msg) │ │ │
|
||||
│ │ │ │ │
|
||||
│ │ nc.Publish() │ │
|
||||
│ │ "chat.room.Go" │ │
|
||||
│ │ │ │ │
|
||||
│ │<-- Subscribe -----|---- Subscribe --->│ │
|
||||
│ │ callback │ callback │ │
|
||||
│ │ │ │ │
|
||||
│ │-- c.Sync() ------>│<--- c.Sync() -----| │
|
||||
│ │ (SSE) │ (SSE) │ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## JetStream Durability
|
||||
|
||||
Messages persist to disk via JetStream:
|
||||
|
||||
```go
|
||||
js.AddStream(&nats.StreamConfig{
|
||||
Name: "CHAT",
|
||||
Subjects: []string{"chat.>"},
|
||||
MaxMsgs: 1000, // Keep last 1000 messages
|
||||
MaxAge: 24 * time.Hour,
|
||||
})
|
||||
```
|
||||
|
||||
Stop and restart the app - chat history survives.
|
||||
|
||||
## Code Comparison
|
||||
|
||||
**Original chatroom - 160+ lines of custom pub/sub:**
|
||||
- `Rooms` struct with named rooms
|
||||
- `Room` with member tracking, mutex, dirty flag
|
||||
- Ticker-based publish loop
|
||||
- Manual join/leave channels
|
||||
|
||||
**This example - ~60 lines of NATS integration:**
|
||||
- `embeddednats.New()` starts the server
|
||||
- `nc.Subscribe(subject, handler)` for receiving
|
||||
- `nc.Publish(subject, data)` for sending
|
||||
- NATS handles delivery, no polling
|
||||
|
||||
## Next Steps
|
||||
|
||||
If this pattern proves useful, it could be promoted to a Via plugin:
|
||||
|
||||
```go
|
||||
// Hypothetical future API
|
||||
v.Config(via.WithEmbeddedNATS("./data/nats"))
|
||||
|
||||
// In page init
|
||||
c.Subscribe("events.user.*", func(data []byte) {
|
||||
c.Sync()
|
||||
})
|
||||
|
||||
c.Publish("events.user.login", userData)
|
||||
```
|
||||
Reference in New Issue
Block a user