Local development with Unison Cloud
You can develop and test your application locally before ever deploying it on Cloud infrastructure. Changing the deployment environment is often as simple as swapping Cloud.main in place of one of its Cloud.main.local handler variants, while the code which describes your infrastructure remains exactly the same.
A service with environment-dependent resources
Here's a Unison native service that splits the given text argument into a list and records the total words seen over time in a table. It involves a few resources that are frequently different per environment:
- Secure
Environment.Configvalues like API keys or feature flags may vary between environments Logoutputs might be more verbose for debuggingCelltable data would only be persistently stored in the Cloud
wordCountService : Cell Nat -> Text -> {Remote, Log, Environment.Config} Nat
wordCountService totalCountTable text =
size = List.size (Text.split ?\s text)
isDebug = Optional.exists (value -> value === "true") (Environment.Config.lookup "debugMode")
logger = if isDebug then Log.debug else Log.info
logger "adding-message" [("size", Nat.toText size)]
Cell.modify totalCountTable (cases current ->
total = current + size
(total, total)
)
Describe your service resources
You can defer the decision for which infrastructure to run your service on, even as you describe the kinds of resources that your service requires. This service needs an Environment to group together the service, database, and secure config variables; a Database to house the table for saving the total value; and a call to the deploy function to host the service.
cloud.deploy.impl : Text -> {Exception, Cloud, IO} ServiceHash Text Nat
cloud.deploy.impl envVar =
env = Environment.named "myServiceEnvironment"
(IO.getEnv envVar) |> Environment.setValue env "debugMode"
database = Database.named "myServiceDatabase"
Database.assign database env
totalCountTable = Cell.named database "totalCount" 0
deploy env (wordCountService totalCountTable)
The function keeps the {Cloud} ability around in the signature because the choice of whether to run the service locally or on the Cloud is still up in the air.
Local and prod deployments
We've pushed the environment-dependent elements of the deployment process to the edge of our runnable programs. The Unison Cloud infrastructure deployment of this service wraps the implementation with Cloud.main, while a local deployment will Cloud.main.local or Cloud.main.local.serve.
cloud.deploy.prod : '{IO, Exception} ServiceHash Text Nat
cloud.deploy.prod = Cloud.main do (deploy.impl "CONFIG_KEY")
Interactive local testing of HTTP or WebSockets services is best handled by the Cloud.main.local.serve handler, since it will wait for a user to enter a newline to stop the service. Our local service test can just use Cloud.main.local since we don't want to interactively call our service once it's hosted. Instead, inside the same Cloud.main.local scope, let's issue a few test requests using the ServiceHash returned from the deployment function.
cloud.deploy.local : '{IO, Exception} Nat
cloud.deploy.local =
Cloud.main.local do
serviceHash = deploy.impl "LOCAL_CONFIG_KEY"
testEnv = Environment.named "testEnvironment"
Cloud.submit testEnv do
_ = Services.call serviceHash "Hello, world!"
Services.call serviceHash "Wow, another request."
Note that the Environment in which you run your requests to the service does not need to be the same as the one you created for running the service.
Deploy with run in the UCM
There's no executable files to upload or lengthy build pipelines in the way of deploying to prod; just issue the UCM run command for the deployments to kick things off in both environments.
myProject/main> run cloud.deploy.prod
myProject/main> run cloud.deploy.local
For more tips and caveats on local development, check out our local development FAQs.