Run a process with multiple actors, actions and responses.
In the previous tutorial we created our first Live Contract and tested it with lctest. The scenario only defined a single actor. In most workflows (especially decentralized once), multiple actors participate on the process.
Create a new handshake subdirectory and copy scenario.yml from the basic directory.
Adding the recipient actor
Let's expand the scenario for a process where two actors greet each other. We start with our previous scenario and add the recipient actor.
{"schema": "https://specs.letsflow.io/v0.3.0/scenario","title": "A handshake","actors": {"initiator": {"title":"Initiator" },"recipient": {"title":"Recipient" } },"actions": {"complete": {"title":"Complete the process","actor":"initiator" } },"states": {"initial": {"on":"complete","goto":"(success)" } }}
Greeting each other
We want the two actors to interact in the form of a simple greeting:
Initiator: Hi, how are you?
Recipient: Fine. How about you?
Initiator: Fine
The initiator will still complete the process, but from the initial state it will first do a greeting, expecting a reply. We'll add these 2 actions and subsequently the states where these can be performed. First the initiator waits on the recipient and then visa versa.
{"schema": "https://specs.letsflow.io/v0.3.0/scenario","title": "Basic user","actors": {"initiator": {"title":"Initiator" },"recipient": {"title":"Recipient" } },"actions": {"greet": {"title":"Greet the recipient","actor":"initiator" },"reply": {"title":"Reply to the greeting","actor":"recipient" },"complete": {"title":"Finish the conversation","actor":"initiator" } },"states": {"initial": {"on":"greet","goto":"wait_on_recipient" },"wait_on_recipient": {"on":"reply","goto":"wait_on_initiator" },"wait_on_initiator": {"on":"complete","goto":"(success)" } }}
We see that we transition from the initial state to wait_on_recipient, than wait on initiator and finally to a successful completion of the proces.
Running the test
As before, we want to create a run a test to make sure the process runs as expected.
main.feature
Feature: Two actors greet each other
Background:
Given a chain is created by "Joe"
Given "Joe" creates the "main" process using the "handshake" scenario
And "Joe" is the "initiator" actor of the "main" process
And "Jane" is the "recipient" actor of the "main" process
Scenario:
When "Joe" runs the "greet" action of the "main" process
Then the "main" process is in the "wait_on_recipient" state
When "Jane" runs the "reply" action of the "main" process
Then the "main" process is in the "wait_on_initiator" state
When "Joe" runs the "complete" action of the "main" process
Then the "main" process is completed
Choose to ignore
While it's nice to reply, the recipient may also choose to ignore the greeting. In this the process will be cancelled. Let's add an ignore action and make is possible to either reply or ignore in the wait on recipient state.
Rather than a single action property, the wait_on_recipient state now has an actions property that contains an array with the actions that be performed. The transition property has been replaced with a transitions property, defining the transition for each action.
Testing the scenario
We can add a new Scenario section in our test file to test the path in the process where the recipient "Jane" ignores the greeting.
main.feature
Feature: Two actors greet each other
Background:
Given a chain is created by "Joe"
Given "Joe" creates the "main" process using the "handshake" scenario
And "Joe" is the "initiator" actor of the "main" process
And "Jane" is the "recipient" actor of the "main" process
Scenario:
When "Joe" runs the "greet" action of the "main" process
Then the "main" process is in the "wait_on_recipient" state
When "Jane" runs the "reply" action of the "main" process
Then the "main" process is in the "wait_on_initiator" state
When "Joe" runs the "complete" action of the "main" process
Then the "main" process is completed
Scenario:
When "Joe" runs the "greet" action of the "main" process
Then the "main" process is in the "wait_on_recipient" state
When "Jane" runs the "ignore" action of the "main" process
Then the "main" process is cancelled
You can have multiple Scenario sections. The Background runs once and than make a backup of the databases. After each scenario the data in the database is reverted, to run the next scenario.
A conversation
In this process Jane (the recipient) can only ignore the greeting or respond with "fine". But unfortunately it's not always sunshine and rainbows. It's possible to define different responses for an action.
Up until now, we've created the scenario first and added a test later. It's recommended to take a TDD style approach, where you start with a test and then write or modify your scenario.
Jane might answer with "not so good", asking for sympathy from Joe. He might still end the conversation (complete) by answering with "Sorry to hear that" and move on. A nicer thing to is is to ask "What's the matter?" upon which Jane can give a response.
Testing the scenario
We could add a new Scenario section to our main.feature test file, but instead we'll create a new test file recipient-not-good.feature specifically for the use case where the greeting turns into a bigger conversation.
We already have two paths we want to test. One where Joe brushes Jane off and one where he's genuinely interested. We'll expand the background to the point where Jane give her response.
recipient-not-good.feature
Feature: Two actors meet. The recipient is not doing well.
Background:
Given a chain is created by "Joe"
Given "Joe" creates the "main" process using the "handshake" scenario
And "Joe" is the "initiator" actor of the "main" process
And "Jane" is the "recipient" actor of the "main" process
When "Joe" runs the "greet" action of the "main" process
Then the "main" process is in the "wait_on_recipient" state
Scenario:
When "Jane" runs the "reply" action of the "main" process giving a "not_good" response
Then the "main" process is in the "expect_sympathy" state
When "Joe" runs the "complete" action of the "main" process
Then the "main" process is completed
Scenario:
When "Jane" runs the "reply" action of the "main" process giving a "not_good" response
Then the "main" process is in the "expect_sympathy" state
When "Joe" runs the "sympathize" action of the "main" process
Then the "main" process is in the "recipient_can_elaborate" state
When "Jane" runs the "elaborate" action of the "main" process with:
| reason | My cat is stealing my boyfriend. She pnly cuddles with him. |
Then the "main" process is in the "wait_on_initiator" state
When "Joe" runs the "complete" action of the "main" process
Then the "main" process is completed
When the recipient elaborates, she'll send the reason as additional response data. In the tutorial "A proper introduction" we'll see how to use the response data in the process.
The scenario
When we run this test we can see that it fails. The not good response hasn't been defined. We also need to define the sympathize and elaborate actions and the expect sympathy and recipient can elaborate states.
The default response has the key "ok" by default. If we define a set of responses without "ok", we also need to set the default_response property of the action.
Keeping the conversation going
Instead of asking "What's the matter?", Joe could say "Sorry to hear that. Please tell me more". By repeating the sympathize action, Joe can keep the conversation going.
recipient-not-good.feature
Feature: Two actors meet. The recipient is not doing well.
Background:
Given a chain is created by "Joe"
Given "Joe" creates the "main" process using the "handshake" scenario
And "Joe" is the "initiator" actor of the "main" process
And "Jane" is the "recipient" actor of the "main" process
When "Joe" runs the "greet" action of the "main" process
Then the "main" process is in the "wait_on_recipient" state
Scenario:
When "Jane" runs the "reply" action of the "main" process giving a "not_good" response
Then the "main" process is in the "expect_sympathy" state
When "Joe" runs the "complete" action of the "main" process
Then the "main" process is completed
Scenario:
When "Jane" runs the "reply" action of the "main" process giving a "not_good" response
Then the "main" process is in the "expect_sympathy" state
When "Joe" runs the "sympathize" action of the "main" process
Then the "main" process is in the "recipient_can_elaborate" state
When "Jane" runs the "elaborate" action of the "main" process with:
| reason | My cat is stealing my boyfriend. |
Then the "main" process is in the "expect_sympathy" state
When "Joe" runs the "complete" action of the "main" process
Then the "main" process is completed
Scenario:
When "Jane" runs the "reply" action of the "main" process giving a "not_good" response
Then the "main" process is in the "expect_sympathy" state
When "Joe" runs the "sympathize" action of the "main" process
Then the "main" process is in the "recipient_can_elaborate" state
When "Jane" runs the "elaborate" action of the "main" process with:
| reason | My cat is stealing my boyfriend. |
Then the "main" process is in the "expect_sympathy" state
When "Joe" runs the "sympathize" action of the "main" process
Then the "main" process is in the "recipient_can_elaborate" state
When "Jane" runs the "elaborate" action of the "main" process with:
| reason | She always comes in to cuddle with him. |
Then the "main" process is in the "expect_sympathy" state
When "Joe" runs the "sympathize" action of the "main" process
Then the "main" process is in the "recipient_can_elaborate" state
When "Jane" runs the "elaborate" action of the "main" process with:
| reason | Misty has a mean purr. I know it's to taunt me. |
Then the "main" process is in the "expect_sympathy" state
When "Joe" runs the "complete" action of the "main" process
Then the "main" process is completed
Modifying the scenario
To do so, we just need to modify the recipient can elaborate state to transition to expect sympathy. Since the initiator can also complete the process from that state, it doesn't affect our existing use cases.
Previous tests should be, of course, also modified.
As briefly show in the previous tutorial, actors and actions can have a title. We can also provide a title for states and responses. These titles won't affect the process, but can be used in a UI to display to the end users. Additionally it will make the scenario a bit less cryptic.
The title should tell what we're waiting on. The title of the action describes what an actor will do, while the title of the response describes what the actor has done (or has said in this example).
Instructions for a specific actor can be defined for each state. Again, this doesn't influence the process, but can be used in a UI.
schema:https://specs.letsflow.io/v0.3.0/scenariotitle:A handshakeactors:initiator:title:Initiatorrecipient:title:Recipientactions:greet:actor:initiatortitle:Greet the person you're meetingresponses:ok:title:Hi, how are you?reply:actor:recipienttitle:Respond to the greetingresponses:ok:title:Fine. How about you?not_good:title:Not so good.ignore:actor:recipienttitle:Ignore the greetingsympathize:actor:initiatortitle:Ask furtherresponses:ok:title:Sorry to hear that. Please tell me more.elaborate:actor:recipienttitle:Tell what's the matter.complete:actor:initiatortitle:End the conversationstates:initial:action:greettransition:wait_on_recipientwait_on_recipient:title:Waiting on the recipient to respond.instructions:recipient:Respond or, if you're feeling rude, ignore it.actions: - reply - ignoretransitions: - action:replyresponse:oktransition:wait_on_initiator - action:replyresponse:not_goodtransition:expect_sympathy - action:ignoretransition::cancelledexpect_sympathy:title:Waiting on the initiator to respond.instructions:initiator:Ask further or end the conversation politely.actions: - sympathize - completetransitions: - action:sympathizetransition:recipient_can_elaborate - action:completetransition::successrecipient_can_elaborate:title:Waiting on the recipient to elaborate.instructions:recipient:Please explain why it's not going well.action:elaboratetransition:expect_sympathywait_on_initiator:title:Waiting on the initiator to respond.action:completetransition::success
{"schema": "https://specs.letsflow.io/v0.3.0/scenario","title": "A handshake","actors": {"initiator": {"title":"Initiator" },"recipient": {"title":"Recipient" } },"actions": {"greet": {"actor":"initiator","title":"Greet the person you're meeting","responses": {"ok": {"title":"Hi, how are you?" } } },"reply": {"actor":"recipient","title":"Respond to the greeting","responses": {"ok": {"title":"Fine. How about you?" },"not_good": {"title":"Not so good." } } },"ignore": {"actor":"recipient","title":"Ignore the greeting" },"sympathize": {"actor":"initiator","title":"Ask further","responses": {"ok": {"title":"Sorry to hear that. Please tell me more." } } },"elaborate": {"actor":"recipient","title":"Tell what's the matter." },"complete": {"actor":"initiator","title":"End the conversation" } },"states": {"initial": {"action":"greet","transition":"wait_on_recipient" },"wait_on_recipient": {"title":"Waiting on the recipient to respond.","instructions": {"recipient":"Respond or, if you're feeling rude, ignore it." },"actions": ["reply","ignore" ],"transitions": [ {"action":"reply","response":"ok","transition":"wait_on_initiator" }, {"action":"reply","response":"not_good","transition":"expect_sympathy" }, {"action":"ignore","transition":":cancelled" } ] },"expect_sympathy": {"title":"Waiting on the initiator to respond.","instructions": {"initiator":"Ask further or end the conversation politely." },"actions": ["sympathize","complete" ],"transitions": [ {"action":"sympathize","transition":"recipient_can_elaborate" }, {"action":"complete","transition":":success" } ] },"recipient_can_elaborate": {"title":"Waiting on the recipient to elaborate.","instructions": {"recipient":"Please explain why it's not going well." },"action":"elaborate","transition":"expect_sympathy" },"wait_on_initiator": {"title":"Waiting on the initiator to respond.","action":"complete","transition":":success" } }}
Adding titles and instructions is optional. While it makes the scenario less cryptic. It also substantially increase lines of code of the scenario.
We didn't specify a title for the complete action. That's because the response that would giving in the wait on initiator state would would "Fine". In the state where we expect sympathy from initiator would the response for the same action would be "Sorry, to hear that".
We could define a new action, but there are better ways to handle this. We'll discuss that in the next tutorial.
Now you!
The initiator can still only reply with "Fine" when the recipient ask "How about you?". We should change that, so the initiator could also reply with "Could be better". To keep it simple, this will not resort in a conversation.
Keep the structure so that it's still up to the initiator to complete the process. The recipient can only respond with "Sorry to hear that", after which we transition to the state where we wait on the initiator to end the process.
In other words; The initiator can respond with no good, after witch the process transitions to a new state where we expect an acknowledgement from the recipient.
Create a new test file named initiator-not-good.feature
The Before section should go through the process all the way to the state where we wait on the the initiator.
The initiator should then respond with no good, after witch the process transitions to a new state where we expect an acknowledgement from the recipient.
Create a new test Scenario section for this use case.