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
Hyper
- π Itβs low-level library (not framework).
- π Itβs the basic library for warp and reqwest
- π It has a Asynchronous design
- π It has been tested
- π Extensive production use
- π Leading in performance
- π Low Crate Size: 142 kB
- π Open Source: MIT License
- π No native JSON support.
- π 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
- π Supports get, post delete, patch, put, options, head, connect and trace
- π Supports any for above APIs
- π Supports scope for scope routes
- π High Crate Size: 965 kB
- π No native JSON support
- π 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
- π Itβs JSON based
- π High Crate Size: 754 kB
- π 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:
- Rocket
- Warp
- 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
- π Express-style
- Poor documentation
- π 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
- π Supports HTTP/1.x and HTTP/2
- π Keep-alive and slow requests handling
- π Client/server WebSockets support
- π Transparent content compression/decompression (br, gzip, deflate)
- π Powerful request routing
- π Includes an async HTTP client
- π 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
- π Huge community
- π Same structure as Java Spring
- π Client/server WebSockets support
- π Powerful request routing
- π Includes an async HTTP client
- π Well documented
@RestController
class CloudBankingController {
@GetMapping("/api/users")
fun welcome() name: String) =
Response.ok().entity("hi").build()
}
Vert.x
- π Client/server WebSockets support
- π Powerful request routing
- π 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
- π Jetty based. Lightweight
- π Simple and Interoperable
- π Poor community
- π 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
- π Huge community
- π Based in GraalVM (low ram-figerprint)
- π Client/server WebSockets support
- π Powerful request routing
- π Includes an async HTTP client
-
π Well documented
- π Container-first
- π 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
- π 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).
- π Support for reactive and imperative model.
- π Early detection of dependency injection errors. Quarkus catches dependency injection errors during compilation instead of at runtime.
- π 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!