usecasepgx

package
v0.0.13 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package usecasepgx is the pgx-backed UnitOfWork for FlowCatalyst use cases. It wraps a pgxpool.Pool and a usecasepgx.Sink (which decides where domain events and audit logs are written — typically outboxpgx.Sink for consumer apps, or a platform-specific sink that writes to msg_events / aud_logs directly).

Use cases call the generic free functions Commit, CommitDelete, CommitAll, and EmitEvent. Those are the only paths to a Success-valued usecase.Result outside this SDK.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Commit

func Commit[A usecase.HasID, E usecase.DomainEvent, C any](
	ctx context.Context,
	uow *UnitOfWork,
	aggregate *A,
	repo Persist[A],
	event E,
	command C,
	opts ...CommitOption,
) usecase.Result[E]

Commit upserts the aggregate via its repository, writes the domain event and audit log via the configured Sink — all in one transaction.

This is one of the few public paths to a Success-valued usecase.Result outside the SDK. The seal on usecase.Success is satisfied here by importing internal/sealed.

func CommitAll

func CommitAll[A usecase.HasID, E usecase.DomainEvent, C any](
	ctx context.Context,
	uow *UnitOfWork,
	aggregates []A,
	repo Persist[A],
	event E,
	command C,
	opts ...CommitOption,
) usecase.Result[E]

CommitAll upserts a batch of aggregates of the same type via one repository and emits a single summary event + audit log. Use when one logical operation touches many rows (e.g. toggling client → application enablement).

func CommitDelete

func CommitDelete[A usecase.HasID, E usecase.DomainEvent, C any](
	ctx context.Context,
	uow *UnitOfWork,
	aggregate *A,
	repo Persist[A],
	event E,
	command C,
	opts ...CommitOption,
) usecase.Result[E]

CommitDelete deletes the aggregate via its repository and emits the deletion event + audit log atomically.

func CommitDeleteScoped

func CommitDeleteScoped[A usecase.HasID, E usecase.DomainEvent, C any](
	ctx context.Context,
	scoped *TxScopedUnitOfWork,
	aggregate *A,
	repo Persist[A],
	event E,
	command C,
	opts ...CommitOption,
) usecase.Result[E]

CommitDeleteScoped is the CommitDelete equivalent for a TxScopedUnitOfWork.

func CommitScoped

func CommitScoped[A usecase.HasID, E usecase.DomainEvent, C any](
	ctx context.Context,
	scoped *TxScopedUnitOfWork,
	aggregate *A,
	repo Persist[A],
	event E,
	command C,
	opts ...CommitOption,
) usecase.Result[E]

CommitScoped is the Commit equivalent for a TxScopedUnitOfWork. It appends the aggregate write + event + audit to the open transaction but does NOT commit; the surrounding Run does.

func CommitSync

func CommitSync[A usecase.HasID, RE usecase.DomainEvent, C any](
	ctx context.Context,
	uow *UnitOfWork,
	repo Persist[A],
	saves []SyncSaveItem[A],
	deletes []SyncDeleteItem[A],
	rollup RE,
	command C,
	opts ...CommitOption,
) usecase.Result[RE]

CommitSync persists a batch of aggregate saves and deletes, writes one domain event + audit row per touched aggregate, and writes a rollup event + audit row — ALL within a single transaction. The rollup's value is returned as the sealed Success.

