New doc supporting CORS, gRCP, and general client integration
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
# Quick Start: Notifier Client Integration
|
||||
|
||||
**5-minute guide to integrating with the Notifier service**
|
||||
|
||||
## Essential Requirements
|
||||
|
||||
### 1. Connection: Use Long-Lived gRPC Connection ⚡
|
||||
|
||||
```go
|
||||
// ✅ DO THIS: Create once, reuse forever
|
||||
client, _ := NewNotifierClient("notifier-grpc:50051", apiKey)
|
||||
defer client.Close()
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
client.SendNotification(ctx, req) // Reuse connection
|
||||
}
|
||||
|
||||
// ❌ DON'T DO THIS: Creating connection per request
|
||||
for i := 0; i < 10000; i++ {
|
||||
client, _ := NewNotifierClient("notifier-grpc:50051", apiKey)
|
||||
client.SendNotification(ctx, req)
|
||||
client.Close() // SLOW: 10x slower!
|
||||
}
|
||||
```
|
||||
|
||||
**Why?** Connection reuse is 10x faster (1.5ms vs 15ms per request)
|
||||
|
||||
### 2. Authentication: Send API Key with Every Request 🔐
|
||||
|
||||
```go
|
||||
import "google.golang.org/grpc/metadata"
|
||||
|
||||
// Add API key to metadata for each request
|
||||
ctx = metadata.AppendToOutgoingContext(ctx,
|
||||
"authorization", "Bearer "+apiKey)
|
||||
|
||||
resp, err := client.SendNotification(ctx, req)
|
||||
```
|
||||
|
||||
**Two accepted formats:**
|
||||
- `authorization: Bearer nk_your_api_key` (preferred)
|
||||
- `x-api-key: nk_your_api_key`
|
||||
|
||||
**API Key Format:** `nk_` + 64 hex characters (e.g., `nk_a1b2c3d4...`)
|
||||
|
||||
### 3. Credentials: Load from Environment 🔒
|
||||
|
||||
```go
|
||||
// ✅ DO THIS
|
||||
apiKey := os.Getenv("NOTIFIER_API_KEY")
|
||||
|
||||
// ❌ DON'T DO THIS
|
||||
apiKey := "nk_abc123..." // NEVER hardcode!
|
||||
```
|
||||
|
||||
**Kubernetes Secret:**
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: notifier-credentials
|
||||
stringData:
|
||||
api-key: nk_your_actual_key_here
|
||||
---
|
||||
# In your deployment:
|
||||
env:
|
||||
- name: NOTIFIER_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: notifier-credentials
|
||||
key: api-key
|
||||
```
|
||||
|
||||
### 4. Keepalive: Prevent Connection Timeouts ⏰
|
||||
|
||||
```go
|
||||
import "google.golang.org/grpc/keepalive"
|
||||
|
||||
conn, err := grpc.Dial(address,
|
||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: 10 * time.Second, // Ping every 10s
|
||||
Timeout: 3 * time.Second, // Wait 3s for response
|
||||
PermitWithoutStream: true, // Ping when idle
|
||||
}),
|
||||
)
|
||||
```
|
||||
|
||||
### 5. Error Handling: Retry Only Specific Errors 🔄
|
||||
|
||||
```go
|
||||
err := client.SendNotification(ctx, req)
|
||||
|
||||
switch status.Code(err) {
|
||||
case codes.ResourceExhausted:
|
||||
// Rate limit exceeded - RETRY with backoff
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// Try again...
|
||||
|
||||
case codes.Unauthenticated:
|
||||
// Invalid API key - DON'T RETRY, log error
|
||||
log.Errorf("Authentication failed: %v", err)
|
||||
|
||||
case codes.InvalidArgument:
|
||||
// Bad request - DON'T RETRY, fix request
|
||||
log.Errorf("Invalid request: %v", err)
|
||||
|
||||
case codes.Unavailable:
|
||||
// Service down - RETRY with backoff
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// Try again...
|
||||
}
|
||||
```
|
||||
|
||||
**Retry Strategy:**
|
||||
- Exponential backoff: 100ms → 200ms → 400ms → 800ms
|
||||
- Max 3-5 attempts
|
||||
- Only retry: `ResourceExhausted`, `Unavailable`, `DeadlineExceeded`
|
||||
|
||||
### 6. Context Timeout: Always Set Timeout ⏱️
|
||||
|
||||
```go
|
||||
// ✅ DO THIS: Set timeout for each request
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := client.SendNotification(ctx, req)
|
||||
|
||||
// ❌ DON'T DO THIS: No timeout
|
||||
ctx := context.Background()
|
||||
resp, err := client.SendNotification(ctx, req) // Could hang forever!
|
||||
```
|
||||
|
||||
## Complete Minimal Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
pb "github.com/igodwin/notifier/api/grpc/pb"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
type NotifierClient struct {
|
||||
conn *grpc.ClientConn
|
||||
client pb.NotifierServiceClient
|
||||
apiKey string
|
||||
}
|
||||
|
||||
func NewNotifierClient(address, apiKey string) (*NotifierClient, error) {
|
||||
// Create long-lived connection
|
||||
conn, err := grpc.Dial(address,
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: 10 * time.Second,
|
||||
Timeout: 3 * time.Second,
|
||||
PermitWithoutStream: true,
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &NotifierClient{
|
||||
conn: conn,
|
||||
client: pb.NewNotifierServiceClient(conn),
|
||||
apiKey: apiKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (nc *NotifierClient) SendNotification(ctx context.Context, req *pb.SendNotificationRequest) (*pb.SendNotificationResponse, error) {
|
||||
// Add timeout
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Add API key to metadata
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+nc.apiKey)
|
||||
|
||||
// Make RPC call
|
||||
return nc.client.SendNotification(ctx, req)
|
||||
}
|
||||
|
||||
func (nc *NotifierClient) Close() error {
|
||||
return nc.conn.Close()
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Load credentials
|
||||
address := os.Getenv("NOTIFIER_ADDRESS")
|
||||
apiKey := os.Getenv("NOTIFIER_API_KEY")
|
||||
|
||||
if address == "" {
|
||||
address = "notifier-grpc:50051"
|
||||
}
|
||||
if apiKey == "" {
|
||||
log.Fatal("NOTIFIER_API_KEY environment variable required")
|
||||
}
|
||||
|
||||
// Create client (once)
|
||||
client, err := NewNotifierClient(address, apiKey)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Send notification
|
||||
resp, err := client.SendNotification(context.Background(), &pb.SendNotificationRequest{
|
||||
Type: pb.NotificationType_NOTIFICATION_TYPE_EMAIL,
|
||||
Subject: "Test Notification",
|
||||
Body: "This is a test from the notifier client",
|
||||
Recipients: []string{"user@example.com"},
|
||||
Priority: pb.Priority_PRIORITY_NORMAL,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to send notification: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Notification sent successfully: %s", resp.NotificationId)
|
||||
}
|
||||
```
|
||||
|
||||
## Service Connection Details
|
||||
|
||||
**Address:**
|
||||
- Same namespace: `notifier-grpc:50051`
|
||||
- Different namespace: `notifier-grpc.default.svc.cluster.local:50051`
|
||||
|
||||
**Rate Limit:** 100 requests/minute (default, configurable per key)
|
||||
|
||||
**Available Methods:**
|
||||
- `SendNotification` - Send single notification
|
||||
- `SendBatchNotifications` - Send multiple notifications
|
||||
- `GetNotification` - Get notification status
|
||||
- `ListNotifications` - List recent notifications
|
||||
- `CancelNotification` - Cancel pending notification
|
||||
- `RetryNotification` - Retry failed notification
|
||||
- `GetStats` - Get service statistics
|
||||
- `GetNotifiers` - List available notifiers
|
||||
- `HealthCheck` - Check service health
|
||||
|
||||
**Notification Types:**
|
||||
- `NOTIFICATION_TYPE_EMAIL` - Email via SMTP
|
||||
- `NOTIFICATION_TYPE_SLACK` - Slack webhook
|
||||
- `NOTIFICATION_TYPE_NTFY` - Ntfy.sh push notifications
|
||||
- `NOTIFICATION_TYPE_STDOUT` - Console output (dev only)
|
||||
|
||||
## Security Checklist
|
||||
|
||||
- [ ] API key loaded from environment variable
|
||||
- [ ] No hardcoded credentials in source code
|
||||
- [ ] API key never logged in full (mask it: `nk_abc...xyz`)
|
||||
- [ ] Kubernetes Secret created for API key
|
||||
- [ ] Deployment configured to load secret as env var
|
||||
- [ ] `.gitignore` excludes any credential files
|
||||
|
||||
## Testing Your Integration
|
||||
|
||||
```bash
|
||||
# Set environment variables
|
||||
export NOTIFIER_ADDRESS="notifier-grpc:50051"
|
||||
export NOTIFIER_API_KEY="nk_your_api_key_here"
|
||||
|
||||
# Run your application
|
||||
go run main.go
|
||||
|
||||
# Check logs for:
|
||||
# ✅ "Notification sent successfully: <id>"
|
||||
# ❌ "Authentication failed" - check API key
|
||||
# ❌ "Rate limit exceeded" - slow down requests
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
**"Unauthenticated" error:**
|
||||
- Check API key format starts with `nk_`
|
||||
- Verify key is 66 characters total (nk_ + 64 hex)
|
||||
- Confirm key is active in notifier service
|
||||
- Check you're adding it to metadata correctly
|
||||
|
||||
**"Rate limit exceeded" error:**
|
||||
- Implement exponential backoff retry
|
||||
- Consider batching notifications
|
||||
- Request higher rate limit for your key
|
||||
|
||||
**Connection timeouts:**
|
||||
- Add keepalive configuration
|
||||
- Check network connectivity to `notifier-grpc:50051`
|
||||
- Verify notifier service is running: `kubectl get pods -l app=notifier`
|
||||
|
||||
**Slow performance:**
|
||||
- Confirm you're reusing connection (not creating per request)
|
||||
- Check keepalive is configured
|
||||
- Verify you're using connection pooling for high concurrency
|
||||
|
||||
## Next Steps
|
||||
|
||||
For more details, see:
|
||||
- `CLIENT_INTEGRATION_PROMPT.md` - Complete prompt for AI assistants
|
||||
- `GRPC_CONNECTION_OPTIMIZATION.md` - Deep dive on performance
|
||||
- `CLIENT_RECOMMENDATIONS.md` - Advanced patterns and best practices
|
||||
- `AUTH.md` - Authentication system details
|
||||
|
||||
## Getting Your API Key
|
||||
|
||||
Contact your Notifier service administrator to:
|
||||
1. Create an API key for your application
|
||||
2. Set appropriate rate limits
|
||||
3. Assign required roles for notifier access
|
||||
4. Get the key in format: `nk_<64-hex-characters>`
|
||||
|
||||
Store it securely and never commit it to version control!
|
||||
Reference in New Issue
Block a user