R1 Frameworks

As usual, the entire learning process is discussed in the respective issues of this repository. About the chosen frameworks can be found in issue #70

To achieve the widest possible coverage, four microservices have been implemented using different frameworks in different languages (polyglot property of the microservices):

We are going to detail below, which frameworks we have valued, which qualities they offer us, which disadvantages and which final reasons have motivated me to use or discard them.

Rust

  1. Hyper
  2. Burner
  3. Trek-router
  4. Resty
  5. Rocket
  6. Warp
  7. Actix
Hyper
  1. πŸ‘ It’s low-level library (not framework).
  2. πŸ‘ It’s the basic library for warp and reqwest
  3. πŸ‘ It has a Asynchronous design
  4. πŸ‘ It has been tested
  5. πŸ‘ Extensive production use
  6. πŸ‘ Leading in performance
  7. πŸ‘ Low Crate Size: 142 kB
  8. πŸ‘ Open Source: MIT License
  9. πŸ‘Ž No native JSON support.
  10. πŸ‘Ž Last update: 3 months ago
use std::convert::Infallible;

use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};

async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> {
    Ok(Response::new(Body::from("Hi Cloudbanking")))
}

#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    pretty_env_logger::init();

    let handler_service = make_service_fn(|_conn| {
        async { Ok::<_, Infallible>(service_fn(hello)) }
    });

    let host_port = ([0,0,0,0], 8080).into();

    let server = Server::bind(&host_port).serve(handler_service);

    println!("Listening on http://{}", host_port);

    server.await?;

    Ok(())
}
Burner

πŸ‘ It’s a express (nodejs) based framework. πŸ‘Ž Last update: 6 months ago πŸ‘Ž No native JSON support.

extern crate burner;

use burner::{Server, Request, Response, RouterService};
let mut server = Server::new();

let handler = |req: &Request, res: &mut Response| {
	res.status(200);
};

let path = "/api";
server.get(path, Box::new(handler));


server.listen(8080);
Trek-Router
  1. πŸ‘ Supports get, post delete, patch, put, options, head, connect and trace
  2. πŸ‘ Supports any for above APIs
  3. πŸ‘ Supports scope for scope routes
  4. πŸ‘Ž High Crate Size: 965 kB
  5. πŸ‘Ž No native JSON support
  6. πŸ‘Ž Poor documentation. No examples
let addr = ([127, 0, 0, 1], 3000).into();

    let mut router = Router::<Handler>::new();

    router

        .scope("/api1", |api1| {
            api1.get("/login", api1_login)
                .post("/submit", api1_submit)
                .delete("/read", api1_read);
        })
        .scope("/api2", |api2| {
            api2.get("/login", api2_login)
                .post("/submit", api2_submit)
                .delete("/read", api2_read)
                .scope("users", |u| {
                    u.any("", users);
                });
        })
        .get("/products", products)
        .post("/product", product)
        .delete("/product", product);
Resty
  1. πŸ‘ It’s JSON based
  2. πŸ‘Ž High Crate Size: 754 kB
  3. πŸ‘Ž Last Updated 3 years ago
fn main() {
    let mut server = resty::Router::new();
    server.get("/", |_| {
        Ok("Welcome!!")
    });
    server.post("/api", |request| {
        request.json().map(|mut call: Call| {
            response
        })
    });

    let listening = server.bind("localhost:3000").unwrap();
    listening.wait()
}
Rocket

Rocket is a web framework for Rust that makes it simple to write fast secure web applications and type safe.

πŸ‘ It’s type safe πŸ‘ With Config Environments πŸ‘ With Testing Library for API πŸ‘ With Typed URIs πŸ‘ Large Community πŸ‘Ž πŸ‘Ž πŸ‘Ž Only supported by nightly version of Rust.

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use] extern crate rocket;

#[get("/hello/<name>/<age>")]
fn hello(name: String, age: u8) -> String {
    format!("Hello, {} year old named {}!", age, name)
}

fn main() {
    rocket::ignite().mount("/", routes![hello]).launch();
}
Warp

https://crates.io/crates/warp

It’s a super-easy, composable, web server framework. Build over hyper.

πŸ‘ Path routing and parameter extraction πŸ‘ Header requirements and extraction πŸ‘ Query string deserialization πŸ‘ JSON and Form bodies πŸ‘ Multipart form data πŸ‘ Static Files and Directories πŸ‘ Websockets πŸ‘ Access logging πŸ‘ Gzip and Deflate compression πŸ‘ 111kB πŸ‘ Updated every 2 months

