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 ¶
- func Commit[A usecase.HasID, E usecase.DomainEvent, C any](ctx context.Context, uow *UnitOfWork, aggregate *A, repo Persist[A], event E, ...) usecase.Result[E]
- func CommitAll[A usecase.HasID, E usecase.DomainEvent, C any](ctx context.Context, uow *UnitOfWork, aggregates []A, repo Persist[A], event E, ...) usecase.Result[E]
- func CommitDelete[A usecase.HasID, E usecase.DomainEvent, C any](ctx context.Context, uow *UnitOfWork, aggregate *A, repo Persist[A], event E, ...) usecase.Result[E]
- func CommitDeleteScoped[A usecase.HasID, E usecase.DomainEvent, C any](ctx context.Context, scoped *TxScopedUnitOfWork, aggregate *A, repo Persist[A], ...) usecase.Result[E]
- func CommitScoped[A usecase.HasID, E usecase.DomainEvent, C any](ctx context.Context, scoped *TxScopedUnitOfWork, aggregate *A, repo Persist[A], ...) usecase.Result[E]
- func CommitSync[A usecase.HasID, RE usecase.DomainEvent, C any](ctx context.Context, uow *UnitOfWork, repo Persist[A], saves []SyncSaveItem[A], ...) usecase.Result[RE]
- func EmitEvent[E usecase.DomainEvent, C any](ctx context.Context, uow *UnitOfWork, event E, command C, opts ...CommitOption) usecase.Result[E]
- func EmitEventScoped[E usecase.DomainEvent, C any](ctx context.Context, scoped *TxScopedUnitOfWork, event E, command C, ...) usecase.Result[E]
- func Run[R any](ctx context.Context, uow *UnitOfWork, ...) usecase.Result[R]
- func RunErr(ctx context.Context, uow *UnitOfWork, fn func(*TxScopedUnitOfWork) error) error
- type CommitOption
- type DbTx
- type DispatchJobSink
- type Persist
- type Sink
- type SyncDeleteItem
- type SyncSaveItem
- type TxScopedUnitOfWork
- type UnitOfWork
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 ¶
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.
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.
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.