-
Notifications
You must be signed in to change notification settings - Fork 10
WIP feat: add an article about crossing #133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
moul
wants to merge
4
commits into
main
Choose a base branch
from
dev/moul/crossing
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
--- | ||
publication_date: 2025-05-XXXT00:00:00Z | ||
slug: XXX | ||
tags: [gnovm] | ||
authors: [manfred] | ||
--- | ||
|
||
# Interrealm Calls vs. Traditional Microservices: A Paradigm Shift | ||
|
||
Understanding Gno's interrealm call mechanism can be simplified by comparing it | ||
to the familiar concept of microservices in traditional distributed systems. | ||
While both aim to modularize functionality, Gno offers a fundamentally simpler, | ||
more robust, and integrated approach. | ||
|
||
## The Traditional Microservice Experience | ||
|
||
In typical microservice architectures (e.g., using gRPC, REST APIs), developers | ||
face several inherent complexities: | ||
|
||
1. **Connection Management:** Explicit steps are needed to establish connections | ||
(e.g., `grpc.Dial`, HTTP client setup). These connections can fail due to | ||
network issues, configuration errors, or service unavailability, requiring | ||
dedicated error handling. | ||
2. **Network Failures:** A call to a remote service can fail *not only* because | ||
the service's logic encountered an error, but also due to transient network | ||
problems, timeouts, or infrastructure issues. Distinguishing between these | ||
failure modes adds complexity. | ||
3. **Data Serialization:** Data exchanged between services must often be | ||
explicitly serialized and deserialized into primitive types or specific | ||
formats (e.g., JSON, Protobuf). Passing complex objects or pointers directly | ||
is generally not possible. | ||
4. **Manual Transaction Management:** Ensuring atomicity across multiple service | ||
calls is notoriously difficult. If a sequence of calls needs to succeed or | ||
fail together, developers must implement complex rollback or compensation | ||
logic. For example, if Service A succeeds but a subsequent call to Service B | ||
fails, the developer is responsible for: | ||
* Rolling back local state changes. | ||
* Potentially calling Service A again with a compensating action to undo its | ||
previous operation. | ||
This manual coordination is error-prone and can lead to inconsistent states, | ||
often requiring complex distributed locking mechanisms. | ||
5. **Language/Protocol Proliferation:** Developers typically need to master the | ||
primary programming language *plus* various data formats (JSON, Protobuf, | ||
XML) and potentially interface definition languages (IDLs). | ||
|
||
```ascii | ||
Traditional Microservices: | ||
|
||
[Your App] ------> Connect() -----> [Network] ------> [Service A] | ||
| | | | | ||
|---< Fail? <-----| | | | ||
| | | | | ||
+-----> Call(primitive) -> Marshal -> + -> Unmarshal -> +---> Logic A ---+ | ||
| | | | | | ||
|<---- Fail? <----|<---- Fail? <-----| |<--- Logic OK ---+ | ||
| | | | | ||
+-----> Call(primitive) -> Marshal -> + -> Unmarshal -> [Service B] ---> Logic B ---+ | ||
| | | | | | ||
|<---- Fail? <----|<---- Fail? <-----| |<--------- Logic FAIL! -----+ | ||
| | | ||
+---> // Now what? Manually rollback state? | | ||
| // Call Service A again to compensate? | | ||
+------------------------------------------------------+ | ||
``` | ||
|
||
## The Gno Interrealm Experience | ||
|
||
Gno's interrealm calls, facilitated by the GnoVM, provide a seamless and | ||
powerful alternative, akin to an idealized microservice architecture: | ||
|
||
1. **Transparent Connection:** The "connection" between realms is managed | ||
implicitly by the GnoVM. There's no separate connection step to manage or | ||
fail. | ||
2. **Reliable Calls (Logic-Centric Failures):** Interrealm calls abstract away | ||
network failures. A call fails *only* if the logic within the called realm's | ||
function panics or returns an error. The underlying transport is guaranteed | ||
by the VM and the consensus layer. | ||
3. **Rich Data Types:** Gno allows passing complex data types, including slices, | ||
maps, structs, and even pointers (with specific rules), directly between | ||
realms without manual serialization/deserialization. | ||
4. **Automatic Atomic Transactions:** This is a major advantage. All operations | ||
within a Gno transaction, including multiple interrealm calls across | ||
different realms, are atomic. If *any* part of the transaction fails (e.g., | ||
a panic in one of the called realms), the *entire* transaction is | ||
automatically rolled back by the GnoVM. There is no need for manual | ||
compensation logic; consistency is guaranteed at the VM level. | ||
5. **Unified Language:** Developers only need to know Gno. The language used for | ||
writing realm logic is the same language used for making interrealm calls. | ||
No separate IDLs or data formats are required. | ||
|
||
```ascii | ||
Gno Interrealm Call: | ||
|
||
[Realm A] ----> call_other_realm(complex_type) ----> [GnoVM Manages Interaction] ----> [Realm B] | ||
| ^ | | ||
| | | | ||
| +-------------------- Logic OK ------------------------------+ | ||
| | | ||
+----------------------------------- Logic OK -------------------------------------+ | ||
|
||
// OR, if Realm B fails: | ||
|
||
[Realm A] ----> call_other_realm(complex_type) ----> [GnoVM Manages Interaction] ----> [Realm B] | ||
| ^ | | ||
| | X<--- Logic Panic! | ||
| +--------------------- Panic Bubbles ------------------------+ | ||
| | ||
X<----------- GnoVM Automatically Rolls Back ENTIRE Transaction -------------------+ | ||
|
||
``` | ||
|
||
In essence, Gno provides the "best microservice experience" imaginable: simple, | ||
highly composable, robust error handling, and automatic transactional | ||
consistency, all within a single language and runtime environment. This opens | ||
the door to powerful new patterns of code reuse and composability directly on | ||
the blockchain. | ||
|
||
## Understanding `cross` and `crossing` | ||
|
||
While basic interrealm calls provide access to another realm's functions and | ||
data (read-only), the optional `cross` keyword and `crossing()` function | ||
modifier introduce control over the execution *context*. | ||
|
||
* **Calling without `cross` (Non-crossing call):** When you call a function in | ||
another realm *without* using `cross`, you are essentially executing that | ||
remote code within your *current* realm's context and memory space. Direct | ||
modification of the remote realm's state is restricted. Think of it like | ||
borrowing a library from another service but running it locally. Trying to | ||
modify memory of another realm without `cross` is forbidden and will result in | ||
a panic. For example, if Realm A calls a function in Realm B that attempts | ||
`realmB.someState = "new value"`, this operation will fail if not done within | ||
a `crossing()` call. | ||
|
||
```ascii | ||
Non-crossing Call (Realm A calls Realm B's function): | ||
|
||
[Realm A Context]------------+ | ||
| | | ||
| Executes Realm B's code | | ||
| (within Realm A's context) | | ||
| | | ||
+----------------------------+ | ||
| | ||
| Reads data from | ||
V | ||
[Realm B State] (Read-Only Access) | ||
``` | ||
|
||
* **Calling with `cross` (Crossing call):** When you call a `crossing()` | ||
function using the `cross(realmB.Function)(...)` syntax, you explicitly shift | ||
the execution context *into* the target realm (Realm B). The function now | ||
executes with Realm B's permissions, environment (`std.CurrentRealm()` | ||
changes), and can directly modify Realm B's state. This is like truly entering | ||
the other microservice's environment to perform an operation; Realm A is | ||
allowed to modify Realm B's memory, but this modification happens within Realm | ||
B's context. | ||
|
||
```ascii | ||
Crossing Call (Realm A calls Realm B's crossing function): | ||
|
||
[Realm A Context] --cross()--> [Realm B Context]-----------+ | ||
| | | ||
| Executes Realm B's code | | ||
| (within Realm B's context)| | ||
| Can modify Realm B State | | ||
| | | ||
[Realm A State] <----return----+- std.CurrentRealm() is B -+ | ||
| | | ||
+---------------------------+ | ||
``` | ||
|
||
A common question is: What if Realm A crosses into Realm B, and then Realm B | ||
attempts to cross back into Realm A within the same transaction? While a direct | ||
circular import between realm packages is disallowed at compile time, a circular | ||
*call* sequence (A -> B -> A) during a crossing call is more nuanced. When B | ||
calls back into A, the context shifts *again*. It's not as if A never crossed; | ||
rather, it's a nested context change. The execution is now in Realm A's context, | ||
but this is a *new* context initiated by Realm B. `std.CurrentRealm()` would | ||
reflect Realm A, and modifications would apply to Realm A's state. However, the | ||
overall transaction's atomicity is still managed by the GnoVM. If any part of | ||
this A -> B -> A sequence fails, the entire set of operations across all | ||
involved realms is rolled back. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is my common question: When calling another realm's crossing function, why does the calling realm have to call with
cross
? Shouldn't this be the default and be handled automatically by the GnoVM? In other words, what is an example where calling another realm's crossing function should not cross?