Header Ads Widget

Ticker

6/recent/ticker-posts

Óxido vs. Haskell: ¿Qué lenguaje es mejor para el diseño de API?

 


Cuando se trata de diseñar, construir y mantener una API , no es inmediatamente obvio qué herramientas de desarrollo y lenguajes de programación debe usar. Dado que las API son esencialmente el sistema nervioso de las aplicaciones móviles, tiene sentido que haya una gran cantidad de recursos para programadores y desarrolladores. .

Saber qué herramientas de desarrollo utilizar para crear su propia API depende de su nivel de experiencia técnica. Algunos entornos de desarrollo ofrecen un entorno básico de programación de línea de comandos. Otros funcionan más como una aplicación, con GUI sofisticadas y muchas campanas y silbidos, con depuradores de código y abundantes bibliotecas integradas.

Hoy vamos a comparar dos lenguajes de programación populares que quizás no se le ocurran de inmediato cuando piense en diseñar una API. Haremos una comparación en paralelo de Haskell frente a Rust para determinar qué lenguaje es mejor para el diseño de API.

Presentando Haskell

Haskell es uno de los más potentes y fiables lenguajes de programación funcional que existen. El énfasis de Haskell en la programación de alto nivel permite a los desarrolladores concentrarse en obtener resultados en lugar de atascarse en interminables minucias.

La programación en Haskell también permite la creación rápida de prototipos, gracias a su excelente compilador, que permite que las aplicaciones y el software lleguen al mercado mucho más rápido que otros lenguajes de desarrollo. Esto hace que Haskell sea una buena opción para nuevas empresas más pequeñas o para aquellos que buscan lanzar su primera aplicación.

Conoce a Rust

Mozilla se dedica a desarrollar herramientas y hacer evolucionar la web utilizando estándares abiertos, comenzando con su navegador de Internet insignia, Firefox. Todos los navegadores de Internet del mercado, incluido Firefox, están escritos en C ++. Firefox cuenta con 12,900,992 líneas de código. Google Chrome tiene 4.490.488. Si bien esto acelera los programas, algunos argumentan que son más inseguros. No se comprueba la validez de las manipulaciones de memoria de C y C ++. Si algo sale mal, puede provocar un bloqueo del programa, pérdidas de memoria, desbordamientos de búfer, fallas de segmentación y punteros nulos.

Rust escribe de forma predeterminada "código seguro", asignando memoria a los objetos y no desasignándola hasta que el proceso se haya completado. Esto elimina los 'punteros colgantes' que representan un riesgo de seguridad  y hacen que el código sea mucho menos eficiente.

La seguridad y la eficiencia son algunas de las razones por las que Rust es uno de los lenguajes de programación más queridos entre desarrolladores y programadores, como se muestra en esta encuesta de Stack Overflow. .

Haskell vs. Oxido

Según esta tabla de StackShare , Rust y Haskell tienen varias similitudes y algunas diferencias notables. Para empezar, Rust es un poco más popular, con 416 desarrolladores que utilizan Rust en lugar de 347 que desarrollan con Haskell.

Debido a su popularidad, hay mucho más contenido de Rust en Internet que Haskell. Hay más de 23.000 referencias a Rust en Hacker News, mientras que Haskell solo tiene 763. Haskell tiene más de tres veces más contenido en Stack Overflow que Rust, debido a su longevidad.

Las ventajas de Rust, según los programadores de Stack Overflow, incluyen:

  • Seguridad de la memoria garantizada (75 votos)
  • Velocidad (64 votos)
  • Duración mínima (46 votos)
  • Código abierto (46 votos)
  • Coincidencia de patrones (38 votos)
  • Inferencia de tipo (36 votos)
  • Tipos de datos algebraicos (34 votos)
  • Concurrente (34 votos)
  • Fijaciones Efficient C (28 votos)
  • Práctico (28 votos)

Las ventajas de Haskell, por otro lado, incluyen:

  • Programación puramente funcional (66 votos)
  • Mecanografiado estáticamente (53 votos)
  • Tipo seguro (44 votos)
  • Gran comunidad (29 votos)
  • Código abierto (29 votos)
  • Composable (28 votos)
  • Una función de la concurrencia (24 votos)
  • Paralelismo incorporado (22 votos)
  • Referencialmente transparente (17 votos)
  • Genéricos (15 votos)

