With the Cloud it's possible to host arbitrary typed Unison functions as services. Calls to Unison native services take place over the network without the need for HTTP requests, JSON blobs, or other serialization formats. We'll write a simple native service here to introduce the Services
API.
π What we'll cover
- Deploying a Unison native service to the Cloud
- Using the
Services
ability for calling remote functions - The API for running Cloud jobs vs. deploying Cloud services
Heads up! This exercise has three parts to it so don't worry if your first submission doesn't pass all the tests in the print out. We'll guide you through the subsequent steps. π
If you haven't already created a local workspace for the exercises in the cloud-start
project, get the stubs and exercise runner with the following commands:
.> project.create-empty cloud-start
cloud-start/main> pull @unison/cloud-start/releases/latest
πͺ Part 1: Deploy a simple native service
Implement a Unison Cloud Native service that expects a Nat
as its argument and returns a Nat
that is the square of the input. (Again, we're just focusing on the mechanics of deploying and calling a service here. π)
π Instructions
To get started run the following command in the UCM:
cloud-start/main> edit exercises.ex2_nativeService.deploy
It should look something like this to start:
exercises.ex2_nativeService.deploy : '{IO, Exception} ServiceHash Nat Nat
exercises.ex2_nativeService.deploy =
todo "Implement the Cloud deployment and service logic. The deployed service should square a Nat value"
Unison native services are deployed to the Cloud using the Cloud.deploy
function. Cloud.deploy
takes two arguments, an Environment
where the service should be deployed and a function to be run as a service.
Cloud.deploy : Environment
-> (a ->{Environment.Config, [...], Scratch} b)
-> {Exception, Cloud} ServiceHash a b
A native service is fundamentally a function from a
to b
which runs on the Cloud instead of on your local machine.
The second argument is where you should put the logic which squares (via Nat.pow
) a given number.
π When you're ready to submit your solution, save the file and update
your codebase before running the following:
cloud-start/main> run submit.ex2_nativeService
The general pattern for these service exercises will involve writing service "business logic" functionality, creating the cloud resources that your service needs to run, and then deploying it. We'll use the ServiceHash
returned by Cloud.deploy
to call your service in tests.
π₯ Part 2: Practice returning errors
Take a closer look at the service logic argument from Cloud.deploy
, it looks a good deal more complex than a -> b
because there's actually a set of abilities included in the function signature.
(a -> {Environment.Config,
Exception,
Storage,
Http,
Services,
Remote,
Random,
Log,
Scratch} b)
These abilities are available to your service when it runs on the Cloud. For example, your services might write Log
messages on errors, or issue HTTP requests with Http
.
π Instructions
Update the implementation of the service to raise an error when trying to square a number that would cause a Nat
overflow.
cloud-start/main> edit exercises.ex2_nativeService.deploy
Your service should raise an Exception
with a message containing the string "cannot be squared".
Check out ex2_nativeService.failure
for a pre-made Failure
type to use.
exercises.ex2_nativeService.failure : Failure
exercises.ex2_nativeService.failure =
Failure (typeLink Nat) "cannot be squared" (Any ())
When you're ready, save the file, update
again, and run the following:
cloud-start/main> run submit.ex2_nativeService
π Part 3: How do you call a native service?
Let's say you need to call the service you just deployed in another Unison Cloud job. You can't use an HTTP client for a native service, but you can use the Services
ability for calling typed remote functions.
The Services
ability exposes two primary functions for calling remote services: Services.call
and Services.callName
.
Services.call : ServiceHash a b -> a ->{Services, Remote} b
Services.callName : ServiceName a b -> a ->{Services, Remote} b
Service hashes identify a single service deployment while a service name refers to the latest of potentially many service deployments over time. Our deployment function just returns a hash, so we'll use Services.call
with the test value 5
as an argument.
Next we need to deploy this service call to the Cloud to evaluate it. We've seen Cloud.deployHttp
and Cloud.deploy
used to lift a function to the Cloud as a service, with inputs and outputs, but jobs like this one, which aren't expressed in terms of dynamic inputs and outputs, can be run on the Cloud with Cloud.submit
:
Cloud.submit : Environment
-> '{Services, [...], Scratch} a
-> {Exception, Cloud} a
The second argument to Cloud.submit
is a delayed computation that returns a single value.
π Instructions
Call ex2_nativeService.deploy
with the argument 5
in a new Cloud job function, ex2_nativeService.callService
.
cloud-start/main> edit exercises.ex2_nativeService.callService
When you're ready to submit your solution, save the file, update
your codebase, and run the following command:
cloud-start/main> run submit.ex2_nativeService