kozmod / oniontx Goto Github PK
View Code? Open in Web Editor NEWThe library for transferring transaction management to the application layer.
License: MIT License
The library for transferring transaction management to the application layer.
License: MIT License
Description
1 - It will be nice to add PR
template.
https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository
2 - move CONTRIBUTING.md
to .github
.
Bug when build locally:
0.823 main.go:11:2: github.com/jackc/pgx/[email protected]: missing go.sum entry for go.mod file; to add it:
0.823 go mod download github.com/jackc/pgx/v5
0.823 main.go:12:2: github.com/jackc/pgx/[email protected]: missing go.sum entry for go.mod file; to add it:
0.823 go mod download github.com/jackc/pgx/v5
go
version: 1.22oniontx
version (version tag or commit sha):v0.3.7Add sections (or add reference) which describe the testing the oniotx.Transacror
(closures functions) with popular testing frameworks.
Add info about possibility to use oniotx.Transacror
with mockery
, gorm
, pgx
, etc.
need to try add coverage for all package throughout integration tests.
need to update all dependencies)
When function WithinTx
(WithinTxWithOpts
) calls inside other WithinTx
(WithinTxWithOpts
) function (one or multiple times), defer
calls every time when WithinTx
(WithinTxWithOpts
) finish
defer func() {
switch p := recover(); {
case p != nil:
if rbErr := tx.Rollback(ctx); rbErr != nil {
err = xerrors.Errorf("transactor - panic [%v]: %w", p, errors.Join(rbErr, ErrRollbackFailed))
return
}
err = xerrors.Errorf("transactor - panic [%v]: %w", p, ErrRollbackSuccess)
case err != nil:
if rbErr := tx.Rollback(ctx); rbErr != nil {
err = xerrors.Errorf("transactor - call: %w", errors.Join(err, rbErr, ErrRollbackFailed))
return
}
err = xerrors.Errorf("transactor - call: %w", errors.Join(err, ErrRollbackSuccess))
default:
if err = tx.Commit(ctx); err != nil {
err = xerrors.Errorf("transactor: %w", errors.Join(err, ErrCommitFailed))
}
}
}()
and Rollback
or Commit
functions may calls before top level's WithinTx
(WithinTxWithOpts
) function commit or rollback.
Only top level 's WithinTx
(WithinTxWithOpts
) has to have a control of the commit or rollback.
For build app with ald version of go
need to down Transactor
(and submodules version)
go: github.com/kozmod/oniontx/[email protected]: module github.com/kozmod/oniontx/[email protected] requires go >= 1.22 (running go 1.21.6)
go version: 1.21.6
Current ContextOperator
has the overhead on injecting/extracting Tx
. Problem is the Key
function ( convetation pointer's value to string
's value 1️⃣):
// Inject returns new context.Context contains Tx as value.
func (p *ContextOperator[B, T]) Inject(ctx context.Context, tx T) context.Context {
key := p.Key() // 👈🏻
return context.WithValue(ctx, key, tx)
}
// Extract returns Tx extracted from context.Context.
func (p *ContextOperator[B, T]) Extract(ctx context.Context) (T, bool) {
key := p.Key() // 👈🏻
c, ok := ctx.Value(key).(T)
return c, ok
}
// Key returns key (CtxKey) for injecting or extracting Tx from context.Context.
func (p *ContextOperator[B, T]) Key() CtxKey {
return CtxKey(fmt.Sprintf("%p", p.beginner)) // 1️⃣
}
According to benchmarks, overhead about x4 👇🏻
func Benchmark(bch *testing.B) {
var (
c = committerMock{}
b = &beginnerMock[*committerMock, any]{}
)
benchmarks := []struct {
name string
operator СtxOperator[*committerMock]
fn func(ctx context.Context, o СtxOperator[*committerMock]) context.Context
}{
{
name: "current",
operator: NewContextOperator[*beginnerMock[*committerMock, any], *committerMock](&b),
fn: func(ctx context.Context, o СtxOperator[*committerMock]) context.Context {
c := o.Inject(ctx, &c)
_, _ = o.Extract(ctx)
return c
},
},
{
name: "new",
operator: NewContextOperatorV2[*beginnerMock[*committerMock, any], *committerMock](&b),
fn: func(ctx context.Context, o СtxOperator[*committerMock]) context.Context {
c := o.Inject(ctx, &c)
_, _ = o.Extract(ctx)
return c
},
},
}
for _, bm := range benchmarks {
bch.Run(bm.name, func(b *testing.B) {
var (
ctx = context.Background()
o = bm.operator
)
bch.ReportAllocs()
bch.ResetTimer()
for i := 0; i < b.N; i++ {
ctx = bm.fn(ctx, o)
}
_ = ctx
})
}
}
goos: darwin
goarch: amd64
pkg: github.com/kozmod/oniontx
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Benchmark
Benchmark/new
Benchmark/new-12 11328888 101.9 ns/op
Benchmark/current
Benchmark/current-12 3089184 432.9 ns/op
PASS
because pointer
is comparable, pointer's value will be able to using without convetation to string
.
Description
Current coverage is
PASS
github.com/kozmod/oniontx coverage: 94.9% of statements
ok github.com/kozmod/oniontx 0.003s coverage: 94.9% of statements
github.com/kozmod/oniontx/operator.go:13: NewContextOperator 100.0%
github.com/kozmod/oniontx/operator.go:20: Inject 100.0%
github.com/kozmod/oniontx/operator.go:25: Extract 100.0%
github.com/kozmod/oniontx/transactor.go:50: NewTransactor 100.0%
github.com/kozmod/oniontx/transactor.go:63: WithinTx 100.0%
github.com/kozmod/oniontx/transactor.go:104: WithinTxWithOpts 93.3%
github.com/kozmod/oniontx/transactor.go:163: TryGetTx 100.0%
github.com/kozmod/oniontx/transactor.go:169: TxBeginner 100.0%
total: (statements) 94.9%
Need to add test for Transactor
recursive call.
Error occurred
go: github.com/kozmod/[email protected]: module github.com/kozmod/[email protected] requires go >= 1.22 (running go 1.21.6)
because submodules have not direct version deps
module github.com/kozmod/oniontx/gorm
go 1.20
replace github.com/kozmod/oniontx => ../
require (
github.com/kozmod/oniontx v0.3.1
gorm.io/gorm v1.25.8
)
go
version: 1.21oniontx
version (version tag or commit sha): 0.3.9Need to generalise the Transactor
for using with different libs witch supported transactions (pix, sqlx, etc.).
It will be make using generic
approach
type (
TxBeginner[C TxCommitter, O any] interface {
comparable
BeginTx(ctx context.Context, opts ...Option[O]) (C, error)
}
TxCommitter interface {
Rollback(ctx context.Context) error
Commit(ctx context.Context) error
}
Option[TxOpt any] interface {
Apply(in TxOpt)
}
СtxOperator[C TxCommitter] interface {
Inject(ctx context.Context, c C) context.Context
Extract(ctx context.Context) (C, bool)
}
)
type Transactor[B TxBeginner[C, O], C TxCommitter, O any] struct {
beginner B
operator СtxOperator[C]
}
, or by another way.
1️⃣ update dependencies versions
2️⃣ add target to Makefile
Change Makefile
:
.PHONY: up
up: ## Up dest database
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose.yml up
.PHONY: up.d
up.d: ## Up dest database (detached)
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose.yml up -d
to
.PHONY: up
up: ## Up dest database
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose.yml up --build --force-recreate
.PHONY: up.d
up.d: ## Up dest database (detached)
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f docker-compose.yml up -d --build --force-recreate
Default ContextOperator
uses genetic type any
(v0.2.3) (comparable
#88 ) as key for storing/getting Tx
.
Changing the key
field to the function that return (calculate) the key, will make ContextOperator
more flexible.
// ContextOperator inject and extract Tx from context.Context.
type ContextOperator[B comparable, T Tx] struct {
key func() B
}
// NewContextOperator returns new ContextOperator.
func NewContextOperator[B comparable, T Tx](key func() B) *ContextOperator[B, T] {
return &ContextOperator[B, T]{
key: key,
}
}
// Inject returns new context.Context contains Tx as value.
func (p *ContextOperator[B, T]) Inject(ctx context.Context, tx T) context.Context {
return context.WithValue(ctx, p.key(), tx)
}
// Extract returns Tx extracted from context.Context.
func (p *ContextOperator[B, T]) Extract(ctx context.Context) (T, bool) {
c, ok := ctx.Value(p.key()).(T)
return c, ok
}
Update:
github.com/jackc/pgx/v4 v4.18.2 -> github.com/jackc/pgx/v5 v5.5.5
github.com/jackc/pgx/v5 v5.5.4 -> github.com/jackc/pgx/v5 v5.5.5
github.com/jackc/pgx/v5 v5.5.4 -> github.com/jackc/pgx/v5 v5.5.5
In the current implementation СtxOperator
inject transaction into context every call WithinTxWithOpts
( and WithinTx
)
defer func() {
switch p := recover(); {
case p != nil:
if rbErr := tx.Rollback(ctx); rbErr != nil {
err = xerrors.Errorf("transactor - panic [%v]: %w", p, errors.Join(rbErr, ErrRollbackFailed))
return
}
err = xerrors.Errorf("transactor - panic [%v]: %w", p, ErrRollbackSuccess)
case err != nil:
if rbErr := tx.Rollback(ctx); rbErr != nil {
err = xerrors.Errorf("transactor: %w", errors.Join(err, rbErr, ErrRollbackFailed))
return
}
err = xerrors.Errorf("transactor: %w", errors.Join(err, ErrRollbackSuccess))
default:
if err = tx.Commit(ctx); err != nil {
err = xerrors.Errorf("transactor: %w", errors.Join(err, ErrCommitFailed))
}
}
}()
ctx = t.operator.Inject(ctx, tx) // 👈🏻 ❗️❗️❗️
return fn(ctx)
and It has overhead (standard ContextOperator
implementation, for example)
// Inject returns new context.Context contains Tx as value.
func (p *ContextOperator[B, T]) Inject(ctx context.Context, tx T) context.Context {
key := p.Key()
return context.WithValue(ctx, key, tx)
}
// Extract returns Tx extracted from context.Context.
func (p *ContextOperator[B, T]) Extract(ctx context.Context) (T, bool) {
key := p.Key()
c, ok := ctx.Value(key).(T)
return c, ok
}
// Key returns key (CtxKey) for injecting or extracting Tx from context.Context.
func (p *ContextOperator[B, T]) Key() CtxKey {
return CtxKey(fmt.Sprintf("%p", p.beginner))
}
👇🏻 Benchmarks👇🏻
func Benchmark_v1(b *testing.B) {
const (
k = "a"
v = "x"
)
b.ResetTimer()
b.ReportAllocs()
var ctx context.Context = context.WithValue(context.Background(), k, v)
for i := 0; i < b.N; i++ {
_, ok := ctx.Value(k).(string)
if !ok {
ctx = context.WithValue(ctx, k, v)
}
}
_ = ctx
}
func Benchmark_v2(b *testing.B) {
const (
k = "a"
v = "x"
)
b.ResetTimer()
b.ReportAllocs()
var ctx context.Context = context.WithValue(context.Background(), k, v)
for i := 0; i < b.N; i++ {
c, _ := ctx.Value(k).(string)
ctx = context.WithValue(ctx, k, c)
}
_ = ctx
}
pkg: github.com/kozmod/oniontx
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Benchmark_v1
Benchmark_v1-12 187710049 6.388 ns/op 0 B/op 0 allocs/op
Benchmark_v2
Benchmark_v2-12 9519942 116.6 ns/op 64 B/op 2 allocs/op
PASS
reusing the existing check (on Tx
in the input context ) helps to avoid overhead
tx, ok := t.operator.Extract(ctx)
if !ok {
tx, err = t.beginner.BeginTx(ctx, opts...)
if err != nil {
return xerrors.Errorf("transactor - cannot begin: %w", errors.Join(err, ErrBeginTx))
}
}
defer func() {
switch p := recover(); {
// ...
if !ok {
ctx = t.operator.Inject(ctx, tx)
}
return fn(ctx)
}
need fix
error=failed to get module path: exit status 1: reading go.work: /home/runner/work/oniontx/oniontx/go.work:1: invalid go version '1.21.0': must match format 1.23
set running "Release action" on tag creating.
need to configure release action to run release only on main
branch when tag creating
Experiment
Split stdlib
and core
into submodules to get stdlib
independently
some URLs in the main Readme.md
is was brocken.
go
version: 1.22oniontx
version (version tag or commit sha): v0.3.1 (main)need to add Transaction.TryExtractTransaction
function to have straight forward way get transaction from context.
function WithinTransaction
func (t *Transactor) WithinTransaction(ctx context.Context, fn func(ctx context.Context) error, options ...Option) (err error) {
...
will have to be separate into two functions
func (t *Transactor) WithinOptionalTransaction(ctx context.Context, fn func(ctx context.Context) error, options ...Option) (err error) {
...
func (t *Transactor) WithinTransaction(ctx context.Context, fn func(ctx context.Context) error) (err error) {
return t.WithinOptionalTransaction(ctx, fn)
}
Need to make Readme.md
more clear.
Need to add submodules tags automatically. Try to use actions/github-script@v7
:
- name: Set submodule tags
uses: actions/github-script@v7
with:
script: |
const creator = github.ref_name
let submodules: string[] = ['stdlib', 'pgx', 'sqlx', 'gorm'];
for (const submodule of submodules) {
echo 'refs/tags/${{ ref_name }}'
}
remove xerror .Errorf
(golang.org/x/xerrors
) and use fmt.Errorf
or errors.New
.
improve descriptions of types stdlib
and main
pkg structs and methods.
oniontx
version (version tag or commit sha): 0.2.1Need to add function to extract transaction from context.Context
. It will be convenient to refactoring the code base.
func (r *Repository) Do(ctx context.Context, userID, postID, message string, interests []feed.UserInterest) (Some, error) {
var value Some
tx, err := r.db.BeginTx(ctx, &sql.TxOptions{})
defer func() {
switch p := recover(); {
case p != nil:
err = fmt.Errorf("repository: tx execute with panic: %v", p)
case err != nil:
_ = tx.Rollback()
default:
err = tx.Commit()
}
}()
row := tx.QueryRowContext(ctx, `SELECT`)
if err := row.Err(); err != nil {
return value, fmt.Errorf("row: %w", err)
}
err = row.Scan(/* SCAN */)
if err != nil {
return value,fmt.Errorf("scan: %w", err)
}
_, err = tx.ExecContext(ctx, `UPDATE`)
if err != nil {
return value, fmt.Errorf("update posts quantity: %w", err)
}
return value, nil
}
👇🏻
func (r *Repository) Do(ctx context.Context) (Some, error) {
tx, ok := ExtractTx
if !ok {
var value Some
tx, err := r.db.BeginTx(ctx, &sql.TxOptions{})
defer func() {
switch p := recover(); {
case p != nil:
err = fmt.Errorf("repository: tx execute with panic: %v", p)
case err != nil:
_ = tx.Rollback()
default:
err = tx.Commit()
}
}()
}
}
....
need to add templates for new issuer
and pull request
😊
1️⃣ add target to update all deps
2️⃣ fix help
target to show targets like update.deps
Need to remove ExtractExecutorOrDefault
and ExtractTx
functions (static), because it is not "safe" when using different *sql.DB
instances.
need to make script to auto update github.com/kozmod/oniontx
version in submodules.
example:
.PHONT: up.submods.deps
up.sm.deps:
@(for sub in ${TAG_SUBMODULES} ; do \
pushd $$sub && \
sed -i '' 's/github.com\/kozmod\/oniontx v[^0-9]*\(\([0-9]\.\)\{0,4\}[0-9][^.]\)/github.com\/kozmod\/oniontx v1.1.1/g' go.mod && \
popd; \
done)
move integration tests from https://github.com/kozmod/oniontx-examples to this repo (and create gitlab action)
1️⃣ simplify methods TryGetTx
and TxBeginner
.
this methods returns wrappers of *sql.Tx
and *sql.DB
// TryGetTx returns pointer of sql.Tx wrapper and "true" from context.Context or return `false`.
func (t *Transactor) TryGetTx(ctx context.Context) (*Tx, bool) {
return t.transactor.TryGetTx(ctx)
}
// TxBeginner returns pointer of sql.DB wrapper.
func (t *Transactor) TxBeginner() *DB {
return t.transactor.TxBeginner()
}
Its have to return simple only *sql.Tx
and *sql.DB
// TryGetTx returns pointer of sql.Tx and "true" from context.Context or return `false`.
func (t *Transactor) TryGetTx(ctx context.Context) (*sql.Tx, bool) {
wrapper, ok := t.transactor.TryGetTx(ctx)
if !ok || wrapper == nil || wrapper.Tx == nil {
return nil, false
}
return wrapper.Tx, true
}
// TxBeginner returns pointer of sql.DB.
func (t *Transactor) TxBeginner() *sql.DB {
return t.transactor.TxBeginner().DB
}
2️⃣ make wrappers
private to improve incopsulation
Need to change the description of the project in Readmy.md
to more abstract.
For Example:
NOTE: Transactor was developed to work with only the same instance of tha *sql.DB
to
NOTE: Transactor was developed to work with only the same instance of the "repository" (
*sql.DB
, etc.)
Need to add examples of assertions of the Transactor
with mockery
Set goreleaser
config to replace release notes:
release:
# What to do with the release notes in case there the release already exists.
#
# Valid options are:
# - `keep-existing`: keep the existing notes
# - `append`: append the current release notes to the existing notes
# - `prepend`: prepend the current release notes to the existing notes
# - `replace`: replace existing notes
#
# Default is `keep-existing`.
mode: replace
generalise Test_Transactor_recursive_call
need to add CONTRIBUTING.md
After generalization (#26)^ it will be nice to create common realization for stdlib
:
type Transactor struct {
transactor *oniontx.Transactor[*DB, *Tx, *sql.TxOptions]
operator *oniontx.ContextOperator[*DB, *Tx, *sql.TxOptions]
}
Need to add tests for pgx
,sqlx
,gorm
with canceling ctx
.
var (
ctx, cancel = context.WithCancel(context.Background())
transactor = ostdlib.NewTransactor(db)
repositoryA = NewTextRepository(transactor, false)
repositoryB = NewTextRepository(transactor, false)
useCase = NewUseCase(repositoryA, repositoryB, transactor)
)
cancel()
...
Now walk through dirs in the project and define them as submodules
.PHONT: deps.update
deps.update: ## Update dependencies versions (root and sub modules)
@GOTOOLCHAIN=local go get -u all
@go mod tidy
@for d in */ ; do pushd "$$d" && go get -u all && go mod tidy && popd; done
@go work sync
wiil be nice to use the variable for updating deps
SUBMODULES=stdlib pgx sqlx gorm
BUG:
default ContextOperator
use database (TxBeginner
) for extracting and injecting ( Inject
/Extract
) transaction (Tx
) to context.Context
:
// ContextOperator inject and extract Tx from context.Context.
type ContextOperator[B any 👈🏻 , T Tx] struct {
beginner *B
}
// NewContextOperator returns new ContextOperator.
func NewContextOperator[B any, T Tx](b *B) *ContextOperator[B, T] {
return &ContextOperator[B, T]{
beginner: b,
}
}
// Inject returns new context.Context contains Tx as value.
func (p *ContextOperator[B, T]) Inject(ctx context.Context, tx T) context.Context {
return context.WithValue(ctx, p.beginner, tx) 👈🏻
}
// Extract returns Tx extracted from context.Context.
func (p *ContextOperator[B, T]) Extract(ctx context.Context) (T, bool) {
c, ok := ctx.Value(p.beginner).(T) 👈🏻
return c, ok
}
if type B
is not comparable, context.Context
produce the panic: " key is not comparable"
To avoid the produce, need to change type B
to comparable
:
// ContextOperator inject and extract Tx from context.Context.
type ContextOperator[B comparable, T Tx] struct {
beginner B
}
// NewContextOperator returns new ContextOperator.
func NewContextOperator[B comparable, T Tx](b B) *ContextOperator[B, T] {
return &ContextOperator[B, T]{
beginner: b,
}
}
ENCHANCEMENT:
default ContextOperator
use *B
type in the constructor. It's a bit unhandy.
// NewContextOperator returns new ContextOperator.
func NewContextOperator[B any, T Tx](b *B 👈🏻) *ContextOperator[B, T] {
return &ContextOperator[B, T]{
beginner: b,
}
}
the pointer may be removed.
Targets:
1️⃣ show all coverage (add cover.out
to .gitignore
)
2️⃣ update all tools (goimports, etc.)
Need to remove
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.22.0
- name: Run migrations // remove
run: |
go version
- name: Run migrations
run: |
cd test/integration/migration
go run . -cmd up -url "postgresql://test:passwd@localhost:5432/test?sslmode=disable"
1 - pull tags from origin
2 - check that last tag point to last commit on branch (not -> skip)
3 - add submodules tag (skip test dir)
need to add:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.