Core Concepts
The Cloud deploys Unison code without the need for configuring infrastructure, build pipelines, or physical hardware. Instead, you'll interact with the Cloud via Unison code, guided by some conventions and core concepts.
If you prefer a hands-on approach first, learn as you go with this http service deployment tutorial.
Working with the Cloud
With the Unison Cloud, infrastructure management is unified with regular Unison application code and benefits from all the language features and tooling improvements that Unison offers.
Most Unison Cloud programs will start with a top-level function that calls Cloud.main
or Cloud.main.local.serve
, depending upon whether your service should be run on Cloud infrastructure or locally on your own machine. Within the Cloud.main
block, you'll specify resources like databases and deployments or even orchestrate the interaction of multiple jobs and services at once.
main =
Cloud.main do
serviceHash1 = deploy !Environment.default service1
serviceHash2 = deploy !Environment.default service2
Cloud.submit !Environment.default do
resultService1 = Services.call serviceHash1 myServiceInput
Services.call serviceHash2 resultService1
Say you want to deploy a service which uses the Cloud's secure configuration store; that functionality, called Environment.Config
, is included in a specific set of abilities which are supported by the Cloud. You can see them in functions that send your code to the Cloud for execution, like submit
, or deploy
:
deploy : Environment
-> (a ->{Environment.Config, Exception, Storage, Http, Services, Remote, Random, Log, Scratch} b)
->{Exception, Cloud} ServiceHash a b
When working with the Cloud, remember that many of the features your service requires, like caching or logging or durable storage, might have a natively supported Unison solution represented by these abilities.
🏁 Experience the Cloud workflow with a quickstart deployment
Remote: the I/O of the Cloud
Think of the
Remote
ability as the “I/O of the Cloud.” It’s an ability which describes the runtime for a distributed system.
Remote
handles forking arbitrary computations to different locations in a cluster and also handles some of the functionality that you might expect from I/O, like getting the current time or a random value.
Put simply, Remote
describes a program being executed on other machines and the Cloud provides the compute pool for it. All computations that run in the Cloud use the Remote
ability as their runtime, and the Cloud
ability describes the infrastructure needed to deploy
or submit
such computations as services or batch jobs.
There are a number of additional abilities we provide to computations running in the Cloud besides those provided by the Remote
interface. For example:
Http
- issues http requestsStorage
- provides persistent storageScratch
- provides ephemeral storageServices
- allows you to make calls to other cloud services
Since we expect these abilities to be available anytime Remote
is available, we provide a helper function, toRemote
, that will run all of the other abilities provided by the Cloud in the Remote
runtime.
toRemote : '{Environment.Config, Exception, Storage, Http, Services, Remote, websockets.HttpWebSocket, WebSockets, Random, Log, Scratch} a ->{Remote} a
📚 Learn more about the Remote ability
Deploying cloud services
You can think of Unison services as typed, versioned, Cloud-hosted functions.
These functions can be called remotely, from one cloud node to another, reaping all of the benefits of the type-system and composability without having to worry about serialization. If a service's type signature is one that matches one of a Http (or WebSockets) service, that service can be trivially exposed to the web.
Just like Unison code is identified by a hash of its contents, every deployed service is known by its ServiceHash
. If you deploy the same code, you'll get the same hash—update your code and the Cloud will return a different hash representing the new version. Should you need a static name, you can assign a ServiceName
to your service and always refer to the latest service deployment that way.
Unison Cloud's model means that calling hosted services is as easy as a local function call. Here's what it looks like to deploy and call a native service:
main =
Cloud.main do
aService = Nat.toText
myService : ServiceHash Nat Text
myService = deploy !Environment.default aService
Cloud.submit !Environment.default do
Services.call myService 2
🤖 Deploy a Slackbot as an Http service
Storing data on the Cloud
The most common storage types you'll see are: Database
, Cell
, and OrderedTable
.
Cell
and OrderedTable
are different kinds of tables that can be used to store data in the Cloud. Cell
is way of storing a single value and OrderedTable
is a typed key-value store which provides a wider set of querying operations. A Database
contains potentially many Cells
, OrderedTables
or other tables.
The creation of these Cloud storage resources is lightweight (you don't need to worry about a complex provisioning process) and idempotent (so you can rerun your deployments and not accidentally recreate your databases).
You'll be interacting with Cloud databases via the Storage
and Transaction
abilities. Storage
is the general ability for interacting with durable storage—all updates or reads to data in the Cloud will use it. Transaction
represents the reads and/or updates to the database that should be performed atomically. They're how you can keep two OrderedTables
or Tables
in sync.
🌳 Learn more about Cloud storage with our OrderedTable deep dive video
Cloud access management
When you deploy a service, you'll need to specify the Environment
in which it will run. Environments provide a way to group Cloud resources together for basic access management. For example, only a service running in the same Environment
as a Database
can write or read from its data.
The Environment
type is closely related to Environment.Config
, which provides access to encrypted secrets at runtime. Load your secrets and configuration values into an Environment
with Environment.setValue
and then use the Environment.Config.lookup
or Environment.Config.expect
functions to use them in your service or job.
serviceLogic : Nat -> {Environment.Config, Exception} Text
serviceLogic input =
key = Environment.Config.expect "apiKey"
signRequest k = "signed_" ++ k
(Nat.toText input) ++ (signRequest key)
main : '{IO, Exception} ServiceHash Nat Text
main =
Cloud.main do
env = Environment.named "myServiceEnvironment"
(IO.getEnv "API_KEY") |> Environment.setValue env "apiKey"
deploy env serviceLogic