Uso: Consulta rapida durante implementacao Documento completo: FASE_0.1_DOCUMENTACAO_EXTERNA.md
dsn := "host=10.1.2.3 user=app password=secret dbname=typecraft port=5432 sslmode=verify-full sslrootcert=/etc/ssl/certs/server-ca.pem TimeZone=UTC"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
TranslateError: true, // IMPORTANTE
})
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(25)
sqlDB.SetConnMaxLifetime(5 * time.Minute)disable - NUNCA em producao
require - Evitar (vulnerable to MITM)
verify-ca - OK para private CA
verify-full - OBRIGATORIO para producao (Cloud SQL, RDS)
if errors.Is(err, gorm.ErrRecordNotFound) { /* not found */ }
if errors.Is(err, gorm.ErrDuplicatedKey) { /* unique violation */ }
if errors.Is(err, gorm.ErrForeignKeyViolated) { /* FK violation */ }Development: AutoMigrate
Staging: Versioned migrations (gormigrate/Atlas)
Production: Versioned migrations + backup + testing
client := asynq.NewClient(asynq.RedisClientOpt{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
defer client.Close()
task, _ := NewEmailTask(userID, email)
client.Enqueue(task, asynq.MaxRetry(3), asynq.Timeout(30*time.Second))srv := asynq.NewServer(
redisOpt,
asynq.Config{
Concurrency: 10,
Queues: map[string]int{
"critical": 6,
"default": 3,
"low": 1,
},
},
)func HandleTask(ctx context.Context, t *asynq.Task) error {
var p Payload
if err := json.Unmarshal(t.Payload(), &p); err != nil {
return fmt.Errorf("unmarshal failed: %w", asynq.SkipRetry)
}
// Process task
if err := process(p); err != nil {
return err // Will retry
}
return nil // Success
}appendonly yes
appendfsync everysec
maxmemory-policy noeviction
router := gin.New()
router.Use(corsMiddleware()) // 1. CORS (preflight)
router.Use(gin.Recovery()) // 2. Panic recovery
router.Use(gin.Logger()) // 3. Logging
router.Use(rateLimitMiddleware()) // 4. Rate limit
router.Use(authMiddleware()) // 5. Authenticationimport "github.com/gin-contrib/cors"
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://app.typecraft.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))// Use AbortWithStatusJSON for errors
if err := validate(); err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": err.Error()})
return
}srv := &http.Server{
Addr: ":" + os.Getenv("PORT"),
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
go srv.ListenAndServe()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)// Liveness
router.GET("/healthz", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})
// Readiness
router.GET("/readyz", func(c *gin.Context) {
if err := db.Exec("SELECT 1").Error; err != nil {
c.JSON(503, gin.H{"status": "not ready"})
return
}
c.JSON(200, gin.H{"status": "ready"})
})opts := append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.NoSandbox, // Docker REQUIRED
chromedp.DisableDevShmUsage, // Docker REQUIRED
chromedp.DisableGPU,
)
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancel()
browserCtx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
// Initialize (NO TIMEOUT)
chromedp.Run(browserCtx, chromedp.Navigate("about:blank"))func generatePDF(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(browserCtx, 30*time.Second)
defer cancel()
var buf []byte
err := chromedp.Run(ctx,
chromedp.Navigate(url),
chromedp.WaitReady("body"),
chromedp.Sleep(1*time.Second), // Safety margin for images
chromedp.ActionFunc(func(ctx context.Context) error {
var err error
buf, _, err = page.PrintToPDF().
WithPrintBackground(true).
WithPaperWidth(8.27).
WithPaperHeight(11.69).
WithMarginTop(0.4).
WithMarginBottom(0.4).
WithMarginLeft(0.4).
WithMarginRight(0.4).
Do(ctx)
return err
}),
)
return buf, err
}FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o app
FROM chromedp/headless-shell:latest
COPY --from=builder /app/app /app
USER chromedp
EXPOSE 8080
ENTRYPOINT ["/app"]FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath -ldflags="-s -w" -o app
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/app /app
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/app"]apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: typecraft
spec:
template:
metadata:
annotations:
run.googleapis.com/timeout: '300s'
autoscaling.knative.dev/maxScale: '10'
spec:
containers:
- image: gcr.io/PROJECT_ID/typecraft:latest
env:
- name: PORT
value: "8080"
- name: GIN_MODE
value: "release"
- name: INSTANCE_CONNECTION_NAME
value: "project:region:instance"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-password
key: latest
resources:
limits:
memory: 512Mi
cpu: '1'
startupProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
timeoutSeconds: 1
failureThreshold: 3// Unix Socket (Recommended)
dsn := fmt.Sprintf("host=/cloudsql/%s user=%s password=%s dbname=%s sslmode=disable",
os.Getenv("INSTANCE_CONNECTION_NAME"),
os.Getenv("DB_USER"),
os.Getenv("DB_PASSWORD"),
os.Getenv("DB_NAME"),
)
// Private IP (VPC)
dsn := fmt.Sprintf("host=%s port=5432 user=%s password=%s dbname=%s sslmode=require",
os.Getenv("DB_HOST"),
os.Getenv("DB_USER"),
os.Getenv("DB_PASSWORD"),
os.Getenv("DB_NAME"),
)# Build
gcloud builds submit --tag gcr.io/PROJECT_ID/typecraft
# Deploy
gcloud run services replace cloud-run.yaml --region=us-central1import "github.com/stripe/stripe-go/v76"
stripe.Key = os.Getenv("STRIPE_SECRET_KEY")func handleWebhook(c *gin.Context) {
payload, _ := ioutil.ReadAll(c.Request.Body)
sig := c.GetHeader("Stripe-Signature")
secret := os.Getenv("STRIPE_WEBHOOK_SECRET")
event, err := webhook.ConstructEvent(payload, sig, secret)
if err != nil {
c.JSON(400, gin.H{"error": "invalid signature"})
return
}
c.JSON(200, gin.H{"received": true})
go processWebhookEvent(event)
}func processWebhookEvent(event stripe.Event) {
switch event.Type {
case "payment_intent.succeeded":
var pi stripe.PaymentIntent
json.Unmarshal(event.Data.Raw, &pi)
// IDEMPOTENT check
var order Order
if db.Where("stripe_payment_intent_id = ? AND status = ?", pi.ID, "paid").First(&order).Error == nil {
return // Already processed
}
db.Model(&Order{}).Where("stripe_payment_intent_id = ?", pi.ID).Update("status", "paid")
}
}import "github.com/google/uuid"
params := &stripe.PaymentIntentParams{
Amount: stripe.Int64(1000),
Currency: stripe.String("usd"),
}
params.IdempotencyKey = stripe.String(uuid.New().String())
pi, err := paymentintent.New(params)if err != nil {
if stripeErr, ok := err.(*stripe.Error); ok {
switch stripeErr.Type {
case stripe.ErrorTypeCard:
// Don't retry
case stripe.ErrorTypeRateLimit:
// Retry with backoff
}
}
}Small/Medium App:
SetMaxIdleConns: 10
SetMaxOpenConns: 25
ConnMaxLifetime: 5 * time.Minute
High Traffic:
SetMaxIdleConns: 25
SetMaxOpenConns: 100
ConnMaxLifetime: 5 * time.Minute
CPU-bound: runtime.NumCPU()
I/O-bound: runtime.NumCPU() * 2
Production: 10-50
Request timeout (Services):
Default: 300s (5 min)
Max: 3600s (1 hour)
Task timeout (Jobs):
Default: 600s (10 min)
Max: 604800s (7 days)
Health check timeout:
Default: 1s
Max: 240s
Browser allocation: NO TIMEOUT
PDF generation: 30s
Page navigation: 30s
Element wait: 10s
A4: 8.27 x 11.69
Letter: 8.5 x 11
Legal: 8.5 x 14
A3: 11.69 x 16.54
- Forgetting TranslateError: true
- Using == instead of errors.Is()
- Not setting connection pool limits
- sslmode=disable in production
- Not wrapping unmarshal errors with SkipRetry
- AOF disabled (data loss)
- No idempotency in handlers
- Concurrency too high
- AllowAllOrigins with AllowCredentials
- c.JSON for errors (should use Abort)
- No graceful shutdown
- Heavy health checks
- Timeout on first Run()
- Creating new browser per request
- No wait before PrintToPDF
- Missing Docker flags (NoSandbox, DisableDevShmUsage)
- Hardcoded port (should read PORT env var)
- Secrets in env vars (should use Secret Manager)
- MaxOpenConns too high (* num_instances)
- Request timeout too short for workload
- No signature verification
- Blocking webhook response
- Reusing idempotency keys
- Non-idempotent handlers
# Application
PORT=8080
GIN_MODE=release
# Database
DB_HOST=10.1.2.3
DB_PORT=5432
DB_NAME=typecraft
DB_USER=app
DB_PASSWORD=<from-secret-manager>
# Cloud SQL
INSTANCE_CONNECTION_NAME=project:region:instance
# Redis
REDIS_ADDR=localhost:6379
REDIS_PASSWORD=
REDIS_DB=0
# Stripe
STRIPE_SECRET_KEY=sk_live_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
# Logging
LOG_LEVEL=info- Multi-stage Dockerfile
- Non-root user
- All secrets in Secret Manager
- Health checks implemented
- Graceful shutdown implemented
- Connection pooling configured
- Build image: gcloud builds submit
- Deploy service: gcloud run services replace
- Verify health checks pass
- Test endpoints
- Monitor logs
- Check metrics
- Verify DB connections
- Test Stripe webhooks
- Monitor error rates
- Check Asynq queue depth
- Verify PDF generation works
FIM DA QUICK REFERENCE
Para detalhes completos, consultar: FASE_0.1_DOCUMENTACAO_EXTERNA.md