use warp::{Filter};

mod song;
mod json;

use crate::song::model::{Store};
use crate::song::handler::{update_list_handler,get_list_handler,delete_list_handler};
use crate::json::helpers::{post_json,delete_json};


#[tokio::main]
async fn main() {
    let store = Store::new();
    let store_filter = warp::any().map(move || store.clone());

    let add_items = warp::post()
        .and(warp::path("songs"))
        .and(warp::path::end()).and(post_json())
        .and(store_filter.clone())
        .and_then(update_list_handler);

    let get_items = warp::get()
        .and(warp::path("songs"))
        .and(warp::path::end())
        .and(store_filter.clone())
        .and_then(get_list_handler);

    let delete_item = warp::delete()
        .and(warp::path("songs"))
        .and(warp::path::end())
        .and(delete_json())
        .and(store_filter.clone())
        .and_then(delete_list_handler);

    let update_item = warp::put()
        .and(warp::path("songs"))
        .and(warp::path::end())
        .and(post_json())
        .and(store_filter.clone())
        .and_then(update_list_handler);

    let routes = add_items.or(get_items).or(delete_item).or(update_item);

    warp::serve(routes)
        .run(([0, 0, 0, 0], 8000))
        .await;
}

Complete example can be found here: https://github.com/pepitoenpeligro/music_store

Actix

πŸ‘ Supports HTTP/1.x and HTTP/2 πŸ‘ Streaming and pipelining πŸ‘ Keep-alive and slow requests handling πŸ‘ Client/server WebSockets support πŸ‘ Transparent content compression/decompression (br, gzip, deflate) πŸ‘ Powerful request routing πŸ‘ Multipart streams πŸ‘ Static assets πŸ‘ SSL support using OpenSSL or Rustls πŸ‘ Middlewares (Logger, Session, CORS, etc) πŸ‘ Includes an async HTTP client πŸ‘ Supports Actix actor framework πŸ‘ Runs on stable Rust 1.42+ πŸ‘ Low Size: 134 kB πŸ‘ Last Updated: 18 days ago

use actix_web::{get, web, App, HttpServer, Responder};

#[get("/{id}/{name}/index.html")]
async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder {
    format!("Hello {}! id:{}", name, id)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(index))
        .bind("127.0.0.1:8080")?
        .run()
        .await
}

Without a doubt, the most attractive frameworks are:

  1. Rocket
  2. Warp
  3. Actix

Because they are fast, robust, reliable, have native json support, low weight, with a future (http 2 support), with large developer communities behind them and with good documentation.

Of all of them we discarded Rocket as it is not yet on the stable Rust channel (it is only on the nightly channel).

Nim

Nawak
  1. πŸ‘ Express-style
  2. Poor documentation
  3. πŸ‘Ž No native JSON support
import nawak_mongrel, strutils

get "/":
    return response("Hi Cloudbanking!")

get "/api/@userid/?":
    return response("Welcomeback $1!" % url_params.username)

run()
Jester
  1. πŸ‘ Supports HTTP/1.x and HTTP/2
  2. πŸ‘ Keep-alive and slow requests handling
  3. πŸ‘ Client/server WebSockets support
  4. πŸ‘ Transparent content compression/decompression (br, gzip, deflate)
  5. πŸ‘ Powerful request routing
  6. πŸ‘ Includes an async HTTP client
  7. πŸ‘ Well documented
import json
import dotenv, os
import asyncdispatch, jester, strutils
import logging
import etcd_client
include ./jester_cards/controller

let  logger = newConsoleLogger()
let etcdClientObj = new_etcd_client(failover=false)

proc messageJson(msg: string): string =
  """{"msg": $#}""" % [msg]


let controller = CardController(bankCards: TableRef[string, Card]() )

router cloudbankingCardsRoutes:

  # Get a bank card by id
  get "/cards/@id":
    info("[GET] /cards/{id}")
    let paramReceived = @"id"
    let recoveredCard = controller.getBankCard(paramReceived)
    let idField = "" & recoveredCard.id;
    if idField == "-":
      error("Card not founded")
      resp(Http200, messageJson("Card not founded" & $(recoveredCard)), contentType="application/json")

    info("Card founded")
    resp(Http404, messageJson("Card founded: " & $(recoveredCard)), contentType="application/json")


proc main() =
  var port:string = ""
  try:
    port = $etcdClientObj.get("PORT")
  except:
    echo("Could not found ETCD server")
  finally:
    echo("We are reading values from .env")
    let env = initDotEnv()
    env.load()

    port = os.getEnv("PORT")
    let settings = newSettings(port=port.parseInt().Port)
    var jester = initJester(cloudbankingCardsRoutes, settings=settings)
    jester.serve()

