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.Config
values like API keys or feature flags may vary between environments Log
outputs might be more verbose for debuggingCell
table 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.