Skip to main content

Understanding Capability Driven Development

One of the most important concepts when developing applications with Cosmonic is the capability.

info

A capability is an abstraction or representation of a non-functional requirement; some functionality required by your application that is not considered part of the core business logic. Capabilities both describe functionality (for example, an HTTP Server receives requests and returns responses via TCP) and are used to verifiably allow application components to access resources. Capabilities are deny-by-default and must be explicitly granted for each actor that wishes to use that functionality. In wasmCloud (and thus Cosmonic), capabilities are satisfied by providers.

Many software products that you use today operate with a capability-based model for security. Browsers ask for the capability to access your location, mobile phones ask for the capability to connect to Bluetooth devices, and your favorite SaaS provider asked for the capability to read your email address. At Cosmonic, we believe software development can be more secure and more flexible by embracing this model directly in our applications. Security is the first major benefit of capability-based development.

In the Quickstart, our "Hello, World" application made use of a single capability: HTTP Server. This allowed our app to receive HTTP requests and return HTTP responses, without having to worry about the mechanics of building and running web servers. However, if you attempted to add code to access a database, files, or make network requests, you would see an error like the following:

{"error":"missing capability claim for database|filesystem|httpclient"}

Adding Another Capability: KeyValue

Let's take a look at a slightly different actor, the KVCounter. This actor uses the HTTP server capability just like the "Hello World" actor from the previous section, but instead of returning a static string, it returns a count equal to the number of times that it has been invoked. We have a tutorial for this actor as well:

cosmo new actor --git https://github.com/cosmonic/awesome-cosmonic --subfolder kvcounter/rust kvcounter

Again, you'll be presented with a publicly-accessible endpoint that you can reach with an HTTP request. You'll get back a response like {"counter": 1234}. The count will increment each time you send the actor a new request.

Taking a look at this code, we can see what it took to add this capability:

use serde_json::json;
use wasmbus_rpc::actor::prelude::*;
use wasmcloud_interface_httpserver::{HttpRequest, HttpResponse, HttpServer, HttpServerReceiver};
use wasmcloud_interface_keyvalue::{IncrementRequest, KeyValue, KeyValueSender};

#[derive(Debug, Default, Actor, HealthResponder)]
#[services(Actor, HttpServer)]
struct KvcounterActor {}

/// Implementation of HttpServer trait methods
#[async_trait]
impl HttpServer for KvcounterActor {
    async fn handle_request(&self, ctx: &Context, req: &HttpRequest) -> RpcResult<HttpResponse> {
        // Format key with a friendly separator
        let key = format!("counter:{}", req.path.replace('/', ":"));

        let (body, status_code) = match increment_counter(ctx, key).await {
            Ok(v) => (json!({ "counter": v }).to_string(), 200),
            // Ensure we properly return database errors as server errors
            Err(e) => (json!({ "error": e.to_string() }).to_string(), 500),
        };

        HttpResponse::json(body, status_code)
    }
}

/// Increment the key in the keyvalue store by 1, returning the new value
async fn increment_counter(ctx: &Context, key: String) -> RpcResult<i32> {
    Ok(KeyValueSender::new()
        .increment(ctx, &IncrementRequest { key, value: 1 })
        .await?)
}

This actor uses the HTTP server capability to receive requests and the keyvalue capability to access a keyvalue store. The increment_counter function initiates a connection to a keyvalue store and increments the value at the requested key by 1. The specific keyvalue store is chosen at runtime, and it doesn't need to be specified here, nor do you need to worry about how to connect or authenticate to that data store. Try changing that number to increment by more than 1, and run cosmo launch to deploy the actor again. Now when you send a request to your endpoint, your actor will increment the counter by the new value.

Remember that capabilities are deny-by-default, and this actor had to explicitly declare that it wanted to make use of the HTTP server and keyvalue capabilities. If you open up wasmcloud.toml, you can see this specified under the actor section.

name = "kvcounter"
language = "rust"
type = "actor"
version = "0.1.0"

[actor]
wasm_target = "wasm32-wasi"
claims = ["wasmcloud:httpserver", "wasmcloud:keyvalue"]

Feel free to continue modifying the KVCounter actor and deploying it with cosmo launch. You can take advantage of any of the keyvalue operations, like Set, Add, Delete, etc., to add even more functionality to this tutorial app.

What's Next?

Now that we've gone through a couple tutorials, take a look at the next page on creating applications to understand where you can go from here to turn your ideas into reality.