Feedback on SysML v2 use case model from functional requirements

Hello,

I hope you are doing well.

I recently tried to model a simple system (Ticket Vending Machine) in SysML v2, starting from a set of functional requirements and converting them into a use case model using textual notation.

I would really appreciate your feedback on whether my approach is correct, and whether the level of detail I used is sufficient.

Functional Requirements:

  • The system shall allow a commuter to initiate a ticket purchase

  • The system shall allow the commuter to select ticket type

  • The system shall display ticket details, including price and selected options

  • The system shall process payments through a bank when a debit or credit card is used

  • The system shall send payment authorization requests to the bank

  • The system shall receive approval or rejection responses from the bank

  • The system shall notify the commuter if the payment is rejected

  • The system shall generate and issue a ticket after successful payment

  • The system shall print the ticket for the commuter

  • The system shall allow the commuter to cancel the transaction before payment is completed

Original Use Case Diagram:

SysML v2 Textual Model:

package 'Ticket Vending Machine' {

    public import ScalarValues::Boolean;

    // System (Subject)
    part def 'Ticket Vending Machine';

    // Actor Definitions
    part def Commuter;
    part def Bank;

    // Data Definitions
    part def Ticket;
    part def PaymentRequest;
    part def PaymentResponse {
        attribute authorized : Boolean;
    }

    // Use Case Definition
    use case def 'Purchase Ticket' {

        subject tvm : 'Ticket Vending Machine';
        actor commuter : Commuter;
        actor bank : Bank;

        objective 'Successful Purchase' {
            doc /* Commuter receives a printed ticket after successful payment */
        }
    }

    // System Context (Use Case Usage)
    part def SystemContext {

        part myTvm : 'Ticket Vending Machine';
        part myCommuter : Commuter;
        part myBank : Bank;

        use case purchaseTicket : 'Purchase Ticket' {

            subject :>> tvm = myTvm;
            actor   :>> commuter = myCommuter;
            actor   :>> bank = myBank;

            // Attributes
            attribute commuterCancels : Boolean;
            attribute authorized : Boolean;

            // Data
            part ticket : Ticket;
            part request : PaymentRequest;
            part response : PaymentResponse;

            // Actions
            action 'Initiate Purchase';
            action 'Select Ticket Type';
            action 'Display Ticket Details';
            action 'Send Auth Request' {
                out request;
            }
            action 'Receive Auth Response' {
                in response;
                authorized = response.authorized; // or purchaseTicket.response.authorized?
            }
            action 'Notify Payment Failure';
            action 'Cancel Transaction';
            action 'Issue Ticket' {
                out ticket;
            }
            action 'Print Ticket';

            // Composite Actions
            action 'Fulfill Order' {
                first 'Issue Ticket'; // or first start?
                then 'Print Ticket';
            }
            action 'Process Card Payment' {
                first 'Send Auth Request'; // or first start?
                then 'Receive Auth Response';
                then decide;
                    if authorized then 'Fulfill Order';
                    else 'Notify Payment Failure';
            }

            // Flow
            first start;
            then 'Initiate Purchase';
            then 'Select Ticket Type';
            then 'Display Ticket Details';
            then decide;
                if commuterCancels then 'Cancel Transaction';
                else 'Process Card Payment';
            then done;
        }
    }
}

My main questions are:

  1. Does this model correctly represent the given functional requirements, or are there important issues or missing elements?

  2. Is this level of detail appropriate for a use case model?

  3. Should I explicitly specify which actor is responsible for each action, or is the current representation sufficient?

Any feedback or suggestions for improvement would be greatly appreciated.

Thank you.

Hi Hadeel, thanks for sharing this — it’s a clean, readable model and a good example to discuss. A few thoughts on your three questions.

Q1 — correctness and gaps. Rather than point them out directly, I’d suggest a small exercise that will surface them naturally: try encoding the functional requirements themselves as SysML v2 requirement defs, then add satisfy relationships from each action (or composite action) to the requirement it fulfills. Once every requirement has at least one satisfier and every action traces back to something, gaps tend to stand out on their own. Two spots worth looking at while you do that: commuterCancels is branched on but never assigned anywhere, and the flow after ‘Notify Payment Failure’ falls straight through to done — worth checking whether those match what the requirements actually ask for.

Q2 — level of detail. This really comes down to the deliverable: who’s reading it, and what decision it needs to support. A use case model for a stakeholder review usually stays at the level of goals and include/extend relationships; one feeding into behavioral analysis or a
handoff to designers tends to go deeper into succession and decide nodes (roughly where you are now). If you can pin down the audience first, the right level of detail usually follows from that.

Q3 — actor responsibility per action. Partly the same audience question — some MBSE practices expect per-action actor allocation at the use case layer, others consider it out of scope there. Since this is more of a methodology call than a SysML syntax one, I’d rather loop in my colleague @KestutisJankevicius, who can give you a grounded engineering answer.

One small syntactic note that might help: authorized = response.authorized; is fine as written — the in response parameter is in scope inside the action, so no extra qualification is needed.

Hope this helps — happy to dig into any of it further if useful.

Adam Layne, PhD

Thank you very much for your detailed and thoughtful feedback, I really appreciate it.

It was very helpful and gave me a better understanding of how to approach the model.

