Perform I/O from anywhere or push it out to the program boundary? #96
Replies: 2 comments 2 replies
-
Wow. That's so beautiful! Pure components with clearly defined interfaces compose like Lego bricks. Finally! All you need to do to build a larger system is to connect the "color coded I/O ports". And that's actually something a "place and route" software could do. Libretto's abstractions work so well on any level! No matter whether you "zoom" into the picture or step back and "zoom" out of it. At the same time the port types ("interfaces of interaction") describe exactly what capabilities a component requires from its outside world. You can only stack bricks when they fit together. Finally pure FP is like Lego—like once advertised—instead of "monads don't compose" shit. (And no, putting everything inside a Über-monad like IO / ZIO doesn't solve anything. When everything is in a monad you just "lifted" imperative programming one level up the abstraction chain, making just everything more complex for almost no gain!) |
Beta Was this translation helpful? Give feedback.
-
BTW, note: A (physical) computer also doesn't have anything conceptually like this "worm hole" construct. A computer has only dedicated interfaces on it's "edges", which speak clearly defined protocols (like e.g. USB). If you want to connect to some internal components directly this is only possible in a lab setting (where you need the appropriate tools). |
Beta Was this translation helpful? Give feedback.
-
TL;DR
Perform I/O from anywhere
To perform I/O from anywhere in the program, the language must have some support for I/O operations in the first place.
Libretto's core DSL does not have any support for I/O.
Scaletto
can support I/O by wrapping effectful Scala code.There could also be a different DSL (extending the core DSL) with built-in I/O operations, such as
The objection against doing I/O (or side-effects in general) from anywhere is that it becomes hard to reason about program behavior. The program is no longer a pure function with clearly defined input and output. It may interact with its environment behind the scenes, as if through a "wormhole". Such interaction is not manifested in program's declared interface.
Push I/O to the program boundary
An alternative is to keep the (bulk of the) program free from side-effects and only have side-effectful code at the outer edge of the program. In Libretto, a pure Libretto program would accept an interface of interaction (think of it as a capability) through which it interacts with the world.
The extra port provides I/O capability. It is wired through to all places where I/O is needed.
This approach does have some benefits, although of questionable magnitude:
Although the capability provider might have to be written in a more powerful DSL, the business logic part of the program sticks to the least power.
These benefits might not be worth the extra wiring work and one might want to reach for a more powerful DSL that supports I/O from anywhere.
Let's refine the interface of interaction
Our program probably does not need the whole I/O stack. Perhaps we only use HTTP client and connect to a SQL database. That is, our program looks like this on the inside:
Looking closer, we find that it only consumes two REST APIs and one SQL database:
We have now identified a more precise interface of the core program:
The interface now tells much more about what the program is or is not doing.
Moreover, it is now feasible to implement a provider of the interface, both mocked and real. Here is a provider implemented using a more powerful DSL, composed with our pure program:
We have thus reclaimed some of the benefits of pushing I/O to the program boundary.
Orchestrator that understands program interfaces
Normally, we would not be able to deploy our core program as is, since it has unconnected ports:
We would need to complete it so that it is of the form
Done -⚬ Done
(by composing it with capability providers etc.), which can be turned into an executable, which in turn could be packaged as a container image, which could be run as a service.In my opinion, that's a terrible way to deploy microservices.
We have to get the configuration (addresses, port numbers, connection strings, database names) into a running program, only to establish connections back to the outside world.
I would much rather have someone else, the orchestrating program, take care of wiring my service's ports. I don't want to care where the REST API that my service consumes is hosted and establish the connection myself. I just care about the schema (type) of the consumed API.
For an analogy, which do you prefer more:
stdin
and writes tostdout
. It's up to the outer (orchestrating) program to pipe data tostdin
and out ofstdout
.I prefer the latter. I want the orchestrator to be like the Unix pipe on steroids.
I want the orchestrator to understand (the types of) the unconnected ports of my program. It would then let me assemble a type-safe composition of microservices:
(The different color of the orchestrating program indicates that it might be written in a different language. Though it has to understand the interfaces of the services. To avoid any confusion, such orchestration language is purely hypothesized here. Libretto does not have anything like that.)
Notice that the "Web API X" consumed by our service is an internal API provided by a different service. We might realize that it was quite unwieldy to shoehorn the interface into a REST API, and that there is some tricky encoding and decoding from and to some more natural interface
X
going on on each side:Since the (hypothesized) orchestrator understands more general Libretto interfaces (not just REST APIs), it can connect an
X
-typed out-port to anX
-typed in-port directly. It will take care of (generating the code for) establishing connections, serialization, etc.:This is how I imagine composition of microservices. Notice that it is possible only if programs declare an explicit (and sufficiently precise) interface of interaction, instead of doing I/O themselves from deep inside the program.
Beta Was this translation helpful? Give feedback.
All reactions