This project is supposed to be a generic and very customizable idempotency utility.
Right now we support only fiber web framework with Postgres as persistence using pgx v5.
I will be very pleased to receive pull requests to support other persistences and frameworks.
go get github.com/dalthon/anaThe simplest way of using it is shown by examples/02-fiber.go which
you can run with make example-02:
package main
import (
"context"
"fmt"
"os"
a "github.com/dalthon/ana"
r "github.com/dalthon/ana/repository/pgx"
af "github.com/dalthon/ana/web/fiber"
f "github.com/gofiber/fiber/v2"
fr "github.com/gofiber/fiber/v2/middleware/recover"
"github.com/jackc/pgx/v5/pgxpool"
)
type idCtx = r.PgxContext[af.HttpPayload, af.HttpResponse]
func main() {
pool := newPool()
repo := r.NewPgxRepository[af.HttpPayload, af.HttpResponse](pool)
ana := a.New(repo)
middleware := af.New(ana, &af.Config{})
app := f.New()
app.Use(fr.New())
app.Get("/*", middleware.Call(idempotentHandler, nil))
app.Listen(":3000")
}
func idempotentHandler(c *f.Ctx, i *idCtx) (*af.HttpResponse, error) {
fmt.Println("Not persisted!")
return &af.HttpResponse{
f.StatusOK,
fmt.Sprintf("Hello %s!\n", c.Get("X-Idempotency-Key")),
}, nil
}
func newPool() *pgxpool.Pool {
pool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL"))
if err != nil {
panic(err)
}
return pool
}In that example we apply idempotency on any GET route with default config.
Default config expects to get idempotency key from HTTP header
X-Idempotency-Key and also expects three more HTTP headers:
X-Idempotency-Reference-Time, X-Idempotency-Timeout and
X-Idempotency-Expiration.
All expected headers means:
X-Idempotency-Key: Idempotency key provided by clientX-Idempotency-Reference-Time: Reference time that will be considered from first attempt to process a given operation (format RFC 3339)X-Idempotency-Timeout: Timeout, in duration, that will not allow more than one simultaneous attempt of operation (format from time.ParseDuration)X-Idempotency-Expiration: Timeout, in duration, that will not try to execute a given operation again (format from time.ParseDuration)
curl \
-H "X-Idempotency-Key: some-key-as-string" \
-H "X-Idempotency-Reference-Time: 1835-09-20T09:00:00Z" \
-H "X-Idempotency-Timeout: 10s" \
-H "X-Idempotency-Expiration: 24h" \
http://localhost:3000/whatever-you-may-likeOn middleware.Call, we can use an *af.Config to override given config at
middleware initialization.
Config is defined as:
type Config struct {
Key func(*fiber.Ctx) string
Target func(*fiber.Ctx) string
Payload func(*fiber.Ctx) *HttpPayload
ReferenceTime func(*fiber.Ctx) time.Time
Timeout func(*fiber.Ctx) time.Duration
Expiration func(*fiber.Ctx) time.Duration
}Which are functions that returns value:
Key: used as idempotency keyTarget: used to identify target of operationPayload: that stores HTTP path and body of a given requestReferenceTime: that is considered the first possible time that this operation should have startedTimeout: that ensures that no more than one operation of sameKeyandTargetwill run simultaneouslyExpiration: used as duration which afterReferenceTime+Durationwe assume this execution should not execute again.
Also there is a nice helper which in that example could be invoked as
af.Value to set configs as this:
&af.Config{
Timeout: af.Value(10 * time.Second)
}So, this helper is just a function that returns a function that always returns
the same value. This seems silly, but is quite useful to have fixed configs for
Timeout and Expiration.
- Chores:
- Add a few more tests on postgres repository showing that transactions are really rolling back everything done by user in case of failure.
- Add fiber's middleware tests.
- Features:
- Add
FinishedAtandErrorCountonTrackedOperation. - Add
net/httpmiddleware. - Add Redis persistence.
- On Postgres repository, add config to store Response in Redis instead of Postgres.
- Consider timeout to add statement timeout on session.
- Add
Pull requests and issues are welcome! I'll try to review them as soon as I can.
This project is quite simple and its Makefile is quite useful to do
whatever you may need. Run make help for more info.
To run tests, run make test.
To run test with coverage, run make cover.
To run a full featured example available at examples/02-fiber.go, run
make example-02.
This project is released under the MIT License