Rust是目前最受关注的语言之一。 虽然该语言在去年五月刚刚达到1.0,但是它在稳定性和功能方面Swift发展。 由于这种快速而稳定的增长,Rust确实引起了编程界许多人的关注。 但是,是什么使Rust如此吸引人? 这值得我们花费时间和精力吗?
使Rust真正令我兴奋的是,它开始被人们称为C / C ++替代品。 我个人从来没有真正关心过C和C ++的设计,但是Rust似乎解决了许多我不喜欢这些语言的问题。 更重要的是,它使我再次对低级系统编程感到兴奋!
作为Rubyist,我对诸如Helix这样的项目非常感兴趣,这些项目允许开发人员使用Rust作为扩展Ruby库(而不是C)功能的手段。 随着Helix的孕育和成熟,Rust很可能在不久的将来成为基于Ruby的堆栈的重要组成部分。
Rust社区中发生了很多值得一提的事情。 但是,尽管我认为Rust的未来令人兴奋,但重要的是要了解Rust在当今的应用方式 。
我之前写过这样的想法,即在您的堆栈中添加相当新的语言或框架可能会带来相当大的风险。 在较年轻的语言中,突破性变化往往发生得更多,Rust也不例外。 尽管Dropbox和Mozilla等公司在生产中都有大量基于Rust的项目,但我仍然相信,与他们相比,让我们承担较小的风险是明智的。
这就是微服务在您的体系结构中具有强大优势的地方。 它们使您能够冒着Rust等新事物的风险,并以相当低的成本将其实施到您的生态系统中。 同样,这并不能消除使用较年轻语言的风险和风险。 但是,它无疑使您可以承担较小剂量的风险。
我想探索的想法是在Rust中创建一个小型微服务的感觉。 有多难? 可能吗 我需要什么工具? 这个想法伴随着一系列健康的问题。
所以这是我们要做的事情:我们将假设我们有一个具有基本API的博客应用程序。 尽管听起来有些愚蠢,但我们将创建一个使用此API的Rust服务,并将每个博客帖子存储到数据库中。 假设我们有一个看起来像这样的Blog API项目,让我们利用Rust来创建一些很棒的东西!
培养锈
在开始任何这些操作之前,我们需要找出将Rust引入我们的编程环境的最佳方法。
有两种非常简单的方法来设置Rust。 但是,我个人使用RustUp来处理Rust版本。 这很像Ruby的RVM或Node的NVM。
RustUp的出色之处在于它能够轻松在Rust的两个发行渠道之间切换。 对于那些不知道的人,Rust本质上有两种语言实例。 一个实例(或通道)被称为“稳定”,其中包含稳定的功能。 另一个称为Nightly,它包含边缘功能。
对于我们今天要构建的内容,我将使用Nightly。 这似乎是一个奇怪的选择,因为我们正在寻求创建更“稳定”的东西。 但是,对于Rust来说,对未来进行规划会比较明智,因为我们知道许多这些功能最终都将进入稳定的Rust。
要在RustUp中进行设置:
rustup install stable
rustup install nightly
rustup default nightly
这应该将Nightly安装在我们的钥匙串中,并使其成为我们创建的任何新项目的默认Rust源。 如果您想使用Stable,请不要担心。 您随时可以随意切换回它!
现在通过Cargo(Rust的本地包管理器)创建Rust项目:
cargo new rustweb
这应该在当前目录中创建一个非常简单的Rust项目。 如果您打开该项目,则可以看到它包含一个名为Cargo.toml
的文件。 这是我们对文件的依赖关系存储的地方。
Cargo.toml
与我们在NPM的package.json
或Rails的Gemfile
看到的非常相似。 该文件列出了项目依赖项,而我们出色的工具可帮助我们找出其余部分。
用柴油生Rust
一群很棒的软件开发人员最近创建了一个称为Diesel的Rust框架。 柴油很有趣,因为它从Rails的Active Record中汲取了许多ORM设计灵感。 基于我在Rails中的经验,我发现Diesel确实很有吸引力并且易于使用。
Diesel需要一些额外的设置,但它使我们可以相对轻松地针对自己选择的数据库查询数据。 它还将帮助我们稳定地构建微服务。 让我们来看看。
设置柴油
我们将密切关注Diesel已发布的教程 。 但是,我们将在中途偏离它的道路来创建我们的服务。 如果我说的话让您感到困惑,请参阅其指南以获取清晰的信息。
要将Diesel和其他依赖项引入到Rust项目中,我们需要更新Cargo.toml
以显示以下内容:
Cargo.toml
[dependencies]
diesel = "0.8.0"
diesel_codegen = { version = "0.8.0", features = ["postgres"] }
dotenv = "0.8.0"
hyper = { version = "0.9", default-features = false, features = ["security-framework"] }
rustc-serialize = "0.3.19"
要将这些库放入我们的项目,请运行cargo build
。 这不仅会获取库,还会针对它们构建我们的应用程序实例。 这对于确定哪些依赖项对您的代码库有效,而对您的代码库无效则特别有用。
现在我们已经将Diesel的库集成到我们的Rust应用程序中,但我们仍然需要一种通过命令行控制它的方法。 为此,我们将要运行以下命令来安装diesel_cli
: cargo install diesel_cli
。
接下来,我们将告诉Diesel在哪里寻找我们的数据库。 在我们的案例中,我们正在使用自己的新数据库创建微服务。 对于我们的应用程序,我建议使用Postgres 。 但是,如果您需要其他内容,Diesel可以处理各种数据库选项。
我们将其数据库命名为: rustweb
。
echo DATABASE_URL=postgres://username:password@localhost/rustweb > .env
为了完成设置,我们将运行diesel setup
以完成工作。 这应该将我们的项目和环境配置为与Diesel框架一起正常工作。 在执行一些简单的命令之后,我们就可以开始绘制数据库的基础了。
制作我们的数据库
由于我们的应用程序是微服务,因此我们假设我们的项目依赖于其他类型的应用程序。 无论该应用程序是巨型独石还是一系列较小的微服务,我们都需要设计该微服务,以便它可以转换和存储其他来源的数据。 有效的微服务就像可互换的部分一样,我们的应用程序需要反映这种模块化。
我们将通过构建一个posts
表来开始此过程,该表将存储我们通过外部API使用的博客文章。 为了在Diesel中进行构建,我们将输入diesel migration generate create_posts
。
生成器将创建一个up.sql
和一个down.sql
文件,这将帮助我们构建数据库。 我不确定您编写纯SQL的舒适程度如何,但是Diesel是开始学习的好地方。
在up.sql
迁移中,我们将编写逻辑以在数据库中创建实际的posts
表。 给定一个抽象的Post
模型,它应该看起来像这样。
Post
- id (int)
- title (string)
- body (text / string)
我们将按照以下方式编写SQL迁移:
up.sql
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title VARCHAR NOT NULL,
body TEXT NOT NULL
)
在继续之前,让我们谈谈Rust的原始数据结构。
Rust使用的底层数据类型与C和C ++非常相似。 对于那些处理Ruby或JavaScript之类的人来说,这似乎有些陌生。 但是,学习如何处理不同的数据原语和结构当然不会造成伤害。 Rust使您可以更好地控制要处理的数据。 但是,它确实需要更多步骤才能正确处理所有内容。
我们的down.sql
看起来没有那么吓人,因为它的工作非常简单:删除posts
表。
down.sql
DROP TABLE posts
为了在数据库中实际创建表,我们需要运行diesel migration run
。
而已! 现在,我们有了所需的数据库。 现在,我们有了基本的数据库结构,让我们处理Diesel应用程序的内部逻辑。
建立库逻辑
还记得Cargo生成的其余文件吗? 我们将从src/lib.rs
开始。 这是我们的库文件,我们将使用它来将所有单个部分连接在一起。 将其视为整个应用程序的主要方法。
首先,我们需要编写一些内容以将我们连接到刚刚创建的数据库。 我们还将要构建我们的模型并导入我们的模式。 我们将一口气完成所有这些工作:
src/lib.rs
#![feature(proc_macro)]
#[macro_use] extern crate diesel;
#[macro_use] extern crate diesel_codegen;
extern crate dotenv;
pub mod schema;
pub mod models;
use diesel::prelude::*;
use diesel::pg::PgConnection;
use dotenv::dotenv;
use std::env;
use self::models::{Post, NewPost};
pub fn establish_connection() -> PgConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
PgConnection::establish(&database_url)
.expect(&format!("Error connecting to {}", database_url))
}
pub fn create_post(conn: &PgConnection, title: &str, body: &str) -> Post {
use schema::posts;
let new_post = NewPost {
title: title,
body: body,
};
diesel::insert(≠w_post).into(posts::table)
.get_result(conn)
.expect("Error saving new post")
}
到目前为止,我们拥有的是一大堆标题内容和两个函数。 似乎仅需几种方法就可以完成很多设置。
但是,请将其视为我们应用程序的基础。 我们正在做的所有事情都将贯穿于此。 这很重要,因为它将整个事情联系在一起。
它的值得指出的是怎么establish_connection
方法只需要在数据库URL通过.env
我们创建并试图与它连接的Postgres。 很简单!
如果您仔细查看具有以下内容的部分:
pub mod schema;
pub mod models;
您开始怀疑架构和模型部分的实际位置。 好吧,我们将要编写它们!
src/models.rs
use schema::posts;
#[derive(Queryable)]
pub struct Post {
pub id: i32,
pub title: String,
pub body: String
}
#[derive(Insertable)]
#[table_name="posts"]
pub struct NewPost<'a> {
pub title: &'a str,
pub body: &'a str,
}
这将创建一个可查询的Post
对象和一个可插入的NewPost
对象。 我们将串联使用这些数据,以针对我们的数据库插入和查询信息。 总体来说,这是一个非常简单的文件。 对schema.rs
文件也可以这样说:
src/schema.rs
infer_schema!("dotenv:DATABASE_URL");
该文件所做的只是从您先前创建的.env
文件中获取数据库架构,其中包含数据库URL。 这为我们与数据库交互提供了至关重要的联系。 这是Rust和Diesel多么简单的简短示例!
回到我们的lib.rs
文件,我们有create_post
函数。
pub fn create_post(conn: &PgConnection, title: &str, body: &str) -> Post {
use schema::posts;
let new_post = NewPost {
title: title,
body: body,
};
diesel::insert(≠w_post).into(posts::table)
.get_result(conn)
.expect("Error saving new post")
}
create_post
很有趣,因为它接受Postgres连接,title属性和body属性,并NewPost
创建一个NewPost
对象。 我们在这里使用架构来定义在何处插入NewPost
对象。
编写可执行脚本
现在,我们已经建立了坚实的基础,让我们编写直接与我们的API交互的Rust代码。
Rust库项目的工作方式是在/bin/
文件夹中创建可执行文件。 然后,我们从库中获取代码片段,并使用它们来执行任务。 为了演示,我们将创建以下内容:
get_posts_from_source.rs
extern crate rustweb;
extern crate diesel;
extern crate hyper;
extern crate rustc_serialize;
use self::rustweb::*;
use hyper::{Client};
use std::io::Read;
use rustc_serialize::json;
use std::env::args;
// Automatically generates traits to the struct
#[derive(RustcDecodable, RustcEncodable)]
pub struct PostSerializer {
id: u8,
title: String,
body: String,
}
fn main() {
let start_s = args().nth(1).expect("Please provide a min id");
let start : i32 = match start_s.parse() {
Ok(n) => {
n
},
Err(_) => {
println!("error: first argument not an integer");
return;
},
};
let stop_s = args().nth(2).expect("Please provide a max id");
let stop : i32 = match stop_s.parse() {
Ok(n) => {
n
},
Err(_) => {
println!("error: second argument not an integer");
return;
},
};
for x in start..stop {
let url = format!("http://localhost:3000/api/v1/posts/{}", x);
let response = get_content(&url).unwrap();
let decoded: PostSerializer = json::decode(&response).unwrap();
create_post_from_object(&decoded);
}
}
fn get_content(url: &str) -> hyper::Result {
let client = Client::new();
let mut response = try!(client.get(url).send());
let mut buffer = String::new();
try!(response.read_to_string(μt buffer));
Ok(buffer)
}
fn create_post_from_object(post: &PostSerializer) {
let connection = establish_connection();
println!("==========================================================");
println!("Title: {}", post.title);
println!("==========================================================\n");
println!("{}\n", post.body);
create_post(&connection, &post.title, &post.body);
}
ew。 这要花很多钱。让我们从标头和序列化器模型开始将其分解为几部分:
extern crate rustweb;
extern crate diesel;
extern crate hyper;
extern crate rustc_serialize;
use self::rutweb::*;
use hyper::{Client};
use std::io::Read;
use rustc_serialize::json;
use std::env::args;
// Automatically generates traits to the struct
#[derive(RustcDecodable, RustcEncodable)]
pub struct PostSerializer {
id: u8,
title: String,
body: String,
}
这段代码有趣的是,我们正在做很多以前见过的事情。 我们正在导入我们的项目和Diesel。 但是,现在还有其他一些人加入该党。 该文件正在使用Rust的Hyper库和Rust的序列化器功能。
这些额外的库使我们可以从服务器请求并序列化JSON响应。 Hyper完成请求。 序列化程序执行序列化。 很简单。
接下来,我们将看一下文件get_content
和create_post_from_object
:
fn get_content(url: &str) -> hyper::Result {
let client = Client::new();
let mut response = try!(client.get(url).send());
let mut buffer = String::new();
try!(response.read_to_string(μt buffer));
Ok(buffer)
}
get_content
使用Hyper库从我们的本地主机URL中提取数据。 它读取响应并将其转换为Rust可以理解的字符串。 如果所有这些都成功,它将返回API Post对象的String JSON表示形式。
fn create_post_from_object(post: &PostSerializer) {
let connection = establish_connection();
println!("==========================================================");
println!("Title: {}", post.title);
println!("==========================================================\n");
println!("{}\n", post.body);
create_post(&connection, &post.title, &post.body);
}
create_post_from_object
使用序列化的Post
对象并建立与我们数据库的连接。 然后,它利用我们的create_post
函数在数据库中创建Post
对象!
最后,我们将看到main()
方法如何将所有这些功能联系在一起。
fn main() {
let start_s = args().nth(1).expect("Please provide a min id");
let start : i32 = match start_s.parse() {
Ok(n) => {
n
},
Err(_) => {
println!("error: first argument not an integer");
return;
},
};
let stop_s = args().nth(2).expect("Please provide a max id");
let stop : i32 = match stop_s.parse() {
Ok(n) => {
n
},
Err(_) => {
println!("error: second argument not an integer");
return;
},
};
for x in start..stop {
let url = format!("http://localhost:3000/api/v1/posts/{}", x);
let response = get_content(&url).unwrap();
let decoded: PostSerializer = json::decode(&response).unwrap();
create_post_from_object(&decoded);
}
}
我们将迭代X次,并在API的一系列记录中创建Post对象。 我们将利用上面刚刚创建的PostSerializer
将数据组织成Diesel可以使用的东西。
假设我们的API数据库中有10个帖子,则可以通过在控制台中键入以下内容来运行脚本:
cargo run --bin get_posts_from_source 1 10
您应该看到我们的代码正在运行,输出API发布数据并将其存储到我们的rustweb数据库中!
让我们从这里经历的所有事情解压缩。
包起来
我们已经阅读了许多材料,可能会觉得不知所措或过于复杂。 但是,我鼓励您使用它来帮助确定编写Rust微服务是否适合您。 可以将其视为草绘一个想法,希望它会变得非常有用。
如果您对将来我将如何应用这个想法感兴趣,请跟随GitHub上的项目! 我很乐意看到一些有关我的表现的意见和建议。
Rust可能需要一段时间才能掌握。 但是,这是一种探索和学习的有益语言。 也许在您的学习中,您可能会发现一些对您和您的组织有益的东西!
翻译自: https://www.javacodegeeks.com/2016/12/creating-expedient-microservices-rust-diesel.html