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.
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.
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:
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")
})
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.
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)
})
})
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)
})
})
Questa separazione delle responsabilità fornisce diversi vantaggi:
Indipendenza dal Protocollo
Gestione Coerente degli Errori
Documentazione Migliore
Ecco come funziona questa separazione nella pratica:
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...
}
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:
Prima il Dominio
Mappatura Coerente
Proprietà degli Errori
Temporary()
, Timeout()
, Fault()
) per indicare le caratteristicheDocumentazione
Separando gli errori di dominio dalla loro rappresentazione di trasporto, Goa ti permette di: