You can host arbitrary typed Unison functions as services on the Cloud. 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
We'll be implementing a Unison Cloud Native service that expects a Nat
as its argument and returns a Nat
that is the square of the input. (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
A native service is a function, a -> b
, which runs on the Cloud instead of on your local machine.
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
The second argument is where you should put the logic which squares (via Nat.pow
) a given number.
In your scratch file, change the ex2_nativeService.deploy
function to call Cloud.deploy
with the exercises.env()
Environment and a function that squares a Nat
value.
π 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
Solution
exercises.ex2_nativeService.deploy : '{IO, Exception} ServiceHash Nat Nat
exercises.ex2_nativeService.deploy = Cloud.main do
Cloud.deploy exercises.env() (n -> Nat.pow n 2)
The general pattern for these 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 Exception
when trying to square a number that would cause a Nat
overflow.
cloud-start/main> edit exercises.ex2_nativeService.deploy
The argument to Exception.raise
is a Failure
type that contains some type information and a message. For convenience, your service should raise the ex2_nativeService.failure
value when the input is too large.
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
Solution
exercises.ex2_nativeService.deploy : '{IO, Exception} ServiceHash Nat Nat
exercises.ex2_nativeService.deploy = Cloud.main do
threshold = sqrt (Float.fromNat maxNat) |> Float.ceiling |> Float.toNat
Cloud.deploy exercises.env() (n ->
(if Optional.exists (t -> n >= t) threshold then
Exception.raise ex2_nativeService.failure
else Nat.pow n 2)
)
π 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.
π Instructions
We'll practice calling the service you just deployed in a new Cloud job function, ex2_nativeService.callService
.
cloud-start/main> edit exercises.ex2_nativeService.callService
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.
The deployment function we just wrote returns a hash, so we can use it directly as an argument to Services.call
.
Services.call ex2_nativeService.deploy() 5
Next we need to run this service call on 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 your native service with the argument 5
in 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
Solution
exercises.ex2_nativeService.callService :
'{IO, Exception} Nat
exercises.ex2_nativeService.callService =
Cloud.main do
serviceHash = exercises.ex2_nativeService.deploy()
Cloud.submit exercises.env() do
Services.call serviceHash 5