when isMainModule:
  main()

It is quite clear, that our favourite is: Jester

Kotlin

Spring
  1. πŸ‘ Huge community
  2. πŸ‘ Same structure as Java Spring
  3. πŸ‘ Client/server WebSockets support
  4. πŸ‘ Powerful request routing
  5. πŸ‘ Includes an async HTTP client
  6. πŸ‘ Well documented
@RestController
class CloudBankingController {

    @GetMapping("/api/users")
    fun welcome() name: String) =
           Response.ok().entity("hi").build()

}
Vert.x
  1. πŸ‘ Client/server WebSockets support
  2. πŸ‘ Powerful request routing
  3. πŸ‘Ž Poor community
import io.vertx.core.AbstractVerticle

class Server : AbstractVerticle() {
  override fun start() {
    vertx.createHttpServer().requestHandler { req ->
      req.response()
        .putHeader("content-type", "text/plain")
        .end("Hello cloudbanking!")
    }.listen(8080)
  }
}
Javalin
  1. πŸ‘ Jetty based. Lightweight
  2. πŸ‘ Simple and Interoperable
  3. πŸ‘Ž Poor community
  4. πŸ‘Ž Poor documentation
import io.javalin.Javalin

fun main(args: Array<String>) {
    val app = Javalin.create().start(8080)
    app.get("/") { ctx -> ctx.result("Hi cloudbanking") }
}
Quarkus
  1. πŸ‘ Huge community
  2. πŸ‘ Based in GraalVM (low ram-figerprint)
  3. πŸ‘ Client/server WebSockets support
  4. πŸ‘ Powerful request routing
  5. πŸ‘ Includes an async HTTP client
  6. πŸ‘ Well documented

  7. πŸ‘ Container-first
  8. πŸ‘ Quarkus builds applications to consume 1/10th the memory when compared to traditional Java, and has a faster startup time (as much as 300 times faster
  9. πŸ‘ Quarkus applications can start in under .0015 seconds, making it possible to use the existing Spring and Java API knowledge with FaaS functions. (Azure, AWS Lambda).
  10. πŸ‘ Support for reactive and imperative model.
  11. πŸ‘ Early detection of dependency injection errors. Quarkus catches dependency injection errors during compilation instead of at runtime.
  12. πŸ‘ Use best of breed frameworks and standards together. Quarkus supports Spring API compatibility, Eclipse Vert.x, MicroProfile (JAX-RS, CDI, etc), reactive streams and messaging, and more in the same application
package com.pepe.rest.resteasyjackson

import java.util.*
import javax.ws.rs.*
import javax.ws.rs.core.MediaType

@Path("/resteasy-jackson/quarks")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
class JacksonResource {

    private val quarks = Collections.newSetFromMap(Collections.synchronizedMap(LinkedHashMap<Quark, Boolean>()))

    init {
        quarks.add(Quark("Up", "The up quark or u quark (symbol: u) is the lightest of all quarks, a type of elementary particle, and a major constituent of matter."))
        quarks.add(Quark("Strange", "The strange quark or s quark (from its symbol, s) is the third lightest of all quarks, a type of elementary particle."))
        quarks.add(Quark("Charm", "The charm quark, charmed quark or c quark (from its symbol, c) is the third most massive of all quarks, a type of elementary particle."))
        quarks.add(Quark("???", null))
    }

    @GET
    fun list(): Set<Quark> {
        return quarks
    }

    @POST
    fun add(quark: Quark): Set<Quark> {
        quarks.add(quark)
        return quarks
    }

    @DELETE
    fun delete(quark: Quark): Set<Quark> {
        quarks.removeIf { existingQuark: Quark -> existingQuark.name!!.contentEquals(quark.name!!) }
        return quarks
    }

    class Quark {
        var name: String? = null
        var description: String? = null

        constructor() {}
        constructor(name: String?, description: String?) {
            this.name = name
            this.description = description
        }
    }
}

Any option could be interesting, but of all of them, the most promising is Quarkus. Let’s give it a try!

References

  1. Choosing a Rust Web Framework in 2020
  2. Hyper
  3. Burner
  4. Trek Router
  5. Resty
  6. Rocket
  7. Warp
  8. Actix
  9. Nawak
  10. Jester
  11. Spring
  12. Quarkus RedHat
  13. Quarkus vs Spring