翻译进度
2
分块数量
3
参与人数

创建 REST API 层

这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。


 Create a REST API Layer

Since our GUI is going to run in the browser, we need something for the browser

to talk to. There are other choices we could make, but REST is the most sensible

choice we can make for this app in 2019. So that's what we're going to do!

Rocket is a web framework for Rust that will make it easy

for us to quickly write a Rust program to serve our API.

 Add Rocket to Cargo.toml

We need to add Rocket to our app. Make your Cargo.toml's dependencies section

look like:


{{#include ../ch7-mytodo/Cargo.toml:dependencies}}

Run cargo build to download and compile all the new dependencies.

 Create the Backend Binary

We'll write our backend in src/bin/backend.rs. Let's make a first rough pass:


{{#include ../ch7-mytodo/src/bin/backend-stub.rs}}

The first line enables a couple of features that Rocket's macros need. These are

experimental features, and this is part of why we need to build with Rust

nightly instead of stable.

Then we pull in Rocket's macros.

Then we've got the handler for our route. For the moment it just returns a

static string.

In our main we create a Rocket instance, mount our handler, and start it.

Now if you cargo run --bin backend it will compile everything and start our

backend listening on localhost port 8000. If you open your browser to

localhost:8000/tasks (or use curl) you will see "this is a response".

 Query the Database

So far we've got a pretty lame API. It just spits out a static string. It's cool

that we can create a functioning web server from so little code, but we want to

return dynamic data -- info pulled from the database.

We've basically already written this code -- it's nearly the same as the

subcommand in our CLI program:


{{#include ../ch7-mytodo/src/bin/backend.rs:tasks_get}}

Run this (cargo will rebuild the changes for you), and refresh your browser --

you should see the task titles we inserted earlier. Great! But also, not so

great -- we might want to add more data to the tasks in the future as our app

gets popular. Like done-ness, due dates, and priority.

Just printing a bunch of lines of output is going to be tedious and error-prone

for our frontend to parse.

We can make it easier for our frontend and our backend to communicate

with each other if we use a standard data serialization format for our API, and

likewise if we use some standard tools to do it.

That format will be JSON. There are lots of tools for dealing with JSON, and

luckily for us one of them is part of Rocket. The other tool we need is the

excellent serde framework for serializing and

deserializing data (in JSON and many other formats).

 Serializing to JSON

We need to add serde and JSON support from rocket_contrib to our

Cargo.toml:


{{#include ../ch7a-mytodo/Cargo.toml}}

And then we need to use rocket_contrib and serde in backend.rs:


{{#include ../ch7a-mytodo/src/bin/backend.rs:use}}

Let's think about how we want to format our response. We could just send back an

array of tasks, where each task is an object with a title key that has a

string value:


[

    { "title": "do the thing" },

    { "title": "get stuff done" }

]

One problem with this is that we have no uniform way to indicate an error. We'll

be better off if we're closer to complying with the [JSON API

spec](jsonapi.org), which requires an object at the top level, and a

data key at that level -- something like this:


{

    "data": [

        { "id": 1, "title": "do the thing" },

        { "id": 2, "title": "get stuff done" },

    ]

}

Note that this isn't strictly conforming with the JSON API spec because the

resource objects (the stuff in the data array) aren't formatted properly. But

for the sake of keeping this exposition simple we're going to settle for being

non-compliant for now -- see the exercises for an approach to getting this done

the right way.

Now that we know what we want to return, let's put together a Rust structure to

represent it in backend.rs:


{{#include ../ch7a-mytodo/src/bin/backend.rs:response}}

Here we're using the Serialize derive-macro from serde for our struct. This

makes it so that we can magically get JSON out of it. However, if we try to

build this we get an error:


error[E0277]: the trait bound `mytodo::db::models::Task: serde::ser::Serialize`

    is not satisfied

We can only Serialize a struct if all the things in the struct also implement

Serialize -- and Task doesn't do that... yet.

Can we fix it? YES WE CAN!

At the top of both lib.rs and backend.rs we need to enable serde macros:


{{#include ../ch7a-mytodo/src/lib.rs:serde}}

And then in db/models.rs we need to slap a Serialize on the Task struct:


{{#include ../ch7a-mytodo/src/db/models.rs:Task}}

Our handler function will use the Json type from rocket_contrib -- note

that this is a different type than serde's Json type! We need to add a use

declaration for it at the top of backend.rs:


{{#include ../ch7a-mytodo/src/bin/backend.rs:json}}

With all of that in place we can modify our handler function to push the tasks

we get back from query_task onto a response object, and then convert that to

Json on the way out:


{{#include ../ch7a-mytodo/src/bin/backend.rs:tasks_get}}

Run the backend, refresh your browser, and you should see json similar to the

sample above. (It won't be pretty-printed -- you can `curl --silent

localhost:8000/tasks/ | jq .` if you're into that.)

 REST API Layer Wrap-Up

We just built a functional REST API backend, and I don't know about you, but I

didn't even break a sweat. Of course, there are things we'd do differently in a

production app:

* testing, of both the unit and integration varieties

* documentation, from comments to docstrings to REST API user (developer) docs

* stricter conformance to JSON API

* API versioning

database connection pooling

  so that we don't have to do establish_connection for every request, which

  would be important under load

* make our response object use a parameterized type so that we can return

  different object types as the API grows new features

In the next chapter we will place the final layer -- a browser-based UI based on

the Seed framework. But first -- some exercises!

 REST API Layer Exercises

These exercises are more challenging and will require consulting more external

documentation than in the last chapter.

 Bring the API in conformance with the JSON API Spec

Specifically, this part:

 A resource object MUST contain at least the following top-level members:

 

 * id

 * type

 Exception: The id member is not required when the resource object originates at

 the client and represents a new resource to be created on the server.

 In addition, a resource object MAY contain any of these top-level members:

 * attributes: an attributes object representing some of the resource’s data.

To do this, create a new Task wrapper struct that contains the id, type, and

attributes, modify the JsonApiResponse struct to contain a vector of that

wrapper, and modify the loop around task_query to create and push this type

onto the response.

 Use Connection Pooling

Read the Rocket documentation on [database connection

pooling](rocket.rs/v0.4/guide/state/#databa...) and implement it in

backend.rs.

(译者注:以下为多余部分,我也不知为何会多,提交的原文本来没有的)
创建 REST API 层

chen0adapter 翻译于 3年前

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

贡献者:3
讨论数量: 0
发起讨论 只看当前版本


暂无话题~