Los contras de usar Rust incluyen:

  • Curva de aprendizaje de propiedad
  • Sombreado variable
  • Difícil de aprender

Los contras de usar Haskell:

  • No hay buen ABI
  • Rendimiento impredecible
  • Mala documentación para bibliotecas
  • Embalaje deficiente para aplicaciones
  • Mensajes de error confusos
  • Compilación lenta
  • Sin mejores prácticas
  • Demasiadas distracciones en las extensiones de idioma

Programas que se integran con Rust:

  • Remacs
  • Centinela
  • Hierro
  • Hoja
  • Lápiz
  • Ruru
  • Zapador
  • Hélice
  • Tokamak
  • Cohete
  • Freno de aire
  • Marco de tejo
  • Dependabot
  • Torre Web

Programas que interactúan con Haskell:

  • Eta
  • Yesod
  • Barra antivuelco
  • Miso
  • compañero

Finalmente, eche un vistazo a este gráfico de Google Trends de interés a lo largo del tiempo en Rust frente a Haskell:

Como puede ver, mientras que ambos lenguajes de programación tienen sus altibajos, Rust es exponencialmente más popular que Haskell. Esto significa que hay más recursos disponibles para Rust, lo que lo convierte en una mejor opción para crear API si desea algo que funcione directamente desde el principio.

Sin embargo, Haskell es experto en la creación rápida de prototipos y la creación de marcos. El código que escribe en Haskell puede ser parte del producto terminado, como un beneficio adicional.

Haskell vs.Rust: ¿Cuál es mejor para diseñar API?

Ahora que sabemos un poco más sobre Haskell vs.Rust, profundicemos en el meollo del asunto.

¿Qué lenguaje de programación es mejor para el diseño de API ? Eso dependerá de lo que intente hacer y de lo cómodo que se sienta con la programación.

Echemos un vistazo a algunas instancias específicas, para ayudarlo a descubrir qué enfoque es el adecuado para el diseño de su API.

Diseñar una API RESTful con Haskell

Diseñar una API con un lenguaje de programación funcional puede parecer complicado. Sin embargo, no tiene por qué serlo, ya que existen herramientas de terceros para facilitar el desarrollo web con Haskell. Por ejemplo, el marco SNAP actúa como un traductor, lo que permite que Haskell se comunique con la web de manera fácil y sin problemas.

Introducción a Haskell y SNAP

Comenzará cargando algunos comandos en Haskell. También puede clonar estos comandos directamente desde ThoughtBot.com .

    git clone git@github.com:thoughtbot/snap-api-tutorial.git
    cd snap-api-tutorial
    git checkout baseline
    cabal sandbox init
    cabal install snap
    cabal install --dependencies-only

Creación de un Snaplet de API

Los Snaplets son piezas componibles de una aplicación SNAP. Las aplicaciones SNAP se crean anidando applets . Mire el archivo application.hs y notará que el inicializador de la aplicación 'app' está compuesto por funciones makeSnaplet .

