entx

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 16, 2024 License: MIT Imports: 3 Imported by: 0

README

Entx

Entx is my personal collection of useful snippet I use in any project involving using Ent. Currently only three feature is present.

Usage

Create a new go file, we'll use it to generate our ent codebase. You need to tweak the path you pass to the entc.Generate function.

In this example you can notice I use some community extension and additional ent features, you can tweak it as you want, the only important part is the use of entx.New(nil), to instantiate the extension. You can use the nil value to enable all features of the extension of pass an option struct to customize which extension to enable.

package main

import (
	"log"

	"entgo.io/ent/entc"
	"entgo.io/ent/entc/gen"
	"github.com/hedwigz/entviz"
	"github.com/m-vinc/entx"
)

func main() {
	err := entc.Generate("../../pkg/ent/schema", &gen.Config{
		Features: []gen.Feature{gen.FeatureLock, gen.FeatureExecQuery, gen.FeatureUpsert},
	}, entc.Extensions(
        entviz.Extension{},
        entx.New(nil),
        // entx.New(&entx.Options{Tx: true}),
    ))
	if err != nil {
		log.Fatalf("running ent codegen: %v", err)
	}
}

Features

Postgres

Postgres generate only simple function when using a postgresql database.

db, err := ent.Postgres(&ent.PostgresConfiguration{
    Host:     configDatabase.Host,
    Port:     strconv.FormatUint(uint64(configDatabase.Port), 10),
    Username: configDatabase.Username,
    Password: configDatabase.Password,
    Database: configDatabase.Database,
    SSLMode:  configDatabase.SSLMode,
})

if err != nil {
    log.Fatal(err)
}
Queryable

Queryable generate what you need to abstract a plain client or a transaction and create agnostic function, allowing you to build function which is able to query the database in any circumstance. Useful for validations function we'll see in this simple example :

func IsNameUnique(queryable ent.Queryable, name string) error {
	ok, err := queryable.AccountClient().Query().
        Where(account.NameEQ(name)).
        Exist(queryable.Context())
	if err != nil {
		return err
	}

	if ok {
		return ErrNameDuplicate
	}

	return nil
}

ctx := context.Background()

// Use queryable with a simple ent client
var db *ent.Client
q := db.Queryable(ctx)

// Run with a simple client
IsNameUnique(q, "toto")



// Use queryable with a transaction
var tx *ent.Tx

q := tx.Queryable(ctx)

// Run within the transaction
IsNameUnique(q, "toto")
Tx

The Tx feature extend the ent Client sturct and the Tx struct, adding two function Acquire to the ent *Client and a Release function on *Tx.

With these two function we can instantiate a transaction with Acquire which store a transaction in the given context and returning a new context. :


ctx := context.Background()

var db *ent.Client

tx, ctx, err := db.Acquire(ctx)
if err != nil {
    log.Fatal("help !", err)
}

When a transaction is initated this way, you can release it by calling the Release function on the *Tx struct with the current context :


err := errors.New("toto")
// this tx will be rollbacked because an error is passed to the release function
err = tx.Release(ctx, err) // Rollback

// However we can commit the transaction if no error is passed
err = tx.Release(ctx, nil) // Commit !

By using those two functions, we can create function using them and allowing a function to initate a transaction, use it, pass it to another function which use the current transaction if a transaction is present.

The original caller is "responsible" of the transaction, only him can Commit or Rollback the transaction by calling Release, if a child function call need to use the transaction, you'll need to pass the context created by the Acquire function, the child function will use the same pattern as the original caller but in that case no new transaction will be initiated and every call to Release or Acquire will not do anything other than forwarding the error to othe caller.

Let's demonstrate how this mechanism work, let's define two simple function :

func GetUser(ctx context.Context, username string) (*ent.User, error) {
    tx, ctx, err := db.Acquire(ctx)
    if err != nil {
        return nil, err
    }

    user, err := tx.User.Query().Where(user.UsernameEQ(username)).First(ctx)
    if err != nil {
        return nil, tx.Release(ctx, err)
    }

    return user, tx.Release(ctx, nil)
}

func UpdateUsername(ctx context.Context, username stirng, newUsername string) error {
    tx, ctx, err := db.Acquire(ctx)
    if err != nil {
        return err
    }

    user, err := GetUser(ctx, username)
    if err != nil {
        return tx.Release(ctx, err)
    }

    _, err = tx.User.UpdateOne(user).SetUsername(newUsername).Save(ctx)
    if err != nil {
        return tx.Release(ctx, err)
    }

    return tx.Release(ctx, nil)
}

Then let's use them :

var db *ent.Client

ctx := context.Backgound()

// In this case, UpdateUsername will initiate the transaction
// Pass it to GetUser which use it to retrieve a user or throw an error if not exist
// If an error occured GetUser call tx.Release but since the transaction is not owned by GetUser the error is forward to UpdateUsername and the tx.Release call is performed by UpdateUsername rollbacking the transaction or commiting it.
err = UpdateUsername(ctx, "toto", "tata")

// We can use GetUser without initating a transaction, the Acquire call will do it for you.
user, err := GetUser(ctx, "toto")

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(options *Options) entc.Extension

Types

type Entx

type Entx struct {
	// contains filtered or unexported fields
}

func (*Entx) Annotations

func (entx *Entx) Annotations() []entc.Annotation

func (*Entx) Hooks

func (entx *Entx) Hooks() []gen.Hook

func (*Entx) Options

func (entx *Entx) Options() []entc.Option

func (*Entx) Templates

func (entx *Entx) Templates() []*gen.Template

type Options

type Options struct {
	Postgres  bool
	Queryable bool
	Tx        bool
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL