Revolutionize Cloud Programming with Wing: A New Cloud-Oriented Language
Programming cloud applications can be a real pain in the neck, especially when you’re trying to deal with low-level details of cloud services and testing and debugging applications locally. That’s where a new programming language called Wing comes to help.
What is Wing?
The official documentation comes with a definition of a new paradigm called cloud-oriented language:
allows developers to build distributed systems that fully leverage the power of the cloud without having to worry about the underlying infrastructure.
The Wing Programming Language is trying to solve the problem/need of the Cloud Programmers to be capable of writing:
- Code itself with all the application logic
- Infrastructure code
- Tests for the infrastructure itself
All this could be now achieved using only one programming language and the cloud simulator application called Wing Console.
You may ask — do I need to learn another programming language? Well, yes, Wing actually has the explanation of why they are doing it, which may convince you or may not.
Concepts
As far as I understood from the documentation, the guys standing behind the Wing (specifically Elad Ben-Israel) are guys who were responsible for creating another great IaC tool — AWS CDK, so many concepts could be similar e.g:
Resource
Fundamental unit of abstraction for a cloud-based service or infrastructure component, logical representation of a real-world object, such as an Amazon S3 bucket, an AWS Lambda function, or a Google Cloud Pub/Sub topic.
The resource has two fundamental elements — preflight ( executes during compilation) and inflight (executes after the system has been deployed) code.
Preflight initializers are basically constructors, and are used to validate the syntax of resource definition and ensure necessary services are available e.g:
// example from https://docs.winglang.io/contributors/rfcs/2022-05-28-winglang-reqs#preflight-warnings-and-errors
struct MyResourceProps {
max_len: number;
name: string;
}
class MyResource {
// this is a preflight initializer validating properties and throwing errors
init(props: MyResourceProps) {
if props.name.length > props.max_len {
throw("`name` is too long (${props.name.length} > ${props.max_len})")
}
if props.name.length == props.max_len {
print("Be careful, your name is at the maximum length")
}
}
}
Inflight code is more complex and thus requires a separate section.
Inflight code
As one of the Wings RFCs says:
The ability to express runtime logic as an integral part of the desired-state definition, and naturally interact between the two execution domains is a unique and fundamental capability of the wing language.
So Wing is introducing a language primitive called “inflight functions” (in short “inflights”) which represents an isolated computation unit that can be packaged and executed later on various compute platforms.
Inflights are used to naturally communicate with the resources defined outside of them. The simplest example is the code of the lambda function, which uses the resources defined outside of the code.
// example from https://docs.winglang.io/contributors/rfcs/2022-05-28-winglang-reqs#inflight-functions-inflights
let bucket = new cloud.Bucket();
let topic = new cloud.Topic();
let message_count = 4;
let fn = cloud.Function(inflight (e: cloud.BucketUploadEvent) => {
for i in 0..message_count {
topic.publish("new file uploaded to our bucket: ${e.name} - ${i}");
}
});
bucket.on_upload(fn);
As you can see, to define the simple logic of publishing messages to the topic we don’t really need to define any environment variables with the names of the topic, import boto3, etc. Wing inflights do the hard work.
What I can do with it?
Let’s create a sample application with two buckets, function and queue. Buckets would be of “low” and “high” priority and depending on the payload we will decide if we put the file in a “low” or “high” priority bucket.
The Wing code for this simple application will look like this:
bring cloud;
let low_priority_bucket = new cloud.Bucket() as "low_priority_bucket";
let high_priority_bucket = new cloud.Bucket() as "high_priority_bucket";
let queue = new cloud.Queue(timeout: 10s);
enum Priority {
Low,
High
}
struct Message {
body: str;
priority: Priority;
id: str;
}
let handler = inflight (body: str /* string arg */): str => {
let json_body = Json.parse(body);
let var priority = Priority.High;
if json_body.get("priority") == "Low" {
priority = Priority.Low;
}
let msg = Message {
body: str.fromJson(json_body.get("body")),
priority: priority,
id: str.fromJson(json_body.get("id"))
};
log("${json_body.get("body")}");
if msg.priority == Priority.Low {
low_priority_bucket.put("${msg.id}.txt", msg.body);
} else {
high_priority_bucket.put("${msg.id}.txt", msg.body);
}
};
queue.addConsumer(handler);
So what we’ve done here is that we have created the infrastructure for all of our resources and used the references in the application code itself. Mixing application and infrastructure code here allowed us not to bother setting additional variables, and the simplicity of Wing — to write a simple script without knowing the low-level details, such as the API client for a particular Cloud or concepts such as permissions for lambda or queue mapping.
Let’s see how it works, by running a command which allows running the program in Wing Console.
wing it hello.w
The console shows us all the resources defined, as well as connections between them and properties of the specific resource.
The best thing about it is that we can actually test the whole flow right in the Console. Here I push the JSON message right into the queue.
And see the results, as the file appears in the right bucket.
We could also test the flow programmatically, using “@winglang/sdk” library.
➜ test-wing node --experimental-repl-await
ba
> const sdk = require('@winglang/sdk')
> await simulator.start() // start the simulator
undefined
> const queue = simulator.getResource('root/Default/cloud.Queue') // retrieve the queue resource
undefined
> await queue.push(JSON.stringify({ id: '1', body: 'test', priority: 'Low' }))
undefined
> simulator.getResource('root/Default/low_priority_bucket')
Bucket {
objectKeys: Set(1) { '1.txt' },
fileDir: '/var/folders/pm/rl02w7t95rs196phpwhdm4f00000gn/T/wing-sim-EYxZFg',
context: {
simdir: './target/hello.wsim',
resourcePath: 'root/Default/low_priority_bucket',
findInstance: [Function: findInstance],
addTrace: [Function: addTrace],
withTrace: [AsyncFunction: withTrace],
listTraces: [Function: listTraces]
},
initialObjects: {},
_public: false,
topicHandlers: {}
}
> const b = simulator.getResource('root/Default/low_priority_bucket')
undefined
> await b.list()
[ '1.txt' ]
Deploy
Now that we have our program, we are ready to deploy. In order to deploy our program we need to compile it. Wing supports many targets for the compilation, as CLI Docs say:
-t, --target <target> Target platform (choices: "tf-aws", "tf-azure", "tf-gcp", "sim", "awscdk", default: "sim")
Let’s compile to Terraform by running
wing compile -t tf-aws hello.w
After Wing compiles our program, we get a bunch of files in target/hello.tfaws
, which includes the code for our function:
.
├── assets
│ └── root_cloudQueueAddConsumere46e5cb7_Asset_C378D444
│ └── ECC853FE9B3C8A36DCD810B835AF48CD
│ └── archive.zip
├── main.tf.json
└── tree.json
The resources in main.tf.json:
And the graph looks like this:
So as you see Wing created all the necessary resources for us. We didn’t have to create things like IAM Policies or Lambda Event Source Mapping.
Supported Resources
First of all, Wing is still in its early development stage and of course, not all of the resources are supported (see API reference), as well as not all of the Wing SDK methods are supported in every compilation target (see compatibility matrix).
The supported resources (as of June 2023) include:
- Api
- Bucket — cloud object store
- Counter
- Function
- Queue
- Resource
- Schedule
- Secret
- Service
- Table
- Test
- TestRunner
- Topic
- Website
Apart from well-known services, like Bucket (which could be mapped to S3 Bucket in AWS or Blob Storage in Azure), there are some resources that are new — like Counter or Test (representing unit-test in Wing), which make it easier to develop cloud applications.
Simple CRUD
With all the knowledge gained, let’s try creating a simple CRUD application using Wing.
Let’s start by creating infrastructure, for our CRUD app we need a Table and API.
bring cloud;
let api = new cloud.Api() as "users_api";
let table = new cloud.Table(
name: "users",
primaryKey: "id",
columns: {
"id": cloud.ColumnType.STRING,
"email": cloud.ColumnType.STRING,
"name": cloud.ColumnType.STRING
}
) as "users_table";
Now let’s create the structure of the user:
struct User {
id: str;
email: str;
name: str;
}
We need to implement CRUD methods, such as GET, POST and DELETE. We can do it using api resource methods, like this:
api.post("/users", inflight (req: cloud.ApiRequest): cloud.ApiResponse => {
let usr = User {
id: Utils.makeId(),
email: str.fromJson(req.body?.get("email")),
name: str.fromJson(req.body?.get("name")),
};
table.insert(usr.id, usr);
return cloud.ApiResponse {
status: 201,
body: usr.id
};
});
We can also define additional methods (cause we wanna use DRY):
let userExists = inflight (id: str): bool => {
let usr = table.get(id);
if (!Utils.isUndefined(usr)){
return true;
}
else {
return false;
}
};
And you noticed that we are using some kind of Utils module. Right, Wing allows us to import the Javascript code and use it inside the Wing program. We are using it right now to cover all the not-implemented Wing functionality.
class Utils {
init() {}
extern "./helpers.js" static inflight makeId(): str;
extern "./helpers.js" static inflight getIdFromPath(path : str): str;
extern "./helpers.js" static inflight isUndefined(t : Json): bool;
}
After defining everything, we can launch our program in Wing Console:
By clicking on Api resource we get a window where we can easily test our API. There is also an URL on localhost available, so we could just test it using any other tool (e.g. curl).
Let’s test this out:
$ curl -X POST -H "Content-Type: application/json" -d '{"email":"test@example.com", "name":"artem"}' http://127.0.0.1:64180/users
"fe21527a-80b3-4080-9b9c-4aff6f7ddb2a"%
$ curl -X GET http://127.0.0.1:64180/users
["fe21527a-80b3-4080-9b9c-4aff6f7ddb2a"]%
$ curl -X GET http://127.0.0.1:64180/users/fe21527a-80b3-4080-9b9c-4aff6f7ddb2a
{"id":"fe21527a-80b3-4080-9b9c-4aff6f7ddb2a","email":"test@example.com","name":"artem"}%
$ curl -X DELETE http://127.0.0.1:64180/users/fe21527a-80b3-4080-9b9c-4aff6f7ddb2a
$ curl -X GET http://127.0.0.1:64180/users/fe21527a-80b3-4080-9b9c-4aff6f7ddb2a
"User not found"%
Seems like it is working perfectly fine! Let’s try deploying it to AWS. I have chosen tf-aws target and Wing created a total of 29 resources.
After deploying it to AWS I got my API!
And seems like it’s also working, though it’s not returning the 404 status code when I try getting or deleting a non-existing user 🤔. Something to work on here…
$ curl -X POST -H "Content-Type: application/json" -d '{"email":"test@example.com", "name":"artem"}' https://wfqwvl78s5.execute-api.eu-central-1.amazonaws.com/prod/users
"fc4f4b8d-d7a3-4235-bb6f-bbf351308904"%
$ curl -X GET https://wfqwvl78s5.execute-api.eu-central-1.amazonaws.com/prod/users
["fc4f4b8d-d7a3-4235-bb6f-bbf351308904"]%
$curl -X GET https://wfqwvl78s5.execute-api.eu-central-1.amazonaws.com/prod/users/fc4f4b8d-d7a3-4235-bb6f-bbf351308904
{"email":"test@example.com","id":"fc4f4b8d-d7a3-4235-bb6f-bbf351308904","name":"artem"}%
$ curl -X DELETE https://wfqwvl78s5.execute-api.eu-central-1.amazonaws.com/prod/users/fc4f4b8d-d7a3-4235-bb6f-bbf351308904
$ curl -X GET https://wfqwvl78s5.execute-api.eu-central-1.amazonaws.com/prod/users/fc4f4b8d-d7a3-4235-bb6f-bbf351308904
{}%
$ curl -X GET https://wfqwvl78s5.execute-api.eu-central-1.amazonaws.com/prod/users
[]%
$ curl -X DELETE https://wfqwvl78s5.execute-api.eu-central-1.amazonaws.com/prod/users/fc4f4b8d-d7a3-4235-bb6f-bbf351308904
What’s next?
Although Wing provides us with simple examples, it is currently in the early stages of development, making it challenging to envision its use in real-world production applications. However, Wing introduces a compelling concept of a “cloud-oriented” language that expands our thinking. For many Cloud Developers, understanding every detail of the underlying infrastructure is unnecessary for building applications. This language offers a promising solution, especially considering the ability to deploy these applications on any cloud platform.
I highly recommend subscribing to Wing’s slack and also giving a star to their Github. I believe in the future of this project.
Sources
Github with examples used in this article
Links
If you want to talk about Cloud, Web3, or AI — contact me on LinkedIn.