Comenzaremos haciendo un snaplet llamado 'API'. Este snaplet es responsable de crear el espacio de nombres de nivel superior / api. Va a cargar algunas extensiones de idioma, importar los módulos necesarios y definir el tipo de datos 'API'. Luego, definirás el inicializador del snaplet.

    -- new file: src/api/Core.hs
    {-# LANGUAGE OverloadedStrings #-}
    module Api.Core where
    import Snap.Snaplet
    data Api = Api
    apiInit :: SnapletInit b Api
    apiInit = makeSnaplet "api" "Core Api" Nothing $ return Api

Observe la 'b' en la línea 'apilnit :: Snapletinit b Api' en lugar del comando 'app'. Esto significa que este snaplet se puede cargar en cualquier operación básica, no solo en la aplicación. Esta es la base de la componibilidad de SNAP.

Ahora le dirá al tipo de datos 'Aplicación' que espere un snaplet de API.

    -- src/Application.hs
    import Api.Core (Api(Api))
    data App = App { _api :: Snaplet Api }

Luego, anidará el snaplet de Api dentro del snaplet de la aplicación, usando nestSnaplet:

    nestSnaplet :: ByteString -> Lens v (Snaplet v1) -> SnapletInit b v1 -> Initializer b v         (Snaplet v1)

El primer comando define la URL base raíz para la ruta del snaplet, / api en este caso. El segundo argumento es un Lens, que identifica el snaplet, generado por la función makeLenses en src / application.hs. El argumento final es el inicializador de snaplet apiInit que hemos definido previamente.

-- src/Site.hs
import Api.Core (Api(Api), apiInit) app :: SnapletInit App App app = makeSnaplet "app" "An snaplet example application." Nothing $ do api <- nestSnaplet "api" api apiInit addRoutes routes return $ App api

Ahora ha anidado su primer snaplet de Api. Sin embargo, aún no tiene rutas, por lo que no sabe si está funcionando o no. Agregar una ruta / api / status que siempre responde con un '200 ok' le permitirá ver la salida de este snaplet.

Los controladores de ruta rápida normalmente devuelven un tipo de Handler ()El Handler es un ejemplo de HandlerSnap , que proporciona acceso con estado para la solicitud y la respuesta HTTP.

Todas las solicitudes y modificaciones de respuesta tienen lugar dentro de una mónada Handler. Así que definiremos `respondOk :: Handler b Api () '

    -- src/api/Core.hs
    import           Snap.Core
    import qualified Data.ByteString.Char8 as B

    apiRoutes :: [(B.ByteString, Handler b Api ())]
    apiRoutes = [("status", method GET respondOk)]

    respondOk :: Handler b Api ()
    respondOk = modifyResponse $ setResponseCode 200

    apiInit :: SnapletInit b Api
    apiInit = makeSnaplet "api" "Core Api" Nothing $ do
        addRoutes apiRoutes
        return Api

Ahora mire las firmas de tipo para modifiedResponse y setResponseCode :

modifyResponse :: (MonadSnap m) => (Response -> Response) -> m ()
setResponseCode :: Int -> Response -> Response

Esto significa que setResponseCodetoma un número entero y devuelve una función de modificación de respuesta a la que se puede pasar modifyResponsemodifyResponserealiza la modificación de respuesta dentro de la función de mónada.

Ahora ejecuta el siguiente código:

    $ cabal run -- -p 9000
    $ curl -I -XGET "localhost:9000/api/status"

    HTTP/1.1 200 OK
    Server: Snap 0.9.4.6
    Date: ...
    Transfer-Encoding: chunked

Esto debería darte tu primera respuesta.

Un Todo Snaplet

Ahora que hemos visto cómo obtener una respuesta simple de un snaplet, aprendamos cómo hacer un snaplet de Todo dentro del snaplet de Api. Luego, aprenderemos cómo conectar ese snaplet de Todo a una base de datos, escribir controladores Get y Post para / api / todos, lo que le permite crear y buscar nuevos elementos de tareas pendientes.

Comenzaremos con un código repetitivo, que definirá nuestro snaplet, luego lo anidaremos dentro del snaplet API.

   -- new file: src/api/services/TodoService.hs

    {-# LANGUAGE OverloadedStrings #-}

    module Api.Services.TodoService where

    import Api.Types (Todo(Todo))
    import Control.Lens (makeLenses)
    import Snap.Core
    import Snap.Snaplet

    data TodoService = TodoService

    todoServiceInit :: SnapletInit b TodoService
    todoServiceInit = makeSnaplet "todos" "Todo Service" Nothing $ return TodoService

    -- src/api/Core.hs

    {-# LANGUAGE TemplateHaskell #-}

    import Control.Lens (makeLenses)
    import Api.Services.TodoService(TodoService(TodoService), todoServiceInit)
    -- ...

    data Api = Api { _todoService :: Snaplet TodoService }

    makeLenses ''Api
    -- ...

    apiInit :: SnapletInit b Api
    apiInit = makeSnaplet "api" "Core Api" Nothing $ do
      ts <- nestSnaplet "todos" todoService todoServiceInit
      addRoutes apiRoutes
      return $ Api ts

A continuación, anidaremos un PostgreSQL, proporcionado por snaplet-postgresql-simple , en TodoService. Esto proporciona a TodoService una conexión a la base de datos y hace posibles las consultas. Luego, va a importar Aeson , codificando sus respuestas en JSON usando ToJSON usando la instancia de ToJSON que definimos antes.

   -- src/api/services/TodoService.hs

    {-# LANGUAGE TemplateHaskell -#}
    {-# LANGUAGE FlexibleInstances -#}

    import Control.Lens (makeLenses)
    import Control.Monad.State.Class (get)
    import Data.Aeson (encode)
    import Snap.Snaplet.PostgresqlSimple
    import qualified Data.ByteString.Char8 as B
    -- ...

    data TodoService = TodoService { _pg :: Snaplet Postgres }

    makeLenses ''TodoService
    -- ...

    todoServiceInit :: SnapletInit b TodoService
    todoServiceInit = makeSnaplet "todos" "Todo Service" Nothing $ do
      pg <- nestSnaplet "pg" pg pgsInit
      return $ TodoService pg

    instance HasPostgres (Handler b TodoService) where
      getPostgresState = with pg get

Un poco de SQL configura la base de datos e inserta algunas líneas de datos de prueba:

CREATE DATABASE snaptutorial;
CREATE TABLE todos (id SERIAL, text TEXT);
INSERT INTO todos (text) VALUES ('First todo');
INSERT INTO todos (text) VALUES ('Second todo');

Finalmente, configurará el snaplet de postgres editando el siguiente archivo:

snaplets/api/snaplets/todos/snaplets/postgresql-simple/devel.cfg

Ahora está listo para ejecutar su primer GET en / api / todos. Recuperaremos todas las filas de la tabla de todos, las convertiremos en datos Todo y luego las serializaremos como JSON para obtener su primera respuesta.

En primer lugar, usted va a utilizar el query- función, lo que convierte una cadena SQL y devuelve una matriz monádica de datos que implementa la clase de tipos FromRow:
query_ :: (HasPostgres m, FromRow r) => Query -> m [r]

A continuación, usará writeLBS junto con la función de codificación para escribir una cadena JSON en el cuerpo de la respuesta:
writeLBS :: MonadSnap m => ByteString -> m ()

Esta función llama a la función modificarResponse mencionada anteriormente.

Luego, usará la función de ejecución (que es la versión de la consulta de la base de datos) para insertar los datos recopilados de getPostParam en la base de datos:

todoRoutes :: [(B.ByteString, Handler b TodoService ())]
todoRoutes = [("/", method GET getTodos)
,("/", method POST createTodo)]

createTodo :: Handler b TodoService ()
createTodo = do
todoTextParam <- getPostParam "text"
newTodo <- execute "INSERT INTO todos (text) VALUES (?)" (Only todoTextParam)
modifyResponse $ setResponseCode 201

Aquí, la única es la versión de postgresql-simple de colecciones de valor único.

Aquí está la versión terminada:

$ cabal run -- -p 9000
$ curl -i -XPOST --data "text=Third todo" "localhost:9000/api/todos"

HTTP/1.1 201 Created
Server: Snap 0.9.4.6
Date: ...
Transfer-Encoding: chunked

$ psql snaptutorial
$ SELECT * FROM todos;

id | text
----+--------------
1 | First todo
2 | Second todo
3 | Third todo

Ahora tiene una API REST en funcionamiento escrita para Haskell y SNAP. Si desea saber más sobre el marco SNAP, puede leer la documentación de SNAP o visitar el #snapframework en freenode .

Diseñar una API REST en Rust

Ahora que hemos aprendido cómo configurar una API en Haskell, dirijamos nuestra atención a Rust . Ver cómo configurar una API en Rust le ayudará a tener una idea de qué lenguaje podría ser mejor para diseñar su API.

En primer lugar, vamos a cargar algunas cajas, que son las bibliotecas de Rust. Usaremos Rocket para crear la API y Diesel para manejar la base de datos. Diesel funciona con Postgres, MySQL y SQLite.

Defina sus dependencias

Antes de comenzar, definirás tus dependencias:

[dependencies]
rocket = "0.3.6"
rocket_codegen = "0.3.6"
diesel = { version = "1.0.0", features = ["postgres"] }
dotenv = "0.9.0"
r2d2-diesel = "1.0"
r2d2 = "0.8"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
[dependencies.rocket_contrib]
version = "*"
default-features = false
features = ["json"]

Notarás que se están cargando varias cajas. Ya hemos mencionado Rocket y Diesel. Rocket_codegen llama a macros, mientras que dotenv permite que se invoquen variables desde un archivo externo. R2d2 y r2d2-diesel se conectan a la base de datos usando diesel. Por último, pero no menos importante, serde, serde_derive y serde_json se utilizan para la serialización y deserialización de los datos enviados y recuperados de la API REST.

En este caso, se ha especificado que postgres solo incluya módulos de Postgres en la caja diesel. Si desea acceder a otra base de datos, o varias bases de datos, solo necesita especificarlas o eliminar la lista de características por completo.

Una nota final, para usar Rocket, debe usar la compilación nocturna de Rust, ya que usa características no incluidas en las compilaciones estables.

Accediendo a la base de datos con Diesel

Primero, comenzaremos configurando Diesel. Una vez que esté configurado, tendrá definido su esquema que usará para construir la aplicación.

Para configurar Diesel, comience por construir una CLI de Diesel. Si no sabe cómo hacer eso, aquí tiene una guía para comenzar con Diesel .

Usaremos Postgres, ya que no podrá acceder a todas las funciones de MySQL. PostGRES es rápido y fácil de configurar y le brindará todas las funciones que necesita para crear una base de datos.

Crear una tabla

Comience configurando database_url para conectarse a PostGRES o simplemente agréguelo al archivo .env.

echo
DATABASE_URL = postgres://postgres:password@localhost/rust-web-with-rocket > .env

Ahora ejecutará la configuración de diesel para crear una base de datos y una carpeta de migraciones vacía para usar más adelante.

Modelará personas que se pueden agregar, recuperar, modificar o eliminar de la base de datos. Necesitará una tabla para almacenarlos. Para ello, creará su primera migración.

    diesel migration generate create_people

Esto crea dos archivos nuevos que se almacenan en la carpeta de migraciones. Up.sql es para actualizar y es donde colocará el SQL para crear su tabla. Down.sql es para degradar, por lo que puede deshacer las actualizaciones si es necesario.

En este ejemplo, creará la tabla de personas.

CREATE TABLE people(
id SERIAL PRIMARY KEY,
first_name VARCHAR NOT NULL,
last_name VARCHAR NOT NULL,
age INT NOT NULL,
profession VARCHAR NOT NULL,
salary INT NOT NULL
)

Para deshacer la creación de la tabla, solo tienes que usar:

    DROP TABLE people 

Para ejecutar una migración, ejecute:
DROP TABLE people

Si necesita deshacer la migración, simplemente use:
diesel migration redo

Asignar a estructuras

Ahora que ha creado su tabla de personas, está listo para comenzar a agregar datos. Dado que Diesel es un ORM, necesitará traducirlo en algo que Rust pueda leer. Vas a usar una estructura para hacer eso.

use super::schema::people;
#[derive(Queryable, AsChangeset, Serialize, Deserialize)]
#[table_name = "people"]
pub struct Person {
pub id: i32,
pub first_name: String,
pub last_name: String,
pub age: i32,
pub profession: String,
pub salary: i32,
}

Vas a escribir una estructura que representará cada registro en la tabla de personas, también conocido como persona. Usted va a utilizar tres comandos especiales para Diesel - #[derive(Queryable)]#[derive(AsChangeSet)]#[table_name].

#[derive(Queryable)]genera el código que recuperará a una persona de la base de datos. #[derive(AsChangeSet)]le permite utilizar update.set en el futuro, si así lo desea. #[table_name = "people"]nombra la tabla, como Diesel interpreta el plural de Persona y Personas. Si estuviera usando otro nombre con un plural más común, este paso no sería necesario.

Las otras funciones se utilizan para permitir que los datos JSON interactúen con la API REST. #[derive(Serialize)]#[derive(Deserialize)]ambos vienen de la caja de serde. Profundizaremos más en estos comandos un poco más adelante.

Ahora va a crear un esquema, específicamente un esquema Rust usando la "tabla". comando que maneja el mapeo de Rust a la base de datos.

Ejecute el siguiente comando:
diesel print-schema > src/schema.rs

Esto genera el siguiente archivo:

table! {
people (id) {
id -> Int4,
first_name -> Varchar,
last_name -> Varchar,
age -> Int4,
profession -> Varchar,
salary -> Int4,
}
}

Ahora ejecutará las consultas SELECT y UPDATE, usando la estructura Person que creamos anteriormente. DELETE no requiere una estructura, ya que solo requiere la identificación del registro. También usará INSERT, pero de una manera diferente a la recomendada en la documentación de Diesel.

#[derive(Insertable)]
#[table_name = "people"]
struct InsertablePerson {
first_name: String,
last_name: String,
age: i32,
profession: String,
salary: i32,
}
impl InsertablePerson {
fn from_person(person: Person) -> InsertablePerson {
InsertablePerson {
first_name: person.first_name,
last_name: person.last_name,
age: person.age,
profession: person.profession,
salary: person.salary,
}
}
}

InsertablePersones casi idéntica a la estructura Person pero con una diferencia clave: no hay tabla de ID. La tabla de ID se genera automáticamente cuando usa Insertar, por lo que no es necesario.

Finalmente, #[derive(Insertable)]se agrega para generar el código para insertar un nuevo registro.

Ejecución de consultas

Ahora que sus tablas están creadas y las estructuras asignadas a ellas, las pondrá en acción.

Así es como implementa la API REST básica:

use diesel;
use diesel::prelude::*;
use schema::people;
use people::Person;
pub fn all(connection: &PgConnection) -> QueryResult> {
people::table.load::(&*connection)
}
pub fn get(id: i32, connection: &PgConnection) -> QueryResult {
people::table.find(id).get_result::(connection)
}
pub fn insert(person: Person, connection: &PgConnection) -> QueryResult {
diesel::insert_into(people::table)
. values(&InsertablePerson::from_person(person))
. get_result(connection)
}
pub fn update(id: i32, person: Person, connection: &PgConnection) -> QueryResult {
diesel::update(people::table.find(id))
. set(&person)
. get_result(connection)
}
pub fn delete(id: i32, connection: &PgConnection) -> QueryResult {
diesel::delete(people::table.find(id))
. execute(connection)
}

El diesel se utiliza para acceder insert_into, actualizar y eliminar funciones. diesel::prelude::*accede a una variedad de estructuras y módulos que son útiles para ejecutar Diesel. En este ejemplo, usamos PgConnectionQueryResultTambién incluimos schema :: people para poder acceder a la tabla People con Rust y ejecutar métodos en ella.

Veamos una de estas funciones más de cerca:

pub fn get(id: i32, connection: &PgConnection) -> QueryResult {
people::table.find(id).get_result::(connection)
}

En este ejemplo, QueryResultse devuelve el de cada función. Diesel regresa QueryResult<t>de cada método y es una abreviatura de Result<T, Error>debido a esta línea:

pub type QueryResult = Result;

El uso QueryResultnos permite ver si algo sale mal si la consulta falla por cualquier motivo. Si desea devolver un resultado de Persona directamente desde una función, esperaría registrar el error inmediatamente.

Como estamos usando Postgres, Pgconnectionse usa el comando. Hay otros comandos para diferentes bases de datos, como Mysqlconnection, por ejemplo.

Aquí hay otro ejemplo:

pub fn insert(person: Person, connection: &PgConnection) -> QueryResult {
diesel::insert_into(people::table)
.values(&InsertablePerson::from_person(person))
.get_result(connection)
}

Esto es un poco diferente a la función get anterior. En lugar de acceder a una entrada desde el people::table, se pasa a la insert_intofunción Diesel. Anteriormente, creamos la InsertablePersonvariable para recibir nuevos registros. Los valores de la tabla Person se recuperan mediante el from_personcomando. Get_resultpermite que se ejecute la sentencia.

Lanzamiento de cohete

En este punto, su base de datos debería estar en funcionamiento. Ahora solo necesitamos crear la API REST y vincularla al back-end. En Rocket, esto consiste en solicitudes entrantes y funciones de manejo que tratan con las solicitudes. Entonces tienes que crear las rutas y las funciones del controlador.

Funciones del controlador

Es un poco más fácil comenzar con los controladores y trabajar hacia atrás, para que sepa a qué rutas se asignan. Aquí están todos los controladores que necesitará para implementar los verbos HTTP GET, POST, PUT, DELETE:

use connection::DbConn;
use diesel::result::Error;
use std::env;
use people;
use people::Person;
use rocket::http::Status;
use rocket::response::{Failure, status};
use rocket_contrib::Json;
#[get("/")]
fn all(connection: DbConn) -> Result>, Failure> {
people::repository::all(&connection)
.map(|people| Json(people))
.map_err(|error| error_status(error))
}
fn error_status(error: Error) -> Failure {
Failure(match error {
Error::NotFound => Status::NotFound,
_ => Status::InternalServerError
#[get("/")]
}) }
fn get(id: i32, connection: DbConn) -> Result, Failure> {
people::repository::get(id, &connection)
.map(|person| Json(person))
}
.map_err(|error| error_status(error))
#[post("/", format = "application/json", data = "")]
fn post(person: Json, connection: DbConn) -> Result>, Failure> {
people::repository::insert(person.into_inner(), &connection)
.map(|person| person_created(person))
fn person_created(person: Person) -> status::Created> {
.map_err(|error| error_status(error)) }
let host = env::var("ROCKET_ADDRESS").expect("ROCKET_ADDRESS must be set");
let port = env::var("ROCKET_PORT").expect("ROCKET_PORT must be set");
status::Created(
format!("{host}:{port}/people/{id}", host = host, port = port, id = person.id).to_string(),
Some(Json(person)))
}
#[put("/", format = "application/json", data = "")]
fn put(id: i32, person: Json, connection: DbConn) -> Result, Failure> {
people::repository::update(id, person.into_inner(), &connection)
.map(|person| Json(person))
#[delete("/")]
.map_err(|error| error_status(error)) }
fn delete(id: i32, connection: DbConn) -> Result {
match people::repository::get(id, &connection) {
Ok(_) => people::repository::delete(id, &connection)
.map(|_| status::NoContent)
.map_err(|error| error_status(error)),
Err(error) => Err(error_status(error))
}
}

Cada una de estas funciones define un verbo REST y la ruta necesaria para llegar allí. Aún falta parte de la ruta, ya que se definirán cuando se creen las rutas.

Suponga que los métodos del controlador son localhost: 8000 / people hasta que entremos en el enrutamiento.

Este es uno de los manejadores más fáciles:

#[get("/")]
fn all(connection: DbConn) -> Result>, Failure> {
people::repository::all(&connection)
.map(|people| Json(people))
.map_err(|error| error_status(error))
}
fn error_status(error: Error) -> Failure {
Failure(match error {
Error::NotFound => Status::NotFound,
_ => Status::InternalServerError
}
})

Para acceder a Curl para esta función, use:
curl localhost:8000/people

Ahora veamos el controlador PUT:

#[put("/", format = "application/json", data = "")]
fn put(id: i32, person: Json, connection: DbConn) -> Result, Failure> {
people::repository::update(id, person.into_inner(), &connection)
.map(|person| Json(person))
.map_err(|error| error_status(error))
}

La diferencia entre esta función y el ejemplo anterior de ALL son las variables id y person. </id>representa la variable id. Data = "<person">representa la solicitud que asigna la variable de persona a los argumentos de la función. La propiedad de formato especifica la forma que tomará el cuerpo de la solicitud de contenido, en este caso JSON, debido a la extensión Json<Person>.

Estamos usando serde nuevamente para recuperar JSON<body>del cuerpo de la solicitud.

Vamos a usar into_inner()para llamar al contenido de Person. También usaremos la actualización, que mapeará el error o el resultado en la variable Result. Como también estamos usando error_status, se producirá un error si un registro no coincide con una ID existente.

Si desea insertar un nuevo registro, debe usar el error :: no encontrado y usar un código de llamada similar al que se usa en la función Publicar.

En esa nota, la función Publicar se ve así:

#[post("/", format = "application/json", data = "")]
fn post(person: Json, connection: DbConn) -> Result>, Failure> {
people::repository::insert(person.into_inner(), &connection)
.map(|person| person_created(person))
.map_err(|error| error_status(error))
}
fn person_created(person: Person) -> status::Created> {
status::Created(
format!("{host}:{port}/people/{id}", host = host(), port = port(), id = person.id).to_string(),
Some(Json(person)))
}
fn host() -> String {
env::var("ROCKET_ADDRESS").expect("ROCKET_ADDRESS must be set")
}
fn port() -> String {
env::var("ROCKET_PORT").expect("ROCKET_PORT must be set")
}

Esta función usa piezas similares al identificador PUT que ya hemos discutido. La principal diferencia es que POST devolverá 201 Creado en lugar de 200 OK. Para producir un resultado diferente, la variable Result debe usar el status::Createdidentificador en lugar de Json<person>Esto es lo que causa el código de estado 201.

Para hacer la estructura status :: created, el registro creado y la ruta para recuperarlo deben pasarse al constructor usando la solicitud Get.

Enrutamiento

Todos los manipuladores están configurados. Ahora necesitamos enrutarlos a las diferentes funciones. Cada uno de los identificadores de esta función está relacionado con personas, por lo que todos se asignarán a /people.

use people;
use rocket;
use connection;
pub fn create_routes() {
rocket::ignite()
.manage(connection::init_pool())
.mount("/people",
routes![people::handler::all,
people::handler::get,
people::handler::post,
people::handler::put,
people::handler::delete],
).launch();
}

Create_routeses llamado por la función principal para que todo comience. Ignite crea una nueva versión de Rocket. Las funciones del controlador se cargan en una ruta de solicitud base de / people, ¡definiéndolas todas dentro de las rutas! Launch ejecuta la aplicación.

Configuraciones

Anteriormente, creamos variables de entorno para recuperar el puerto y el host del servidor en ejecución. A continuación, se explica cómo cambiar el puerto y el host del servidor en ejecución. Hay varias formas de hacerlo. Puede crear un archivo .env o un archivo rocket.toml.

Al usar archivos .env, la mayoría de los valores se formatean como ROCKET_{PARAM}PARAMes la variable que está intentando definir. {Address}representa el host mientras que {port}representa el puerto.

El archivo .env se vería así:

ROCKET_ADDRESS=localhost
ROCKET_PORT=8000

Si quisiera usar Rocket.toml en su lugar, podría verse así:

[development]
address = "localhost"
port = 8000

Si no incluye ninguno de estos, Rocket volverá a su configuración predeterminada.

Si desea obtener más información sobre cómo configurar Rocket, consulte la documentación de Rocket .

Último paso: crear el método principal

El paso final es crear el método principal para que se pueda ejecutar la aplicación.

#![feature(plugin, decl_macro, custom_derive)]
#![plugin(rocket_codegen)]
#[macro_use]
extern crate diesel;
extern crate dotenv;
extern crate r2d2;
extern crate r2d2_diesel;
extern crate rocket;
extern crate rocket_contrib;
#[macro_use]
extern crate serde_derive;
use dotenv::dotenv;
mod people;
mod schema;
mod connection;
fn main() {
dotenv().ok();
people::router::create_routes();
}

En este caso, lo único que hace Main es cargar las variables de entorno e iniciar Rocket llamando create_routesEl resto del código solo carga un montón de cajas para que no estén esparcidas por todo el código.

Para ver el código completo, puede consultar el GitHub de LankyDan .

Conclusión: Rust vs. Haskell: ¿Qué idioma es mejor para crear API?

Como dijimos al principio, saber qué lenguaje de programación se adapta mejor a sus necesidades no es solo seguir una receta o fórmula paso a paso. Depende de numerosas variables, incluida su competencia técnica y en qué está trabajando.

Dicho esto, hay algunas razones por las que Rust tiene algunas ventajas sobre Haskell para crear API, sobre todo su popularidad. Rust ha sido tendencia en los últimos años, por lo que hay un montón de bibliotecas y marcos útiles, sin mencionar una comunidad vibrante para ayudarlo a responder cualquier pregunta que pueda encontrar.

En segundo lugar, Rust también es preferible cuando el tamaño, la velocidad y la seguridad son importantes, que es la mayoría de las veces, en este punto de la evolución de la Web.

Haskell definitivamente requiere una comprensión más técnica. Existen ciertas ventajas en el uso de la programación funcional para construir API. La principal ventaja de utilizar Haskell para el diseño de su API es su utilidad en la creación rápida de prototipos. El código que escriba mientras construye su prototipo aún debería poder usarse en su producto oficial.

Sin embargo, Haskell requiere marcos adicionales para conectarse fácilmente a los servicios web, mientras que Rust parece interactuar un poco más naturalmente con los servicios web.

A menos que sea un diseñador de API experimentado, o esté tratando de llevar una aplicación al mercado lo más rápido posible, Rust tiene una ligera ventaja sobre Haskell para el diseño de API, en nuestra opinión.

Publicar un comentario

0 Comentarios