|
| 1 | +# Golem Go Example with Multiple Components and Worker to Worker RPC Communication |
| 2 | + |
| 3 | +## Building |
| 4 | +The project uses [magefile](https://magefile.org/) for building. Either install the tool binary `mage`, |
| 5 | +or use the __zero install option__: `go run mage.go`. This readme will use the latter. |
| 6 | + |
| 7 | +To see the available commands use: |
| 8 | + |
| 9 | +```shell |
| 10 | +go run mage.go |
| 11 | +Targets: |
| 12 | + addStubDependency adds generated and built stub dependency to componentGolemCliAddStubDependency |
| 13 | + build alias for BuildAllComponents |
| 14 | + buildAllComponents builds all components |
| 15 | + buildComponent builds component by name |
| 16 | + buildStubComponent builds RPC stub for component |
| 17 | + clean cleans the projects |
| 18 | + deploy adds or updates all the components with golem-cli\'s default profile |
| 19 | + generateBinding generates go binding from WIT |
| 20 | + generateNewComponent generates a new component based on the component-template |
| 21 | + stubCompose composes dependencies |
| 22 | + testIntegration tests the deployed components |
| 23 | + tinyGoBuildComponentBinary build wasm component binary with tiny go |
| 24 | + updateRpcStubs builds rpc stub components and adds them as dependency |
| 25 | + wasmToolsComponentEmbed embeds type info into wasm component with wasm-tools |
| 26 | + wasmToolsComponentNew create golem component with wasm-tools |
| 27 | +``` |
| 28 | + |
| 29 | +For building the project for the first time (or after `clean`) use the following commands: |
| 30 | + |
| 31 | +```shell |
| 32 | +go run mage.go updateRpcStubs |
| 33 | +go run mage.go build |
| 34 | +``` |
| 35 | + |
| 36 | +After this, using the `build` command is enough, unless there are changes in the RPC dependencies, |
| 37 | +in that case `updateRpcStubs` is needed again. |
| 38 | + |
| 39 | +The final components that are usable by golem are placed in the `target/components` folder. |
| 40 | + |
| 41 | +## Deploying and testing the example |
| 42 | + |
| 43 | +In the example 3 simple counter components are defined, which can be familiar from the smaller examples. To showcase the remote calls, the counters `add` functions are connected, apart from increasing their own counter: |
| 44 | + - **component one** delegates the add call to **component two** and **three** too, |
| 45 | + - and **component two** delegates to **component three**. |
| 46 | + |
| 47 | +In both cases the _current worker name_ will be used as _target worker name_ too. |
| 48 | + |
| 49 | +Apart from _worker name_, remote calls also require the **target components' deployed ID**. For this the example uses environment variables, and uses the `lib/cfg` subpackage (which is shared between the components) to extract it. |
| 50 | + |
| 51 | +The examples assume a configured default `golem-cli` profile, and will use that. |
| 52 | + |
| 53 | +To test, first we have to build the project as seen in the above: |
| 54 | + |
| 55 | +```shell |
| 56 | +go run mage.go updateRpcStubs |
| 57 | +go run mage.go build |
| 58 | +``` |
| 59 | + |
| 60 | +Then we can deploy our components with `golem-cli`, for which a wrapper magefile command is provided: |
| 61 | + |
| 62 | +```shell |
| 63 | +go run mage.go deploy |
| 64 | +``` |
| 65 | + |
| 66 | +Once the components are deployed, a simple example integration test suite can be used to test the components. |
| 67 | +The tests are in the [/integration/integration_test.go](/integration/integration_test.go) test file, and can be run with: |
| 68 | + |
| 69 | +```shell |
| 70 | +go run mage.go testIntegration |
| 71 | +``` |
| 72 | + |
| 73 | +The `TestDeployed` simply tests if our components metadata is available through `golem-cli component get`. |
| 74 | + |
| 75 | +The `TestCallingAddOnComponentOneCallsToOtherComponents` will: |
| 76 | + - get the _component URNs_ with `golem-cli component get` |
| 77 | + - generates a _random worker name_, so our tests are starting from a clean state |
| 78 | + - adds 1 - 1 worker for component one and component two with the required _environment variables_ containing the other workers' _component ids_ |
| 79 | + - then makes various component invocations with `golem-cli worker invoke-and-await` and tests if the counters - after increments - are holding the right value according to the delegated `add` function calls. |
| 80 | + |
| 81 | +## Adding Components |
| 82 | + |
| 83 | +Use the `generateNewComponent` command to add new components to the project: |
| 84 | + |
| 85 | +```shell |
| 86 | +go run mage.go generateNewComponent component-four |
| 87 | +``` |
| 88 | + |
| 89 | +The above will create a new component in the `components/component-four` directory based on the template at [/component-template/component](/component-template/component). |
| 90 | + |
| 91 | +After adding a new component the `build` command will also include it. |
| 92 | + |
| 93 | +## Using Worker to Worker RPC calls |
| 94 | + |
| 95 | +### Under the hood |
| 96 | + |
| 97 | +Under the hood the _magefile_ commands below (and for build) use generic `golem-cli stubgen` subcommands: |
| 98 | + - `golem-cli stubgen build` for creating remote call _stub WIT_ definitions and _WASM components_ for the stubs |
| 99 | + - `golem-cli stubgen add-stub-dependency` for adding the _stub WIT_ definitions to a _component's WIT_ dependencies |
| 100 | + - `golem-cli stubgen compose` for _composing_ components with the stub components |
| 101 | + |
| 102 | +### Magefile commands and required manual steps |
| 103 | + |
| 104 | +The dependencies between components are defined in the [/magefiles/magefile.go](/magefiles/magefile.go) build script: |
| 105 | + |
| 106 | +```go |
| 107 | +// componentDeps defines the Worker to Worker RPC dependencies |
| 108 | +var componentDeps = map[string][]string{ |
| 109 | + "component-one": {"component-two", "component-three"}, |
| 110 | + "component-two": {"component-three"}, |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +After changing dependencies the `updateRpcStubs` command can be used to create the necessary stubs: |
| 115 | + |
| 116 | +```shell |
| 117 | +go run mage.go updateRpcStubs |
| 118 | +``` |
| 119 | + |
| 120 | +The command will create stubs for the dependency projects in the ``/target/stub`` directory and will also place the required stub _WIT_ interfaces on the dependant component's `wit/deps` directory. |
| 121 | + |
| 122 | +To actually use the dependencies in a project it also has to be manually imported in the component's world. |
| 123 | + |
| 124 | +E.g. with the above definitions the following import has to be __manually__ added to `/components/component-one/wit/component-one.wit`: |
| 125 | + |
| 126 | +```wit |
| 127 | +import pack-ns:component-two-stub; |
| 128 | +import pack-ns:component-three-stub; |
| 129 | +``` |
| 130 | + |
| 131 | +So the component definition should like similar to this: |
| 132 | + |
| 133 | +```wit |
| 134 | +package pack-ns:component-one; |
| 135 | +
|
| 136 | +// See https://component-model.bytecodealliance.org/design/wit.html for more details about the WIT syntax |
| 137 | +
|
| 138 | +interface component-one-api { |
| 139 | + add: func(value: u64); |
| 140 | + get: func() -> u64; |
| 141 | +} |
| 142 | +
|
| 143 | +world component-one { |
| 144 | + // Golem dependencies |
| 145 | + import golem:api/host@0.2.0; |
| 146 | + import golem:rpc/types@0.1.0; |
| 147 | +
|
| 148 | + // WASI dependencies |
| 149 | + import wasi:blobstore/blobstore; |
| 150 | + // . |
| 151 | + // . |
| 152 | + // . |
| 153 | + // other dependencies |
| 154 | + import wasi:sockets/instance-network@0.2.0; |
| 155 | +
|
| 156 | + // Project Component dependencies |
| 157 | + import pack-ns:component-two-stub; |
| 158 | + import pack-ns:component-three-stub; |
| 159 | +
|
| 160 | + export component-one-api; |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +After this `build` (or the `generateBinding`) command can be used to update bindings, which now should include the |
| 165 | +required functions for calling other components. |
| 166 | + |
| 167 | +Here's an example that delegates the `Add` call to another component and waits for the result: |
| 168 | + |
| 169 | +```go |
| 170 | +import ( |
| 171 | + "github.com/golemcloud/golem-go/std" |
| 172 | + |
| 173 | + "golem-go-project/components/component-one/binding" |
| 174 | +) |
| 175 | + |
| 176 | + |
| 177 | +func (i *Impl) Add(value uint64) { |
| 178 | + std.Init(std.Packages{Os: true, NetHttp: true}) |
| 179 | + |
| 180 | + componentTwo := binding.NewComponentTwoApi(binding.GolemRpc0_1_0_TypesUri{Value: "uri"}) |
| 181 | + defer componentTwo.Drop() |
| 182 | + componentTwo.BlockingAdd(value) |
| 183 | + |
| 184 | + i.counter += value |
| 185 | +} |
| 186 | +``` |
| 187 | + |
| 188 | +Once a remote call is in place, the `build` command will also compose the stub components into the caller component. |
0 commit comments