De los Requisitos a las Especificaciones de Servicios Go
Producto te entrega historias de usuario.
Busca en todas las páginas de la documentación
Producto te entrega historias de usuario.
Ingeniería necesita contratos de manejadores, formas de almacenamiento y SLOs medibles antes de que alguien abra main.go.
Este flujo de trabajo convierte requisitos ambiguos en una especificación de servicio Go que los stakeholders pueden aprobar y los ICs pueden implementar sin retrabajo.
Una especificación completa de servicio Go nombra las superficies HTTP o gRPC, la persistencia, los requisitos no funcionales y los contratos de error.
Las historias proporcionan la voz del cliente; la especificación proporciona la verdad verificable.
Revisa la especificación con producto y consumidores antes del compromiso del sprint.
Itera en el documento compartido; genera código a partir de OpenAPI o protobuf solo después de que las firmas se estabilicen.
Esqueleto mínimo de especificación para un nuevo endpoint HTTP en chi.
## Feature: Exportación de PDF de facturas
### API
- `GET /v1/invoices/{id}/pdf` → 200 `application/pdf`
- Errores: 403 `invoice_unpaid`, 404 `invoice_not_found`, 500 `render_failed`
### Datos
- Leer tabla `invoices` (Postgres); sin nuevas columnas
- Bytes de PDF efímeros; clave de caché opcional en S3 `pdf/{invoice_id}`
### NFRs
- latencia p99 2s @ 100 RPS (staging)
- Idempotente: GET repetido devuelve los mismos bytes para la misma revisión de factura
- Auth: ámbito JWT `billing:read`
### Fuera de alcance
- Entrega por correo electrónico, exportación por lotesCuándo usar esto:
.protoFragmento de especificación más firma de manejador Go y mapeo de errores coincidentes.
// internal/api/export.go
package api
import (
"context"
"errors"
"net/http"
"github.com/go-chi/chi/v5"
)
var (
ErrInvoiceUnpaid = errors.New("invoice_unpaid")
ErrInvoiceNotFound = errors.New("invoice_not_found")
ErrRenderFailed = errors.New("render_failed")
)
type InvoiceReader interface {
GetInvoice(ctx context.Context, id string) (Invoice, error)
}
type PDFRenderer interface {
RenderInvoicePDF(ctx context.Context, inv Invoice) ([]byte, error)
}
func MountExport(r chi.Router, inv InvoiceReader, pdf PDFRenderer) {
r.Get("/v1/invoices/{id}/pdf", func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
inv, err := inv.GetInvoice(r.Context(), id)
if err != nil {
writeErr(w, mapErr(err))
return
}
if inv.Status != "paid" {
writeErr(w, http.StatusForbidden, ErrInvoiceUnpaid)
return
}
bytes, err := pdf.RenderInvoicePDF(r.Context(), inv)
if err != nil {
writeErr(w, http.StatusInternalServerError, ErrRenderFailed)
return
}
w.Header().Set("Content-Type", "application/pdf")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(bytes)
})
}# api/openapi.yaml (extracto)
paths:
/v1/invoices/{id}/pdf:
get:
operationId: getInvoicePdf
responses:
"200":
content:
application/pdf:
schema: { type: string, format: binary }
"403":
content:
application/json:
schema:
properties:
code: { enum: [invoice_unpaid] }Lo que esto demuestra:
InvoiceReader, PDFRenderer) siguen la capa manejador-servicio-repositorioerr.Error()| Sección de la especificación | Entrada del producto | Salida de ingeniería |
|---|---|---|
| API | Viajes del usuario, pantallas | Rutas, métodos, cargas útiles, códigos de estado |
| Datos | Entidades nombradas en historias | Tablas, eventos, retención, clase PII |
| NFRs | "Rápido", "seguro", "confiable" | p99, RPS, autorización, RTO/RPO, auditoría |
| Errores | Casos extremos en la UX | Códigos estables, política de reintentos, macros de soporte |
| Alcance | MVP vs. posterior | Lista explícita de lo que está fuera de alcance |
Bucle de refinamiento de historias
Variante gRPC
Reemplaza las rutas OpenAPI con RPCs de .proto, campos de mensaje y detalles de google.rpc.Status.
Mantén las mismas secciones de NFR y datos.
Los stubs de Go generados aterrizan en gen/ o un módulo versionado.
| SLI | Objetivo | Medición |
|---|---|---|
| Disponibilidad | 99.9% mensual | Tasa de 5xx excluyendo errores del cliente |
| Latencia | p99 ≤ 2s | Histograma http_server_duration |
| Corrección | 0 exportaciones mal facturadas | Diferencia del trabajo de reconciliación |
// Prefiere el contexto en cada límite de E/S - la especificación debe indicar los tiempos de espera.
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()context que coincidan con los presupuestos de latencia de las NFRs.go generate para oapi-codegen o buf generate en la sección de compilación.Error(). Solución: La especificación requiere un campo code estable; mapea en la capa del manejador.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| OpenAPI-first | Servicios HTTP, múltiples consumidores | gRPC solo interno con buf ya estándar |
| Protobuf-first | gRPC, pipeline de codegen robusto | CRUD simple con un solo cliente web |
| Especificación impulsada por ejemplos | CLI o biblioteca pequeña | Contratos HTTP multi-equipo con revisión legal |
| Solo ADR | Equipo único con API externa nula | Contratos orientados al cliente que requieren alineación UX |
De dos a cuatro páginas por épica.
Suficiente para tablas de API, diferencias de datos, NFRs y errores.
La implementación detallada pertenece a los documentos de diseño enlazados desde la especificación.
Producto aprueba el alcance y los criterios de aceptación.
Ingeniería aprueba la viabilidad.
Los equipos consumidores aprueban los cambios disruptivos.
Incluye modelos lógicos y notas de migración.
El DDL completo puede residir en un archivo de migración enlazado y referenciado por versión.
Documenta el disparador de encolamiento, el esquema de la carga útil, la política de reintentos, el comportamiento de la cola de mensajes fallidos y la API de estado visible para el usuario.
Nombra la clave de la bandera, por defecto desactivada/activada por entorno, y el propietario del interruptor de apagado en la sección NFR.
Los spikes responden a lo desconocido; producen una sección de especificación revisada, no un sustituto de las NFRs.
La especificación lista la ruta del módulo y la versión mínima; anota los cambios disruptivos para los servicios downstream.
SLOs orientados al cliente en la especificación.
Los runbooks y los umbrales de alerta se enlazan desde la sección NFR de la especificación.
Incrementa spec_version ante cualquier cambio de contrato.
Los consumidores se suscriben a las entradas del registro de cambios en las notas de la versión.
Usa borradores para agilizar, pero la revisión humana para errores, autorización y retención de datos es obligatoria.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado GC Green Tea, go fix modernizers - verifica el parche en la compilación), chi (última versión - verifica en la compilación), gin (última versión - verifica en la compilación), echo (última versión - verifica en la compilación), google.golang.org/grpc (última versión - verifica en la compilación), sigs.k8s.io/controller-runtime (última versión - verifica en la compilación), kubebuilder (última versión - verifica en la compilación), tinygo (última versión - verifica los objetivos de la placa en la compilación), wazero (última versión - verifica en la compilación) y golangci-lint (última versión - verifica el conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 16 jul 2026