I have updated the model based on your comments, especially regarding the cancellation handling and the flow after payment failure (including adding a retry loop). I would appreciate it if you could take a quick look and let me know if this direction is correct.


use case purchaseTicket : 'Purchase Ticket' {
    // ...

    // Actions
    action 'Initiate Purchase';
    action 'Select Ticket Type' {
        in ticketTypeSelection; // Input for ticket type selection
    }
    action 'Display Ticket Details' {
        out ticketDetails; // Output for displaying ticket details
    }
    action 'Send Auth Request' {
        out request;
    }
    action 'Receive Auth Response' {
        in response;
        authorized = response.authorized;
    }

    // ---------------------------
    // Assign commuter cancellation input to the attribute for decision making
    action 'Check Cancellation' {
        in commuterCancels;
    }
    // ---------------------------

    action 'Notify Payment Failure';
    action 'Cancel Transaction';
    action 'Issue Ticket' {
        out ticket;
    }
    action 'Print Ticket';

    // Composite Actions
    action 'Fulfill Order' {
        first 'Issue Ticket';
        then 'Print Ticket';
    }
    
    // ---------------------------
    // Handling payment retries
    action 'Handle Failure' {
        first 'Notify Payment Failure';
        then 'Payment Retry Loop'; // Loop back to payment retry if authorization fails
    }
    // ---------------------------

    action 'Process Card Payment' {
        first 'Send Auth Request';
        then 'Receive Auth Response';
        then decide;
            if authorized then 'Fulfill Order';
            else 'Handle Failure';
    }

    // Main Flow
    first start;
    then 'Initiate Purchase';
    then 'Select Ticket Type';
    then 'Display Ticket Details';
    then merge 'Payment Retry Loop'; // Loop for handling payment retries
    then 'Check Cancellation';
    then decide;
        if commuterCancels then 'Cancel Transaction';
        else 'Process Card Payment';
    then done;
}

Thanks again for your guidance.

The error Syside reports is a reference-error: No Feature named 'authorized' found on response.authorized, and the cause is that in response; declares a fresh parameter named response that shadows the outer part response : PaymentResponse;. The name matches, but in SysML v2 a name match between an inner parameter and an outer usage does not imply inheritance — so the untyped parameter has no authorized feature to resolve.

Two ways to fix it, depending on intent:

  • in response : PaymentResponse; — gives the parameter a type, which is right when you just want a fresh parameter that happens to be of that type.
  • in :>> response; — redefines the outer feature via :>>, which is right when you want the action parameter to be the outer feature (identity, not just same type).

Either restores the assignment; both pass Syside cleanly. Without one of them, the outer authorized attribute never gets bound, which leaves the downstream if authorized then ... decision with nothing driving it.

If you haven’t yet, adding requirement defs for each FR with satisfy links to the realizing actions tends to surface the “any gaps?” question mechanically rather than by inspection.

Thank you for the clear explanation, that helped me understand the issue.

I have a quick follow-up question regarding modeling the request/response data.

Right now, I defined them as parts at the use case level and then used them inside the actions, like this:

part request : PaymentRequest;
part response : PaymentResponse;

action 'Send Auth Request' {
    out :>> request;
}

action 'Receive Auth Response' {
    in :>> response;
    authorized = response.authorized;
}

Alternatively, I can define them locally inside the actions without defining them as parts:

action 'Send Auth Request' {
    out request : PaymentRequest;
}

action 'Receive Auth Response' {
    in response : PaymentResponse;
    authorized = response.authorized;
}

From a modeling perspective, is one approach preferred over the other?
Or do they serve the same purpose, and it mainly depends on whether the data needs to be shared across actions?

Thanks again for your help.

It mainly depends on whether the data needs to be shared across actions.

The way I’d frame it: does the same object flow through several actions, or does each action produce its own?

  • part request : PaymentRequest; at the use-case level, then out :>> request; / in :>> request; inside actions. Each action still owns its own parameter feature — they aren’t collapsed into one feature in the model — but each of those inner features carries a Redefinition relationship whose target is the same outer request. That outer feature is also the only place the PaymentRequest typing is declared; the inner features inherit it. So the identity and the typing have one source, and the :>> ties every use back to it.
  • out request : PaymentRequest; defined only inside an action creates a feature that is fully local: its own name, its own typing, no Redefinition relationship to anything outer. Two such actions end up with two independent features that merely happen to share a type. To connect them you’d add a binding/succession at the enclosing level, but nothing in the model says “this is the same request.”

Intuition:

  • If the data has an identity that persists across steps (the PaymentRequest the commuter builds is the same one the bank sees), lift it to the use case level and redefine with :>>. The Redefinition relationships give you the traceability.
  • If the data is genuinely transient to an action — an intermediate value, a local computation — keep it local.

For your ticket-vending case, PaymentRequest and PaymentResponse both feel like shared-identity data: one request is built, sent, and answered. Ticket similarly has one identity from generation onward.

A small exercise that tends to settle this: sketch the lifeline of the object — where is it created, where is it read, where does it cease to matter? Features whose lifeline spans multiple actions want to live at the enclosing level.

The broader “what should a use case model say about data flow” question is more methodology than syntax. Like in my previous messages, it depends on what audience the use case is for.

Thank you very much for your detailed explanation and for taking the time to clarify everything.

This was very helpful, and I now have a much clearer understanding.

I really appreciate your help.