Microservices
Services running on the same hub can talk to one another directly.
To demonstrate this, we need two scripts. Here is examples/bar.pf
:
cmd
returnTheThing(s) :
post "Hello " + s + "!" to Output()
doTheThing(s) :
post "**** Hello " + s + "! ****" to Terminal()
Let's start it up in the REPL:
→ hub run "examples/bar.pf" as "Bar"
Starting script 'examples/bar.pf' as service 'Bar'.
Bar → returnTheThing "Marmaduke"
Hello Marmaduke!
Bar →
Now let's take a look at examples/foo.pf
:
contacts
Bar::"examples/bar.pf"
cmd
getTheThing(s) :
get x from Bar --- returnTheThing |s|
post "* " + x + " *" to Output()
makeBarDoTheThing(s) :
post Bar --- doTheThing |s|
Note the word contacts
. This is a headword, as defined earlier. What it does is declare that the service expects to be able to talk to another service called Bar
.
So if we try it out in the REPL:
Bar → hub run "examples/foo.pf" as "Foo"
Starting script 'examples/foo.pf' as service 'Foo'.
Foo → getTheThing "Marmaduke"
* Hello Marmaduke! *
Foo → makeBarDoTheThing "Ermintrude"
"**** Hello Ermintrude! ****"
Foo →
Some things to note:
-
Just as with SQL, we use the
|
delimiter to indicate expressions to be embedded from the calling service. So we must writeget x from Bar --- returnTheThing |s|
and not justget x from Bar --- returnTheThing s
because in the latter case the serviceBar
would look for the variables
in its own environment, fail to find it, and return an error. As with embedding in SQL, the|...|
can contain any expression that makes sense in the calling service, not just variable names. -
What the calling service gets with a
get
command is whatever the called service posts toOutput
. -
If the called service posts nothing to
Output
(exceptok
, as explained below) then you should usepost
,put
, ordelete
. As usual it is up to you to make sure that the name of the command reflects the semantics. -
You are only allowed to call commands, not functions. This is because any two services which wanted to have a function in common could and should put it in a common library.
-
When it comes to calling commands, the calling service has exactly the same access rights to the called service as would a human user talking to the REPL of the called service. What is private from the REPL is private from another service, and vice-versa.
-
If hotcoding is turned on, then a contact of your service is rebuilt if its source code has been changed.
In the page on Basic IO, we noted that you could output either to Terminal
or to Output
, and promised to explain the difference later. Now is the time.
Sending a value to Terminal
prints a string representation of the value directly to the terminal that the hub is running on. Sending it to Output
sends the value to whoever or whatever is making the request, whether it's you in the REPL, another Pipefish service, a desktop Pipefish client, or whatever.
At present the string representation sent to Terminal
is in the form of a Pipefish literal, e.g. "Hello world!"
is displayed as "Hello world!"
and not Hello world!
as it would appear in the REPL. This feature is under review and may not be stable.
One other difference is that when Pipefish is asked to do something it always sends something to Output
. If it sends nothing else, it sends what is internally called a Successful Object
but which you will have met as the little green ok
that you get as a result when you ask Pipefish to do something and there is no other output.
So when talking to another service, a command that posts an ok
to Output
must be accessed by put
, post
, or delete
, and a command that posts an actual value to Output()
must be accessed by get
. A command that conditionally does one or the other will break either way of accessing it. Don't do that.
A contact of a service is, by definition, another service running on the hub. Whereas an imported module of a service is inside of that service.
So a service communicates with its contacts by means of put
and post
and get
commands, whereas it accesses its imported modules by namespacing, e.g. strings.toUpper("hello")
.
When Service A launches service B as a contact, this does not imply that service B will automatically be shut down when service A shuts down. This must be done explicitly. Imported modules, by contrast, are inside the service that imports them, and so are shut down and rebuilt along with the service.
🧿 Pipefish is distributed under the MIT license. Please steal my code and ideas.