Skip to content

Commit

Permalink
feat(syahidfrd#8): transaction on usecase layer
Browse files Browse the repository at this point in the history
  • Loading branch information
chud-lori committed Feb 19, 2025
1 parent f352316 commit 9398290
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 139 deletions.
8 changes: 4 additions & 4 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,17 @@ func main() {

// Setup repository
redisRepo := redisRepository.NewRedisRepository(cacheInstance)
todoRepo := pgsqlRepository.NewPgsqlTodoRepository(dbInstance)
userRepo := pgsqlRepository.NewPgsqlUserRepository(dbInstance)
todoRepo := pgsqlRepository.NewPgsqlTodoRepository()
userRepo := pgsqlRepository.NewPgsqlUserRepository()

// Setup Service
cryptoSvc := crypto.NewCryptoService()
jwtSvc := jwt.NewJWTService(configApp.JWTSecretKey)

// Setup usecase
ctxTimeout := time.Duration(configApp.ContextTimeout) * time.Second
todoUC := usecase.NewTodoUsecase(todoRepo, redisRepo, ctxTimeout)
authUC := usecase.NewAuthUsecase(userRepo, cryptoSvc, jwtSvc, ctxTimeout)
todoUC := usecase.NewTodoUsecase(dbInstance, todoRepo, redisRepo, ctxTimeout)
authUC := usecase.NewAuthUsecase(dbInstance, userRepo, cryptoSvc, jwtSvc, ctxTimeout)

// Setup app middleware
appMiddleware := appMiddleware.NewMiddleware(jwtSvc)
Expand Down
21 changes: 21 additions & 0 deletions domain/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package domain

import (
"context"
"database/sql"
)

// Transaction interface (wraps DBTX)
type Transaction interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
Commit() error
Rollback() error
}

// Database interface
type Database interface {
BeginTx(ctx context.Context) (Transaction, error)
Close() error
}
10 changes: 5 additions & 5 deletions domain/todo.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ type Todo struct {

// TodoRepository represent the todos repository contract
type TodoRepository interface {
Create(ctx context.Context, todo *Todo) error
GetByID(ctx context.Context, id int64) (Todo, error)
Fetch(ctx context.Context) ([]Todo, error)
Update(ctx context.Context, todo *Todo) error
Delete(ctx context.Context, id int64) error
Create(ctx context.Context, tx Transaction, todo *Todo) error
GetByID(ctx context.Context, tx Transaction, id int64) (Todo, error)
Fetch(ctx context.Context, tx Transaction) ([]Todo, error)
Update(ctx context.Context, tx Transaction, todo *Todo) error
Delete(ctx context.Context, tx Transaction, id int64) error
}

// TodoUsecase represent the todos usecase contract
Expand Down
4 changes: 2 additions & 2 deletions domain/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ type User struct {

// UserRepository represent the todos repository contract
type UserRepository interface {
Create(ctx context.Context, user *User) error
GetByEmail(ctx context.Context, email string) (User, error)
Create(ctx context.Context, tx Transaction, user *User) error
GetByEmail(ctx context.Context, tx Transaction, email string) (User, error)
}
55 changes: 50 additions & 5 deletions infrastructure/datastore/database.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,73 @@
package datastore

import (
"context"
"database/sql"
"net/url"
"time"

_ "github.com/lib/pq"
"github.com/syahidfrd/go-boilerplate/domain"
)

type Database struct {
db *sql.DB
}

// NewDatabase will create new database instance
func NewDatabase(databaseURL string) (db *sql.DB, err error) {
func NewDatabase(databaseURL string) (domain.Database, error) {
parseDBUrl, _ := url.Parse(databaseURL)
db, err = sql.Open(parseDBUrl.Scheme, databaseURL)
db, err := sql.Open(parseDBUrl.Scheme, databaseURL)
if err != nil {
return
return nil, err
}

if err = db.Ping(); err != nil {
return
return nil, err
}

db.SetConnMaxLifetime(time.Minute * 5)
db.SetMaxIdleConns(0)
db.SetMaxOpenConns(5)

return
return &Database{db: db}, nil
}

func (p *Database) BeginTx(ctx context.Context) (domain.Transaction, error) {
tx, err := p.db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelReadCommitted,
})
if err != nil {
return nil, err
}
return &Transaction{tx: tx}, nil
}

func (p *Database) Close() error {
return p.db.Close()
}

// Transaction implements Transaction interface
type Transaction struct {
tx *sql.Tx
}

func (t *Transaction) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
return t.tx.ExecContext(ctx, query, args...)
}

func (t *Transaction) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
return t.tx.QueryRowContext(ctx, query, args...)
}

func (t *Transaction) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
return t.tx.QueryContext(ctx, query, args...)
}

func (t *Transaction) Commit() error {
return t.tx.Commit()
}

func (t *Transaction) Rollback() error {
return t.tx.Rollback()
}
86 changes: 8 additions & 78 deletions repository/pgsql/pgsql_todo.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,34 @@ package pgsql

import (
"context"
"database/sql"
"fmt"

"github.com/syahidfrd/go-boilerplate/domain"
)

type pgsqlTodoRepository struct {
db *sql.DB
}

