'Error wrapping in Ports&Adapters Architecture
Let's assume that we have a simple layered structure for a single API call
package main
import "fmt"
func selectQuery() error {
return fmt.Errorf("sql: invalid sql syntax")
}
// persistence layer
func FindUser() error {
err := selectQuery()
return fmt.Errorf("FindUser: %w", err)
}
// domain layer
func SetUserEmail() error {
err := FindUser()
return fmt.Errorf("SetUserEmail: %w", err)
}
// application layer
func UpdateUser() error {
err := SetUserEmail()
return fmt.Errorf("UpdateUser: %w", err)
}
func main() {
err := UpdateUser()
fmt.Println(err)
}
To be able to respond with correct HTTP Code you need to know what happened. To do that we define an specific error that we can check later on:
package main
import (
"errors"
"fmt"
)
func selectQuery() error {
return fmt.Errorf("sql: invalid sql syntax")
}
// persistence layer
var ErrUserNotFound = errors.New("user not found")
func FindUser() error {
err := selectQuery()
if true {
return ErrUserNotFound
}
return fmt.Errorf("FindUser: %w", err)
}
// domain layer
func SetUserEmail() error {
err := FindUser()
return fmt.Errorf("SetUserEmail: %w", err)
}
// application layer
func UpdateUser() error {
err := SetUserEmail()
return fmt.Errorf("UpdateUser: %w", err)
}
func main() {
err := UpdateUser()
if errors.Is(err, ErrUserNotFound) {
fmt.Println("404 Not found")
return
}
fmt.Println("500 Internal Server Error")
}
This error lives in the persistence layer. However, in Ports&Adapters software architecture, you are encouraged to create a Port that defines the methods that the adapter implements. This means that the ErrUserNotFound error no longer lives in the persistence (adapter) layer, but in the domain layer.
This is where I have an issue. How do you wrap the error so it makes the most sense?
// import ErrUserNotFound from domain package
func FindUser() eror {
err := selectQuery()
if true {
return fmt.Errorf("FindUser: %w: %w", domain.ErrUserNotFound, err)
// or
return fmt.Errorf("%w: %w", ErrUserNotFound, err)
// or
return ErrUserNotFound
}
return fmt.Errorf("FindUser: %w", err)
}
Solution 1:[1]
Check this article https://go.dev/blog/go1.13-errors On the controller layer you may use errors.Is and errors.As to understand how to answer the client. Additionally it possible to enrich errors with context, for example:
var _ error = (*Error)(nil)
type StatusError struct {
Status int
Err error
}
func (e *Error) Error() string {
return e.Err.Error()
}
func (e *Error) Is(t error) bool {
// Implement this method
return false
}
func WithStatus(status int, err error) error {
return &StatusError{
Status: http.StatusForbidden,
Err: e,
}
}
Dave Cheney - Dont Just Check Errors Handle Them Gracefully: https://www.youtube.com/watch?v=lsBF58Q-DnY
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | thrownew |
