API-DD

Guía práctica

El nombre que obliga a abrir la implementación

Comparar OrderService.Process con Checkout.Place muestra cómo los nombres técnicos esconden la conversación y cómo encontrar vocabulario más estable.

Por Lautaro Mei

Esta API podría gestionar pagos, importar archivos o generar informes:

go
type OrderService interface {
    Process(context.Context, OrderRequest) (OrderResponse, error)
}

Solo sabemos que existe un servicio, que procesa algo y que devuelve una respuesta. Para usarlo hay que abrir OrderRequest, leer Process y descubrir qué significa cada error.

El nombre no reduce la complejidad. La desplaza a la implementación.

Lee la llamada como una frase#

El consumidor quiere convertir un carrito en un pedido:

go
response, err := orderService.Process(ctx, OrderRequest{
    CartID: cartID,
})

Podemos repartir el significado entre módulo, mensaje y resultado:

go
orderID, err := checkout.Place(ctx, cartID)

La segunda llamada se lee como una frase: «checkout, realiza el pedido de este carrito». OrderID explica qué obtiene el consumidor. Errores como ErrEmptyCart o ErrPaymentRejected nombran situaciones ante las que puede actuar.

No hay más información en el nombre largo. Hay menos categorías técnicas.

Los nombres malos suelen ser verdaderos para demasiadas cosas#

Process, Execute, Handle, Data, Item y Response no siempre son incorrectos. Son débiles cuando podrían nombrar casi cualquier operación del sistema.

text
OrderService.Process(OrderRequest) → OrderResponse

describe la forma del código: servicio, request, proceso y response.

text
Checkout.Place(CartID) → OrderID | EmptyCart | PaymentRejected

describe la conversación: contexto, intención, entrada y diferencias observables.

El consumidor no necesita saber si por dentro se ejecuta un workflow, una transacción o varios handlers.

Un nombre bueno sobrevive a otra implementación#

Supongamos que Process hoy llama a un proveedor de pagos y guarda en SQL. Nombrarlo ProcessAndPersistOrder parece más preciso:

go
ProcessAndPersistOrder(ctx, request)

Pero la precisión está puesta en el mecanismo. Si mañana se publica un evento y la persistencia ocurre de otra forma, el nombre deja de ser cierto aunque la necesidad del consumidor no cambie.

Place sigue funcionando. Describe la intención que ambas implementaciones conservan.

Una buena comprobación es imaginar dos interiores válidos. Si el nombre solo describe uno, probablemente pertenece dentro del módulo.

El contexto permite nombres cortos#

PlaceOrder puede ser útil como función aislada. Dentro de checkout, repetir CheckoutOrderService.PlaceOrder añade ruido:

go
checkout.Place(cartID)

El paquete ya aporta el contexto. El método aporta la acción. El argumento aporta el objeto.

Los nombres no se evalúan por separado. Get puede ser ambiguo en un paquete genérico y suficiente en una colección pequeña. Place puede significar muchas cosas solo, pero es concreto dentro de checkout y junto a CartID.

El resultado también necesita nombre#

OrderResponse obliga a inspeccionar campos:

go
type OrderResponse struct {
    Success bool
    ID      string
    Message string
}

Permite combinaciones dudosas: Success == false con ID no vacío o Success == true sin ID. El nombre describe el transporte de una respuesta, no su significado.

La conversación puede usar valores y errores concretos:

go
type OrderID string

var (
    ErrEmptyCart       = errors.New("empty cart")
    ErrPaymentRejected = errors.New("payment rejected")
)

func (c *Checkout) Place(
    ctx context.Context,
    cartID CartID,
) (OrderID, error)

El tipo nuevo aporta porque evita confundir un pedido con un carrito y nombra el resultado. No hace falta crear OrderIDValueObject ni PlaceOrderResponseDTO en la API principal.

Renombrar puede revelar un problema de límites#

Si cuesta encontrar un verbo, quizá el módulo reúne varias intenciones:

go
OrderService.Process(request)

podría crear, cancelar, exportar o reintentar según un campo Action. Ningún sinónimo de Process arregla esa mezcla. Separar mensajes hace visible la capacidad:

go
checkout.Place(cartID)
orders.Cancel(orderID)
orders.Export(query)

El problema no era falta de creatividad. Era una conversación demasiado grande.

Los tests también hablan mejor#

Compara estos nombres:

go
func TestProcessReturnsFalse(t *testing.T)
func TestPlaceRejectsAnEmptyCart(t *testing.T)

El segundo declara intención, condición y resultado. Permite imaginar la alternativa incorrecta sin abrir el cuerpo del test.

Un vocabulario preciso mejora código, documentación, métricas y conversaciones de equipo porque todos describen la misma promesa.

Un buen nombre permite entender la conversación; un mal nombre obliga a reconstruirla desde el mecanismo.