Errori di Dominio vs Errori di Trasporto
Quando si progetta la gestione degli errori in Goa, è importante comprendere la distinzione tra errori di dominio e la loro rappresentazione a livello di trasporto. Questa separazione ti permette di mantenere una logica di dominio pulita garantendo al contempo una corretta comunicazione degli errori attraverso diversi protocolli.
Errori di Dominio
Gli errori di dominio rappresentano i fallimenti della logica di business nella tua applicazione. Sono
indipendenti dal protocollo e si concentrano su cosa è andato storto dal punto di vista
della logica di business. Il tipo predefinito ErrorResult di Goa è spesso sufficiente per esprimere
errori di dominio - i tipi di errore personalizzati sono opzionali e necessari solo per casi
specializzati.
Uso del Tipo di Errore Predefinito
Il tipo predefinito ErrorResult combinato con nomi significativi, descrizioni e
proprietà degli errori può esprimere efficacemente la maggior parte degli errori di dominio:
var _ = Service("payment", func() {
// Definisci errori di dominio usando il tipo ErrorResult predefinito
Error("insufficient_funds", ErrorResult, func() {
Description("Il conto non ha fondi sufficienti per la transazione")
// Le proprietà dell'errore aiutano a definire le caratteristiche dell'errore
Temporary() // L'errore può risolversi se l'utente aggiunge fondi
})
Error("card_expired", ErrorResult, func() {
Description("La carta di pagamento è scaduta")
// Questo è un errore permanente finché la carta non viene aggiornata
})
Error("processing_failed", ErrorResult, func() {
Description("Sistema di elaborazione pagamenti temporaneamente non disponibile")
Temporary() // Si può riprovare più tardi
Fault() // Problema lato server
})
Method("process", func() {
// ... definizione del metodo
})
})
Gli errori di dominio dovrebbero:
- Avere nomi chiari e descrittivi che riflettono lo scenario di business
- Includere descrizioni significative per la documentazione e il debug
- Usare proprietà degli errori per indicare le caratteristiche dell’errore
- Essere indipendenti da come verranno trasmessi
Tipi di Errore Personalizzati (Opzionali)
Per i casi in cui sono necessari dati di errore strutturati aggiuntivi, puoi definire tipi di errore personalizzati. Vedi la
documentazione principale sulla gestione degli errori per informazioni dettagliate sui
tipi di errore personalizzati, inclusi i requisiti importanti per il campo name e i metadati struct:error:name.
// Tipo personalizzato per quando è richiesto un contesto di errore extra
var PaymentError = Type("PaymentError", func() {
Description("PaymentError rappresenta un fallimento nell'elaborazione del pagamento")
Field(1, "message", String, "Messaggio di errore leggibile")
Field(2, "code", String, "Codice di errore interno")
Field(3, "transaction_id", String, "ID della transazione fallita")
Field(4, "name", String, "Nome dell'errore per la mappatura di trasporto", func() {
Meta("struct:error:name")
})
Required("message", "code", "name")
})
Mappatura dei Trasporti
Le mappature di trasporto definiscono come gli errori di dominio sono rappresentati in specifici protocolli. Questo include codici di stato, headers e formati di risposta.
Trasporto HTTP
var _ = Service("payment", func() {
// Definisci errori di dominio
Error("insufficient_funds", PaymentError)
Error("card_expired", PaymentError)
Error("processing_failed", PaymentError)
HTTP(func() {
// Mappa gli errori di dominio ai codici di stato HTTP
Response("insufficient_funds", StatusPaymentRequired, func() {
// Aggiungi headers specifici per il pagamento
Header("Retry-After")
// Personalizza il formato della risposta di errore
Body(func() {
Attribute("error_code")
Attribute("message")
})
})
Response("card_expired", StatusUnprocessableEntity)
Response("processing_failed", StatusServiceUnavailable)
})
})
Trasporto gRPC
var _ = Service("payment", func() {
// Stessi errori di dominio
Error("insufficient_funds", PaymentError)
Error("card_expired", PaymentError)
Error("processing_failed", PaymentError)
GRPC(func() {
// Mappa ai codici di stato gRPC
Response("insufficient_funds", CodeFailedPrecondition)
Response("card_expired", CodeInvalidArgument)
Response("processing_failed", CodeUnavailable)
})
})
Vantaggi della Separazione
Questa separazione delle responsabilità fornisce diversi vantaggi:
Indipendenza dal Protocollo
- Gli errori di dominio rimangono focalizzati sulla logica di business
- Lo stesso errore può essere mappato diversamente per protocolli diversi
- Facile aggiungere nuovi protocolli di trasporto
Gestione Coerente degli Errori
- Definizioni di errore centralizzate
- Gestione uniforme degli errori tra i servizi
- Mappatura chiara tra errori di dominio e di trasporto
Documentazione Migliore
- Gli errori di dominio documentano le regole di business
- Le mappature di trasporto documentano il comportamento dell’API
- La separazione chiara aiuta i consumatori dell’API
Esempio di Implementazione
Ecco come funziona questa separazione nella pratica:
Uso di ErrorResult Predefinito
func (s *paymentService) Process(ctx context.Context, p *payment.ProcessPayload) (*payment.ProcessResult, error) {
// Logica di dominio
if !hasEnoughFunds(p.Amount) {
// Restituisci errore usando la funzione helper generata
return nil, payment.MakeInsufficientFunds(
fmt.Errorf("saldo conto %d sotto l'importo richiesto %d", balance, p.Amount))
}
if isSystemOverloaded() {
// Restituisci errore per problema temporaneo del sistema
return nil, payment.MakeProcessingFailed(
fmt.Errorf("sistema di pagamento temporaneamente non disponibile"))
}
// Altra elaborazione...
}
Uso di Tipo di Errore Personalizzato (Quando Serve Contesto Aggiuntivo)
func (s *paymentService) Process(ctx context.Context, p *payment.ProcessPayload) (*payment.ProcessResult, error) {
// Logica di dominio
if !hasEnoughFunds(p.Amount) {
// Restituisci errore di dominio con contesto aggiuntivo
return nil, &payment.PaymentError{
Name: "insufficient_funds",
Message: "Saldo del conto troppo basso per la transazione",
Code: "FUNDS_001",
TransactionID: txID,
}
}
// Altra elaborazione...
}
Il layer di trasporto automaticamente:
- Mappa l’errore di dominio al codice di stato appropriato
- Formatta la risposta di errore secondo il protocollo
- Include eventuali headers o metadati specifici del protocollo
Best Practice
Prima il Dominio
- Progetta gli errori basandoti sui requisiti di business
- Usa terminologia di dominio nei messaggi di errore
- Includi contesto rilevante per il debug
Mappatura Coerente
- Usa codici di stato appropriati per ogni protocollo
- Mantieni mappature coerenti tra i servizi
- Documenta la logica delle mappature
Proprietà degli Errori
- Usa le proprietà degli errori (
Temporary(),Timeout(),Fault()) per indicare le caratteristiche - Considera l’implementazione di proprietà simili nei tipi di errore personalizzati
- Documenta come le proprietà influenzano il comportamento del client
- Usa le proprietà degli errori (
Documentazione
- Documenta sia il significato di dominio che il comportamento di trasporto
- Includi esempi di risposte di errore
- Spiega strategie di retry e gestione lato client
Conclusione
Separando gli errori di dominio dalla loro rappresentazione di trasporto, Goa ti permette di:
- Mantenere una logica di dominio pulita
- Fornire risposte di errore appropriate al protocollo
- Supportare più protocolli in modo coerente
- Scalare la gestione degli errori mentre la tua API cresce