From bbb9abb46d9ce3eb1ac43a281644ed599f9e5676 Mon Sep 17 00:00:00 2001 From: Str1m Date: Fri, 11 Apr 2025 12:20:43 +0300 Subject: [PATCH 1/7] add client, rework interfeces --- internal/client/db/db.go | 1 + internal/client/db/postgres/client.go | 1 + internal/client/db/postgres/postgres.go | 1 + 3 files changed, 3 insertions(+) create mode 100644 internal/client/db/db.go create mode 100644 internal/client/db/postgres/client.go create mode 100644 internal/client/db/postgres/postgres.go diff --git a/internal/client/db/db.go b/internal/client/db/db.go new file mode 100644 index 0000000..3a49c63 --- /dev/null +++ b/internal/client/db/db.go @@ -0,0 +1 @@ +package db diff --git a/internal/client/db/postgres/client.go b/internal/client/db/postgres/client.go new file mode 100644 index 0000000..bf560be --- /dev/null +++ b/internal/client/db/postgres/client.go @@ -0,0 +1 @@ +package postgres diff --git a/internal/client/db/postgres/postgres.go b/internal/client/db/postgres/postgres.go new file mode 100644 index 0000000..bf560be --- /dev/null +++ b/internal/client/db/postgres/postgres.go @@ -0,0 +1 @@ +package postgres From 2128aae560d642a85e24e05f9fe8f758ac212187 Mon Sep 17 00:00:00 2001 From: Str1m Date: Fri, 11 Apr 2025 12:54:51 +0300 Subject: [PATCH 2/7] add transactions --- go.mod | 2 + go.sum | 16 ++- internal/api/user/service.go | 13 +-- internal/app/service_provider.go | 105 ++++++++++++------ internal/client/db/db.go | 53 +++++++++ internal/client/db/postgres/client.go | 62 +++++++++++ internal/client/db/postgres/postgres.go | 84 ++++++++++++++ internal/client/db/transaction/transaction.go | 64 +++++++++++ internal/service/service.go | 13 ++- internal/service/user/service.go | 17 +-- internal/storage/storage.go | 13 ++- internal/storage/users/model/user.go | 22 ++-- internal/storage/users/postgres/repository.go | 74 +++++++++--- 13 files changed, 453 insertions(+), 85 deletions(-) create mode 100644 internal/client/db/transaction/transaction.go diff --git a/go.mod b/go.mod index b9d737b..953466e 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,10 @@ go 1.24.1 require ( github.com/Masterminds/squirrel v1.5.4 github.com/fatih/color v1.18.0 + github.com/georgysavva/scany/v2 v2.1.4 github.com/jackc/pgx/v5 v5.7.4 github.com/joho/godotenv v1.5.1 + github.com/pkg/errors v0.9.1 golang.org/x/crypto v0.37.0 google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.36.6 diff --git a/go.sum b/go.sum index c2dd7c6..0961648 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,16 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs= +github.com/cockroachdb/cockroach-go/v2 v2.2.0/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/georgysavva/scany/v2 v2.1.4 h1:nrzHEJ4oQVRoiKmocRqA1IyGOmM/GQOEsg9UjMR5Ip4= +github.com/georgysavva/scany/v2 v2.1.4/go.mod h1:fqp9yHZzM/PFVa3/rYEC57VmDx+KDch0LoqrJzkvtos= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -21,19 +27,25 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= +github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= diff --git a/internal/api/user/service.go b/internal/api/user/service.go index 62bb833..1f92a3e 100644 --- a/internal/api/user/service.go +++ b/internal/api/user/service.go @@ -1,24 +1,17 @@ package user import ( - "context" + "github.com/Str1m/auth/internal/service" - modelService "github.com/Str1m/auth/internal/model" desc "github.com/Str1m/auth/pkg/auth_v1" ) -type Service interface { - Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) - Get(ctx context.Context, id int64) (*modelService.User, error) - Update(ctx context.Context, id int64, name *string, email *string) error - Delete(ctx context.Context, id int64) error -} type Implementation struct { desc.UnimplementedAuthV1Server - userService Service + userService service.Service } -func NewImplementation(userService Service) *Implementation { +func NewImplementation(userService service.Service) *Implementation { return &Implementation{ userService: userService, } diff --git a/internal/app/service_provider.go b/internal/app/service_provider.go index fb1bba5..5718e68 100644 --- a/internal/app/service_provider.go +++ b/internal/app/service_provider.go @@ -2,6 +2,11 @@ package app import ( "context" + "github.com/Str1m/auth/internal/client/db" + dbPG "github.com/Str1m/auth/internal/client/db/postgres" + "github.com/Str1m/auth/internal/client/db/transaction" + "github.com/Str1m/auth/internal/service" + "github.com/Str1m/auth/internal/storage" "log" "log/slog" "os" @@ -11,10 +16,8 @@ import ( "github.com/Str1m/auth/internal/closer" "github.com/Str1m/auth/internal/config/env" "github.com/Str1m/auth/internal/lib/logger/handlers/slogpretty" - modelService "github.com/Str1m/auth/internal/model" "github.com/Str1m/auth/internal/service/user" "github.com/Str1m/auth/internal/storage/users/postgres" - "github.com/jackc/pgx/v5/pgxpool" ) const ( @@ -23,20 +26,19 @@ const ( envProd = "prod" ) -type Storage interface { - Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) - Get(ctx context.Context, id int64) (*modelService.User, error) - Update(ctx context.Context, id int64, name, email *string) error - Delete(ctx context.Context, id int64) error -} - -type Service interface { - Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) - Get(ctx context.Context, id int64) (*modelService.User, error) - Update(ctx context.Context, id int64, name *string, email *string) error - Delete(ctx context.Context, id int64) error -} - +// type Storage interface { +// Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) +// Get(ctx context.Context, id int64) (*modelService.User, error) +// Update(ctx context.Context, id int64, name, email *string) error +// Delete(ctx context.Context, id int64) error +// } +// +// type Service interface { +// Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) +// Get(ctx context.Context, id int64) (*modelService.User, error) +// Update(ctx context.Context, id int64, name *string, email *string) error +// Delete(ctx context.Context, id int64) error +// } type StorageConfig interface { DSN() string } @@ -45,6 +47,37 @@ type GRPCConfig interface { Address() string } +//type DB interface { +// SQLExecer +// Pinger +// Close() +//} +// +//type SQLExecer interface { +// NamedExecer +// QueryExecer +//} +// +//type NamedExecer interface { +// ScanOneContext(ctx context.Context, dest interface{}, q db.Query, args ...interface{}) error +// ScanAllContext(ctx context.Context, dest interface{}, q db.Query, args ...interface{}) error +//} +// +//type QueryExecer interface { +// ExecContext(ctx context.Context, q db.Query, args ...interface{}) (pgconn.CommandTag, error) +// QueryContext(ctx context.Context, q db.Query, args ...interface{}) (pgx.Rows, error) +// QueryRowContext(ctx context.Context, q db.Query, args ...interface{}) pgx.Row +//} +// +//type Pinger interface { +// Ping(ctx context.Context) error +//} +// +//type Client interface { +// DB() DB +// Close() error +//} + type ServiceProvider struct { cls *closer.Closer log *slog.Logger @@ -52,10 +85,11 @@ type ServiceProvider struct { repoConfig StorageConfig grpcConfig GRPCConfig - pgPool *pgxpool.Pool - userRepository Storage + txManager db.TxManager + dbClient db.Client + userRepository storage.Repository - userService Service + userService service.Service userAPI *userAPI.Implementation } @@ -112,35 +146,40 @@ func (s *ServiceProvider) GRPCConfig() GRPCConfig { return s.grpcConfig } -func (s *ServiceProvider) PGPool(ctx context.Context) *pgxpool.Pool { - if s.pgPool == nil { - pool, err := pgxpool.New(ctx, s.RepoConfig().DSN()) +func (s *ServiceProvider) TxManager(ctx context.Context) db.TxManager { + if s.txManager == nil { + s.txManager = transaction.NewTransactionManager(s.DBClient(ctx).DB()) + } + return s.txManager +} + +func (s *ServiceProvider) DBClient(ctx context.Context) db.Client { + if s.dbClient == nil { + cl, err := dbPG.NewPGClient(ctx, s.RepoConfig().DSN()) if err != nil { log.Fatalf("failed to connect to database: %s", err.Error()) } - if err = pool.Ping(ctx); err != nil { + if err = cl.DB().Ping(ctx); err != nil { log.Fatalf("ping error: %s", err.Error()) } - s.Closer().Add(func() error { - pool.Close() - return nil - }) - s.pgPool = pool + s.Closer().Add(cl.Close) + + s.dbClient = cl } - return s.pgPool + return s.dbClient } -func (s *ServiceProvider) UserRepository(ctx context.Context) Storage { +func (s *ServiceProvider) UserRepository(ctx context.Context) storage.Repository { if s.userRepository == nil { - s.userRepository = postgres.NewRepository(s.PGPool(ctx)) + s.userRepository = postgres.NewRepository(s.DBClient(ctx)) } return s.userRepository } -func (s *ServiceProvider) UserService(ctx context.Context) Service { +func (s *ServiceProvider) UserService(ctx context.Context) service.Service { if s.userService == nil { - s.userService = user.NewService(s.Log(), s.UserRepository(ctx)) + s.userService = user.NewService(s.Log(), s.UserRepository(ctx), s.TxManager(ctx)) } return s.userService } diff --git a/internal/client/db/db.go b/internal/client/db/db.go index 3a49c63..134bffc 100644 --- a/internal/client/db/db.go +++ b/internal/client/db/db.go @@ -1 +1,54 @@ package db + +import ( + "context" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type Handler func(ctx context.Context) error + +type Client interface { + DB() DB + Close() error +} + +type Query struct { + Name string + QueryRaw string +} + +type SQLExecer interface { + NamedExecer + QueryExecer +} + +type NamedExecer interface { + ScanOneContext(ctx context.Context, dest interface{}, q Query, args ...interface{}) error + ScanAllContext(ctx context.Context, dest interface{}, q Query, args ...interface{}) error +} + +type QueryExecer interface { + ExecContext(ctx context.Context, q Query, args ...interface{}) (pgconn.CommandTag, error) + QueryContext(ctx context.Context, q Query, args ...interface{}) (pgx.Rows, error) + QueryRowContext(ctx context.Context, q Query, args ...interface{}) pgx.Row +} + +type Pinger interface { + Ping(ctx context.Context) error +} + +type DB interface { + SQLExecer + Pinger + Transactor + Close() +} + +type Transactor interface { + BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) +} + +type TxManager interface { + ReadCommitted(ctx context.Context, f Handler) error +} diff --git a/internal/client/db/postgres/client.go b/internal/client/db/postgres/client.go index bf560be..1a08605 100644 --- a/internal/client/db/postgres/client.go +++ b/internal/client/db/postgres/client.go @@ -1 +1,63 @@ package postgres + +import ( + "context" + "github.com/Str1m/auth/internal/client/db" + "github.com/jackc/pgx/v5/pgxpool" +) + +//type DB interface { +// SQLExecer +// Pinger +// Close() +//} +// +//type SQLExecer interface { +// NamedExecer +// QueryExecer +//} +// +//type NamedExecer interface { +// ScanOneContext(ctx context.Context, dest interface{}, q db.Query, args ...interface{}) error +// ScanAllContext(ctx context.Context, dest interface{}, q db.Query, args ...interface{}) error +//} +// +//type QueryExecer interface { +// ExecContext(ctx context.Context, q db.Query, args ...interface{}) (pgconn.CommandTag, error) +// QueryContext(ctx context.Context, q db.Query, args ...interface{}) (pgx.Rows, error) +// QueryRowContext(ctx context.Context, q db.Query, args ...interface{}) pgx.Row +//} +// +//type Pinger interface { +// Ping(ctx context.Context) error +//} +// +//type Client interface { +// DB() DB +// Close() error +//} + +type pgClient struct { + masterDB db.DB +} + +func NewPGClient(ctx context.Context, dsn string) (db.Client, error) { + db, err := pgxpool.New(ctx, dsn) + if err != nil { + return nil, err + } + return &pgClient{ + masterDB: &pg{db: db}, + }, nil +} + +func (p *pgClient) DB() db.DB { + return p.masterDB +} + +func (p *pgClient) Close() error { + if p.masterDB != nil { + p.masterDB.Close() + } + return nil +} diff --git a/internal/client/db/postgres/postgres.go b/internal/client/db/postgres/postgres.go index bf560be..e7a9ec8 100644 --- a/internal/client/db/postgres/postgres.go +++ b/internal/client/db/postgres/postgres.go @@ -1 +1,85 @@ package postgres + +import ( + "context" + "github.com/Str1m/auth/internal/client/db" + "github.com/georgysavva/scany/v2/pgxscan" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" +) + +type key string + +const ( + TxKey key = "tx" +) + +type pg struct { + db *pgxpool.Pool +} + +func New(db *pgxpool.Pool) db.DB { + return &pg{ + db: db, + } +} + +func (p *pg) ExecContext(ctx context.Context, q db.Query, args ...any) (pgconn.CommandTag, error) { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.Exec(ctx, q.QueryRaw, args...) + } + return p.db.Exec(ctx, q.QueryRaw, args...) +} + +func (p *pg) QueryContext(ctx context.Context, q db.Query, args ...any) (pgx.Rows, error) { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.Query(ctx, q.QueryRaw, args...) + } + return p.db.Query(ctx, q.QueryRaw, args...) +} + +func (p *pg) QueryRowContext(ctx context.Context, q db.Query, args ...any) pgx.Row { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.QueryRow(ctx, q.QueryRaw, args...) + } + + return p.db.QueryRow(ctx, q.QueryRaw, args...) +} + +func (p *pg) Ping(ctx context.Context) error { + + return p.db.Ping(ctx) +} + +func (p *pg) Close() { + p.db.Close() +} + +func (p *pg) BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) { + return p.db.BeginTx(ctx, txOptions) +} + +func (p *pg) ScanOneContext(ctx context.Context, dest any, q db.Query, args ...any) error { + row, err := p.QueryContext(ctx, q, args...) + if err != nil { + return err + } + return pgxscan.ScanOne(dest, row) +} + +func (p *pg) ScanAllContext(ctx context.Context, dest any, q db.Query, args ...any) error { + row, err := p.QueryContext(ctx, q, args...) + if err != nil { + return err + } + + return pgxscan.ScanAll(dest, row) +} + +func MakeContextTx(ctx context.Context, tx pgx.Tx) context.Context { + return context.WithValue(ctx, TxKey, tx) +} diff --git a/internal/client/db/transaction/transaction.go b/internal/client/db/transaction/transaction.go new file mode 100644 index 0000000..cbdcf31 --- /dev/null +++ b/internal/client/db/transaction/transaction.go @@ -0,0 +1,64 @@ +package transaction + +import ( + "context" + "github.com/Str1m/auth/internal/client/db" + "github.com/Str1m/auth/internal/client/db/postgres" + "github.com/jackc/pgx/v5" + "github.com/pkg/errors" +) + +type manager struct { + db db.Transactor +} + +func NewTransactionManager(db db.Transactor) db.TxManager { + return &manager{ + db: db, + } +} + +func (m *manager) ReadCommitted(ctx context.Context, f db.Handler) error { + txOpts := pgx.TxOptions{IsoLevel: pgx.ReadCommitted} + return m.transaction(ctx, txOpts, f) +} + +func (m *manager) transaction(ctx context.Context, opts pgx.TxOptions, f db.Handler) (err error) { + tx, ok := ctx.Value(postgres.TxKey).(pgx.Tx) + if ok { + return f(ctx) + } + + tx, err = m.db.BeginTx(ctx, opts) + if err != nil { + return err + } + + ctx = postgres.MakeContextTx(ctx, tx) + + defer func() { + if r := recover(); r != nil { + err = errors.Errorf("panic recovered: %v", r) + } + + if err != nil { + if errRollback := tx.Rollback(ctx); err != nil { + err = errors.Wrapf(err, "errRollback: %v", errRollback) + + } + return + } + + if err == nil { + err = tx.Commit(ctx) + if err != nil { + err = errors.Wrap(err, "commit failed") + } + } + }() + + if err = f(ctx); err != nil { + err = errors.Wrap(err, "failed exec code inside transaction") + } + return nil +} diff --git a/internal/service/service.go b/internal/service/service.go index bb3368d..8f38199 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -1,7 +1,18 @@ package service -import "errors" +import ( + "context" + "errors" + modelService "github.com/Str1m/auth/internal/model" +) var ( ErrPassNotEqual = errors.New("passwords are not equal") ) + +type Service interface { + Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) + Get(ctx context.Context, id int64) (*modelService.User, error) + Update(ctx context.Context, id int64, name *string, email *string) error + Delete(ctx context.Context, id int64) error +} diff --git a/internal/service/user/service.go b/internal/service/user/service.go index 651b498..9b21baa 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -1,25 +1,18 @@ package user import ( - "context" + "github.com/Str1m/auth/internal/client/db" + "github.com/Str1m/auth/internal/storage" "log/slog" - - modelService "github.com/Str1m/auth/internal/model" ) -type Repository interface { - Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) - Get(ctx context.Context, id int64) (*modelService.User, error) - Update(ctx context.Context, id int64, name, email *string) error - Delete(ctx context.Context, id int64) error -} - type Service struct { log *slog.Logger - UserRepository Repository + UserRepository storage.Repository + TxManager db.TxManager } -func NewService(log *slog.Logger, authRepo Repository) *Service { +func NewService(log *slog.Logger, authRepo storage.Repository, txManager db.TxManager) *Service { return &Service{ log: log, UserRepository: authRepo, diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 4ff8c3f..3b20491 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -1,7 +1,18 @@ package storage -import "errors" +import ( + "context" + "errors" + modelService "github.com/Str1m/auth/internal/model" +) var ( ErrUserNotFound = errors.New("user not found") ) + +type Repository interface { + Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) + Get(ctx context.Context, id int64) (*modelService.User, error) + Update(ctx context.Context, id int64, name, email *string) error + Delete(ctx context.Context, id int64) error +} diff --git a/internal/storage/users/model/user.go b/internal/storage/users/model/user.go index f5a0727..7118b68 100644 --- a/internal/storage/users/model/user.go +++ b/internal/storage/users/model/user.go @@ -7,18 +7,18 @@ import ( ) type User struct { - ID int64 - Name string - Email string - Role desc.Role - CreatedAt time.Time - UpdatedAt time.Time + ID int64 `db:"id"` + Name string `db:"name"` + Email string `db:"email"` + Role desc.Role `db:"role"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } type UserInfo struct { - Name string - Email string - Password string - PasswordConfirm string - Role desc.Role + Name string `db:"id"` + Email string `db:"email"` + Password string `db:"password"` + PasswordConfirm string `db:"password_confirm"` + Role desc.Role `db:"role"` } diff --git a/internal/storage/users/postgres/repository.go b/internal/storage/users/postgres/repository.go index e5a8143..09a22a3 100644 --- a/internal/storage/users/postgres/repository.go +++ b/internal/storage/users/postgres/repository.go @@ -3,15 +3,15 @@ package postgres import ( "context" "fmt" - + "github.com/Str1m/auth/internal/client/db" modelService "github.com/Str1m/auth/internal/model" + "log" "github.com/Str1m/auth/internal/storage" "github.com/Str1m/auth/internal/storage/users/converter" modelRepo "github.com/Str1m/auth/internal/storage/users/model" sq "github.com/Masterminds/squirrel" - "github.com/jackc/pgx/v5/pgxpool" ) const ( @@ -26,11 +26,42 @@ const ( updatedAtColumn = "updated_at" ) +//type DB interface { +// SQLExecer +// Pinger +// Close() +//} +// +//type SQLExecer interface { +// NamedExecer +// QueryExecer +//} +// +//type NamedExecer interface { +// ScanOneContext(ctx context.Context, dest interface{}, q db.Query, args ...interface{}) error +// ScanAllContext(ctx context.Context, dest interface{}, q db.Query, args ...interface{}) error +//} +// +//type QueryExecer interface { +// ExecContext(ctx context.Context, q db.Query, args ...interface{}) (pgconn.CommandTag, error) +// QueryContext(ctx context.Context, q db.Query, args ...interface{}) (pgx.Rows, error) +// QueryRowContext(ctx context.Context, q db.Query, args ...interface{}) pgx.Row +//} +// +//type Pinger interface { +// Ping(ctx context.Context) error +//} +// +//type Client interface { +// DB() DB +// Close() error +//} + type Repo struct { - db *pgxpool.Pool + db db.Client } -func NewRepository(db *pgxpool.Pool) *Repo { +func NewRepository(db db.Client) storage.Repository { return &Repo{db: db} } @@ -51,8 +82,13 @@ func (r *Repo) Create(ctx context.Context, info *modelService.UserInfo, hashedPa return 0, fmt.Errorf("%s: %w", op, err) } + q := db.Query{ + Name: op, + QueryRaw: query, + } + var id int64 - err = r.db.QueryRow(ctx, query, args...).Scan(&id) + err = r.db.DB().QueryRowContext(ctx, q, args...).Scan(&id) if err != nil { return 0, fmt.Errorf("%s: %w", op, err) } @@ -62,7 +98,6 @@ func (r *Repo) Create(ctx context.Context, info *modelService.UserInfo, hashedPa func (r *Repo) Get(ctx context.Context, id int64) (*modelService.User, error) { const op = "repository.users.Get" - builder := sq.Select(idColumn, nameColumn, emailColumn, roleColumn, createdAtColumn, updatedAtColumn). PlaceholderFormat(sq.Dollar). From(tableName). @@ -73,14 +108,13 @@ func (r *Repo) Get(ctx context.Context, id int64) (*modelService.User, error) { if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } - err = r.db.QueryRow(ctx, query, args...).Scan( - &user.ID, - &user.Name, - &user.Email, - &user.Role, - &user.CreatedAt, - &user.UpdatedAt) + q := db.Query{ + Name: op, + QueryRaw: query, + } + err = r.db.DB().ScanOneContext(ctx, &user, q, args...) if err != nil { + log.Printf("Error: %s", err.Error()) return nil, fmt.Errorf("%s: %w", op, err) } @@ -107,7 +141,12 @@ func (r *Repo) Update(ctx context.Context, id int64, name, email *string) error return fmt.Errorf("%s: %w", op, err) } - result, err := r.db.Exec(ctx, query, args...) + q := db.Query{ + Name: op, + QueryRaw: query, + } + + result, err := r.db.DB().ExecContext(ctx, q, args...) if err != nil { return fmt.Errorf("%s: %w", op, err) } @@ -131,7 +170,12 @@ func (r *Repo) Delete(ctx context.Context, id int64) error { return fmt.Errorf("%s: %w", op, err) } - result, err := r.db.Exec(ctx, query, args...) + q := db.Query{ + Name: op, + QueryRaw: query, + } + + result, err := r.db.DB().ExecContext(ctx, q, args...) if err != nil { return fmt.Errorf("%s: %w", op, err) } From c0988103b71e7032e3b0e11118708cf7716026a6 Mon Sep 17 00:00:00 2001 From: Str1m Date: Fri, 11 Apr 2025 14:45:53 +0300 Subject: [PATCH 3/7] refact storage/user/postgres/postgres.go --- internal/app/service_provider.go | 44 ------------ .../postgres/{repository.go => postgres.go} | 69 +++++++------------ 2 files changed, 25 insertions(+), 88 deletions(-) rename internal/storage/users/postgres/{repository.go => postgres.go} (64%) diff --git a/internal/app/service_provider.go b/internal/app/service_provider.go index 5718e68..d154dd8 100644 --- a/internal/app/service_provider.go +++ b/internal/app/service_provider.go @@ -26,19 +26,6 @@ const ( envProd = "prod" ) -// type Storage interface { -// Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) -// Get(ctx context.Context, id int64) (*modelService.User, error) -// Update(ctx context.Context, id int64, name, email *string) error -// Delete(ctx context.Context, id int64) error -// } -// -// type Service interface { -// Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) -// Get(ctx context.Context, id int64) (*modelService.User, error) -// Update(ctx context.Context, id int64, name *string, email *string) error -// Delete(ctx context.Context, id int64) error -// } type StorageConfig interface { DSN() string } @@ -47,37 +34,6 @@ type GRPCConfig interface { Address() string } -//type DB interface { -// SQLExecer -// Pinger -// Close() -//} -// -//type SQLExecer interface { -// NamedExecer -// QueryExecer -//} -// -//type NamedExecer interface { -// ScanOneContext(ctx context.Context, dest interface{}, q db.Query, args ...interface{}) error -// ScanAllContext(ctx context.Context, dest interface{}, q db.Query, args ...interface{}) error -//} -// -//type QueryExecer interface { -// ExecContext(ctx context.Context, q db.Query, args ...interface{}) (pgconn.CommandTag, error) -// QueryContext(ctx context.Context, q db.Query, args ...interface{}) (pgx.Rows, error) -// QueryRowContext(ctx context.Context, q db.Query, args ...interface{}) pgx.Row -//} -// -//type Pinger interface { -// Ping(ctx context.Context) error -//} -// -//type Client interface { -// DB() DB -// Close() error -//} - type ServiceProvider struct { cls *closer.Closer log *slog.Logger diff --git a/internal/storage/users/postgres/repository.go b/internal/storage/users/postgres/postgres.go similarity index 64% rename from internal/storage/users/postgres/repository.go rename to internal/storage/users/postgres/postgres.go index 09a22a3..4639dd9 100644 --- a/internal/storage/users/postgres/repository.go +++ b/internal/storage/users/postgres/postgres.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/Str1m/auth/internal/client/db" modelService "github.com/Str1m/auth/internal/model" + "github.com/jackc/pgx/v5/pgxpool" "log" "github.com/Str1m/auth/internal/storage" @@ -26,50 +27,19 @@ const ( updatedAtColumn = "updated_at" ) -//type DB interface { -// SQLExecer -// Pinger -// Close() -//} -// -//type SQLExecer interface { -// NamedExecer -// QueryExecer -//} -// -//type NamedExecer interface { -// ScanOneContext(ctx context.Context, dest interface{}, q db.Query, args ...interface{}) error -// ScanAllContext(ctx context.Context, dest interface{}, q db.Query, args ...interface{}) error -//} -// -//type QueryExecer interface { -// ExecContext(ctx context.Context, q db.Query, args ...interface{}) (pgconn.CommandTag, error) -// QueryContext(ctx context.Context, q db.Query, args ...interface{}) (pgx.Rows, error) -// QueryRowContext(ctx context.Context, q db.Query, args ...interface{}) pgx.Row -//} -// -//type Pinger interface { -// Ping(ctx context.Context) error -//} -// -//type Client interface { -// DB() DB -// Close() error -//} - -type Repo struct { - db db.Client +type StoragePG struct { + db *pgxpool.Pool } -func NewRepository(db db.Client) storage.Repository { - return &Repo{db: db} +func NewRepository(db *pgxpool.Pool) *StoragePG { + return &StoragePG{db: db} } -func (r *Repo) Close() { +func (r *StoragePG) Close() { r.db.Close() } -func (r *Repo) Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) { +func (r *StoragePG) Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) { const op = "repository.users.Create" builder := sq.Insert(tableName). @@ -88,7 +58,8 @@ func (r *Repo) Create(ctx context.Context, info *modelService.UserInfo, hashedPa } var id int64 - err = r.db.DB().QueryRowContext(ctx, q, args...).Scan(&id) + //err = r.db.DB().QueryRowContext(ctx, q, args...).Scan(&id) + err = r.db.QueryRow(ctx, q.QueryRaw, args...).Scan(&id) if err != nil { return 0, fmt.Errorf("%s: %w", op, err) } @@ -96,7 +67,7 @@ func (r *Repo) Create(ctx context.Context, info *modelService.UserInfo, hashedPa return id, nil } -func (r *Repo) Get(ctx context.Context, id int64) (*modelService.User, error) { +func (r *StoragePG) Get(ctx context.Context, id int64) (*modelService.User, error) { const op = "repository.users.Get" builder := sq.Select(idColumn, nameColumn, emailColumn, roleColumn, createdAtColumn, updatedAtColumn). PlaceholderFormat(sq.Dollar). @@ -112,7 +83,15 @@ func (r *Repo) Get(ctx context.Context, id int64) (*modelService.User, error) { Name: op, QueryRaw: query, } - err = r.db.DB().ScanOneContext(ctx, &user, q, args...) + //err = r.db.DB().ScanOneContext(ctx, &user, q, args...) + err = r.db.QueryRow(ctx, q.QueryRaw, args...).Scan( + &user.ID, + &user.Name, + &user.Email, + &user.Role, + &user.CreatedAt, + &user.UpdatedAt, + ) if err != nil { log.Printf("Error: %s", err.Error()) return nil, fmt.Errorf("%s: %w", op, err) @@ -121,7 +100,7 @@ func (r *Repo) Get(ctx context.Context, id int64) (*modelService.User, error) { return converter.ToUserFromStorage(&user), nil } -func (r *Repo) Update(ctx context.Context, id int64, name, email *string) error { +func (r *StoragePG) Update(ctx context.Context, id int64, name, email *string) error { const op = "repository.users.Update" builder := sq.Update(tableName). @@ -146,7 +125,8 @@ func (r *Repo) Update(ctx context.Context, id int64, name, email *string) error QueryRaw: query, } - result, err := r.db.DB().ExecContext(ctx, q, args...) + //result, err := r.db.DB().ExecContext(ctx, q, args...) + result, err := r.db.Exec(ctx, q.QueryRaw, args) if err != nil { return fmt.Errorf("%s: %w", op, err) } @@ -158,7 +138,7 @@ func (r *Repo) Update(ctx context.Context, id int64, name, email *string) error return nil } -func (r *Repo) Delete(ctx context.Context, id int64) error { +func (r *StoragePG) Delete(ctx context.Context, id int64) error { const op = "repository.users.Delete" builder := sq.Delete(tableName). @@ -175,7 +155,8 @@ func (r *Repo) Delete(ctx context.Context, id int64) error { QueryRaw: query, } - result, err := r.db.DB().ExecContext(ctx, q, args...) + //result, err := r.db.DB().ExecContext(ctx, q, args...) + result, err := r.db.Exec(ctx, q.QueryRaw, args) if err != nil { return fmt.Errorf("%s: %w", op, err) } From 40582c6aeb5d6067ee97b5e756cfffce69d40dc4 Mon Sep 17 00:00:00 2001 From: Str1m Date: Fri, 11 Apr 2025 14:58:38 +0300 Subject: [PATCH 4/7] add postgres/client.go --- internal/storage/users/postgres/client.go | 74 +++++++++++++++++++++ internal/storage/users/postgres/postgres.go | 30 +++------ 2 files changed, 84 insertions(+), 20 deletions(-) create mode 100644 internal/storage/users/postgres/client.go diff --git a/internal/storage/users/postgres/client.go b/internal/storage/users/postgres/client.go new file mode 100644 index 0000000..e88ba48 --- /dev/null +++ b/internal/storage/users/postgres/client.go @@ -0,0 +1,74 @@ +package postgres + +import ( + "context" + "github.com/Str1m/auth/internal/client/db" + "github.com/georgysavva/scany/v2/pgxscan" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" +) + +type key string + +const ( + TxKey key = "tx" +) + +type ClientPG struct { + pg *pgxpool.Pool +} + +func NewClientPG(pg *pgxpool.Pool) *ClientPG { + return &ClientPG{pg: pg} +} + +func (p *ClientPG) ExecContext(ctx context.Context, q db.Query, args ...any) (pgconn.CommandTag, error) { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.Exec(ctx, q.QueryRaw, args...) + } + return p.pg.Exec(ctx, q.QueryRaw, args...) +} + +func (p *ClientPG) QueryContext(ctx context.Context, q db.Query, args ...any) (pgx.Rows, error) { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.Query(ctx, q.QueryRaw, args...) + } + return p.pg.Query(ctx, q.QueryRaw, args...) +} + +func (p *ClientPG) QueryRowContext(ctx context.Context, q db.Query, args ...any) pgx.Row { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.QueryRow(ctx, q.QueryRaw, args...) + } + + return p.pg.QueryRow(ctx, q.QueryRaw, args...) +} + +func (p *ClientPG) Ping(ctx context.Context) error { + return p.pg.Ping(ctx) +} + +func (p *ClientPG) Close() { + p.pg.Close() +} + +func (p *ClientPG) ScanOneContext(ctx context.Context, dest any, q db.Query, args ...any) error { + row, err := p.QueryContext(ctx, q, args...) + if err != nil { + return err + } + return pgxscan.ScanOne(dest, row) +} + +func (p *ClientPG) ScanAllContext(ctx context.Context, dest any, q db.Query, args ...any) error { + row, err := p.QueryContext(ctx, q, args...) + if err != nil { + return err + } + + return pgxscan.ScanAll(dest, row) +} diff --git a/internal/storage/users/postgres/postgres.go b/internal/storage/users/postgres/postgres.go index 4639dd9..d842941 100644 --- a/internal/storage/users/postgres/postgres.go +++ b/internal/storage/users/postgres/postgres.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/Str1m/auth/internal/client/db" modelService "github.com/Str1m/auth/internal/model" - "github.com/jackc/pgx/v5/pgxpool" "log" "github.com/Str1m/auth/internal/storage" @@ -28,15 +27,15 @@ const ( ) type StoragePG struct { - db *pgxpool.Pool + client *ClientPG } -func NewRepository(db *pgxpool.Pool) *StoragePG { - return &StoragePG{db: db} +func NewStoragePG(db *ClientPG) *StoragePG { + return &StoragePG{client: db} } func (r *StoragePG) Close() { - r.db.Close() + r.client.Close() } func (r *StoragePG) Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) { @@ -58,8 +57,8 @@ func (r *StoragePG) Create(ctx context.Context, info *modelService.UserInfo, has } var id int64 - //err = r.db.DB().QueryRowContext(ctx, q, args...).Scan(&id) - err = r.db.QueryRow(ctx, q.QueryRaw, args...).Scan(&id) + err = r.client.QueryRowContext(ctx, q, args...).Scan(&id) + if err != nil { return 0, fmt.Errorf("%s: %w", op, err) } @@ -83,15 +82,8 @@ func (r *StoragePG) Get(ctx context.Context, id int64) (*modelService.User, erro Name: op, QueryRaw: query, } - //err = r.db.DB().ScanOneContext(ctx, &user, q, args...) - err = r.db.QueryRow(ctx, q.QueryRaw, args...).Scan( - &user.ID, - &user.Name, - &user.Email, - &user.Role, - &user.CreatedAt, - &user.UpdatedAt, - ) + err = r.client.ScanOneContext(ctx, &user, q, args...) + if err != nil { log.Printf("Error: %s", err.Error()) return nil, fmt.Errorf("%s: %w", op, err) @@ -125,8 +117,7 @@ func (r *StoragePG) Update(ctx context.Context, id int64, name, email *string) e QueryRaw: query, } - //result, err := r.db.DB().ExecContext(ctx, q, args...) - result, err := r.db.Exec(ctx, q.QueryRaw, args) + result, err := r.client.ExecContext(ctx, q, args...) if err != nil { return fmt.Errorf("%s: %w", op, err) } @@ -155,8 +146,7 @@ func (r *StoragePG) Delete(ctx context.Context, id int64) error { QueryRaw: query, } - //result, err := r.db.DB().ExecContext(ctx, q, args...) - result, err := r.db.Exec(ctx, q.QueryRaw, args) + result, err := r.client.ExecContext(ctx, q, args...) if err != nil { return fmt.Errorf("%s: %w", op, err) } From b779eaa10990c17c6ebe62049ea741dd5848fa7e Mon Sep 17 00:00:00 2001 From: Str1m Date: Fri, 11 Apr 2025 16:07:44 +0300 Subject: [PATCH 5/7] improve interfaces logic --- cmd/auth/main.go | 21 +++++- internal/api/user/service.go | 15 ++-- internal/app/app.go | 6 +- internal/app/service_provider.go | 79 ++++++++++----------- internal/client/db/postgres/postgres.go | 9 +-- internal/service/service.go | 14 ++-- internal/service/user/create.go | 3 +- internal/service/user/delete.go | 2 +- internal/service/user/get.go | 2 +- internal/service/user/service.go | 22 +++--- internal/service/user/update.go | 2 +- internal/storage/users/postgres/client.go | 14 +++- internal/storage/users/postgres/postgres.go | 1 - 13 files changed, 112 insertions(+), 78 deletions(-) diff --git a/cmd/auth/main.go b/cmd/auth/main.go index 1c51e53..84fc3a1 100644 --- a/cmd/auth/main.go +++ b/cmd/auth/main.go @@ -2,12 +2,29 @@ package main import ( "context" - "log" - "github.com/Str1m/auth/internal/app" + "log" ) +const DSN = "test" + func main() { + + //cfg, err := env.NewPGConfig() + //if err != nil { + // log.Fatalf("failed to get pg config: %s", err.Error()) + //} + // + + // + //p, err := pgxpool.New(ctx, cfg.DSN()) + //if err != nil { + // log.Fatalln("err") + //} + // + //client := postgres.NewClientPG(p) + // + //DBLayer := postgres.NewStoragePG(client) ctx := context.Background() a, err := app.NewApp(ctx) diff --git a/internal/api/user/service.go b/internal/api/user/service.go index 1f92a3e..945ec42 100644 --- a/internal/api/user/service.go +++ b/internal/api/user/service.go @@ -1,17 +1,24 @@ package user import ( - "github.com/Str1m/auth/internal/service" - + "context" + modelService "github.com/Str1m/auth/internal/model" desc "github.com/Str1m/auth/pkg/auth_v1" ) +type Service interface { + Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) + Get(ctx context.Context, id int64) (*modelService.User, error) + Update(ctx context.Context, id int64, name *string, email *string) error + Delete(ctx context.Context, id int64) error +} + type Implementation struct { desc.UnimplementedAuthV1Server - userService service.Service + userService Service } -func NewImplementation(userService service.Service) *Implementation { +func NewImplementation(userService Service) *Implementation { return &Implementation{ userService: userService, } diff --git a/internal/app/app.go b/internal/app/app.go index ea8d52d..9b39f61 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -75,7 +75,7 @@ func (a *App) initGRPC(ctx context.Context) error { reflection.Register(a.grpcServer) desc.RegisterAuthV1Server(a.grpcServer, a.serviceProvider.UserAPIImpl(ctx)) - a.serviceProvider.cls.Add(func() error { + a.serviceProvider.GetCloser().Add(func() error { a.grpcServer.GracefulStop() return nil }) @@ -83,11 +83,11 @@ func (a *App) initGRPC(ctx context.Context) error { } func (a *App) runGRPCServer() error { - a.serviceProvider.Log().Info("server listening", slog.String("Addr", a.serviceProvider.GRPCConfig().Address())) + a.serviceProvider.GetLog().Info("server listening", slog.String("Addr", a.serviceProvider.GRPCConfig().Address())) l, err := net.Listen("tcp", a.serviceProvider.GRPCConfig().Address()) if err != nil { - a.serviceProvider.Log().Error("failed to listen", sl.Err(err)) + a.serviceProvider.GetLog().Error("failed to listen", sl.Err(err)) return err } diff --git a/internal/app/service_provider.go b/internal/app/service_provider.go index d154dd8..c11cd16 100644 --- a/internal/app/service_provider.go +++ b/internal/app/service_provider.go @@ -2,11 +2,9 @@ package app import ( "context" - "github.com/Str1m/auth/internal/client/db" - dbPG "github.com/Str1m/auth/internal/client/db/postgres" - "github.com/Str1m/auth/internal/client/db/transaction" - "github.com/Str1m/auth/internal/service" - "github.com/Str1m/auth/internal/storage" + modelService "github.com/Str1m/auth/internal/model" + "github.com/Str1m/auth/internal/service/user" + "github.com/jackc/pgx/v5/pgxpool" "log" "log/slog" "os" @@ -16,7 +14,6 @@ import ( "github.com/Str1m/auth/internal/closer" "github.com/Str1m/auth/internal/config/env" "github.com/Str1m/auth/internal/lib/logger/handlers/slogpretty" - "github.com/Str1m/auth/internal/service/user" "github.com/Str1m/auth/internal/storage/users/postgres" ) @@ -34,18 +31,31 @@ type GRPCConfig interface { Address() string } +type Service interface { + Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) + Get(ctx context.Context, id int64) (*modelService.User, error) + Update(ctx context.Context, id int64, name *string, email *string) error + Delete(ctx context.Context, id int64) error +} + +type Storage interface { + Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) + Get(ctx context.Context, id int64) (*modelService.User, error) + Update(ctx context.Context, id int64, name, email *string) error + Delete(ctx context.Context, id int64) error +} + type ServiceProvider struct { cls *closer.Closer log *slog.Logger - repoConfig StorageConfig - grpcConfig GRPCConfig + storageConfig StorageConfig + grpcConfig GRPCConfig - txManager db.TxManager - dbClient db.Client - userRepository storage.Repository + dbClient *postgres.ClientPG + dbLayer Storage - userService service.Service + userService Service userAPI *userAPI.Implementation } @@ -53,7 +63,7 @@ func newServiceProvider() *ServiceProvider { return &ServiceProvider{} } -func (s *ServiceProvider) Closer() *closer.Closer { +func (s *ServiceProvider) GetCloser() *closer.Closer { if s.cls == nil { s.cls = closer.New(os.Interrupt, syscall.SIGTERM) } @@ -61,7 +71,7 @@ func (s *ServiceProvider) Closer() *closer.Closer { return s.cls } -func (s *ServiceProvider) Log() *slog.Logger { +func (s *ServiceProvider) GetLog() *slog.Logger { if s.log == nil { env := os.Getenv("ENV") switch env { @@ -80,15 +90,15 @@ func (s *ServiceProvider) Log() *slog.Logger { return s.log } -func (s *ServiceProvider) RepoConfig() StorageConfig { - if s.repoConfig == nil { +func (s *ServiceProvider) GetStorageConfig() StorageConfig { + if s.storageConfig == nil { cfg, err := env.NewPGConfig() if err != nil { log.Fatalf("failed to get pg config: %s", err.Error()) } - s.repoConfig = cfg + s.storageConfig = cfg } - return s.repoConfig + return s.storageConfig } func (s *ServiceProvider) GRPCConfig() GRPCConfig { @@ -102,40 +112,29 @@ func (s *ServiceProvider) GRPCConfig() GRPCConfig { return s.grpcConfig } -func (s *ServiceProvider) TxManager(ctx context.Context) db.TxManager { - if s.txManager == nil { - s.txManager = transaction.NewTransactionManager(s.DBClient(ctx).DB()) - } - return s.txManager -} - -func (s *ServiceProvider) DBClient(ctx context.Context) db.Client { +func (s *ServiceProvider) GetDBClient(ctx context.Context) *postgres.ClientPG { if s.dbClient == nil { - cl, err := dbPG.NewPGClient(ctx, s.RepoConfig().DSN()) + p, err := pgxpool.New(ctx, s.GetStorageConfig().DSN()) if err != nil { - log.Fatalf("failed to connect to database: %s", err.Error()) + log.Fatalln("err") } - if err = cl.DB().Ping(ctx); err != nil { - log.Fatalf("ping error: %s", err.Error()) - } - s.Closer().Add(cl.Close) - s.dbClient = cl + s.dbClient = postgres.NewClientPG(p) } + return s.dbClient } -func (s *ServiceProvider) UserRepository(ctx context.Context) storage.Repository { - if s.userRepository == nil { - s.userRepository = postgres.NewRepository(s.DBClient(ctx)) +func (s *ServiceProvider) GetDBLayer(ctx context.Context) Storage { + if s.dbLayer == nil { + s.dbLayer = postgres.NewStoragePG(s.GetDBClient(ctx)) } - - return s.userRepository + return s.dbLayer } -func (s *ServiceProvider) UserService(ctx context.Context) service.Service { +func (s *ServiceProvider) UserService(ctx context.Context) Service { if s.userService == nil { - s.userService = user.NewService(s.Log(), s.UserRepository(ctx), s.TxManager(ctx)) + s.userService = user.NewService(s.GetLog(), s.GetDBLayer(ctx)) } return s.userService } diff --git a/internal/client/db/postgres/postgres.go b/internal/client/db/postgres/postgres.go index e7a9ec8..ec89099 100644 --- a/internal/client/db/postgres/postgres.go +++ b/internal/client/db/postgres/postgres.go @@ -59,10 +59,6 @@ func (p *pg) Close() { p.db.Close() } -func (p *pg) BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) { - return p.db.BeginTx(ctx, txOptions) -} - func (p *pg) ScanOneContext(ctx context.Context, dest any, q db.Query, args ...any) error { row, err := p.QueryContext(ctx, q, args...) if err != nil { @@ -80,6 +76,11 @@ func (p *pg) ScanAllContext(ctx context.Context, dest any, q db.Query, args ...a return pgxscan.ScanAll(dest, row) } +// // ------------------------------- +func (p *pg) BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) { + return p.db.BeginTx(ctx, txOptions) +} + func MakeContextTx(ctx context.Context, tx pgx.Tx) context.Context { return context.WithValue(ctx, TxKey, tx) } diff --git a/internal/service/service.go b/internal/service/service.go index 8f38199..1e5e733 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -1,18 +1,16 @@ package service import ( - "context" "errors" - modelService "github.com/Str1m/auth/internal/model" ) var ( ErrPassNotEqual = errors.New("passwords are not equal") ) -type Service interface { - Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) - Get(ctx context.Context, id int64) (*modelService.User, error) - Update(ctx context.Context, id int64, name *string, email *string) error - Delete(ctx context.Context, id int64) error -} +//type Service interface { +// Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) +// Get(ctx context.Context, id int64) (*modelService.User, error) +// Update(ctx context.Context, id int64, name *string, email *string) error +// Delete(ctx context.Context, id int64) error +//} diff --git a/internal/service/user/create.go b/internal/service/user/create.go index f10adbc..553bac1 100644 --- a/internal/service/user/create.go +++ b/internal/service/user/create.go @@ -22,10 +22,9 @@ func (s *Service) Create(ctx context.Context, userInfo *modelService.UserInfo) ( userInfo.Password, userInfo.PasswordConfirm = "", "" - id, err := s.UserRepository.Create(ctx, userInfo, hashedPassword) + id, err := s.UserDBClient.Create(ctx, userInfo, hashedPassword) if err != nil { return 0, fmt.Errorf("%s: %w", op, err) } - return id, nil } diff --git a/internal/service/user/delete.go b/internal/service/user/delete.go index 4e06727..72f3d53 100644 --- a/internal/service/user/delete.go +++ b/internal/service/user/delete.go @@ -7,7 +7,7 @@ import ( func (s *Service) Delete(ctx context.Context, id int64) error { const op = "service.user.Delete" - err := s.UserRepository.Delete(ctx, id) + err := s.UserDBClient.Delete(ctx, id) if err != nil { return fmt.Errorf("%s: %w", op, err) } diff --git a/internal/service/user/get.go b/internal/service/user/get.go index 8d9a683..a7beb02 100644 --- a/internal/service/user/get.go +++ b/internal/service/user/get.go @@ -9,7 +9,7 @@ import ( func (s *Service) Get(ctx context.Context, id int64) (*modelService.User, error) { const op = "service.user.Get" - user, err := s.UserRepository.Get(ctx, id) + user, err := s.UserDBClient.Get(ctx, id) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } diff --git a/internal/service/user/service.go b/internal/service/user/service.go index 9b21baa..aa7e300 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -1,20 +1,26 @@ package user import ( - "github.com/Str1m/auth/internal/client/db" - "github.com/Str1m/auth/internal/storage" + "context" + modelService "github.com/Str1m/auth/internal/model" "log/slog" ) +type DBLayer interface { + Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) + Get(ctx context.Context, id int64) (*modelService.User, error) + Update(ctx context.Context, id int64, name, email *string) error + Delete(ctx context.Context, id int64) error +} type Service struct { - log *slog.Logger - UserRepository storage.Repository - TxManager db.TxManager + log *slog.Logger + UserDBClient DBLayer + //TxManager db.TxManager } -func NewService(log *slog.Logger, authRepo storage.Repository, txManager db.TxManager) *Service { +func NewService(log *slog.Logger, dbClient DBLayer) *Service { return &Service{ - log: log, - UserRepository: authRepo, + log: log, + UserDBClient: dbClient, } } diff --git a/internal/service/user/update.go b/internal/service/user/update.go index 25f1546..1e8d891 100644 --- a/internal/service/user/update.go +++ b/internal/service/user/update.go @@ -7,7 +7,7 @@ import ( func (s *Service) Update(ctx context.Context, id int64, name *string, email *string) error { const op = "service.user.Update" - err := s.UserRepository.Update(ctx, id, name, email) + err := s.UserDBClient.Update(ctx, id, name, email) if err != nil { return fmt.Errorf("%s: %w", op, err) } diff --git a/internal/storage/users/postgres/client.go b/internal/storage/users/postgres/client.go index e88ba48..330487c 100644 --- a/internal/storage/users/postgres/client.go +++ b/internal/storage/users/postgres/client.go @@ -6,20 +6,28 @@ import ( "github.com/georgysavva/scany/v2/pgxscan" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgxpool" ) +// TODO: Пусть будет врапер type key string const ( TxKey key = "tx" ) +type DBLayer interface { + Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) + Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) + QueryRow(ctx context.Context, sql string, args ...any) pgx.Row + Ping(ctx context.Context) error + Close() +} + type ClientPG struct { - pg *pgxpool.Pool + pg DBLayer } -func NewClientPG(pg *pgxpool.Pool) *ClientPG { +func NewClientPG(pg DBLayer) *ClientPG { return &ClientPG{pg: pg} } diff --git a/internal/storage/users/postgres/postgres.go b/internal/storage/users/postgres/postgres.go index d842941..1b72590 100644 --- a/internal/storage/users/postgres/postgres.go +++ b/internal/storage/users/postgres/postgres.go @@ -62,7 +62,6 @@ func (r *StoragePG) Create(ctx context.Context, info *modelService.UserInfo, has if err != nil { return 0, fmt.Errorf("%s: %w", op, err) } - return id, nil } From ea1d4c4c054cd242d9b64bddc83acff6bf4dd8d2 Mon Sep 17 00:00:00 2001 From: Str1m Date: Fri, 11 Apr 2025 17:29:19 +0300 Subject: [PATCH 6/7] add transactions, still thinking about interfaces --- internal/app/service_provider.go | 29 +++++- internal/client/db/client.go | 95 +++++++++++++++++++ internal/client/db/db.go | 54 ----------- internal/client/db/postgres/client.go | 63 ------------ internal/client/db/postgres/postgres.go | 86 ----------------- internal/client/db/transaction/transaction.go | 23 ++--- internal/service/user/service.go | 6 +- internal/storage/storage.go | 9 -- internal/storage/users/postgres/client.go | 82 ---------------- internal/storage/users/postgres/postgres.go | 4 +- 10 files changed, 137 insertions(+), 314 deletions(-) create mode 100644 internal/client/db/client.go delete mode 100644 internal/client/db/db.go delete mode 100644 internal/client/db/postgres/client.go delete mode 100644 internal/client/db/postgres/postgres.go delete mode 100644 internal/storage/users/postgres/client.go diff --git a/internal/app/service_provider.go b/internal/app/service_provider.go index c11cd16..fd3d39b 100644 --- a/internal/app/service_provider.go +++ b/internal/app/service_provider.go @@ -2,8 +2,11 @@ package app import ( "context" + "github.com/Str1m/auth/internal/client/db" + "github.com/Str1m/auth/internal/client/db/transaction" modelService "github.com/Str1m/auth/internal/model" "github.com/Str1m/auth/internal/service/user" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "log" "log/slog" @@ -45,6 +48,14 @@ type Storage interface { Delete(ctx context.Context, id int64) error } +type Transactor interface { + BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) +} + +type TxManager interface { + ReadCommitted(ctx context.Context, f db.Handler) error +} + type ServiceProvider struct { cls *closer.Closer log *slog.Logger @@ -52,8 +63,9 @@ type ServiceProvider struct { storageConfig StorageConfig grpcConfig GRPCConfig - dbClient *postgres.ClientPG - dbLayer Storage + dbClient *db.Client + txManager *transaction.TxManager + dbLayer Storage userService Service userAPI *userAPI.Implementation @@ -112,19 +124,26 @@ func (s *ServiceProvider) GRPCConfig() GRPCConfig { return s.grpcConfig } -func (s *ServiceProvider) GetDBClient(ctx context.Context) *postgres.ClientPG { +func (s *ServiceProvider) GetDBClient(ctx context.Context) *db.Client { if s.dbClient == nil { p, err := pgxpool.New(ctx, s.GetStorageConfig().DSN()) if err != nil { log.Fatalln("err") } - s.dbClient = postgres.NewClientPG(p) + s.dbClient = db.NewClient(p) } return s.dbClient } +func (s *ServiceProvider) GetTxManager(ctx context.Context) *transaction.TxManager { + if s.txManager == nil { + s.txManager = transaction.NewTransactionManager(s.GetDBClient(ctx)) + } + return s.txManager +} + func (s *ServiceProvider) GetDBLayer(ctx context.Context) Storage { if s.dbLayer == nil { s.dbLayer = postgres.NewStoragePG(s.GetDBClient(ctx)) @@ -134,7 +153,7 @@ func (s *ServiceProvider) GetDBLayer(ctx context.Context) Storage { func (s *ServiceProvider) UserService(ctx context.Context) Service { if s.userService == nil { - s.userService = user.NewService(s.GetLog(), s.GetDBLayer(ctx)) + s.userService = user.NewService(s.GetLog(), s.GetDBLayer(ctx), s.GetTxManager(ctx)) } return s.userService } diff --git a/internal/client/db/client.go b/internal/client/db/client.go new file mode 100644 index 0000000..1db4d32 --- /dev/null +++ b/internal/client/db/client.go @@ -0,0 +1,95 @@ +package db + +import ( + "context" + "github.com/georgysavva/scany/v2/pgxscan" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type Handler func(ctx context.Context) error + +type Query struct { + Name string + QueryRaw string +} +type key string + +const ( + TxKey key = "tx" +) + +type DBLayer interface { + Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) + Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) + QueryRow(ctx context.Context, sql string, args ...any) pgx.Row + Ping(ctx context.Context) error + Close() + BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) +} + +type Client struct { + db DBLayer +} + +func NewClient(db DBLayer) *Client { + return &Client{db: db} +} + +func (p *Client) GetDB() DBLayer { + return p.db +} + +func (p *Client) ExecContext(ctx context.Context, q Query, args ...any) (pgconn.CommandTag, error) { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.Exec(ctx, q.QueryRaw, args...) + } + return p.db.Exec(ctx, q.QueryRaw, args...) +} + +func (p *Client) QueryContext(ctx context.Context, q Query, args ...any) (pgx.Rows, error) { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.Query(ctx, q.QueryRaw, args...) + } + return p.db.Query(ctx, q.QueryRaw, args...) +} + +func (p *Client) QueryRowContext(ctx context.Context, q Query, args ...any) pgx.Row { + tx, ok := ctx.Value(TxKey).(pgx.Tx) + if ok { + return tx.QueryRow(ctx, q.QueryRaw, args...) + } + + return p.db.QueryRow(ctx, q.QueryRaw, args...) +} + +func (p *Client) Ping(ctx context.Context) error { + return p.db.Ping(ctx) +} + +func (p *Client) Close() { + p.db.Close() +} + +func (p *Client) ScanOneContext(ctx context.Context, dest any, q Query, args ...any) error { + row, err := p.QueryContext(ctx, q, args...) + if err != nil { + return err + } + return pgxscan.ScanOne(dest, row) +} + +func (p *Client) ScanAllContext(ctx context.Context, dest any, q Query, args ...any) error { + row, err := p.QueryContext(ctx, q, args...) + if err != nil { + return err + } + + return pgxscan.ScanAll(dest, row) +} + +func MakeContextTx(ctx context.Context, tx pgx.Tx) context.Context { + return context.WithValue(ctx, TxKey, tx) +} diff --git a/internal/client/db/db.go b/internal/client/db/db.go deleted file mode 100644 index 134bffc..0000000 --- a/internal/client/db/db.go +++ /dev/null @@ -1,54 +0,0 @@ -package db - -import ( - "context" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" -) - -type Handler func(ctx context.Context) error - -type Client interface { - DB() DB - Close() error -} - -type Query struct { - Name string - QueryRaw string -} - -type SQLExecer interface { - NamedExecer - QueryExecer -} - -type NamedExecer interface { - ScanOneContext(ctx context.Context, dest interface{}, q Query, args ...interface{}) error - ScanAllContext(ctx context.Context, dest interface{}, q Query, args ...interface{}) error -} - -type QueryExecer interface { - ExecContext(ctx context.Context, q Query, args ...interface{}) (pgconn.CommandTag, error) - QueryContext(ctx context.Context, q Query, args ...interface{}) (pgx.Rows, error) - QueryRowContext(ctx context.Context, q Query, args ...interface{}) pgx.Row -} - -type Pinger interface { - Ping(ctx context.Context) error -} - -type DB interface { - SQLExecer - Pinger - Transactor - Close() -} - -type Transactor interface { - BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) -} - -type TxManager interface { - ReadCommitted(ctx context.Context, f Handler) error -} diff --git a/internal/client/db/postgres/client.go b/internal/client/db/postgres/client.go deleted file mode 100644 index 1a08605..0000000 --- a/internal/client/db/postgres/client.go +++ /dev/null @@ -1,63 +0,0 @@ -package postgres - -import ( - "context" - "github.com/Str1m/auth/internal/client/db" - "github.com/jackc/pgx/v5/pgxpool" -) - -//type DB interface { -// SQLExecer -// Pinger -// Close() -//} -// -//type SQLExecer interface { -// NamedExecer -// QueryExecer -//} -// -//type NamedExecer interface { -// ScanOneContext(ctx context.Context, dest interface{}, q db.Query, args ...interface{}) error -// ScanAllContext(ctx context.Context, dest interface{}, q db.Query, args ...interface{}) error -//} -// -//type QueryExecer interface { -// ExecContext(ctx context.Context, q db.Query, args ...interface{}) (pgconn.CommandTag, error) -// QueryContext(ctx context.Context, q db.Query, args ...interface{}) (pgx.Rows, error) -// QueryRowContext(ctx context.Context, q db.Query, args ...interface{}) pgx.Row -//} -// -//type Pinger interface { -// Ping(ctx context.Context) error -//} -// -//type Client interface { -// DB() DB -// Close() error -//} - -type pgClient struct { - masterDB db.DB -} - -func NewPGClient(ctx context.Context, dsn string) (db.Client, error) { - db, err := pgxpool.New(ctx, dsn) - if err != nil { - return nil, err - } - return &pgClient{ - masterDB: &pg{db: db}, - }, nil -} - -func (p *pgClient) DB() db.DB { - return p.masterDB -} - -func (p *pgClient) Close() error { - if p.masterDB != nil { - p.masterDB.Close() - } - return nil -} diff --git a/internal/client/db/postgres/postgres.go b/internal/client/db/postgres/postgres.go deleted file mode 100644 index ec89099..0000000 --- a/internal/client/db/postgres/postgres.go +++ /dev/null @@ -1,86 +0,0 @@ -package postgres - -import ( - "context" - "github.com/Str1m/auth/internal/client/db" - "github.com/georgysavva/scany/v2/pgxscan" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgxpool" -) - -type key string - -const ( - TxKey key = "tx" -) - -type pg struct { - db *pgxpool.Pool -} - -func New(db *pgxpool.Pool) db.DB { - return &pg{ - db: db, - } -} - -func (p *pg) ExecContext(ctx context.Context, q db.Query, args ...any) (pgconn.CommandTag, error) { - tx, ok := ctx.Value(TxKey).(pgx.Tx) - if ok { - return tx.Exec(ctx, q.QueryRaw, args...) - } - return p.db.Exec(ctx, q.QueryRaw, args...) -} - -func (p *pg) QueryContext(ctx context.Context, q db.Query, args ...any) (pgx.Rows, error) { - tx, ok := ctx.Value(TxKey).(pgx.Tx) - if ok { - return tx.Query(ctx, q.QueryRaw, args...) - } - return p.db.Query(ctx, q.QueryRaw, args...) -} - -func (p *pg) QueryRowContext(ctx context.Context, q db.Query, args ...any) pgx.Row { - tx, ok := ctx.Value(TxKey).(pgx.Tx) - if ok { - return tx.QueryRow(ctx, q.QueryRaw, args...) - } - - return p.db.QueryRow(ctx, q.QueryRaw, args...) -} - -func (p *pg) Ping(ctx context.Context) error { - - return p.db.Ping(ctx) -} - -func (p *pg) Close() { - p.db.Close() -} - -func (p *pg) ScanOneContext(ctx context.Context, dest any, q db.Query, args ...any) error { - row, err := p.QueryContext(ctx, q, args...) - if err != nil { - return err - } - return pgxscan.ScanOne(dest, row) -} - -func (p *pg) ScanAllContext(ctx context.Context, dest any, q db.Query, args ...any) error { - row, err := p.QueryContext(ctx, q, args...) - if err != nil { - return err - } - - return pgxscan.ScanAll(dest, row) -} - -// // ------------------------------- -func (p *pg) BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) { - return p.db.BeginTx(ctx, txOptions) -} - -func MakeContextTx(ctx context.Context, tx pgx.Tx) context.Context { - return context.WithValue(ctx, TxKey, tx) -} diff --git a/internal/client/db/transaction/transaction.go b/internal/client/db/transaction/transaction.go index cbdcf31..ee8622e 100644 --- a/internal/client/db/transaction/transaction.go +++ b/internal/client/db/transaction/transaction.go @@ -2,39 +2,40 @@ package transaction import ( "context" - "github.com/Str1m/auth/internal/client/db" - "github.com/Str1m/auth/internal/client/db/postgres" + db2 "github.com/Str1m/auth/internal/client/db" "github.com/jackc/pgx/v5" "github.com/pkg/errors" ) -type manager struct { - db db.Transactor +// TODO: Проверить работу транзакций + +type TxManager struct { + db *db2.Client } -func NewTransactionManager(db db.Transactor) db.TxManager { - return &manager{ +func NewTransactionManager(db *db2.Client) *TxManager { + return &TxManager{ db: db, } } -func (m *manager) ReadCommitted(ctx context.Context, f db.Handler) error { +func (m *TxManager) ReadCommitted(ctx context.Context, f db2.Handler) error { txOpts := pgx.TxOptions{IsoLevel: pgx.ReadCommitted} return m.transaction(ctx, txOpts, f) } -func (m *manager) transaction(ctx context.Context, opts pgx.TxOptions, f db.Handler) (err error) { - tx, ok := ctx.Value(postgres.TxKey).(pgx.Tx) +func (m *TxManager) transaction(ctx context.Context, opts pgx.TxOptions, f db2.Handler) (err error) { + tx, ok := ctx.Value(db2.TxKey).(pgx.Tx) if ok { return f(ctx) } - tx, err = m.db.BeginTx(ctx, opts) + tx, err = m.db.GetDB().BeginTx(ctx, opts) if err != nil { return err } - ctx = postgres.MakeContextTx(ctx, tx) + ctx = db2.MakeContextTx(ctx, tx) defer func() { if r := recover(); r != nil { diff --git a/internal/service/user/service.go b/internal/service/user/service.go index aa7e300..2c912ea 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -2,6 +2,7 @@ package user import ( "context" + "github.com/Str1m/auth/internal/client/db/transaction" modelService "github.com/Str1m/auth/internal/model" "log/slog" ) @@ -15,12 +16,13 @@ type DBLayer interface { type Service struct { log *slog.Logger UserDBClient DBLayer - //TxManager db.TxManager + txManager *transaction.TxManager } -func NewService(log *slog.Logger, dbClient DBLayer) *Service { +func NewService(log *slog.Logger, dbClient DBLayer, txManager *transaction.TxManager) *Service { return &Service{ log: log, UserDBClient: dbClient, + txManager: txManager, } } diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 3b20491..7dac14f 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -1,18 +1,9 @@ package storage import ( - "context" "errors" - modelService "github.com/Str1m/auth/internal/model" ) var ( ErrUserNotFound = errors.New("user not found") ) - -type Repository interface { - Create(ctx context.Context, info *modelService.UserInfo, hashedPassword []byte) (int64, error) - Get(ctx context.Context, id int64) (*modelService.User, error) - Update(ctx context.Context, id int64, name, email *string) error - Delete(ctx context.Context, id int64) error -} diff --git a/internal/storage/users/postgres/client.go b/internal/storage/users/postgres/client.go deleted file mode 100644 index 330487c..0000000 --- a/internal/storage/users/postgres/client.go +++ /dev/null @@ -1,82 +0,0 @@ -package postgres - -import ( - "context" - "github.com/Str1m/auth/internal/client/db" - "github.com/georgysavva/scany/v2/pgxscan" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" -) - -// TODO: Пусть будет врапер -type key string - -const ( - TxKey key = "tx" -) - -type DBLayer interface { - Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) - Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) - QueryRow(ctx context.Context, sql string, args ...any) pgx.Row - Ping(ctx context.Context) error - Close() -} - -type ClientPG struct { - pg DBLayer -} - -func NewClientPG(pg DBLayer) *ClientPG { - return &ClientPG{pg: pg} -} - -func (p *ClientPG) ExecContext(ctx context.Context, q db.Query, args ...any) (pgconn.CommandTag, error) { - tx, ok := ctx.Value(TxKey).(pgx.Tx) - if ok { - return tx.Exec(ctx, q.QueryRaw, args...) - } - return p.pg.Exec(ctx, q.QueryRaw, args...) -} - -func (p *ClientPG) QueryContext(ctx context.Context, q db.Query, args ...any) (pgx.Rows, error) { - tx, ok := ctx.Value(TxKey).(pgx.Tx) - if ok { - return tx.Query(ctx, q.QueryRaw, args...) - } - return p.pg.Query(ctx, q.QueryRaw, args...) -} - -func (p *ClientPG) QueryRowContext(ctx context.Context, q db.Query, args ...any) pgx.Row { - tx, ok := ctx.Value(TxKey).(pgx.Tx) - if ok { - return tx.QueryRow(ctx, q.QueryRaw, args...) - } - - return p.pg.QueryRow(ctx, q.QueryRaw, args...) -} - -func (p *ClientPG) Ping(ctx context.Context) error { - return p.pg.Ping(ctx) -} - -func (p *ClientPG) Close() { - p.pg.Close() -} - -func (p *ClientPG) ScanOneContext(ctx context.Context, dest any, q db.Query, args ...any) error { - row, err := p.QueryContext(ctx, q, args...) - if err != nil { - return err - } - return pgxscan.ScanOne(dest, row) -} - -func (p *ClientPG) ScanAllContext(ctx context.Context, dest any, q db.Query, args ...any) error { - row, err := p.QueryContext(ctx, q, args...) - if err != nil { - return err - } - - return pgxscan.ScanAll(dest, row) -} diff --git a/internal/storage/users/postgres/postgres.go b/internal/storage/users/postgres/postgres.go index 1b72590..b9a8317 100644 --- a/internal/storage/users/postgres/postgres.go +++ b/internal/storage/users/postgres/postgres.go @@ -27,10 +27,10 @@ const ( ) type StoragePG struct { - client *ClientPG + client *db.Client } -func NewStoragePG(db *ClientPG) *StoragePG { +func NewStoragePG(db *db.Client) *StoragePG { return &StoragePG{client: db} } From 34ed1f913f332b1fec0fafeef1c98dc2bd7088c9 Mon Sep 17 00:00:00 2001 From: Str1m Date: Fri, 11 Apr 2025 17:37:33 +0300 Subject: [PATCH 7/7] delete DBLayer interface --- cmd/auth/main.go | 21 +---------- internal/api/user/service.go | 1 + internal/app/service_provider.go | 19 +++++----- internal/client/db/{ => postgres}/client.go | 37 ++++++++----------- internal/client/db/transaction/transaction.go | 16 ++++---- internal/service/service.go | 7 ---- internal/service/user/service.go | 3 +- internal/storage/users/postgres/postgres.go | 17 +++++---- 8 files changed, 47 insertions(+), 74 deletions(-) rename internal/client/db/{ => postgres}/client.go (51%) diff --git a/cmd/auth/main.go b/cmd/auth/main.go index 84fc3a1..1c51e53 100644 --- a/cmd/auth/main.go +++ b/cmd/auth/main.go @@ -2,29 +2,12 @@ package main import ( "context" - "github.com/Str1m/auth/internal/app" "log" -) -const DSN = "test" + "github.com/Str1m/auth/internal/app" +) func main() { - - //cfg, err := env.NewPGConfig() - //if err != nil { - // log.Fatalf("failed to get pg config: %s", err.Error()) - //} - // - - // - //p, err := pgxpool.New(ctx, cfg.DSN()) - //if err != nil { - // log.Fatalln("err") - //} - // - //client := postgres.NewClientPG(p) - // - //DBLayer := postgres.NewStoragePG(client) ctx := context.Background() a, err := app.NewApp(ctx) diff --git a/internal/api/user/service.go b/internal/api/user/service.go index 945ec42..ffabf57 100644 --- a/internal/api/user/service.go +++ b/internal/api/user/service.go @@ -2,6 +2,7 @@ package user import ( "context" + modelService "github.com/Str1m/auth/internal/model" desc "github.com/Str1m/auth/pkg/auth_v1" ) diff --git a/internal/app/service_provider.go b/internal/app/service_provider.go index fd3d39b..6f14804 100644 --- a/internal/app/service_provider.go +++ b/internal/app/service_provider.go @@ -2,16 +2,17 @@ package app import ( "context" - "github.com/Str1m/auth/internal/client/db" + "log" + "log/slog" + "os" + "syscall" + + pgClient "github.com/Str1m/auth/internal/client/db/postgres" "github.com/Str1m/auth/internal/client/db/transaction" modelService "github.com/Str1m/auth/internal/model" "github.com/Str1m/auth/internal/service/user" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" - "log" - "log/slog" - "os" - "syscall" userAPI "github.com/Str1m/auth/internal/api/user" "github.com/Str1m/auth/internal/closer" @@ -53,7 +54,7 @@ type Transactor interface { } type TxManager interface { - ReadCommitted(ctx context.Context, f db.Handler) error + ReadCommitted(ctx context.Context, f pgClient.Handler) error } type ServiceProvider struct { @@ -63,7 +64,7 @@ type ServiceProvider struct { storageConfig StorageConfig grpcConfig GRPCConfig - dbClient *db.Client + dbClient *pgClient.ClientPG txManager *transaction.TxManager dbLayer Storage @@ -124,14 +125,14 @@ func (s *ServiceProvider) GRPCConfig() GRPCConfig { return s.grpcConfig } -func (s *ServiceProvider) GetDBClient(ctx context.Context) *db.Client { +func (s *ServiceProvider) GetDBClient(ctx context.Context) *pgClient.ClientPG { if s.dbClient == nil { p, err := pgxpool.New(ctx, s.GetStorageConfig().DSN()) if err != nil { log.Fatalln("err") } - s.dbClient = db.NewClient(p) + s.dbClient = pgClient.NewClient(p) } return s.dbClient diff --git a/internal/client/db/client.go b/internal/client/db/postgres/client.go similarity index 51% rename from internal/client/db/client.go rename to internal/client/db/postgres/client.go index 1db4d32..24dfe25 100644 --- a/internal/client/db/client.go +++ b/internal/client/db/postgres/client.go @@ -1,10 +1,12 @@ -package db +package postgres import ( "context" + "github.com/georgysavva/scany/v2/pgxscan" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" ) type Handler func(ctx context.Context) error @@ -19,28 +21,19 @@ const ( TxKey key = "tx" ) -type DBLayer interface { - Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) - Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) - QueryRow(ctx context.Context, sql string, args ...any) pgx.Row - Ping(ctx context.Context) error - Close() - BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) -} - -type Client struct { - db DBLayer +type ClientPG struct { + db *pgxpool.Pool } -func NewClient(db DBLayer) *Client { - return &Client{db: db} +func NewClient(db *pgxpool.Pool) *ClientPG { + return &ClientPG{db: db} } -func (p *Client) GetDB() DBLayer { +func (p *ClientPG) GetDB() *pgxpool.Pool { return p.db } -func (p *Client) ExecContext(ctx context.Context, q Query, args ...any) (pgconn.CommandTag, error) { +func (p *ClientPG) ExecContext(ctx context.Context, q Query, args ...any) (pgconn.CommandTag, error) { tx, ok := ctx.Value(TxKey).(pgx.Tx) if ok { return tx.Exec(ctx, q.QueryRaw, args...) @@ -48,7 +41,7 @@ func (p *Client) ExecContext(ctx context.Context, q Query, args ...any) (pgconn. return p.db.Exec(ctx, q.QueryRaw, args...) } -func (p *Client) QueryContext(ctx context.Context, q Query, args ...any) (pgx.Rows, error) { +func (p *ClientPG) QueryContext(ctx context.Context, q Query, args ...any) (pgx.Rows, error) { tx, ok := ctx.Value(TxKey).(pgx.Tx) if ok { return tx.Query(ctx, q.QueryRaw, args...) @@ -56,7 +49,7 @@ func (p *Client) QueryContext(ctx context.Context, q Query, args ...any) (pgx.Ro return p.db.Query(ctx, q.QueryRaw, args...) } -func (p *Client) QueryRowContext(ctx context.Context, q Query, args ...any) pgx.Row { +func (p *ClientPG) QueryRowContext(ctx context.Context, q Query, args ...any) pgx.Row { tx, ok := ctx.Value(TxKey).(pgx.Tx) if ok { return tx.QueryRow(ctx, q.QueryRaw, args...) @@ -65,15 +58,15 @@ func (p *Client) QueryRowContext(ctx context.Context, q Query, args ...any) pgx. return p.db.QueryRow(ctx, q.QueryRaw, args...) } -func (p *Client) Ping(ctx context.Context) error { +func (p *ClientPG) Ping(ctx context.Context) error { return p.db.Ping(ctx) } -func (p *Client) Close() { +func (p *ClientPG) Close() { p.db.Close() } -func (p *Client) ScanOneContext(ctx context.Context, dest any, q Query, args ...any) error { +func (p *ClientPG) ScanOneContext(ctx context.Context, dest any, q Query, args ...any) error { row, err := p.QueryContext(ctx, q, args...) if err != nil { return err @@ -81,7 +74,7 @@ func (p *Client) ScanOneContext(ctx context.Context, dest any, q Query, args ... return pgxscan.ScanOne(dest, row) } -func (p *Client) ScanAllContext(ctx context.Context, dest any, q Query, args ...any) error { +func (p *ClientPG) ScanAllContext(ctx context.Context, dest any, q Query, args ...any) error { row, err := p.QueryContext(ctx, q, args...) if err != nil { return err diff --git a/internal/client/db/transaction/transaction.go b/internal/client/db/transaction/transaction.go index ee8622e..bf23315 100644 --- a/internal/client/db/transaction/transaction.go +++ b/internal/client/db/transaction/transaction.go @@ -2,7 +2,8 @@ package transaction import ( "context" - db2 "github.com/Str1m/auth/internal/client/db" + + "github.com/Str1m/auth/internal/client/db/postgres" "github.com/jackc/pgx/v5" "github.com/pkg/errors" ) @@ -10,22 +11,22 @@ import ( // TODO: Проверить работу транзакций type TxManager struct { - db *db2.Client + db *postgres.ClientPG } -func NewTransactionManager(db *db2.Client) *TxManager { +func NewTransactionManager(db *postgres.ClientPG) *TxManager { return &TxManager{ db: db, } } -func (m *TxManager) ReadCommitted(ctx context.Context, f db2.Handler) error { +func (m *TxManager) ReadCommitted(ctx context.Context, f postgres.Handler) error { txOpts := pgx.TxOptions{IsoLevel: pgx.ReadCommitted} return m.transaction(ctx, txOpts, f) } -func (m *TxManager) transaction(ctx context.Context, opts pgx.TxOptions, f db2.Handler) (err error) { - tx, ok := ctx.Value(db2.TxKey).(pgx.Tx) +func (m *TxManager) transaction(ctx context.Context, opts pgx.TxOptions, f postgres.Handler) (err error) { + tx, ok := ctx.Value(postgres.TxKey).(pgx.Tx) if ok { return f(ctx) } @@ -35,7 +36,7 @@ func (m *TxManager) transaction(ctx context.Context, opts pgx.TxOptions, f db2.H return err } - ctx = db2.MakeContextTx(ctx, tx) + ctx = postgres.MakeContextTx(ctx, tx) defer func() { if r := recover(); r != nil { @@ -45,7 +46,6 @@ func (m *TxManager) transaction(ctx context.Context, opts pgx.TxOptions, f db2.H if err != nil { if errRollback := tx.Rollback(ctx); err != nil { err = errors.Wrapf(err, "errRollback: %v", errRollback) - } return } diff --git a/internal/service/service.go b/internal/service/service.go index 1e5e733..2b030cb 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -7,10 +7,3 @@ import ( var ( ErrPassNotEqual = errors.New("passwords are not equal") ) - -//type Service interface { -// Create(ctx context.Context, userInfo *modelService.UserInfo) (int64, error) -// Get(ctx context.Context, id int64) (*modelService.User, error) -// Update(ctx context.Context, id int64, name *string, email *string) error -// Delete(ctx context.Context, id int64) error -//} diff --git a/internal/service/user/service.go b/internal/service/user/service.go index 2c912ea..3158072 100644 --- a/internal/service/user/service.go +++ b/internal/service/user/service.go @@ -2,9 +2,10 @@ package user import ( "context" + "log/slog" + "github.com/Str1m/auth/internal/client/db/transaction" modelService "github.com/Str1m/auth/internal/model" - "log/slog" ) type DBLayer interface { diff --git a/internal/storage/users/postgres/postgres.go b/internal/storage/users/postgres/postgres.go index b9a8317..ea80499 100644 --- a/internal/storage/users/postgres/postgres.go +++ b/internal/storage/users/postgres/postgres.go @@ -3,10 +3,11 @@ package postgres import ( "context" "fmt" - "github.com/Str1m/auth/internal/client/db" - modelService "github.com/Str1m/auth/internal/model" "log" + "github.com/Str1m/auth/internal/client/db/postgres" + modelService "github.com/Str1m/auth/internal/model" + "github.com/Str1m/auth/internal/storage" "github.com/Str1m/auth/internal/storage/users/converter" modelRepo "github.com/Str1m/auth/internal/storage/users/model" @@ -27,10 +28,10 @@ const ( ) type StoragePG struct { - client *db.Client + client *postgres.ClientPG } -func NewStoragePG(db *db.Client) *StoragePG { +func NewStoragePG(db *postgres.ClientPG) *StoragePG { return &StoragePG{client: db} } @@ -51,7 +52,7 @@ func (r *StoragePG) Create(ctx context.Context, info *modelService.UserInfo, has return 0, fmt.Errorf("%s: %w", op, err) } - q := db.Query{ + q := postgres.Query{ Name: op, QueryRaw: query, } @@ -77,7 +78,7 @@ func (r *StoragePG) Get(ctx context.Context, id int64) (*modelService.User, erro if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } - q := db.Query{ + q := postgres.Query{ Name: op, QueryRaw: query, } @@ -111,7 +112,7 @@ func (r *StoragePG) Update(ctx context.Context, id int64, name, email *string) e return fmt.Errorf("%s: %w", op, err) } - q := db.Query{ + q := postgres.Query{ Name: op, QueryRaw: query, } @@ -140,7 +141,7 @@ func (r *StoragePG) Delete(ctx context.Context, id int64) error { return fmt.Errorf("%s: %w", op, err) } - q := db.Query{ + q := postgres.Query{ Name: op, QueryRaw: query, }