Skip to content

Commit

Permalink
fix: review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
turip committed Oct 30, 2024
1 parent a3c6310 commit 2b1dc5a
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 27 deletions.
3 changes: 3 additions & 0 deletions openmeter/billing/adapter/invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ func (r *adapter) AssociatedLineCounts(ctx context.Context, input billing.Associ
}

func (r *adapter) validateUpdateRequest(req billing.UpdateInvoiceAdapterInput, existing *db.BillingInvoice) error {
// The user is expected to submit the updatedAt of the source invoice version it based the update on
// if this doesn't match the current updatedAt, we can't allow the update as it might overwrite some already
// changed values.
if !existing.UpdatedAt.Equal(req.UpdatedAt) {
return billing.ConflictError{
Entity: billing.EntityInvoice,
Expand Down
6 changes: 3 additions & 3 deletions openmeter/billing/service/invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ func (s *Service) gatherInscopeLines(ctx context.Context, input billing.CreateIn
return lines, nil
}

func (s *Service) getInvoiceFSMWithLock(ctx context.Context, txAdapter billing.Adapter, invoiceID billingentity.InvoiceID) (*InvoiceStateMachine, error) {
func (s *Service) getInvoiceStatMachineWithLock(ctx context.Context, txAdapter billing.Adapter, invoiceID billingentity.InvoiceID) (*InvoiceStateMachine, error) {
// let's lock the invoice for update, we are using the dedicated call, so that
// edges won't end up having SELECT FOR UPDATE locks
if err := txAdapter.LockInvoicesForUpdate(ctx, billing.LockInvoicesForUpdateInput{
Expand Down Expand Up @@ -309,7 +309,7 @@ func (s *Service) AdvanceInvoice(ctx context.Context, input billing.AdvanceInvoi
}

return entutils.TransactingRepo(ctx, s.adapter, func(ctx context.Context, txAdapter billing.Adapter) (*billingentity.Invoice, error) {
fsm, err := s.getInvoiceFSMWithLock(ctx, txAdapter, input)
fsm, err := s.getInvoiceStatMachineWithLock(ctx, txAdapter, input)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -340,7 +340,7 @@ func (s *Service) ApproveInvoice(ctx context.Context, input billing.ApproveInvoi
}

return entutils.TransactingRepo(ctx, s.adapter, func(ctx context.Context, txAdapter billing.Adapter) (*billingentity.Invoice, error) {
fsm, err := s.getInvoiceFSMWithLock(ctx, txAdapter, input)
fsm, err := s.getInvoiceStatMachineWithLock(ctx, txAdapter, input)
if err != nil {
return nil, err
}
Expand Down
48 changes: 24 additions & 24 deletions openmeter/billing/service/invoicestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
)

type InvoiceStateMachine struct {
Invoice *billingentity.Invoice
FSM *stateless.StateMachine
Invoice *billingentity.Invoice
StateMachine *stateless.StateMachine
}

var (
Expand All @@ -39,7 +39,7 @@ func NewInvoiceStateMachine(invoice *billingentity.Invoice) *InvoiceStateMachine
// TODO[later]: Delete invoice is not implemented yet
// TODO[optimization]: The state machine can be added to sync.Pool to avoid allocations (state is stored in the Invoice entity)

fsm := stateless.NewStateMachineWithExternalStorage(
stateMachine := stateless.NewStateMachineWithExternalStorage(
func(ctx context.Context) (stateless.State, error) {
return out.Invoice.Status, nil
},
Expand All @@ -61,18 +61,18 @@ func NewInvoiceStateMachine(invoice *billingentity.Invoice) *InvoiceStateMachine
// NOTE: we are not using the substate support of stateless for now, as the
// substate inherits all the parent's state transitions resulting in unexpected behavior.

fsm.Configure(billingentity.InvoiceStatusDraftCreated).
stateMachine.Configure(billingentity.InvoiceStatusDraftCreated).
Permit(triggerNext, billingentity.InvoiceStatusDraftValidating)

fsm.Configure(billingentity.InvoiceStatusDraftValidating).
stateMachine.Configure(billingentity.InvoiceStatusDraftValidating).
Permit(triggerNext, billingentity.InvoiceStatusDraftSyncing).
Permit(triggerFailed, billingentity.InvoiceStatusDraftInvalid).
OnActive(out.validateDraftInvoice)

fsm.Configure(billingentity.InvoiceStatusDraftInvalid).
stateMachine.Configure(billingentity.InvoiceStatusDraftInvalid).
Permit(triggerRetry, billingentity.InvoiceStatusDraftValidating)

fsm.Configure(billingentity.InvoiceStatusDraftSyncing).
stateMachine.Configure(billingentity.InvoiceStatusDraftSyncing).
Permit(triggerNext,
billingentity.InvoiceStatusDraftManualApprovalNeeded,
boolFn(not(out.isAutoAdvanceEnabled))).
Expand All @@ -82,14 +82,14 @@ func NewInvoiceStateMachine(invoice *billingentity.Invoice) *InvoiceStateMachine
Permit(triggerFailed, billingentity.InvoiceStatusDraftSyncFailed).
OnActive(out.syncDraftInvoice)

fsm.Configure(billingentity.InvoiceStatusDraftSyncFailed).
stateMachine.Configure(billingentity.InvoiceStatusDraftSyncFailed).
Permit(triggerRetry, billingentity.InvoiceStatusDraftValidating)

fsm.Configure(billingentity.InvoiceStatusDraftReadyToIssue).
stateMachine.Configure(billingentity.InvoiceStatusDraftReadyToIssue).
Permit(triggerNext, billingentity.InvoiceStatusIssuing)

// Automatic and manual approvals
fsm.Configure(billingentity.InvoiceStatusDraftWaitingAutoApproval).
stateMachine.Configure(billingentity.InvoiceStatusDraftWaitingAutoApproval).
// Manual approval forces the draft invoice to be issued regardless of the review period
Permit(triggerApprove, billingentity.InvoiceStatusDraftReadyToIssue).
Permit(triggerNext,
Expand All @@ -99,39 +99,39 @@ func NewInvoiceStateMachine(invoice *billingentity.Invoice) *InvoiceStateMachine

// This state is a pre-issuing state where we can halt the execution and execute issuing in the background
// if needed
fsm.Configure(billingentity.InvoiceStatusDraftManualApprovalNeeded).
stateMachine.Configure(billingentity.InvoiceStatusDraftManualApprovalNeeded).
Permit(triggerApprove, billingentity.InvoiceStatusDraftReadyToIssue)

// Issuing state

fsm.Configure(billingentity.InvoiceStatusIssuing).
stateMachine.Configure(billingentity.InvoiceStatusIssuing).
Permit(triggerNext, billingentity.InvoiceStatusIssued).
Permit(triggerFailed, billingentity.InvoiceStatusIssuingSyncFailed).
OnActive(out.issueInvoice)

fsm.Configure(billingentity.InvoiceStatusIssuingSyncFailed).
stateMachine.Configure(billingentity.InvoiceStatusIssuingSyncFailed).
Permit(triggerRetry, billingentity.InvoiceStatusIssuing)

// Issued state (final)
fsm.Configure(billingentity.InvoiceStatusIssued)
stateMachine.Configure(billingentity.InvoiceStatusIssued)

out.FSM = fsm
out.StateMachine = stateMachine

return out
}

func (m *InvoiceStateMachine) StatusDetails(ctx context.Context) billingentity.InvoiceStatusDetails {
actions := make([]billingentity.InvoiceAction, 0, 4)

if ok, err := m.FSM.CanFireCtx(ctx, triggerNext); err == nil && ok {
if ok, err := m.StateMachine.CanFireCtx(ctx, triggerNext); err == nil && ok {
actions = append(actions, billingentity.InvoiceActionAdvance)
}

if ok, err := m.FSM.CanFireCtx(ctx, triggerRetry); err == nil && ok {
if ok, err := m.StateMachine.CanFireCtx(ctx, triggerRetry); err == nil && ok {
actions = append(actions, billingentity.InvoiceActionRetry)
}

if ok, err := m.FSM.CanFireCtx(ctx, triggerApprove); err == nil && ok {
if ok, err := m.StateMachine.CanFireCtx(ctx, triggerApprove); err == nil && ok {
actions = append(actions, billingentity.InvoiceActionApprove)
}

Expand All @@ -146,7 +146,7 @@ func (m *InvoiceStateMachine) StatusDetails(ctx context.Context) billingentity.I

func (m *InvoiceStateMachine) ActivateUntilStateStable(ctx context.Context) error {
for {
canFire, err := m.FSM.CanFireCtx(ctx, triggerNext)
canFire, err := m.StateMachine.CanFireCtx(ctx, triggerNext)
if err != nil {
return err
}
Expand All @@ -163,28 +163,28 @@ func (m *InvoiceStateMachine) ActivateUntilStateStable(ctx context.Context) erro
}

func (m *InvoiceStateMachine) CanFire(ctx context.Context, trigger stateless.Trigger) (bool, error) {
return m.FSM.CanFireCtx(ctx, trigger)
return m.StateMachine.CanFireCtx(ctx, trigger)
}

// FireAndActivate fires the trigger and activates the new state, if activation fails it automatically
// transitions to the failed state and activates that.
func (m *InvoiceStateMachine) FireAndActivate(ctx context.Context, trigger stateless.Trigger) error {
if err := m.FSM.FireCtx(ctx, trigger); err != nil {
if err := m.StateMachine.FireCtx(ctx, trigger); err != nil {
return err
}

err := m.FSM.ActivateCtx(ctx)
err := m.StateMachine.ActivateCtx(ctx)
if err != nil {
// There was an error activating the state, we should trigger a transition to the failed state
activationError := err

// TODO[later]: depending on the final implementation, we might want to make this a special error
// that signals that the invoice is in an inconsistent state
if err := m.FSM.FireCtx(ctx, triggerFailed); err != nil {
if err := m.StateMachine.FireCtx(ctx, triggerFailed); err != nil {
return fmt.Errorf("failed to transition to failed state: %w", err)
}

if err := m.FSM.ActivateCtx(ctx); err != nil {
if err := m.StateMachine.ActivateCtx(ctx); err != nil {
return fmt.Errorf("failed to activate failed state: %w", err)
}

Expand Down

0 comments on commit 2b1dc5a

Please sign in to comment.