Kosimba mabunga na Go ezali pete mpe flexible – nzokande structure moko te!
Esengelaki kozala pete, boye te? Zongisa kaka error
, ezingami na nsango, mpe kende liboso. Bon, simplicité wana ebongwanaka noki na mobulu lokola codebase na biso ezali kokola na ba forfaits mingi, ba développeurs mingi, mpe ba "fixes rapides" mingi oyo etikalaka wana libela. Na tango, ba journal etondi na "elongi te kosala boye" mpe "yango oyo ekanisamaki te", mpe moto moko te ayebi soki ezali faute ya usager, faute ya serveur, code ya buggy, to ezali kaka misalignement ya ba étoiles!
Mabunga esalemaka na ba messages oyo ezali na boyokani te. Paquet moko na moko ezali na ensemble na yango ya ba styles, ba constantes, to ba types ya erreur personnalisée. Ba code ya erreur ebakisami na ndenge ya arbitraire. Moyen facile te ya koyeba ba erreurs nini ekoki kozonga na fonction nini sans kotimola na mise en œuvre na yango!
Donc, nazuaki défi ya ko créer cadre ya sika ya erreur. Tozwaki mokano ya kokende na système structuré, centralisé oyo esalelaka ba codes ya espace ya kombo mpo na kosala ete mabunga ezala na tina, ezala traçable, mpe – oyo eleki ntina – kopesa biso kimia ya makanisi!
Oyo ezali lisolo ya lolenge nini tobandaki na lolenge ya pete ya kosimba mabunga, tozwaki mpenza kozanga bosepeli lokola mikakatano ekolaki, mpe na nsuka totongaki cadre na biso moko ya mabunga. Mikano ya bokeli, lolenge nini esalemi, mateya oyo tozwi, mpe mpo na nini ebongolaki lolenge na biso ya kotambwisa mabunga. Nazali na elikya ete ekomemela bino mpe mwa makanisi!
Go ezali na lolenge ya semba ya kosilisa mabunga: mabunga ezali kaka motuya. Libunga ezali kaka motuya oyo esalelaka interface error
na Error() string
. Na esika ya kobwaka exception mpe ko déranger flux ya exécution ya lelo, ba fonctions Go ezongisaka valeur error
pembeni ya ba résultats misusu. Na nsima, moto oyo abengi akoki kozwa ekateli ya ndenge ya kosimba yango: kotala motuya na yango mpo na kozwa ekateli, kozinga na bansango ya sika mpe na contexte, to kozongisa kaka libunga, kotika logique ya kosimba mpo na baboti oyo abengi.
Tokoki kosala type nionso error
na kobakisa méthode Error() string
likolo na yango. Bobongwani oyo epesaka nzela na ensemble moko na moko kolimbola mayele na yango moko ya kosilisa mabunga, mpe kopona oyo ekosala malamu mpo na bango. Yango mpe esangani malamu na philosophie ya Go ya composabilité, kosala ete ezala pete mpo na ko envelopper, ko extend, to ko personnaliser ba erreurs ndenge esengeli.
Momeseno oyo bato mingi basalaka ezali ya kozongisa motuya ya libunga oyo esalelaka interface error
mpe etikaka moto oyo abengi azwa ekateli ya kosala nini na nsima. Talá ndakisa moko ya momeseno:
func loadCredentials() (Credentials, error) { data, err := os.ReadFile("cred.json") if errors.Is(err, os.ErrNotExist) { return nil, fmt.Errorf("file not found: %w", err) } if err != nil { return nil, fmt.Errorf("failed to read file: %w", err) } cred, err := verifyCredentials(cred); if err != nil { return nil, fmt.Errorf("invalid credentials: %w", err) } return cred, nil }
Go epesaka mwa ndambo ya ba utilités mpo na kosala na ba erreurs:
errors.New()
mpe fmt.Errorf()
mpo na kobimisa mabunga ya pete.fmt.Errorf()
mpe verbe %w
.errors.Join()
esangisaka mabunga ebele na moko.errors.Is()
ekokani na libunga na motuya moko ya sikisiki, errors.As()
ekokani na libunga na lolenge moko ya sikisiki, mpe errors.Unwrap()
ezwi libunga oyo ezali na nsé.
Na misala, mbala mingi tomonaka ba modèles oyo:
errors.New()
to fmt.Errorf()
.Na mikolo ya ebandeli, lokola ba développeurs mingi ya Go, tolandaki ba pratiques communes ya Go mpe to garder traitement ya erreur minimal mais fonctionnel. Esalaki malamu mpenza mpo na mwa bambula.
Botia stacktrace na kosalelaka pkg/errors , paquet oyo eyebani mingi na tango wana.
Exporter ba constantes to ba variables pona ba erreurs spécifiques ya paquet.
Salelá errors.Is()
mpo na kotala soki ezali na mabunga ya sikisiki.
Envelopper ba erreurs na ba messages ya sika na contexte.
Mpo na mabunga ya API, tozali kolimbola mitindo ya mabunga mpe ba code na Protobuf enum.
Na kati na yango stacktrace na pkg/errors
Tosalelaki pkg/errors , ensemble oyo eyebanaki mingi mpo na kosilisa mabunga na ntango wana, mpo na kokɔtisa stacktrace na mabunga na biso. Yango esalisaki mingi mpo na kosala débogage, mpamba te epesaki biso nzela ya kolanda ebandeli ya mabunga na bisika ndenge na ndenge ya programɛ.
Mpo na kosala, kozinga, mpe kopalanganisa mabunga na stacktrace, tosalelaki misala lokola Newf()
, NewValuef()
, mpe Wrapf()
. Tala ndakisa ya bosaleli na biso ya ebandeli:
type xError struct { msg message, stack: callers(), } func Newf(msg string, args ...any) error { return &xError{ msg: fmt.Sprintf(msg, args...), stack: callers(), // 👈 stacktrace } } func NewValuef(msg string, args ...any) error { return fmt.Errorf(msg, args...) // 👈 no stacktrace } func Wrapf(err error, msg string, args ...any) error { if err == nil { return nil } stack := getStack(err) if stack == nil { stack = callers() } return &xError{ msg: fmt.Sprintf(msg, args...), stack: stack, } }
Ko exporter ba variables ya erreur
Paquet moko na moko na codebase na biso elimbolaki ba variables ya erreur na yango moko, mbala mingi na ba styles oyo eyokani te.
package database var ErrNotFound = errors.NewValue("record not found") var ErrMultipleFound = errors.NewValue("multiple records found") var ErrTimeout = errors.NewValue("request timeout")
package profile var ErrUserNotFound = errors.NewValue("user not found") var ErrBusinessNotFound = errors.NewValue("business not found") var ErrContextCancel = errors.NewValue("context canceled")
Ko vérifier ba erreurs na errors.Is()
pe ko envelopper na contexte ya kobakisa
res, err := repo.QueryUser(ctx, req) switch { case err == nil: // continue case errors.Is(database.NotFound): return nil, errors.Wrapf(ErrUserNotFound, "user not found (id=%v)", req.UserID) default: return nil, errors.Wrapf(ctx, "failed to query user (id=%v)", req.UserID) }
Yango esalisaki mpo na kopalanganisa mabunga na makambo mingi kasi mbala mingi ezalaki kobimisa maloba mingi, kosala ba doublons, mpe kozala polele mingi te na ba journals:
internal server error: failed to query user: user not found (id=52a0a433-3922-48bd-a7ac-35dd8972dfe5): record not found: not found
Kolimbola mabunga ya libanda na Protobuf
Mpo na ba API oyo etali libanda, to adoptaki modèle ya erreur basé na Protobuf oyo e inspiré naAPI Graph ya Meta :
message Error { string message = 1; ErrorType type = 2; ErrorCode code = 3; string user_title = 4; string user_message = 5; string trace_id = 6; map<string, string> details = 7; } enum ErrorType { ERROR_TYPE_UNSPECIFIED = 1; ERROR_TYPE_AUTHENTICATION = 2; ERROR_TYPE_INVALID_REQUEST = 3; ERROR_TYPE_RATE_LIMIT = 4; ERROR_TYPE_BUSINESS_LIMIT = 5; ERROR_TYPE_WEBHOOK_DELIVERY = 6; } enum ErrorCode { ERROR_CODE_UNSPECIFIED = 1 [(error_type = UNSPECIFIED)]; ERROR_CODE_UNAUTHENTICATED = 2 [(error_type = AUTHENTICATION)]; ERROR_CODE_CAMPAIGN_NOT_FOUND = 3 [(error_type = NOT_FOUND)]; ERROR_CODE_META_CHOSE_NOT_TO_DELIVER = 4 /* ... */; ERROR_CODE_MESSAGE_WABA_TEMPLATE_CAN_ONLY_EDIT_ONCE_IN_24_HOURS = 5; }
Ndenge wana ya kosala esalisaki na kobongisa mabunga, kasi na boumeli ya ntango, mitindo ya mabunga mpe ba code ebakisama kozanga mwango ya polele, oyo ememaki na bozangi boyokani mpe na ba doublons.
Mabunga esakolamaki bisika nyonso
gorm.ErrRecordNotFound
to user.ErrNotFound
to nionso mibale?
Enveloppement ya erreur aléatoire ememaki na ba journals inconsistents mpe arbitraires
unexpected gorm error: failed to find business channel: error received when invoking API: unexpected: context canceled
Standardisation moko te ememaki na traitement ya ba erreurs na ndenge ya mabe
Ata categorisation moko te esalaki que suivi ezala impossible
context.Canceled
annulé ekoki kozala comportement normal tango mosaleli akangi onglet ya navigateur, kasi ezali na ntina soki demande e annulé mpo query wana ezali malembe na ndenge ya pwasa.Mpo na kosilisa mikakatano oyo ezali se kokola, tozwaki mokano ya kotonga mayele ya mabunga ya malamu koleka nzinganzinga ya likanisi ya moboko ya ba code ya mabunga oyo ezali na esika moko mpe oyo ebongisami .
Error
na ensemble ya basalisi ya mobimba.Ba codes nionso ya erreur e définir na esika centralisé na structure ya espace ya kombo.
Salelá bisika ya nkombo mpo na kosala ba code ya mabunga ya polele, ya ntina, mpe oyo ekoki kobakisama. Ndakisa:
PRFL.USR.NOT_FOUND
mpo na "Mosaleli azwami te."FLD.NOT_FOUND
mpo na "Mokanda ya bopanzani ezwami te."DEPS.PG.NOT_FOUND
, elingi koloba "Enregistrement ezwami te na PostgreSQL."
Couche moko na moko ya service to bibliothèque esengeli kaka kozongisa ba codes ya espace ya kombo na yango moko .
gorm.ErrRecordNotFound
uta na bozangisi, liboke ya "base ya ba données" esengeli kozinga yango lokola DEPS.PG.NOT_FOUND
. Na sima, service "profile/utilisateur" esengeli e envelopper yango lisusu lokola PRFL.USR.NOT_FOUND
.
Ba erreurs nionso esengeli e mettre en œuvre interface Error
.
error
) na Error
s na biso ya kati.
Libunga ekoki kozinga libunga moko to ebele. Elongo, basali nzete.
[FLD.INVALID_ARGUMENT] invalid argument → [TPL.INVALID_PARAMS] invalid input params 1. [TPL.PARAM.EMPTY] name can not be empty 2. [TPL.PARAM.MALFORM] invalid format for param[2]
Esengaka ntango nyonso context.Context
. Ekoki ko attache contexte na erreur.
trace_id
te, mpe tozali na idée te esika ewuti.
Ntango mabunga etindami na ndelo ya mosala, kaka code ya mabunga ya nivo ya likolo nde emonisami.
Mpo na mabunga ya libanda, kobá kosalela Protobuf ErrorCode mpe ErrorType ya lelo.
Automap ba code ya erreur ya espace ya kombo na ba code ya Protobuf, ba code ya état HTTP, na ba balises.
ErrorCode
oyo ekokani , ErrorType
, état ya gRPC, état HTTP, mpe ba balises mpo na logging/metrics.Ezali na mwa ba forfaits ya moboko oyo esali moboko ya cadre na biso ya sika ya kosilisa mabunga.
connectly.ai/go/pkgs/
errors
: Paquet principal oyo e définir type Error
na ba codes.errors/api
: Mpo na kotinda mabunga na API ya liboso to ya libanda.errors/E
: Liboke ya mosungi oyo ekanamaki mpo na kosalela na bokɔtisi ba points.testing
: Komeka ba utilités pona kosala na ba erreurs ya espace ya kombo.
Error
na Code
Interface Error
ezali extension ya interface error
standard, na ba méthodes ya kobakisa pona kozongisa Code
. Code
moko esalemi lokola uint16
.
package errors // import "connectly.ai/go/pkgs/errors" type Error interface { error Code() Code } type Code struct { code uint16 } type CodeI interface { CodeDesc() CodeDesc } type GroupI interface { /* ... */ } type CodeDesc struct { /* ... */ }
Ba erreurs ya paquet errors/E
exporter ba codes nionso ya erreur na ba types communs
package E // import "connectly.ai/go/pkgs/errors/E" import "connectly.ai/go/pkgs/errors" type Error = errors.Error var ( DEPS = errors.DEPS PRFL = errors.PRFL ) func MapError(ctx context.Context, err error) errors.Mapper { /* ... */ } func IsErrorCode(err error, codes ...errors.CodeI) { /* ... */ } func IsErrorGroup(err error, groups ...errors.GroupI) { /* ... */ }
Ndakisa ya ba code ya erreur:
// dependencies → postgres DEPS.PG.NOT_FOUND DEPS.PG.UNEXPECTED // sdk → hash SDK.HASH.UNEXPECTED // profile → user PRFL.USR.NOT_FOUND PFRL.USR.UNKNOWN // profile → user → repository PRFL.USR.REPO.NOT_FOUND PRFL.USR.REPO.UNKNOWN // profile → auth PRFL.AUTH.UNAUTHENTICATED PRFL.AUTH.UNKNOWN PRFL.AUTH.UNEXPECTED
database
ya ba paquets :
package database // import "connectly.ai/go/pkgs/database" import "gorm.io/gorm" import . "connectly.ai/go/pkgs/errors/E" type DB struct { gorm: gorm.DB } func (d *DB) Exec(ctx context.Context, sql string, params ...any) *DB { tx := d.gorm.WithContext(ctx).Exec(sql, params...) return wrapTx(tx) } func (x *DB) Error(msgArgs ...any) Error { return wrapError(tx.Error()) // 👈 convert gorm error to 'Error' } func (x *DB) SingleRowError(msgArgs ...any) Error { if err := x.Error(); err != nil { return err } switch { case x.RowsAffected == 1: return nil case x.RowsAffected == 0: return DEPS.PG.NOT_FOUND.CallerSkip(1). New(x.Context(), formatMsgArgs(msgArgs)) default: return DEPS.PG.UNEXPECTED.CallerSkip(1). New(x.Context(), formatMsgArgs(msgArgs)) } }
pb/services/profile
ya forfait :
package profile // import "connectly.ai/pb/services/profile" // these types are generated from services/profile.proto type QueryUserRequest struct { BusinessId string UserId string } type LoginRequest struct { Username string Password string }
service/profile
ya forfait :
package profile import uuid "github.com/google/uuid" import . "connectly.ai/go/pkgs/errors/E" import l "connectly.ai/go/pkgs/logging/l" import profilepb "connectly.ai/pb/services/profile" // repository requests type QueryUserByUsernameRequest struct { Username string } // repository layer → query user func (r *UserRepository) QueryUserByUsernameAuth( ctx context.Context, req *QueryUserByUsernameRequest, ) (*User, Error) { if req.Username == "" { return PRFL.USR.REPO.INVALID_ARGUMENT.New(ctx, "empty request") } var user User sqlQuery := `SELECT * FROM "user" WHERE username = ? LIMIT 1` tx := r.db.Exec(ctx, sqlQuery, req.Username).Scan(&user) err := tx.SingleRowError() switch { case err == nil: return &user, nil case IsErrorCode(DEPS.PG.NOT_FOUND): return PRFL.USR.REPO.USER_NOT_FOUND. With(l.String("username", req.Username)) Wrap(ctx, "user not found") default: return PRFL.USR.REPO.UNKNOWN. Wrap(ctx, "failed to query user") } } // user service layer → query user func (u *UserService) QueryUser( ctx context.Context, req *profilepb.QueryUserRequest, ) (*profilepb.QueryUserResponse, Error) { // ... rr := QueryUserByUsernameRequest{ Username: req.Username } err := u.repo.QueryUserByUsername(ctx, rr) if err != nil { return nil, MapError(ctx, err). Map(PRFL.USR.REPO.NOT_FOUND, PRFL.USR.NOT_FOUND, "the user %q cannot be found", req.UserName, api.UserTitle("User Not Found"), api.UserMsg("The requested user id %q can not be found", req.UserId)). KeepGroup(PRFL.USR). Default(PRFL.USR.UNKNOWN, "failed to query user") } // ... return resp, nil } // auth service layer → login user func (a *AuthService) Login( ctx context.Context, req *profilepb.LoginRequest, ) (*profilepb.LoginResponse, *profilepb.LoginResponse, Error) { vl := PRFL.AUTH.INVALID_ARGUMENT.WithMsg("invalid request") vl.Vl(req.Username != "", "no username", api.Detail("username is required")) vl.Vl(req.Password != "", "no password", api.Detail("password is required")) if err := vl.ToError(ctx); err != nil { return err } hashpwd, err := hash.Hash(req.Password) if err != nil { return PRFL.AUTH.UNEXPECTED.Wrap(ctx, err, "failed to calc hash") } usrReq := profilepb.QueryUserByUsernameRequest{/*...*/} usrRes, err := a.userServiceClient.QueryUserByUsername(ctx, usrReq) if err != nil { return nil, MapError(ctx, err). Map(PRFL.USR.NOT_FOUND, PRFL.AUTH.UNAUTHENTICATED, "unauthenticated"). Default(PRFL.AUTH.UNKNOWN, "failed to query by username") } // ... }
Bon, ezali na ebele ya ba fonctions na ba concepts ya sika na code oyo ezali likolo. Toleka na bango litambe na litambe.
Ya liboso, importer errors/E
na nzela ya importation ya dot
Yango eko permettre yo osalela directement ba types communs lokola Error
na esika ya errors.Error
pe accès na ba codes na PRFL.USR.NOT_FOUND
na esika ya errors.PRFL.USR.NOT_FOUND
.
import . "connectly.ai/go/pkgs/errors/E"
Bosala mabunga ya sika na kosalelaka CODE.New()
Kanisa ete ozwi bosengi oyo ezali na ntina te, okoki kosala libunga ya sika na nzela ya:
err := PRFL.USR.INVALID_ARGUMENT.New(ctx, "invalid request")
PRFL.USR.INVALID_ARGUMENT
ezali Code
.Code
moko emonisaka ba méthodes lokola New()
to Wrap()
pona kosala erreur ya sika.New()
ezuaka context.Context
lokola argument ya liboso, elandi na message na ba arguments optionnels.
Imprimer yango na fmt.Print(err)
:
[PRFL.USR.INVALID_ARGUMENT] invalid request
to na fmt.Printf("%+v")
mpo na komona makambo mosusu:
[PRFL.USR.INVALID_ARGUMENT] invalid request connectly.ai/go/services/profile.(*UserService).QueryUser /usr/i/src/go/services/profile/user.go:1234 connectly.ai/go/services/profile.(*UserRepository).QueryUser /usr/i/src/go/services/profile/repo/user.go:2341
Zinga libunga na kati ya libunga ya sika na kosalelaka CODE.Wrap()
dbErr := DEPS.PG.NOT_FOUND.Wrap(ctx, gorm.ErrRecordNotFound, "not found") usrErr := PRFL.USR.NOT_FOUND.Wrap(ctx, dbErr, "user not found")
ekobimisa sortie oyo na fmt.Print(usrErr)
:
[PRFL.USR.NOT_FOUND] user not found → [DEPS.PG.NOT_FOUND] not found → record not found
to na fmt.Printf("%+v", usrErr)
[PRFL.USR.NOT_FOUND] user not found → [DEPS.PG.NOT_FOUND] not found → record not found connectly.ai/go/services/profile.(*UserService).QueryUser /usr/i/src/go/services/profile/user.go:1234
Stacktrace ekouta na Error
ya kati . Soki ozali kokoma fonction ya mosungi, okoki kosalela CallerSkip(skip)
mpo na koleka ba cadres:
func mapUserError(ctx context.Context, err error) Error { switch { case IsErrorCode(err, DEPS.PG.NOT_FOUND): return PRFL.USR.NOT_FOUND.CallerSkip(1).Wrap(ctx, err, "...") default: return PRFL.USR.UNKNOWN.CallerSkip(1).Wrap(ctx, err, "...") } }
Bakisa contexte na erreur na kosalelaka With()
.With(l.String(...))
.logging/l
ezali ensemble ya mosungi mpo na kobimisa ba fonctions ya sukali mpo na logging.l.String("flag", flag)
zongisa Tag{String: flag}
mpe l.UUID("user_id, userID)
zongisa Tag{Stringer: userID}
. import l "connectly.ai/go/pkgs/logging/l" usrErr := PRFL.USR.NOT_FOUND. With(l.UUID("user_id", req.UserID), l.String("flag", flag)). Wrap(ctx, dbErr, "user not found")
Ba balises ekoki kobima na fmt.Printf("%+v", usrErr)
:
[PRFL.USR.NOT_FOUND] user not found {"user_id": "81febc07-5c06-4e01-8f9d-995bdc2e0a9a", "flag": "ABRW"} → [DEPS.PG.NOT_FOUND] not found {"a number": 42} → record not found
Bakisa contexte na ba erreurs directement na kati ya New()
, Wrap()
, to MapError()
:
Na leverage fonction l.String()
na famille na yango, New()
na ba fonctions ya ndenge moko ekoki ko détecter na mayele ba balises parmi ba arguments ya formatage. Esengeli te kokɔtisa misala ndenge na ndenge.
err := INF.HEALTH.NOT_READY.New(ctx, "service %q is not ready (retried %v times)", req.ServiceName, l.String("flag", flag) countRetries, l.Number("count", countRetries), )
ekobimisa:
[INF.HEALTH.NOT_READY] service "magic" is not ready (retried 2 times) {"flag": "ABRW", "count": 2}
Error0
, VlError
, ApiError
Actuellement, ezali na 3 types oyo e mettre en œuvre ba interfaces Error
. Okoki kobakisa mitindo mosusu soki esengeli. Moko na moko akoki kozala na structure ekeseni, na ba méthodes personnalisées pona ba besoins spécifiques.
Error
ezali extension ya interface ya error
standard ya Go
type Error interface { error Code() Message() Fields() []tags.Field StackTrace() stacktrace.StackTrace _base() *base // a private method }
Ezali na méthode privée mpo na kosala que to mettre en œuvre par accident te ba types ya sika Error
libanda ya ensemble ya errors
. Tokoki (to tokoki te) kolongola epekiseli wana na mikolo mizali koya ntango tokokutana na ndenge ya kosalela mingi.
Pourquoi tosalelaka kaka interface error
standard te pe tosalelaka assertion ya type?
Po tolingi kokabola entre ba erreurs ya troisième partie na ba erreurs na biso ya kati. Ba couches nionso na ba paquets na ba codes internes na biso esengeli toujours ezongisa Error
. Na lolenge oyo tokoki koyeba na bozangi likama ntango nini tosengeli kobongola mabunga ya bato ya misato, mpe ntango nini tosengeli kaka kosala na ba codes ya mabunga na biso ya kati.
Ezali mpe kosala ndelo kati ya ba paquets oyo ebongwani mpe ba paquets oyo ekendeki naino te. Kozonga na bosolo, tokoki te kaka kosakola lolenge ya sika, koningisa lingenda ya maji, koloba na mongongo ya nse mpo na kosɛnga sort , mpe na nsima bamilio nyonso ya milɔngɔ ya code ebongwanaka na ndenge ya maji mpe esalaka malamu kozanga mpasi! Te, avenire wana ezali naino awa te. Ekoki koya mokolo mosusu, kasi mpo na sikoyo, tosengeli kaka kosala migration ya ba forfaits na biso moko na moko.
Error0
ezali lolenge ya Error
par défaut
Ba code ya erreur mingi ekobimisa valeur Error0
. Ezali na base
mpe sous-erreur optionnelle. Okoki kosalela NewX()
mpo na kozongisa struct *Error0
ya solo na esika ya interface Error
, kasi esengeli ozala na bokebi .
type Error0 struct { base err error } var errA: Error = DEPS.PG.NOT_FOUND.New (ctx, "not found") var errB: *Error0 = DEPS.PG.NOT_FOUND.NewX(ctx, "not found")
base
ezali structure commune oyo ekabolami na mise en œuvre nionso Error
, pona kopesa fonctionnalité commune: Code()
, Message()
, StackTrace()
, Fields()
, pe ebele.
type base struct { code Code msg string kv []tags.Field stack stacktrace.StackTrace }
VlError
ezali mpo na mabunga ya bondimi
Ekoki kozala na ba sous-erreurs ebele, pe kopesa ba méthodes ya bien pona kosala na ba aides ya validation.
type VlError struct { base errs []error }
Okoki kosala VlError
oyo ekokani na Error
mosusu :
err := PRFL.USR.INVALID_ARGUMENT.New(ctx, "invalid request")
To sala VlBuilder
, bakisa ba erreurs na yango, sima convertir yango na VlError
:
userID, err0 := parseUUID(req.UserId) err1 := validatePassword(req.Password) vl := PRFL.USR.INVALID_ARGUMENT.WithMsg("invalid request") vl.Add(err0, err1) vlErr := vl.ToError(ctx)
Mpe kotia ba paires ya ba clés/valeur lokola momesano:
vl := PRFL.USR.INVALID_ARGUMENT. With(l.Bool("testingenv", true)). WithMsg("invalid request") userID, err0 := parseUUID(req.UserId) err1 := validatePassword(req.Password) vl.Add(err0, err1) vlErr := vl.ToError(ctx, l.String("user_id", req.UserId))
Kosalela fmt.Printf("%+v", vlErr)
ekobimisa:
[PRFL.USR.INVALID_ARGUMENT] invalid request {"testingenv": true, "user_id": "A1234567890"}
ApiError
ezali adaptateur mpo na kosala migration ya ba erreurs ya API
Liboso, tosalelaki struct api.Error
ekeseni mpo na kozongisa mabunga ya API na ba clients ya liboso mpe ya libanda. Ezali na ErrorType
lokola ErrorCode
ndenge tolobelaki liboso .
package api import errorpb "connectly.ai/pb/models/error" // Deprecated type Error struct { pbType errorpb.ErrorType pbCode errorpb.ErrorCode cause error msg string usrMsg string usrTitle string // ... }
Lolenge oyo esili sikawa kosalelama. Na esika na yango, tokosakola ba cartographies nionso ( ErrorType
, ErrorCode
, code gRPC, code HTTP) na esika ya centraliser, mpe tokobongola yango na ba frontières correspondantes. Nako lobela na ntina ya bosakoli ya code na eteni oyo ekolanda .
Mpo na kosala migration na cadre ya erreur ya espace ya kombo ya sika, tobakisi esika ya kombo ya tango moke ZZZ.API_TODO
. ErrorCode
nionso ekomaka code ya ZZZ.API_TODO
.
ZZZ.API_TODO.UNEXPECTED ZZZ.API_TODO.INVALID_REQUEST ZZZ.API_TODO.USERNAME_ ZZZ.API_TODO.META_CHOSE_NOT_TO_DELIVER ZZZ.API_TODO.MESSAGE_WABA_TEMPLATE_CAN_ONLY_EDIT_ONCE_IN_24_HOURS
Na ApiError
esalemi lokola adaptateur. Misala nyonso oyo ezongisaka liboso *api.Error
ebongwanaki mpo na kozongisa Error
(esalelami na *ApiError
) na esika na yango.
package api import . "connectly.ai/go/pkgs/errors/E" // previous func FailPreconditionf(err error, msg string, args ...any) *Error { return &Error{ pbType: ERROR_TYPE_FAILED_PRECONDITION, pbCode: ERROR_CODE_MESSAGE_WABA_TEMPLATE_CAN_ONLY_EDIT_ONCE_IN_24_HOURS, cause: err, msg: fmt.Sprintf(msg, args...) } } // current: this is deprecated, and serves and an adapter func FailPreconditionf(err error, msg string, args ...any) *Error { ctx := context.TODO() return ZZZ.API_TODO.MESSAGE_WABA_TEMPLATE_CAN_ONLY_EDIT_ONCE_IN_24_HOURS. CallerSkip(1). // correct the stacktrace by 1 frame Wrap(ctx, err, msg, args...) }
Tango migration nionso esalemi, usage ya liboso:
wabaErr := verifyWabaTemplateStatus(tpl) apiErr := api.FailPreconditionf(wabaErr, "template cannot be edited"). WithErrorCode(ERROR_CODE_MESSAGE_WABA_TEMPLATE_CAN_ONLY_EDIT_ONCE_IN_24_HOURS). WithUserMsg("According to WhatsApp, the message template can be only edited once in 24 hours. Consider creating a new message template instead."). ErrorOrNil()
esengeli kokoma:
CPG.TPL.EDIT_ONCE_IN_24_HOURS.Wrap( wabaErr, "template cannot be edited", api.UserMsg("According to WhatsApp, the message template can be only edited once in 24 hours. Consider creating a new message template instead."))
Simbá ete ErrorCode
euti na ndenge ya implicit na code ya esika ya nkombo ya kati. Esengeli te kopesa yango polele mbala nyonso. Kasi ndenge nini ko déclarer relation entre ba codes? Tokolimbola yango na eteni oyo ekolanda.
Na esika oyo, oyebi déjà ndenge ya kosala ba erreurs ya sika na ba codes oyo ezali. Ezali tango ya kolimbola na ntina ya ba codes mpe ndenge nini kobakisa ya sika.
Code
moko esalemi lokola valeur uint16
, oyo ezali na présentation ya molongo oyo ekokani.
type Code struct { code: uint16 } fmt.Printf("%q", DEPS.PG.NOT_FOUND) // "DEPS.PG.NOT_FOUND"
Mpo na kobomba ba chaînes wana, ezali na array ya CodeDesc
nionso oyo ezali :
const MaxCode = 321 // 👈 this value is generated var allCodes [MaxCode]CodeDesc type CodeDesc { c int // 42 code string // DEPS.PG.NOT_FOUND api APICodeDesc } type APICodeDesc { ErrorType errorpb.ErrorType ErrorCode errorpb.ErrorCode HttpCode int DefMessage string UserMessage string UserTitle string }
Tala ndenge ba codes esakolamaka:
var DEPS deps // dependencies var PRFL prfl // profile var FLD fld // flow document type deps struct { PG pg // postgres RD rd // redis } // tag:postgres type pg struct { NOT_FOUND Code0 // record not found CONFLICT Code0 // record already exist MALFORM_SQL Code0 } // tag:profile type PRFL struct { REPO prfl_repo USR usr AUTH auth } // tag:profile type prfl_repo struct { NOT_FOUND Code0 // internal error code INVALID_ARGUMENT VlCode // internal error code } // tag:usr type usr struct { NOT_FOUND Code0 `api-code:"USER_NOT_FOUND"` INVALID_ARGUMENT VlCode `api-code:"INVALID_ARGUMENT"` DISABlED_ACCOUNT Code0 `api-code:"DISABLED_ACCOUNT"` } // tag:auth type auth struct { UNAUTHENTICATED Code0 `api-code:"UNAUTHENTICATED"` PERMISSION_DENIED Code0 `api-code:"PERMISSION_DENIED"` }
Sima ya kosakola ba codes ya sika, esengeli osala script ya génération:
run gen-errors
Code oyo esalemi ekozala boye:
// Code generated by error-codes. DO NOT EDIT. func init() { // ... PRFL.AUTH.UNAUTHENTICATED = Code0{Code{code: 143}} PRFL.AUTH.PERMISSION_DENIED = Code0{Code{code: 144}} // ... allCodes[143] = CodeDesc{ c: 143, code: "PRFL.AUTH.UNAUTHENTICATED", tags: []string{"auth", "profile"}, api: APICodeDesc{ ErrorType: ERROR_TYPE_UNAUTHENTICATED, ErrorCode: ERROR_CODE_UNAUTHENTICATED, HTTPCode: 401, DefMessage: "Unauthenticated error", UserMessage: "You are not authenticated.", UserTitle: "Unauthenticated error", })) }
Lolenge moko na moko Error
ezali na lolenge ya Code
oyo ekokani na yango
Omituna naino ndenge nini PRFL.USR.NOT_FOUND.New()
esali *Error0
mpe PRFL.USR.INVALID_ARGUMENTS.New()
esali *VlError
? Ezali mpo basalelaka mitindo ya code ndenge na ndenge.
Mpe lolenge moko na moko Code
ezongisaka lolenge ya Error
ekeseni, moko na moko ekoki kozala na ba méthodes na yango ya likolo:
type Code0 struct { Code } type VlCode struct { Code } func (c Code0) New(/*...*/) Error { return &Error0{/*...*/} } func (c VlCode) New(/*...*/) Error { return &VlError{/*...*/} } // extra methods on VlCode to create VlBuilder func (c VlCode) WithMsg(msg string, args ...any) *VlBuilder {/*...*/} type VlBuilder struct { code VlCode msg string args []any } func (b *VlBuilder) ToError(/*...*/) Error { return &VlError{Code: code, /*...*/ } }
Salelá api-code
mpo na kotya elembo na ba code oyo ezali mpo na API ya libanda
Esengeli kosalela code ya erreur ya espace ya kombo na kati.
Mpo na kosala ete code ezala mpo na kozongisa na API HTTP ya libanda, osengeli kotya elembo na yango na api-code
. Motuya ezali errorpb.ErrorCode
oyo ekokani.
Soki code ya erreur e marqué na api-code
te , ezali code ya kati mpe ekolakisama lokola Internal Server Error
générique .
Boyeba ete PRFL.USR.NOT_FOUND
ezali code ya libanda, nzokande PRFL.USR.REPO.NOT_FOUND
ezali code ya kati.
Kosakola cartographie kati ya ErrorCode
, ErrorType
, mpe ba code ya gRPC/HTTP na protobuf na kosalelaka option enum:
// error/type.proto ERROR_TYPE_PERMISSION_DENIED = 707 [(error_type_detail_option) = { type: "PermissionDeniedError", grpc_code: PERMISSION_DENIED, http_code: 403, // Forbidden message: "permission denied", user_title: "Permission denied", user_message: "The caller does not have permission to execute the specified operation.", }]; // error/code.proto ERROR_CODE_DISABlED_ACCOUNT = 70020 [(error_code_detail_option) = { error_type: ERROR_TYPE_DISABlED_ACCOUNT, grpc_code: PERMISSION_DENIED, http_code: 403, // Forbidden message: "account is disabled", user_title: "Account is disabled", user_message: "Your account is disabled. Please contact support for more information.", }];
Ba code UNEXPECTED
mpe UNKNOWN
Couche moko na moko ezalaka mingi mingi na 2 code générique UNEXPECTED
na UNKNOWN
. Bazali kokokisa mwa mikano ekeseni:
UNEXPECTED
esalelamaka mpo na mabunga oyo esengeli kosalema ata moke te.UNKNOWN
esalelamaka mpo na mabunga oyo esimbami polele te.Ntango ozali kozwa libunga oyo ezongisami uta na fonction, esengeli osimba yango: kobongola mabunga ya bato ya misato na mabunga ya esika ya nkombo ya kati mpe ba code ya libunga ya kosala carte uta na ba couches ya kati kino na ba couches ya libanda.
Bobongola mabunga ya bato ya misato na mabunga ya esika ya nkombo ya kati
Ndenge nini osimbaka mabunga etali: nini ensemble ya bato mosusu ezongisaka mpe nini application na yo esengeli na yango. Ndakisa, ntango ozali kosimba mabunga ya base de données to ya API ya libanda:
switch { case errors.Is(err, sql.ErrNoRows): // map a database "no rows" error to an internal "not found" error return nil, PRFL.USR.NOT_FOUND.Wrap(ctx, err, "user not found") case errors.Is(err, context.DeadlineExceeded): // map a context deadline exceeded error to a timeout error return nil, PRFL.USR.TIMEOUT.Wrap(ctx, err, "query timeout") default: // wrap any other error as unknown return nil, PRFL.USR.UNKNOWN.Wrap(ctx, err, "unexpected error") }
Kosalela basalisi mpo na mabunga ya esika ya nkombo ya kati
IsErrorCode(err, CODES...)
: Ezali kotala soki libunga ezali na moko ya ba code oyo elakisami.IsErrorGroup(err, GROUP)
: Zongisa solo soki libunga ezali ya lisanga ya bokotisi.
Motindo ya kosalela oyo momeseno:
user, err := queryUser(ctx, userReq) switch { case err == nil: // continue case IsErrorCode(PRL.USR.REPO.NOT_FOUND): // check for specific error code and convert to external code // and return as HTTP 400 Not Found return nil, PRFL.USR.NOT_FOUND.Wrap(ctx, err, "user not found") case IsGroup(PRL.USR): // errors belong to the PRFL.USR group are returned as is return nil, err default: return nil, PRL.USR.UNKNOWN.Wrap(ctx, err, "failed to query user") }
MapError()
mpo na kokoma code ya cartographie na pete:
Lokola kosala cartographie ya ba codes ya erreur ezali modèle oyo esalemaka mingi, ezali na mosungi MapError()
mpo na kosala code ya kokoma noki. Code oyo ezali likolo ekoki kokomama lisusu lokola:
user, err := queryUser(ctx, userReq) if err != nil { return nil, MapError(ctx, err). Map(PRL.USR.REPO.NOT_FOUND, PRFL.USR.NOT_FOUND, "user not found"). KeepGroup(PRF.USR). Default(PRL.USR.UNKNOWN, "failed to query user") }
Okoki kosala formatage ya ba arguments pe kobakisa ba paires ya clé/valeur ndenge ezalaka:
return nil, MapError(ctx, err). Map(PRL.USR.REPO.NOT_FOUND, PRFL.USR.NOT_FOUND, "user %v not found", username, l.String("flag", flag)). KeepGroup(PRF.USR). Default(PRL.USR.UNKNOWN, "failed to query user", l.Any("retries", retryCount))
Error
s Komeka ezali na ntina mingi mpo na base ya code nyonso ya makasi. Cadre epesaka ba aides spécialisés lokola ΩxError()
pona kosala que kokoma pe ko asserter ba conditions ya erreur na ba tests ezala facile pe expressive.
// 👉 return true if the error contains the message ΩxError(err).Contains("not found") // 👉 return true if the error does not contain the message ΩxError(err).NOT().Contains("not found")
Ezali na mayele mosusu mingi, mpe okoki mpe kokanga yango na minyɔlɔlɔ:
ΩxError(err). MatchCode(DEPS.PG.NOT_FOUND). // match any code in top or wrapped errors TopErrorMatchCode(PRFL.TPL.NOT_FOUND) // only match code from the top error MatchAPICode(API_CODE.WABA_TEMPLATE_NOTE_FOUND). // match errorpb.ErrorCode MatchExact("exact message to match")
Mpo na nini kosalela ba méthodes na esika ya Ω(err).To(testing.MatchCode())
?
Po ba méthodes ezalaka plus découvertes. Ntango ozali kokutana na ebele ya misala lokola testing.MatchValues()
, ezali mpasi mpo na koyeba oyo ekosala na Error
s mpe oyo ekosala te. Na ba méthodes, okoki kaka kokoma point .
, mpe IDE na yo ekotanga ba méthodes nionso oyo ezali oyo ebongisami spécifiquement pona ko asserter Error
s.
Cadre ezali kaka ndambo ya lisolo. Kokoma code? Yango nde eteni ya pete. Mokakatano ya solosolo ebandaka ntango osengeli komema yango na codebase moko monene, ya bomoi epai wapi ebele ya ba ingénieurs bazali kotindika mbongwana mokolo na mokolo, bakiliya bazali kozela ete makambo nyonso esalaka malamu mpenza, mpe système ekoki kaka te kotika kotambola.
Migration eyaka na responsabilité. Ezali mpo na kokabola na bokebi suki biteni mikemike ya code, kosala mbongwana ya mikemike na mbala moko, kobuka tonne ya mimekano na nzela. Na sima ko inspecter manuellement pe ko bongisa bango moko moko, kosangisa na filiale principale, ko déployer na production, kotala ba journals na ba alertes. Kozongela yango mbala na mbala...
Talá mwa batoli mpo na kokende na mikili mosusu oyo toyekolaki na nzela:
Bandá na koluka mpe kozongisa: Bandá na kozongisa ba modèles ya kala na cadre ya sika. Bongisa ba problèmes nionso ya compilation oyo ebimaka na processus oyo.
Ndakisa, zongisa error
nyonso na kati ya liboke oyo na Error
.
type ProfileController interface { LoginUser(req *LoginRequest) (*LoginResponse, error) QueryUser(req *QueryUserRequest) (*QueryUserResponse, error) }
Code ya sika ekozala boye:
import . "connectly.ai/go/pkgs/errors" type ProfileController interface { LoginUser(req *LoginRequest) (*LoginResponse, Error) QueryUser(req *QueryUserRequest) (*QueryUserResponse, Error) }
Migrate forfait moko na mbala moko: Bandá na ba paquets ya niveau ya se mpe sala nzela na yo likolo. Na ndenge wana, okoki kosala ete ba paquets ya niveau ya se ezala mobimba migré avant ya kokende na oyo ya niveau ya likolo.
Bakisa ba tests unitaires oyo ezangi: Soki biteni ya codebase ezangi ba tests, bakisa yango. Soki ozali na elikya te na mbongwana na yo, bakisa ba tests mosusu. Bazali na litomba mpo na kosala ete mbongwana na yo ebuka te misala oyo ezali.
Soki paquet na yo etali kobenga ba paquets ya niveau ya likolo: Tala kobongola ba fonctions oyo etali yango na DEPRECATED sima bakisa ba fonctions ya sika na type ya sika Error
.
Kanisa ete ozali kosala migration ya ensemble ya base de données, oyo ezali na méthode Transaction()
:
package database func (db *DB) Transaction(ctx context.Context, fn func(tx *gorm.DB) error) error { return db.gorm.Transaction(func(tx *gorm.DB) error { return fn(tx) }) }
Mpe esalelamaka na kati ya ensemble ya service ya mosaleli:
err = s.DB(ctx).Transaction(func(tx *database.DB) error { user, usrErr := s.repo.CreateUser(ctx, tx, user) if usrErr != nil { return usrErr } }
Lokola ozali kosala migration ya liboso forfait database
, kotika user
mpe ba douzaines ya ba paquets mosusu lokola yango. Libenga s.repo.CreateUser()
ezongisaka kaka lolenge ya error
ya kala ntango lolenge ya Transaction()
esengeli kozongisa lolenge ya Error
ya sika. Okoki kobongola lolenge ya Transaction()
na DEPRECATED
mpe kobakisa lolenge ya sika TransactionV2()
:
package database // DEPRECATED: use TransactionV2 instead func (db *DB) Transaction_DEPRECATED(ctx context.Context, fn func(tx *gorm.DB) error) error { return db.gorm.Transaction(func(tx *gorm.DB) error { return fn(tx) }) } func (db *DB) TransactionV2(ctx context.Context, fn func(tx *gorm.DB) error) Error { err := db.gorm.Transaction(func(tx *gorm.DB) error { return fn(tx) }) return adaptToErrorV2(err) }
Bakisa ba code ya sika ya erreur tango ozali kokende : Tango okutani na erreur oyo ekoti te na oyo ezali, bakisa code ya sika. Yango ekosalisa yo otonga ensemble complète ya ba codes ya erreur na tango. Ba codes oyo ewutaka na ba forfaits misusu ezalaka toujours lokola ba références.
Kosimba mabunga na Go ekoki koyoka pete na ebandeli —zongisela kaka error
mpe kokende liboso. Kasi lokola codebase na biso ekolaki, bopete wana ebongwanaki mobulu ya kokangama ya ba journals vagues, manipulation inconsistente, mpe ba sessions ya débogage oyo ezangi nsuka.
Na kozonga sima mpe kokanisa lisusu lolenge nini tosimbaka mabunga, totongaki système oyo esalaka mpo na biso, kasi contre biso te. Ba code ya esika ya nkombo oyo esalemi na esika ya nkombo mpe oyo ebongisami epesaka biso polele, nzokande bisaleli mpo na kosala cartographie, kozinga, mpe komeka mabunga esalaka ete bomoi na biso ezala pete. Na esika ya kobɛta mai na mbu ya mabaya, tozali sikawa na mabunga ya ntina, oyo ekoki kolandama oyo ezali koyebisa biso nini ezali mabe mpe esika nini tosengeli kotala.
Cadre oyo ezali kaka te mpo na kosala ete code na biso ezala pɛto; ezali mpo na kobomba ntango, kokitisa kozanga bosepeli, mpe kosalisa biso tómilengela mpo na makambo oyo eyebani te. Ezali kaka ebandeli ya mobembo — tozali naino kozwa ba modèles mingi — kasi mbano ezali système oyo ekoki na lolenge moko to mosusu komema kimia ya makanisi na kosimba mabunga. Na elikya, ekoki kobimisa mwa makanisi mpo na misala na yo mpe! 😊
Nazali Oliver Nguyen. Mosali ya logiciel oyo asalaka mingi na Go na JavaScript. Nasepelaka koyekola mpe komona lolenge moko ya malamu koleka ya ngai moko mokolo na mokolo. Tango mosusu spin off ba projets ya sika ya source ouverte. Kabola boyebi mpe makanisi na boumeli ya mobembo na ngai.
Poste yango ebimi pe na blog.connectly.ai na olivernguyen.io 👋