Use for sync / bulk-upsert endpoints (e.g. the SDK's sync-platform catalog refresh) whose consumers project the per-row events. Compared to CommitAll (one summary event only), this gives subscribers incremental visibility into what changed while preserving the transactional atomicity guarantee.

Write order: saves (in given order) → deletes (in given order) → rollup. Each event row hits msg_events / outbox_messages in commit order, so downstream readers see per-row events before the rollup.

All audit rows record the outer `command` (typically the bulk sync command). Per-row context lives in the event payloads themselves.

func EmitEvent

func EmitEvent[E usecase.DomainEvent, C any](
	ctx context.Context,
	uow *UnitOfWork,
	event E,
	command C,
	opts ...CommitOption,
) usecase.Result[E]

EmitEvent writes a domain event + audit log without an entity change. Used for events that don't modify an entity directly (e.g. UserLoggedIn).

func EmitEventScoped

func EmitEventScoped[E usecase.DomainEvent, C any](
	ctx context.Context,
	scoped *TxScopedUnitOfWork,
	event E,
	command C,
	opts ...CommitOption,
) usecase.Result[E]

EmitEventScoped is the EmitEvent equivalent for a TxScopedUnitOfWork.

func Run

func Run[R any](
	ctx context.Context,
	uow *UnitOfWork,
	fn func(*TxScopedUnitOfWork) usecase.Result[R],
) usecase.Result[R]

Run opens one transaction on the pool, builds a TxScopedUnitOfWork bound to it, and invokes fn. Commits on Success, rolls back on Failure or panic. Use this when a handler needs to compose multiple aggregate writes (or non-aggregate writes via WithTx) atomically.

Run is a free function because Go does not allow type parameters on methods. Pass the UnitOfWork as the first argument:

result := usecasepgx.Run(ctx, uow, func(s *usecasepgx.TxScopedUnitOfWork) usecase.Result[OrderShipped] {
    if r := usecasepgx.CommitScoped(ctx, s, &order, orderRepo, shipEvent, shipCmd); !usecase.IsSuccess(r) {
        return r
    }
    return usecasepgx.CommitScoped(ctx, s, &ledger, ledgerRepo, debitEvent, debitCmd)
})

func RunErr

func RunErr(
	ctx context.Context,
	uow *UnitOfWork,
	fn func(*TxScopedUnitOfWork) error,
) error

RunErr opens one transaction on the pool, builds a TxScopedUnitOfWork bound to it, invokes fn, and commits on a nil error / rolls back on a non-nil error or panic. It is the error-returning sibling of Run, for multi-aggregate orchestrations whose outcome is a custom value rather than a single sealed event (the caller captures that value in a closure). Every aggregate write inside fn must still go through the scoped Commit* helpers, so each change is accompanied by its event + audit in this same tx.

Types

type CommitOption

type CommitOption func(*commitOptions)

CommitOption adds to what a commit writes besides the aggregate change, its event and its audit row.

func WithDispatchJobs

func WithDispatchJobs(jobs ...outbox.DispatchJob) CommitOption

WithDispatchJobs makes the commit also write these dispatch jobs, in the same transaction: after the event, before the audit row (framework write-path contract 7a). Every job is validated (outbox.DispatchJob.Validate, the 7a rules and texts) before the transaction opens, so an invalid job fails the commit as INTERNAL DISPATCH_JOB_INVALID with nothing written. A job without a correlation id takes the committed event's. usecaseop's Plan.Dispatch is the usual way to pass jobs; this option is for the lower-level Commit* / *Scoped helpers.

type DbTx

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

DbTx is an opaque write handle passed to repository Persist methods. Wraps a pgx.Tx so repository code doesn't import pgx directly through some unrelated path; a future driver swap touches this file plus the commit.go file, nothing else.

Repositories access the underlying pgx.Tx via Inner().

func WrapTxForBootstrap

func WrapTxForBootstrap(tx pgx.Tx) *DbTx

WrapTxForBootstrap exposes a DbTx around an externally-managed pgx.Tx for infrastructure-bootstrap callers ONLY (init commands, seeders, admin tools). These paths run outside the use-case envelope: no executing principal, no domain events emitted. Production use cases MUST go through Commit/CommitDelete/CommitAll/EmitEvent — those are the only entry points that preserve the sealed-event guarantee.

If you find yourself reaching for this from a production code path, you almost certainly want a real use case instead.

func (*DbTx) Inner

func (t *DbTx) Inner() pgx.Tx

Inner exposes the underlying pgx.Tx. Repository methods call this to execute SQL.

type DispatchJobSink

type DispatchJobSink interface {
	Sink
	// WriteDispatchJobs writes the jobs, in order, in the open transaction.
	// The jobs are already validated and resolved (each has its id). Must
	// not commit or roll back the tx.
	WriteDispatchJobs(ctx context.Context, tx *DbTx, jobs []outbox.DispatchJob) error
}

DispatchJobSink is a Sink that also writes dispatch jobs (outbox rows of type DISPATCH_JOB, which the platform's outbox processor delivers to POST /api/dispatch-jobs/batch). outboxpgx.Sink implements it. A commit that carries dispatch jobs through a Sink without it fails before anything is written (INTERNAL DISPATCH_JOB_UNSUPPORTED).

type Persist

type Persist[A usecase.HasID] interface {
	Persist(ctx context.Context, agg *A, tx *DbTx) error
	Delete(ctx context.Context, agg *A, tx *DbTx) error
}

Persist is implemented by a repository to upsert and delete aggregates of type A within a transaction. Implement on the *repository*, not the aggregate — the CLAUDE.md "aggregates don't persist themselves" rule applies in Go too.

Example:

type OrderRepository struct{ pool *pgxpool.Pool }

func (r *OrderRepository) Persist(ctx context.Context, o *Order, tx *usecasepgx.DbTx) error {
    _, err := tx.Inner().Exec(ctx,
        `INSERT INTO orders (id, customer_id, total) VALUES ($1, $2, $3)
         ON CONFLICT (id) DO UPDATE SET customer_id = $2, total = $3`,
        o.ID, o.CustomerID, o.Total)
    return err
}

func (r *OrderRepository) Delete(ctx context.Context, o *Order, tx *usecasepgx.DbTx) error {
    _, err := tx.Inner().Exec(ctx, `DELETE FROM orders WHERE id = $1`, o.ID)
    return err
}

type Sink

type Sink interface {
	// WriteEvent appends the domain event to its destination, using the
	// already-open transaction. Must not commit or roll back the tx.
	WriteEvent(ctx context.Context, tx *DbTx, event usecase.DomainEvent) error

	// WriteAudit appends an audit log row for (event, command), using
	// the same open transaction. Implementations may no-op when audit
	// logging is disabled.
	WriteAudit(ctx context.Context, tx *DbTx, event usecase.DomainEvent, command any) error
}

Sink is the destination for domain events and audit logs. The UnitOfWork doesn't know whether events go to outbox_messages (consumer apps via outboxpgx.Sink) or directly to msg_events / aud_logs (platform code via a platform-specific sink); it just calls into this interface inside the transaction it has open.

A Sink that also implements DispatchJobSink can write dispatch jobs (WithDispatchJobs / usecaseop's Plan.Dispatch).

Implementations:

  • outboxpgx.Sink — consumer apps; writes to outbox_messages so fc-outbox-processor forwards events to the platform API. Also a DispatchJobSink.
  • Platform's own sink (in flowcatalyst-go) — writes directly to msg_events and aud_logs. Not part of this SDK.

type SyncDeleteItem

type SyncDeleteItem[A usecase.HasID] struct {
	Aggregate *A
	Event     usecase.DomainEvent
}

SyncDeleteItem pairs an aggregate to delete with the per-row domain event to emit for it. See CommitSync.

type SyncSaveItem

type SyncSaveItem[A usecase.HasID] struct {
	Aggregate *A
	Event     usecase.DomainEvent
}

SyncSaveItem pairs an aggregate to upsert with the per-row domain event to emit for it (typically a Created or Updated event). See CommitSync.

type TxScopedUnitOfWork

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

TxScopedUnitOfWork is a UnitOfWork bound to an externally-orchestrated transaction opened by Run. Every commit performed against it joins that single transaction. The scoped UoW does NOT close the tx; the surrounding Run does (commits on Success, rolls back on Failure).

Use TxScopedUnitOfWork.WithTx for ad-hoc writes that must be atomic with the events the scoped UoW produces (e.g. updating a non-aggregate row, raw SQL on a join table).

func (*TxScopedUnitOfWork) Sink

func (s *TxScopedUnitOfWork) Sink() Sink

Sink returns the configured sink. Useful when constructing other use cases that take a Sink directly.

func (*TxScopedUnitOfWork) WithTx

func (s *TxScopedUnitOfWork) WithTx(ctx context.Context, fn func(pgx.Tx) error) error

WithTx invokes the callback with the underlying pgx.Tx so callers can run ad-hoc writes (raw SQL, repository methods) on the same tx as the outbox / audit rows. Throws propagate to trigger rollback of the entire Run block.

type UnitOfWork

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

UnitOfWork is the pgx-backed UnitOfWork. Each top-level Commit / CommitDelete / CommitAll / EmitEvent opens its own transaction and commits or rolls back on exit. For orchestrated transactions that span multiple use cases (or mix use cases with ad-hoc writes), use Run.

Construct with New, then pass the *UnitOfWork into every use case that needs to commit. The Sink wired here decides where domain events and audit logs land.

func New

func New(pool *pgxpool.Pool, sink Sink) *UnitOfWork

New wires a UnitOfWork against an existing pgx pool and a sink.

func (*UnitOfWork) Pool

func (u *UnitOfWork) Pool() *pgxpool.Pool

Pool exposes the underlying pgx pool for callers that need to run read-only queries outside a use case (e.g. handlers loading data for GET endpoints). Writes must still go through Commit / CommitDelete / CommitAll / EmitEvent.

func (*UnitOfWork) Sink

func (u *UnitOfWork) Sink() Sink

Sink returns the configured sink. Useful for testing.

Jump to

Keyboard shortcuts

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