Gli interceptor di errore sono un componente cruciale del sistema di middleware di Goa, che permettono di gestire gli errori in modo centralizzato e coerente. Questi interceptor possono intercettare, registrare, trasformare e potenzialmente recuperare dagli errori che si verificano durante l’esecuzione del servizio.
Un interceptor di errore base può registrare e potenzialmente trasformare gli errori:
func ErrorHandler(logger *log.Logger) func(goa.Endpoint) goa.Endpoint {
return func(e goa.Endpoint) goa.Endpoint {
return func(ctx context.Context, req interface{}) (interface{}, error) {
res, err := e(ctx, req)
if err != nil {
// Registra l'errore
logger.Printf("errore: %v", err)
// Opzionalmente, trasforma l'errore
if _, ok := err.(*CustomError); !ok {
err = &CustomError{
Original: err,
Message: "Si è verificato un errore interno",
}
}
}
return res, err
}
}
}
Gestione degli errori che si verificano durante la validazione dei dati:
func ValidationErrorHandler(e goa.Endpoint) goa.Endpoint {
return func(ctx context.Context, req interface{}) (interface{}, error) {
res, err := e(ctx, req)
if err != nil {
if ve, ok := err.(*ValidationError); ok {
return nil, &goa.ServiceError{
Name: "validation_error",
Message: ve.Error(),
Details: ve.Fields,
}
}
}
return res, err
}
}
Gestione degli errori specifici del dominio:
func BusinessErrorHandler(e goa.Endpoint) goa.Endpoint {
return func(ctx context.Context, req interface{}) (interface{}, error) {
res, err := e(ctx, req)
if err != nil {
switch be := err.(type) {
case *NotFoundError:
return nil, &goa.ServiceError{
Name: "not_found",
Message: be.Error(),
}
case *ConflictError:
return nil, &goa.ServiceError{
Name: "conflict",
Message: be.Error(),
}
}
}
return res, err
}
}
Gestione degli errori tecnici e di sistema:
func SystemErrorHandler(e goa.Endpoint) goa.Endpoint {
return func(ctx context.Context, req interface{}) (interface{}, error) {
res, err := e(ctx, req)
if err != nil {
if se, ok := err.(*SystemError); ok {
// Registra l'errore di sistema
log.Printf("errore di sistema: %v", se)
// Restituisci un errore generico all'utente
return nil, &goa.ServiceError{
Name: "internal_error",
Message: "Si è verificato un errore interno",
}
}
}
return res, err
}
}
Gli interceptor possono implementare logiche di recupero dagli errori:
func RetryHandler(maxRetries int) func(goa.Endpoint) goa.Endpoint {
return func(e goa.Endpoint) goa.Endpoint {
return func(ctx context.Context, req interface{}) (interface{}, error) {
var lastErr error
for i := 0; i < maxRetries; i++ {
res, err := e(ctx, req)
if err == nil {
return res, nil
}
// Verifica se l'errore è recuperabile
if !isRetryable(err) {
return nil, err
}
lastErr = err
// Attendi prima del prossimo tentativo
time.Sleep(backoff(i))
}
return nil, fmt.Errorf("massimi tentativi raggiunti: %v", lastErr)
}
}
}
func backoff(attempt int) time.Duration {
return time.Duration(math.Pow(2, float64(attempt))) * time.Second
}
func isRetryable(err error) bool {
// Implementa la logica per determinare se un errore è recuperabile
return false
}
Gli interceptor di errore possono essere combinati in una catena:
func main() {
// Crea gli interceptor di errore
validation := ValidationErrorHandler()
business := BusinessErrorHandler()
system := SystemErrorHandler()
retry := RetryHandler(3)
// Crea gli endpoint
endpoints := service.NewEndpoints(svc)
// Applica gli interceptor nell'ordine desiderato
endpoints.Use(system) // Prima gli errori di sistema
endpoints.Use(business) // Poi gli errori di business
endpoints.Use(validation) // Infine gli errori di validazione
endpoints.Use(retry) // Wrapper di retry esterno
}
Design
Implementazione
Considerazioni Generali
Gli interceptor di errore sono fondamentali per:
Implementa questi pattern considerando: