TBD54566975 / ftl

FTL - Towards a 𝝺-calculus for large-scale systems
Apache License 2.0
18 stars 6 forks source link

Consider making FSMs exportable #1689

Open jiyoontbd opened 4 weeks ago

jiyoontbd commented 4 weeks ago

I have a project that contains 2 modules: payments and ticketing. I'd like to initiate a Payment from ticketing module by using the fsm declared in payments module, but currently I must wrap fsm.Send() call in a Verb inside payments module to be called by ticketing module.

current behavior

  1. in payments module, declare the FSM

    var fsm = ftl.FSM("payments",
    ftl.Start(Invoiced),
    ftl.Transition(Invoiced, Defaulted),
    ftl.Transition(Invoiced, PayinStarted),
    ftl.Transition(PayinStarted, PayinCompleted),
    ftl.Transition(PayinStarted, Defaulted),
    )
  2. in ticketing module, try to call the FSM by using something like fsm.Send(...), but realize I cannot export the FSM

  3. write an FTL verb in payments that wraps fsm.Send(...)

    //ftl:verb export
    func Invoice(ctx context.Context, req InvoiceRequest) (InvoiceResponse, error) {
        token := GetFromSomewhere()
    event := InvoiceEvent{
        ...
    }
    if err := fsm.Send(ctx, token, event); err != nil {
        return InvoiceResponse{}, fmt.Errorf(..)
    }
    
    return ...
    }
  4. In ticketing module, import the ftl verb from above and call it

    func generateInvoice(ctx context.Context, ...) (...) {
    
    invoiceRequest := payments.InvoiceRequest{
        ...
    }
    
       // ideally would like to call `fsm.Send(...)` here
    _, err := ftl.Call(ctx, payments.Invoice, invoiceRequest)
    if err != nil {
        ...
    }

expected behavior

Once fsm is created in a module, I'd like to be able to reference it from any module so I can send events directly via fsm.Send(..)

alecthomas commented 4 weeks ago

FSMs are deliberately not exportable because they require implementation details in order to use, specifically the "instance key". If you think of a module like a traditional class, it would be like exposing the internal private implementation details directly to all callers.

In your example, whatever the instance key is for each Send() call would have to be an implementation detail known to all callers, which makes future refactoring and maintenance much more difficult because you will have spread those internal details across modules.

That said, we're leaning towards a "allow anything" approach, so we should probably allow this with a warning.

This is blocked on #1691