// NewPgsqlTodoRepository will create a new todoRepository object representation of TodoRepository interface
func NewPgsqlTodoRepository(db *sql.DB) *pgsqlTodoRepository {
return &pgsqlTodoRepository{
db: db,
}
// NewPgsqlTodoRepository will create new an todoRepository object representation of TodoRepository interface
func NewPgsqlTodoRepository() *pgsqlTodoRepository {
return &pgsqlTodoRepository{}
}

func (r *pgsqlTodoRepository) Create(ctx context.Context, todo *domain.Todo) (err error) {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
err = fmt.Errorf("failed to begin transaction: %v", err)
return
}
defer tx.Rollback()

func (r *pgsqlTodoRepository) Create(ctx context.Context, tx domain.Transaction, todo *domain.Todo) (err error) {
query := "INSERT INTO todos (name, created_at, updated_at) VALUES ($1, $2, $3)"
_, err = tx.ExecContext(ctx, query, todo.Name, todo.CreatedAt, todo.UpdatedAt)

if err != nil {
err = fmt.Errorf("failed to insert todo: %v", err)
return
}

if err = tx.Commit(); err != nil {
err = fmt.Errorf("failed to commit transaction: %v", err)
return
}

return
}

func (r *pgsqlTodoRepository) GetByID(ctx context.Context, id int64) (todo domain.Todo, err error) {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
err = fmt.Errorf("failed to begin transaction: %v", err)
return
}
defer tx.Rollback()

func (r *pgsqlTodoRepository) GetByID(ctx context.Context, tx domain.Transaction, id int64) (todo domain.Todo, err error) {
query := "SELECT id, name, created_at, updated_at FROM todos WHERE id = $1"
err = tx.QueryRowContext(ctx, query, id).Scan(&todo.ID, &todo.Name, &todo.CreatedAt, &todo.UpdatedAt)

if err = tx.Commit(); err != nil {
err = fmt.Errorf("failed to commit transaction: %v", err)
return
}

return
}

func (r *pgsqlTodoRepository) Fetch(ctx context.Context) (todos []domain.Todo, err error) {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
err = fmt.Errorf("failed to begin transaction: %v", err)
return
}

func (r *pgsqlTodoRepository) Fetch(ctx context.Context, tx domain.Transaction) (todos []domain.Todo, err error) {
query := "SELECT id, name, created_at, updated_at FROM todos"
rows, err := tx.QueryContext(ctx, query)

if err != nil {
return todos, err
}
Expand All @@ -88,21 +46,10 @@ func (r *pgsqlTodoRepository) Fetch(ctx context.Context) (todos []domain.Todo, e
todos = append(todos, todo)
}

if err = tx.Commit(); err != nil {
return todos, fmt.Errorf("failed to commit transaction: %v", err)
}

return todos, nil
}

func (r *pgsqlTodoRepository) Update(ctx context.Context, todo *domain.Todo) (err error) {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
err = fmt.Errorf("failed to begin transaction: %v", err)
return
}
defer tx.Rollback()

func (r *pgsqlTodoRepository) Update(ctx context.Context, tx domain.Transaction, todo *domain.Todo) (err error) {
query := "UPDATE todos SET name = $1, updated_at = $2 WHERE id = $3"
res, err := tx.ExecContext(ctx, query, todo.Name, todo.UpdatedAt, todo.ID)
if err != nil {
Expand All @@ -118,22 +65,10 @@ func (r *pgsqlTodoRepository) Update(ctx context.Context, todo *domain.Todo) (er
err = fmt.Errorf("weird behavior, total affected: %d", affect)
}

if err = tx.Commit(); err != nil {
err = fmt.Errorf("failed to commit transaction: %v", err)
return
}

return
}

func (r *pgsqlTodoRepository) Delete(ctx context.Context, id int64) (err error) {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
err = fmt.Errorf("failed to begin transaction: %v", err)
return
}
defer tx.Rollback()

func (r *pgsqlTodoRepository) Delete(ctx context.Context, tx domain.Transaction, id int64) (err error) {
query := "DELETE FROM todos WHERE id = $1"
res, err := tx.ExecContext(ctx, query, id)
if err != nil {
Expand All @@ -149,10 +84,5 @@ func (r *pgsqlTodoRepository) Delete(ctx context.Context, id int64) (err error)
err = fmt.Errorf("weird behavior, total affected: %d", affect)
}

if err = tx.Commit(); err != nil {
err = fmt.Errorf("failed to commit transaction: %v", err)
return
}

return
}
37 changes: 4 additions & 33 deletions repository/pgsql/pgsql_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,25 @@ package pgsql

import (
"context"
"database/sql"
"fmt"

"github.com/syahidfrd/go-boilerplate/domain"
)

type pgsqlUserRepository struct {
db *sql.DB
}

func NewPgsqlUserRepository(db *sql.DB) *pgsqlUserRepository {
return &pgsqlUserRepository{
db: db,
}
func NewPgsqlUserRepository() *pgsqlUserRepository {
return &pgsqlUserRepository{}
}

func (r *pgsqlUserRepository) Create(ctx context.Context, user *domain.User) (err error) {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return
}
defer tx.Rollback()

func (r *pgsqlUserRepository) Create(ctx context.Context, tx domain.Transaction, user *domain.User) (err error) {
query := "INSERT INTO users (email, password, created_at, updated_at) VALUES ($1, $2, $3, $4)"
_, err = tx.ExecContext(ctx, query, user.Email, user.Password, user.CreatedAt, user.UpdatedAt)

if err = tx.Commit(); err != nil {
err = fmt.Errorf("failed to commit transaction: %v", err)
return
}

return
}

func (r *pgsqlUserRepository) GetByEmail(ctx context.Context, email string) (user domain.User, err error) {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return
}
defer tx.Rollback()

func (r *pgsqlUserRepository) GetByEmail(ctx context.Context, tx domain.Transaction, email string) (user domain.User, err error) {
query := "SELECT id, email, password, created_at, updated_at FROM users WHERE email = $1"
err = tx.QueryRowContext(ctx, query, email).Scan(&user.ID, &user.Email, &user.Password, &user.CreatedAt, &user.UpdatedAt)

if err = tx.Commit(); err != nil {
err = fmt.Errorf("failed to commit transaction: %v", err)
return
}

return
}
Loading

0 comments on commit 9398290

Please sign in to comment.